lazarus/components/aggpas/src/agg_bezier_arc.pas
mattias 36a2b1ea07 added aggpas
git-svn-id: trunk@21942 -
2009-10-01 12:24:32 +00:00

508 lines
9.9 KiB
ObjectPascal

//----------------------------------------------------------------------------
// Anti-Grain Geometry - Version 2.4 (Public License)
// Copyright (C) 2002-2005 Maxim Shemanarev (http://www.antigrain.com)
//
// Anti-Grain Geometry - Version 2.4 Release Milano 3 (AggPas 2.4 RM3)
// Pascal Port By: Milan Marusinec alias Milano
// milan@marusinec.sk
// http://www.aggpas.org
// Copyright (c) 2005-2006
//
// Permission to copy, use, modify, sell and distribute this software
// is granted provided this copyright notice appears in all copies.
// This software is provided "as is" without express or implied
// warranty, and with no claim as to its suitability for any purpose.
//
//----------------------------------------------------------------------------
// Contact: mcseem@antigrain.com
// mcseemagg@yahoo.com
// http://www.antigrain.com
//
//----------------------------------------------------------------------------
//
// Arc generator. Produces at most 4 consecutive cubic bezier curves, i.e.,
// 4, 7, 10, or 13 vertices.
//
// [Pascal Port History] -----------------------------------------------------
//
// 23.06.2006-Milano: ptrcomp adjustments
// 18.12.2005-Milano: Unit port establishment
//
{ agg_bezier_arc.pas }
unit
agg_bezier_arc ;
INTERFACE
{$I agg_mode.inc }
uses
Math ,
agg_basics ,
agg_vertex_source ,
agg_trans_affine ;
{ TYPES DEFINITION }
type
bezier_arc_ptr = ^bezier_arc;
bezier_arc = object(vertex_source )
m_vertex ,
m_num_vertices : unsigned;
m_vertices : double_26;
m_cmd : unsigned;
constructor Construct; overload;
constructor Construct(x ,y ,rx ,ry ,start_angle ,sweep_angle : double ); overload;
procedure init(x ,y ,rx ,ry ,start_angle ,sweep_angle : double );
procedure rewind(path_id : unsigned ); virtual;
function vertex(x ,y : double_ptr ) : unsigned; virtual;
// Supplemantary functions. num_vertices() actually returns doubled
// number of vertices. That is, for 1 vertex it returns 2.
function num_vertices : unsigned;
function vertices : double_26_ptr;
end;
//==========================================================bezier_arc_svg
// Compute an SVG-style bezier arc.
//
// Computes an elliptical arc from (x1, y1) to (x2, y2). The size and
// orientation of the ellipse are defined by two radii (rx, ry)
// and an x-axis-rotation, which indicates how the ellipse as a whole
// is rotated relative to the current coordinate system. The center
// (cx, cy) of the ellipse is calculated automatically to satisfy the
// constraints imposed by the other parameters.
// large-arc-flag and sweep-flag contribute to the automatic calculations
// and help determine how the arc is drawn.
bezier_arc_svg_ptr = ^bezier_arc_svg;
bezier_arc_svg = object(bezier_arc )
m_radii_ok : boolean;
constructor Construct; overload;
constructor Construct(x1 ,y1 ,rx ,ry ,angle : double; large_arc_flag ,sweep_flag : boolean; x2 ,y2 : double ); overload;
procedure init(x0 ,y0 ,rx ,ry ,angle : double; large_arc_flag ,sweep_flag : boolean; x2 ,y2 : double );
function radii_ok : boolean;
end;
{ GLOBAL PROCEDURES }
procedure arc_to_bezier(cx ,cy ,rx ,ry ,start_angle ,sweep_angle : double; curve : double_8_ptr );
IMPLEMENTATION
{ LOCAL VARIABLES & CONSTANTS }
const
bezier_arc_angle_epsilon = 0.01;
{ UNIT IMPLEMENTATION }
{ ARC_TO_BEZIER }
procedure arc_to_bezier;
var
i : unsigned;
sn ,cs ,
x0 ,y0 ,
tx ,ty : double;
px ,py : array[0..3 ] of double;
begin
x0:=Cos(sweep_angle / 2.0 );
y0:=Sin(sweep_angle / 2.0 );
tx:=(1.0 - x0 ) * 4.0 / 3.0;
ty:=y0 - tx * x0 / y0;
px[0 ]:=x0;
py[0 ]:=-y0;
px[1 ]:=x0 + tx;
py[1 ]:=-ty;
px[2 ]:=x0 + tx;
py[2 ]:=ty;
px[3 ]:=x0;
py[3 ]:=y0;
sn:=Sin(start_angle + sweep_angle / 2.0 );
cs:=Cos(start_angle + sweep_angle / 2.0 );
for i:=0 to 3 do
begin
curve[i * 2 ] :=cx + rx * (px[i ] * cs - py[i ] * sn );
curve[i * 2 + 1 ]:=cy + ry * (px[i ] * sn + py[i ] * cs );
end;
end;
{ CONSTRUCT }
constructor bezier_arc.Construct;
begin
m_vertex:=26;
m_num_vertices:=0;
m_cmd:=path_cmd_line_to;
end;
{ CONSTRUCT }
constructor bezier_arc.Construct(x ,y ,rx ,ry ,start_angle ,sweep_angle : double );
begin
init(x ,y ,rx ,ry ,start_angle ,sweep_angle );
end;
{ INIT }
procedure bezier_arc.init;
var
i : int;
f : double;
total_sweep ,
local_sweep ,
prev_sweep : double;
done : boolean;
begin
i:=trunc(start_angle / (2.0 * pi ) );
f:=start_angle - (i * 2.0 * pi );
start_angle:=f;
if sweep_angle >= 2.0 * pi then
sweep_angle:=2.0 * pi;
if sweep_angle <= -2.0 * pi then
sweep_angle:=-2.0 * pi;
if Abs(sweep_angle ) < 1e-10 then
begin
m_num_vertices:=4;
m_cmd:=path_cmd_line_to;
m_vertices[0 ]:=x + rx * Cos(start_angle );
m_vertices[1 ]:=y + ry * Sin(start_angle );
m_vertices[2 ]:=x + rx * Cos(start_angle + sweep_angle );
m_vertices[3 ]:=y + ry * Sin(start_angle + sweep_angle );
exit;
end;
total_sweep:=0.0;
local_sweep:=0.0;
m_num_vertices:=2;
m_cmd:=path_cmd_curve4;
done :=false;
repeat
if sweep_angle < 0.0 then
begin
prev_sweep :=total_sweep;
local_sweep:=-pi * 0.5;
total_sweep:=total_sweep - (pi * 0.5 );
if total_sweep <= sweep_angle + bezier_arc_angle_epsilon then
begin
local_sweep:=sweep_angle - prev_sweep;
done:=true;
end;
end
else
begin
prev_sweep :=total_sweep;
local_sweep:=pi * 0.5;
total_sweep:=total_sweep + (pi * 0.5 );
if total_sweep >= sweep_angle - bezier_arc_angle_epsilon then
begin
local_sweep:=sweep_angle - prev_sweep;
done:=true;
end;
end;
arc_to_bezier(
x ,y ,rx ,ry ,
start_angle ,local_sweep ,
@m_vertices[m_num_vertices - 2 ] );
m_num_vertices:=m_num_vertices + 6;
start_angle :=start_angle + local_sweep;
until done or (m_num_vertices >= 26 );
end;
{ REWIND }
procedure bezier_arc.rewind;
begin
m_vertex:=0;
end;
{ VERTEX }
function bezier_arc.vertex;
begin
if m_vertex >= m_num_vertices then
begin
result:=path_cmd_stop;
exit;
end;
x^:=m_vertices[m_vertex ];
y^:=m_vertices[m_vertex + 1 ];
inc(m_vertex ,2 );
if m_vertex = 2 then
result:=path_cmd_move_to
else
result:=m_cmd;
end;
{ NUM_VERTICES }
function bezier_arc.num_vertices;
begin
result:=m_num_vertices;
end;
{ VERTICES }
function bezier_arc.vertices;
begin
result:=@m_vertices;
end;
{ CONSTRUCT }
constructor bezier_arc_svg.Construct;
begin
inherited Construct;
m_radii_ok:=false;
end;
{ CONSTRUCT }
constructor bezier_arc_svg.Construct(x1 ,y1 ,rx ,ry ,angle : double; large_arc_flag ,sweep_flag : boolean; x2 ,y2 : double );
begin
inherited Construct;
m_radii_ok:=false;
init(x1 ,y1 ,rx ,ry ,angle ,large_arc_flag ,sweep_flag ,x2 ,y2 );
end;
{ INIT }
procedure bezier_arc_svg.init;
var
i : unsigned;
v ,
p , n ,
sq ,
x1 ,y1 ,
cx ,cy ,
ux ,uy ,
vx ,vy ,
dx2 ,dy2 ,
prx ,pry ,
px1 ,py1 ,
cx1 ,cy1 ,
sx2 ,sy2 ,
sign ,coef ,
radii_check ,
start_angle ,
sweep_angle ,
cos_a ,sin_a : double;
mtx : trans_affine_rotation;
trn : trans_affine_translation;
begin
m_radii_ok:=true;
if rx < 0.0 then
rx:=-rx;
if ry < 0.0 then
ry:=-rx;
// Calculate the middle point between
// the current and the final points
dx2:=(x0 - x2 ) / 2.0;
dy2:=(y0 - y2 ) / 2.0;
// Convert angle from degrees to radians
cos_a:=Cos(angle );
sin_a:=Sin(angle );
// Calculate (x1, y1)
x1:= cos_a * dx2 + sin_a * dy2;
y1:=-sin_a * dx2 + cos_a * dy2;
// Ensure radii are large enough
prx:=rx * rx;
pry:=ry * ry;
px1:=x1 * x1;
py1:=y1 * y1;
// Check that radii are large enough
radii_check:=px1 / prx + py1 / pry;
if radii_check > 1.0 then
begin
rx :=sqrt(radii_check ) * rx;
ry :=sqrt(radii_check ) * ry;
prx:=rx * rx;
pry:=ry * ry;
if radii_check > 10.0 then
m_radii_ok:=false;
end;
// Calculate (cx1, cy1)
if large_arc_flag = sweep_flag then
sign:=-1.0
else
sign:=1.0;
sq:=(prx * pry - prx * py1 - pry * px1 ) / (prx * py1 + pry * px1 );
if sq < 0 then
coef:=sign * sqrt(0 )
else
coef:=sign * sqrt(sq );
cx1:=coef * ((rx * y1 ) / ry );
cy1:=coef * -((ry * x1 ) / rx );
// Calculate (cx, cy) from (cx1, cy1)
sx2:=(x0 + x2 ) / 2.0;
sy2:=(y0 + y2 ) / 2.0;
cx :=sx2 + (cos_a * cx1 - sin_a * cy1 );
cy :=sy2 + (sin_a * cx1 + cos_a * cy1 );
// Calculate the start_angle (angle1) and the sweep_angle (dangle)
ux:= (x1 - cx1 ) / rx;
uy:= (y1 - cy1 ) / ry;
vx:=(-x1 - cx1 ) / rx;
vy:=(-y1 - cy1 ) / ry;
// Calculate the angle start
n:=sqrt(ux * ux + uy * uy );
p:=ux; // (1 * ux ) + (0 * uy )
if uy < 0 then
sign:=-1.0
else
sign:=1.0;
v:=p / n;
if v < -1.0 then
v:=-1.0;
if v > 1.0 then
v:=1.0;
start_angle:=sign * ArcCos(v );
// Calculate the sweep angle
n:=sqrt((ux * ux + uy * uy ) * (vx * vx + vy * vy ) );
p:=ux * vx + uy * vy;
if ux * vy - uy * vx < 0 then
sign:=-1.0
else
sign:=1.0;
v:=p / n;
if v < -1.0 then
v:=-1.0;
if v > 1.0 then
v:=1.0;
sweep_angle:=sign * ArcCos(v );
if (not sweep_flag ) and
(sweep_angle > 0 ) then
sweep_angle:=sweep_angle - pi * 2.0
else
if sweep_flag and
(sweep_angle < 0 ) then
sweep_angle:=sweep_angle + pi * 2.0;
// We can now build and transform the resulting arc
inherited init(0.0 ,0.0 ,rx ,ry ,start_angle ,sweep_angle );
mtx.Construct(angle );
trn.Construct(cx ,cy );
mtx.multiply(@trn );
i:=2;
while i < num_vertices - 2 do
begin
//mtx.transform(@m_arc.vertices[i ] ,@m_arc.vertices[i + 1 ] );
mtx.transform(
@mtx ,
double_ptr(ptrcomp(vertices ) + i * sizeof(double ) ) ,
double_ptr(ptrcomp(vertices ) + (i + 1 ) * sizeof(double ) ) );
inc(i ,2 );
end;
// We must make sure that the starting and ending points
// exactly coincide with the initial (x0,y0) and (x2,y2)
vertices[0 ]:=x0;
vertices[1 ]:=y0;
if num_vertices > 2 then
begin
vertices[num_vertices - 2 ]:=x2;
vertices[num_vertices - 1 ]:=y2;
end;
end;
{ RADII_OK }
function bezier_arc_svg.radii_ok;
begin
result:=m_radii_ok;
end;
END.