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"
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::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, SnapPointsIter p);
41 static void sp_spiral_set_shape (SPShape *shape);
42 static void sp_spiral_update_patheffect (SPShape *shape, bool write);
44 static NR::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 SPShapeClass *shape_class;
85 gobject_class = (GObjectClass *) klass;
86 sp_object_class = (SPObjectClass *) klass;
87 item_class = (SPItemClass *) klass;
88 shape_class = (SPShapeClass *) klass;
90 parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE);
92 sp_object_class->build = sp_spiral_build;
93 sp_object_class->write = sp_spiral_write;
94 sp_object_class->set = sp_spiral_set;
95 sp_object_class->update = sp_spiral_update;
97 item_class->description = sp_spiral_description;
98 item_class->snappoints = sp_spiral_snappoints;
100 shape_class->set_shape = sp_spiral_set_shape;
101 shape_class->update_patheffect = sp_spiral_update_patheffect;
102 }
104 /**
105 * Callback for SPSpiral object initialization.
106 */
107 static void
108 sp_spiral_init (SPSpiral * spiral)
109 {
110 spiral->cx = 0.0;
111 spiral->cy = 0.0;
112 spiral->exp = 1.0;
113 spiral->revo = 3.0;
114 spiral->rad = 1.0;
115 spiral->arg = 0.0;
116 spiral->t0 = 0.0;
117 }
119 /**
120 * Virtual build: set spiral properties from corresponding repr.
121 */
122 static void
123 sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr)
124 {
125 if (((SPObjectClass *) parent_class)->build)
126 ((SPObjectClass *) parent_class)->build (object, document, repr);
128 sp_object_read_attr (object, "sodipodi:cx");
129 sp_object_read_attr (object, "sodipodi:cy");
130 sp_object_read_attr (object, "sodipodi:expansion");
131 sp_object_read_attr (object, "sodipodi:revolution");
132 sp_object_read_attr (object, "sodipodi:radius");
133 sp_object_read_attr (object, "sodipodi:argument");
134 sp_object_read_attr (object, "sodipodi:t0");
135 }
137 /**
138 * Virtual write: write spiral attributes to corresponding repr.
139 */
140 static Inkscape::XML::Node *
141 sp_spiral_write (SPObject *object, Inkscape::XML::Node *repr, guint flags)
142 {
143 SPSpiral *spiral = SP_SPIRAL (object);
145 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
146 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
147 repr = xml_doc->createElement("svg:path");
148 }
150 if (flags & SP_OBJECT_WRITE_EXT) {
151 /* Fixme: we may replace these attributes by
152 * sodipodi:spiral="cx cy exp revo rad arg t0"
153 */
154 repr->setAttribute("sodipodi:type", "spiral");
155 sp_repr_set_svg_double(repr, "sodipodi:cx", spiral->cx);
156 sp_repr_set_svg_double(repr, "sodipodi:cy", spiral->cy);
157 sp_repr_set_svg_double(repr, "sodipodi:expansion", spiral->exp);
158 sp_repr_set_svg_double(repr, "sodipodi:revolution", spiral->revo);
159 sp_repr_set_svg_double(repr, "sodipodi:radius", spiral->rad);
160 sp_repr_set_svg_double(repr, "sodipodi:argument", spiral->arg);
161 sp_repr_set_svg_double(repr, "sodipodi:t0", spiral->t0);
162 }
164 // make sure the curve is rebuilt with all up-to-date parameters
165 sp_spiral_set_shape ((SPShape *) spiral);
167 //Duplicate the path
168 SPCurve *curve = ((SPShape *) spiral)->curve;
169 //Nulls might be possible if this called iteratively
170 if ( !curve ) {
171 //g_warning("sp_spiral_write(): No path to copy\n");
172 return NULL;
173 }
174 NArtBpath *bpath = SP_CURVE_BPATH(curve);
175 if ( !bpath ) {
176 //g_warning("sp_spiral_write(): No path to copy\n");
177 return NULL;
178 }
179 char *d = sp_svg_write_path ( bpath );
180 repr->setAttribute("d", d);
181 g_free (d);
183 if (((SPObjectClass *) (parent_class))->write)
184 ((SPObjectClass *) (parent_class))->write (object, repr, flags | SP_SHAPE_WRITE_PATH);
186 return repr;
187 }
189 /**
190 * Virtual set: change spiral object attribute.
191 */
192 static void
193 sp_spiral_set (SPObject *object, unsigned int key, const gchar *value)
194 {
195 SPSpiral *spiral;
196 SPShape *shape;
198 spiral = SP_SPIRAL (object);
199 shape = SP_SHAPE (object);
201 /// \todo fixme: we should really collect updates
202 switch (key) {
203 case SP_ATTR_SODIPODI_CX:
204 if (!sp_svg_length_read_computed_absolute (value, &spiral->cx)) {
205 spiral->cx = 0.0;
206 }
207 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
208 break;
209 case SP_ATTR_SODIPODI_CY:
210 if (!sp_svg_length_read_computed_absolute (value, &spiral->cy)) {
211 spiral->cy = 0.0;
212 }
213 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
214 break;
215 case SP_ATTR_SODIPODI_EXPANSION:
216 if (value) {
217 /** \todo
218 * FIXME: check that value looks like a (finite)
219 * number. Create a routine that uses strtod, and
220 * accepts a default value (if strtod finds an error).
221 * N.B. atof/sscanf/strtod consider "nan" and "inf"
222 * to be valid numbers.
223 */
224 spiral->exp = g_ascii_strtod (value, NULL);
225 spiral->exp = CLAMP (spiral->exp, 0.0, 1000.0);
226 } else {
227 spiral->exp = 1.0;
228 }
229 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
230 break;
231 case SP_ATTR_SODIPODI_REVOLUTION:
232 if (value) {
233 spiral->revo = g_ascii_strtod (value, NULL);
234 spiral->revo = CLAMP (spiral->revo, 0.05, 1024.0);
235 } else {
236 spiral->revo = 3.0;
237 }
238 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
239 break;
240 case SP_ATTR_SODIPODI_RADIUS:
241 if (!sp_svg_length_read_computed_absolute (value, &spiral->rad)) {
242 spiral->rad = MAX (spiral->rad, 0.001);
243 }
244 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
245 break;
246 case SP_ATTR_SODIPODI_ARGUMENT:
247 if (value) {
248 spiral->arg = g_ascii_strtod (value, NULL);
249 /** \todo
250 * FIXME: We still need some bounds on arg, for
251 * numerical reasons. E.g., we don't want inf or NaN,
252 * nor near-infinite numbers. I'm inclined to take
253 * modulo 2*pi. If so, then change the knot editors,
254 * which use atan2 - revo*2*pi, which typically
255 * results in very negative arg.
256 */
257 } else {
258 spiral->arg = 0.0;
259 }
260 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
261 break;
262 case SP_ATTR_SODIPODI_T0:
263 if (value) {
264 spiral->t0 = g_ascii_strtod (value, NULL);
265 spiral->t0 = CLAMP (spiral->t0, 0.0, 0.999);
266 /** \todo
267 * Have shared constants for the allowable bounds for
268 * attributes. There was a bug here where we used -1.0
269 * as the minimum (which leads to NaN via, e.g.,
270 * pow(-1.0, 0.5); see sp_spiral_get_xy for
271 * requirements.
272 */
273 } else {
274 spiral->t0 = 0.0;
275 }
276 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
277 break;
278 default:
279 if (((SPObjectClass *) parent_class)->set)
280 ((SPObjectClass *) parent_class)->set (object, key, value);
281 break;
282 }
283 }
285 /**
286 * Virtual update callback.
287 */
288 static void
289 sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags)
290 {
291 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
292 sp_shape_set_shape ((SPShape *) object);
293 }
295 if (((SPObjectClass *) parent_class)->update)
296 ((SPObjectClass *) parent_class)->update (object, ctx, flags);
297 }
299 static void
300 sp_spiral_update_patheffect(SPShape *shape, bool write)
301 {
302 sp_spiral_set_shape(shape);
304 if (write) {
305 Inkscape::XML::Node *repr = SP_OBJECT_REPR(shape);
306 if ( shape->curve != NULL ) {
307 NArtBpath *abp = sp_curve_first_bpath(shape->curve);
308 if (abp) {
309 gchar *str = sp_svg_write_path(abp);
310 repr->setAttribute("d", str);
311 g_free(str);
312 } else {
313 repr->setAttribute("d", "");
314 }
315 } else {
316 repr->setAttribute("d", NULL);
317 }
318 }
320 ((SPObject *)shape)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
321 }
323 /**
324 * Return textual description of spiral.
325 */
326 static gchar *
327 sp_spiral_description (SPItem * item)
328 {
329 // TRANSLATORS: since turn count isn't an integer, please adjust the
330 // string as needed to deal with an localized plural forms.
331 return g_strdup_printf (_("<b>Spiral</b> with %3f turns"), SP_SPIRAL(item)->revo);
332 }
335 /**
336 * Fit beziers together to spiral and draw it.
337 *
338 * \pre dstep \> 0.
339 * \pre is_unit_vector(*hat1).
340 * \post is_unit_vector(*hat2).
341 **/
342 static void
343 sp_spiral_fit_and_draw (SPSpiral const *spiral,
344 SPCurve *c,
345 double dstep,
346 NR::Point darray[],
347 NR::Point const &hat1,
348 NR::Point &hat2,
349 double *t)
350 {
351 #define BEZIER_SIZE 4
352 #define FITTING_MAX_BEZIERS 4
353 #define BEZIER_LENGTH (BEZIER_SIZE * FITTING_MAX_BEZIERS)
354 g_assert (dstep > 0);
355 g_assert (is_unit_vector (hat1));
357 NR::Point bezier[BEZIER_LENGTH];
358 double d;
359 int depth, i;
361 for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) {
362 darray[i] = sp_spiral_get_xy(spiral, d);
364 /* Avoid useless adjacent dups. (Otherwise we can have all of darray filled with
365 the same value, which upsets chord_length_parameterize.) */
366 if ((i != 0)
367 && (darray[i] == darray[i - 1])
368 && (d < 1.0)) {
369 i--;
370 d += dstep;
371 /** We mustn't increase dstep for subsequent values of
372 * i: for large spiral.exp values, rate of growth
373 * increases very rapidly.
374 */
375 /** \todo
376 * Get the function itself to decide what value of d
377 * to use next: ensure that we move at least 0.25 *
378 * stroke width, for example. The derivative (as used
379 * for get_tangent before normalization) would be
380 * useful for estimating the appropriate d value. Or
381 * perhaps just start with a small dstep and scale by
382 * some small number until we move >= 0.25 *
383 * stroke_width. Must revert to the original dstep
384 * value for next iteration to avoid the problem
385 * mentioned above.
386 */
387 }
388 }
390 double const next_t = d - 2 * dstep;
391 /* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */
393 hat2 = -sp_spiral_get_tangent (spiral, next_t);
395 /** \todo
396 * We should use better algorithm to specify maximum error.
397 */
398 depth = sp_bezier_fit_cubic_full (bezier, NULL, darray, SAMPLE_SIZE,
399 hat1, hat2,
400 SPIRAL_TOLERANCE*SPIRAL_TOLERANCE,
401 FITTING_MAX_BEZIERS);
402 g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier)));
403 #ifdef SPIRAL_DEBUG
404 if (*t == spiral->t0 || *t == 1.0)
405 g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n",
406 debug_state, depth, dstep, spiral->t0, *t, spiral->arg);
407 #endif
408 if (depth != -1) {
409 for (i = 0; i < 4*depth; i += 4) {
410 sp_curve_curveto (c,
411 bezier[i + 1],
412 bezier[i + 2],
413 bezier[i + 3]);
414 }
415 } else {
416 #ifdef SPIRAL_VERBOSE
417 g_print ("cant_fit_cubic: t=%g\n", *t);
418 #endif
419 for (i = 1; i < SAMPLE_SIZE; i++)
420 sp_curve_lineto (c, darray[i]);
421 }
422 *t = next_t;
423 g_assert (is_unit_vector (hat2));
424 }
426 static void
427 sp_spiral_set_shape (SPShape *shape)
428 {
429 NR::Point darray[SAMPLE_SIZE + 1];
430 double t;
432 SPSpiral *spiral = SP_SPIRAL(shape);
434 SP_OBJECT (spiral)->requestModified(SP_OBJECT_MODIFIED_FLAG);
436 SPCurve *c = sp_curve_new ();
438 #ifdef SPIRAL_VERBOSE
439 g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n",
440 spiral->cx,
441 spiral->cy,
442 spiral->exp,
443 spiral->revo,
444 spiral->rad,
445 spiral->arg,
446 spiral->t0);
447 #endif
449 /* Initial moveto. */
450 sp_curve_moveto(c, sp_spiral_get_xy(spiral, spiral->t0));
452 double const tstep = SAMPLE_STEP / spiral->revo;
453 double const dstep = tstep / (SAMPLE_SIZE - 1);
455 NR::Point hat1 = sp_spiral_get_tangent (spiral, spiral->t0);
456 NR::Point hat2;
457 for (t = spiral->t0; t < (1.0 - tstep);) {
458 sp_spiral_fit_and_draw (spiral, c, dstep, darray, hat1, hat2, &t);
460 hat1 = -hat2;
461 }
462 if ((1.0 - t) > SP_EPSILON)
463 sp_spiral_fit_and_draw (spiral, c, (1.0 - t)/(SAMPLE_SIZE - 1.0),
464 darray, hat1, hat2, &t);
466 sp_shape_perform_path_effect(c, SP_SHAPE (spiral));
467 sp_shape_set_curve_insync ((SPShape *) spiral, c, TRUE);
468 sp_curve_unref (c);
469 }
471 /**
472 * Set spiral properties and update display.
473 */
474 void
475 sp_spiral_position_set (SPSpiral *spiral,
476 gdouble cx,
477 gdouble cy,
478 gdouble exp,
479 gdouble revo,
480 gdouble rad,
481 gdouble arg,
482 gdouble t0)
483 {
484 g_return_if_fail (spiral != NULL);
485 g_return_if_fail (SP_IS_SPIRAL (spiral));
487 /** \todo
488 * Consider applying CLAMP or adding in-bounds assertions for
489 * some of these parameters.
490 */
491 spiral->cx = cx;
492 spiral->cy = cy;
493 spiral->exp = exp;
494 spiral->revo = revo;
495 spiral->rad = MAX (rad, 0.001);
496 spiral->arg = arg;
497 spiral->t0 = CLAMP(t0, 0.0, 0.999);
499 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
500 }
502 /**
503 * Virtual snappoints callback.
504 */
505 static void sp_spiral_snappoints(SPItem const *item, SnapPointsIter p)
506 {
507 if (((SPItemClass *) parent_class)->snappoints) {
508 ((SPItemClass *) parent_class)->snappoints (item, p);
509 }
510 }
512 /**
513 * Return one of the points on the spiral.
514 *
515 * \param t specifies how far along the spiral.
516 * \pre \a t in [0.0, 2.03]. (It doesn't make sense for t to be much more
517 * than 1.0, though some callers go slightly beyond 1.0 for curve-fitting
518 * purposes.)
519 */
520 NR::Point sp_spiral_get_xy (SPSpiral const *spiral, gdouble t)
521 {
522 g_assert (spiral != NULL);
523 g_assert (SP_IS_SPIRAL(spiral));
524 g_assert (spiral->exp >= 0.0);
525 /* Otherwise we get NaN for t==0. */
526 g_assert (spiral->exp <= 1000.0);
527 /* Anything much more results in infinities. Even allowing 1000 is somewhat overkill. */
528 g_assert (t >= 0.0);
529 /* Any callers passing -ve t will have a bug for non-integral values of exp. */
531 double const rad = spiral->rad * pow(t, (double) spiral->exp);
532 double const arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
534 return NR::Point(rad * cos (arg) + spiral->cx,
535 rad * sin (arg) + spiral->cy);
536 }
539 /**
540 * Returns the derivative of sp_spiral_get_xy with respect to t,
541 * scaled to a unit vector.
542 *
543 * \pre spiral != 0.
544 * \pre 0 \<= t.
545 * \pre p != NULL.
546 * \post is_unit_vector(*p).
547 */
548 static NR::Point
549 sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t)
550 {
551 NR::Point ret(1.0, 0.0);
552 g_return_val_if_fail (( ( spiral != NULL )
553 && SP_IS_SPIRAL(spiral) ),
554 ret);
555 g_assert (t >= 0.0);
556 g_assert (spiral->exp >= 0.0);
557 /* See above for comments on these assertions. */
559 double const t_scaled = 2.0 * M_PI * spiral->revo * t;
560 double const arg = t_scaled + spiral->arg;
561 double const s = sin (arg);
562 double const c = cos (arg);
564 if (spiral->exp == 0.0) {
565 ret = NR::Point(-s, c);
566 } else if (t_scaled == 0.0) {
567 ret = NR::Point(c, s);
568 } else {
569 NR::Point unrotated(spiral->exp, t_scaled);
570 double const s_len = L2 (unrotated);
571 g_assert (s_len != 0);
572 /** \todo
573 * Check that this isn't being too hopeful of the hypot
574 * function. E.g. test with numbers around 2**-1070
575 * (denormalized numbers), preferably on a few different
576 * platforms. However, njh says that the usual implementation
577 * does handle both very big and very small numbers.
578 */
579 unrotated /= s_len;
581 /* ret = spiral->exp * (c, s) + t_scaled * (-s, c);
582 alternatively ret = (spiral->exp, t_scaled) * (( c, s),
583 (-s, c)).*/
584 ret = NR::Point(dot(unrotated, NR::Point(c, -s)),
585 dot(unrotated, NR::Point(s, c)));
586 /* ret should already be approximately normalized: the
587 matrix ((c, -s), (s, c)) is orthogonal (it just
588 rotates by arg), and unrotated has been normalized,
589 so ret is already of unit length other than numerical
590 error in the above matrix multiplication. */
592 /** \todo
593 * I haven't checked how important it is for ret to be very
594 * near unit length; we could get rid of the below.
595 */
597 ret.normalize();
598 /* Proof that ret length is non-zero: see above. (Should be near 1.) */
599 }
601 g_assert (is_unit_vector (ret));
602 return ret;
603 }
605 /**
606 * Compute rad and/or arg for point on spiral.
607 */
608 void
609 sp_spiral_get_polar (SPSpiral const *spiral, gdouble t, gdouble *rad, gdouble *arg)
610 {
611 g_return_if_fail (spiral != NULL);
612 g_return_if_fail (SP_IS_SPIRAL(spiral));
614 if (rad)
615 *rad = spiral->rad * pow(t, (double) spiral->exp);
616 if (arg)
617 *arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
618 }
620 /**
621 * Return true if spiral has properties that make it invalid.
622 */
623 bool
624 sp_spiral_is_invalid (SPSpiral const *spiral)
625 {
626 gdouble rad;
628 sp_spiral_get_polar (spiral, 0.0, &rad, NULL);
629 if (rad < 0.0 || rad > SP_HUGE) {
630 g_print ("rad(t=0)=%g\n", rad);
631 return TRUE;
632 }
633 sp_spiral_get_polar (spiral, 1.0, &rad, NULL);
634 if (rad < 0.0 || rad > SP_HUGE) {
635 g_print ("rad(t=1)=%g\n", rad);
636 return TRUE;
637 }
638 return FALSE;
639 }
641 /*
642 Local Variables:
643 mode:c++
644 c-file-style:"stroustrup"
645 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
646 indent-tabs-mode:nil
647 fill-column:99
648 End:
649 */
650 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :