首页 > 其他分享 >从零手撕一个网页版图形编辑器之坐标变换(3)

从零手撕一个网页版图形编辑器之坐标变换(3)

时间:2024-06-09 19:32:24浏览次数:27  
标签:scaling 网页 零手 世界坐标 画布 编辑器 视口 坐标 屏幕


本编辑器(土豆猫图形编辑器)社区版代码已开源,开源库地址:https://gitee.com/longhan13/lgxmap_community.git

本文暂时中断前面章节的代码框架讲解,先讲解一下本编辑器所使用的坐标变换方法及涉及的相关代码,是本编辑器基础的基础。本编辑器所使用的坐标系为右手坐标系,既X正向水平朝屏幕右边,Y轴正向垂直朝屏幕上方,Z轴正向垂直于屏幕指向屏幕外面。

在这里讲解坐标变换的时候没有像常见的计算机图形学书籍那样,一来就使用矩阵,个人认为这种讲法对于数学已经忘记差不多的人来说更难以理解,而是先用一种容易理解的方法来讲解屏幕坐标到世界坐标相互变换。鼠标绘图、鼠标拾取图形使用的是屏幕坐标到世界坐标的变换;图形在屏幕上显示,则使用的是世界坐标到屏幕坐标的变换。

        下面所讲的屏幕坐标都是以以H5 canvas画布左上角为原点,X轴朝东,Y轴朝南的坐标系,其X范围为(0,画布宽度s_width),Y范围为(0,画布高度s_height)

        当然本编辑器内部还是会使用到矩阵工具,用于图形任意状态下(如进行了旋转、缩放操作后)都可以进行鼠标拖拽进行拉伸缩放。

下面正式开始。

首先先看一张图:

图1 坐标变换

图中渐变填充的三角形表示世界坐标系下的一个物体。

1)、黑色矩形框分别表示世界坐标系范围,(w_orgX,w_orgY)为世界坐标系原点,w_width为世界范围宽度,w_height为时间范围高度;

2)、蓝色矩形表示画布屏幕坐标系范围,左上角(s_orgX,s_orgY)为原点,恒等于(0,0),s_width和s_height分别表示画布的屏幕尺寸宽度和高度

3)、紫色矩形框表示当前视口范围(既在此区域内的图形对象才会显示并且等比例投影到画布),视口范围下的参数都是位于屏幕坐标系下,(v_left,v_bottom)表示视口区域左下角,(v_right,v_top)表示视口区域右上角。

所谓的世界坐标到屏幕坐标的变换,就是要把视口范围内的图形投影到屏幕进行显示,世界坐标范围(可自定义,默认跟画布范围一样,只不过世界原点变成了屏幕右下角,Y轴朝上)在编辑器初始化就固定下来了,视口范围在随着我们缩放画布、拖动画布而变化,我们看到图形放大显示了实际就是视口范围变小了;拖动画布,实际就是视口范围的原点(v_left,v_bottom)变化了。

一、初始状态(图形不缩放、不拖动、视口范围完全等于世界范围)

图2 初始态坐标变换实例

   我们设定初始状态下画布的左下角跟世界坐标系原点重合(粗黑色框表示世界坐标系范围,细一点的紫色矩形框表示初始状态下视口区域),整个世界坐标范围正好完全在画布内显示,相当于我们把世界范围想象成一块可等比拉伸的橡皮条(长w_width,宽w_height),把它左下角(w_orgX,w_orgY)用于一个钉子钉在屏幕的左下角(0,s_height),这时候我们就得到两个压缩系数:

      projSX = w_width/s_width-------世界坐标X方向到屏幕X方向的压缩率

      projSY = w_height/s_height-------世界坐标Y方向到屏幕Y方向的压缩率

因为这两个压缩比例可能是不一样的,为了让我们的图形显示的时候不变形(如把世界坐标系下的一个正方形显示成了一个长方形),所以我们取大者作为整体投影缩放系数:

    projectScale = max(projSX,projSY)------世界坐标到屏幕坐标的投影缩放系数

这个值表示一个屏幕坐标单位对应于世界坐标的多少个单位。为了便于理解,我们把画布尺寸和世界坐标取成好理解的实际值来讲解,我们假设w_width = 1000,w_height=800,画布尺寸为(100x80),那么projectScale=10,假设我们在世界坐标系下有一根直线(0,0)到(0,100),那么这根直线在世界坐标系下长度为100,投影到画布上进行显示,它的长度就只有10个屏幕单位了。

 有些朋友可能会问, projectScale = max(projSX,projSY)为什么取大者而不是取小者呢?因为世界坐标到屏幕坐标是除的关系,这个系数越小,那得到的屏幕尺寸就会越大,当projSX和projSY 不相等时,就会有部分在视口范围内,但是屏幕上却显示不出来的图形,例如画布尺寸只有100x80,但是换算得到一个坐标点为(200,160),肯定就显示不出来了,而取大者就不会存在这个问题。

