1 /* === S Y N F I G ========================================================= */
3 ** \brief Implementation of the "Outline" layer
8 ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 ** Copyright (c) 2007 Chris Moore
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.
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.
22 /* ========================================================================= */
24 //! \note This whole file should be rewritten at some point (darco)
26 /* === H E A D E R S ======================================================= */
36 #include <synfig/string.h>
37 #include <synfig/time.h>
38 #include <synfig/context.h>
39 #include <synfig/paramdesc.h>
40 #include <synfig/renddesc.h>
41 #include <synfig/surface.h>
42 #include <synfig/value.h>
43 #include <synfig/valuenode.h>
45 #include <ETL/calculus>
47 #include <ETL/hermite>
50 #include <synfig/valuenode_bline.h>
56 /* === M A C R O S ========================================================= */
59 #define ROUND_END_FACTOR (4)
60 #define CUSP_THRESHOLD (0.40)
61 #define SPIKE_AMOUNT (4)
62 #define NO_LOOP_COOKIE synfig::Vector(84951305,7836658)
63 #define EPSILON (0.000000001)
64 #define CUSP_TANGENT_ADJUST (0.025)
66 /* === G L O B A L S ======================================================= */
68 SYNFIG_LAYER_INIT(Outline
);
69 SYNFIG_LAYER_SET_NAME(Outline
,"outline");
70 SYNFIG_LAYER_SET_LOCAL_NAME(Outline
,N_("Outline"));
71 SYNFIG_LAYER_SET_CATEGORY(Outline
,N_("Geometry"));
72 SYNFIG_LAYER_SET_VERSION(Outline
,"0.2");
73 SYNFIG_LAYER_SET_CVS_ID(Outline
,"$Id$");
75 /* === P R O C E D U R E S ================================================= */
77 // This function was adapted from what was
78 // described on http://www.whisqu.se/per/docs/math28.htm
79 Point
line_intersection(
86 const float& x0(p1
[0]);
87 const float& y0(p1
[1]);
89 const float x1(p1
[0]+t1
[0]);
90 const float y1(p1
[1]+t1
[1]);
92 const float& x2(p2
[0]);
93 const float& y2(p2
[1]);
95 const float x3(p2
[0]+t2
[0]);
96 const float y3(p2
[1]+t2
[1]);
98 const float near_infinity((float)1e+10);
100 float m1
,m2
; // the slopes of each line
102 // compute slopes, note the kluge for infinity, however, this will
106 m1
= (y1
-y0
)/(x1
-x0
);
111 m2
= (y3
-y2
)/(x3
-x2
);
118 const float b1(-1.0f
);
119 const float b2(-1.0f
);
120 const float c1(y0
-m1
*x0
);
121 const float c2(y2
-m2
*x2
);
123 // compute the inverse of the determinate
124 const float det_inv(1.0f
/(a1
*b2
- a2
*b1
));
126 // use Kramers rule to compute the intersection
128 ((b1
*c2
- b2
*c1
)*det_inv
),
129 ((a2
*c1
- a1
*c2
)*det_inv
)
131 } // end Intersect_Lines
133 /* === M E T H O D S ======================================================= */
145 homogeneous_width
=true;
148 vector
<BLinePoint
> bline_point_list
;
149 bline_point_list
.push_back(BLinePoint());
150 bline_point_list
.push_back(BLinePoint());
151 bline_point_list
.push_back(BLinePoint());
152 bline_point_list
[0].set_vertex(Point(0,1));
153 bline_point_list
[1].set_vertex(Point(0,-1));
154 bline_point_list
[2].set_vertex(Point(1,0));
155 bline_point_list
[0].set_tangent(bline_point_list
[1].get_vertex()-bline_point_list
[2].get_vertex()*0.5f
);
156 bline_point_list
[1].set_tangent(bline_point_list
[2].get_vertex()-bline_point_list
[0].get_vertex()*0.5f
);
157 bline_point_list
[2].set_tangent(bline_point_list
[0].get_vertex()-bline_point_list
[1].get_vertex()*0.5f
);
158 bline_point_list
[0].set_width(1.0f
);
159 bline_point_list
[1].set_width(1.0f
);
160 bline_point_list
[2].set_width(1.0f
);
161 bline
=bline_point_list
;
167 /*! The Sync() function takes the values
168 ** and creates a polygon to be rendered
169 ** with the polygon layer.
176 if (!bline
.get_list().size())
178 synfig::warning(string("Outline::sync():")+N_("No vertices in outline " + string("\"") + get_description() + string("\"")));
185 const bool loop(bline
.get_loop());
186 const vector
<synfig::BLinePoint
> bline_(bline
.get_list().begin(),bline
.get_list().end());
189 vector
<BLinePoint
>::const_iterator
193 const vector
<BLinePoint
>::const_iterator
210 Vector first_tangent
=bline
.front().get_tangent2();
211 Vector last_tangent
=iter
->get_tangent1();
213 // if we are looped and drawing sharp cusps, we'll need a value for the incoming tangent
214 if (loop
&& sharp_cusps
&& last_tangent
.is_equal_to(Vector::zero()))
216 hermite
<Vector
> curve((iter
-1)->get_vertex(), iter
->get_vertex(), (iter
-1)->get_tangent2(), iter
->get_tangent1());
217 const derivative
< hermite
<Vector
> > deriv(curve
);
218 last_tangent
=deriv(1.0-CUSP_TANGENT_ADJUST
);
221 // `first' is for making the cusps; don't do that for the first point if we're not looped
222 for(bool first
=!loop
; next
!=end
; iter
=next
++)
224 Vector
prev_t(iter
->get_tangent1());
225 Vector
iter_t(iter
->get_tangent2());
226 Vector
next_t(next
->get_tangent1());
228 bool split_flag(iter
->get_split_tangent_flag());
230 // if iter.t2 == 0 and next.t1 == 0, this is a straight line
231 if(iter_t
.is_equal_to(Vector::zero()) && next_t
.is_equal_to(Vector::zero()))
233 iter_t
=next_t
=next
->get_vertex()-iter
->get_vertex();
236 // if the two points are on top of each other, ignore this segment
237 // leave `first' true if was before
238 if (iter_t
.is_equal_to(Vector::zero()))
243 hermite
<Vector
> curve(
251 iter_w((iter
->get_width()*width
)*0.5f
+expand
),
252 next_w((next
->get_width()*width
)*0.5f
+expand
);
254 const derivative
< hermite
<Vector
> > deriv(curve
);
257 first_tangent
= deriv(CUSP_TANGENT_ADJUST
);
259 // Make cusps as necessary
260 if(!first
&& sharp_cusps
&& split_flag
&& (!prev_t
.is_equal_to(iter_t
) || iter_t
.is_equal_to(Vector::zero())) && !last_tangent
.is_equal_to(Vector::zero()))
262 Vector
curr_tangent(deriv(CUSP_TANGENT_ADJUST
));
264 const Vector
t1(last_tangent
.perp().norm());
265 const Vector
t2(curr_tangent
.perp().norm());
267 Real
cross(t1
*t2
.perp());
268 Real
perp((t1
-t2
).mag());
269 if(cross
>CUSP_THRESHOLD
)
271 const Point
p1(iter
->get_vertex()+t1
*iter_w
);
272 const Point
p2(iter
->get_vertex()+t2
*iter_w
);
274 side_a
.push_back(line_intersection(p1
,last_tangent
,p2
,curr_tangent
));
276 else if(cross
<-CUSP_THRESHOLD
)
278 const Point
p1(iter
->get_vertex()-t1
*iter_w
);
279 const Point
p2(iter
->get_vertex()-t2
*iter_w
);
281 side_b
.push_back(line_intersection(p1
,last_tangent
,p2
,curr_tangent
));
283 else if(cross
>0 && perp
>1)
285 float amount(max(0.0f
,(float)(cross
/CUSP_THRESHOLD
))*(SPIKE_AMOUNT
-1)+1);
287 side_a
.push_back(iter
->get_vertex()+(t1
+t2
).norm()*iter_w
*amount
);
289 else if(cross
<0 && perp
>1)
291 float amount(max(0.0f
,(float)(-cross
/CUSP_THRESHOLD
))*(SPIKE_AMOUNT
-1)+1);
293 side_b
.push_back(iter
->get_vertex()-(t1
+t2
).norm()*iter_w
*amount
);
298 if(homogeneous_width
)
300 const float length(curve
.length());
303 for(float n
=0.0f
;n
<0.999999f
;n
+=1.0f
/SAMPLES
)
305 const Vector
d(deriv(n
>CUSP_TANGENT_ADJUST
?n
:CUSP_TANGENT_ADJUST
).perp().norm());
306 const Vector
p(curve(n
));
309 dist
+=(p
-lastpoint
).mag();
311 const float w(((next_w
-iter_w
)*(dist
/length
)+iter_w
));
313 side_a
.push_back(p
+d
*w
);
314 side_b
.push_back(p
-d
*w
);
320 for(float n
=0.0f
;n
<0.999999f
;n
+=1.0f
/SAMPLES
)
322 const Vector
d(deriv(n
>CUSP_TANGENT_ADJUST
?n
:CUSP_TANGENT_ADJUST
).perp().norm());
323 const Vector
p(curve(n
));
324 const float w(((next_w
-iter_w
)*n
+iter_w
));
326 side_a
.push_back(p
+d
*w
);
327 side_b
.push_back(p
-d
*w
);
329 last_tangent
=deriv(1.0-CUSP_TANGENT_ADJUST
);
330 side_a
.push_back(curve(1.0)+last_tangent
.perp().norm()*next_w
);
331 side_b
.push_back(curve(1.0)-last_tangent
.perp().norm()*next_w
);
338 reverse(side_b
.begin(),side_b
.end());
344 // Insert code for adding end tip
345 if(round_tip
[1] && !loop
&& side_a
.size())
347 // remove the last point
350 const Point
vertex(bline
.back().get_vertex());
351 const Vector
tangent(last_tangent
.norm());
352 const float w((bline
.back().get_width()*width
)*0.5f
+expand
);
354 hermite
<Vector
> curve(
355 vertex
+tangent
.perp()*w
,
356 vertex
-tangent
.perp()*w
,
357 tangent
*w
*ROUND_END_FACTOR
,
358 -tangent
*w
*ROUND_END_FACTOR
361 for(float n
=0.0f
;n
<0.999999f
;n
+=1.0f
/SAMPLES
)
362 side_a
.push_back(curve(n
));
365 for(;!side_b
.empty();side_b
.pop_back())
366 side_a
.push_back(side_b
.back());
368 // Insert code for adding begin tip
369 if(round_tip
[0] && !loop
&& side_a
.size())
371 // remove the last point
374 const Point
vertex(bline
.front().get_vertex());
375 const Vector
tangent(first_tangent
.norm());
376 const float w((bline
.front().get_width()*width
)*0.5f
+expand
);
378 hermite
<Vector
> curve(
379 vertex
-tangent
.perp()*w
,
380 vertex
+tangent
.perp()*w
,
381 -tangent
*w
*ROUND_END_FACTOR
,
382 tangent
*w
*ROUND_END_FACTOR
385 for(float n
=0.0f
;n
<0.999999f
;n
+=1.0f
/SAMPLES
)
386 side_a
.push_back(curve(n
));
395 if(bline
.get_contained_type()==ValueBase::TYPE_BLINEPOINT
)
397 ValueBase
value(bline
);
401 value
.set_loop(false);
405 loop_
=value
.get_loop();
407 segment_list
=convert_bline_to_segment_list(value
);
408 width_list
=convert_bline_to_width_list(value
);
418 if(segment_list
.empty())
420 synfig::warning("Outline: segment_list is empty, layer disabled");
426 // Repair the width list if we need to
429 if(width_list
.empty())
432 default_width
=width_list
.back();
434 while(width_list
.size()<segment_list
.size()+1)
435 width_list
.push_back(default_width
);
436 while(width_list
.size()>segment_list
.size()+1)
437 width_list
.pop_back();
441 // Repair the zero tangents (if any)
443 vector
<Segment
>::iterator iter
;
444 for(iter
=segment_list
.begin();iter
!=segment_list
.end();++iter
)
446 if(iter
->t1
.mag_squared()<=EPSILON
&& iter
->t2
.mag_squared()<=EPSILON
)
447 iter
->t1
=iter
->t2
=iter
->p2
-iter
->p1
;
451 vector
<Real
>::iterator iter
;
452 vector
<Real
> scaled_width_list
;
453 for(iter
=width_list
.begin();iter
!=width_list
.end();++iter
)
455 scaled_width_list
.push_back((*iter
*width
+expand
)*0.5f
);
458 Vector::value_type n
;
459 etl::hermite
<Vector
> curve
;
460 vector
<Point
> vector_list
;
461 Vector
last_tangent(segment_list
.back().t2
);
465 last_tangent
=NO_LOOP_COOKIE
;
468 vector
<Segment
>::iterator iter
;
469 vector
<Real
>::iterator witer
;
471 iter
=segment_list
.begin(),
472 witer
=scaled_width_list
.begin();
473 iter
!=segment_list
.end();
476 if(iter
->t1
.mag_squared()<=EPSILON
&& iter
->t2
.mag_squared()<=EPSILON
)
478 vector_list
.push_back(iter
->p1
-(iter
->p2
-iter
->p1
).perp().norm()*witer
[0]);
479 vector_list
.push_back((iter
->p2
-iter
->p1
)*0.05+iter
->p1
-(iter
->p2
-iter
->p1
).perp().norm()*((witer
[1]-witer
[0])*0.05+witer
[0]));
480 vector_list
.push_back((iter
->p2
-iter
->p1
)*0.95+iter
->p1
-(iter
->p2
-iter
->p1
).perp().norm()*((witer
[1]-witer
[0])*0.95+witer
[0]));
481 vector_list
.push_back(iter
->p2
-(iter
->p2
-iter
->p1
).perp().norm()*witer
[1]);
491 etl::derivative
<etl::hermite
<Vector
> > deriv(curve
);
493 // without this if statement, the broken tangents would
495 if(sharp_cusps
&& last_tangent
!=NO_LOOP_COOKIE
&& !last_tangent
.is_equal_to(iter
->t1
))
497 //Vector curr_tangent(iter->t1);
498 Vector
curr_tangent(deriv(CUSP_TANGENT_ADJUST
));
500 const Vector
t1(last_tangent
.perp().norm());
501 const Vector
t2(curr_tangent
.perp().norm());
503 Point
p1(iter
->p1
+t1
*witer
[0]);
504 Point
p2(iter
->p1
+t2
*witer
[0]);
506 Real
cross(t1
*t2
.perp());
508 if(cross
>CUSP_THRESHOLD
)
509 vector_list
.push_back(line_intersection(p1
,last_tangent
,p2
,curr_tangent
));
512 float amount(max(0.0f
,(float)(cross
/CUSP_THRESHOLD
))*(SPIKE_AMOUNT
-1)+1);
513 // Push back something to make it look vaguely round;
514 //vector_list.push_back(iter->p1+(t1*1.25+t2).norm()*witer[0]*amount);
515 vector_list
.push_back(iter
->p1
+(t1
+t2
).norm()*witer
[0]*amount
);
516 //vector_list.push_back(iter->p1+(t1+t2*1.25).norm()*witer[0]*amount);
519 //last_tangent=iter->t2;
520 last_tangent
=deriv(1.0f
-CUSP_TANGENT_ADJUST
);
522 for(n
=0.0f
;n
<1.0f
;n
+=1.0f
/SAMPLES
)
523 vector_list
.push_back(curve(n
)+deriv(n
>CUSP_TANGENT_ADJUST
?n
:CUSP_TANGENT_ADJUST
).perp().norm()*((witer
[1]-witer
[0])*n
+witer
[0]) );
524 vector_list
.push_back(curve(1.0)+deriv(1.0-CUSP_TANGENT_ADJUST
).perp().norm()*witer
[1]);
528 if(round_tip
[1] && !loop_
/* && (!sharp_cusps || segment_list.front().p1!=segment_list.back().p2)*/)
530 // remove the last point
531 vector_list
.pop_back();
535 curve
.p1()=iter
->p2
+Vector(last_tangent
[1],-last_tangent
[0]).norm()*(*witer
);
536 curve
.p2()=iter
->p2
-(Vector(last_tangent
[1],-last_tangent
[0]).norm()*(*witer
));
537 curve
.t2()=-(curve
.t1()=last_tangent
/last_tangent
.mag()*(*witer
)*ROUND_END_FACTOR
);
539 for(n
=0.0f
;n
<1.0f
;n
+=1.0f
/SAMPLES
)
540 vector_list
.push_back(curve(n
));
542 // remove the last point
543 vector_list
.pop_back();
548 last_tangent
=NO_LOOP_COOKIE
;
551 add_polygon(vector_list
);
553 last_tangent
=segment_list
.front().t1
;
557 // last_tangent=segment_list.back().t2;
560 vector
<Segment
>::reverse_iterator iter
;
561 vector
<Real
>::reverse_iterator witer
;
563 iter
=segment_list
.rbegin(),
564 witer
=scaled_width_list
.rbegin(),++witer
;
565 !(iter
==segment_list
.rend());
569 if(iter
->t1
.mag_squared()<=EPSILON
&& iter
->t2
.mag_squared()<=EPSILON
)
571 vector_list
.push_back(iter
->p2
+(iter
->p2
-iter
->p1
).perp().norm()*witer
[0]);
572 vector_list
.push_back((iter
->p2
-iter
->p1
)*0.95+iter
->p1
+(iter
->p2
-iter
->p1
).perp().norm()*((witer
[-1]-witer
[0])*0.95+witer
[0]));
573 vector_list
.push_back((iter
->p2
-iter
->p1
)*0.05+iter
->p1
+(iter
->p2
-iter
->p1
).perp().norm()*((witer
[-1]-witer
[0])*0.05+witer
[0]));
574 vector_list
.push_back(iter
->p1
+(iter
->p2
-iter
->p1
).perp().norm()*witer
[-1]);
584 etl::derivative
<etl::hermite
<Vector
> > deriv(curve
);
586 // without this if statement, the broken tangents would
588 if(sharp_cusps
&& last_tangent
!=NO_LOOP_COOKIE
&& !last_tangent
.is_equal_to(iter
->t2
))
590 //Vector curr_tangent(iter->t2);
591 Vector
curr_tangent(deriv(1.0f
-CUSP_TANGENT_ADJUST
));
593 const Vector
t1(last_tangent
.perp().norm());
594 const Vector
t2(curr_tangent
.perp().norm());
596 Point
p1(iter
->p2
-t1
*witer
[-1]);
597 Point
p2(iter
->p2
-t2
*witer
[-1]);
599 Real
cross(t1
*t2
.perp());
601 //if(last_tangent.perp().norm()*curr_tangent.norm()<-CUSP_THRESHOLD)
602 if(cross
>CUSP_THRESHOLD
)
603 vector_list
.push_back(line_intersection(p1
,last_tangent
,p2
,curr_tangent
));
606 float amount(max(0.0f
,(float)(cross
/CUSP_THRESHOLD
))*(SPIKE_AMOUNT
-1)+1);
607 // Push back something to make it look vaguely round;
608 //vector_list.push_back(iter->p2-(t1*1.25+t2).norm()*witer[-1]*amount);
609 vector_list
.push_back(iter
->p2
-(t1
+t2
).norm()*witer
[-1]*amount
);
610 //vector_list.push_back(iter->p2-(t1+t2*1.25).norm()*witer[-1]*amount);
613 //last_tangent=iter->t1;
614 last_tangent
=deriv(CUSP_TANGENT_ADJUST
);
616 for(n
=1.0f
;n
>CUSP_TANGENT_ADJUST
;n
-=1.0f
/SAMPLES
)
617 vector_list
.push_back(curve(n
)-deriv(1-n
>CUSP_TANGENT_ADJUST
?n
:1-CUSP_TANGENT_ADJUST
).perp().norm()*((witer
[-1]-witer
[0])*n
+witer
[0]) );
618 vector_list
.push_back(curve(0.0f
)-deriv(CUSP_TANGENT_ADJUST
).perp().norm()*witer
[0]);
621 if(round_tip
[0] && !loop_
/* && (!sharp_cusps || segment_list.front().p1!=segment_list.back().p2)*/)
623 // remove the last point
624 vector_list
.pop_back();
628 curve
.p1()=iter
->p1
+Vector(last_tangent
[1],-last_tangent
[0]).norm()*(*witer
);
629 curve
.p2()=iter
->p1
-(Vector(last_tangent
[1],-last_tangent
[0]).norm()*(*witer
));
630 curve
.t1()=-(curve
.t2()=last_tangent
/last_tangent
.mag()*(*witer
)*ROUND_END_FACTOR
);
633 for(n
=1.0;n
>0.0;n
-=1.0/SAMPLES
)
634 vector_list
.push_back(curve(n
));
636 // remove the last point
637 vector_list
.pop_back();
642 // reverse(vector_list.begin(),vector_list.end());
646 vector
<Point
>::iterator iter
;
647 for(iter
=vector_list
.begin();iter
!=vector_list
.end();++iter
)
648 if(!iter
->is_valid())
650 synfig::error("Outline::sync(): Bad point in vector_list!");
652 //synfig::info("BLEHH__________--- x:%f, y:%f",vector_list.front()[0],vector_list.front()[1]);
656 add_polygon(vector_list
);
660 } catch (...) { synfig::error("Outline::sync(): Exception thrown"); throw; }
666 Outline::set_param(const String
& param
, const ValueBase
&value
)
668 if(param
=="segment_list")
670 if(dynamic_param_list().count("segment_list"))
672 connect_dynamic_param("bline",dynamic_param_list().find("segment_list")->second
);
673 disconnect_dynamic_param("segment_list");
674 synfig::warning("Outline::set_param(): Updated valuenode connection to use the new \"bline\" parameter.");
677 synfig::warning("Outline::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead.");
680 if( (param
=="segment_list" || param
=="bline") && value
.get_type()==ValueBase::TYPE_LIST
)
682 //if(value.get_contained_type()!=ValueBase::TYPE_BLINEPOINT)
690 if( param=="seg" && value.get_type()==ValueBase::TYPE_SEGMENT)
692 if(!segment_list.empty())
693 segment_list.clear();
695 segment_list.push_back(value.get(Segment()));
700 if( param=="w[0]" && value.get_type()==ValueBase::TYPE_REAL)
702 if(width_list.size()<2)
704 width_list.push_back(value.get(Real()));
705 width_list.push_back(value.get(Real()));
709 width_list[0]=value.get(Real());
716 if( param=="w[1]" && value.get_type()==ValueBase::TYPE_REAL)
718 if(width_list.size()<2)
720 width_list.push_back(value.get(Real()));
721 width_list.push_back(value.get(Real()));
725 width_list[1]=value.get(Real());
732 if( param=="width_list" && value.same_type_as(width_list))
740 IMPORT(round_tip
[0]);
741 IMPORT(round_tip
[1]);
743 IMPORT_PLUS(width
,if(old_version
){width
*=2.0;});
746 IMPORT(homogeneous_width
);
748 if(param
!="vector_list")
749 return Layer_Polygon::set_param(param
,value
);
755 Outline::set_time(Context context
, Time time
)const
757 const_cast<Outline
*>(this)->sync();
758 context
.set_time(time
);
762 Outline::set_time(Context context
, Time time
, Vector pos
)const
764 const_cast<Outline
*>(this)->sync();
765 context
.set_time(time
,pos
);
769 Outline::get_param(const String
& param
)const
773 //EXPORT(width_list);
774 //EXPORT(segment_list);
775 EXPORT(homogeneous_width
);
776 EXPORT(round_tip
[0]);
777 EXPORT(round_tip
[1]);
785 if(param
!="vector_list")
786 return Layer_Polygon::get_param(param
);
791 Outline::get_param_vocab()const
793 Layer::Vocab
ret(Layer_Polygon::get_param_vocab());
795 // Pop off the polygon parameter from the polygon vocab
798 ret
.push_back(ParamDesc("bline")
799 .set_local_name(_("Vertices"))
800 .set_origin("offset")
802 .set_description(_("A list of BLine Points"))
806 ret.push_back(ParamDesc("width_list")
807 .set_local_name(_("Point Widths"))
808 .set_origin("segment_list")
814 ret
.push_back(ParamDesc("width")
816 .set_local_name(_("Outline Width"))
819 ret
.push_back(ParamDesc("expand")
821 .set_local_name(_("Expand"))
824 ret
.push_back(ParamDesc("sharp_cusps")
825 .set_local_name(_("Sharp Cusps"))
826 .set_description(_("Determines cusp type"))
829 ret
.push_back(ParamDesc("round_tip[0]")
830 .set_local_name(_("Rounded Begin"))
831 .set_description(_("Round off the tip"))
834 ret
.push_back(ParamDesc("round_tip[1]")
835 .set_local_name(_("Rounded End"))
836 .set_description(_("Round off the tip"))
838 ret
.push_back(ParamDesc("loopyness")
839 .set_local_name(_("Loopyness"))
841 ret
.push_back(ParamDesc("homogeneous_width")
842 .set_local_name(_("Homogeneous"))