Code

moving trunk for module inkscape
[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 "display/bezier-utils.h"
23 #include "display/curve.h"
24 #include <glibmm/i18n.h>
25 #include "xml/repr.h"
27 #include "sp-spiral.h"
29 static void sp_spiral_class_init (SPSpiralClass *klass);
30 static void sp_spiral_init (SPSpiral *spiral);
32 static void sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
33 static Inkscape::XML::Node *sp_spiral_write (SPObject *object, Inkscape::XML::Node *repr, guint flags);
34 static void sp_spiral_set (SPObject *object, unsigned int key, const gchar *value);
35 static void sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags);
37 static gchar * sp_spiral_description (SPItem * item);
38 static void sp_spiral_snappoints(SPItem const *item, SnapPointsIter p);
39 static void sp_spiral_set_shape (SPShape *shape);
41 static NR::Point sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t);
43 static SPShapeClass *parent_class;
45 /**
46  * Register SPSpiral class and return its type number.
47  */
48 GType
49 sp_spiral_get_type (void)
50 {
51         static GType spiral_type = 0;
53         if (!spiral_type) {
54                 GTypeInfo spiral_info = {
55                         sizeof (SPSpiralClass),
56                         NULL,   /* base_init */
57                         NULL,   /* base_finalize */
58                         (GClassInitFunc) sp_spiral_class_init,
59                         NULL,   /* class_finalize */
60                         NULL,   /* class_data */
61                         sizeof (SPSpiral),
62                         16,     /* n_preallocs */
63                         (GInstanceInitFunc) sp_spiral_init,
64                         NULL,   /* value_table */
65                 };
66                 spiral_type = g_type_register_static (SP_TYPE_SHAPE, "SPSpiral", &spiral_info, (GTypeFlags)0);
67         }
68         return spiral_type;
69 }
71 /**
72  * SPSpiral vtable initialization.
73  */
74 static void
75 sp_spiral_class_init (SPSpiralClass *klass)
76 {
77         GObjectClass * gobject_class;
78         SPObjectClass * sp_object_class;
79         SPItemClass * item_class;
80         SPShapeClass *shape_class;
82         gobject_class = (GObjectClass *) klass;
83         sp_object_class = (SPObjectClass *) klass;
84         item_class = (SPItemClass *) klass;
85         shape_class = (SPShapeClass *) klass;
87         parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE);
89         sp_object_class->build = sp_spiral_build;
90         sp_object_class->write = sp_spiral_write;
91         sp_object_class->set = sp_spiral_set;
92         sp_object_class->update = sp_spiral_update;
94         item_class->description = sp_spiral_description;
95         item_class->snappoints = sp_spiral_snappoints;
97         shape_class->set_shape = sp_spiral_set_shape;
98 }
100 /**
101  * Callback for SPSpiral object initialization.
102  */
103 static void
104 sp_spiral_init (SPSpiral * spiral)
106         spiral->cx         = 0.0;
107         spiral->cy         = 0.0;
108         spiral->exp        = 1.0;
109         spiral->revo       = 3.0;
110         spiral->rad        = 1.0;
111         spiral->arg        = 0.0;
112         spiral->t0         = 0.0;
115 /**
116  * Virtual build: set spiral properties from corresponding repr.
117  */
118 static void
119 sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr)
121         if (((SPObjectClass *) parent_class)->build)
122                 ((SPObjectClass *) parent_class)->build (object, document, repr);
124         sp_object_read_attr (object, "sodipodi:cx");
125         sp_object_read_attr (object, "sodipodi:cy");
126         sp_object_read_attr (object, "sodipodi:expansion");
127         sp_object_read_attr (object, "sodipodi:revolution");
128         sp_object_read_attr (object, "sodipodi:radius");
129         sp_object_read_attr (object, "sodipodi:argument");
130         sp_object_read_attr (object, "sodipodi:t0");
133 /**
134  * Virtual write: write spiral attributes to corresponding repr.
135  */
136 static Inkscape::XML::Node *
137 sp_spiral_write (SPObject *object, Inkscape::XML::Node *repr, guint flags)
139         SPSpiral *spiral = SP_SPIRAL (object);
141         if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
142                 repr = sp_repr_new ("svg:path");
143         }
145         if (flags & SP_OBJECT_WRITE_EXT) {
146                 /* Fixme: we may replace these attributes by
147                  * sodipodi:spiral="cx cy exp revo rad arg t0"
148                  */
149                 repr->setAttribute("sodipodi:type", "spiral");
150                 sp_repr_set_svg_double(repr, "sodipodi:cx", spiral->cx);
151                 sp_repr_set_svg_double(repr, "sodipodi:cy", spiral->cy);
152                 sp_repr_set_svg_double(repr, "sodipodi:expansion", spiral->exp);
153                 sp_repr_set_svg_double(repr, "sodipodi:revolution", spiral->revo);
154                 sp_repr_set_svg_double(repr, "sodipodi:radius", spiral->rad);
155                 sp_repr_set_svg_double(repr, "sodipodi:argument", spiral->arg);
156                 sp_repr_set_svg_double(repr, "sodipodi:t0", spiral->t0);
157         }
159         //Duplicate the path
160         SPCurve *curve = ((SPShape *) spiral)->curve;
161         //Nulls might be possible if this called iteratively
162         if ( !curve ) {
163                 //g_warning("sp_spiral_write(): No path to copy\n");
164                 return NULL;
165         }
166         NArtBpath *bpath = curve->bpath;
167         if ( !bpath ) {
168                 //g_warning("sp_spiral_write(): No path to copy\n");
169                 return NULL;
170         }
171         char *d = sp_svg_write_path ( bpath );
172         repr->setAttribute("d", d);
173         g_free (d);
175         if (((SPObjectClass *) (parent_class))->write)
176                 ((SPObjectClass *) (parent_class))->write (object, repr, flags | SP_SHAPE_WRITE_PATH);
178         return repr;
181 /**
182  * Virtual set: change spiral object attribute.
183  */
184 static void
185 sp_spiral_set (SPObject *object, unsigned int key, const gchar *value)
187         SPSpiral *spiral;
188         SPShape  *shape;
190         spiral = SP_SPIRAL (object);
191         shape  = SP_SHAPE (object);
193         /// \todo fixme: we should really collect updates 
194         switch (key) {
195         case SP_ATTR_SODIPODI_CX:
196                 if (!sp_svg_length_read_computed_absolute (value, &spiral->cx)) {
197                         spiral->cx = 0.0;
198                 }
199                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
200                 break;
201         case SP_ATTR_SODIPODI_CY:
202                 if (!sp_svg_length_read_computed_absolute (value, &spiral->cy)) {
203                         spiral->cy = 0.0;
204                 }
205                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
206                 break;
207         case SP_ATTR_SODIPODI_EXPANSION:
208                 if (value) {
209                         /** \todo 
210                          * FIXME: check that value looks like a (finite) 
211                          * number. Create a routine that uses strtod, and 
212                          * accepts a default value (if strtod finds an error).
213                          * N.B. atof/sscanf/strtod consider "nan" and "inf" 
214                          * to be valid numbers.
215                          */
216                         spiral->exp = g_ascii_strtod (value, NULL);
217                         spiral->exp = CLAMP (spiral->exp, 0.0, 1000.0);
218                 } else {
219                         spiral->exp = 1.0;
220                 }
221                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
222                 break;
223         case SP_ATTR_SODIPODI_REVOLUTION:
224                 if (value) {
225                         spiral->revo = g_ascii_strtod (value, NULL);
226                         spiral->revo = CLAMP (spiral->revo, 0.05, 1024.0);
227                 } else {
228                         spiral->revo = 3.0;
229                 }
230                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
231                 break;
232         case SP_ATTR_SODIPODI_RADIUS:
233                 if (!sp_svg_length_read_computed_absolute (value, &spiral->rad)) {
234                         spiral->rad = MAX (spiral->rad, 0.001);
235                 }
236                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
237                 break;
238         case SP_ATTR_SODIPODI_ARGUMENT:
239                 if (value) {
240                         spiral->arg = g_ascii_strtod (value, NULL);
241                         /** \todo
242                          * FIXME: We still need some bounds on arg, for 
243                          * numerical reasons. E.g., we don't want inf or NaN, 
244                          * nor near-infinite numbers. I'm inclined to take 
245                          * modulo 2*pi.  If so, then change the knot editors, 
246                          * which use atan2 - revo*2*pi, which typically 
247                          * results in very negative arg.
248                          */
249                 } else {
250                         spiral->arg = 0.0;
251                 }
252                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
253                 break;
254         case SP_ATTR_SODIPODI_T0:
255                 if (value) {
256                         spiral->t0 = g_ascii_strtod (value, NULL);
257                         spiral->t0 = CLAMP (spiral->t0, 0.0, 0.999);
258                         /** \todo
259                          * Have shared constants for the allowable bounds for
260                          * attributes. There was a bug here where we used -1.0 
261                          * as the minimum (which leads to NaN via, e.g., 
262                          * pow(-1.0, 0.5); see sp_spiral_get_xy for 
263                          * requirements.
264                          */
265                 } else {
266                         spiral->t0 = 0.0;
267                 }
268                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
269                 break;
270         default:
271                 if (((SPObjectClass *) parent_class)->set)
272                         ((SPObjectClass *) parent_class)->set (object, key, value);
273                 break;
274         }
277 /**
278  * Virtual update callback.
279  */
280 static void
281 sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags)
283         if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
284                 sp_shape_set_shape ((SPShape *) object);
285         }
287         if (((SPObjectClass *) parent_class)->update)
288                 ((SPObjectClass *) parent_class)->update (object, ctx, flags);
291 /**
292  * Return textual description of spiral.
293  */
294 static gchar *
295 sp_spiral_description (SPItem * item)
297         // TRANSLATORS: since turn count isn't an integer, please adjust the
298         // string as needed to deal with an localized plural forms.
299         return g_strdup_printf (_("<b>Spiral</b> with %3f turns"), SP_SPIRAL(item)->revo);
303 /** 
304  * Fit beziers together to spiral and draw it.
305  *
306  * \pre dstep \> 0.
307  * \pre is_unit_vector(*hat1).
308  * \post is_unit_vector(*hat2).
309  **/
310 static void
311 sp_spiral_fit_and_draw (SPSpiral const *spiral,
312                         SPCurve  *c,
313                         double dstep,
314                         NR::Point darray[],
315                         NR::Point const &hat1,
316                         NR::Point &hat2,
317                         double *t)
319 #define BEZIER_SIZE   4
320 #define FITTING_MAX_BEZIERS 4
321 #define BEZIER_LENGTH (BEZIER_SIZE * FITTING_MAX_BEZIERS)
322         g_assert (dstep > 0);
323         g_assert (is_unit_vector (hat1));
325         NR::Point bezier[BEZIER_LENGTH];
326         double d;
327         int depth, i;
329         for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) {
330                 darray[i] = sp_spiral_get_xy(spiral, d);
332                 /* Avoid useless adjacent dups.  (Otherwise we can have all of darray filled with
333                    the same value, which upsets chord_length_parameterize.) */
334                 if ((i != 0)
335                     && (darray[i] == darray[i - 1])
336                     && (d < 1.0)) {
337                         i--;
338                         d += dstep;
339                         /** We mustn't increase dstep for subsequent values of 
340                          * i: for large spiral.exp values, rate of growth 
341                          * increases very rapidly.  
342                          */
343                         /** \todo 
344                          * Get the function itself to decide what value of d 
345                          * to use next: ensure that we move at least 0.25 * 
346                          * stroke width, for example.  The derivative (as used 
347                          * for get_tangent before normalization) would be 
348                          * useful for estimating the appropriate d value.  Or 
349                          * perhaps just start with a small dstep and scale by 
350                          * some small number until we move >= 0.25 * 
351                          * stroke_width.  Must revert to the original dstep 
352                          * value for next iteration to avoid the problem 
353                          * mentioned above.
354                          */
355                 }
356         }
358         double const next_t = d - 2 * dstep;
359         /* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */
361         hat2 = -sp_spiral_get_tangent (spiral, next_t);
363         /** \todo
364          * We should use better algorithm to specify maximum error.
365          */
366         depth = sp_bezier_fit_cubic_full (bezier, NULL, darray, SAMPLE_SIZE,
367                                           hat1, hat2,
368                                           SPIRAL_TOLERANCE*SPIRAL_TOLERANCE,
369                                           FITTING_MAX_BEZIERS);
370         g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier)));
371 #ifdef SPIRAL_DEBUG
372         if (*t == spiral->t0 || *t == 1.0)
373                 g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n",
374                          debug_state, depth, dstep, spiral->t0, *t, spiral->arg);
375 #endif
376         if (depth != -1) {
377                 for (i = 0; i < 4*depth; i += 4) {
378                         sp_curve_curveto (c,
379                                           bezier[i + 1],
380                                           bezier[i + 2],
381                                           bezier[i + 3]);
382                 }
383         } else {
384 #ifdef SPIRAL_VERBOSE
385                 g_print ("cant_fit_cubic: t=%g\n", *t);
386 #endif
387                 for (i = 1; i < SAMPLE_SIZE; i++)
388                         sp_curve_lineto (c, darray[i]);
389         }
390         *t = next_t;
391         g_assert (is_unit_vector (hat2));
394 static void
395 sp_spiral_set_shape (SPShape *shape)
397         NR::Point darray[SAMPLE_SIZE + 1];
398         double t;
400         SPSpiral *spiral = SP_SPIRAL(shape);
402         SP_OBJECT (spiral)->requestModified(SP_OBJECT_MODIFIED_FLAG);
404         SPCurve *c = sp_curve_new ();
405         
406 #ifdef SPIRAL_VERBOSE
407         g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n",
408                  spiral->cx,
409                  spiral->cy,
410                  spiral->exp,
411                  spiral->revo,
412                  spiral->rad,
413                  spiral->arg,
414                  spiral->t0);
415 #endif
417         /* Initial moveto. */
418         sp_curve_moveto(c, sp_spiral_get_xy(spiral, spiral->t0));
420         double const tstep = SAMPLE_STEP / spiral->revo;
421         double const dstep = tstep / (SAMPLE_SIZE - 1);
423         NR::Point hat1 = sp_spiral_get_tangent (spiral, spiral->t0);
424         NR::Point hat2;
425         for (t = spiral->t0; t < (1.0 - tstep);) {
426                 sp_spiral_fit_and_draw (spiral, c, dstep, darray, hat1, hat2, &t);
428                 hat1 = -hat2;
429         }
430         if ((1.0 - t) > SP_EPSILON)
431                 sp_spiral_fit_and_draw (spiral, c, (1.0 - t)/(SAMPLE_SIZE - 1.0),
432                                         darray, hat1, hat2, &t);
434         sp_shape_set_curve_insync ((SPShape *) spiral, c, TRUE);
435         sp_curve_unref (c);
438 /**
439  * Set spiral properties and update display.
440  */
441 void
442 sp_spiral_position_set       (SPSpiral          *spiral,
443                      gdouble            cx,
444                      gdouble            cy,
445                      gdouble            exp,
446                      gdouble            revo,
447                      gdouble            rad,
448                      gdouble            arg,
449                      gdouble            t0)
451         g_return_if_fail (spiral != NULL);
452         g_return_if_fail (SP_IS_SPIRAL (spiral));
454         /** \todo
455          * Consider applying CLAMP or adding in-bounds assertions for 
456          * some of these parameters.
457          */
458         spiral->cx         = cx;
459         spiral->cy         = cy;
460         spiral->exp        = exp;
461         spiral->revo       = revo;
462         spiral->rad        = MAX (rad, 0.001);
463         spiral->arg        = arg;
464         spiral->t0         = CLAMP(t0, 0.0, 0.999);
465         
466         ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
469 /**
470  * Virtual snappoints callback.
471  */
472 static void sp_spiral_snappoints(SPItem const *item, SnapPointsIter p)
474         if (((SPItemClass *) parent_class)->snappoints) {
475                 ((SPItemClass *) parent_class)->snappoints (item, p);
476         }
479 /** 
480  * Return one of the points on the spiral.
481  *
482  * \param t specifies how far along the spiral.
483  * \pre \a t in [0.0, 2.03].  (It doesn't make sense for t to be much more 
484  * than 1.0, though some callers go slightly beyond 1.0 for curve-fitting 
485  * purposes.)
486  */
487 NR::Point sp_spiral_get_xy (SPSpiral const *spiral, gdouble t)
489         g_assert (spiral != NULL);
490         g_assert (SP_IS_SPIRAL(spiral));
491         g_assert (spiral->exp >= 0.0);
492         /* Otherwise we get NaN for t==0. */
493         g_assert (spiral->exp <= 1000.0);
494         /* Anything much more results in infinities.  Even allowing 1000 is somewhat overkill. */
495         g_assert (t >= 0.0);
496         /* Any callers passing -ve t will have a bug for non-integral values of exp. */
498         double const rad = spiral->rad * pow(t, (double) spiral->exp);
499         double const arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
501         return NR::Point(rad * cos (arg) + spiral->cx,
502                          rad * sin (arg) + spiral->cy);
506 /** 
507  * Returns the derivative of sp_spiral_get_xy with respect to t,
508  *  scaled to a unit vector.
509  *
510  *  \pre spiral != 0.
511  *  \pre 0 \<= t.
512  *  \pre p != NULL.
513  *  \post is_unit_vector(*p).
514  */
515 static NR::Point
516 sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t)
518         NR::Point ret(1.0, 0.0);
519         g_return_val_if_fail (( ( spiral != NULL )
520                                 && SP_IS_SPIRAL(spiral) ),
521                               ret);
522         g_assert (t >= 0.0);
523         g_assert (spiral->exp >= 0.0);
524         /* See above for comments on these assertions. */
526         double const t_scaled = 2.0 * M_PI * spiral->revo * t;
527         double const arg = t_scaled + spiral->arg;
528         double const s = sin (arg);
529         double const c = cos (arg);
531         if (spiral->exp == 0.0) {
532                 ret = NR::Point(-s, c);
533         } else if (t_scaled == 0.0) {
534                 ret = NR::Point(c, s);
535         } else {
536                 NR::Point unrotated(spiral->exp, t_scaled);
537                 double const s_len = L2 (unrotated);
538                 g_assert (s_len != 0);
539                 /** \todo 
540                  * Check that this isn't being too hopeful of the hypot 
541                  * function.  E.g. test with numbers around 2**-1070 
542                  * (denormalized numbers), preferably on a few different 
543                  * platforms.  However, njh says that the usual implementation 
544                  * does handle both very big and very small numbers.
545                  */
546                 unrotated /= s_len;
548                 /* ret = spiral->exp * (c, s) + t_scaled * (-s, c);
549                    alternatively ret = (spiral->exp, t_scaled) * (( c, s),
550                                                                   (-s, c)).*/
551                 ret = NR::Point(dot(unrotated, NR::Point(c, -s)),
552                                 dot(unrotated, NR::Point(s, c)));
553                 /* ret should already be approximately normalized: the
554                    matrix ((c, -s), (s, c)) is orthogonal (it just
555                    rotates by arg), and unrotated has been normalized,
556                    so ret is already of unit length other than numerical
557                    error in the above matrix multiplication. */
559                 /** \todo 
560                  * I haven't checked how important it is for ret to be very 
561                  * near unit length; we could get rid of the below.
562                  */
564                 ret.normalize();
565                 /* Proof that ret length is non-zero: see above.  (Should be near 1.) */
566         }
568         g_assert (is_unit_vector (ret));
569         return ret;
572 /**
573  * Compute rad and/or arg for point on spiral.
574  */
575 void
576 sp_spiral_get_polar (SPSpiral const *spiral, gdouble t, gdouble *rad, gdouble *arg)
578         g_return_if_fail (spiral != NULL);
579         g_return_if_fail (SP_IS_SPIRAL(spiral));
581         if (rad)
582                 *rad = spiral->rad * pow(t, (double) spiral->exp);
583         if (arg)
584                 *arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
587 /**
588  * Return true if spiral has properties that make it invalid.
589  */
590 bool
591 sp_spiral_is_invalid (SPSpiral const *spiral)
593         gdouble rad;
595         sp_spiral_get_polar (spiral, 0.0, &rad, NULL);
596         if (rad < 0.0 || rad > SP_HUGE) {
597                 g_print ("rad(t=0)=%g\n", rad);
598                 return TRUE;
599         }
600         sp_spiral_get_polar (spiral, 1.0, &rad, NULL);
601         if (rad < 0.0 || rad > SP_HUGE) {
602                 g_print ("rad(t=1)=%g\n", rad);
603                 return TRUE;
604         }
605         return FALSE;
608 /*
609   Local Variables:
610   mode:c++
611   c-file-style:"stroustrup"
612   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
613   indent-tabs-mode:nil
614   fill-column:99
615   End:
616 */
617 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :