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);
40 static void sp_spiral_set_shape (SPShape *shape);
42 static NR::Point sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t);
44 static SPShapeClass *parent_class;
46 /**
47 * Register SPSpiral class and return its type number.
48 */
49 GType
50 sp_spiral_get_type (void)
51 {
52 static GType spiral_type = 0;
54 if (!spiral_type) {
55 GTypeInfo spiral_info = {
56 sizeof (SPSpiralClass),
57 NULL, /* base_init */
58 NULL, /* base_finalize */
59 (GClassInitFunc) sp_spiral_class_init,
60 NULL, /* class_finalize */
61 NULL, /* class_data */
62 sizeof (SPSpiral),
63 16, /* n_preallocs */
64 (GInstanceInitFunc) sp_spiral_init,
65 NULL, /* value_table */
66 };
67 spiral_type = g_type_register_static (SP_TYPE_SHAPE, "SPSpiral", &spiral_info, (GTypeFlags)0);
68 }
69 return spiral_type;
70 }
72 /**
73 * SPSpiral vtable initialization.
74 */
75 static void
76 sp_spiral_class_init (SPSpiralClass *klass)
77 {
78 GObjectClass * gobject_class;
79 SPObjectClass * sp_object_class;
80 SPItemClass * item_class;
81 SPShapeClass *shape_class;
83 gobject_class = (GObjectClass *) klass;
84 sp_object_class = (SPObjectClass *) klass;
85 item_class = (SPItemClass *) klass;
86 shape_class = (SPShapeClass *) klass;
88 parent_class = (SPShapeClass *)g_type_class_ref (SP_TYPE_SHAPE);
90 sp_object_class->build = sp_spiral_build;
91 sp_object_class->write = sp_spiral_write;
92 sp_object_class->set = sp_spiral_set;
93 sp_object_class->update = sp_spiral_update;
95 item_class->description = sp_spiral_description;
96 item_class->snappoints = sp_spiral_snappoints;
98 shape_class->set_shape = sp_spiral_set_shape;
99 }
101 /**
102 * Callback for SPSpiral object initialization.
103 */
104 static void
105 sp_spiral_init (SPSpiral * spiral)
106 {
107 spiral->cx = 0.0;
108 spiral->cy = 0.0;
109 spiral->exp = 1.0;
110 spiral->revo = 3.0;
111 spiral->rad = 1.0;
112 spiral->arg = 0.0;
113 spiral->t0 = 0.0;
114 }
116 /**
117 * Virtual build: set spiral properties from corresponding repr.
118 */
119 static void
120 sp_spiral_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr)
121 {
122 if (((SPObjectClass *) parent_class)->build)
123 ((SPObjectClass *) parent_class)->build (object, document, repr);
125 sp_object_read_attr (object, "sodipodi:cx");
126 sp_object_read_attr (object, "sodipodi:cy");
127 sp_object_read_attr (object, "sodipodi:expansion");
128 sp_object_read_attr (object, "sodipodi:revolution");
129 sp_object_read_attr (object, "sodipodi:radius");
130 sp_object_read_attr (object, "sodipodi:argument");
131 sp_object_read_attr (object, "sodipodi:t0");
132 }
134 /**
135 * Virtual write: write spiral attributes to corresponding repr.
136 */
137 static Inkscape::XML::Node *
138 sp_spiral_write (SPObject *object, Inkscape::XML::Node *repr, guint flags)
139 {
140 SPSpiral *spiral = SP_SPIRAL (object);
142 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
143 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
144 repr = xml_doc->createElement("svg:path");
145 }
147 if (flags & SP_OBJECT_WRITE_EXT) {
148 /* Fixme: we may replace these attributes by
149 * sodipodi:spiral="cx cy exp revo rad arg t0"
150 */
151 repr->setAttribute("sodipodi:type", "spiral");
152 sp_repr_set_svg_double(repr, "sodipodi:cx", spiral->cx);
153 sp_repr_set_svg_double(repr, "sodipodi:cy", spiral->cy);
154 sp_repr_set_svg_double(repr, "sodipodi:expansion", spiral->exp);
155 sp_repr_set_svg_double(repr, "sodipodi:revolution", spiral->revo);
156 sp_repr_set_svg_double(repr, "sodipodi:radius", spiral->rad);
157 sp_repr_set_svg_double(repr, "sodipodi:argument", spiral->arg);
158 sp_repr_set_svg_double(repr, "sodipodi:t0", spiral->t0);
159 }
161 // make sure the curve is rebuilt with all up-to-date parameters
162 sp_spiral_set_shape ((SPShape *) spiral);
164 //Duplicate the path
165 SPCurve *curve = ((SPShape *) spiral)->curve;
166 //Nulls might be possible if this called iteratively
167 if ( !curve ) {
168 //g_warning("sp_spiral_write(): No path to copy\n");
169 return NULL;
170 }
171 NArtBpath *bpath = SP_CURVE_BPATH(curve);
172 if ( !bpath ) {
173 //g_warning("sp_spiral_write(): No path to copy\n");
174 return NULL;
175 }
176 char *d = sp_svg_write_path ( bpath );
177 repr->setAttribute("d", d);
178 g_free (d);
180 if (((SPObjectClass *) (parent_class))->write)
181 ((SPObjectClass *) (parent_class))->write (object, repr, flags | SP_SHAPE_WRITE_PATH);
183 return repr;
184 }
186 /**
187 * Virtual set: change spiral object attribute.
188 */
189 static void
190 sp_spiral_set (SPObject *object, unsigned int key, const gchar *value)
191 {
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 }
280 }
282 /**
283 * Virtual update callback.
284 */
285 static void
286 sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags)
287 {
288 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
289 sp_shape_set_shape ((SPShape *) object);
290 }
292 if (((SPObjectClass *) parent_class)->update)
293 ((SPObjectClass *) parent_class)->update (object, ctx, flags);
294 }
296 /**
297 * Return textual description of spiral.
298 */
299 static gchar *
300 sp_spiral_description (SPItem * item)
301 {
302 // TRANSLATORS: since turn count isn't an integer, please adjust the
303 // string as needed to deal with an localized plural forms.
304 return g_strdup_printf (_("<b>Spiral</b> with %3f turns"), SP_SPIRAL(item)->revo);
305 }
308 /**
309 * Fit beziers together to spiral and draw it.
310 *
311 * \pre dstep \> 0.
312 * \pre is_unit_vector(*hat1).
313 * \post is_unit_vector(*hat2).
314 **/
315 static void
316 sp_spiral_fit_and_draw (SPSpiral const *spiral,
317 SPCurve *c,
318 double dstep,
319 NR::Point darray[],
320 NR::Point const &hat1,
321 NR::Point &hat2,
322 double *t)
323 {
324 #define BEZIER_SIZE 4
325 #define FITTING_MAX_BEZIERS 4
326 #define BEZIER_LENGTH (BEZIER_SIZE * FITTING_MAX_BEZIERS)
327 g_assert (dstep > 0);
328 g_assert (is_unit_vector (hat1));
330 NR::Point bezier[BEZIER_LENGTH];
331 double d;
332 int depth, i;
334 for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) {
335 darray[i] = sp_spiral_get_xy(spiral, d);
337 /* Avoid useless adjacent dups. (Otherwise we can have all of darray filled with
338 the same value, which upsets chord_length_parameterize.) */
339 if ((i != 0)
340 && (darray[i] == darray[i - 1])
341 && (d < 1.0)) {
342 i--;
343 d += dstep;
344 /** We mustn't increase dstep for subsequent values of
345 * i: for large spiral.exp values, rate of growth
346 * increases very rapidly.
347 */
348 /** \todo
349 * Get the function itself to decide what value of d
350 * to use next: ensure that we move at least 0.25 *
351 * stroke width, for example. The derivative (as used
352 * for get_tangent before normalization) would be
353 * useful for estimating the appropriate d value. Or
354 * perhaps just start with a small dstep and scale by
355 * some small number until we move >= 0.25 *
356 * stroke_width. Must revert to the original dstep
357 * value for next iteration to avoid the problem
358 * mentioned above.
359 */
360 }
361 }
363 double const next_t = d - 2 * dstep;
364 /* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */
366 hat2 = -sp_spiral_get_tangent (spiral, next_t);
368 /** \todo
369 * We should use better algorithm to specify maximum error.
370 */
371 depth = sp_bezier_fit_cubic_full (bezier, NULL, darray, SAMPLE_SIZE,
372 hat1, hat2,
373 SPIRAL_TOLERANCE*SPIRAL_TOLERANCE,
374 FITTING_MAX_BEZIERS);
375 g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier)));
376 #ifdef SPIRAL_DEBUG
377 if (*t == spiral->t0 || *t == 1.0)
378 g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n",
379 debug_state, depth, dstep, spiral->t0, *t, spiral->arg);
380 #endif
381 if (depth != -1) {
382 for (i = 0; i < 4*depth; i += 4) {
383 sp_curve_curveto (c,
384 bezier[i + 1],
385 bezier[i + 2],
386 bezier[i + 3]);
387 }
388 } else {
389 #ifdef SPIRAL_VERBOSE
390 g_print ("cant_fit_cubic: t=%g\n", *t);
391 #endif
392 for (i = 1; i < SAMPLE_SIZE; i++)
393 sp_curve_lineto (c, darray[i]);
394 }
395 *t = next_t;
396 g_assert (is_unit_vector (hat2));
397 }
399 static void
400 sp_spiral_set_shape (SPShape *shape)
401 {
402 NR::Point darray[SAMPLE_SIZE + 1];
403 double t;
405 SPSpiral *spiral = SP_SPIRAL(shape);
407 SP_OBJECT (spiral)->requestModified(SP_OBJECT_MODIFIED_FLAG);
409 SPCurve *c = sp_curve_new ();
411 #ifdef SPIRAL_VERBOSE
412 g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n",
413 spiral->cx,
414 spiral->cy,
415 spiral->exp,
416 spiral->revo,
417 spiral->rad,
418 spiral->arg,
419 spiral->t0);
420 #endif
422 /* Initial moveto. */
423 sp_curve_moveto(c, sp_spiral_get_xy(spiral, spiral->t0));
425 double const tstep = SAMPLE_STEP / spiral->revo;
426 double const dstep = tstep / (SAMPLE_SIZE - 1);
428 NR::Point hat1 = sp_spiral_get_tangent (spiral, spiral->t0);
429 NR::Point hat2;
430 for (t = spiral->t0; t < (1.0 - tstep);) {
431 sp_spiral_fit_and_draw (spiral, c, dstep, darray, hat1, hat2, &t);
433 hat1 = -hat2;
434 }
435 if ((1.0 - t) > SP_EPSILON)
436 sp_spiral_fit_and_draw (spiral, c, (1.0 - t)/(SAMPLE_SIZE - 1.0),
437 darray, hat1, hat2, &t);
439 sp_shape_set_curve_insync ((SPShape *) spiral, c, TRUE);
440 sp_curve_unref (c);
441 }
443 /**
444 * Set spiral properties and update display.
445 */
446 void
447 sp_spiral_position_set (SPSpiral *spiral,
448 gdouble cx,
449 gdouble cy,
450 gdouble exp,
451 gdouble revo,
452 gdouble rad,
453 gdouble arg,
454 gdouble t0)
455 {
456 g_return_if_fail (spiral != NULL);
457 g_return_if_fail (SP_IS_SPIRAL (spiral));
459 /** \todo
460 * Consider applying CLAMP or adding in-bounds assertions for
461 * some of these parameters.
462 */
463 spiral->cx = cx;
464 spiral->cy = cy;
465 spiral->exp = exp;
466 spiral->revo = revo;
467 spiral->rad = MAX (rad, 0.001);
468 spiral->arg = arg;
469 spiral->t0 = CLAMP(t0, 0.0, 0.999);
471 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
472 }
474 /**
475 * Virtual snappoints callback.
476 */
477 static void sp_spiral_snappoints(SPItem const *item, SnapPointsIter p)
478 {
479 if (((SPItemClass *) parent_class)->snappoints) {
480 ((SPItemClass *) parent_class)->snappoints (item, p);
481 }
482 }
484 /**
485 * Return one of the points on the spiral.
486 *
487 * \param t specifies how far along the spiral.
488 * \pre \a t in [0.0, 2.03]. (It doesn't make sense for t to be much more
489 * than 1.0, though some callers go slightly beyond 1.0 for curve-fitting
490 * purposes.)
491 */
492 NR::Point sp_spiral_get_xy (SPSpiral const *spiral, gdouble t)
493 {
494 g_assert (spiral != NULL);
495 g_assert (SP_IS_SPIRAL(spiral));
496 g_assert (spiral->exp >= 0.0);
497 /* Otherwise we get NaN for t==0. */
498 g_assert (spiral->exp <= 1000.0);
499 /* Anything much more results in infinities. Even allowing 1000 is somewhat overkill. */
500 g_assert (t >= 0.0);
501 /* Any callers passing -ve t will have a bug for non-integral values of exp. */
503 double const rad = spiral->rad * pow(t, (double) spiral->exp);
504 double const arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
506 return NR::Point(rad * cos (arg) + spiral->cx,
507 rad * sin (arg) + spiral->cy);
508 }
511 /**
512 * Returns the derivative of sp_spiral_get_xy with respect to t,
513 * scaled to a unit vector.
514 *
515 * \pre spiral != 0.
516 * \pre 0 \<= t.
517 * \pre p != NULL.
518 * \post is_unit_vector(*p).
519 */
520 static NR::Point
521 sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t)
522 {
523 NR::Point ret(1.0, 0.0);
524 g_return_val_if_fail (( ( spiral != NULL )
525 && SP_IS_SPIRAL(spiral) ),
526 ret);
527 g_assert (t >= 0.0);
528 g_assert (spiral->exp >= 0.0);
529 /* See above for comments on these assertions. */
531 double const t_scaled = 2.0 * M_PI * spiral->revo * t;
532 double const arg = t_scaled + spiral->arg;
533 double const s = sin (arg);
534 double const c = cos (arg);
536 if (spiral->exp == 0.0) {
537 ret = NR::Point(-s, c);
538 } else if (t_scaled == 0.0) {
539 ret = NR::Point(c, s);
540 } else {
541 NR::Point unrotated(spiral->exp, t_scaled);
542 double const s_len = L2 (unrotated);
543 g_assert (s_len != 0);
544 /** \todo
545 * Check that this isn't being too hopeful of the hypot
546 * function. E.g. test with numbers around 2**-1070
547 * (denormalized numbers), preferably on a few different
548 * platforms. However, njh says that the usual implementation
549 * does handle both very big and very small numbers.
550 */
551 unrotated /= s_len;
553 /* ret = spiral->exp * (c, s) + t_scaled * (-s, c);
554 alternatively ret = (spiral->exp, t_scaled) * (( c, s),
555 (-s, c)).*/
556 ret = NR::Point(dot(unrotated, NR::Point(c, -s)),
557 dot(unrotated, NR::Point(s, c)));
558 /* ret should already be approximately normalized: the
559 matrix ((c, -s), (s, c)) is orthogonal (it just
560 rotates by arg), and unrotated has been normalized,
561 so ret is already of unit length other than numerical
562 error in the above matrix multiplication. */
564 /** \todo
565 * I haven't checked how important it is for ret to be very
566 * near unit length; we could get rid of the below.
567 */
569 ret.normalize();
570 /* Proof that ret length is non-zero: see above. (Should be near 1.) */
571 }
573 g_assert (is_unit_vector (ret));
574 return ret;
575 }
577 /**
578 * Compute rad and/or arg for point on spiral.
579 */
580 void
581 sp_spiral_get_polar (SPSpiral const *spiral, gdouble t, gdouble *rad, gdouble *arg)
582 {
583 g_return_if_fail (spiral != NULL);
584 g_return_if_fail (SP_IS_SPIRAL(spiral));
586 if (rad)
587 *rad = spiral->rad * pow(t, (double) spiral->exp);
588 if (arg)
589 *arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
590 }
592 /**
593 * Return true if spiral has properties that make it invalid.
594 */
595 bool
596 sp_spiral_is_invalid (SPSpiral const *spiral)
597 {
598 gdouble rad;
600 sp_spiral_get_polar (spiral, 0.0, &rad, NULL);
601 if (rad < 0.0 || rad > SP_HUGE) {
602 g_print ("rad(t=0)=%g\n", rad);
603 return TRUE;
604 }
605 sp_spiral_get_polar (spiral, 1.0, &rad, NULL);
606 if (rad < 0.0 || rad > SP_HUGE) {
607 g_print ("rad(t=1)=%g\n", rad);
608 return TRUE;
609 }
610 return FALSE;
611 }
613 /*
614 Local Variables:
615 mode:c++
616 c-file-style:"stroustrup"
617 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
618 indent-tabs-mode:nil
619 fill-column:99
620 End:
621 */
622 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :