blob: 75989185cc35048f02733b64c8f4595aaa93b4e1 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tests/common/arc_parameters.h"
#include <math.h>
// All computations taken from http://www.w3.org/TR/SVG/implnote.html
arc_endpoint_parameters_t
arc_endpoint_parameters_from_center(arc_center_parameters_t params)
{
// Section B.2.3. Conversion from center to endpoint parameterization
const double cos_phi = cos(params.phi);
const double sin_phi = sin(params.phi);
const double org_x1 = params.rx * cos(params.theta);
const double org_y1 = params.ry * sin(params.theta);
const double org_x2 = params.rx * cos(params.theta + params.theta_delta);
const double org_y2 = params.ry * sin(params.theta + params.theta_delta);
return {
.x1 = params.cx + cos_phi * org_x1 - sin_phi * org_y1,
.y1 = params.cy + sin_phi * org_x1 + cos_phi * org_y1,
.x2 = params.cx + cos_phi * org_x2 - sin_phi * org_y2,
.y2 = params.cy + sin_phi * org_x2 + cos_phi * org_y2,
.large_arc_flag = fabs(params.theta_delta) > M_PI,
.sweep_flag = params.theta_delta > 0,
.rx = params.rx,
.ry = params.ry,
.phi = params.phi,
};
}
static double
angle_from(double dx, double dy)
{
double len = hypot(dx, dy);
if (!isnormal(len))
return 0.;
double angle = acos(dx / len);
return dy < 0 ? -angle : angle;
}
// Compute the endpoint parameterization of a given arc from its center one.
arc_center_parameters_t
arc_center_parameters_from_endpoint(arc_endpoint_parameters_t params)
{
// Section C.4.2. Out-of-range parameters
// "If the endpoints [...] are identical, then this is equivalent to omitting
// the elliptic arc segment entirely."
if (params.x2 == params.x1 && params.y2 == params.y1)
return {};
// B.2.5 step2 (Ensure radii are positive).
double rx = fabs(params.rx);
double ry = fabs(params.ry);
// B.2.5 step 1 (Ensure radii are non zero).
if (rx == 0. || ry == 0.)
return {};
// B.2.4 step1 (Equation 5.1)
const double cos_phi = cos(params.phi);
const double sin_phi = sin(params.phi);
#if 1
// NOTE: The following computations are equivalent to the one specified
// by the SVG implementation note (and found below in the #else .. #endif
// block). Experimentation / debugging shows that they give the same result
// up to the 14th decimal, and that this version seems to have slightly less
// rounding errors overall.
// Undo axis rotation and radii scaling first.
const double x1 = (+params.x1 * cos_phi + params.y1 * sin_phi) / rx;
const double y1 = (-params.x1 * sin_phi + params.y1 * cos_phi) / ry;
const double x2 = (+params.x2 * cos_phi + params.y2 * sin_phi) / rx;
const double y2 = (-params.x2 * sin_phi + params.y2 * cos_phi) / ry;
// Points are now on a unit circle, find its center in transformed space.
const double lx = (x1 - x2) * 0.5;
const double ly = (y1 - y2) * 0.5;
double cx = (x1 + x2) * 0.5;
double cy = (y1 + y2) * 0.5;
const double llen2 = lx * lx + ly * ly;
if (llen2 < 1)
{
double radicand = sqrt((1 - llen2) / llen2);
if (params.large_arc_flag != params.sweep_flag)
radicand = -radicand;
cx += -ly * radicand;
cy += +lx * radicand;
}
// Get angle and angle sweep.
double theta = angle_from(x1 - cx, y1 - cy);
double theta_delta = angle_from(x2 - cx, y2 - cy) - theta;
// convert center coordinates back to original space.
const double cxs = cx * rx;
const double cys = cy * ry;
cx = cxs * cos_phi - cys * sin_phi;
cy = cxs * sin_phi + cys * cos_phi;
#else // !1
const double mx = (params.x1 - params.x2) * 0.5;
const double my = (params.y1 - params.y2) * 0.5;
const double x1p = cos_phi * mx + sin_phi * my;
const double y1p = cos_phi * my - sin_phi * mx;
// B.5.2. step 3 (Ensure radii are large enough).
double rxrx = rx * rx;
double ryry = ry * ry;
const double x1px1p = x1p * x1p;
const double y1py1p = y1p * y1p;
// Starting values for cx', cy', cx and cy, corresponding to the case
// where |sigma > 1| below.
double cxp = 0.;
double cyp = 0.;
double cx = (params.x1 + params.x2) * 0.5;
double cy = (params.y1 + params.y2) * 0.5;
const double sigma = x1px1p / rxrx + y1py1p / ryry;
if (sigma >= 1.0)
{
const double sigma_sqrt = sqrt(sigma);
rx = sigma_sqrt * rx;
ry = sigma_sqrt * ry;
}
else
{
// Back to Section B.2.4: Equation 5.2
const double aa = rxrx * y1py1p;
const double bb = ryry * x1px1p;
double radicand = sqrt((rxrx * ryry - aa - bb) / (aa + bb));
if (params.large_arc_flag == params.sweep_flag)
radicand = -radicand;
cxp = radicand * rx * y1p / ry;
cyp = -radicand * ry * x1p / rx;
// B.5.2. step 3 Compute center.
cx += cos_phi * cxp - sin_phi * cyp;
cy += sin_phi * cxp + cos_phi * cyp;
}
// B.5.2. step 4 (Compute theta1 and Dtheta)
double theta = angle_from((x1p - cxp) / rx, (y1p - cyp) / ry);
double theta_delta = angle_from(-(x1p + cxp) / rx, -(y1p + cyp) / ry) - theta;
#endif // !1
if (params.sweep_flag)
{
if (theta_delta < 0.)
theta_delta += M_PI * 2.;
}
else
{
if (theta_delta > 0.)
theta_delta -= M_PI * 2;
}
return {
.cx = cx,
.cy = cy,
.rx = rx,
.ry = ry,
.phi = params.phi,
.theta = theta,
.theta_delta = theta_delta,
};
}