X-Git-Url: https://git.pterodactylus.net/?a=blobdiff_plain;f=synfig-core%2Fsrc%2Fmodules%2Fmod_geometry%2Foutline.cpp;fp=synfig-core%2Fsrc%2Fmodules%2Fmod_geometry%2Foutline.cpp;h=e84effb2a99d78523f7d56d07e182807fe25130c;hb=a095981e18cc37a8ecc7cd237cc22b9c10329264;hp=0000000000000000000000000000000000000000;hpb=9459638ad6797b8139f1e9f0715c96076dbf0890;p=synfig.git diff --git a/synfig-core/src/modules/mod_geometry/outline.cpp b/synfig-core/src/modules/mod_geometry/outline.cpp new file mode 100644 index 0000000..e84effb --- /dev/null +++ b/synfig-core/src/modules/mod_geometry/outline.cpp @@ -0,0 +1,854 @@ +/* === S Y N F I G ========================================================= */ +/*! \file outline.cpp +** \brief Implementation of the "Outline" layer +** +** $Id$ +** +** \legal +** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley +** Copyright (c) 2007, 2008 Chris Moore +** +** This package is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License as +** published by the Free Software Foundation; either version 2 of +** the License, or (at your option) any later version. +** +** This package 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 +** General Public License for more details. +** \endlegal +*/ +/* ========================================================================= */ + +//! \note This whole file should be rewritten at some point (darco) + +/* === H E A D E R S ======================================================= */ + +#ifdef USING_PCH +# include "pch.h" +#else +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "outline.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#endif + +using namespace etl; + +/* === M A C R O S ========================================================= */ + +#define SAMPLES 50 +#define ROUND_END_FACTOR (4) +#define CUSP_THRESHOLD (0.40) +#define SPIKE_AMOUNT (4) +#define NO_LOOP_COOKIE synfig::Vector(84951305,7836658) +#define EPSILON (0.000000001) +#define CUSP_TANGENT_ADJUST (0.025) + +/* === G L O B A L S ======================================================= */ + +SYNFIG_LAYER_INIT(Outline); +SYNFIG_LAYER_SET_NAME(Outline,"outline"); +SYNFIG_LAYER_SET_LOCAL_NAME(Outline,N_("Outline")); +SYNFIG_LAYER_SET_CATEGORY(Outline,N_("Geometry")); +SYNFIG_LAYER_SET_VERSION(Outline,"0.2"); +SYNFIG_LAYER_SET_CVS_ID(Outline,"$Id$"); + +/* === P R O C E D U R E S ================================================= */ + +// This function was adapted from what was +// described on http://www.whisqu.se/per/docs/math28.htm +Point line_intersection( + const Point& p1, + const Vector& t1, + const Point& p2, + const Vector& t2 +) +{ + const float& x0(p1[0]); + const float& y0(p1[1]); + + const float x1(p1[0]+t1[0]); + const float y1(p1[1]+t1[1]); + + const float& x2(p2[0]); + const float& y2(p2[1]); + + const float x3(p2[0]+t2[0]); + const float y3(p2[1]+t2[1]); + + const float near_infinity((float)1e+10); + + float m1,m2; // the slopes of each line + + // compute slopes, note the kluge for infinity, however, this will + // be close enough + + if ((x1-x0)!=0) + m1 = (y1-y0)/(x1-x0); + else + m1 = near_infinity; + + if ((x3-x2)!=0) + m2 = (y3-y2)/(x3-x2); + else + m2 = near_infinity; + + // compute constants + const float& a1(m1); + const float& a2(m2); + const float b1(-1.0f); + const float b2(-1.0f); + const float c1(y0-m1*x0); + const float c2(y2-m2*x2); + + // compute the inverse of the determinate + const float det_inv(1.0f/(a1*b2 - a2*b1)); + + // use Kramers rule to compute the intersection + return Point( + ((b1*c2 - b2*c1)*det_inv), + ((a2*c1 - a1*c2)*det_inv) + ); +} // end Intersect_Lines + +/* === M E T H O D S ======================================================= */ + + +Outline::Outline() +{ + old_version=false; + round_tip[0]=true; + round_tip[1]=true; + sharp_cusps=true; + width=1.0f; + loopyness=1.0f; + expand=0; + homogeneous_width=true; + clear(); + + vector bline_point_list; + bline_point_list.push_back(BLinePoint()); + bline_point_list.push_back(BLinePoint()); + bline_point_list.push_back(BLinePoint()); + bline_point_list[0].set_vertex(Point(0,1)); + bline_point_list[1].set_vertex(Point(0,-1)); + bline_point_list[2].set_vertex(Point(1,0)); + bline_point_list[0].set_tangent(bline_point_list[1].get_vertex()-bline_point_list[2].get_vertex()*0.5f); + bline_point_list[1].set_tangent(bline_point_list[2].get_vertex()-bline_point_list[0].get_vertex()*0.5f); + bline_point_list[2].set_tangent(bline_point_list[0].get_vertex()-bline_point_list[1].get_vertex()*0.5f); + bline_point_list[0].set_width(1.0f); + bline_point_list[1].set_width(1.0f); + bline_point_list[2].set_width(1.0f); + bline=bline_point_list; + + needs_sync=true; +} + + +/*! The Sync() function takes the values +** and creates a polygon to be rendered +** with the polygon layer. +*/ +void +Outline::sync() +{ + clear(); + + if (!bline.get_list().size()) + { + synfig::warning(string("Outline::sync():")+N_("No vertices in outline " + string("\"") + get_description() + string("\""))); + return; + } + + try { +#if 1 + + const bool loop(bline.get_loop()); + + ValueNode_BLine::Handle bline_valuenode; + if (bline.get_contained_type() == ValueBase::TYPE_SEGMENT) + { + bline_valuenode = ValueNode_BLine::create(bline); + bline = (*bline_valuenode)(0); + } + + const vector bline_(bline.get_list().begin(),bline.get_list().end()); +#define bline bline_ + + vector::const_iterator + iter, + next(bline.begin()); + + const vector::const_iterator + end(bline.end()); + + vector + side_a, + side_b; + + if(loop) + iter=--bline.end(); + else + iter=next++; + + // iter next + // ---- ---- + // looped nth 1st + // !looped 1st 2nd + + Vector first_tangent=bline.front().get_tangent2(); + Vector last_tangent=iter->get_tangent1(); + + // if we are looped and drawing sharp cusps, we'll need a value for the incoming tangent + if (loop && sharp_cusps && last_tangent.is_equal_to(Vector::zero())) + { + hermite curve((iter-1)->get_vertex(), iter->get_vertex(), (iter-1)->get_tangent2(), iter->get_tangent1()); + const derivative< hermite > deriv(curve); + last_tangent=deriv(1.0-CUSP_TANGENT_ADJUST); + } + + // `first' is for making the cusps; don't do that for the first point if we're not looped + for(bool first=!loop; next!=end; iter=next++) + { + Vector prev_t(iter->get_tangent1()); + Vector iter_t(iter->get_tangent2()); + Vector next_t(next->get_tangent1()); + + bool split_flag(iter->get_split_tangent_flag()); + + // if iter.t2 == 0 and next.t1 == 0, this is a straight line + if(iter_t.is_equal_to(Vector::zero()) && next_t.is_equal_to(Vector::zero())) + { + iter_t=next_t=next->get_vertex()-iter->get_vertex(); + // split_flag=true; + + // if the two points are on top of each other, ignore this segment + // leave `first' true if was before + if (iter_t.is_equal_to(Vector::zero())) + continue; + } + + // Setup the curve + hermite curve( + iter->get_vertex(), + next->get_vertex(), + iter_t, + next_t + ); + + const float + iter_w((iter->get_width()*width)*0.5f+expand), + next_w((next->get_width()*width)*0.5f+expand); + + const derivative< hermite > deriv(curve); + + if (first) + first_tangent = deriv(CUSP_TANGENT_ADJUST); + + // Make cusps as necessary + 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())) + { + Vector curr_tangent(deriv(CUSP_TANGENT_ADJUST)); + + const Vector t1(last_tangent.perp().norm()); + const Vector t2(curr_tangent.perp().norm()); + + Real cross(t1*t2.perp()); + Real perp((t1-t2).mag()); + if(cross>CUSP_THRESHOLD) + { + const Point p1(iter->get_vertex()+t1*iter_w); + const Point p2(iter->get_vertex()+t2*iter_w); + + side_a.push_back(line_intersection(p1,last_tangent,p2,curr_tangent)); + } + else if(cross<-CUSP_THRESHOLD) + { + const Point p1(iter->get_vertex()-t1*iter_w); + const Point p2(iter->get_vertex()-t2*iter_w); + + side_b.push_back(line_intersection(p1,last_tangent,p2,curr_tangent)); + } + else if(cross>0 && perp>1) + { + float amount(max(0.0f,(float)(cross/CUSP_THRESHOLD))*(SPIKE_AMOUNT-1)+1); + + side_a.push_back(iter->get_vertex()+(t1+t2).norm()*iter_w*amount); + } + else if(cross<0 && perp>1) + { + float amount(max(0.0f,(float)(-cross/CUSP_THRESHOLD))*(SPIKE_AMOUNT-1)+1); + + side_b.push_back(iter->get_vertex()-(t1+t2).norm()*iter_w*amount); + } + } + + // Make the outline + if(homogeneous_width) + { + const float length(curve.length()); + float dist(0); + Point lastpoint; + for(float n=0.0f;n<0.999999f;n+=1.0f/SAMPLES) + { + const Vector d(deriv(n>CUSP_TANGENT_ADJUST?n:CUSP_TANGENT_ADJUST).perp().norm()); + const Vector p(curve(n)); + + if(n) + dist+=(p-lastpoint).mag(); + + const float w(((next_w-iter_w)*(dist/length)+iter_w)); + + side_a.push_back(p+d*w); + side_b.push_back(p-d*w); + + lastpoint=p; + } + } + else + for(float n=0.0f;n<0.999999f;n+=1.0f/SAMPLES) + { + const Vector d(deriv(n>CUSP_TANGENT_ADJUST?n:CUSP_TANGENT_ADJUST).perp().norm()); + const Vector p(curve(n)); + const float w(((next_w-iter_w)*n+iter_w)); + + side_a.push_back(p+d*w); + side_b.push_back(p-d*w); + } + last_tangent=deriv(1.0-CUSP_TANGENT_ADJUST); + side_a.push_back(curve(1.0)+last_tangent.perp().norm()*next_w); + side_b.push_back(curve(1.0)-last_tangent.perp().norm()*next_w); + + first=false; + } + + if(loop) + { + reverse(side_b.begin(),side_b.end()); + add_polygon(side_a); + add_polygon(side_b); + return; + } + + // Insert code for adding end tip + if(round_tip[1] && !loop && side_a.size()) + { + // remove the last point + side_a.pop_back(); + + const Point vertex(bline.back().get_vertex()); + const Vector tangent(last_tangent.norm()); + const float w((bline.back().get_width()*width)*0.5f+expand); + + hermite curve( + vertex+tangent.perp()*w, + vertex-tangent.perp()*w, + tangent*w*ROUND_END_FACTOR, + -tangent*w*ROUND_END_FACTOR + ); + + for(float n=0.0f;n<0.999999f;n+=1.0f/SAMPLES) + side_a.push_back(curve(n)); + } + + for(;!side_b.empty();side_b.pop_back()) + side_a.push_back(side_b.back()); + + // Insert code for adding begin tip + if(round_tip[0] && !loop && side_a.size()) + { + // remove the last point + side_a.pop_back(); + + const Point vertex(bline.front().get_vertex()); + const Vector tangent(first_tangent.norm()); + const float w((bline.front().get_width()*width)*0.5f+expand); + + hermite curve( + vertex-tangent.perp()*w, + vertex+tangent.perp()*w, + -tangent*w*ROUND_END_FACTOR, + tangent*w*ROUND_END_FACTOR + ); + + for(float n=0.0f;n<0.999999f;n+=1.0f/SAMPLES) + side_a.push_back(curve(n)); + } + + add_polygon(side_a); + + +#else /* 1 */ + + bool loop_; + if(bline.get_contained_type()==ValueBase::TYPE_BLINEPOINT) + { + ValueBase value(bline); + + if(loopyness<0.5f) + { + value.set_loop(false); + loop_=false; + } + else + loop_=value.get_loop(); + + segment_list=convert_bline_to_segment_list(value); + width_list=convert_bline_to_width_list(value); + } + else + { + clear(); + return; + } + + + + if(segment_list.empty()) + { + synfig::warning("Outline: segment_list is empty, layer disabled"); + clear(); + return; + } + + + // Repair the width list if we need to + { + Real default_width; + if(width_list.empty()) + default_width=0.01; + else + default_width=width_list.back(); + + while(width_list.size()segment_list.size()+1) + width_list.pop_back(); + + } + + // Repair the zero tangents (if any) + { + vector::iterator iter; + for(iter=segment_list.begin();iter!=segment_list.end();++iter) + { + if(iter->t1.mag_squared()<=EPSILON && iter->t2.mag_squared()<=EPSILON) + iter->t1=iter->t2=iter->p2-iter->p1; + } + } + + vector::iterator iter; + vector scaled_width_list; + for(iter=width_list.begin();iter!=width_list.end();++iter) + { + scaled_width_list.push_back((*iter*width+expand)*0.5f); + } + + Vector::value_type n; + etl::hermite curve; + vector vector_list; + Vector last_tangent(segment_list.back().t2); + clear(); + + if(!loop_) + last_tangent=NO_LOOP_COOKIE; + + { + vector::iterator iter; + vector::iterator witer; + for( + iter=segment_list.begin(), + witer=scaled_width_list.begin(); + iter!=segment_list.end(); + ++iter,++witer) + { + if(iter->t1.mag_squared()<=EPSILON && iter->t2.mag_squared()<=EPSILON) + { + vector_list.push_back(iter->p1-(iter->p2-iter->p1).perp().norm()*witer[0]); + 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])); + 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])); + vector_list.push_back(iter->p2-(iter->p2-iter->p1).perp().norm()*witer[1]); + } + else + { + curve.p1()=iter->p1; + curve.t1()=iter->t1; + curve.p2()=iter->p2; + curve.t2()=iter->t2; + curve.sync(); + + etl::derivative > deriv(curve); + + // without this if statement, the broken tangents would + // have boxed edges + if(sharp_cusps && last_tangent!=NO_LOOP_COOKIE && !last_tangent.is_equal_to(iter->t1)) + { + //Vector curr_tangent(iter->t1); + Vector curr_tangent(deriv(CUSP_TANGENT_ADJUST)); + + const Vector t1(last_tangent.perp().norm()); + const Vector t2(curr_tangent.perp().norm()); + + Point p1(iter->p1+t1*witer[0]); + Point p2(iter->p1+t2*witer[0]); + + Real cross(t1*t2.perp()); + + if(cross>CUSP_THRESHOLD) + vector_list.push_back(line_intersection(p1,last_tangent,p2,curr_tangent)); + else if(cross>0) + { + float amount(max(0.0f,(float)(cross/CUSP_THRESHOLD))*(SPIKE_AMOUNT-1)+1); + // Push back something to make it look vaguely round; + //vector_list.push_back(iter->p1+(t1*1.25+t2).norm()*witer[0]*amount); + vector_list.push_back(iter->p1+(t1+t2).norm()*witer[0]*amount); + //vector_list.push_back(iter->p1+(t1+t2*1.25).norm()*witer[0]*amount); + } + } + //last_tangent=iter->t2; + last_tangent=deriv(1.0f-CUSP_TANGENT_ADJUST); + + for(n=0.0f;n<1.0f;n+=1.0f/SAMPLES) + vector_list.push_back(curve(n)+deriv(n>CUSP_TANGENT_ADJUST?n:CUSP_TANGENT_ADJUST).perp().norm()*((witer[1]-witer[0])*n+witer[0]) ); + vector_list.push_back(curve(1.0)+deriv(1.0-CUSP_TANGENT_ADJUST).perp().norm()*witer[1]); + + } + } + if(round_tip[1] && !loop_/* && (!sharp_cusps || segment_list.front().p1!=segment_list.back().p2)*/) + { + // remove the last point + vector_list.pop_back(); + + iter--; + + curve.p1()=iter->p2+Vector(last_tangent[1],-last_tangent[0]).norm()*(*witer); + curve.p2()=iter->p2-(Vector(last_tangent[1],-last_tangent[0]).norm()*(*witer)); + curve.t2()=-(curve.t1()=last_tangent/last_tangent.mag()*(*witer)*ROUND_END_FACTOR); + curve.sync(); + for(n=0.0f;n<1.0f;n+=1.0f/SAMPLES) + vector_list.push_back(curve(n)); + + // remove the last point + vector_list.pop_back(); + } + } + + if(!loop_) + last_tangent=NO_LOOP_COOKIE; + else + { + add_polygon(vector_list); + vector_list.clear(); + last_tangent=segment_list.front().t1; + } + + //else + // last_tangent=segment_list.back().t2; + + { + vector::reverse_iterator iter; + vector::reverse_iterator witer; + for( + iter=segment_list.rbegin(), + witer=scaled_width_list.rbegin(),++witer; + !(iter==segment_list.rend()); + ++iter,++witer) + { + + if(iter->t1.mag_squared()<=EPSILON && iter->t2.mag_squared()<=EPSILON) + { + vector_list.push_back(iter->p2+(iter->p2-iter->p1).perp().norm()*witer[0]); + 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])); + 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])); + vector_list.push_back(iter->p1+(iter->p2-iter->p1).perp().norm()*witer[-1]); + } + else + { + curve.p1()=iter->p1; + curve.t1()=iter->t1; + curve.p2()=iter->p2; + curve.t2()=iter->t2; + curve.sync(); + + etl::derivative > deriv(curve); + + // without this if statement, the broken tangents would + // have boxed edges + if(sharp_cusps && last_tangent!=NO_LOOP_COOKIE && !last_tangent.is_equal_to(iter->t2)) + { + //Vector curr_tangent(iter->t2); + Vector curr_tangent(deriv(1.0f-CUSP_TANGENT_ADJUST)); + + const Vector t1(last_tangent.perp().norm()); + const Vector t2(curr_tangent.perp().norm()); + + Point p1(iter->p2-t1*witer[-1]); + Point p2(iter->p2-t2*witer[-1]); + + Real cross(t1*t2.perp()); + + //if(last_tangent.perp().norm()*curr_tangent.norm()<-CUSP_THRESHOLD) + if(cross>CUSP_THRESHOLD) + vector_list.push_back(line_intersection(p1,last_tangent,p2,curr_tangent)); + else if(cross>0) + { + float amount(max(0.0f,(float)(cross/CUSP_THRESHOLD))*(SPIKE_AMOUNT-1)+1); + // Push back something to make it look vaguely round; + //vector_list.push_back(iter->p2-(t1*1.25+t2).norm()*witer[-1]*amount); + vector_list.push_back(iter->p2-(t1+t2).norm()*witer[-1]*amount); + //vector_list.push_back(iter->p2-(t1+t2*1.25).norm()*witer[-1]*amount); + } + } + //last_tangent=iter->t1; + last_tangent=deriv(CUSP_TANGENT_ADJUST); + + for(n=1.0f;n>CUSP_TANGENT_ADJUST;n-=1.0f/SAMPLES) + 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]) ); + vector_list.push_back(curve(0.0f)-deriv(CUSP_TANGENT_ADJUST).perp().norm()*witer[0]); + } + } + if(round_tip[0] && !loop_/* && (!sharp_cusps || segment_list.front().p1!=segment_list.back().p2)*/) + { + // remove the last point + vector_list.pop_back(); + iter--; + witer--; + + curve.p1()=iter->p1+Vector(last_tangent[1],-last_tangent[0]).norm()*(*witer); + curve.p2()=iter->p1-(Vector(last_tangent[1],-last_tangent[0]).norm()*(*witer)); + curve.t1()=-(curve.t2()=last_tangent/last_tangent.mag()*(*witer)*ROUND_END_FACTOR); + curve.sync(); + + for(n=1.0;n>0.0;n-=1.0/SAMPLES) + vector_list.push_back(curve(n)); + + // remove the last point + vector_list.pop_back(); + } + } + + //if(loop_) + // reverse(vector_list.begin(),vector_list.end()); + +#ifdef _DEBUG + { + vector::iterator iter; + for(iter=vector_list.begin();iter!=vector_list.end();++iter) + if(!iter->is_valid()) + { + synfig::error("Outline::sync(): Bad point in vector_list!"); + } + //synfig::info("BLEHH__________--- x:%f, y:%f",vector_list.front()[0],vector_list.front()[1]); + } +#endif /* _DEBUG */ + + add_polygon(vector_list); + + +#endif /* 1 */ + } catch (...) { synfig::error("Outline::sync(): Exception thrown"); throw; } +} + +#undef bline + +bool +Outline::set_param(const String & param, const ValueBase &value) +{ + if(param=="segment_list") + { + if(dynamic_param_list().count("segment_list")) + { + connect_dynamic_param("bline",dynamic_param_list().find("segment_list")->second); + disconnect_dynamic_param("segment_list"); + synfig::warning("Outline::set_param(): Updated valuenode connection to use the new \"bline\" parameter."); + } + else + synfig::warning("Outline::set_param(): The parameter \"segment_list\" is deprecated. Use \"bline\" instead."); + } + + if( (param=="segment_list" || param=="bline") && value.get_type()==ValueBase::TYPE_LIST) + { + //if(value.get_contained_type()!=ValueBase::TYPE_BLINEPOINT) + // return false; + + bline=value; + + return true; + } + /* + if( param=="seg" && value.get_type()==ValueBase::TYPE_SEGMENT) + { + if(!segment_list.empty()) + segment_list.clear(); + + segment_list.push_back(value.get(Segment())); + loop_=false; + //sync(); + return true; + } + if( param=="w[0]" && value.get_type()==ValueBase::TYPE_REAL) + { + if(width_list.size()<2) + { + width_list.push_back(value.get(Real())); + width_list.push_back(value.get(Real())); + } + else + { + width_list[0]=value.get(Real()); + } + width=1; + //sync(); + return true; + } + + if( param=="w[1]" && value.get_type()==ValueBase::TYPE_REAL) + { + if(width_list.size()<2) + { + width_list.push_back(value.get(Real())); + width_list.push_back(value.get(Real())); + } + else + { + width_list[1]=value.get(Real()); + } + width=1; + //sync(); + return true; + } + + if( param=="width_list" && value.same_type_as(width_list)) + { + width_list=value; + //sync(); + return true; + } + */ + + IMPORT(round_tip[0]); + IMPORT(round_tip[1]); + IMPORT(sharp_cusps); + IMPORT_PLUS(width,if(old_version){width*=2.0;}); + IMPORT(loopyness); + IMPORT(expand); + IMPORT(homogeneous_width); + + if(param!="vector_list") + return Layer_Polygon::set_param(param,value); + + return false; +} + +void +Outline::set_time(Context context, Time time)const +{ + const_cast(this)->sync(); + context.set_time(time); +} + +void +Outline::set_time(Context context, Time time, Vector pos)const +{ + const_cast(this)->sync(); + context.set_time(time,pos); +} + +ValueBase +Outline::get_param(const String& param)const +{ + EXPORT(bline); + EXPORT(expand); + //EXPORT(width_list); + //EXPORT(segment_list); + EXPORT(homogeneous_width); + EXPORT(round_tip[0]); + EXPORT(round_tip[1]); + EXPORT(sharp_cusps); + EXPORT(width); + EXPORT(loopyness); + + EXPORT_NAME(); + EXPORT_VERSION(); + + if(param!="vector_list") + return Layer_Polygon::get_param(param); + return ValueBase(); +} + +Layer::Vocab +Outline::get_param_vocab()const +{ + Layer::Vocab ret(Layer_Polygon::get_param_vocab()); + + // Pop off the polygon parameter from the polygon vocab + ret.pop_back(); + + ret.push_back(ParamDesc("bline") + .set_local_name(_("Vertices")) + .set_origin("origin") + .set_hint("width") + .set_description(_("A list of BLine Points")) + ); + + /* + ret.push_back(ParamDesc("width_list") + .set_local_name(_("Point Widths")) + .set_origin("segment_list") + .hidden() + .not_critical() + ); + */ + + ret.push_back(ParamDesc("width") + .set_is_distance() + .set_local_name(_("Outline Width")) + ); + + ret.push_back(ParamDesc("expand") + .set_is_distance() + .set_local_name(_("Expand")) + ); + + ret.push_back(ParamDesc("sharp_cusps") + .set_local_name(_("Sharp Cusps")) + .set_description(_("Determines cusp type")) + ); + + ret.push_back(ParamDesc("round_tip[0]") + .set_local_name(_("Rounded Begin")) + .set_description(_("Round off the tip")) + ); + + ret.push_back(ParamDesc("round_tip[1]") + .set_local_name(_("Rounded End")) + .set_description(_("Round off the tip")) + ); + ret.push_back(ParamDesc("loopyness") + .set_local_name(_("Loopyness")) + ); + ret.push_back(ParamDesc("homogeneous_width") + .set_local_name(_("Homogeneous")) + ); + + return ret; +}