乐趣区

图形选择物质不易

这一章,咱们来说鼠标如何选择变换后的图形。
首先给大家举个栗子:在 2029 年末世之战的时候,终结者想干掉人类领袖大壮,可是大壮太强,而且其实力需要复杂运算才能知晓。所以终结者就想回到 1997 年,在大壮实力弱小、且已知的情况下将其干掉。这样根据物质不易法则,2029 年末世之战中的人类领袖大壮也就不会存在。

接下来给大家解密终结者穿梭时间的方法

终结者需要知道数据:

  • 1997 年大壮的初始属性,比如构成大壮轮廓的顶点集合;
  • 大壮从 1997 到 2029 的变换信息,比如其大壮移动了多少、旋转了多少、长大了多少。

根据物质不易法则:物质不变,空间不变;空间不变,时间不变。
将物质不易法则逆推,依旧成立:物质改变,空间改变;空间改变,时间改变。
所以终结者想要回到 1997 年,只要根据大壮的变换规则逆向变换自己的位置就可以回到 1997 年。

比如:

  • 从 1997 年到 2029 年,大壮沿 x 轴移动了 100,沿 y 轴移动了 200,旋转了 90 度,变大了 2 倍。
  • 终结者 (鼠标点位) 就要沿 x 轴移动了 -100,沿 y 轴移动了 -200,旋转 -90 度,点位到圆心点的距离缩小 2 倍。

注意:终结者的变换顺序要和大壮的变换顺序一致;终结者改变的只是点位,点没有尺寸,其点位变换本质是在目标对象所在的 canvas 画布的坐标系的位移。只有如此,当终结者穿梭到 1997 年的时候,才可以精准定位大壮。
图示:

接下来我们就在代码里走一下这个原理:
先画了一颗爱心,其所在的 canvas 画布坐标系在 x、y 方向分别位移了(300,400)

const poly=new Poly({position:new Vector2(300,400),
    stroke:true,
    close:true,
    crtPath:function(ctx){ctx.beginPath();
        ctx.moveTo(0,0);
        ctx.bezierCurveTo(-200,-50,-180,-300,0,-200);
        ctx.bezierCurveTo(180,-300,200,-50,0,0);
    }
});
poly.draw(ctx);

若我的鼠标想要选择这颗爱心,那它的位置就要基于爱心的变换信息反向变换:x、y 方向分别位移(-300,-400)。代码如下:

const mousePos=getMousePos(event);
poly.crtPath(ctx);
const [nx,ny]=[
    mousePos.x-poly.position.x,
    mousePos.y-poly.position.y
];
const bool=ctx.isPointInPath(nx,ny);

图形变换中的位移说完了,那它的旋转、缩放也是同样道理,就是让鼠标位置基于图形的变换信息反向变换。
下面我直接将所有变换的方法封装到了获取鼠标点位的方法里,即 getMousePos(event,poly),event 是事件,poly 是图形。
代码如下:

const getMousePos=function(event,obj=null){
    // 获取鼠标位置
    const {clientX,clientY}=event;
    // 获取 canvas 边界位置
    const {top,left}=canvas.getBoundingClientRect();
    // 计算鼠标在 canvas 中的位置
    const x=clientX-left;
    const y=clientY-top;
    const mousePos=new Vector2(x,y);
    if(obj){const {position,scale,rotation}=obj;
        mousePos.sub(position);
        mousePos.rotate(-rotation);
        mousePos.divide(scale);
    }
    return mousePos;
};

mousePos 是一个 Vector2 对象,其中封装了关于向量的常用方法。如:

export default class Vector2{constructor(x=0,y=0){
        this.x=x;
        this.y=y;
    }
    // 减法
    sub(v){
        this.x -= v.x;
        this.y -= v.y;
        return this;
    }
    // 基于原点旋转
    rotate(angle){const c = Math.cos( angle), s = Math.sin(angle);
        const {x,y}=this;
        this.x = x * c - y * s;
        this.y = x * s + y * c;
        return this;
    }
    // 向量除法
    divide (v) {
       this.x /= v.x;
       this.y /= v.y;
       return this;
    }
    //...
}

好啦,关于变换后的图形选择我们就说到这。
其实图形图形选择的方法是有很多的,下一章我再跟大家说一个图形选择的方法:图形选择 - 网格选择

注:物质不易是我从修仙小说上课看的,没有科学依据,只为辅助大家理解代码。

源码地址

退出移动版