为了说明换算过程,我们看上图,假设WP1(500,400)为世界坐标上的一个点,那么投影到画布显示时它的屏幕坐标就是SP1(40,45),计算过程如下:

因为这时候视口原点跟世界原点重合,既(v_left,v_bottom)= (m_orgX,m_orgY),我们设世界坐标WP(wx,wy),投影后的屏幕坐标为(sx,sy),则:

sx = (wx - v_left)/projectScale = (wx - w_orgX)/projectScale = (500 - 100)/10 = 40

sy = s_height  - (wy - v_bottom)/projectScale = s_height - (wy - w_orgY)/projectScale  = 80 - (400- 50)/10 = 45------这里计算Y时,为什么要由画布高度s_height来减呢?通俗来讲,那是因为通过 (wy - v_bottom)/projectScale计算后得到值是相对于画布左下角点(0,s_height),既这个值越小它的Y值就越接近于画布的底部,越大就越接近画布顶部。如果用计算机图形学的矩阵来讲就是进行了投影变换,变换矩阵是:

图3 投影变换矩阵

二、拖动状态(图形不缩放,进行拖动,视口原点发生变动)

图4 无缩放拖动状态下坐标换算实例

以上图为例进行说明,我们假设视口区域发生了移动,视口原点(v_left,v_bottom)移动到了(200,150)。假设WP1(500,400)为世界坐标上的一个点,那么投影到画布显示时它的屏幕坐标就是SP1(30,55),计算过程如下:

设世界坐标WP(wx,wy),投影后的屏幕坐标为(sx,sy),则:

sx = (wx  - v_left)/projectScale = (500  - 200)/10 = 30

sy = s_height  - (wy - v_bottom)/projectScale = 80 - (400- 150)/10 = 55

二、复杂状态(图形进行了缩放,也进行了拖动,既视口原点发生变动,视口尺寸也发生变化)

图5 复杂显示状态下坐标换算实例

所谓缩放,就是视口区域与世界区域不一致,例如变大了或者变小了,举个例子来说,我们把窗户当作画布,窗户外面有座山,山上有一座庙,苗里还有个和尚在敲木鱼,当我要通过这个窗户完整看到整座山的时候,视口区域就是整座山的包围盒矩形,这时候我看到的庙肯定是很小的,几乎是一个点,庙里的和尚和木鱼也变成了一点,当我要想看清楚庙里甚至和尚和木鱼的时,我就得把视口区域变小,变成跟庙得包围盒矩形一样大,相当于有个超人抱着我的窗户,飞到庙的前面,从窗户看庙,刚好把庙包围住,这时候从视觉上看图形肯定就变大了。

        从这里开始,我们引入了另外一个缩放系数,图形全局缩放系数(后面还会涉及到单个图形如模具图形、矩形、椭圆等单个图形得局部缩放系数),我们命名此系数为:scaling,这个系数可以通过如下公式计算得到:

    scaling_x = w_width/(v_right - v_left)

    scaling_y = w_height/(v_top - v_bottom)

取小者,得scaling = min(scaling_x,scaling_y)。取小者得原因跟前面计算投影缩放系数一样,都是为了让视口内的图形都在画布内显示,但因为本系数用于世界坐标到屏幕坐标的换算中是乘的发关系,所以取小者。

实际应用中,我们很少会用这种方法来计算scaling(框选一个矩形区域然后放大显示的时候用到),而是对当前值乘以一个系数进行放大或缩小,例如滚轮缩放的时候,前滚放大scaling *=1.2,后滚缩小scaling /=1.2(1.2这个系数是随便定义的,实际编码中可自定义设置,想缩放速度快这个值就取大些)。

下面给出我们考虑了scaling后的完整的坐标换算公式,假设世界坐标点(wx,wy):

    let scale = scaling / projectScale;

    let sx = (wx - v_left) * scale;

    let sy = s_height -  (wy - v_bottom) * scale;

    let screenPt = {sx, sy };

screenPt就是世界坐标点(wx,wy)在画布上的投影坐标。

我们以上图中的实际点wp1(500,400)来说明上面公式的应用。

首先计算scaling,

scaling_x = w_width/(v_right - v_left) = 1000/(700 - 200) = 2

scaling_y = w_height/(v_top - v_bottom) = 800/(550 - 350) = 2

scaling = min(scaling_x ,scaling_y) = 2

let scale = scaling / projectScale = 2/10 = 0.2;

let sx = (wx - v_left) * scale = (500 - 200)*0.2 = 60;

let sy = s_height -  (wy - v_bottom) * scale = 80 - (400 - 150)*0.2 = 30;

let screenPt = {sx, sy } = {60,30};

