Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
sp-spiral.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/*
6 * Authors:
7 * Mitsuru Oka <oka326@parkcity.ne.jp>
8 * Lauris Kaplinski <lauris@kaplinski.com>
9 * Abhishek Sharma
10 * Jon A. Cruz <jon@joncruz.org>
11 *
12 * Copyright (C) 1999-2002 Lauris Kaplinski
13 * Copyright (C) 2000-2001 Ximian, Inc.
14 *
15 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
16 */
17
18#include "sp-spiral.h"
19
20#include <glibmm/i18n.h>
21
22#include <2geom/bezier-utils.h>
23
24#include "attributes.h"
25
26#include "display/curve.h"
27#include "snap-candidate.h" // for SnapCandidatePoint
28#include "snap-enums.h" // for SnapTargetType, SnapSourceType
29#include "snap-preferences.h" // for SnapPreferences
30#include "svg/svg.h"
31#include "xml/document.h" // for Document
32#include "xml/node.h" // for Node
33
34class SPDocument;
35
37 : SPShape()
38 , cx(0)
39 , cy(0)
40 , exp(1)
41 , revo(3)
42 , rad(1)
43 , arg(0)
44 , t0(0)
45{
46}
47
48SPSpiral::~SPSpiral() = default;
49
61
63 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
64 repr = xml_doc->createElement("svg:path");
65 }
66
67 if (flags & SP_OBJECT_WRITE_EXT) {
68 /* Fixme: we may replace these attributes by
69 * sodipodi:spiral="cx cy exp revo rad arg t0"
70 */
71 repr->setAttribute("sodipodi:type", "spiral");
72 repr->setAttributeSvgDouble("sodipodi:cx", this->cx);
73 repr->setAttributeSvgDouble("sodipodi:cy", this->cy);
74 repr->setAttributeSvgDouble("sodipodi:expansion", this->exp);
75 repr->setAttributeSvgDouble("sodipodi:revolution", this->revo);
76 repr->setAttributeSvgDouble("sodipodi:radius", this->rad);
77 repr->setAttributeSvgDouble("sodipodi:argument", this->arg);
78 repr->setAttributeSvgDouble("sodipodi:t0", this->t0);
79 }
80
81 // make sure the curve is rebuilt with all up-to-date parameters
82 this->set_shape();
83
84 // Nulls might be possible if this called iteratively
85 if (!this->_curve) {
86 //g_warning("sp_spiral_write(): No path to copy");
87 return nullptr;
88 }
89
90 repr->setAttribute("d", sp_svg_write_path(this->_curve->get_pathvector()));
91
92 SPShape::write(xml_doc, repr, flags | SP_SHAPE_WRITE_PATH);
93
94 return repr;
95}
96
97void SPSpiral::set(SPAttr key, gchar const* value) {
99 switch (key) {
101 if (!sp_svg_length_read_computed_absolute (value, &this->cx)) {
102 this->cx = 0.0;
103 }
104
105 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
106 break;
107
109 if (!sp_svg_length_read_computed_absolute (value, &this->cy)) {
110 this->cy = 0.0;
111 }
112
113 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
114 break;
115
117 if (value) {
125 this->exp = g_ascii_strtod (value, nullptr);
126 this->exp = CLAMP (this->exp, 0.0, 1000.0);
127 } else {
128 this->exp = 1.0;
129 }
130
131 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
132 break;
133
135 if (value) {
136 this->revo = g_ascii_strtod (value, nullptr);
137 this->revo = CLAMP (this->revo, 0.05, 1024.0);
138 } else {
139 this->revo = 3.0;
140 }
141
142 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
143 break;
144
146 if (!sp_svg_length_read_computed_absolute (value, &this->rad)) {
147 this->rad = MAX (this->rad, 0.001);
148 }
149
150 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
151 break;
152
154 if (value) {
155 this->arg = g_ascii_strtod (value, nullptr);
164 } else {
165 this->arg = 0.0;
166 }
167
168 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
169 break;
170
172 if (value) {
173 this->t0 = g_ascii_strtod (value, nullptr);
174 this->t0 = CLAMP (this->t0, 0.0, 0.999);
182 } else {
183 this->t0 = 0.0;
184 }
185
186 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
187 break;
188
189 default:
190 SPShape::set(key, value);
191 break;
192 }
193}
194
195void SPSpiral::update(SPCtx *ctx, guint flags) {
196 if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
197 this->set_shape();
198 }
199
200 SPShape::update(ctx, flags);
201}
202
203const char* SPSpiral::typeName() const {
204 return "spiral";
205}
206
207const char* SPSpiral::displayName() const {
208 return _("Spiral");
209}
210
211gchar* SPSpiral::description() const {
212 // TRANSLATORS: since turn count isn't an integer, please adjust the
213 // string as needed to deal with an localized plural forms.
214 return g_strdup_printf (_("with %3f turns"), this->revo);
215}
216
224void SPSpiral::fitAndDraw(SPCurve* c, double dstep, Geom::Point darray[], Geom::Point const& hat1, Geom::Point& hat2, double* t) const {
225#define BEZIER_SIZE 4
226#define FITTING_MAX_BEZIERS 4
227#define BEZIER_LENGTH (BEZIER_SIZE * FITTING_MAX_BEZIERS)
228
229 g_assert (dstep > 0);
230 g_assert (is_unit_vector (hat1));
231
232 Geom::Point bezier[BEZIER_LENGTH];
233 double d;
234 int depth, i;
235
236 for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) {
237 darray[i] = this->getXY(d);
238
239 /* Avoid useless adjacent dups. (Otherwise we can have all of darray filled with
240 the same value, which upsets chord_length_parameterize.) */
241 if ((i != 0) && (darray[i] == darray[i - 1]) && (d < 1.0)) {
242 i--;
243 d += dstep;
260 }
261 }
262
263 double const next_t = d - 2 * dstep;
264 /* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */
265
266 hat2 = -this->getTangent(next_t);
267
271 depth = Geom::bezier_fit_cubic_full (bezier, nullptr, darray, SAMPLE_SIZE,
272 hat1, hat2,
273 SPIRAL_TOLERANCE*SPIRAL_TOLERANCE,
274 FITTING_MAX_BEZIERS);
275
276 g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier)));
277
278#ifdef SPIRAL_DEBUG
279 if (*t == spiral->t0 || *t == 1.0)
280 g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n",
281 debug_state, depth, dstep, spiral->t0, *t, spiral->arg);
282#endif
283
284 if (depth != -1) {
285 for (i = 0; i < 4*depth; i += 4) {
286 c->curveto(bezier[i + 1],
287 bezier[i + 2],
288 bezier[i + 3]);
289 }
290 } else {
291#ifdef SPIRAL_VERBOSE
292 g_print ("cant_fit_cubic: t=%g\n", *t);
293#endif
294 for (i = 1; i < SAMPLE_SIZE; i++)
295 c->lineto(darray[i]);
296 }
297
298 *t = next_t;
299
300 g_assert (is_unit_vector (hat2));
301}
302
304 if (checkBrokenPathEffect()) {
305 return;
306 }
307
308 Geom::Point darray[SAMPLE_SIZE + 1];
309
310 this->requestModified(SP_OBJECT_MODIFIED_FLAG);
311
312 SPCurve c;
313
314#ifdef SPIRAL_VERBOSE
315 g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n",
316 this->cx,
317 this->cy,
318 this->exp,
319 this->revo,
320 this->rad,
321 this->arg,
322 this->t0);
323#endif
324
325 /* Initial moveto. */
326 c.moveto(this->getXY(this->t0));
327
328 double const tstep = SAMPLE_STEP / this->revo;
329 double const dstep = tstep / (SAMPLE_SIZE - 1);
330
331 Geom::Point hat1 = this->getTangent(this->t0);
332 Geom::Point hat2;
333
334 double t;
335 for (t = this->t0; t < (1.0 - tstep);) {
336 this->fitAndDraw(&c, dstep, darray, hat1, hat2, &t);
337
338 hat1 = -hat2;
339 }
340
341 if ((1.0 - t) > SP_EPSILON) {
342 this->fitAndDraw(&c, (1.0 - t) / (SAMPLE_SIZE - 1.0), darray, hat1, hat2, &t);
343 }
344
346
347}
348
352void SPSpiral::setPosition(gdouble cx, gdouble cy, gdouble exp, gdouble revo, gdouble rad, gdouble arg, gdouble t0) {
357 this->cx = cx;
358 this->cy = cy;
359 this->exp = exp;
360 this->revo = revo;
361 this->rad = MAX (rad, 0.0);
362 this->arg = arg;
363 this->t0 = CLAMP(t0, 0.0, 0.999);
364
365 this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
366}
367
368void SPSpiral::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) const {
369 // We will determine the spiral's midpoint ourselves, instead of trusting on the base class
370 // Therefore snapping to object midpoints is temporarily disabled
371 Inkscape::SnapPreferences local_snapprefs = *snapprefs;
373
374 SPShape::snappoints(p, &local_snapprefs);
375
377 Geom::Affine const i2dt (this->i2dt_affine ());
378
380 // This point is the start-point of the spiral, which is also returned when _snap_to_itemnode has been set
381 // in the object snapper. In that case we will get a duplicate!
382 }
383}
384
389{
391 return xform;
392 }
393 // Only set transform with proportional scaling
394 if (!xform.withoutTranslation().isUniformScale()) {
395 return xform;
396 }
397 /* Calculate spiral start in parent coords. */
398 Geom::Point pos( Geom::Point(this->cx, this->cy) * xform );
399
400 /* This function takes care of translation and scaling, we return whatever parts we can't
401 handle. */
402 Geom::Affine ret(Geom::Affine(xform).withoutTranslation());
403 gdouble const s = hypot(ret[0], ret[1]);
404 if (s > 1e-9) {
405 ret[0] /= s;
406 ret[1] /= s;
407 ret[2] /= s;
408 ret[3] /= s;
409 } else {
410 ret[0] = 1.0;
411 ret[1] = 0.0;
412 ret[2] = 0.0;
413 ret[3] = 1.0;
414 }
415
416 this->rad *= s;
417
418 /* Find start in item coords */
419 pos = pos * ret.inverse();
420 this->cx = pos[Geom::X];
421 this->cy = pos[Geom::Y];
422
423 this->set_shape();
424
425 // Adjust stroke width
426 this->adjust_stroke(s);
427
428 // Adjust pattern fill
429 this->adjust_pattern(xform * ret.inverse());
430
431 // Adjust gradient fill
432 this->adjust_gradient(xform * ret.inverse());
433
434 return ret;
435}
436
440
449Geom::Point SPSpiral::getXY(gdouble t) const {
450 g_assert (this->exp >= 0.0);
451 /* Otherwise we get NaN for t==0. */
452 g_assert (this->exp <= 1000.0);
453 /* Anything much more results in infinities. Even allowing 1000 is somewhat overkill. */
454 g_assert (t >= 0.0);
455 /* Any callers passing -ve t will have a bug for non-integral values of exp. */
456
457 double const rad = this->rad * pow(t, (double)this->exp);
458 double const arg = 2.0 * M_PI * this->revo * t + this->arg;
459
460 return Geom::Point(rad * cos(arg) + this->cx, rad * sin(arg) + this->cy);
461}
462
463
474 Geom::Point ret(1.0, 0.0);
475
476 g_assert (t >= 0.0);
477 g_assert (this->exp >= 0.0);
478 /* See above for comments on these assertions. */
479
480 double const t_scaled = 2.0 * M_PI * this->revo * t;
481 double const arg = t_scaled + this->arg;
482 double const s = sin(arg);
483 double const c = cos(arg);
484
485 if (this->exp == 0.0) {
486 ret = Geom::Point(-s, c);
487 } else if (t_scaled == 0.0) {
488 ret = Geom::Point(c, s);
489 } else {
490 Geom::Point unrotated(this->exp, t_scaled);
491 double const s_len = L2 (unrotated);
492 g_assert (s_len != 0);
500 unrotated /= s_len;
501
502 /* ret = spiral->exp * (c, s) + t_scaled * (-s, c);
503 alternatively ret = (spiral->exp, t_scaled) * (( c, s),
504 (-s, c)).*/
505 ret = Geom::Point(dot(unrotated, Geom::Point(c, -s)),
506 dot(unrotated, Geom::Point(s, c)));
507 /* ret should already be approximately normalized: the
508 matrix ((c, -s), (s, c)) is orthogonal (it just
509 rotates by arg), and unrotated has been normalized,
510 so ret is already of unit length other than numerical
511 error in the above matrix multiplication. */
512
518 ret.normalize();
519 /* Proof that ret length is non-zero: see above. (Should be near 1.) */
520 }
521
522 g_assert (is_unit_vector(ret));
523 return ret;
524}
525
529void SPSpiral::getPolar(gdouble t, gdouble* rad, gdouble* arg) const {
530 if (rad) {
531 *rad = this->rad * pow(t, (double)this->exp);
532 }
533
534 if (arg) {
535 *arg = 2.0 * M_PI * this->revo * t + this->arg;
536 }
537}
538
543 gdouble rad;
544
545 this->getPolar(0.0, &rad, nullptr);
546
547 if (rad < 0.0 || rad > SP_HUGE) {
548 g_warning("rad(t=0)=%g", rad);
549 return true;
550 }
551
552 this->getPolar(1.0, &rad, nullptr);
553
554 if (rad < 0.0 || rad > SP_HUGE) {
555 g_warning("rad(t=1)=%g", rad);
556 return true;
557 }
558
559 return false;
560}
561
562/*
563 Local Variables:
564 mode:c++
565 c-file-style:"stroustrup"
566 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
567 indent-tabs-mode:nil
568 fill-column:99
569 End:
570*/
571// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Lookup dictionary for attributes/properties.
SPAttr
Definition attributes.h:27
@ SODIPODI_CY
@ SODIPODI_REVOLUTION
@ SODIPODI_CX
@ SODIPODI_RADIUS
@ SODIPODI_EXPANSION
@ SODIPODI_T0
@ SODIPODI_ARGUMENT
Bezier fitting algorithms.
3x3 matrix representing an affine transformation.
Definition affine.h:70
bool isUniformScale(Coord eps=EPSILON) const
Check whether this matrix represents pure uniform scaling.
Definition affine.cpp:174
Affine inverse() const
Compute the inverse matrix.
Definition affine.cpp:388
Affine withoutTranslation() const
Definition affine.h:169
Two-dimensional point that doubles as a vector.
Definition point.h:66
void normalize()
Normalize the vector representing the point.
Definition point.cpp:96
Storing of snapping preferences.
void setTargetSnappable(Inkscape::SnapTargetType const target, bool enabled)
bool isTargetSnappable(Inkscape::SnapTargetType const target) const
Interface for refcounted XML nodes.
Definition node.h:80
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
Wrapper around a Geom::PathVector object.
Definition curve.h:26
void moveto(Geom::Point const &p)
Perform a moveto to a point, thus starting a new subpath.
Definition curve.cpp:138
Typed SVG document implementation.
Definition document.h:103
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1828
void adjust_gradient(Geom::Affine const &postmul, bool set=false)
Definition sp-item.cpp:1433
void adjust_pattern(Geom::Affine const &postmul, bool set=false, PaintServerTransform=TRANSFORM_BOTH)
Definition sp-item.cpp:1387
void adjust_stroke(double ex)
Definition sp-item.cpp:1479
bool pathEffectsEnabled() const
bool optimizeTransforms()
returns false when LPE write unoptimiced
Inkscape::XML::Node * repr
Definition sp-object.h:193
void requestModified(unsigned int flags)
Requests that a modification notification signal be emitted later (e.g.
SPDocument * document
Definition sp-object.h:188
void readAttr(char const *key)
Read value of key attribute from XML node into object.
void requestDisplayUpdate(unsigned int flags)
Queues an deferred update of this object's display.
Base class for shapes, including <path> element.
Definition sp-shape.h:38
void update(SPCtx *ctx, unsigned int flags) override
Definition sp-shape.cpp:125
void snappoints(std::vector< Inkscape::SnapCandidatePoint > &p, Inkscape::SnapPreferences const *snapprefs) const override
Definition sp-shape.cpp:991
bool prepareShapeForLPE(SPCurve const *c)
Definition sp-shape.cpp:479
void set(SPAttr key, char const *value) override
Definition sp-shape.cpp:115
void build(SPDocument *document, Inkscape::XML::Node *repr) override
Definition sp-shape.cpp:63
void update_patheffect(bool write) override
Definition sp-shape.cpp:650
std::shared_ptr< SPCurve const > _curve
Definition sp-shape.h:70
Inkscape::XML::Node * write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override
Definition sp-shape.cpp:120
bool checkBrokenPathEffect()
Definition sp-shape.cpp:459
void update_patheffect(bool write) override
void set_shape() override
bool isInvalid() const
Return true if spiral has properties that make it invalid.
void build(SPDocument *doc, Inkscape::XML::Node *repr) override
Definition sp-spiral.cpp:50
Geom::Point getTangent(double t) const
Returns the derivative of sp_spiral_get_xy with respect to t, scaled to a unit vector.
void update(SPCtx *ctx, unsigned int flags) override
float t0
Definition sp-spiral.h:50
float cx
Definition sp-spiral.h:45
~SPSpiral() override
float revo
Spiral revolution factor.
Definition sp-spiral.h:47
float rad
Spiral radius.
Definition sp-spiral.h:48
const char * typeName() const override
The item's type name, not node tag name.
float exp
Spiral expansion factor.
Definition sp-spiral.h:46
float arg
Spiral argument.
Definition sp-spiral.h:49
const char * displayName() const override
The item's type name as a translated human string.
void fitAndDraw(SPCurve *c, double dstep, Geom::Point darray[], Geom::Point const &hat1, Geom::Point &hat2, double *t) const
Fit beziers together to spiral and draw it.
Inkscape::XML::Node * write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, unsigned int flags) override
Definition sp-spiral.cpp:62
void setPosition(double cx, double cy, double exp, double revo, double rad, double arg, double t0)
Set spiral properties and update display.
char * description() const override
void getPolar(double t, double *rad, double *arg) const
Compute rad and/or arg for point on spiral.
Geom::Affine set_transform(Geom::Affine const &xform) override
Set spiral transform.
void snappoints(std::vector< Inkscape::SnapCandidatePoint > &p, Inkscape::SnapPreferences const *snapprefs) const override
void set(SPAttr key, char const *value) override
Definition sp-spiral.cpp:97
float cy
Definition sp-spiral.h:45
Geom::Point getXY(double t) const
Return one of the points on the spiral.
double c[8][4]
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
int bezier_fit_cubic_full(Point bezier[], int split_points[], Point const data[], int len, Point const &tHat1, Point const &tHat2, double error, unsigned max_beziers)
Fit a multi-segment Bezier curve to a set of digitized points, without possible weedout of identical ...
@ SNAPSOURCE_OBJECT_MIDPOINT
Definition snap-enums.h:53
@ SNAPTARGET_OBJECT_MIDPOINT
Definition snap-enums.h:115
static cairo_user_data_key_t key
Some utility classes to store various kinds of snap candidates.
Interface for XML documents.
Definition document.h:43
virtual Node * createElement(char const *name)=0
Unused.
Definition sp-object.h:94
unsigned int sp_svg_length_read_computed_absolute(gchar const *str, float *length)
static void sp_svg_write_path(Inkscape::SVG::PathString &str, Geom::Path const &p, bool normalize=false)
Definition svg-path.cpp:109
void dot(Cairo::RefPtr< Cairo::Context > &cr, double x, double y)
Interface for XML documents.
Interface for XML nodes.