mirror of
https://gitlab.gnome.org/GNOME/gtk.git
synced 2024-12-27 06:00:22 +00:00
209 lines
6.9 KiB
C
209 lines
6.9 KiB
C
|
/*
|
||
|
* Copyright © 2002 University of Southern California
|
||
|
* 2020 Benjamin Otte
|
||
|
*
|
||
|
* This library is free software; you can redistribute it and/or
|
||
|
* modify it under the terms of the GNU Lesser General Public
|
||
|
* License as published by the Free Software Foundation; either
|
||
|
* version 2.1 of the License, or (at your option) any later version.
|
||
|
*
|
||
|
* This library is distributed in the hope that it will be useful,
|
||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
* Lesser General Public License for more details.
|
||
|
*
|
||
|
* You should have received a copy of the GNU Lesser General Public
|
||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||
|
*
|
||
|
* Authors: Benjamin Otte <otte@gnome.org>
|
||
|
* Carl D. Worth <cworth@cworth.org>
|
||
|
*/
|
||
|
|
||
|
#include "config.h"
|
||
|
|
||
|
#include "gsksplineprivate.h"
|
||
|
|
||
|
#include <math.h>
|
||
|
|
||
|
/* Spline deviation from the circle in radius would be given by:
|
||
|
|
||
|
error = sqrt (x**2 + y**2) - 1
|
||
|
|
||
|
A simpler error function to work with is:
|
||
|
|
||
|
e = x**2 + y**2 - 1
|
||
|
|
||
|
From "Good approximation of circles by curvature-continuous Bezier
|
||
|
curves", Tor Dokken and Morten Daehlen, Computer Aided Geometric
|
||
|
Design 8 (1990) 22-41, we learn:
|
||
|
|
||
|
abs (max(e)) = 4/27 * sin**6(angle/4) / cos**2(angle/4)
|
||
|
|
||
|
and
|
||
|
abs (error) =~ 1/2 * e
|
||
|
|
||
|
Of course, this error value applies only for the particular spline
|
||
|
approximation that is used in _cairo_gstate_arc_segment.
|
||
|
*/
|
||
|
static float
|
||
|
arc_error_normalized (float angle)
|
||
|
{
|
||
|
return 2.0/27.0 * pow (sin (angle / 4), 6) / pow (cos (angle / 4), 2);
|
||
|
}
|
||
|
|
||
|
static float
|
||
|
arc_max_angle_for_tolerance_normalized (float tolerance)
|
||
|
{
|
||
|
float angle, error;
|
||
|
guint i;
|
||
|
|
||
|
/* Use table lookup to reduce search time in most cases. */
|
||
|
struct {
|
||
|
float angle;
|
||
|
float error;
|
||
|
} table[] = {
|
||
|
{ G_PI / 1.0, 0.0185185185185185036127 },
|
||
|
{ G_PI / 2.0, 0.000272567143730179811158 },
|
||
|
{ G_PI / 3.0, 2.38647043651461047433e-05 },
|
||
|
{ G_PI / 4.0, 4.2455377443222443279e-06 },
|
||
|
{ G_PI / 5.0, 1.11281001494389081528e-06 },
|
||
|
{ G_PI / 6.0, 3.72662000942734705475e-07 },
|
||
|
{ G_PI / 7.0, 1.47783685574284411325e-07 },
|
||
|
{ G_PI / 8.0, 6.63240432022601149057e-08 },
|
||
|
{ G_PI / 9.0, 3.2715520137536980553e-08 },
|
||
|
{ G_PI / 10.0, 1.73863223499021216974e-08 },
|
||
|
{ G_PI / 11.0, 9.81410988043554039085e-09 },
|
||
|
};
|
||
|
|
||
|
for (i = 0; i < G_N_ELEMENTS (table); i++)
|
||
|
{
|
||
|
if (table[i].error < tolerance)
|
||
|
return table[i].angle;
|
||
|
}
|
||
|
|
||
|
i++;
|
||
|
do {
|
||
|
angle = G_PI / i++;
|
||
|
error = arc_error_normalized (angle);
|
||
|
} while (error > tolerance);
|
||
|
|
||
|
return angle;
|
||
|
}
|
||
|
|
||
|
static guint
|
||
|
arc_segments_needed (float angle,
|
||
|
float radius,
|
||
|
float tolerance)
|
||
|
{
|
||
|
float max_angle;
|
||
|
|
||
|
/* the error is amplified by at most the length of the
|
||
|
* major axis of the circle; see cairo-pen.c for a more detailed analysis
|
||
|
* of this. */
|
||
|
max_angle = arc_max_angle_for_tolerance_normalized (tolerance / radius);
|
||
|
|
||
|
return ceil (fabs (angle) / max_angle);
|
||
|
}
|
||
|
|
||
|
/* We want to draw a single spline approximating a circular arc radius
|
||
|
R from angle A to angle B. Since we want a symmetric spline that
|
||
|
matches the endpoints of the arc in position and slope, we know
|
||
|
that the spline control points must be:
|
||
|
|
||
|
(R * cos(A), R * sin(A))
|
||
|
(R * cos(A) - h * sin(A), R * sin(A) + h * cos (A))
|
||
|
(R * cos(B) + h * sin(B), R * sin(B) - h * cos (B))
|
||
|
(R * cos(B), R * sin(B))
|
||
|
|
||
|
for some value of h.
|
||
|
|
||
|
"Approximation of circular arcs by cubic polynomials", Michael
|
||
|
Goldapp, Computer Aided Geometric Design 8 (1991) 227-238, provides
|
||
|
various values of h along with error analysis for each.
|
||
|
|
||
|
From that paper, a very practical value of h is:
|
||
|
|
||
|
h = 4/3 * R * tan(angle/4)
|
||
|
|
||
|
This value does not give the spline with minimal error, but it does
|
||
|
provide a very good approximation, (6th-order convergence), and the
|
||
|
error expression is quite simple, (see the comment for
|
||
|
_arc_error_normalized).
|
||
|
*/
|
||
|
static gboolean
|
||
|
gsk_spline_decompose_arc_segment (const graphene_point_t *center,
|
||
|
float radius,
|
||
|
float angle_A,
|
||
|
float angle_B,
|
||
|
GskSplineAddCurveFunc curve_func,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
float r_sin_A, r_cos_A;
|
||
|
float r_sin_B, r_cos_B;
|
||
|
float h;
|
||
|
|
||
|
r_sin_A = radius * sin (angle_A);
|
||
|
r_cos_A = radius * cos (angle_A);
|
||
|
r_sin_B = radius * sin (angle_B);
|
||
|
r_cos_B = radius * cos (angle_B);
|
||
|
|
||
|
h = 4.0/3.0 * tan ((angle_B - angle_A) / 4.0);
|
||
|
|
||
|
return curve_func ((graphene_point_t[4]) {
|
||
|
GRAPHENE_POINT_INIT (
|
||
|
center->x + r_cos_A,
|
||
|
center->y + r_sin_A
|
||
|
),
|
||
|
GRAPHENE_POINT_INIT (
|
||
|
center->x + r_cos_A - h * r_sin_A,
|
||
|
center->y + r_sin_A + h * r_cos_A
|
||
|
),
|
||
|
GRAPHENE_POINT_INIT (
|
||
|
center->x + r_cos_B + h * r_sin_B,
|
||
|
center->y + r_sin_B - h * r_cos_B
|
||
|
),
|
||
|
GRAPHENE_POINT_INIT (
|
||
|
center->x + r_cos_B,
|
||
|
center->y + r_sin_B
|
||
|
)
|
||
|
},
|
||
|
user_data);
|
||
|
}
|
||
|
|
||
|
gboolean
|
||
|
gsk_spline_decompose_arc (const graphene_point_t *center,
|
||
|
float radius,
|
||
|
float tolerance,
|
||
|
float start_angle,
|
||
|
float end_angle,
|
||
|
GskSplineAddCurveFunc curve_func,
|
||
|
gpointer user_data)
|
||
|
{
|
||
|
float step = start_angle - end_angle;
|
||
|
guint i, n_segments;
|
||
|
|
||
|
/* Recurse if drawing arc larger than pi */
|
||
|
if (ABS (step) > G_PI)
|
||
|
{
|
||
|
float mid_angle = (start_angle + end_angle) / 2.0;
|
||
|
|
||
|
return gsk_spline_decompose_arc (center, radius, tolerance, start_angle, mid_angle, curve_func, user_data)
|
||
|
&& gsk_spline_decompose_arc (center, radius, tolerance, mid_angle, end_angle, curve_func, user_data);
|
||
|
}
|
||
|
else if (ABS (step) < tolerance)
|
||
|
{
|
||
|
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
|
||
|
}
|
||
|
|
||
|
n_segments = arc_segments_needed (ABS (step), radius, tolerance);
|
||
|
step = (end_angle - start_angle) / n_segments;
|
||
|
|
||
|
for (i = 0; i < n_segments - 1; i++, start_angle += step)
|
||
|
{
|
||
|
if (!gsk_spline_decompose_arc_segment (center, radius, start_angle, start_angle + step, curve_func, user_data))
|
||
|
return FALSE;
|
||
|
}
|
||
|
return gsk_spline_decompose_arc_segment (center, radius, start_angle, end_angle, curve_func, user_data);
|
||
|
}
|
||
|
|