AS3位图任意形变一步一步来

在Flash/ActionScript中,只提供了仿射变换功能。仿射变换包括平移(x 和 y 重新定位)、旋转、缩放和倾斜,在进行这些形变时,平行线仍然会保持平行,因此没办法做到任意形变。

实现位图或者MC(MovieClip)的任意形变的一种方式就是把它们分割成三角行,然后对三角形进行相应的仿射变换。比较流行的PV3D(papervision3d)就是采用的这种方式。

一切从简单开始,我们先看看一个点在平面上的几个简单的基本变换。一般来说,我们都将平面上的一个点表示为一个1×2的矩阵,我们可以通过将改点乘以一个2×2的矩阵来对该点进行变换。以下是一些简单的示例:

由于平移变换不是线性的,因此不能表示为2×2的矩阵,通过与一个1×2的矩阵相加即可得到相应的平移变换位置。上图中的取镜像变换从某种意义上也可以看做是一种平移变换:

通过上面的一些基本变换方式,我们可以得到一些比较复杂的变换:

上面的这中变换就叫做仿射变换。对于这种仿射变换,它需要两个矩阵进行变换(一个2×2的矩阵用于旋转,一个1×2的矩阵用于平移),为了方便,我 们有一种替换方案,将整个变换存储于一个3×3的矩阵中。另外,还需要将点坐标由1×2扩充为1×3,组后一位通常补1,如以前的(2,1)就变为 [2,1,1]。下面的矩阵变换就是上图的复杂变换:

其变换矩阵的意义如下:

在Flash/ActionScript中,略有不同的是,Macromedia(现在应该叫Adobe了)把平移部分放到了第三列中,即是这样的:

请务必记住这一点不同! 通过这个3×3的矩阵,我们就可以进行任何仿射变换,但记住一点:经过任何反射变换,原来的直线还是直线,平行的直线依然还会平行!至于怎么实现“不平行”的变换,我们将在后面继续讲解。

 

AS3位图任意形变一步一步来(2)——计算变换矩阵

由于仿射变换时,平行的边依然平行,所以,我们无法对一个矩形的位图进行随意变换,比如我们无法拉伸一个角,也无法进行把它变成梯形等。在上文中,我们提到过一种思路,就是把矩形分割成两个三角形,这样变换时从表面上 看来就没有什么平行边了,这样我们就可以变换其中的一个或者多个角了。注意,我这里说的是从表面上看,这一点在后面的变换中要进行一定的处理的。

这样我们只需要对三个点进行平面变换,假设我们的三个点分别为(x1, y1)、(x2, y2)、(x3, y3),要变换的位置分别为(x1',y1')、(x2', y2')、(x3', y3')、现在,我们要做的就是计算其变换矩阵。假设其变换矩阵为:

注意,在Flash中,其对应的变换矩阵应该是这个(平移变换的位置略有不同):

根据上一篇文章 中提到的,点(x1, y1)变换为(x1', y1')时,有:

于是可以得到方程:

  • a*x1 + c * y1 + tx = x1'
  • b*x1 + d * y1 + ty = y1'

同理,我们还可以得到其他四个方程:

  • a*x2 + c * y2 + tx = x2'
  • b*x2 + d * y2 + ty = y2'
  • a*x3 + c * y3 + tx = x3'
  • b*x3 + d * y3 + ty = y3'

正好六个方程,六个未知数:a、b、c、d、tx、ty,解这六个方程就可以得到我们的变换矩阵。方程的解为:

这几个结果看起来够晕的,本来还想化简一下的,懒了,还怕弄错,谁有兴趣看看还能不能再简化一下,这个结果应该是没有错的。解这个方程够晕的,这篇就到这里为止吧,这几个结果留着后面用,下一篇讲讲如何把一个位图切割成三角形。

 

 

  AS3位图任意形变一步一步来(3)——切割位图

对不起,让大家久等了,继续写blog。

今天我们继续研究位图的任意形变,今天的主要目的就是从外部加载一张图片,然后分割成两个三角形。当然了,分割成两个三角形是远远不够的,这一点,我们在后面会讲到。

从外部加载一张图片很简单,使用Loader类就可以轻松完成了。不过要注意的是这里是Loader类,在包flash.display下,而不是 flash.net下的URLLoader类。这个类使用很简单,如下(为了简单,这就不写那些条条框框了,直接写在时间轴上了,所有的代码都在第一 帧):

参考代码: [复制代码]  [保存代码]
  1. // 这个是我们要load的图片
  2. var url:String = 'green.jpg' ;
  3. //这个是用来保存位图数据的
  4. var bmd:BitmapData = null;
  5. // load方法
  6. function Load():void {
  7.     var req:URLRequest = new URLRequest(url);
  8.     var loader:Loader = new Loader();
  9.     // 添加事件侦听
  10.     ConfigureListeners(loader.contentLoaderInfo);
  11.     try {
  12.         // 加载图片
  13.         loader.load(req);
  14.         // 如果你要在舞台上显示出来
  15.         // 可以添加下面的代码
  16.         // this.addChild(loader);
  17.     } catch (err:Error) {
  18.     }
  19. }
  20. function ConfigureListeners(dispatcher:IEventDispatcher):void {
  21.     dispatcher.addEventListener(Event.COMPLETE, CompleteHandler);
  22. }
  23. // 事件控制函数
  24. function CompleteHandler(evt:Event):void {
  25.     var ld:Loader = Loader(evt.target.loader);
  26.     var info:LoaderInfo = LoaderInfo(ld.contentLoaderInfo);
  27.     // 保存位图数据
  28.     bmd = new BitmapData(info.width, info.height);
  29.     bmd.draw(info.content);
  30. }

要分割成三角行,我们实现的方式是,先绘制一个三角形,然后用位图来填充,绘制和填充也很简单:

参考代码: [复制代码]  [保存代码]
  1. // bw是我们最后绘制的三角形的宽
  2. // bh是我们最后绘制的三角形的高
  3. // 我这里的图片宽高比是4 :3,为了简单,就这样直接指定了
  4. var bw:Number = 280 , bh:Number = 210 ;
  5. // 给出三个点,绘制三角形
  6. // 并使用位图数据填充
  7. function drawTriagle(pa:Object, pb:Object, pc:Object):Shape{
  8.     var r = new Shape();
  9.     // 为了清晰可见,你可以给三角形画一个边框
  10.     // 加上下面一行代码即可
  11.     // r.graphics.lineStyle(00xccff00 );
  12.     var m:Matrix = new Matrix();
  13.     // 由于图片比绘制的三角形的宽高要大
  14.     // 需要对其进行缩放处理
  15.     var sx = bw / bm.width;
  16.     var sy = sx;
  17.     m.scale(sx, sy);
  18.     // 使用位图填充三角形
  19.     r.graphics.beginBitmapFill(bm.bitmapData, m, false, true);
  20.     r.graphics.moveTo(pa.x, pa.y);
  21.     r.graphics.lineTo(pb.x, pb.y);
  22.     r.graphics.lineTo(pc.x, pc.y);
  23.     r.graphics.lineTo(pa.x, pa.y);
  24.     r.graphics.endFill();
  25.     // 返回当前绘制的三角形
  26.     return r;
  27. }

还差一步,就是要把这两步连接起来,我们再来写一个非常简单的split方法,当位图加载完成后,调用split方法。通过split方法,绘制两个三角形:

参考代码: [复制代码]  [保存代码]
  1. var triangle_a:DisplayObject = null;
  2. var triangle_b:DisplayObject = null;
  3. // 这个方法需要在位图加载之后调用
  4. function split(){
  5.     var p1  = {x: 0 , y: 0 };   // 左上角
  6.     var p2  = {x: bw, y: 0 };  // 右上角
  7.     var p3  = {x: 0 , y: bh};  // 左下角
  8.     var p4  = {x: bw, y: bh}; // 右下角
  9.     
  10.     // 以p2和p3为分割线,切割成两个三角形
  11.     // 绘制完成后添加到舞台上
  12.     triangle_a = DisplayObject(drawTriagle(p1p2p4 ));
  13.     this.addChild(triangle_a);
  14.     triangle_b = DisplayObject(drawTriagle(p2p4p5 ));
  15.     this.addChild(triangle_b);
  16. }

这样我们就完成了把位图分割成两个三角形的任务,下面的任务是对两个三角形形变,并对这两个三角形进行拼接,以达到对位图任意形变的目的,这个我们在下一篇中再讲。

 

AS3位图任意形变一步一步来(4)——任意形变

放着放着就忘了,多亏army同学在后面催我,要不然还真不知道什么时候会继续弄这个。今天上午又抽空捣鼓了一下,偏移总算是弄出来了,不过渲染效率很低,中间的计算过程应该还可以继续优化的。另外,弄到这里发现还是有问题,分得较细的时候会出现裂缝……误差真可怕!

先不管这个误差,我们来看看能不能形变先。由于计算形变矩阵需要初始的坐标和新的坐标,需要两个方法来计算(貌似可以合并?):

参考代码: [复制代码]  [保存代码]
  1. // calculate points
  2. // 包含x坐标和y坐标,分别存储在pxs和pys数组中
  3. // @params:
  4. //   w: 位图宽
  5. //   h: 位图高
  6. //   hs: 水平分段数
  7. //   vs: 垂直分段数
  8. // @return:
  9. //   void
  10. function CalculatePoints(w:Number, h:Number, hs:Number, vs:Number):void {
  11.     var iw:Number = w / hs;
  12.     var ih:Number = h / vs;
  13.     for (var h=0 ; h<=hs; h++) {
  14.         pxs[h] = h * iw;
  15.     }
  16.     for (var v=0 ; v<=vs; v++) {
  17.         pys[v] = v * ih;
  18.     }
  19. }

计算新的坐标,也就是形变后的坐标:

参考代码: [复制代码]  [保存代码]
  1. function CalculateNewPoints(p0 :Point, p1 :Point, p2 :Point, p3 :Point, hs:Number, vs:Number):void {
  2.     var lp:Point = new Point(p0 .x, p0 .y);
  3.     var rp:Point = new Point(p1 .x, p1 .y);
  4.     var lxd:Number = (p2 .x - p0 .x)/vs;
  5.     var lyd:Number = (p2 .y - p0 .y)/vs;
  6.     var rxd:Number = (p3 .x - p1 .x)/vs;
  7.     var ryd:Number = (p3 .y - p1 .y)/vs;
  8.     nps = [];
  9.     for (var v=0 ; v<=vs; v++) {
  10.         lp.x = p0 .x + lxd * v;
  11.         lp.y = p0 .y + lyd * v;
  12.         rp.x = p1 .x + rxd * v;
  13.         rp.y = p1 .y + ryd * v;
  14.         nps.push(new Array());
  15.         var xd:Number = (rp.x - lp.x)/hs;
  16.         var yd:Number = (rp.y - lp.y)/hs;
  17.         for (var h=0 ; h<=hs; h++) {
  18.             nps[v].push(new Point(lp.x + xd * h, lp.y + yd * h));
  19.         }
  20.     }
  21. }

再是计算形变矩阵的函数,由于在这里我们会对便宜进行单独处理,所以这里不需要计算偏移。

参考代码: [复制代码]  [保存代码]
  1. function CalculateMatrix(oa:Object, ob:Object):Matrix {
  2.     var p1  = oa.p1p2  = oa.p2p3  = oa.p3 ;
  3.     var pa = ob.p1 , pb = ob.p2 , pc = ob.p3 ;
  4.     var mt:Matrix = new Matrix();
  5.     mt.a = ((p3 .y - p1 .y)*(pb.x - pa.x) - (p2 .y-p1 .y)*(pc.x-pa.x))/((p3 .y-p1 .y)*(p2 .x-p1 .x)-(p3 .x-p1 .x)*(p2 .y-p1 .y));
  6.     mt.b = ((p3 .y - p1 .y)*(pb.y - pa.y) - (p2 .y-p1 .y)*(pc.y-pa.y))/((p3 .y-p1 .y)*(p2 .x-p1 .x)-(p3 .x-p1 .x)*(p2 .y-p1 .y));
  7.     mt.c = ((p3 .x - p1 .x)*(pb.x - pa.x) - (p2 .x-p1 .x)*(pc.x-pa.x))/((p2 .y-p1 .y)*(p3 .x-p1 .x)-(p3 .y-p1 .y)*(p2 .x-p1 .x));
  8.     mt.d = ((p3 .x - p1 .x)*(pb.y - pa.y) - (p2 .x-p1 .x)*(pc.y-pa.y))/((p2 .y-p1 .y)*(p3 .x-p1 .x)-(p3 .y-p1 .y)*(p2 .x-p1 .x));
  9.     // mt.tx = ((pb.x*p1 .x - pa.x*p2 .x)*(p3 .y*p1 .x-p1 .y*p3 .x)-(pc.x*p1 .x-pa.x*p3 .x)*(p2 .y*p1 .x-p1 .y*p2 .x))/((p1 .x-p2 .x)*(p3 .y*p1 .x-p1 .y*p3 .x)-(p1 .x-p3 .x)*(p2 .y*p1 .x-p1 .y*p2 .x));
  10.     // mt.tx = ((pb.y*p1 .x - pa.y*p2 .x)*(p3 .y*p1 .x-p1 .y*p3 .x)-(pc.y*p1 .x-pa.y*p3 .x)*(p2 .y*p1 .x-p1 .y*p2 .x))/((p1 .x-p2 .x)*(p3 .y*p1 .x-p1 .y*p3 .x)-(p1 .x-p3 .x)*(p2 .y*p1 .x-p1 .y*p2 .x));
  11.     return mt;
  12. }

最后就是要对三角形进行形变,这个函数:

参考代码: [复制代码]  [保存代码]
  1. // 要传进来的参数是位图四个角的新坐标
  2. function rendNew(p0 :Point, p1 :Point, p2 :Point, p3 :Point) {
  3.     // 先计算每个点的新坐标位置
  4.     CalculateNewPoints(p0p1p2p3 , hs, vs);
  5.     var pa:Point, pb:Point, pc:Point, pd:Point;
  6.     var m1 :Matrix, m2 :Matrix;
  7.     var tid:Number = 0 ;
  8.     var ps0 :Point = new Point(pxs[0 ], pys[0 ]);
  9.     var psa:Point = nps[0 ][0 ];
  10.     for (var v=0 ; v<vs; v++) {
  11.         for (var h=0 ; h<hs; h++) {
  12.             p0 .x = pxs[h];
  13.             p0 .y = pys[v];
  14.             p1 .x = pxs[h+1 ];
  15.             p1 .y = pys[v];
  16.             p2 .x = pxs[h];
  17.             p2 .y = pys[v+1 ];
  18.             p3 .x = pxs[h+1 ];
  19.             p3 .y = pys[v+1 ];
  20.             pa = nps[v][h];
  21.             pb = nps[v][h+1 ];
  22.             pc = nps[v+1 ][h];
  23.             pd = nps[v+1 ][h+1 ];
  24.             tid = v*hs*2  + h*2 ;
  25.             var ta = trs[tid];
  26.             var tb = trs[tid+1 ];
  27.             m1  = CalculateMatrix({p1 :p0p2 :p1p3 :p2 }, {p1 :pa, p2 :pb, p3 :pc});
  28.             if (tid > hs * 2  && h != 0 ) {
  29.                 m1 .tx = trs[tid - hs * 2  - 1 ].transform.matrix.tx;
  30.                 m1 .ty = trs[tid - hs * 2  - 1 ].transform.matrix.ty;
  31.             } else {
  32.                 m1 .tx = 0 ;
  33.                 m1 .ty = 0 ;
  34.             }
  35.             
  36.             m2  = CalculateMatrix({p1 :p1p2 :p2p3 :p3 }, {p1 :pb, p2 :pc, p3 :pd});
  37.             if(v > 0 ){
  38.                 m2 .tx = trs[1 ].transform.matrix.tx * (h + 1 ) * (v + 1 );
  39.             }
  40.             else{
  41.                 m2 .tx = ((pb.x - psa.x) - (p1 .x - pxs[0 ])) - ((pd.x - nps[v+1 ][0 ].x) - (p3 .x - pxs[0 ]));
  42.             }
  43.             if(tid > 0 ){
  44.                 m2 .ty = trs[1 ].transform.matrix.ty * (h + 1 ) * (v + 1 );
  45.             }
  46.             else{
  47.                 m2 .ty = ((pc.y - psa.y) - (p2 .y - pys[0 ])) - ((pd.y - nps[0 ][h+1 ].y) - (p3 .y - pys[0 ]));
  48.                 trace(m2 .ty);
  49.             }
  50.             ta.transform.matrix = m1 ;
  51.             tb.transform.matrix = m2 ;
  52.         }
  53.     }
  54. }

嗯,这实在是太凑合了,需要合并许多计算,后面继续优化~

你可能感兴趣的:(AS3位图任意形变一步一步来)