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, std::vector<Inkscape::SnapCandidatePoint> &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;
106 }
108 /**
109 * Callback for SPSpiral object initialization.
110 */
111 static void
112 sp_spiral_init (SPSpiral * spiral)
113 {
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;
121 }
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)
128 {
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");
139 }
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)
146 {
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;
185 }
187 /**
188 * Virtual set: change spiral object attribute.
189 */
190 static void
191 sp_spiral_set (SPObject *object, unsigned int key, const gchar *value)
192 {
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 }
281 }
283 /**
284 * Virtual update callback.
285 */
286 static void
287 sp_spiral_update (SPObject *object, SPCtx *ctx, guint flags)
288 {
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);
295 }
297 static void
298 sp_spiral_update_patheffect(SPLPEItem *lpeitem, bool write)
299 {
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);
315 }
317 /**
318 * Return textual description of spiral.
319 */
320 static gchar *
321 sp_spiral_description (SPItem * item)
322 {
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);
326 }
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)
344 {
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));
417 }
419 static void
420 sp_spiral_set_shape (SPShape *shape)
421 {
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();
483 }
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)
497 {
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);
514 }
516 /**
517 * Virtual snappoints callback.
518 */
519 static void sp_spiral_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs)
520 {
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, 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 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(spiral->cx, spiral->cy) * i2d, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT));
539 // This point is the start-point of the spiral, which is also returned when _snap_to_itemnode has been set
540 // in the object snapper. In that case we will get a duplicate!
541 }
542 }
544 /**
545 * Return one of the points on the spiral.
546 *
547 * \param t specifies how far along the spiral.
548 * \pre \a t in [0.0, 2.03]. (It doesn't make sense for t to be much more
549 * than 1.0, though some callers go slightly beyond 1.0 for curve-fitting
550 * purposes.)
551 */
552 Geom::Point sp_spiral_get_xy (SPSpiral const *spiral, gdouble t)
553 {
554 g_assert (spiral != NULL);
555 g_assert (SP_IS_SPIRAL(spiral));
556 g_assert (spiral->exp >= 0.0);
557 /* Otherwise we get NaN for t==0. */
558 g_assert (spiral->exp <= 1000.0);
559 /* Anything much more results in infinities. Even allowing 1000 is somewhat overkill. */
560 g_assert (t >= 0.0);
561 /* Any callers passing -ve t will have a bug for non-integral values of exp. */
563 double const rad = spiral->rad * pow(t, (double) spiral->exp);
564 double const arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
566 return Geom::Point(rad * cos (arg) + spiral->cx,
567 rad * sin (arg) + spiral->cy);
568 }
571 /**
572 * Returns the derivative of sp_spiral_get_xy with respect to t,
573 * scaled to a unit vector.
574 *
575 * \pre spiral != 0.
576 * \pre 0 \<= t.
577 * \pre p != NULL.
578 * \post is_unit_vector(*p).
579 */
580 static Geom::Point
581 sp_spiral_get_tangent (SPSpiral const *spiral, gdouble t)
582 {
583 Geom::Point ret(1.0, 0.0);
584 g_return_val_if_fail (( ( spiral != NULL )
585 && SP_IS_SPIRAL(spiral) ),
586 ret);
587 g_assert (t >= 0.0);
588 g_assert (spiral->exp >= 0.0);
589 /* See above for comments on these assertions. */
591 double const t_scaled = 2.0 * M_PI * spiral->revo * t;
592 double const arg = t_scaled + spiral->arg;
593 double const s = sin (arg);
594 double const c = cos (arg);
596 if (spiral->exp == 0.0) {
597 ret = Geom::Point(-s, c);
598 } else if (t_scaled == 0.0) {
599 ret = Geom::Point(c, s);
600 } else {
601 Geom::Point unrotated(spiral->exp, t_scaled);
602 double const s_len = L2 (unrotated);
603 g_assert (s_len != 0);
604 /** \todo
605 * Check that this isn't being too hopeful of the hypot
606 * function. E.g. test with numbers around 2**-1070
607 * (denormalized numbers), preferably on a few different
608 * platforms. However, njh says that the usual implementation
609 * does handle both very big and very small numbers.
610 */
611 unrotated /= s_len;
613 /* ret = spiral->exp * (c, s) + t_scaled * (-s, c);
614 alternatively ret = (spiral->exp, t_scaled) * (( c, s),
615 (-s, c)).*/
616 ret = Geom::Point(dot(unrotated, Geom::Point(c, -s)),
617 dot(unrotated, Geom::Point(s, c)));
618 /* ret should already be approximately normalized: the
619 matrix ((c, -s), (s, c)) is orthogonal (it just
620 rotates by arg), and unrotated has been normalized,
621 so ret is already of unit length other than numerical
622 error in the above matrix multiplication. */
624 /** \todo
625 * I haven't checked how important it is for ret to be very
626 * near unit length; we could get rid of the below.
627 */
629 ret.normalize();
630 /* Proof that ret length is non-zero: see above. (Should be near 1.) */
631 }
633 g_assert (is_unit_vector (ret));
634 return ret;
635 }
637 /**
638 * Compute rad and/or arg for point on spiral.
639 */
640 void
641 sp_spiral_get_polar (SPSpiral const *spiral, gdouble t, gdouble *rad, gdouble *arg)
642 {
643 g_return_if_fail (spiral != NULL);
644 g_return_if_fail (SP_IS_SPIRAL(spiral));
646 if (rad)
647 *rad = spiral->rad * pow(t, (double) spiral->exp);
648 if (arg)
649 *arg = 2.0 * M_PI * spiral->revo * t + spiral->arg;
650 }
652 /**
653 * Return true if spiral has properties that make it invalid.
654 */
655 bool
656 sp_spiral_is_invalid (SPSpiral const *spiral)
657 {
658 gdouble rad;
660 sp_spiral_get_polar (spiral, 0.0, &rad, NULL);
661 if (rad < 0.0 || rad > SP_HUGE) {
662 g_print ("rad(t=0)=%g\n", rad);
663 return TRUE;
664 }
665 sp_spiral_get_polar (spiral, 1.0, &rad, NULL);
666 if (rad < 0.0 || rad > SP_HUGE) {
667 g_print ("rad(t=1)=%g\n", rad);
668 return TRUE;
669 }
670 return FALSE;
671 }
673 /*
674 Local Variables:
675 mode:c++
676 c-file-style:"stroustrup"
677 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
678 indent-tabs-mode:nil
679 fill-column:99
680 End:
681 */
682 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :