/* * curve-curve distance * * Authors: * Marco Cecchetti * * Copyright 2008 authors * * This library is free software; you can redistribute it and/or * modify it either under the terms of the GNU Lesser General Public * License version 2.1 as published by the Free Software Foundation * (the "LGPL") or, at your option, under the terms of the Mozilla * Public License Version 1.1 (the "MPL"). If you do not alter this * notice, a recipient may use your version of this file under either * the MPL or the LGPL. * * You should have received a copy of the LGPL along with this library * in the file COPYING-LGPL-2.1; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * You should have received a copy of the MPL along with this library * in the file COPYING-MPL-1.1 * * The contents of this file are subject to the Mozilla Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY * OF ANY KIND, either express or implied. See the LGPL or the MPL for * the specific language governing rights and limitations. */ #include <2geom/d2.h> #include <2geom/sbasis.h> #include <2geom/path.h> #include <2geom/angle.h> #include <2geom/bezier-to-sbasis.h> #include <2geom/sbasis-geometric.h> #include <2geom/piecewise.h> #include #include #include <2geom/nearest-time.h> #include <2geom/numeric/linear_system.h> #include namespace Geom { namespace detail { // this wrapper class is an helper to make up a curve portion and access it // in an homogeneous way template< typename Curve01T > class CurvePortion { public: CurvePortion(const Curve & curve, double from, double to) : m_curve_ptr(curve.portion(from, to)) { } Curve01T & get_curve() { return *( static_cast(m_curve_ptr) ); } ~CurvePortion() { if (m_curve_ptr != NULL) delete m_curve_ptr; } private: Curve* m_curve_ptr; }; template<> class CurvePortion< D2 > { public: CurvePortion< D2 >(const D2 & curve, double from, double to) : m_curve(portion(curve, from, to)) { } D2 & get_curve() { return m_curve; } private: D2 m_curve; }; template< typename Curve01T, typename CurveT > class distance_impl { typedef Curve01T curveA_type; typedef CurveT curveB_type; // determine how near a distance sample and the value computed through // the interpolated function have to be double accuracy; // determine the recursion limit double adaptive_limit; // pieces of the initial subdivision unsigned int piecees; // degree of the polynomial used to interpolate a piece unsigned int piece_degree; // number of coefficients = piece_degree + 1 unsigned int piece_size; unsigned int samples_per_piece; // total initial samples unsigned int N; // a junction is a part of the previous or of the next piece unsigned int samples_per_junction; unsigned int samples_per_2junctions; // number of distance samples used in the interpolation (in the general case) unsigned int samples_per_interpolation; // distance between two consecutive parameters at which samples are evaluated double step; double half_step; // length of the initial domain interval of a piece double piece_step; // length of the interval related to a junction double junction_step; // index of the first sample related to a piece unsigned int interval_si; // index of the last sample related to a piece unsigned int interval_ei; // index of the first sample to be evaluated for the current piece unsigned int evaluation_si; // index of the last sample to be evaluated for the current piece unsigned int evaluation_ei; // index of the first sample to be used for interpolating the current piece unsigned int interpolation_si; // index of the last sample to be used for interpolating the current piece unsigned int interpolation_ei; // number of total samples to be used for interpolating the current piece // this is equal to samples_per_interpolation except for the first and last // piece unsigned int interpolation_samples; // parameter value for the first sample related to the current piece double interval_st; // interval_st + piece_step double interval_et; // curve piece start t double portion_st; // curve piece end t double portion_et; unsigned int rec_pieces; unsigned int rec_N; unsigned int shared_si; unsigned int shared_ei; double rec_step; double rec_half_step; double rec_piece_step; double rec_piece_2steps; unsigned int rec_total_samples; void init() { piece_degree = 3; piece_size = piece_degree + 1; samples_per_piece = 4; N = piecees * samples_per_piece; samples_per_junction = 2; samples_per_2junctions = 2*samples_per_junction; samples_per_interpolation = samples_per_piece + samples_per_2junctions; step = 1.0 / N; half_step = step / 2; piece_step = samples_per_piece * step; junction_step = samples_per_junction * step; interval_si = samples_per_junction; interval_ei = interval_si + samples_per_piece; portion_st = (double)(samples_per_junction) / samples_per_interpolation; portion_et = portion_st + (double)(samples_per_piece) / samples_per_interpolation; // recursive routine parameters rec_pieces = 2; rec_N = rec_pieces * samples_per_piece; rec_total_samples = 2 * samples_per_piece + 1; shared_si = samples_per_piece - samples_per_junction; shared_ei = samples_per_piece + samples_per_junction; rec_step = 1.0 / rec_N; rec_half_step = rec_step / 2; rec_piece_step = samples_per_piece * rec_step; rec_piece_2steps = 2 * rec_piece_step; } bool check_accuracy( SBasis const& piece, NL::Vector const& sample_distances, double step ) { double t = 0; for (unsigned int i = 0; i < sample_distances.size(); ++i) { if ( !are_near(piece(t), sample_distances[i], accuracy) ) { return false; } t += step; } return true; } void append( Piecewise & pwc, Piecewise const& spwc, double interval_st, double interval_length ) { double cut; for (unsigned int i = 0; i < spwc.size(); ++i) { cut = interval_st + spwc.cuts[i+1] * interval_length; pwc.push(spwc.segs[i], cut); } } void init_power_matrix(NL::Matrix & power_matrix) { double t = 0; double u0, u1, s; unsigned int half_rows = power_matrix.rows() / 2; unsigned int n = power_matrix.rows() - 1; for (unsigned int i0 = 0, i1 = n; i0 < half_rows; ++i0, --i1) { u0 = 1-t; u1 = t; s = u0 * u1; for (unsigned int j = 0; j < piece_size; j+=2) { power_matrix(i0, j) = u0; power_matrix(i0, j+1) = u1; power_matrix(i1, j) = u1; power_matrix(i1, j+1) = u0; u0 *= s; u1 *= s; } t += rec_step; } // t = 1/2 assert( are_near(t, 0.5) ); u1 = 1/2.0; s = 1/4.0; for (unsigned int j = 0; j < piece_size; j+=2) { power_matrix(half_rows, j) = u1; power_matrix(half_rows, j+1) = u1; u1 *= s; } } void interpolate( SBasis & piece, NL::Matrix & psdinv_matrix, NL::Vector & sample_distances, double interpolation_si, double interpolation_samples, double _portion_st, double _portion_et ) { piece.resize(2); NL::VectorView v( sample_distances, interpolation_samples, interpolation_si ); NL::Vector coeff = psdinv_matrix * v; for (unsigned int i = 0, k = 0; i < piece_size; i+=2, ++k) { piece[k][0] = coeff[i]; piece[k][1] = coeff[i+1]; } piece = portion(piece, _portion_st, _portion_et); } void evaluate_samples( curveA_type const& A, curveB_type const& B, NL::Vector & sample_distances, double& t ) { Point At; double nptime; for (unsigned int i = evaluation_si; i < evaluation_ei; ++i) { At = A(t); nptime = nearest_time(At, B); sample_distances[i] = distance(At, B(nptime)); t += step; } } void evaluate_piece_rec( Piecewise & pwc, curveA_type const& A, curveB_type const& B, NL::Matrix & psdinv_matrix, NL::Matrix & fpi_matrix, NL::Matrix & lpi_matrix, NL::Vector & curr_vector, NL::Vector & sample_distances, bool adaptive, double _interpolation_si, double _interpolation_ei, double _interval_st, double _interval_et, double half_real_step ) { SBasis piece; double _interpolation_samples = _interpolation_ei - _interpolation_si; interpolate( piece, psdinv_matrix, curr_vector, _interpolation_si, _interpolation_samples, _interval_st, _interval_et ); if (adaptive) { bool good = check_accuracy( piece, sample_distances, rec_step ); if (!good) { Piecewise spwc; CurvePortion cp(A, _interval_st, _interval_et); evaluate_rec( spwc, cp.get_curve(), B, fpi_matrix, lpi_matrix, sample_distances, half_real_step ); append(pwc, spwc, _interval_st, rec_piece_step); } else { pwc.push(piece, _interval_et); } } else { pwc.push(piece, _interval_et); } } // recursive routine: if the interpolated piece is accurate enough // it's returned in the out-parameter pwc, otherwise the computation of // two new piecees is performed using half of the current step so the // number of samples per piece is always the same, while the interpolation // of one piece is split into the computation of two new piecees when // needed. void evaluate_rec( Piecewise & pwc, curveA_type const& A, curveB_type const& B, NL::Matrix & fpi_matrix, NL::Matrix & lpi_matrix, NL::Vector & sample_distances, double real_step ) { const double half_real_step = real_step / 2; const bool adaptive = !(real_step < adaptive_limit); static const unsigned int middle_sample_index = samples_per_piece + 1; pwc.clear(); pwc.push_cut(0); // sample_distances used to check accuracy and for the interpolation // of the two sub-pieces when needed NL::Vector sample_distances_1(rec_total_samples); NL::Vector sample_distances_2(rec_total_samples); // view of even indexes of sample_distances_1 NL::VectorView sd1_view_0(sample_distances_1, middle_sample_index, 0, 2); // view of even indexes of sample_distances_2 NL::VectorView sd2_view_0(sample_distances_2, middle_sample_index, 0, 2); // view of first half (+ 1) of sample_distances NL::VectorView sd_view_1(sample_distances, middle_sample_index, 0); // view of second half of sample_distances NL::VectorView sd_view_2(sample_distances, middle_sample_index, samples_per_piece); sd1_view_0 = sd_view_1; sd2_view_0 = sd_view_2; // if we have to check accuracy and go on with recursion // we need to compute the distance samples of middle points // of all current samples, because the new step is half of // the current one if (adaptive) { Point At; double nptime; double t = rec_half_step; for (unsigned int i = 1; i < sample_distances.size(); i+=2) { At = A(t); nptime = nearest_time(At, B); sample_distances_1[i] = distance(At, B(nptime)); At = A(t + rec_piece_step); nptime = nearest_time(At, B); sample_distances_2[i] = distance(At, B(nptime)); t += rec_step; } } // first piece evaluate_piece_rec( pwc, A, B, fpi_matrix, fpi_matrix, lpi_matrix, sample_distances, sample_distances_1, adaptive, 0, // interpolation_si shared_ei, // interpolation_ei 0, // portion_st rec_piece_step, // portion_et half_real_step ); // copy back junction parts because // the interpolate routine modifies them for ( unsigned int i = 0, j = samples_per_piece - 1; i < samples_per_junction; ++i, --j ) { sd_view_1[j] = sd1_view_0[j]; sd_view_2[i] = sd2_view_0[i]; } // last piece evaluate_piece_rec( pwc, A, B, lpi_matrix, fpi_matrix, lpi_matrix, sample_distances, sample_distances_2, adaptive, shared_si, // interpolation_si rec_total_samples, // interpolation_ei rec_piece_step, // portion_st rec_piece_2steps, // portion_et half_real_step ); } void evaluate_piece( Piecewise & pwc, curveA_type const& A, curveB_type const& B, NL::Matrix & psdinv_matrix, NL::Matrix & fpi_matrix, NL::Matrix & lpi_matrix, NL::Vector & curr_vector, NL::Vector & sample_distances, NL::Vector & end_junction, NL::VectorView & start_junction_view, NL::VectorView & end_junction_view, double & t ) { //static size_t index = 0; //std::cerr << "index = " << index++ << std::endl; bool good; SBasis piece; Piecewise spwc; interval_et += piece_step; //std::cerr << "interval: " << interval_st << ", " << interval_et << std::endl; //std::cerr << "interpolation range: " << interpolation_si << ", " << interpolation_ei << std::endl; //std::cerr << "interpolation samples = " << interpolation_samples << std::endl; evaluate_samples( A, B, curr_vector, t ); //std::cerr << "current vector: " << curr_vector << std::endl; for ( unsigned int i = 0, k = interval_si; i < sample_distances.size(); i+=2, ++k ) { sample_distances[i] = curr_vector[k]; } Point At; double nptime; double tt = interval_st + half_step; for (unsigned int i = 1; i < sample_distances.size(); i+=2) { At = A(tt); nptime = nearest_time(At, B); sample_distances[i] = distance(At, B(nptime)); tt += step; } //std::cerr << "sample_distances: " << sample_distances << std::endl; end_junction = end_junction_view; interpolate( piece, psdinv_matrix, curr_vector, interpolation_si, interpolation_samples, portion_st, portion_et ); good = check_accuracy( piece, sample_distances, rec_step ); //std::cerr << "good: " << good << std::endl; //good = true; if (!good) { CurvePortion cp(A, interval_st, interval_et); evaluate_rec( spwc, cp.get_curve(), B, fpi_matrix, lpi_matrix, sample_distances, half_step ); append(pwc, spwc, interval_st, piece_step); } else { pwc.push(piece, interval_et); } interval_st = interval_et; for (unsigned int i = 0; i < samples_per_junction; ++i) { curr_vector[i] = start_junction_view[i]; curr_vector[samples_per_junction + i] = end_junction[i]; } } public: void evaluate( Piecewise & pwc, curveA_type const& A, curveB_type const& B, unsigned int _piecees ) { piecees = _piecees; init(); assert( !(piecees & 1) ); assert( !(piece_size & 1) ); assert( rec_total_samples & 1); pwc.clear(); pwc.push_cut(0); NL::Matrix power_matrix(rec_total_samples, piece_size); init_power_matrix(power_matrix); NL::MatrixView rec_fmv( power_matrix, 0, 0, shared_ei, piece_size ); NL::Matrix rec_fpim = NL::pseudo_inverse(rec_fmv); NL::MatrixView rec_lmv( power_matrix, shared_si, 0, rec_total_samples - shared_si, piece_size ); NL::Matrix rec_lpim = NL::pseudo_inverse(rec_lmv); NL::Vector curr_vector(samples_per_interpolation); NL::Vector sample_distances(rec_total_samples); NL::Vector end_junction(samples_per_junction); NL::VectorView start_junction_view( sample_distances, samples_per_junction, rec_total_samples - 1 - samples_per_2junctions, 2 ); NL::VectorView end_junction_view( curr_vector, samples_per_junction, samples_per_junction + samples_per_piece ); double t = 0; // first piece evaluation_si = interval_si; evaluation_ei = samples_per_interpolation; interpolation_si = evaluation_si; interpolation_ei = evaluation_ei; interpolation_samples = interpolation_ei - interpolation_si; interval_st = 0; interval_et = 0; NL::MatrixView fmv( power_matrix, interpolation_si, 0, interpolation_samples, piece_size ); NL::Matrix fpim = NL::pseudo_inverse(fmv); evaluate_piece( pwc, A, B, fpim, rec_fpim, rec_lpim, curr_vector, sample_distances, end_junction, start_junction_view, end_junction_view, t ); // general case evaluation_si = interval_si + samples_per_junction; evaluation_ei = samples_per_interpolation; interpolation_si = 0; interpolation_ei = evaluation_ei; interpolation_samples = interpolation_ei - interpolation_si; NL::MatrixView gmv( power_matrix, interpolation_si, 0, interpolation_samples, piece_size ); NL::Matrix gpim = NL::pseudo_inverse(gmv); for ( unsigned int piece_index = 1; piece_index < piecees - 1; ++piece_index ) { evaluate_piece( pwc, A, B, gpim, rec_fpim, rec_lpim, curr_vector, sample_distances, end_junction, start_junction_view, end_junction_view, t ); } // last piece evaluation_si = interval_si + samples_per_junction; evaluation_ei = interval_ei + 1; interpolation_si = 0; interpolation_ei = evaluation_ei; interpolation_samples = interpolation_ei -interpolation_si; NL::MatrixView lmv( power_matrix, interpolation_si, 0, interpolation_samples, piece_size ); NL::Matrix lpim = NL::pseudo_inverse(lmv); evaluate_piece( pwc, A, B, lpim, rec_fpim, rec_lpim, curr_vector, sample_distances, end_junction, start_junction_view, end_junction_view, t ); } distance_impl() : accuracy(1e-3), adaptive_limit(1e-5) {} void set_accuracy(double _accuracy) { accuracy = _accuracy; } void set_adaptive_limit(double _adaptive_limit) { adaptive_limit = _adaptive_limit; } }; // end class distance_impl } // end namespace detail template < typename Curve01T, typename CurveT > inline Piecewise distance( Curve01T const& A, CurveT const& B, unsigned int pieces = 40, double adaptive_limit = 1e-5, double accuracy = 1e-3 ) { detail::distance_impl dist; dist.set_accuracy(accuracy); dist.set_adaptive_limit(adaptive_limit); Piecewise pwc; dist.evaluate(pwc, A, B, pieces); return pwc; } template < typename CurveT > inline Piecewise distance( Piecewise< D2 > const& A, CurveT const& B, unsigned int pieces = 40, double adaptive_limit = 1e-5, double accuracy = 1e-3 ) { Piecewise result; Piecewise pwc; for (unsigned int i = 0; i < A.size(); ++i) { pwc = distance(A[i], B, pieces, adaptive_limit, accuracy); pwc.scaleDomain(A.cuts[i+1] - A.cuts[i]); pwc.offsetDomain(A.cuts[i]); result.concat(pwc); } return result; } template < typename CurveT > inline Piecewise distance( Path const& A, CurveT const& B, unsigned int pieces = 40, double adaptive_limit = 1e-5, double accuracy = 1e-3 ) { Piecewise result; Piecewise pwc; unsigned int sz = A.size(); if (A.closed()) ++sz; for (unsigned int i = 0; i < sz; ++i) { pwc = distance(A[i], B, pieces, adaptive_limit, accuracy); pwc.offsetDomain(i); result.concat(pwc); } return result; } template < typename Curve01T, typename CurveT > unsigned int dist_test( Piecewise const& pwc, Curve01T const& A, CurveT const& B, double step ) { std::cerr << "======= inside dist test =======" << std::endl; unsigned int total_checked_values = 0; unsigned int total_error = 0; double nptime, sample_distance; Point At; for (double t = 0; t <= 1; t += step) { At = A(t); nptime = nearest_time(At, B); sample_distance = distance(At, B(nptime)); if ( !are_near(pwc(t), sample_distance, 0.001) ) { ++total_error; std::cerr << "error at t: " << t << std::endl; } ++total_checked_values; } std::cerr << " total checked values : " << total_checked_values << std::endl; std::cerr << " total error : " << total_error << std::endl; return total_error; } } // end namespace Geom using namespace Geom; class DCCToy : public Toy { private: void draw( cairo_t *cr, std::ostringstream *notify, int width, int height, bool save, std::ostringstream *timer_stream) override { Point ulc(width - 300, height - 60 ); toggles[0].bounds = Rect(ulc, ulc + Point(160,25) ); toggles[1].bounds = Rect(ulc + Point(0,30), ulc + Point(160,55) ); sliders[0].geometry(ulc - Point(450,0), 400); if (toggle0_status != toggles[0].on) { toggle0_status = toggles[0].on; using std::swap; swap(sliders[0], sliders[1]); } cairo_set_source_rgba(cr, 0.3, 0.3, 0.3, 1.0); cairo_set_line_width (cr, 0.3); if (choice == 0) { A = single_curve_psh.asBezier(); cairo_d2_sb(cr, A); } else if (choice == 1) { pA.clear(); for (unsigned int k = 0; k < path_curves; ++k) { PointSetHandle psh; psh.pts.resize(path_handles_per_curve); for (unsigned int h = 0; h < path_handles_per_curve; ++h) { unsigned int kk = k * (path_handles_per_curve-1) + h; psh.pts[h] = path_psh.pts[kk]; } pA.append(psh.asBezier()); } cairo_path(cr, pA); } else if (choice == 2) { for (unsigned int i = 0; i < pwc_curves; ++i) { pwA.segs[i] = pwc_psh[i].asBezier(); } cairo_pw_d2_sb(cr, pwA); } D2 B = B_psh.asBezier(); cairo_d2_sb(cr, B); double t = sliders[0].value(); Piecewise d; unsigned int total_error = 0; Point cursor; if (!toggles[0].on) { if (choice == 0) { cursor = A(t); d = distance(A, B, 40); // uncomment following lines to view errors in computing the distance //total_error = dist_test(d, A, B, 0.0004); } else if (choice == 1) { cursor = pA(t); d = distance(pA, B, 40); // uncomment following lines to view errors in computing the distance //total_error = dist_test(d, pA, B, 0.0004); } else if (choice == 2) { cursor = pwA(t); d = distance(pwA, B, 40); // uncomment following lines to view errors in computing the distance //total_error = dist_test(d, pwA, B, 0.0004); } double nptB = nearest_time(cursor, B); draw_circ(cr, cursor); cairo_move_to(cr, cursor); cairo_line_to(cr, B(nptB)); cairo_stroke(cr); } else { Point np(0,0); cursor = B(t); if (choice == 0) { double nptA = nearest_time(cursor, A); np = A(nptA); d = distance(B, A, 40); // uncomment following lines to view errors in computing the distance //total_error = dist_test(d, B, A, 0.0004); } else if (choice == 1) { double nptA = nearest_time(cursor, pA); np = pA(nptA); d = distance(B, pA, 40); // uncomment following lines to view errors in computing the distance //total_error = dist_test(d, B, pA, 0.0004); } draw_circ(cr, cursor); cairo_move_to(cr, cursor); cairo_line_to(cr, np); cairo_stroke(cr); } if (total_error != 0) *notify << "total error: " << total_error << " "; // draw distance function Piecewise< D2 > pwc; pwc.cuts = d.cuts; pwc.segs.resize(d.size()); D2 piece; double domain_length = 800 / d.domain().extent(); for ( unsigned int i = 0; i < d.size(); ++i ) { piece[X] = SBasis(Linear(20,20) + domain_length * Linear(d.cuts[i], d.cuts[i+1])); piece[Y] = 3 * d.segs[i]; pwc.segs[i] = piece; } cairo_set_source_rgb(cr, 0.7,0,0); cairo_pw_d2_sb(cr, pwc); *notify << "total cuts: " << pwc.cuts.size(); if (toggles[1].on) { for (unsigned int i = 0; i < pwc.cuts.size(); ++i) { draw_handle(cr, pwc(pwc.cuts[i])); } } else { draw_handle(cr, pwc(0.0)); draw_handle(cr, pwc(0.25)); draw_handle(cr, pwc(0.5)); draw_handle(cr, pwc(0.75)); draw_handle(cr, pwc(1)); } draw_circ(cr, pwc(t)); cairo_stroke(cr); Toy::draw(cr, notify, width, height, save,timer_stream); } public: DCCToy() { toggle0_status = false; choice = 0; single_curve_handles = 6; path_curves = 3; path_handles_per_curve = 4; path_total_handles = path_curves * (path_handles_per_curve - 1) + 1; pwc_curves = 3; pwc_handles_per_curve = 4; pwc_total_handles = pwc_curves * pwc_handles_per_curve; B_handles = 4; if (choice == 0) { for (unsigned int i = 0; i < single_curve_handles; ++i) { single_curve_psh.push_back(700*uniform(), 500*uniform()); } handles.push_back(&single_curve_psh); sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); } else if (choice == 1) { for (unsigned int i = 0; i < path_total_handles; ++i) { path_psh.push_back(700*uniform(), 500*uniform()); } handles.push_back(&path_psh); sliders.emplace_back(0.0, path_curves, 0.0, 0.0, "t"); } else if (choice == 2) { pwc_psh.resize(pwc_curves); pwA.segs.resize(pwc_curves); pwA.cuts.resize(pwc_curves+1); pwA.cuts[0] = 0; double length = 1.0 / pwc_curves; for (unsigned int i = 0; i < pwc_curves; ++i) { for (unsigned int j = 0; j < pwc_handles_per_curve; ++j) { pwc_psh[i].push_back(700*uniform(), 500*uniform()); } handles.push_back(&(pwc_psh[i])); pwA.cuts[i+1] = pwA.cuts[i] + length; } sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); } for (unsigned int i = 0; i < B_handles; ++i) { B_psh.push_back(700*uniform(), 500*uniform()); } handles.push_back(&B_psh); sliders.emplace_back(0.0, 1.0, 0.0, 0.0, "t"); toggles.emplace_back("d(A,B) <-> d(B,A)", false); toggles.emplace_back("Show/Hide cuts", false); handles.push_back(&(toggles[0])); handles.push_back(&(toggles[1])); handles.push_back(&(sliders[0])); } private: bool toggle0_status; unsigned int choice; unsigned int single_curve_handles, B_handles; unsigned int path_curves, path_handles_per_curve, path_total_handles; unsigned int pwc_curves, pwc_handles_per_curve, pwc_total_handles; PointSetHandle single_curve_psh; PointSetHandle path_psh; std::vector pwc_psh; PointSetHandle B_psh; std::vector toggles; std::vector sliders; D2 A; Path pA; Piecewise< D2 > pwA; }; int main(int argc, char **argv) { init( argc, argv, new DCCToy(), 840, 600 ); return 0; } /* Local Variables: mode:c++ c-file-style:"stroustrup" c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) indent-tabs-mode:nil fill-column:99 End: */ // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :