roadProgram的专栏

一个程序员的路

转 第十四章 反向运动学: 拖拽与伸展(as3.0)

  第十三章介绍了一些基础的运动学以及正向与反向运动学之间的区别。  前一章我们讲了
正向运动学,本章就要学习与它关系紧密的反向运动学。涉及到的动作就是拖拽与伸展。
    与正向运动学的例子相同,本章的例子也是从独立的关节开始建立系统。  我们从单个关
节开始,然后到多个关节。首先,我会给大家演示最简单的计算角度与位置的方法。只是在
代码中使用基本的三角学进行大概的测算。最后,会给大家简要地介绍使用余弦定理的方法,
这样计算出来的结果更加准确,但会消耗大量的计算——这就是所谓的权衡。
单物体的拖拽与伸展
    前面说过,反向运动学系统可以分为两种不同的类型:拖拽与伸展。
    当系统的自由端向目标点伸展时,系统的另一端——固定端,也许是动不了的,因此如
果目标点位置超出了自由端运动的范围,那么自由端永远也不能到达目标点。举个例子,当
我们试图抓住某个东西时,手指就朝着这个物体移动,  手腕的转动会使我们的手指与目标位
置越来越近,肘部,肩膀和身体其它的部分也都尽可能地伸展。有时,所有这些位置的组合
将会使手指接触到物体;有时也许不行。如果物体是来回运动的,我们的肢体就要做出即时
的反映不断调整位置,为了让手指能够尽可能地够到该物体。反向运动学将会告诉我们,如
何设置所有这些零件的位置,达到最佳的伸展效果。
    另一种反向运动学是在物体被拖拽的时候。这个例子中,  自由端是被一些外部的力所拖
动的。无论何时,系统其余的部分都紧随其后,它们会将自己放置到自然的可能位置上。可
以想象成一个没有知觉死尸(对不起,这是我唯一能想到的)  。抓住他的手然后拽着它走。
    我们施加在对方手上的力,会传到手腕,肘部,肩膀,以及身体的其余部分,它们都沿
着拖拽的方向移动。这个例子中,反向运动学将告诉我们所有的这些零件是如何随着拖拽组
合成正确的位置。
    最好的理解方法就是用例子程序加以说明,每个例子都使用一个关节。我们需要用到
Segment 这个类,因此要保证它在我们工作的工程或类路径中。
单关节伸展
      对于伸展而言,所有关节都要能向目标旋转。目标,如果还没读懂我的意思,就把它想
需要知道两点间 x,y 轴上的距离。然后就可以使用 Math.atan2成鼠标。让关节向目标旋转,

求出该角度的弧度制。将它转换为角度制,就得到了关节的 rotation。代码如下(可见
OneSegment.as) :

package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class OneSegment extends Sprite {
   private var segment0:Segment;
   public function OneSegment() {
     init();
   }
   private function init():void {
     segment0 = new Segment(100, 20);
     addChild(segment0);
     segment0.x = stage.stageWidth / 2;
     segment0.y = stage.stageHeight / 2;
      addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }
    private function onEnterFrame(event:Event):void {
      var dx:Number = mouseX - segment0.x;
      var dy:Number = mouseY - segment0.y;
      var angle:Number = Math.atan2(dy, dx);
      segment0.rotation = angle * 180 / Math.PI;
    }
  }
}
       图 14-1 所示,运行结果。测试一下观察关节是如何跟随鼠标的。即使关节离得很远,
它都像是快要抓住鼠标一样。

转 <wbr>第十四章 <wbr>反向运动学: <wbr>拖拽与伸展(as3.0)

 

图 14-1 单个关节向鼠标伸展

 


单关节拖拽
      现在,我们来试一试拖拽。这里所说的拖拽不是使用 startDrag 和 stopDrag 方法(虽
然你也可以这样做) 。我们要假设关节的第二个枢轴点是与鼠标相连的。
      拖拽的第一部分与伸展相同:让 sprite 影片向着鼠标旋转。然后我们还要多做一步,
将关节移动到可以使第二个枢轴点放到鼠标上的位置。这样一来,就需要知道两个枢轴的每
个轴的位置。我们可以通过关节的 getPin() 方法以及关节实际的 x,y 位置,将它们计算出
来。把这两个距离叫做 w 和 h 吧。最后,从鼠标的当前位置中将 w 和 h 减去,这样就
知道将关节放在哪里了。下面是 OneSegmentDrag.as 中的 onEnterFrame 方法,也是唯一发
生改变的部分:
private function onEnterFrame(event:Event):void {
  var dx:Number = mouseX - segment0.x;
  var dy:Number = mouseY - segment0.y;
  var angle:Number = Math.atan2(dy, dx);
  segment0.rotation = angle * 180 / Math.PI;
  var w:Number = segment0.getPin().x - segment0.x;
  var h:Number = segment0.getPin().y - segment0.y;
  segment0.x = mouseX - w;
  segment0.y = mouseY - h;
}
      我们可以看到这个关节永久地与鼠标相连并旋转,拖拽在鼠标的后面。我们甚至可以把
这个关节推到相反的方向去。

多关节拖拽

      使用反向运动学拖拽一个系统比伸展要简单一些,所以首先介绍拖拽。从两个关节的拖
拽入手。
拖拽两个关节
      继续前面的例子,再创建一个关节,名为 segment1,然后加入显示列表。策略非常简
单。我们已经有了 segment0 拖拽在鼠标上的位置了,只需要再让 segment1 拖拽在
segment0 上即可。首先,简单地复制一些代码,然后改变一些引用。新代码部分加粗表示。
private function onEnterFrame(event:Event):void {
  var dx:Number = mouseX - segment0.x;
  var dy:Number = mouseY - segment0.y;
  var angle:Number = Math.atan2(dy, dx);
  segment0.rotation = angle * 180 / Math.PI;
  var w:Number = segment0.getPin().x - segment0.x;
  var h:Number = segment0.getPin().y - segment0.y;
  segment0.x = mouseX - w;
  segment0.y = mouseY - h;
  dx = segment0.x - segment1.x;
  dy = segment0.y - segment1.y;
  angle = Math.atan2(dy, dx);
  segment1.rotation = angle * 180 / Math.PI;
  w = segment1.getPin().x - segment1.x;
  h = segment1.getPin().y - segment1.y;
  segment1.x = segment0.x - w;
  segment1.y = segment0.y - h;
}
      我们看到新的代码块是如何计算 segment1 到 segment0 的距离,并使用它们计算出
angle 与 rotation 以及 segment1 的位置。不妨测试一下这个例子程序,观察这个非常真实
的双关节系统。
      现在,有了许多复制的代码,这样不太好。如果要加入更多的关节,这个文件会由于这
些相同的重复代码变得越来越长。解决方法是将复制出来的代码单独放到一个名为 drag 的
函数中。这个函数需要知道要被拖拽的关节以及要拖拽到的点的 x,y。然后我们就可以拖拽
segment0 到 mouseX, mouseY,以及 segment1 到 segment0.x, segment0.y。全部代码如下(同
样出现在 TwoSegmentDrag.as 中) :
package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class TwoSegmentDrag extends Sprite {
   private var segment0:Segment;
   private var segment1:Segment;
   public function TwoSegmentDrag() {
     init();
   }
   private function init():void {
     segment0 = new Segment(100, 20);
     addChild(segment0);
     segment1 = new Segment(100, 20);
     addChild(segment1);
      addEventListener(Event.ENTER_FRAME, onEnterFrame);
    }
    private function onEnterFrame(event:Event):void {
      drag(segment0, mouseX, mouseY);
      drag(segment1, segment0.x, segment0.y);
    }
    private function drag(segment:Segment, xpos:Number, ypos:Number):void {
      var dx:Number = xpos - segment.x;
      var dy:Number = ypos - segment.y;
      var angle:Number = Math.atan2(dy, dx);
      segment.rotation = angle * 180 / Math.PI;
      var w:Number = segment.getPin().x - segment.x;
      var h:Number = segment.getPin().y - segment.y;
      segment.x = xpos - w;
      segment.y = ypos - h;
    }
  }
}
拖拽更多的关节
       现在我们可以任意加入多个关节了。假设放入6个关节 ,命名从 segment0 到
segment1,并把它们存入数组。然后使用 for 循环为每个关节调用 drag 函数。可在
MultiSegmentDrag.as 中找到这个例子。代码如下:
package {
  import flash.display.Sprite;
  import flash.events.Event;
  public class MultiSegmentDrag extends Sprite {
   private var segments:Array;
   private var numSegments:uint = 6;
   public function MultiSegmentDrag() {
     init();
   }
   private function init():void {
     segments = new Array();
     for (var i:uint = 0; i < numSegments; i++) {
       var segment:Segment = new Segment(50, 10);
       addChild(segment);
       segments.push(segment);
     }
     addEventListener(Event.ENTER_FRAME, onEnterFrame);
   }
   private function onEnterFrame(event:Event):void {
     drag(segments[0], mouseX, mouseY);
     for (var i:uint = 1; i < numSegments; i++) {
       var segmentA:Segment = segments[i];
       var segmentB:Segment = segments[i - 1];
       drag(segmentA, segmentB.x, segmentB.y);
      }
    }
    private function drag(segment:Segment, xpos:Number, ypos:Number):void {
      var dx:Number = xpos - segment.x;
      var dy:Number = ypos - segment.y;
      var angle:Number = Math.atan2(dy, dx);

阅读更多
个人分类: Flash Flex
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