1 /+
2 The MIT License (MIT)
3 
4     Copyright (c) <2013> <Oleg Butko (deviator), Anton Akzhigitov (Akzwar)>
5 
6     Permission is hereby granted, free of charge, to any person obtaining a copy
7     of this software and associated documentation files (the "Software"), to deal
8     in the Software without restriction, including without limitation the rights
9     to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10     copies of the Software, and to permit persons to whom the Software is
11     furnished to do so, subject to the following conditions:
12 
13     The above copyright notice and this permission notice shall be included in
14     all copies or substantial portions of the Software.
15 
16     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17     IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18     FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19     AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20     LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22     THE SOFTWARE.
23 +/
24 
25 module des.math.method.approx.interp;
26 
27 import std.algorithm;
28 import std.exception;
29 import std.math;
30 import std.traits;
31 
32 version(unittest) import des.math.linear.vector;
33 
34 import des.math.combin;
35 public import des.math.basic.traits;
36 
37 struct InterpolateTableData(T) if( hasBasicMathOp!T ) { float key; T val; }
38 
39 auto lineInterpolate(T)( in InterpolateTableData!T[] tbl, float k, bool line_end=false )
40     if( hasBasicMathOp!T )
41 {
42     enforce( tbl.length > 1 );
43     size_t i = tbl.length - find!"a.key > b"( tbl, k ).length;
44     if( !line_end )
45     {
46         if( i <= 0 ) return tbl[0].val;
47         else if( i >= tbl.length ) return tbl[$-1].val;
48     }
49     else
50     {
51         if( i < 1 ) i = 1;
52         else if( i > tbl.length-1 ) i = tbl.length-1;
53     }
54 
55     auto a = tbl[i-1];
56     auto b = tbl[i];
57     return cast(T)( a.val + ( b.val - a.val ) * ( ( k - a.key ) / ( b.key - a.key ) ) );
58 }
59 
60 unittest
61 {
62     alias InterpolateTableData!float TT;
63     auto tbl =
64         [
65         TT( 0, 10 ),
66         TT( 10, 18 ),
67         TT( 25, 20 ),
68         TT( 50, 13 ),
69         TT( 55, 25 )
70         ];
71 
72     assert( lineInterpolate( tbl, 0 ) == 10 );
73     assert( lineInterpolate( tbl, 5 ) == 14 );
74     assert( lineInterpolate( tbl, 10 ) == 18 );
75     assert( lineInterpolate( tbl, -10 ) == 10 );
76     assert( lineInterpolate( tbl, 80 ) == 25 );
77 }
78 
79 unittest
80 {
81     alias InterpolateTableData!double TD;
82     auto tbl =
83         [
84         TD( 0, 0 ),
85         TD( 1, 1 ),
86         TD( 2, 3 ),
87         TD( 3, 4 )
88         ];
89     assert( lineInterpolate( tbl, 5, true ) == 6 );
90     assert( lineInterpolate( tbl, -3, true ) == -3 );
91 }
92 
93 unittest
94 {
95     alias InterpolateTableData!col3 TC;
96     auto tbl =
97         [
98         TC( 0, col3(1,0,0) ),
99         TC( 1, col3(0,1,0) ),
100         TC( 2, col3(0,0,1) )
101         ];
102 
103     assert( lineInterpolate( tbl, -1 ) == col3(1,0,0) );
104     assert( lineInterpolate( tbl, 0 ) == col3(1,0,0) );
105     assert( lineInterpolate( tbl, 0.5 ) == col3(0.5,0.5,0) );
106     assert( lineInterpolate( tbl, 3 ) == col3(0,0,1) );
107 }
108 
109 @property bool canBezierInterpolate(T,F)()
110 { return is( typeof( T.init * F.init + T.init * F.init ) == T ) && isNumeric!F; }
111 
112 pure nothrow auto bezierInterpolation(T,F=float)( in T[] pts, F t )
113 if( canBezierInterpolate!(T,F) )
114 in
115 {
116     assert( t >= 0 && t <= 1 );
117     assert( pts.length > 0 );
118 }
119 body
120 {
121     auto N = pts.length-1;
122     auto omt = 1.0 - t;
123     T res = pts[0] * pow(omt,N) + pts[$-1] * pow(t,N);
124     for( auto i=1; i < N; i++ )
125         res = res + pts[i] * pow(t,i) * pow(omt,N-i) * combination(N,i);
126     return res;
127 }
128 
129 unittest
130 {
131     auto pts = [ vec2(0,0), vec2(2,2), vec2(4,0) ];
132     assert( bezierInterpolation( pts, 0.5 ) == vec2(2,1) );
133 }
134 
135 pure nothrow auto fixBezierSpline(T,F=float)( in T[] pts, size_t steps )
136 {
137     auto step = cast(F)(1.0) / cast(F)(steps-1);
138     auto res = new T[](steps);
139     for( auto i=0; i < steps; i++ )
140         res[i] = bezierInterpolation( pts, step * i );
141     return res;
142 }
143 
144 interface BezierSplineInterpolator(T,F=float)
145 if( canBezierInterpolate!(T,F) )
146 { T[] opCall( in T[] ); }
147 
148 class FixStepsBezierSplineInterpolator(T,F=float) : BezierSplineInterpolator!(T,F)
149 {
150     size_t steps;
151     this( size_t steps ) { this.steps = steps; }
152 
153     T[] opCall( in T[] pts )
154     { return fixBezierSpline!(T,F)( pts, steps ); }
155 }
156 
157 unittest
158 {
159     enum size_t len = 100;
160     auto fbi = new FixStepsBezierSplineInterpolator!(vec2)(len);
161     auto pts = [ vec2(0,0), vec2(2,2), vec2(4,0) ];
162     auto res = fbi( pts );
163     assert( res.length == len );
164 }
165 
166 /+ функция критеря должна быть функцией хевисада +/
167 auto criteriaBezierSpline(T,F=float)( in T[] pts, bool delegate(in T[], in T) criteria, F min_step=1e-5 )
168 if( canBezierInterpolate!(T,F) )
169 in
170 {
171     assert( pts.length > 1 );
172     assert( criteria !is null );
173     assert( min_step > 0 );
174     assert( min_step < cast(F)(.25) / cast(F)(pts.length-1) );
175 }
176 body
177 {
178     F step = cast(F)(.25) / cast(F)(pts.length-1);
179 
180     auto ret = [ bezierInterpolation( pts, 0 ), bezierInterpolation( pts, min_step ) ];
181 
182     auto t = min_step;
183     auto o = step;
184 
185     while( true )
186     {
187         if( 1-t < o ) o = 1-t;
188         auto buf = bezierInterpolation( pts, t+o );
189 
190         if( criteria( ret, buf ) )
191         {
192             t += o;
193             if( t >= 1 ) return ret ~ bezierInterpolation( pts, 1 );
194             continue;
195         }
196         else
197         {
198             o /= 2.0;
199             if( o > min_step ) continue;
200             else t += o;
201         }
202 
203         buf = bezierInterpolation( pts, t );
204 
205         if( t > 1 ) return ret ~ bezierInterpolation( pts, 1 );
206 
207         ret ~= buf;
208         o = step;
209     }
210 }
211 
212 /+ функция критеря должна быть функцией хевисада +/
213 auto filterBezierSpline(T,F=float)( in T[] pts, bool delegate(in T[], in T) criteria )
214 if( canBezierInterpolate!(T,F) )
215 in
216 {
217     assert( pts.length > 1 );
218     assert( criteria !is null );
219 }
220 body
221 {
222     auto ret = [ pts[0] ];
223 
224     for( auto i = 1; i < pts.length; i++ )
225     {
226         while( i < pts.length && criteria(ret, pts[i]) ) i++;
227 
228         if( ret[$-1] == pts[i-1] )
229             ret ~= pts[i];
230         else
231             ret ~= pts[i-1];
232     }
233 
234     if( ret[$-1] != pts[$-1] )
235         ret ~= pts[$-1];
236 
237     return ret;
238 }
239 
240 auto angleSplineCriteria(T)( float angle )
241     if( isCompatibleVector!(2,float,T) || isCompatibleVector!(3,float,T) )
242 {
243     auto cos_angle = cos( angle );
244     return ( in T[] accepted, in T newpoint )
245     {
246         if( accepted.length < 2 ) return false;
247 
248         auto cc = [ accepted[$-2], accepted[$-1], newpoint ];
249 
250         auto a = (cc[1]-cc[0]).e;
251         auto b = (cc[2]-cc[1]).e;
252 
253         return dot(a,b) >= cos_angle;
254     };
255 }
256 
257 auto lengthSplineCriteria(T)( float len )
258 if( isCompatibleVector!(2,float,T) || isCompatibleVector!(3,float,T) )
259 in{ assert( len > 0 ); } body
260 {
261     return ( in T[] accepted, in T newpoint )
262     { return (accepted[$-1] - newpoint).len2 <= len*len; };
263 }
264 
265 unittest
266 {
267     auto pts = [ vec2(0,0), vec2(5,2), vec2(-1,2), vec2(4,0) ];
268     auto pp = criteriaBezierSpline( pts, angleSplineCriteria!vec2(0.05) );
269 
270     assert( pp.length > pts.length );
271     assert( pp[0] == pts[0] );
272     assert( pp[$-1] == pts[$-1] );
273 }
274 
275 unittest
276 {
277     auto pts = [ vec2(0,0), vec2(5,2), vec2(-1,2), vec2(4,0) ];
278     auto pp = filterBezierSpline( fixBezierSpline( pts, 1000 ), lengthSplineCriteria!vec2(0.05) );
279 
280     assert( pp.length > pts.length );
281     assert( pp[0] == pts[0] );
282     assert( pp[$-1] == pts[$-1] );
283 }