Code

Merge and cleanup of GSoC C++-ification project.
[inkscape.git] / src / sp-spiral.cpp
1 /** \file
2  * <sodipodi:spiral> implementation
3  */
4 /*
5  * Authors:
6  *   Mitsuru Oka <oka326@parkcity.ne.jp>
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Abhishek Sharma
9  *
10  * Copyright (C) 1999-2002 Lauris Kaplinski
11  * Copyright (C) 2000-2001 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #include "config.h"
19 #include "svg/svg.h"
20 #include "attributes.h"
21 #include <2geom/bezier-utils.h>
22 #include <2geom/pathvector.h>
23 #include "display/curve.h"
24 #include <glibmm/i18n.h>
25 #include "xml/repr.h"
26 #include "document.h"
28 #include "sp-spiral.h"
30 static void sp_spiral_class_init (SPSpiralClass *klass);
31 static void sp_spiral_init (SPSpiral *spiral);
33 static void sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
34 static Inkscape::XML::Node *sp_spiral_write (SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
35 static void sp_spiral_set (SPObject *object, unsigned int key, const gchar *value);
36 static void sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags);
38 static gchar * sp_spiral_description (SPItem * item);
39 static void sp_spiral_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs);
41 static void sp_spiral_set_shape (SPShape *shape);
42 static void sp_spiral_update_patheffect (SPLPEItem *lpeitem, bool write);
44 static Geom::Point sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t);
46 static SPShapeClass *parent_class;
48 /**
49  * Register SPSpiral class and return its type number.
50  */
51 GType
52 sp_spiral_get_type (void)
53 {
54     static GType spiral_type = 0;
56     if (!spiral_type) {
57         GTypeInfo spiral_info = {
58             sizeof (SPSpiralClass),
59             NULL,    /* base_init */
60             NULL,    /* base_finalize */
61             (GClassInitFunc) sp_spiral_class_init,
62             NULL,    /* class_finalize */
63             NULL,    /* class_data */
64             sizeof (SPSpiral),
65             16,    /* n_preallocs */
66             (GInstanceInitFunc) sp_spiral_init,
67             NULL,    /* value_table */
68         };
69         spiral_type = g_type_register_static (SP_TYPE_SHAPE, "SPSpiral", &spiral_info, (GTypeFlags)0);
70     }
71     return spiral_type;
72 }
74 /**
75  * SPSpiral vtable initialization.
76  */
77 static void
78 sp_spiral_class_init (SPSpiralClass *klass)
79 {
80     GObjectClass * gobject_class;
81     SPObjectClass * sp_object_class;
82     SPItemClass * item_class;
83     SPLPEItemClass * lpe_item_class;
84     SPShapeClass *shape_class;
86     gobject_class = (GObjectClass *) klass;
87     sp_object_class = (SPObjectClass *) klass;
88     item_class = (SPItemClass *) klass;
89     lpe_item_class = (SPLPEItemClass *) klass;
90     shape_class = (SPShapeClass *) klass;
92     parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE);
94     sp_object_class->build = sp_spiral_build;
95     sp_object_class->write = sp_spiral_write;
96     sp_object_class->set = sp_spiral_set;
97     sp_object_class->update = sp_spiral_update;
99     item_class->description = sp_spiral_description;
100     item_class->snappoints = sp_spiral_snappoints;
102     lpe_item_class->update_patheffect = sp_spiral_update_patheffect;
104     shape_class->set_shape = sp_spiral_set_shape;
107 /**
108  * Callback for SPSpiral object initialization.
109  */
110 static void
111 sp_spiral_init (SPSpiral * spiral)
113     spiral->cx         = 0.0;
114     spiral->cy         = 0.0;
115     spiral->exp        = 1.0;
116     spiral->revo       = 3.0;
117     spiral->rad        = 1.0;
118     spiral->arg        = 0.0;
119     spiral->t0         = 0.0;
122 /**
123  * Virtual build: set spiral properties from corresponding repr.
124  */
125 static void
126 sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr)
128     if (((SPObjectClass *) parent_class)->build)
129         ((SPObjectClass *) parent_class)->build (object, document, repr);
131     object->readAttr( "sodipodi:cx" );
132     object->readAttr( "sodipodi:cy" );
133     object->readAttr( "sodipodi:expansion" );
134     object->readAttr( "sodipodi:revolution" );
135     object->readAttr( "sodipodi:radius" );
136     object->readAttr( "sodipodi:argument" );
137     object->readAttr( "sodipodi:t0" );
140 /**
141  * Virtual write: write spiral attributes to corresponding repr.
142  */
143 static Inkscape::XML::Node *
144 sp_spiral_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
146     SPSpiral *spiral = SP_SPIRAL (object);
148     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
149         repr = xml_doc->createElement("svg:path");
150     }
152     if (flags & SP_OBJECT_WRITE_EXT) {
153         /* Fixme: we may replace these attributes by
154          * sodipodi:spiral="cx cy exp revo rad arg t0"
155          */
156         repr->setAttribute("sodipodi:type", "spiral");
157         sp_repr_set_svg_double(repr, "sodipodi:cx", spiral->cx);
158         sp_repr_set_svg_double(repr, "sodipodi:cy", spiral->cy);
159         sp_repr_set_svg_double(repr, "sodipodi:expansion", spiral->exp);
160         sp_repr_set_svg_double(repr, "sodipodi:revolution", spiral->revo);
161         sp_repr_set_svg_double(repr, "sodipodi:radius", spiral->rad);
162         sp_repr_set_svg_double(repr, "sodipodi:argument", spiral->arg);
163         sp_repr_set_svg_double(repr, "sodipodi:t0", spiral->t0);
164     }
166      // make sure the curve is rebuilt with all up-to-date parameters
167      sp_spiral_set_shape ((SPShape *) spiral);
169     //Duplicate the path
170     SPCurve *curve = ((SPShape *) spiral)->curve;
171     //Nulls might be possible if this called iteratively
172     if ( !curve ) {
173             //g_warning("sp_spiral_write(): No path to copy\n");
174             return NULL;
175     }
176     char *d = sp_svg_write_path ( curve->get_pathvector() );
177     repr->setAttribute("d", d);
178     g_free (d);
180     if (((SPObjectClass *) (parent_class))->write)
181         ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr, flags | SP_SHAPE_WRITE_PATH);
183     return repr;
186 /**
187  * Virtual set: change spiral object attribute.
188  */
189 static void
190 sp_spiral_set (SPObject *object, unsigned int key, const gchar *value)
192     SPSpiral *spiral;
193     SPShape  *shape;
195     spiral = SP_SPIRAL (object);
196     shape  = SP_SHAPE (object);
198     /// \todo fixme: we should really collect updates
199     switch (key) {
200     case SP_ATTR_SODIPODI_CX:
201         if (!sp_svg_length_read_computed_absolute (value, &spiral->cx)) {
202             spiral->cx = 0.0;
203         }
204         object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
205         break;
206     case SP_ATTR_SODIPODI_CY:
207         if (!sp_svg_length_read_computed_absolute (value, &spiral->cy)) {
208             spiral->cy = 0.0;
209         }
210         object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
211         break;
212     case SP_ATTR_SODIPODI_EXPANSION:
213         if (value) {
214             /** \todo
215                          * FIXME: check that value looks like a (finite)
216                          * number. Create a routine that uses strtod, and
217                          * accepts a default value (if strtod finds an error).
218                          * N.B. atof/sscanf/strtod consider "nan" and "inf"
219                          * to be valid numbers.
220                          */
221             spiral->exp = g_ascii_strtod (value, NULL);
222             spiral->exp = CLAMP (spiral->exp, 0.0, 1000.0);
223         } else {
224             spiral->exp = 1.0;
225         }
226         object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
227         break;
228     case SP_ATTR_SODIPODI_REVOLUTION:
229         if (value) {
230             spiral->revo = g_ascii_strtod (value, NULL);
231             spiral->revo = CLAMP (spiral->revo, 0.05, 1024.0);
232         } else {
233             spiral->revo = 3.0;
234         }
235         object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
236         break;
237     case SP_ATTR_SODIPODI_RADIUS:
238         if (!sp_svg_length_read_computed_absolute (value, &spiral->rad)) {
239             spiral->rad = MAX (spiral->rad, 0.001);
240         }
241         object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
242         break;
243     case SP_ATTR_SODIPODI_ARGUMENT:
244         if (value) {
245             spiral->arg = g_ascii_strtod (value, NULL);
246             /** \todo
247                          * FIXME: We still need some bounds on arg, for
248                          * numerical reasons. E.g., we don't want inf or NaN,
249                          * nor near-infinite numbers. I'm inclined to take
250                          * modulo 2*pi.  If so, then change the knot editors,
251                          * which use atan2 - revo*2*pi, which typically
252                          * results in very negative arg.
253                          */
254         } else {
255             spiral->arg = 0.0;
256         }
257         object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
258         break;
259     case SP_ATTR_SODIPODI_T0:
260         if (value) {
261             spiral->t0 = g_ascii_strtod (value, NULL);
262             spiral->t0 = CLAMP (spiral->t0, 0.0, 0.999);
263             /** \todo
264                          * Have shared constants for the allowable bounds for
265                          * attributes. There was a bug here where we used -1.0
266                          * as the minimum (which leads to NaN via, e.g.,
267                          * pow(-1.0, 0.5); see sp_spiral_get_xy for
268                          * requirements.
269                          */
270         } else {
271             spiral->t0 = 0.0;
272         }
273         object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
274         break;
275     default:
276         if (((SPObjectClass *) parent_class)->set)
277             ((SPObjectClass *) parent_class)->set (object, key, value);
278         break;
279     }
282 /**
283  * Virtual update callback.
284  */
285 static void
286 sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags)
288     if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
289         ((SPShape *) object)->setShape ();
290     }
292     if (((SPObjectClass *) parent_class)->update)
293         ((SPObjectClass *) parent_class)->update (object, ctx, flags);
296 static void
297 sp_spiral_update_patheffect(SPLPEItem *lpeitem, bool write)
299     SPShape *shape = (SPShape *) lpeitem;
300     sp_spiral_set_shape(shape);
302     if (write) {
303         Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape);
304         if ( shape->curve != NULL ) {
305             gchar *str = sp_svg_write_path(shape->curve->get_pathvector());
306             repr->setAttribute("d", str);
307             g_free(str);
308         } else {
309             repr->setAttribute("d", NULL);
310         }
311     }
313     ((SPObject *)shape)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
316 /**
317  * Return textual description of spiral.
318  */
319 static gchar *
320 sp_spiral_description (SPItem * item)
322     // TRANSLATORS: since turn count isn't an integer, please adjust the
323     // string as needed to deal with an localized plural forms.
324     return g_strdup_printf (_("<b>Spiral</b> with %3f turns"), SP_SPIRAL(item)->revo);
328 /**
329  * Fit beziers together to spiral and draw it.
330  *
331  * \pre dstep \> 0.
332  * \pre is_unit_vector(*hat1).
333  * \post is_unit_vector(*hat2).
334  **/
335 static void
336 sp_spiral_fit_and_draw (SPSpiral const *spiral,
337             SPCurve     *c,
338             double dstep,
339             Geom::Point darray[],
340             Geom::Point const &hat1,
341             Geom::Point &hat2,
342             double *t)
344 #define BEZIER_SIZE   4
345 #define FITTING_MAX_BEZIERS 4
346 #define BEZIER_LENGTH (BEZIER_SIZE * FITTING_MAX_BEZIERS)
347     g_assert (dstep > 0);
348     g_assert (is_unit_vector (hat1));
350     Geom::Point bezier[BEZIER_LENGTH];
351     double d;
352     int depth, i;
354     for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) {
355         darray[i] = sp_spiral_get_xy(spiral, d);
357         /* Avoid useless adjacent dups.  (Otherwise we can have all of darray filled with
358            the same value, which upsets chord_length_parameterize.) */
359         if ((i != 0)
360             && (darray[i] == darray[i - 1])
361             && (d < 1.0)) {
362             i--;
363             d += dstep;
364             /** We mustn't increase dstep for subsequent values of
365                          * i: for large spiral.exp values, rate of growth
366                          * increases very rapidly.
367                          */
368                         /** \todo
369                          * Get the function itself to decide what value of d
370                          * to use next: ensure that we move at least 0.25 *
371                          * stroke width, for example.  The derivative (as used
372                          * for get_tangent before normalization) would be
373                          * useful for estimating the appropriate d value.  Or
374                          * perhaps just start with a small dstep and scale by
375                          * some small number until we move >= 0.25 *
376                          * stroke_width.  Must revert to the original dstep
377                          * value for next iteration to avoid the problem
378                          * mentioned above.
379                          */
380         }
381     }
383     double const next_t = d - 2 * dstep;
384     /* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */
386     hat2 = -sp_spiral_get_tangent (spiral, next_t);
388     /** \todo
389          * We should use better algorithm to specify maximum error.
390          */
391     depth = Geom::bezier_fit_cubic_full (bezier, NULL, darray, SAMPLE_SIZE,
392                       hat1, hat2,
393                       SPIRAL_TOLERANCE*SPIRAL_TOLERANCE,
394                       FITTING_MAX_BEZIERS);
395     g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier)));
396 #ifdef SPIRAL_DEBUG
397     if (*t == spiral->t0 || *t == 1.0)
398         g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n",
399              debug_state, depth, dstep, spiral->t0, *t, spiral->arg);
400 #endif
401     if (depth != -1) {
402         for (i = 0; i < 4*depth; i += 4) {
403             c->curveto(bezier[i + 1],
404                       bezier[i + 2],
405                       bezier[i + 3]);
406         }
407     } else {
408 #ifdef SPIRAL_VERBOSE
409         g_print ("cant_fit_cubic: t=%g\n", *t);
410 #endif
411         for (i = 1; i < SAMPLE_SIZE; i++)
412             c->lineto(darray[i]);
413     }
414     *t = next_t;
415     g_assert (is_unit_vector (hat2));
418 static void
419 sp_spiral_set_shape (SPShape *shape)
421     SPSpiral *spiral = SP_SPIRAL(shape);
423     if (sp_lpe_item_has_broken_path_effect(SP_LPE_ITEM(shape))) {
424         g_warning ("The spiral shape has unknown LPE on it! Convert to path to make it editable preserving the appearance; editing it as spiral will remove the bad LPE");
425         if (SP_OBJECT_REPR(shape)->attribute("d")) {
426             // unconditionally read the curve from d, if any, to preserve appearance
427             Geom::PathVector pv = sp_svg_read_pathv(SP_OBJECT_REPR(shape)->attribute("d"));
428             SPCurve *cold = new SPCurve(pv);
429             shape->setCurveInsync( cold, TRUE);
430             cold->unref();
431         }
432         return;
433     }
435     Geom::Point darray[SAMPLE_SIZE + 1];
436     double t;
438     SP_OBJECT (spiral)->requestModified(SP_OBJECT_MODIFIED_FLAG);
440     SPCurve *c = new SPCurve ();
442 #ifdef SPIRAL_VERBOSE
443     g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n",
444          spiral->cx,
445          spiral->cy,
446          spiral->exp,
447          spiral->revo,
448          spiral->rad,
449          spiral->arg,
450          spiral->t0);
451 #endif
453     /* Initial moveto. */
454     c->moveto(sp_spiral_get_xy(spiral, spiral->t0));
456     double const tstep = SAMPLE_STEP / spiral->revo;
457     double const dstep = tstep / (SAMPLE_SIZE - 1);
459     Geom::Point hat1 = sp_spiral_get_tangent (spiral, spiral->t0);
460     Geom::Point hat2;
461     for (t = spiral->t0; t < (1.0 - tstep);) {
462         sp_spiral_fit_and_draw (spiral, c, dstep, darray, hat1, hat2, &t);
464         hat1 = -hat2;
465     }
466     if ((1.0 - t) > SP_EPSILON)
467         sp_spiral_fit_and_draw (spiral, c, (1.0 - t)/(SAMPLE_SIZE - 1.0),
468                     darray, hat1, hat2, &t);
470     /* Reset the shape'scurve to the "original_curve"
471      * This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/
472     shape->setCurveInsync( c, TRUE);
473     if (sp_lpe_item_has_path_effect(SP_LPE_ITEM(shape)) && sp_lpe_item_path_effects_enabled(SP_LPE_ITEM(shape))) {
474         SPCurve *c_lpe = c->copy();
475         bool success = sp_lpe_item_perform_path_effect(SP_LPE_ITEM (shape), c_lpe);
476         if (success) {
477             shape->setCurveInsync( c_lpe, TRUE);
478         }
479         c_lpe->unref();
480     }
481     c->unref();
484 /**
485  * Set spiral properties and update display.
486  */
487 void
488 sp_spiral_position_set       (SPSpiral          *spiral,
489              gdouble            cx,
490              gdouble            cy,
491              gdouble            exp,
492              gdouble            revo,
493              gdouble            rad,
494              gdouble            arg,
495              gdouble            t0)
497     g_return_if_fail (spiral != NULL);
498     g_return_if_fail (SP_IS_SPIRAL (spiral));
500     /** \todo
501          * Consider applying CLAMP or adding in-bounds assertions for
502          * some of these parameters.
503          */
504     spiral->cx         = cx;
505     spiral->cy         = cy;
506     spiral->exp        = exp;
507     spiral->revo       = revo;
508     spiral->rad        = MAX (rad, 0.0);
509     spiral->arg        = arg;
510     spiral->t0         = CLAMP(t0, 0.0, 0.999);
512     ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
515 /**
516  * Virtual snappoints callback.
517  */
518 static void sp_spiral_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs)
520     // We will determine the spiral's midpoint ourselves, instead of trusting on the base class
521     // Therefore setSnapObjectMidpoints() is set to false temporarily
522     Inkscape::SnapPreferences local_snapprefs = *snapprefs;
523     local_snapprefs.setSnapObjectMidpoints(false);
525     if (((SPItemClass *) parent_class)->snappoints) {
526         ((SPItemClass *) parent_class)->snappoints (item, p, &local_snapprefs);
527     }
529     // Help enforcing strict snapping, i.e. only return nodes when we're snapping nodes to nodes or a guide to nodes
530     if (!(snapprefs->getSnapModeNode() || snapprefs->getSnapModeGuide())) {
531         return;
532     }
534     if (snapprefs->getSnapObjectMidpoints()) {
535         Geom::Matrix const i2d (item->i2d_affine ());
536         SPSpiral *spiral = SP_SPIRAL(item);
537         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(spiral->cx, spiral->cy) * i2d, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT));
538         // This point is the start-point of the spiral, which is also returned when _snap_to_itemnode has been set
539         // in the object snapper. In that case we will get a duplicate!
540     }
543 /**
544  * Return one of the points on the spiral.
545  *
546  * \param t specifies how far along the spiral.
547  * \pre \a t in [0.0, 2.03].  (It doesn't make sense for t to be much more
548  * than 1.0, though some callers go slightly beyond 1.0 for curve-fitting
549  * purposes.)
550  */
551 Geom::Point sp_spiral_get_xy (SPSpiral const *spiral, gdouble t)
553     g_assert (spiral != NULL);
554     g_assert (SP_IS_SPIRAL(spiral));
555     g_assert (spiral->exp >= 0.0);
556     /* Otherwise we get NaN for t==0. */
557     g_assert (spiral->exp <= 1000.0);
558     /* Anything much more results in infinities.  Even allowing 1000 is somewhat overkill. */
559     g_assert (t >= 0.0);
560     /* Any callers passing -ve t will have a bug for non-integral values of exp. */
562     double const rad = spiral->rad * pow(t, (double) spiral->exp);
563     double const arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
565     return Geom::Point(rad * cos (arg) + spiral->cx,
566                            rad * sin (arg) + spiral->cy);
570 /**
571  * Returns the derivative of sp_spiral_get_xy with respect to t,
572  *  scaled to a unit vector.
573  *
574  *  \pre spiral != 0.
575  *  \pre 0 \<= t.
576  *  \pre p != NULL.
577  *  \post is_unit_vector(*p).
578  */
579 static Geom::Point
580 sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t)
582     Geom::Point ret(1.0, 0.0);
583     g_return_val_if_fail (( ( spiral != NULL )
584                 && SP_IS_SPIRAL(spiral) ),
585                   ret);
586     g_assert (t >= 0.0);
587     g_assert (spiral->exp >= 0.0);
588     /* See above for comments on these assertions. */
590     double const t_scaled = 2.0 * M_PI * spiral->revo * t;
591     double const arg = t_scaled + spiral->arg;
592     double const s = sin (arg);
593     double const c = cos (arg);
595     if (spiral->exp == 0.0) {
596         ret = Geom::Point(-s, c);
597     } else if (t_scaled == 0.0) {
598         ret = Geom::Point(c, s);
599     } else {
600         Geom::Point unrotated(spiral->exp, t_scaled);
601         double const s_len = L2 (unrotated);
602         g_assert (s_len != 0);
603         /** \todo
604                  * Check that this isn't being too hopeful of the hypot
605                  * function.  E.g. test with numbers around 2**-1070
606                  * (denormalized numbers), preferably on a few different
607                  * platforms.  However, njh says that the usual implementation
608                  * does handle both very big and very small numbers.
609                  */
610         unrotated /= s_len;
612         /* ret = spiral->exp * (c, s) + t_scaled * (-s, c);
613            alternatively ret = (spiral->exp, t_scaled) * (( c, s),
614                                   (-s, c)).*/
615         ret = Geom::Point(dot(unrotated, Geom::Point(c, -s)),
616                                   dot(unrotated, Geom::Point(s, c)));
617         /* ret should already be approximately normalized: the
618            matrix ((c, -s), (s, c)) is orthogonal (it just
619            rotates by arg), and unrotated has been normalized,
620            so ret is already of unit length other than numerical
621            error in the above matrix multiplication. */
623         /** \todo
624                  * I haven't checked how important it is for ret to be very
625                  * near unit length; we could get rid of the below.
626                  */
628         ret.normalize();
629         /* Proof that ret length is non-zero: see above.  (Should be near 1.) */
630     }
632     g_assert (is_unit_vector (ret));
633     return ret;
636 /**
637  * Compute rad and/or arg for point on spiral.
638  */
639 void
640 sp_spiral_get_polar (SPSpiral const *spiral, gdouble t, gdouble *rad, gdouble *arg)
642     g_return_if_fail (spiral != NULL);
643     g_return_if_fail (SP_IS_SPIRAL(spiral));
645     if (rad)
646         *rad = spiral->rad * pow(t, (double) spiral->exp);
647     if (arg)
648         *arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
651 /**
652  * Return true if spiral has properties that make it invalid.
653  */
654 bool
655 sp_spiral_is_invalid (SPSpiral const *spiral)
657     gdouble rad;
659     sp_spiral_get_polar (spiral, 0.0, &rad, NULL);
660     if (rad < 0.0 || rad > SP_HUGE) {
661         g_print ("rad(t=0)=%g\n", rad);
662         return TRUE;
663     }
664     sp_spiral_get_polar (spiral, 1.0, &rad, NULL);
665     if (rad < 0.0 || rad > SP_HUGE) {
666         g_print ("rad(t=1)=%g\n", rad);
667         return TRUE;
668     }
669     return FALSE;
672 /*
673   Local Variables:
674   mode:c++
675   c-file-style:"stroustrup"
676   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
677   indent-tabs-mode:nil
678   fill-column:99
679   End:
680 */
681 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :