前言

之所以会开设这个专栏, 是为了弥补部分程序员对代数几何学的短板(当然也是为了巩固我的数学基础), 同时在实用价值上, 代数几何学在编程界也起到了非常重要的推动作用, 比如我们看到的各种建模软件, 仿真&设计软件, 内部都涉及了很多数学原理, 在Web界, 我们比较熟悉的可视化图表, 在线设计软件Figma, 各式各样的可视化低代码产品, 都或多或少的应用了几何学原理, 所以要先让自己做出高价值的产品, 让自己的编程水平更进一步, 代数几何学知识是非常有必要的。

在《100+前端几何学应用案例》 专栏中, 我会和大家由浅入深地分享一些应用几何学知识实现的经典Web案例, 比如:

  • 游戏领域的边界问题(碰撞, 射击策略等)

  • 几何画板的实现方案

  • 常见的几种可视化图表实现方案

  • 骨骼动画实现原理

  • 从零封装一个图形库

等等, 每一个实战案例我都同步到 阿几里德编程实践 代码仓库中, 有兴趣的朋友可以参考学习。

接下来开始我们的第一篇分享——几何边界问题的编程实践

几个常见边界计算的例子和实现原理

image.png

这篇文章主要会介绍三种常见图形(矩形, 圆形, 三角形)的边界计算方案, 其中会应用一些几何学和代数知识, 相信大家会从中汲取到自己需要的知识, 并应用到自己的项目中。

在开始实现之前我们先做一些准备工作:

  1. 约定坐标体系(左上角为原点, x轴向右为正方向, y轴向下为正方向)

  2. 工程采用vite构建, 前端使用vue3作为开发语言(当然其他框架也是完全没问题的, 看个人喜好)

1. 计算鼠标指针是否在矩形内部

有了上面的坐标体系, 我们就来解决矩形边界问题。为了让大家更好的理解边界问题的价值, 我这里来举一个形象的例子:

image.png

比如说我们在玩射击游戏, 只有射中靶子才能得分, 如上图, 这里有涉及到靶的边界问题, 这里转换为矩形边界问题就是: 判断一个点 (x,y) 是否在矩形(ABCD)内部:

image.png

由上图我们很容易就可以想出一种方案, 即判断 x 是否在区间 [a0, a1] 之间, y 是否在 [b0, b1] 之间。

我们先来构造一个矩形, 这里为了保证矩形足够通用, 我写了一个生成矩形的函数:

const generateRectangleMeta = (startPos: [number, number], w: number, h: number) => {if (startPos &&Array.isArray(startPos) &&startPos.length >= 2 &&typeof w === "number" &&typeof h === "number") {const [a0, b0] = startPos;const a1 = a0 + w;const b1 = b0 + h;return {startPos,w,h,endPos: [a1, b1],pos: [[a0, b0],[a1, b0],[a1, b1],[a0, b1],],};} else {throw new Error("Please pass in the correct parameters");}
};

由上面的代码可知, 我们只需要传入矩形的左顶点坐标宽高, 即可生成一个矩形元数据集合, 包含了:

  • 左顶点坐标

  • 矩形的宽高数据

  • 右底点坐标

  • 矩形四个顶点的坐标集合

有了以上数据之后, 我们就可以画出一个任意位置的矩形。

下一步就是获取任意点的坐标, 为了方便演示, 这里以鼠标指针作为点(x, y), 我们再来构造一个画布:

image.png

我们以画布的左上角作为坐标原点(0,0), 来计算一下鼠标在画布中的相对位置, 这里我使用vue3hooks 来实现, 具体代码如下:

const cardOffset = ref({ x: 0, y: 0 });onMounted(() => {// 获取画布左上角距离页面左上角的距离const { offsetTop, offsetLeft } = document.querySelector("#boundCard") as HTMLElement;cardOffset.value.x = offsetLeft;cardOffset.value.y = offsetTop;
});const { x, y } = useMouse(window, (x, y) => {// 边界计算const { x: x0, y: y0 } = cardOffset.value;const dotX = x - x0;const dotY = y - y0;const { startPos, endPos } = rectangle;if (startPos[0] <= dotX &&endPos[0] >= dotX &&startPos[1] <= dotY &&endPos[1] >= dotY) {// 在矩形内console.log("inset");} else {// 在矩形外console.log("out");}
});

上述代码中 useMouse 是一个组合函数, 主要用来获取鼠标的位置, 实现如下:

// event.ts
import { onMounted, onUnmounted } from 'vue'export function useEventListener(target: HTMLElement | Window, event: keyof HTMLElementEventMap, callback: (this: HTMLElement, ev: any) => any) {onMounted(() => target.addEventListener(event, callback))onUnmounted(() => target.removeEventListener(event, callback))
}// mouse.ts
import { ref } from 'vue'
import { useEventListener } from './event'export function useMouse(target: HTMLElement | Window, callback?: (x: number, y:number) => void) {const x = ref(0)const y = ref(0)useEventListener(target, 'mousemove', (event) => {const { pageX, pageY } = event;x.value = pageXy.value = pageYcallback && callback(pageX, pageY)})return { x, y }
}

之所以要把获取鼠标坐标的方法单独提出来, 是为了更好的复用, 也符合 hooks 倡导的理念, 对vue3 hooks感兴趣的朋友也可以研究一下。

经过上述的步骤, 我们就实现了判断矩形边界的功能.

是不是有种实现了 csshover 的感觉呢? 通过以上方式, 我们可以轻松判断在画布中的任意点, 是否在矩形内部, 从而实现有意思的射击游戏。

当然我们探索的本质问题其实是: 判断一个点是否在指定形状的内部。我们有很多方式实现, 比如向量法, 面积法等, 对于简单的矩形, 圆形, 我们可以用精简的方式来判断, 对于比较复杂的如三角形, 我会在后面的内容中和大家详细分享实现方案。

2. 计算鼠标指针是否在圆内部

上面分享了判断一个点是否在矩形中的实现方案, 接下来我们继续探索圆形的边界问题。

image.png

我们都知道只要确定了圆的半径(R)圆心坐标(x0, y0), 就能在坐标系里画出一个圆。这里我们先写一个生成圆的函数:

const generateCircleMeta = (pos: [number, number], r: number = 1) => {if(pos && Array.isArray(pos) && pos.length) {const [x, y] = pos;return {startPos: [x - r, y - r],pos,r}}else {throw new Error("Please pass in the correct parameters");}
}

之所以要写这个函数是因为我们的目标对象是 dom, 这样方便确定 dom 的位置。(当然我们也可以用其他方式定义一个圆, 这里的方案只做参考)

同时由于圆的特殊性, 我们要判断一个点是否在圆内, 只需要判断这个点和圆心的直线距离是否大于半径(r)即可。

我们用 javascript 来实现一下:

const isOutCircle = ref(false);
// 生成圆形数据元
const circle = generateCircleMeta([200, 400], 100);useMouse(window, (x, y) => {// 边界计算const { x: x0, y: y0 } = cardOffset.value;const dotX = x - x0;const dotY = y - y0;const { pos, r } = circle;const r1 = Math.sqrt(Math.pow(dotX - pos[0], 2) + Math.pow(dotY - pos[1], 2));if (r >= r1) {console.log("inset");isOutCircle.value = false;} else {console.log("out");isOutCircle.value = true;}
});

由代码可知我们需要把几何中的坐标计算法转换为 javascript 支持的语法模式, 逻辑也很简单, 这里就不展开讨论了。

通过以上的实现, 我们就可以轻松计算任意矩形和圆形的边界问题了, 这也是我们工作中比较常见的计算场景, 接下来我们再来看一下如何计算三角形的边界。

3. 计算鼠标指针是否在三角形内部

image.png

要想解决这个问题, 我们需要先解决如何使用 HTMLDiv 画一个三角形。这里之所以不用 svg 或者 canvas 来画(虽然这两种方式画三角形会更简单), 主要是为了让大家更充分的感受几何学的魅力。也许有朋友会说了, 画个三角形不很方便吗? 用 css 或者 css背景渐变 都可以画出来, 但是通过上面的方式很难对三角形边界进行计算了, 我们需要知道三角形的三个顶点坐标, 所以这里我讲给大家介绍一种三角函数的方式, 来画任意的三角形。

3.1 从画一个线段开始

image.png

我们先来考虑一个简单的问题: 已知两个点的坐标 A(x0, y0)B(x1, y1), 如何用 dom 画一个线段AB

从图中我们可以分析出, 我们只要知道起点A, 线段AB的长度以及线段的角度a , 就能画出一个线段。 同时利用三角函数, 我们有以下的计算公式:

我们可以通过坐标来计算出角度和线段的长度, 对于web中的角度我们需要做一个基本的换算:

有了以上的知识铺垫, 我们来实现一下生成线段数据元的函数:

