<?xml version="1.0" encoding="utf-8"?>
<mx:Application
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:geom="components.*"
    xmlns="http://www.degrafa.com/2007"
    layout="absolute"
    width="600" height="500" 
    pageTitle="Quad Bezier Arc Length"
    applicationComplete="test()" xmlns:degrafa="http://www.degrafa.com/2007" viewSourceURL="srcview/index.html">
    
    <mx:Style source="assets/style/style.css"/>
    <mx:Canvas id="background" x="50" y="90" width="500" height="320" backgroundColor="#FFFFFF" >
        <mx:Label x="10" y="292" text="Move the 'A', 'B', or 'C' control points to view arc length" width="480" color="#000000" fontSize="12"/>
    </mx:Canvas>
    <mx:Label text="Quad. Bezier Arc Length" x="250" y="30" width="300" styleName="title"/>
    
    <geom:Triangle id="triad" point1="{pointA}" point2="{pointB}" point3="{pointC}" />
    <mx:Canvas id="bezierLayer" x="{background.x}" y="{background.y}" width="{background.width}" height="{background.height}" />
    <geom:InteractivePoint id="pointA"  x="90" y="250" pointLabel="A" radius="5" color="0x00ff00" width="100" height="20" />
    <geom:InteractivePoint id="pointB" x="150" y="150" pointLabel="B" radius="5" color="0x00ff00" width="100" height="20" />
    <geom:InteractivePoint id="pointC" x="290" y="200" pointLabel="C" radius="5" color="0x00ff00" width="100" height="20" />
    
 <QuadraticBezier id="bezier" graphicsTarget="{[bezierLayer]}">
   <stroke> 
    <SolidStroke weight="2" color="#0000FF"/>
  </stroke>
    </QuadraticBezier>
    
    <mx:Label x="50" y="420" text="Segment Arc Length:" width="500" fontSize="12" color="#FFFFFF" id="__segments__"/>
    <mx:Label x="50" y="435" text="Degrafa Arc Length:" width="500" fontSize="12" color="#FFFFFF" id="__degrafa__"/>
    <mx:Label x="50" y="450" text="Elliptic Integral:" width="500" fontSize="12" color="#FFFFFF" id="__elliptic__"/>
    <mx:Label x="50" y="465" text="Gauss-Legendre:" width="500" fontSize="12" color="#FFFFFF" id="__gauss__"/>
        
    <mx:Script>
      <![CDATA[
        import mx.events.PropertyChangeEvent;
        
        import com.degrafa.GraphicPointEX;
     import com.degrafa.core.collections.GraphicPointCollection;
     import  com.degrafa.utilities.math.Gauss;
     
     // bezier polynomial coefficients
     private var __c0X:Number;
     private var __c0Y:Number;
     private var __c1X:Number;
     private var __c1Y:Number;
     private var __c2X:Number;
     private var __c2Y:Number;
     
     // Gaussian quadrature
     private var __integrate:Gauss = new Gauss();
     
        private function test():void
        { 
          // restrict dragging for each point
       var bounds:Rectangle = new Rectangle(background.x, background.y, background.width, background.height);
       pointA.restrict      = bounds;
       pointB.restrict      = bounds;
       pointC.restrict      = bounds;
       
       // actions when a property is changed on any InteractivePoint
       pointA.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChanged);
       pointB.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChanged);
       pointC.addEventListener(PropertyChangeEvent.PROPERTY_CHANGE, onPropertyChanged);
       
       // assign the quad. bezier data from script
       assignBezierControlPoints();
        }
        
        private function assignBezierControlPoints():void
        {
          // property changes trigger redraw
          bezier.x0 = pointA.x - bezierLayer.x;
       bezier.y0 = pointA.y - bezierLayer.y;
       bezier.cx = pointB.x - bezierLayer.x;
       bezier.cy = pointB.y - bezierLayer.y;
       bezier.x1 = pointC.x - bezierLayer.x;
       bezier.y1 = pointC.y - bezierLayer.y;
        }
        
        private function onPropertyChanged(_e:PropertyChangeEvent):void
        {
          switch( _e.property )
          {
            case InteractivePoint.MOUSE_DOWN:
              addEventListener(Event.ENTER_FRAME, onPointMove);
            break;
            
            case InteractivePoint.MOUSE_UP:
              removeEventListener(Event.ENTER_FRAME, onPointMove);
              
              // arc length
              displayArcLength();
            break;
          }
        }
        
        // redraw control points and bezier curve when an InteractivePoint is moved
        private function onPointMove(_e:Event):void
        {
          triad.redraw();
          
          assignBezierControlPoints();
        }
        
        // display arc length in closed form and various approximations
        private function displayArcLength():void
        {
          __segments__.text = "Segment Arc Length: " + arcLengthBySegments().toString();
          __degrafa__.text  = "Degrafa Arc Length: " + bezier.geometricLength.toString();
          __elliptic__.text = "Elliptic Integral: " + arcLengthByIntegral().toString();
          __gauss__.text    = "Gauss-Legendre: " + approxArcLength().toString();
        }
        
        // naive computation of arc length by summing small segment lengths
        private function arcLengthBySegments():Number
        {
          var length:Number    = 0;
          var tPrevious:Number = 0;
          var p:Point          = bezier.pointAt(0);
          var prevX:Number     = p.x;
          var prevY:Number     = p.y;
          for( var t:Number=0.005; t<=1.0; t+=0.005 )
          {
            p                 = bezier.pointAt(t);
            var deltaX:Number = p.x - prevX;
            var deltaY:Number = p.y - prevY;
            length           += Math.sqrt(deltaX*deltaX + deltaY*deltaY);
            
            prevX = p.x;
            prevY = p.y;
          }
          
          // exercise:  due to roundoff, it's possible to miss a small segment at the end.  how to compensate??
          return length;
        }
        
        // closed-form solution to elliptic integral for arc length
        private function arcLengthByIntegral():Number
        {
          var ax:Number = pointA.x - 2*pointB.x + pointC.x;
       var ay:Number = pointA.y - 2*pointB.y + pointC.y;
       var bx:Number = 2*pointB.x - 2*pointA.x;
       var by:Number = 2*pointB.y - 2*pointA.y;
       
       var a:Number = 4*(ax*ax + ay*ay);
       var b:Number = 4*(ax*bx + ay*by);
       var c:Number = bx*bx + by*by;
       
       var abc:Number = 2*Math.sqrt(a+b+c);
       var a2:Number  = Math.sqrt(a);
       var a32:Number = 2*a*a2;
       var c2:Number  = 2*Math.sqrt(c);
       var ba:Number  = b/a2;

       return (a32*abc + a2*b*(abc-c2) + (4*c*a-b*b)*Math.log((2*a2+ba+abc)/(ba+c2)))/(4*a32);
        }
        
        // compute the quad. bezier coefficients
        private function computeBezierCoef():void
        {
          __c0X = pointA.x;
          __c0Y = pointA.y;;

       __c1X = 2.0*(pointB.x-pointA.x);
       __c1Y = 2.0*(pointB.y-pointA.y);

       __c2X = pointA.x-2.0*pointB.x+pointC.x;
       __c2Y = pointA.y-2.0*pointB.y+pointC.y;
        }
        
        // approximate arc-length by numerical integration
        private function approxArcLength():Number
        {
          computeBezierCoef();
          
          // arc length along entire curve from t=0 to t=1
          return __integrate.eval(integrand, 0, 1, 5);
        }
        
        // integrand for Gauss-Legendre numerical integration
        private function integrand(_t:Number):Number
     {
        // first-derivative of the quad. bezier
        var xPrime:Number = __c1X + 2.0*__c2X*_t;
        var yPrime:Number = __c1Y + 2.0*__c2Y*_t;
        
        return Math.sqrt( xPrime*xPrime + yPrime*yPrime );
     }
        
      ]]>
    </mx:Script>
    
    
</mx:Application>