图形编辑器开发:一些会用到的简单几何算法

大家好,我是前端西瓜哥。

开发图形编辑器,你会经常要解决一些算法问题。本文盘点一些我开发图形编辑器时遇到的简单几何算法问题。

矩形碰撞检测

判断两个矩形是否发生碰撞(或者说相交),即两个矩形有重合的区域。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cVfrKWlr-1689474500243)(https://fe-watermelon.oss-cn-shenzhen.aliyuncs.com/%E7%9F%A9%E5%BD%A2%E7%9B%B8%E4%BA%A4.gif)]

常见使用场景:

  1. 使用选择工具框选图形(框选策略除了相交,还可以用相交或其他方案);
  2. 遍历图形,通过判断视口矩形和图形包围盒的矩形碰撞,剔除掉视口外的图形渲染操作,提高性能。
export function isRectIntersect2(rect1: IBox2, rect2: IBox2) {return (rect1.minX <= rect2.maxX &&rect1.maxX >= rect2.minX &&rect1.minY <= rect2.maxY &&rect1.maxY >= rect2.minY);
}

关于 IBox2 为包围盒的接口签名:

interface IBox2 {minX: number;minY: number;maxX: number;maxY: number;
}

矩形包含检测

该算法用于判断矩形 1 是否包含矩形 2。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1iBS4e9A-1689474500244)(https://fe-watermelon.oss-cn-shenzhen.aliyuncs.com/%E7%9F%A9%E5%BD%A2%E5%8C%85%E5%90%AB.gif)]

常见使用场景:

  1. 使用选择工具框选图形(这次用的是包含策略);
function isRectContain2(rect1: IBox2, rect2: IBox2) {return (rect1.minX <= rect2.minX &&rect1.minY <= rect2.minY &&rect1.maxX >= rect2.maxX &&rect1.maxY >= rect2.maxY);
}

计算旋转后坐标

对图形旋转,是一个非常基础的功能。计算旋转后的点是很常见的需求。

常见使用场景:

  1. 计算包围盒旋转后的坐标,绘制缩放控制点;
  2. 计算光标位置是否落在一个旋转的矩形上,因为旋转的矩形并不是一个正交的矩形,计算出来后判断有点复杂。所以通常我们会将光标给予矩形的中点反过来旋转一下,然后判断点是否在矩形中。
const transformRotate = (x: number,y: number,radian: number,cx: number,cy: number,
) => {if (!radian) {return { x, y };}const cos = Math.cos(radian);const sin = Math.sin(radian);return {x: (x - cx) * cos - (y - cy) * sin + cx,y: (x - cx) * sin + (y - cy) * cos + cy,};
}

点是否在矩形中

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UFGSUYIJ-1689474500245)(https://fe-watermelon.oss-cn-shenzhen.aliyuncs.com/%E9%80%89%E4%B8%AD.gif)]

常见使用场景:

  1. 用于实现图形拾取,判断矩形图形或包围盒是否在光标位置上。
function isPointInRect(point: IPoint, rect: IRect) {return (point.x >= rect.x &&point.y >= rect.y &&point.x <= rect.x + rect.width &&point.y <= rect.y + rect.height);
}

多个矩形组成的大矩形

选中多个矩形时,要计算它们组成的大矩形,然后绘制出大选中框。

function getRectsBBox(...rects: IRect[]): IBox {if (rects.length === 0) {throw new Error('the count of rect can not be 0');}const minX = Math.min(...rects.map((rect) => rect.x));const minY = Math.min(...rects.map((rect) => rect.y));const maxX = Math.max(...rects.map((rect) => rect.x + rect.width));const maxY = Math.max(...rects.map((rect) => rect.y + rect.height));return {x: minX,y: minY,width: maxX - minX,height: maxY - minY,};
}

这里用的是另一种包围盒子的表达,所以多了一层转换。

interface IRect = {x: number;y: number;width: number;height: number;
}type IBox = IRect

计算向量夹角

通过旋转控制点旋转图形时,需要通过向量的点积公式来计算移动的夹角,去更新图形的旋转角度。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-I7Jpoyx9-1689474500245)(https://fe-watermelon.oss-cn-shenzhen.aliyuncs.com/%E6%97%8B%E8%BD%AC.gif)]

计算 [x - cx, y - cy][0, -1] 两个向量夹角的算法实现:

/*** 求向量到右侧轴(x正半轴)的夹角* 范围在 [0, Math.PI * 2)*/
export function calcVectorRadian(cx: number, cy: number, x: number, y: number) {const a = [x - cx, y - cy];const b = [0, -1];const dotProduct = a[0] * b[0] + a[1] * b[1];const d =Math.sqrt(a[0] * a[0] + a[1] * a[1]) * Math.sqrt(b[0] * b[0] + b[1] * b[1]);let radian = Math.acos(dotProduct / d);if (x < cx) {radian = Math.PI * 2 - radian;}return radian;
}

结尾

做图形编辑器,经常要和几何算法打交道,各种相交判断、居中计算、光标缩放、找最近的参照线等等。

这对算法能力有一定要求的,建议多去刷刷 leetcode。此外就是多画图分析。

在开发中,我们还要自己去分析需求,结合图形编辑器的具体实现,抽离出算法问题,并配合合适的数据结构,去解题。解法可能一次不是最优解, 但我们可以慢慢迭代,慢慢优化的。

虽然有点耗脑细胞,但最后把难题解决,还是非常有成就感。

我是前端西瓜哥,欢迎关注我,学习更多图形编辑器知识。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Kubernetes运维篇】RBAC认证授权详解(二)

文章目录 一、RBAC认证授权策略1、Role角色2、ClusterRole集群角色3、RoleBinding角色绑定和ClusterRoleBinding集群角色绑定 二、通过API接口授权访问K8S资源三、案例&#xff1a;常见授权策略1、常见的角色授权策略案例2、常见的角色绑定案例3、常见的ServiceAccount授权绑定…

数学专题训练2 组合计数

1. 硬币购物 4 种面值的硬币&#xff0c;第 i 种的面值是 C i C_i Ci​​。 n n n​ 次询问&#xff0c;每次询问给出每种硬币的数量 D i D_i Di​​ 和一个价格 S S S​&#xff0c;问付款方式。 n ≤ 1 0 3 , S ≤ 1 0 5 n\leq 10^3,S\leq 10^5 n≤103,S≤105​. 如果用…

ORCA优化器浅析——​MD Accessor的三级缓存

分析​MD Accessor对元数据的缓存能力 set client_min_messageslog; set optimizer to on; set optimizer_print_optimization_stats to on; --执行SQLoptimizer_print_optimization_stats GUC会打印处ORCA优化器优化流程中的各步骤的统计数据。分析打印日志如下&#xff0c;由…

C++核心编程之函数高级使用

目录 一、函数的默认参数 二、函数占位参数 三、函数重载 四、函数重载-注意事项 一、函数的默认参数 在C中&#xff0c;函数的形参列表中的形参是可以有默认值的 语法&#xff1a;返回值类型 函数名 &#xff08;参数默认值&#xff09;{} 示例1&#xff1a; #includ…

Windows10下ChatGLM2-6B模型本地化安装部署教程图解

随着人工智能技术的不断发展&#xff0c;自然语言处理模型在研究和应用领域备受瞩目。ChatGLM2-6B模型作为其中的一员&#xff0c;以其强大的聊天和问答能力备受关注&#xff0c;并且最突出的优点是性能出色且轻量化。然而&#xff0c;通过云GPU部署安装模型可能需要支付相应的…

在Vitis IDE中使用第三方库 libtiff 保存 tiff 文件

目的和思路 一个Vitis IDE 裸机项目&#xff0c;需要将视频帧无损地保存下来 由于每帧的像素数据是 16bit 1通道的 bayer 格式&#xff0c;满足这一需求的图像格式似乎只有 tiff 格式 开源的tiff 库是 libtiff&#xff0c;而在 Vitis IDE 裸机项目中要使用的话就需要交叉编译…

数据结构day3(2023.7.17)

一、Xmind整理&#xff1a; 二、课上练习&#xff1a; 练习1&#xff1a;时间复杂度 时间复杂度&#xff1a;只保留最高阶f(n)3*n^2n^2100nT(n)O(3*n^3n^2100n)O(3*n^3)O(n^3)1>O(1):常数阶int ta; 1ab; 1at; 1f(n)3T(n)O(3)O(3*n^0)O(n^0)O(1)2>O(n): 线性阶for…

Python 和 RabbitMQ 进行消息传递和处理

一、RabbitMQ 简介 RabbitMQ 是一个开源的消息代理软件&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff09;标准。它的官方客户端提供了多种编程语言的接口&#xff0c;包括 Python、Java 和 Ruby 等。它支持消息的持久化、多种交换机类型、消息通知机制、灵活…

Ubuntu 18.04 Docker 安装配置 Apollo 6.0

百度 Apollo 安装测试&#xff08;1&#xff09; Apollo 6.0 安装完全指南 在这一步出错&#xff1a; 进入到 Apollo 源码根目录&#xff0c;打开终端&#xff0c;执行下述命令以启动 Apollo Docker 开发容器 ./docker/scripts/dev_start.sh并没有成功启动 Apollo docker 开发…

gma 2.0.0a3 (2023.07.17) 更新日志

安装 gma 2.0.0a3 pip install gma2.0.0a3新增 1、为矢量要素&#xff08;Feature&#xff09;添加 【Difference】&#xff08;差集&#xff09;方法   取第一个矢量要素与第二个矢量要素的几何差集。  2、为矢量要素&#xff08;Feature&#xff09;添加几种几何形状测试…

LLaMA(Open and Efficient Foundation Language Models )论文解读(二)

此篇博客主题:LLAMA模型数据、训练时长、功耗及碳排放量 LLaMA: Open and Efficient Foundation Language Models paper https://arxiv.org/pdf/2302.13971v1.pdf 1 训练样本 Overall, our entire training dataset contains roughly 1.4T tokens after tokenization. For mo…

求根节点到叶节点数字之和

给你一个二叉树的根节点 root &#xff0c;树中每个节点都存放有一个 0 到 9 之间的数字。 每条从根节点到叶节点的路径都代表一个数字&#xff1a; 例如&#xff0c;从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。 计算从根节点到叶节点生成的 所有数字之和 。…