const generateLineMeta = (pos: [[number, number], [number, number]]) => {if (pos && Array.isArray(pos) && pos.length >= 2) {const [A, B] = pos;const dx = B[0] - A[0];const dy = B[1] - A[1];const l = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));const tanAB = dy / dx;const AB = (Math.atan(tanAB) * 180) / Math.PI;return {startPos: A,endPos: B,angle: AB,l,};} else {throw new Error("Please pass in the correct parameters");}
};

上面代码中之所以用 tan 来计算, 是为了简化我们的计算公式, 当然大家也可以尝试用反正玄函数来计算。 将生成的元数据应用到我们的 dom 上即可得到我们想要的线段:

image.png

线段实现了, 我们要想画三角形是不是就很方便了呢? 我们只需要用根据三角形的3个顶点坐标画出首尾相连的3条线段即可。

3.2 用 HTMLDiv 画一个三角形

我们只需要对上面的生成线段的函数稍加改造, 就可以实现生成三角形数据元的函数。(更确切的说是生成任意边的数据元的函数)

const generateLinesMeta = (pos: [number, number][], isLine: boolean = false) => {if (pos && Array.isArray(pos) && pos.length >= 2) {let i = 0,len = pos.length,result = [];while (i < len) {// 如果是线段, 则不添加闭合点if (isLine && !pos[i + 1]) {break;}let A = pos[i];let B = pos[i + 1 > len - 1 ? 0 : i + 1];if (A[0] > B[0]) {[B, A] = [A, B];}const dx = B[0] - A[0];const dy = B[1] - A[1];const l = Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));const tanAB = dy / dx;const AB = (Math.atan(tanAB) * 180) / Math.PI;result.push({startPos: A,endPos: B,angle: AB,l,});i++;}return result;} else {throw new Error("Please pass in the correct parameters");}
};

上述笔者实现的代码中, 添加了 isLine 参数,  这个参数可以控制我们绘制的图形是闭合多四边形还是折线集合

经过上面的实现, 我们终于用 HTML 画出了三角形, 接下来就是我们最后的冲刺了—— 判断空间内的点是否在三角形内部

image.png

在上面两个图形的边界计算中我们用特殊方法来计算出了任意一个点是否在其内部, 但是对于三角形, 以上方法可能都不适用了, 那我们怎么来实现它呢?

想想我们初中高中学的向量几何原理, 会不会有一些启发呢?

image.png

由上图可知, 我们是不是可以通过任意一点与三角形(S为该三角形的面积)三个顶点组成的三角形的面积(S1, S2, S3)来判断这个点是否在其内部呢? 如果点在三角形内部, 则会满足如下条件:

如果点在三角形S外部, 则满足如下条件:

所以说现在的问题就变成了求三角形面积的问题了。因为三角形的三个顶点坐标 (x1, y1), (x2, y2), (x3, y3) 是已知的,任意点的坐标 (x0, y0) 也是已知的, 我们可以根据向量叉积的计算方式来求出三角形的面积。

image.png

向量叉积得到的是一个垂直于向量AB, AC所在平面的向量, n代表和平面垂直的法向量。

我们可以直接用高中课本的结论来算三角形的面积, 如下:

$ S = Math.abs(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3) / 2 $

也可以用上面的三角函数来推导出上述的公式, 这里我就不一一和大家介绍了, 有了上面的结论, 我们就很容易算出一个点是否在三角形内部了。

文章的 demo 代码大家想参考也可以在如下地址学习参考: 趣谈前端仓库

同时如果大家有更好的方案, 也欢迎在仓库里反馈交流。

总结

几何学博大精深, 我们市面上看到的很多设计软件, 都应用了大量的几何学原理和算法, 本篇意在为大家展示其在 web 中的一个应用, 我们可以把上述的算法应用到实际工作中, 实现非常有意思的web 应用。同时我也会分享前端可视化低代码的最佳实践, 感兴趣的小伙伴也可以参考我的专栏 低代码可视化专栏.

最后

如果想学习更多H5游戏webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在《趣谈前端》加入我们的技术群一起学习讨论,共同探索前端的边界。

从零搭建全栈可视化大屏制作平台V6.Dooring

从零设计可视化大屏搭建引擎

Dooring可视化搭建平台数据源设计剖析

可视化搭建的一些思考和实践

基于Koa + React + TS从零开发全栈文档编辑器(进阶实战

点个在看你最好看

《前端图形学实战》几何学在前端边界计算中的应用和原理分析相关推荐

  1. php 实现自动加载更多,$.ajax+php实战教程之下拉时自动加载更多文章原理分析二...

    摘要: 继上一篇<$.ajax+php实战教程之下拉时自动加载更多文章原理分析>文章进行进一步讲解,完善之前的代码及引入ajax和php相关内容...... 上次留下的问题不知道看官们有没 ...

  2. html获取url后面的参数_Golang Gin 实战(四)| URL查询参数的获取和原理分析

    在 上一篇 Golang Gin 实战(三)| 路由参数 文章中,主要介绍了路由通配符.路由参数,让我们有了一种可以从URL路径中获取参数的方式,同时又不是重复的注册相似的路由. 这一篇,主要介绍查询 ...

  3. 前端三大构建工具 Webpack、Vite、Rollup 优劣势及原理分析

    在刚刚结束的 VueConf2021 中,除了 Vue 3.0 以外,另外一个亮点就是下一代构建工具 Vite 了. 在尤雨溪分享的[ Vue 3 生态进展和计划]的演讲中,尤大神还特意提到 Vite ...

  4. 前端实现可绘制的canvas画布_前端图形学基础(五)——Canvas状态管理

    点击右上角的关注,不定期前端干货分享!! 欢迎来到我的前端图形学系列文章: 前端图形学基础(一)--Canvas基础入门 前端图形学基础(二)--Canvas基础 前端图形学基础(三)--Canvas ...

  5. 拉钩网前端项目实战04

    拉钩网前端项目实战 拉钩网前端项目实战04 banner和content设计 一.banner部分设计 1.html部分 <div class="banner">< ...

  6. R语言相关性计算及使用ggcorrplot包相关性分析热力图可视化分析实战

    R语言相关性计算及使用ggcorrplot包相关性分析热力图可视化分析实战 目录 R语言相关性计算及使用ggcorrplot包相关性分析热力图可视化分析实战

  7. 【Mysql实战】使用存储过程和计算同比环比

    背景 同环比,是基本的数据分析方法.在各类调研表中屡见不鲜,如果人工向前追溯统计数据,可想而知工作量是非常大的. 标题复制10行,并且每行大于10个字符[源码解析]SpringBoot接口参数[Mys ...

  8. threejs添加立方体_前端图形学(三十)——从源码去看threejs中的光照模型

    欢迎来到[畅哥聊技术]前端图形学相关技术文章,更多精彩内容持续更新中,敬请关注. 上章节回顾 熟悉了threejs中内置的几何图形的渲染原理就是通过顶点渲染 传入自定义顶点渲染自定义的几何图形 本章目 ...

  9. 前端图片上坐标连线_前端图形学(十三)——弹跳运动的深入之傲娇的小球

    欢迎来到[畅哥聊技术]前端图形学相关技术文章,更多精彩内容持续更新中,敬请关注. 前面我们说到了小球的弹跳运动,通过一个方向的加速度和摩擦力去影响小球的动画,其目标点也是一个固定不变的,似乎有些单调. ...

最新文章

  1. zip压缩报错解决:zip warning: name not matched: xxx/xxx/xxx
  2. VRRP协议介绍--转
  3. 极简数据分析实操指南(上)
  4. dede php标签 禁用,DedeCMS Error:Tag disabled:php的解决办法
  5. 区块链 HyperLedger Fabric安装
  6. Promise第二篇:你需要记着的API
  7. windows下搭建Apache+Mysql+PHP开发环境
  8. vue element form 默认校验
  9. ~~spfa判断图中是否存在负环
  10. Git基本介绍(三大分区及核心内部构造)
  11. Nodejs实时通讯 在线聊天室(Socket.io)_收藏
  12. java和C#的相同之处笔记
  13. origin游戏服务器引擎
  14. Avalondock 第一步 创建停靠面板
  15. 修改Windows系统注册表并使其立即生效
  16. Python pgm解析和格式转换
  17. 内网搭建代理DNS使用内网域名代替ip地址
  18. 安卓系统监控任务管理器App推荐
  19. 微信小程序输出Object,Object
  20. python读取excel成绩表,并设置柱状图

热门文章

  1. 正则表达式口诀及常用正则 。正则贪婪与非贪婪模式
  2. java在底图上生成二维码以及文字
  3. Nginx 调整文件上传大小
  4. 读书有益——》你的人生需要一场整理
  5. ansible剧本规范目录结构
  6. 普通排水与实验室给排水的区别TENAISU
  7. request用法以及详解
  8. 精明的程序员——爱学习
  9. raft 线性一致性
  10. 收到银行短信你正在使用Android设备,手机收到扣费短信,无故被扣钱?小心自动订阅的大坑!...