First stab at adding genete's suggested 'Curve Warp' layer. It's pretty slow at...
[synfig.git] / synfig-core / trunk / src / modules / lyr_std / curvewarp.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file curvewarp.cpp
3 **      \brief Implementation of the "Curve Warp" layer
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **      Copyright (c) 2007-2008 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 **
22 ** === N O T E S ===========================================================
23 **
24 ** ========================================================================= */
25
26 /* === H E A D E R S ======================================================= */
27
28 #ifdef USING_PCH
29 #       include "pch.h"
30 #else
31 #ifdef HAVE_CONFIG_H
32 #       include <config.h>
33 #endif
34
35 #include "curvewarp.h"
36
37 #include <synfig/string.h>
38 #include <synfig/time.h>
39 #include <synfig/context.h>
40 #include <synfig/paramdesc.h>
41 #include <synfig/renddesc.h>
42 #include <synfig/surface.h>
43 #include <synfig/value.h>
44 #include <synfig/valuenode.h>
45 #include <ETL/bezier>
46 #include <ETL/hermite>
47 #include <ETL/calculus>
48
49 #endif
50
51 /* === M A C R O S ========================================================= */
52
53 #define FAKE_TANGENT_STEP 0.000001
54
55 /* === G L O B A L S ======================================================= */
56
57 SYNFIG_LAYER_INIT(CurveWarp);
58 SYNFIG_LAYER_SET_NAME(CurveWarp,"curve_warp");
59 SYNFIG_LAYER_SET_LOCAL_NAME(CurveWarp,N_("Curve Warp"));
60 SYNFIG_LAYER_SET_CATEGORY(CurveWarp,N_("Distortions"));
61 SYNFIG_LAYER_SET_VERSION(CurveWarp,"0.0");
62 SYNFIG_LAYER_SET_CVS_ID(CurveWarp,"$Id$");
63
64 /* === P R O C E D U R E S ================================================= */
65
66 inline float calculate_distance(const std::vector<synfig::BLinePoint>& bline)
67 {
68         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
69         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
70
71         float dist(0);
72
73         if (bline.empty()) return dist;
74
75         next=bline.begin();
76         iter=next++;
77
78         for(;next!=end;iter=next++)
79         {
80                 // Setup the curve
81                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
82                 dist+=curve.length();
83         }
84
85         return dist;
86 }
87
88 std::vector<synfig::BLinePoint>::const_iterator
89 find_closest_to_bline(bool fast, const std::vector<synfig::BLinePoint>& bline,const Point& p,float& t, float& len, bool& extreme)
90 {
91         std::vector<synfig::BLinePoint>::const_iterator iter,next,ret;
92         std::vector<synfig::BLinePoint>::const_iterator end(bline.end());
93
94         ret=bline.end();
95         float dist(100000000000.0);
96         next=bline.begin();
97         float best_pos(0), best_len(0);
98         etl::hermite<Vector> best_curve;
99         iter=next++;
100         Point bp;
101         float total_len(0);
102         bool first = true, last = false;
103         extreme = false;
104
105         // printf("  loop\n");
106         for(;next!=end;iter=next++)
107         {
108                 // printf("    top\n");
109                 // Setup the curve
110                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
111                 float thisdist(0);
112                 last = false;
113
114                 if (fast)
115                 {
116 #define POINT_CHECK(x) bp=curve(x);     thisdist=(bp-p).mag_squared(); if(thisdist<dist) { extreme = (first&&x<0.01); ret=iter; best_len = total_len; dist=thisdist; best_curve=curve; last=true; best_pos=x;}
117                         POINT_CHECK(0.0001);  POINT_CHECK((1.0/6)); POINT_CHECK((2.0/6)); POINT_CHECK((3.0/6));
118                         POINT_CHECK((4.0/6)); POINT_CHECK((5.0/6)); POINT_CHECK(0.9999);
119                 }
120                 else
121                 {
122                         float pos = curve.find_closest(fast, p);
123                         thisdist=(curve(pos)-p).mag_squared();
124                         if(thisdist<dist)
125                         {
126                                 extreme = (first && pos == 0);
127                                 ret=iter;
128                                 dist=thisdist;
129                                 best_pos = pos;
130                                 best_curve = curve;
131                                 // printf("set best_curve\n");
132                                 best_len = total_len;
133                                 last = true;
134                                 // printf("      *last\n");
135                         }
136                 }
137                 total_len += curve.length();
138                 first = false;
139         }
140         // printf("  after\n");
141
142         t = best_pos;
143         if (fast)
144         {
145                 len = best_len + best_curve.find_distance(0,best_curve.find_closest(fast, p));
146                 // printf("fast set len = %f + %f = %f\n", best_len, best_curve.find_distance(0,best_curve.find_closest(fast, p)), len);
147                 if (last && t > .99) extreme = true;
148         }
149         else
150         {
151                 len = best_len + best_curve.find_distance(0,best_pos);
152                 // printf("slow set len = %f + %f = %f\n", best_len, best_curve.find_distance(0,best_pos), len);
153                 // printf("find_distance from 0 to %f is %f\n", best_pos, best_curve.find_distance(0,best_pos));
154                 // if (last) printf("last\n");
155
156                 if (last && t > .999)
157                 {
158                         extreme = true;
159                         // printf("extreme end\n");
160                 }
161         }
162         return ret;
163 }
164
165 /* === M E T H O D S ======================================================= */
166
167 inline void
168 CurveWarp::sync()
169 {
170         curve_length_=calculate_distance(bline);
171         perp_ = (end_point - start_point).perp().norm();
172         // printf("curve_length_ = %f\n", curve_length_);
173 }
174
175 CurveWarp::CurveWarp():
176         origin(0,0),
177         width(1),
178         start_point(-3,-1),
179         end_point(3,1),
180         fast(true)
181 {
182         bline.push_back(BLinePoint());
183         bline.push_back(BLinePoint());
184         bline[0].set_vertex(Point(-2.5,0));
185         bline[1].set_vertex(Point(2.5,0));
186         bline[0].set_tangent(Point(1, 1));
187         bline[1].set_tangent(Point(1, -1));
188         bline[0].set_width(1.0f);
189         bline[1].set_width(1.0f);
190
191         sync();
192 }
193
194 inline Point
195 CurveWarp::transform(const Point &point_, int quality, float supersample)const
196 {
197         Vector tangent;
198         Vector diff;
199         Point p1;
200         Real thickness;
201         Real dist;
202         bool edge_case = false;
203         float len(0);
204         bool extreme;
205         float t;
206
207         if(bline.size()==0)
208                 return Point();
209         else if(bline.size()==1)
210         {
211                 tangent=bline.front().get_tangent1();
212                 p1=bline.front().get_vertex();
213                 thickness=bline.front().get_width();
214                 t = 0.5;
215                 extreme = false;
216         }
217         else
218         {
219                 Point point(point_-origin);
220
221                 std::vector<synfig::BLinePoint>::const_iterator iter,next;
222
223                 // Figure out the BLinePoint we will be using,
224                 next=find_closest_to_bline(fast,bline,point,t,len,extreme);
225
226                 iter=next++;
227                 if(next==bline.end()) next=bline.begin();
228
229                 // Setup the curve
230                 etl::hermite<Vector> curve(iter->get_vertex(), next->get_vertex(), iter->get_tangent2(), next->get_tangent1());
231
232                 // Setup the derivative function
233                 etl::derivative<etl::hermite<Vector> > deriv(curve);
234
235                 int search_iterations(7);
236
237                 if(quality<=6)search_iterations=7;
238                 else if(quality<=7)search_iterations=6;
239                 else if(quality<=8)search_iterations=5;
240                 else search_iterations=4;
241
242                 // Figure out the closest point on the curve
243                 if (fast) t = curve.find_closest(fast, point,search_iterations);
244
245                 // Calculate our values
246                 p1=curve(t);                     // the closest point on the curve
247                 tangent=deriv(t);                // the tangent at that point
248
249                 // if the point we're nearest to is at either end of the
250                 // bline, our distance from the curve is the distance from the
251                 // point on the curve.  we need to know which side of the
252                 // curve we're on, so find the average of the two tangents at
253                 // this point
254                 if (t<0.00001 || t>0.99999)
255                 {
256                         bool zero_tangent = (tangent[0] == 0 && tangent[1] == 0);
257
258                         if (t<0.5)
259                         {
260                                 if (iter->get_split_tangent_flag() || zero_tangent)
261                                 {
262                                         // fake the current tangent if we need to
263                                         if (zero_tangent) tangent = curve(FAKE_TANGENT_STEP) - curve(0);
264
265                                         // calculate the other tangent
266                                         Vector other_tangent(iter->get_tangent1());
267                                         if (other_tangent[0] == 0 && other_tangent[1] == 0)
268                                         {
269                                                 // find the previous blinepoint
270                                                 std::vector<synfig::BLinePoint>::const_iterator prev;
271                                                 if (iter != bline.begin()) (prev = iter)--;
272                                                 else prev = iter;
273
274                                                 etl::hermite<Vector> other_curve(prev->get_vertex(), iter->get_vertex(), prev->get_tangent2(), iter->get_tangent1());
275                                                 other_tangent = other_curve(1) - other_curve(1-FAKE_TANGENT_STEP);
276                                         }
277
278                                         // normalise and sum the two tangents
279                                         tangent=(other_tangent.norm()+tangent.norm());
280                                         edge_case=true;
281                                 }
282                         }
283                         else
284                         {
285                                 if (next->get_split_tangent_flag() || zero_tangent)
286                                 {
287                                         // fake the current tangent if we need to
288                                         if (zero_tangent) tangent = curve(1) - curve(1-FAKE_TANGENT_STEP);
289
290                                         // calculate the other tangent
291                                         Vector other_tangent(next->get_tangent2());
292                                         if (other_tangent[0] == 0 && other_tangent[1] == 0)
293                                         {
294                                                 // find the next blinepoint
295                                                 std::vector<synfig::BLinePoint>::const_iterator next2(next);
296                                                 if (++next2 == bline.end())
297                                                         next2 = next;
298
299                                                 etl::hermite<Vector> other_curve(next->get_vertex(), next2->get_vertex(), next->get_tangent2(), next2->get_tangent1());
300                                                 other_tangent = other_curve(FAKE_TANGENT_STEP) - other_curve(0);
301                                         }
302
303                                         // normalise and sum the two tangents
304                                         tangent=(other_tangent.norm()+tangent.norm());
305                                         edge_case=true;
306                                 }
307                         }
308                 }
309                 tangent = tangent.norm();
310
311                 // the width of the bline at the closest point on the curve
312                 thickness=(next->get_width()-iter->get_width())*t+iter->get_width();
313         }
314
315         if (extreme)
316         {
317                 Vector tangent, perp;
318
319                 if (t < 0.5)
320                 {
321                         synfig::BLinePoint start(bline[0]);
322                         // Point a(start.get_vertex());
323                         tangent = start.get_tangent1().norm();
324                         diff = tangent.perp()*thickness*width;
325                         len = (point_-origin - p1)*tangent;
326                 }
327                 else
328                 {
329                         std::vector<synfig::BLinePoint>::const_iterator iter;
330                         iter=bline.end();
331                         iter--;
332                         tangent = iter->get_tangent2().norm();
333                         diff = tangent.perp()*thickness*width;
334                         len = (point_-origin - p1)*tangent + curve_length_;
335                 }
336         }
337         else if (edge_case)
338         {
339                 diff=(p1-(point_-origin));
340                 if(diff*tangent.perp()<0) diff=-diff;
341                 diff=diff.norm()*thickness*width;
342         }
343         else
344                 diff=tangent.perp()*thickness*width;
345
346                 const Real mag(diff.inv_mag());
347                 supersample=supersample*mag;
348                 diff*=mag*mag;
349                 dist=(point_-origin - p1)*diff;
350
351         len /= curve_length_;
352
353         // printf("len %6.2f dist %6.2f\n", len, dist);
354         return ((start_point + (end_point - start_point) * len) +
355                         perp_ * dist);
356 }
357
358 float
359 CurveWarp::calc_supersample(const synfig::Point &/*x*/, float pw,float /*ph*/)const
360 {
361         return pw;
362 }
363
364 synfig::Layer::Handle
365 CurveWarp::hit_check(synfig::Context context, const synfig::Point &point)const
366 {
367         return context.hit_check(transform(point));
368 }
369
370 bool
371 CurveWarp::set_param(const String & param, const ValueBase &value)
372 {
373         IMPORT(origin);
374         IMPORT(start_point);
375         IMPORT(end_point);
376         IMPORT(fast);
377         IMPORT(width);
378
379         if(param=="bline" && value.get_type()==ValueBase::TYPE_LIST)
380         {
381                 bline=value;
382                 sync();
383
384                 return true;
385         }
386
387         IMPORT_AS(origin,"offset");
388
389         return false;
390 }
391
392 ValueBase
393 CurveWarp::get_param(const String & param)const
394 {
395         EXPORT(origin);
396         EXPORT(start_point);
397         EXPORT(end_point);
398         EXPORT(bline);
399         EXPORT(fast);
400         EXPORT(width);
401
402         EXPORT_NAME();
403         EXPORT_VERSION();
404
405         return ValueBase();
406 }
407
408 Layer::Vocab
409 CurveWarp::get_param_vocab()const
410 {
411         Layer::Vocab ret;
412
413         ret.push_back(ParamDesc("origin")
414                                   .set_local_name(_("Origin")));
415
416         ret.push_back(ParamDesc("width")
417                                   .set_local_name(_("Width")));
418
419         ret.push_back(ParamDesc("start_point")
420                                   .set_local_name(_("Start Point")));
421
422         ret.push_back(ParamDesc("end_point")
423                                   .set_local_name(_("End Point")));
424
425         ret.push_back(ParamDesc("bline")
426                                   .set_local_name(_("Vertices"))
427                                   .set_origin("origin")
428                                   .set_hint("width")
429                                   .set_description(_("A list of BLine Points")));
430
431         ret.push_back(ParamDesc("fast")
432                                   .set_local_name(_("Fast")));
433
434         return ret;
435 }
436
437 Color
438 CurveWarp::get_color(Context context, const Point &point)const
439 {
440         return context.get_color(transform(point));
441 }
442
443 bool
444 CurveWarp::accelerated_render(Context context,Surface *surface,int quality, const RendDesc &renddesc, ProgressCallback *cb)const
445 {
446         SuperCallback supercb(cb,0,9500,10000);
447
448         if(!context.accelerated_render(surface,quality,renddesc,&supercb))
449                 return false;
450
451         int x,y;
452
453         const Real pw(renddesc.get_pw()),ph(renddesc.get_ph());
454         Point pos;
455         Point tl(renddesc.get_tl());
456         const int w(surface->get_w());
457         const int h(surface->get_h());
458
459         for(y=0,pos[1]=tl[1];y<h;y++,pos[1]+=ph)
460                 for(x=0,pos[0]=tl[0];x<w;x++,pos[0]+=pw)
461                         if (1)
462                                 (*surface)[y][x]=context.get_color(transform(pos));
463                         else
464                                 (*surface)[y][x]=context.get_color(transform(pos));
465
466         // Mark our progress as finished
467         if(cb && !cb->amount_complete(10000,10000))
468                 return false;
469
470         return true;
471 }