summaryrefslogtreecommitdiff
path: root/graphics/asymptote/base/trembling.asy
blob: f9f0d6a747420d70fcdbfd70273da4a04ff6c6d9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// 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;
  }
}