以上就是世界坐标到画布屏幕坐标换算的推导过程,世界坐标到屏幕坐标的换算用于显示。而当通过鼠标动作绘制图形或者拾取图形时,则涉及就是这个变换的逆变换-屏幕坐标到世界坐标变换,这是二维图形,变换过程比较简单,所以就不再讲推导过程了,直接给给出公式,下面假设画布上某个点的屏幕坐标为(sx,sy):

    let scale = projectScale / scaling;

    let wx = sx* scale + v_left;

    let wy = (mapHgt - sy) * scale + v_top;

    let worldPt = {wx, wy };

worldPt就是画布上点(sx,sy)对应的世界坐标。

下面是代码截图,MapViewParam表示当前画布上各种跟图形显示相关的参数,如投影缩放系数、图形缩放系数、视口区域、世界坐标区域、画布屏幕尺寸等;CoordTRFUtil则是坐标换算工具,里面包含了本文讲到的两种换算,其他换算函数后面章节中会进行讲解。

PS------本人文章中所涉及的所有图,除了屏幕截图之外都是使用本编辑器绘制、编辑的。专业版已发布在本人个人网站上:https://fanghuatech.cn

标签:scaling,网页,零手,世界坐标,画布,编辑器,视口,坐标,屏幕
From: https://blog.csdn.net/longx13/article/details/139562497

相关文章

  • 从零手写实现 nginx-13-nginx.conf 配置例子解释 + nginx 配置文件要如何解析?
    前言大家好,我是老马。很高兴遇到你。我们为java开发者实现了java版本的nginxhttps://github.com/houbb/nginx4j如果你想知道servlet如何处理的,可以参考我的另一个项目:手写从零实现简易版tomcatminicat手写nginx系列如果你对nginx原理感兴趣,可以阅读:从零......
  • C++系统编程篇——linux编辑器vim
    Linux编辑器vim(1)vim常用模式命令/正常模式控制屏幕光标的移动,字符、字或行的删除,移动复制某区段及进入Insertmode下,或者到lastlinemode插入模式只有在Insertmode下,才可以做文字输入,按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。底行模式......
  • 企业网页制作
    随着互联网的普及,企业网站已成为企业展示自己形象、吸引潜在客户、开拓新市场的重要方式。而企业网页制作则是构建企业网站的基础工作,它的质量和效率对于企业网站的成败至关重要。首先,企业网页制作需要根据企业的特点和需求进行规划。在网页设计之前,企业需要明确自己的品牌形......
  • java springboot 网页时装购物系统在线网上平台网站程序源代码+论文
    !!!有需要的小伙伴可以通过文章末尾名片咨询我哦!!! ......
  • 欢迎使用Markdown编辑器
    欢迎使用Markdown编辑器你好!这是你第一次使用Markdown编辑器所展示的欢迎页。如果你想学习如何使用Markdown编辑器,可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。新的改变我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我......
  • 关于Vue开发中的网页路由
    引言Vue.js是一个用于构建用户界面的渐进式JavaScript框架。它设计得非常灵活,允许你以不同的方式将其集成到你的项目中,从简单的交互式页面到复杂的单页应用程序(SPA)。Vue.js的核心库只关注视图层,这使得它非常容易学习,并且与其他库或现有项目集成VueRoute是什么?VueRouter......
  • 从零手写实现 nginx-01-为什么不能有 java 版本的 nginx?
    前言大家好,我是老马。很高兴遇到你。作为一个java开发者,工作中一直在使用nginx。却发现一直停留在使用层面,无法深入理解。有一天我在想,为什么不能有一个java版本的nginx呢?一者是理解nginx的设计灵魂,再者java开发者用java语言的服务器不是更加自然吗。于是......
  • 从零手写实现 nginx-11-文件处理逻辑与 range 范围查询合并
    前言大家好,我是老马。很高兴遇到你。我们为java开发者实现了java版本的nginxhttps://github.com/houbb/nginx4j如果你想知道servlet如何处理的,可以参考我的另一个项目:手写从零实现简易版tomcatminicat手写nginx系列如果你对nginx原理感兴趣,可以阅读:从零......
  • 分享一个超强的网页自动化工具!写得快,跑得快,开发人员狂喜(带私活)
       「今天分享一个开源项目:可控制浏览器,也可收发数据包,可模拟键盘和鼠标的操作」背景做数据采集的同学应该知道,当我们采集要登录的网站时,不仅要分析数据包、JS源码,构造复杂的请求,还要应付验证码、JS混淆、签名参数等反爬手段,门槛较高,开发效率不高。然后使用浏览器,可以......
  • 网页设计与制作基础(HTML+CSS)
    HTML常用标签1、文档结构标签<!DOCTYPEhtml>:告诉浏览器文档是HTML5。<html>:HTML文档的根元素。<head>:包含文档的元数据,如标题、样式和脚本引用等。<title>:设置文档的标题,显示在浏览器的标签页上。<body>:包含文档的可见内容,如文本、图片、链接、列表、表格等。 <!DOCT......