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)
105 {
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;
113 }
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)
120 {
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");
131 }
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)
138 {
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;
179 }
181 /**
182 * Virtual set: change spiral object attribute.
183 */
184 static void
185 sp_spiral_set (SPObject *object, unsigned int key, const gchar *value)
186 {
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 }
275 }
277 /**
278 * Virtual update callback.
279 */
280 static void
281 sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags)
282 {
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);
289 }
291 /**
292 * Return textual description of spiral.
293 */
294 static gchar *
295 sp_spiral_description (SPItem * item)
296 {
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);
300 }
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)
318 {
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));
392 }
394 static void
395 sp_spiral_set_shape (SPShape *shape)
396 {
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 ();
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);
436 }
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)
450 {
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);
466 ((SPObject *)spiral)->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
467 }
469 /**
470 * Virtual snappoints callback.
471 */
472 static void sp_spiral_snappoints(SPItem const *item, SnapPointsIter p)
473 {
474 if (((SPItemClass *) parent_class)->snappoints) {
475 ((SPItemClass *) parent_class)->snappoints (item, p);
476 }
477 }
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)
488 {
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);
503 }
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)
517 {
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;
570 }
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)
577 {
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;
585 }
587 /**
588 * Return true if spiral has properties that make it invalid.
589 */
590 bool
591 sp_spiral_is_invalid (SPSpiral const *spiral)
592 {
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;
606 }
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 :