Math and (Quasi) Physics in Action Script 3

Inverse kinematics with two segments

The angles of a triangle(OAB) can be computed if the length of three sides are known using transformed version of cosine formula. Note that the angle between line L and OB is also needed to obtain actual angles of the segments.

Low of cosines

OAB = 
Math.acos((OA * OA + AB * AB - BO * BO) / 
(2 * OA * AB));

BOA = 
Math.acos((OA * OA + BO * BO - AB * AB) / 
(2 * OA * BO));

ABO = 
Math.acos((AB * AB + BO * BO - OA * OA) / 
(2 * BO* AB));
See ‘Law of cosines - Wikipedia’ for more about cosine formura.

Main.as

´╗┐package {
  import flash.display.*;
  import flash.events.*;
  import flash.geom.*;
  public class Main extends PpGraphics {
    
    //points
    private var pG:PpObject,pO:PpObject, pA:PpObject, pB:PpObject;
    
    //angles
    private var LOB:Number, BOA:Number,OAB:Number,ABO:Number;
    
    //lengths
    private var BO:Number,OA:Number = 80,AB:Number = 80;
    
    public function Main() {
      origin(stage.stageWidth / 2,stage.stageHeight / 2);
      
      guide(- stage.stageWidth / 2, 0, stage.stageWidth /2, 0);
      guideLabel(100,0,"L");
      
      pO = new PpObject(0, 0);
      pA = new PpObject(0, 0);
      pB = new PpObject(0, 0);
      
      circle(pO,5,C_FIXED, "O");
      circle(pA,5,C_MOVABLE, "A");
      circle(pB,5,C_MOVABLE, "B");
      path(pO,pA);
      path(pA,pB);
    }
    
    override protected function step():void {
      var m:Point = new Point(canvas.mouseX, canvas.mouseY);
      BO = Math.min(OA + AB, Point.distance(pO, m));
      LOB = Math.atan2(m.y, m.x);
      OAB = Math.acos((OA * OA + AB * AB - BO * BO) / (2 * OA * AB));
      if (BO != 0) {
        BOA = Math.acos((OA * OA + BO * BO - AB * AB) / (2 * OA * BO));
        ABO = Math.acos((AB * AB + BO * BO - OA * OA) / (2 * BO* AB));
      } else {
        BOA = ABO = Math.PI / 2;
      }
      pB.x = Math.cos(LOB) * BO;
      pB.y = Math.sin(LOB) * BO;
      pA.x = Math.cos(BOA + LOB) * OA;
      pA.y = Math.sin(BOA + LOB) * OA;
      
      
      clearText();
      print("OA = AB = 80px");
      print("OB = " + Math.round(BO) + "px");
      print("LOB(deg.) = " +Math.round( radToDeg(LOB)));
      print("BOA(deg.) = " +Math.round( radToDeg(BOA)));
      print("OAB(deg.) = " +Math.round( radToDeg(OAB)));
      print("ABO(deg.) = " +Math.round( radToDeg(ABO)));
    }
  }
}

PpGraphics.as

´╗┐package {
  import flash.display.*;
  import flash.events.*;
  import flash.text.*;
  class PpGraphics extends Sprite {
    protected var canvas:Sprite;
    protected var gCanvas:Sprite;
    protected var tCanvas:Sprite;
    private var textout:TextField;
    private var shapeList:Array;
    private var pathList:Array;
    private var lineList:Array;
    private var dragTarget:Sprite = null;
    private var handleList:Array;
    private var left:Number, right:Number, top:Number, bottom:Number;
    
    protected const C_MOVABLE:uint = 0xFF6600;
    protected const C_DRAGGABLE:uint = 0x0066FF;
    protected const C_FIXED:uint = 0x000000;
    protected const C_GUIDE:uint = 0xAAAAAA;
    
    public function PpGraphics():void {
      pathList = new Array();
      lineList = new Array();
      shapeList = new Array();
      handleList = new Array();
      
      addChild(gCanvas = new Sprite());
      gCanvas.graphics.lineStyle(0, C_GUIDE, 0.5);
      addChild(canvas = new Sprite());
      addChild(tCanvas = new Sprite());
      addChild(textout = getTextout());
      addEventListener(Event.ENTER_FRAME,h_enterFrame0);
      addEventListener(Event.ENTER_FRAME,h_enterFrame1);
      addEventListener(Event.ENTER_FRAME,h_enterFrame2);
      origin(0,0);
    }
    protected function h_enterFrame0(evt:Event):void {
      updateDrag();
    }
    
    protected function h_enterFrame1(evt:Event):void {
      step();
    }
    
    protected function step():void {}
    
    private function h_enterFrame2(evt:Event):void {
      updateGraphics();
    }
    
    private function updateDrag():void {
      if (!dragTarget) { return }
      
      var item:Object;
      var i:uint;
      
      dragTarget.x = canvas.mouseX;
      dragTarget.y = canvas.mouseY;
      
      for (i = 0; i < handleList.length; i ++) {
        item = handleList[i];
        item.obj.x = item.sprite.x;
        item.obj.y = item.sprite.y;
      }
    }
        
    private function updateGraphics():void {
      var g:Graphics = canvas.graphics;
      var item:Object;
      var i:uint;
      
      for (i = 0; i < shapeList.length; i ++) {
        item = shapeList[i];
        item.sprite.x = item.obj.x;
        item.sprite.y = item.obj.y;
      }
      
      g.clear();
      for (i = 0; i < pathList.length; i ++) {
        item = pathList[i];
        g.lineStyle(item.w, item.col, 0.5);
        g.moveTo(item.obj1.x, item.obj1.y);
        g.lineTo(item.obj2.x, item.obj2.y);
      }

      for (i = 0; i < lineList.length; i ++) {
        item = lineList[i];
        
        var l:Pp2dLine = Pp2dLine.fromPoints(item.obj1.x, item.obj1.y, item.obj2.x, item.obj2.y);
        g.lineStyle(item.w, item.col, 0.5);
        
        if (l.slope == 0) {
          g.moveTo(left, item.obj1.y);
          g.lineTo(right, item.obj1.y);
        } else if (isNaN(l.slope)) {
          g.moveTo(item.obj1.x, top);
          g.lineTo(item.obj1.x, bottom);
        } else if (l.slope > 1 || l.slope < -1) {
          g.moveTo(l.yToX(top), top);
          g.lineTo(l.yToX(bottom), bottom);
        } else {
          g.moveTo(left, l.xToY(left));
          g.lineTo(right, l.xToY(right));
        }
      }
      
    }
    
    /*
    * origin
    */
    
    protected function origin(x,y):void {
      gCanvas.x = canvas.x = x;
      gCanvas.y = canvas.y = y;
      left = x - stage.stageWidth;
      right = stage.stageWidth - x;
      top = y - stage.stageHeight;
      bottom = stage.stageHeight - y;
    }
    
    /*
    * guide
    */
    
    protected function guide(x1:Number,y1:Number,x2:Number,y2:Number):void {
      var g:Graphics = gCanvas.graphics;
      g.moveTo(x1,y1);
      g.lineTo(x2,y2);
    }
    
    protected function guideLabel(x:Number,y:Number,str:String):void {
      var t:TextField = label(str, C_GUIDE);
      t.x = x;
      t.y = y;
      gCanvas.addChild(t);
    }
    
    
    /*
    * graphic items
    */
    
    protected function circle(obj:PpObject, radius:Number = 3, col:uint = C_FIXED,labelStr:String = "") {
      var s:Sprite = new Sprite();
      s.graphics.beginFill(col, 0.5);
      s.graphics.drawCircle(0,0,radius);
      s.graphics.endFill();
      if (labelStr.length > 0) {
        var l:TextField = label(labelStr,col);
        s.addChild(l);
      }
      return addShape(obj, s);
    }
    
    private function addShape(obj:PpObject,s:Sprite):Object {
      var item:Object = {obj:obj,sprite:s};
      s.x = obj.x;
      s.y = obj.y;
      canvas.addChild(s);
      shapeList.push(item);
      return item
    }
    
    protected function path(obj1:PpObject, obj2:PpObject,col:uint = C_MOVABLE, w:Number = 0):Object {
      var item:Object = {obj1:obj1,obj2:obj2,col:col,w:w};
      pathList.push(item);
      return item
    }

    protected function line(obj1:PpObject, obj2:PpObject,col:uint = C_MOVABLE, w:Number = 0):Object {
      var item:Object = {obj1:obj1,obj2:obj2,col:col,w:w};
      lineList.push(item);
      return item
    }

    /*
    * text
    */
    
    private function label(str:String, col:uint):TextField {
      var t:TextField = new TextField();
      var tf:TextFormat = new TextFormat();
      t.autoSize = TextFieldAutoSize.LEFT;
      t.selectable = false;
      tf.font = "_sans";
      tf.color = col;
      t.defaultTextFormat = tf;
      t.text = str;
      return t;
    }
    
    private function getTextout():TextField {
      var t:TextField = new TextField();
      var tf:TextFormat = new TextFormat();
      t.width = stage.width - 20;
      t.height = stage.height - 20;
      t.x = t.y = 10;
      t.multiline = true;
      t.autoSize = TextFieldAutoSize.LEFT;
      t.selectable = false;
      tf.font = "_sans";
      tf.color = C_GUIDE;
      t.defaultTextFormat = tf;
      return t;
    }
    
    protected function print(str:String):void {
      textout.appendText(str + "\n");
    }
    protected function clearText():void {
      textout.text = "";
    }
    
    
    /*
    * drag
    */
    
    protected function enableDrag(...args):void {
      var s:Sprite;
      var item:Object;
      for (var i:uint = 0; i < args.length; i ++) {
        for (var j:uint = 0; j < shapeList.length; j ++) {
          if (shapeList[j].obj == args[i]) { 
            s = shapeList[j].sprite;
            s.addEventListener(MouseEvent.MOUSE_DOWN, h_draggable_mouseDown);      
            s.addEventListener(MouseEvent.MOUSE_UP, h_draggable_mouseUp);
            s.useHandCursor = true;
            s.buttonMode = true;
            s.tabEnabled = false;
            item = {obj:args[i],sprite:s};
            handleList.push(item);
          }
        }
       
      }
    }

    
    private function h_draggable_rollOver(evt:Event):void {
      evt.currentTarget.alpha = 0.5;
    }
    
    private function h_draggable_rollOut(evt:Event):void {
      evt.currentTarget.alpha = 1;
    }
    
    
    private function h_draggable_mouseDown(evt:Event):void {
      dragTarget = evt.currentTarget as Sprite;
    }
    
    private function h_draggable_mouseUp(evt:Event):void {
      dragTarget = null;
    }
    
    /*
    * util
    */
    protected function radToDeg(n:Number):Number {
      return n / Math.PI * 180;
    }
    protected function degToRad(n:Number):Number {
      return n * Math.PI / 180;
    }
    protected function rd(n:Number):Number {
      return Math.round(n * 100) / 100;
    }
  }
}

PpObject.as

´╗┐package {
  import flash.geom.Point;
  public dynamic class PpObject extends Point {
    public function PpObject(x:Number,y:Number):void {
      super(x,y);
    }
  }
}