【HTML5 Canvas】计算元件/显示对象经过Matrix变换后在上级/舞台上的bounds(边界矩形rect)

如上图所示,这样的一个简单矩形,边界矩形是(x:-28, y:-35, width:152, height:128),这是在这个元件/显示对象自己的坐标空间的范围。

那么把这个放到父元件(舞台)中,再做一定变换。如下图所示,白色区域就是舞台,蓝色矩形中的白色十字架标记,就是世界坐标的(0,0)点。蓝色矩形的原点和世界坐标的原点对应,也就是说蓝色矩阵的坐标为(0,0)。

在舞台这个世界坐标系中,边界区域又是什么呢?我们的目标就是计算下图中的红色区域。

其实算法,很简单,在放到舞台之前,在蓝色矩形自己的局部坐标系中,边界是(x:-28, y:-35, width:152, height:128)。

那么,蓝色矩形4个顶点原来的坐标是可以轻易找到的:(-28,-35)、(152-28,-35)、(152,128)、(-28,128-35)。

矩形旋转了-60度,其实这个变换,可以具体转化为一个Matrix矩阵变换。通过自己计算或者利用Flash等辅助,可以知道Matrix如下:

Matrix (a=0.4999847412109375, b=-0.865966796875, c=0.86602783203125, d=0.500030517578125, tx=-44.3, ty=6.8)

最后,计算出每个顶点经过Matrix变换后的新坐标,再通过这4个新坐标,算出上下左右的边框位置。

什么?怎么计算这个Matrix?好吧,先插播一下Matrix的计算。

首先,当然你得有一个Matrix类,可以参考任意语言的Matrix2D,其实都一样的。abcd和tx、ty,6个属性。

然后Matrix中实现rotate、translate等方法。

最后,简单2句,就可以算出:

        var matrix = new Matrix2D();
        matrix.rotate(-60*Math.PI/180);

回到正题,先把Matrix和rect的计算,封装一下:

    p._transformBounds = function (bounds, matrix) {
        if(bounds.x || bounds.y){
            matrix.append(1,0,0,1,bounds.x,bounds.y);
        }

        var lt = {x:0, y:0};
        var rt = {x:bounds.width, y:0};
        var lb = {x:0, y:bounds.height};
        var rb = {x:bounds.width, y:bounds.height};

        lt = transformPoint(matrix, lt);
        rt = transformPoint(matrix, rt);
        lb = transformPoint(matrix, lb);
        rb = transformPoint(matrix, rb);

        var minX = Math.min(lt.x, rt.x, lb.x, rb.x);
        var minY = Math.min(lt.y, rt.y, lb.y, rb.y);
        var maxX = Math.max(lt.x, rt.x, lb.x, rb.x);
        var maxY = Math.max(lt.y, rt.y, lb.y, rb.y);

        function transformPoint(m, p) {
            return {x: m.a * p.x + m.c * p.y + m.tx, y: m.b * p.x + m.d * p.y + m.ty};
        }

        return new Rectangle(minX, minY, maxX - minX, maxY - minY);
    };

这里做了一个变化,如果bounds(也就是边框Rect)有x/y偏移,就把这个偏移转加到Matrix上,把rect简化为(0,0,width,height),这样有利于简化计算。

关键点是transformPoint函数,理解这个线性代数计算就能理解整个2D平面的算法。

其实,由于rect的x、y被简化为0,那么上述算法还可以优化,把函数拆开。最终变成:

    p._transformBounds = function(bounds, matrix) {
        var x = bounds.x, y = bounds.y, width = bounds.width, height = bounds.height;
        var mtx = new Matrix2D();
        mtx.appendMatrix(matrix);
        if (x || y) { mtx.append(1,0,0,1,x,y); }

        var x_a = width*mtx.a, x_b = width*mtx.b;
        var y_c = height*mtx.c, y_d = height*mtx.d;
        var tx = mtx.tx, ty = mtx.ty;

        var minX = tx, maxX = tx, minY = ty, maxY = ty;

        if ((x = x_a + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; }
        if ((x = x_a + y_c + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; }
        if ((x = y_c + tx) < minX) { minX = x; } else if (x > maxX) { maxX = x; }

        if ((y = x_b + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; }
        if ((y = x_b + y_d + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; }
        if ((y = y_d + ty) < minY) { minY = y; } else if (y > maxY) { maxY = y; }

        return new Rectangle(minX, minY, maxX-minX, maxY-minY);
    };

两个函数看起来相差很大,但如果大家有心思慢慢计算一下,会发现其实是一样的。

那么,整个计算的过程如下:

    p.test = function () {
        var bounds = new Rectangle(-28, -35, 152, 128);
        var matrix = new Matrix2D();
        matrix.rotate(-60*Math.PI/180);
        var t = this._transformBounds(bounds, matrix);
        console.log(t);
    };

得到

Rectangle {x: -44.31088913245536, y: -124.88715006927039, width: 186.85125168440817, height: 195.6358613752347, initialize: function…}

大功告成。。。

说了一大堆废话,可能大家不理解这种计算意义何在,其实这种边框计算有很多用途,例如脏区重绘、碰撞检测。虽然一般来说,游戏框架都会给你封装好,但在某些情况下,还是不得不自己重新做一遍,这是Kenko在尝试做Flash转Canvas+脏区重绘的工作。

该项目名为Fanvas,现已加入2014年度腾讯6个公司级开源项目,相信不久将来大家会看到这个项目的开源。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏阮一峰的网络日志

关于颜色理论

我的意思是,我不知道应该选择哪些颜色放在一起,完全凭感觉。于是昨天,我在网上找了一些资料,希望找到理论指导。

672
来自专栏数据小魔方

人口金字塔图

今天跟大家分享的图表是——人口金字塔图! 人口金字塔图是按照人口年龄和性别表示人口分布状况的情况,能形象的表示人口某一年龄和性别构成。 该图表对于数据组织的要求...

3637
来自专栏数据小魔方

数据地图系列2|三维立体数据地图(给你的地图加特效)

今天跟大家分享数据地图系列2——三维立体数据地图(给你的地图加特效)! 昨天已经跟大家分享过了如何在ppt中利用矢量地图图形编辑数据地图,因为是手工编辑,所以门...

3515
来自专栏Coding迪斯尼

VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效

1084
来自专栏walterlv - 吕毅的博客

从 Matrix 解构出 Translate/Scale/Rotate(平移/缩放/旋转)

发布于 2017-11-20 16:20 更新于 2017-11...

431
来自专栏IT杂记

根据两经纬度点计算距离公式推导

已知地球上的点E经纬度为(J1, W1),点F经纬度为(J2, W2),求两点间最短的球面距离。

2359
来自专栏数据小魔方

R语言可视化——REmapH(中心热度图)

今天是REmap系列的最后一篇——REmapH函数。 这个函数的特色是可以做中心辐射的热力图,这种热力图在气象、人口密度、海拔测绘领域有诸多运用,当然也可以上当...

6336
来自专栏腾讯社交用户体验设计

如何让你的动画更自然-运动曲线探究与应用

993
来自专栏逍遥剑客的游戏开发

景深效果(Depth of Field)

2406
来自专栏数据科学学习手札

(数据科学学习手札14)Mean-Shift聚类法简单介绍及Python实现

不管之前介绍的K-means还是K-medoids聚类,都得事先确定聚类簇的个数,而且肘部法则也并不是万能的,总会遇到难以抉择的情况,而本篇将要介绍的Mean-...

3418

扫码关注云+社区