sp-spiral.cpp revision 113c1da23be452320f54194502cbfe284547058d
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm/** \file
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * <sodipodi:spiral> implementation
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak */
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak/*
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Authors:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Mitsuru Oka <oka326@parkcity.ne.jp>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Lauris Kaplinski <lauris@kaplinski.com>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Abhishek Sharma
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Jon A. Cruz <jon@joncruz.org>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak *
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Copyright (C) 1999-2002 Lauris Kaplinski
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Copyright (C) 2000-2001 Ximian, Inc.
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak *
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak * Released under GNU GPL, read the file 'COPYING' for more information
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak */
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include "config.h"
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include "svg/svg.h"
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include "attributes.h"
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include <2geom/bezier-utils.h>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include <2geom/pathvector.h>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include "display/curve.h"
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm#include <glibmm/i18n.h>
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include "xml/repr.h"
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include "document.h"
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm#include "sp-spiral.h"
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak#include "sp-factory.h"
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyaknamespace {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak SPObject* createSpiral() {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak return new SPSpiral();
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm }
871e6dec0a43899a0031100adcf3343a7746bfa5Alex Valavanis
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak bool spiralRegistered = SPFactory::instance().registerObject("spiral", createSpiral);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak}
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
48eeda3eb22fd164613d5d9538c6e720cc92ef01buliabyakSPSpiral::SPSpiral() : SPShape() {
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm this->cobject = this;
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm this->cx = 0.0;
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->cy = 0.0;
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm this->exp = 1.0;
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm this->revo = 3.0;
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->rad = 1.0;
bcceefd876f22bacb6aeb206512ddb1539d2ced8apenner this->arg = 0.0;
bcceefd876f22bacb6aeb206512ddb1539d2ced8apenner this->t0 = 0.0;
bcceefd876f22bacb6aeb206512ddb1539d2ced8apenner}
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakSPSpiral::~SPSpiral() {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak}
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakvoid SPSpiral::build(SPDocument * document, Inkscape::XML::Node * repr) {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak SPShape::build(document, repr);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->readAttr("sodipodi:cx");
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->readAttr("sodipodi:cy");
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->readAttr("sodipodi:expansion");
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->readAttr("sodipodi:revolution");
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->readAttr("sodipodi:radius");
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm this->readAttr("sodipodi:argument");
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->readAttr("sodipodi:t0");
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak}
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakInkscape::XML::Node* SPSpiral::write(Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags) {
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
48eeda3eb22fd164613d5d9538c6e720cc92ef01buliabyak repr = xml_doc->createElement("svg:path");
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm }
60bbcba041e80a4b29118269c0897df5c068563eacspike
bcceefd876f22bacb6aeb206512ddb1539d2ced8apenner if (flags & SP_OBJECT_WRITE_EXT) {
60bbcba041e80a4b29118269c0897df5c068563eacspike /* Fixme: we may replace these attributes by
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm * sodipodi:spiral="cx cy exp revo rad arg t0"
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak */
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak repr->setAttribute("sodipodi:type", "spiral");
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm sp_repr_set_svg_double(repr, "sodipodi:cx", this->cx);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak sp_repr_set_svg_double(repr, "sodipodi:cy", this->cy);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak sp_repr_set_svg_double(repr, "sodipodi:expansion", this->exp);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak sp_repr_set_svg_double(repr, "sodipodi:revolution", this->revo);
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm sp_repr_set_svg_double(repr, "sodipodi:radius", this->rad);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak sp_repr_set_svg_double(repr, "sodipodi:argument", this->arg);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak sp_repr_set_svg_double(repr, "sodipodi:t0", this->t0);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak }
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak // make sure the curve is rebuilt with all up-to-date parameters
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->set_shape();
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak //Nulls might be possible if this called iteratively
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if (!this->_curve) {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak //g_warning("sp_spiral_write(): No path to copy\n");
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak return NULL;
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm }
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak char *d = sp_svg_write_path(this->_curve->get_pathvector());
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak repr->setAttribute("d", d);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak g_free(d);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm SPShape::write(xml_doc, repr, flags | SP_SHAPE_WRITE_PATH);
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak return repr;
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak}
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyakvoid SPSpiral::set(unsigned int key, gchar const* value) {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak /// \todo fixme: we should really collect updates
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak switch (key) {
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak case SP_ATTR_SODIPODI_CX:
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm if (!sp_svg_length_read_computed_absolute (value, &this->cx)) {
8db8b3d85d2f91e5429d9bb407d0babcfe44518bpjrm this->cx = 0.0;
60bbcba041e80a4b29118269c0897df5c068563eacspike }
60bbcba041e80a4b29118269c0897df5c068563eacspike
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
a223910930e4cf964962a08cf1d30928395652c5pjrm break;
a223910930e4cf964962a08cf1d30928395652c5pjrm
a223910930e4cf964962a08cf1d30928395652c5pjrm case SP_ATTR_SODIPODI_CY:
79ec30eb098ea1053718e7b5b0e92b8ff59488a2buliabyak if (!sp_svg_length_read_computed_absolute (value, &this->cy)) {
77d65c763568495a6ffc7c15a81964448139f47apjrm this->cy = 0.0;
a4030d5ca449e7e384bc699cd249ee704faaeab0Chris Morgan }
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
break;
case SP_ATTR_SODIPODI_EXPANSION:
if (value) {
/** \todo
* FIXME: check that value looks like a (finite)
* number. Create a routine that uses strtod, and
* accepts a default value (if strtod finds an error).
* N.B. atof/sscanf/strtod consider "nan" and "inf"
* to be valid numbers.
*/
this->exp = g_ascii_strtod (value, NULL);
this->exp = CLAMP (this->exp, 0.0, 1000.0);
} else {
this->exp = 1.0;
}
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
break;
case SP_ATTR_SODIPODI_REVOLUTION:
if (value) {
this->revo = g_ascii_strtod (value, NULL);
this->revo = CLAMP (this->revo, 0.05, 1024.0);
} else {
this->revo = 3.0;
}
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
break;
case SP_ATTR_SODIPODI_RADIUS:
if (!sp_svg_length_read_computed_absolute (value, &this->rad)) {
this->rad = MAX (this->rad, 0.001);
}
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
break;
case SP_ATTR_SODIPODI_ARGUMENT:
if (value) {
this->arg = g_ascii_strtod (value, NULL);
/** \todo
* FIXME: We still need some bounds on arg, for
* numerical reasons. E.g., we don't want inf or NaN,
* nor near-infinite numbers. I'm inclined to take
* modulo 2*pi. If so, then change the knot editors,
* which use atan2 - revo*2*pi, which typically
* results in very negative arg.
*/
} else {
this->arg = 0.0;
}
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
break;
case SP_ATTR_SODIPODI_T0:
if (value) {
this->t0 = g_ascii_strtod (value, NULL);
this->t0 = CLAMP (this->t0, 0.0, 0.999);
/** \todo
* Have shared constants for the allowable bounds for
* attributes. There was a bug here where we used -1.0
* as the minimum (which leads to NaN via, e.g.,
* pow(-1.0, 0.5); see sp_spiral_get_xy for
* requirements.
*/
} else {
this->t0 = 0.0;
}
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
break;
default:
SPShape::set(key, value);
break;
}
}
void SPSpiral::update(SPCtx *ctx, guint flags) {
SPSpiral* object = this;
if (flags & (SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_STYLE_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG)) {
reinterpret_cast<SPShape *>(object)->set_shape();
}
SPShape::update(ctx, flags);
}
void SPSpiral::update_patheffect(bool write) {
SPSpiral* shape = this;
this->set_shape();
if (write) {
Inkscape::XML::Node *repr = shape->getRepr();
if ( shape->_curve != NULL ) {
gchar *str = sp_svg_write_path(shape->_curve->get_pathvector());
repr->setAttribute("d", str);
g_free(str);
} else {
repr->setAttribute("d", NULL);
}
}
shape->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}
gchar* SPSpiral::description() {
SPSpiral* item = this;
// TRANSLATORS: since turn count isn't an integer, please adjust the
// string as needed to deal with an localized plural forms.
return g_strdup_printf (_("<b>Spiral</b> with %3f turns"), SP_SPIRAL(item)->revo);
}
/**
* Fit beziers together to spiral and draw it.
*
* \pre dstep \> 0.
* \pre is_unit_vector(*hat1).
* \post is_unit_vector(*hat2).
**/
void SPSpiral::fitAndDraw(SPCurve* c, double dstep, Geom::Point darray[], Geom::Point const& hat1, Geom::Point& hat2, double* t) const {
#define BEZIER_SIZE 4
#define FITTING_MAX_BEZIERS 4
#define BEZIER_LENGTH (BEZIER_SIZE * FITTING_MAX_BEZIERS)
g_assert (dstep > 0);
g_assert (is_unit_vector (hat1));
Geom::Point bezier[BEZIER_LENGTH];
double d;
int depth, i;
for (d = *t, i = 0; i <= SAMPLE_SIZE; d += dstep, i++) {
darray[i] = this->getXY(d);
/* Avoid useless adjacent dups. (Otherwise we can have all of darray filled with
the same value, which upsets chord_length_parameterize.) */
if ((i != 0) && (darray[i] == darray[i - 1]) && (d < 1.0)) {
i--;
d += dstep;
/** We mustn't increase dstep for subsequent values of
* i: for large spiral.exp values, rate of growth
* increases very rapidly.
*/
/** \todo
* Get the function itself to decide what value of d
* to use next: ensure that we move at least 0.25 *
* stroke width, for example. The derivative (as used
* for get_tangent before normalization) would be
* useful for estimating the appropriate d value. Or
* perhaps just start with a small dstep and scale by
* some small number until we move >= 0.25 *
* stroke_width. Must revert to the original dstep
* value for next iteration to avoid the problem
* mentioned above.
*/
}
}
double const next_t = d - 2 * dstep;
/* == t + (SAMPLE_SIZE - 1) * dstep, in absence of dups. */
hat2 = -this->getTangent(next_t);
/** \todo
* We should use better algorithm to specify maximum error.
*/
depth = Geom::bezier_fit_cubic_full (bezier, NULL, darray, SAMPLE_SIZE,
hat1, hat2,
SPIRAL_TOLERANCE*SPIRAL_TOLERANCE,
FITTING_MAX_BEZIERS);
g_assert(depth * BEZIER_SIZE <= gint(G_N_ELEMENTS(bezier)));
#ifdef SPIRAL_DEBUG
if (*t == spiral->t0 || *t == 1.0)
g_print ("[%s] depth=%d, dstep=%g, t0=%g, t=%g, arg=%g\n",
debug_state, depth, dstep, spiral->t0, *t, spiral->arg);
#endif
if (depth != -1) {
for (i = 0; i < 4*depth; i += 4) {
c->curveto(bezier[i + 1],
bezier[i + 2],
bezier[i + 3]);
}
} else {
#ifdef SPIRAL_VERBOSE
g_print ("cant_fit_cubic: t=%g\n", *t);
#endif
for (i = 1; i < SAMPLE_SIZE; i++)
c->lineto(darray[i]);
}
*t = next_t;
g_assert (is_unit_vector (hat2));
}
void SPSpiral::set_shape() {
SPSpiral *spiral = this;
SPSpiral* shape = spiral;
if (sp_lpe_item_has_broken_path_effect(SP_LPE_ITEM(shape))) {
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");
if (shape->getRepr()->attribute("d")) {
// unconditionally read the curve from d, if any, to preserve appearance
Geom::PathVector pv = sp_svg_read_pathv(shape->getRepr()->attribute("d"));
SPCurve *cold = new SPCurve(pv);
shape->setCurveInsync( cold, TRUE);
shape->setCurveBeforeLPE( cold );
cold->unref();
}
return;
}
Geom::Point darray[SAMPLE_SIZE + 1];
double t;
spiral->requestModified(SP_OBJECT_MODIFIED_FLAG);
SPCurve *c = new SPCurve ();
#ifdef SPIRAL_VERBOSE
g_print ("cx=%g, cy=%g, exp=%g, revo=%g, rad=%g, arg=%g, t0=%g\n",
spiral->cx,
spiral->cy,
spiral->exp,
spiral->revo,
spiral->rad,
spiral->arg,
spiral->t0);
#endif
/* Initial moveto. */
c->moveto(spiral->getXY(spiral->t0));
double const tstep = SAMPLE_STEP / spiral->revo;
double const dstep = tstep / (SAMPLE_SIZE - 1);
Geom::Point hat1 = spiral->getTangent(spiral->t0);
Geom::Point hat2;
for (t = spiral->t0; t < (1.0 - tstep);) {
spiral->fitAndDraw(c, dstep, darray, hat1, hat2, &t);
hat1 = -hat2;
}
if ((1.0 - t) > SP_EPSILON) {
spiral->fitAndDraw(c, (1.0 - t) / (SAMPLE_SIZE - 1.0), darray, hat1, hat2, &t);
}
/* Reset the shape'scurve to the "original_curve"
* This is very important for LPEs to work properly! (the bbox might be recalculated depending on the curve in shape)*/
shape->setCurveInsync( c, TRUE);
shape->setCurveBeforeLPE( c );
if (sp_lpe_item_has_path_effect(SP_LPE_ITEM(shape)) && sp_lpe_item_path_effects_enabled(SP_LPE_ITEM(shape))) {
SPCurve *c_lpe = c->copy();
bool success = sp_lpe_item_perform_path_effect(SP_LPE_ITEM (shape), c_lpe);
if (success) {
shape->setCurveInsync( c_lpe, TRUE);
}
c_lpe->unref();
}
c->unref();
}
/**
* Set spiral properties and update display.
*/
void SPSpiral::setPosition(gdouble cx, gdouble cy, gdouble exp, gdouble revo, gdouble rad, gdouble arg, gdouble t0) {
/** \todo
* Consider applying CLAMP or adding in-bounds assertions for
* some of these parameters.
*/
this->cx = cx;
this->cy = cy;
this->exp = exp;
this->revo = revo;
this->rad = MAX (rad, 0.0);
this->arg = arg;
this->t0 = CLAMP(t0, 0.0, 0.999);
this->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
}
void SPSpiral::snappoints(std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs) {
// We will determine the spiral's midpoint ourselves, instead of trusting on the base class
// Therefore snapping to object midpoints is temporarily disabled
Inkscape::SnapPreferences local_snapprefs = *snapprefs;
local_snapprefs.setTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT, false);
SPShape::snappoints(p, &local_snapprefs);
if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_OBJECT_MIDPOINT)) {
Geom::Affine const i2dt (this->i2dt_affine ());
p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(this->cx, this->cy) * i2dt, Inkscape::SNAPSOURCE_OBJECT_MIDPOINT, Inkscape::SNAPTARGET_OBJECT_MIDPOINT));
// This point is the start-point of the spiral, which is also returned when _snap_to_itemnode has been set
// in the object snapper. In that case we will get a duplicate!
}
}
/**
* Return one of the points on the spiral.
*
* \param t specifies how far along the spiral.
* \pre \a t in [0.0, 2.03]. (It doesn't make sense for t to be much more
* than 1.0, though some callers go slightly beyond 1.0 for curve-fitting
* purposes.)
*/
Geom::Point SPSpiral::getXY(gdouble t) const {
g_assert (this->exp >= 0.0);
/* Otherwise we get NaN for t==0. */
g_assert (this->exp <= 1000.0);
/* Anything much more results in infinities. Even allowing 1000 is somewhat overkill. */
g_assert (t >= 0.0);
/* Any callers passing -ve t will have a bug for non-integral values of exp. */
double const rad = this->rad * pow(t, (double)this->exp);
double const arg = 2.0 * M_PI * this->revo * t + this->arg;
return Geom::Point(rad * cos(arg) + this->cx, rad * sin(arg) + this->cy);
}
/**
* Returns the derivative of sp_spiral_get_xy with respect to t,
* scaled to a unit vector.
*
* \pre spiral != 0.
* \pre 0 \<= t.
* \pre p != NULL.
* \post is_unit_vector(*p).
*/
Geom::Point SPSpiral::getTangent(gdouble t) const {
Geom::Point ret(1.0, 0.0);
g_assert (t >= 0.0);
g_assert (this->exp >= 0.0);
/* See above for comments on these assertions. */
double const t_scaled = 2.0 * M_PI * this->revo * t;
double const arg = t_scaled + this->arg;
double const s = sin(arg);
double const c = cos(arg);
if (this->exp == 0.0) {
ret = Geom::Point(-s, c);
} else if (t_scaled == 0.0) {
ret = Geom::Point(c, s);
} else {
Geom::Point unrotated(this->exp, t_scaled);
double const s_len = L2 (unrotated);
g_assert (s_len != 0);
/** \todo
* Check that this isn't being too hopeful of the hypot
* function. E.g. test with numbers around 2**-1070
* (denormalized numbers), preferably on a few different
* platforms. However, njh says that the usual implementation
* does handle both very big and very small numbers.
*/
unrotated /= s_len;
/* ret = spiral->exp * (c, s) + t_scaled * (-s, c);
alternatively ret = (spiral->exp, t_scaled) * (( c, s),
(-s, c)).*/
ret = Geom::Point(dot(unrotated, Geom::Point(c, -s)),
dot(unrotated, Geom::Point(s, c)));
/* ret should already be approximately normalized: the
matrix ((c, -s), (s, c)) is orthogonal (it just
rotates by arg), and unrotated has been normalized,
so ret is already of unit length other than numerical
error in the above matrix multiplication. */
/** \todo
* I haven't checked how important it is for ret to be very
* near unit length; we could get rid of the below.
*/
ret.normalize();
/* Proof that ret length is non-zero: see above. (Should be near 1.) */
}
g_assert (is_unit_vector(ret));
return ret;
}
/**
* Compute rad and/or arg for point on spiral.
*/
void SPSpiral::getPolar(gdouble t, gdouble* rad, gdouble* arg) const {
if (rad) {
*rad = this->rad * pow(t, (double)this->exp);
}
if (arg) {
*arg = 2.0 * M_PI * this->revo * t + this->arg;
}
}
/**
* Return true if spiral has properties that make it invalid.
*/
bool SPSpiral::isInvalid() const {
gdouble rad;
this->getPolar(0.0, &rad, NULL);
if (rad < 0.0 || rad > SP_HUGE) {
g_print("rad(t=0)=%g\n", rad);
return TRUE;
}
this->getPolar(1.0, &rad, NULL);
if (rad < 0.0 || rad > SP_HUGE) {
g_print("rad(t=1)=%g\n", rad);
return TRUE;
}
return FALSE;
}
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :