Code

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