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 ///
38 struct InterpolateTableData(T) if( hasBasicMathOp!T ) { float key; T val; }
39 
40 ///
41 auto lineInterpolate(T)( in InterpolateTableData!T[] tbl, float k, bool line_end=false )
42     if( hasBasicMathOp!T )
43 {
44     enforce( tbl.length > 1 );
45     size_t i = tbl.length - find!"a.key > b"( tbl, k ).length;
46     if( !line_end )
47     {
48         if( i <= 0 ) return tbl[0].val;
49         else if( i >= tbl.length ) return tbl[$-1].val;
50     }
51     else
52     {
53         if( i < 1 ) i = 1;
54         else if( i > tbl.length-1 ) i = tbl.length-1;
55     }
56 
57     auto a = tbl[i-1];
58     auto b = tbl[i];
59     return cast(T)( a.val + ( b.val - a.val ) * ( ( k - a.key ) / ( b.key - a.key ) ) );
60 }
61 
62 ///
63 unittest
64 {
65     alias InterpolateTableData!float TT;
66     auto tbl =
67         [
68         TT( 0, 10 ),
69         TT( 10, 18 ),
70         TT( 25, 20 ),
71         TT( 50, 13 ),
72         TT( 55, 25 )
73         ];
74 
75     assert( lineInterpolate( tbl, 0 ) == 10 );
76     assert( lineInterpolate( tbl, 5 ) == 14 );
77     assert( lineInterpolate( tbl, 10 ) == 18 );
78     assert( lineInterpolate( tbl, -10 ) == 10 );
79     assert( lineInterpolate( tbl, 80 ) == 25 );
80 }
81 
82 unittest
83 {
84     alias InterpolateTableData!double TD;
85     auto tbl =
86         [
87         TD( 0, 0 ),
88         TD( 1, 1 ),
89         TD( 2, 3 ),
90         TD( 3, 4 )
91         ];
92     assert( lineInterpolate( tbl, 5, true ) == 6 );
93     assert( lineInterpolate( tbl, -3, true ) == -3 );
94 }
95 
96 unittest
97 {
98     alias InterpolateTableData!col3 TC;
99     auto tbl =
100         [
101         TC( 0, col3(1,0,0) ),
102         TC( 1, col3(0,1,0) ),
103         TC( 2, col3(0,0,1) )
104         ];
105 
106     assert( lineInterpolate( tbl, -1 ) == col3(1,0,0) );
107     assert( lineInterpolate( tbl, 0 ) == col3(1,0,0) );
108     assert( lineInterpolate( tbl, 0.5 ) == col3(0.5,0.5,0) );
109     assert( lineInterpolate( tbl, 3 ) == col3(0,0,1) );
110 }
111 
112 @property bool canBezierInterpolate(T,F)()
113 { return is( typeof( T.init * F.init + T.init * F.init ) == T ) && isNumeric!F; }
114 
115 pure nothrow auto bezierInterpolation(T,F=float)( in T[] pts, F t )
116 if( canBezierInterpolate!(T,F) )
117 in
118 {
119     assert( t >= 0 && t <= 1 );
120     assert( pts.length > 0 );
121 }
122 body
123 {
124     auto N = pts.length-1;
125     auto omt = 1.0 - t;
126     T res = pts[0] * pow(omt,N) + pts[$-1] * pow(t,N);
127     for( auto i=1; i < N; i++ )
128         res = res + pts[i] * pow(t,i) * pow(omt,N-i) * combination(N,i);
129     return res;
130 }
131 
132 unittest
133 {
134     auto pts = [ vec2(0,0), vec2(2,2), vec2(4,0) ];
135     assert( bezierInterpolation( pts, 0.5 ) == vec2(2,1) );
136 }
137 
138 pure nothrow auto fixBezierSpline(T,F=float)( in T[] pts, size_t steps )
139 {
140     auto step = cast(F)(1.0) / cast(F)(steps-1);
141     auto res = new T[](steps);
142     for( auto i=0; i < steps; i++ )
143         res[i] = bezierInterpolation( pts, step * i );
144     return res;
145 }
146 
147 interface BezierSplineInterpolator(T,F=float)
148 if( canBezierInterpolate!(T,F) )
149 { T[] opCall( in T[] ); }
150 
151 class FixStepsBezierSplineInterpolator(T,F=float) : BezierSplineInterpolator!(T,F)
152 {
153     size_t steps;
154     this( size_t steps ) { this.steps = steps; }
155 
156     T[] opCall( in T[] pts )
157     { return fixBezierSpline!(T,F)( pts, steps ); }
158 }
159 
160 unittest
161 {
162     enum size_t len = 100;
163     auto fbi = new FixStepsBezierSplineInterpolator!(vec2)(len);
164     auto pts = [ vec2(0,0), vec2(2,2), vec2(4,0) ];
165     auto res = fbi( pts );
166     assert( res.length == len );
167 }
168 
169 /+ функция критеря должна быть функцией хевисада +/
170 auto criteriaBezierSpline(T,F=float)( in T[] pts, bool delegate(in T[], in T) criteria, F min_step=1e-5 )
171 if( canBezierInterpolate!(T,F) )
172 in
173 {
174     assert( pts.length > 1 );
175     assert( criteria !is null );
176     assert( min_step > 0 );
177     assert( min_step < cast(F)(.25) / cast(F)(pts.length-1) );
178 }
179 body
180 {
181     F step = cast(F)(.25) / cast(F)(pts.length-1);
182 
183     auto ret = [ bezierInterpolation( pts, 0 ), bezierInterpolation( pts, min_step ) ];
184 
185     auto t = min_step;
186     auto o = step;
187 
188     while( true )
189     {
190         if( 1-t < o ) o = 1-t;
191         auto buf = bezierInterpolation( pts, t+o );
192 
193         if( criteria( ret, buf ) )
194         {
195             t += o;
196             if( t >= 1 ) return ret ~ bezierInterpolation( pts, 1 );
197             continue;
198         }
199         else
200         {
201             o /= 2.0;
202             if( o > min_step ) continue;
203             else t += o;
204         }
205 
206         buf = bezierInterpolation( pts, t );
207 
208         if( t > 1 ) return ret ~ bezierInterpolation( pts, 1 );
209 
210         ret ~= buf;
211         o = step;
212     }
213 }
214 
215 /+ функция критеря должна быть функцией хевисада +/
216 auto filterBezierSpline(T,F=float)( in T[] pts, bool delegate(in T[], in T) criteria )
217 if( canBezierInterpolate!(T,F) )
218 in
219 {
220     assert( pts.length > 1 );
221     assert( criteria !is null );
222 }
223 body
224 {
225     auto ret = [ pts[0] ];
226 
227     for( auto i = 1; i < pts.length; i++ )
228     {
229         while( i < pts.length && criteria(ret, pts[i]) ) i++;
230 
231         if( ret[$-1] == pts[i-1] )
232             ret ~= pts[i];
233         else
234             ret ~= pts[i-1];
235     }
236 
237     if( ret[$-1] != pts[$-1] )
238         ret ~= pts[$-1];
239 
240     return ret;
241 }
242 
243 auto angleSplineCriteria(T)( float angle )
244     if( isCompatibleVector!(2,float,T) || isCompatibleVector!(3,float,T) )
245 {
246     auto cos_angle = cos( angle );
247     return ( in T[] accepted, in T newpoint )
248     {
249         if( accepted.length < 2 ) return false;
250 
251         auto cc = [ accepted[$-2], accepted[$-1], newpoint ];
252 
253         auto a = (cc[1]-cc[0]).e;
254         auto b = (cc[2]-cc[1]).e;
255 
256         return dot(a,b) >= cos_angle;
257     };
258 }
259 
260 auto lengthSplineCriteria(T)( float len )
261 if( isCompatibleVector!(2,float,T) || isCompatibleVector!(3,float,T) )
262 in{ assert( len > 0 ); } body
263 {
264     return ( in T[] accepted, in T newpoint )
265     { return (accepted[$-1] - newpoint).len2 <= len*len; };
266 }
267 
268 unittest
269 {
270     auto pts = [ vec2(0,0), vec2(5,2), vec2(-1,2), vec2(4,0) ];
271     auto pp = criteriaBezierSpline( pts, angleSplineCriteria!vec2(0.05) );
272 
273     assert( pp.length > pts.length );
274     assert( pp[0] == pts[0] );
275     assert( pp[$-1] == pts[$-1] );
276 }
277 
278 unittest
279 {
280     auto pts = [ vec2(0,0), vec2(5,2), vec2(-1,2), vec2(4,0) ];
281     auto pp = filterBezierSpline( fixBezierSpline( pts, 1000 ), lengthSplineCriteria!vec2(0.05) );
282 
283     assert( pp.length > pts.length );
284     assert( pp[0] == pts[0] );
285     assert( pp[$-1] == pts[$-1] );
286 }