// Copyright(c) 2008, Philippe Ivaldi.
// Simplified by John Bowman 02Feb2011
// http: //www.piprime.fr/
// trembling.asy: handwriting package for the software Asymptote.

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation; either version 3 of the License, or
//(at your option) any later version.

// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.

// You should have received a copy of the GNU Lesser General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

// COMMENTARY:

// THANKS:

// BUGS:
// magnetic points are experimental...

// CODE:

real magneticRadius=1; // unit is bp in postscript coordinates.
real trembleFuzz(){return min(1e-3,magneticRadius/10);}

real trembleAngle=4, trembleFrequency=0.5, trembleRandom=2;

struct tremble
{
  static real test=5;

  real angle,frequency,random,fuzz;

  pair[] single(pair[] P)
  {
    pair[] op;
    bool allow;
    for(int i=0; i < P.length-1; ++i) {
      allow=true;
      for(int j=i+1; j < P.length; ++j) {
        if(abs(P[i]-P[j]) < magneticRadius) {
          allow=false;
          break;
        }
      }
      if(allow) op.push(P[i]);
    }
    if(P.length > 0) op.push(P[P.length-1]);
    return op;
  }

  real atime(pair m, path g, real fuzz=trembleFuzz())
  {// Return the time of the point on path g nearest to m, within fuzz.
    if(length(g) == 0) return 0.0;
    real[] t=intersect(m,g,fuzz);
    if(t.length > 0) return t[1];
    real ot;
    static real eps=sqrt(realEpsilon);
    real abmax=abs(max(g)-m), abmin=abs(min(g)-m);
    real initr=abs(m-midpoint(g));
    real maxR=2*max(abmax,abmin), step=eps, r=initr;
    real shx=1e-4;
    transform T=shift(m);
    path ig;
    if(t.length > 0) ot=t[1];
    real rm=0, rM=r;
    while(rM-rm > eps) {
      r=(rm+rM)/2;
      t=intersect(T*scale(r)*unitcircle,g,fuzz);
      if(t.length <= 0) {
        rm=r;
      } else {
        rM=r;
        ot=t[1];
      }
    }
    return ot;
  }

  path addnode(path g, real t)
  {// Add a node to 'g' at point(g,t).
    real l=length(g);
    real rt=t % 1;
    if(l == 0 || (t > l && !cyclic(g)) || rt == 0) return g;
    if(cyclic(g)) t=t % l;
    int t0=floor(t);
    int t1=t0+1;
    pair z0=point(g,t0), z1=point(g,t1),
      c0=postcontrol(g,t0), c1=precontrol(g,t1),
      m0=(1-rt)*z0+rt*c0, m1=(1-rt)*c0+rt*c1,
      m2=(1-rt)*c1+rt*z1, m3=(1-rt)*m0+rt*m1,
      m4=(1-rt)*m1+rt*m2;
    guide og=subpath(g,0,t0)..controls m0 and m3..point(g,t);
    if(cyclic(g)) {
      if(t1 < l)
        og=og..controls m4 and m2..subpath(g,t1,l)&cycle;
      else og=og..controls m4 and m2..cycle;
    } else og=og..controls m4 and m2..subpath(g,t1,l);
    return og;
  }

  path addnodes(path g, real fuzz=trembleFuzz()...pair[] P)
  {
    pair[] P=single(P);
    if(length(g) == 0 || P.length == 0 || magneticRadius <= 0) return g;
    path og=g;
    for(pair tp: P) {
      real t=atime(tp,og,fuzz);
      real d=abs(tp-point(og,t));
      if(d < magneticRadius) og=addnode(og,t);
    }
    return og;
  }

  path addnodes(path g, int n)
  {// Add 'n' nodes between each node of 'g'.
    real l=length(g);
    if(n == 0 || l == 0) return g;
    path og=g;
    int np=0;
    for(int i=0; i < l; ++i) {
      real step=1/(n+1);
      for(int j=0; j < n; ++j) {
        og=addnode(og,i*(n+1)+j+step);
        step=1/(n-j);
      }
    }
    return og;
  }

  void operator init(real angle=trembleAngle, real frequency=trembleFrequency,
                     real random=trembleRandom, real fuzz=trembleFuzz()) {
    this.angle=angle;
    this.frequency=frequency;
    this.random=random;
    this.fuzz=fuzz;
  }

  path deform(path g...pair[] magneticPoints) {
    /* Return g as it was handwriting.
       The postcontrols and precontrols of the nodes of g will be rotated
       by an angle proportional to 'angle'(in degrees).
       If frequency < 1, floor(1/frequency) nodes will be added to g to
       increase the control points.
       If frequency>= 1, one point for floor(frequency) will be used to deform
       the path.
       'random' controls the randomized coefficient which will be multiplied
       by 'angle'.
       random is 0 means don't use randomized coefficient;
       The higher 'random' is, the more the trembling is randomized. */
    if(length(g) == 0) return g;
    g=addnodes(g,fuzz*abs(max(g)-min(g))...magneticPoints);
    path tg=g;
    frequency=abs(frequency);
    int f=abs(floor(1/frequency)-1);
    tg=addnodes(tg,f);
    int frequency=floor(frequency);
    int tf=(frequency == 0) ? 1 : frequency;
    int l=length(tg);
    guide og=point(tg,0);
    random=abs(random);
    int rsgn(real x){
      int d2=floor(100*x)-10*floor(10*x);
      if(d2 == 0) return 1;
      return 2 % d2 == 0 ? 1 : -1;
    }
    real randf()
    {
      real or;
      if(random != 0) {
        if(1 % tf != 0) or=0;
        else {
          real ur=unitrand();
          or=rsgn(ur)*angle*(1+ur^(1/random));
        }
      } else or=rsgn(unitrand())*1.5*angle;
      return or;
    }

    real first=randf();
    for(int i=1; i <= l; ++i) {
      pair P=point(tg,i);
      real a=randf();
      pair post=rotate(a,point(tg,i-1))*postcontrol(tg,i-1);
      pair pre=rotate((a+randf())/2,P)*precontrol(tg,i);
      if(i == l && (cyclic(tg)))
        og=og..controls post and pre..cycle;
      else
        og=og..controls post and pre..P;
    }
    return og;
  }
}
