mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-27 19:33:49 +02:00
639 lines
12 KiB
ObjectPascal
639 lines
12 KiB
ObjectPascal
unit
|
|
interactive_polygon_ ;
|
|
|
|
INTERFACE
|
|
|
|
{$I agg_mode.inc }
|
|
|
|
uses
|
|
Math ,
|
|
agg_basics ,
|
|
agg_conv_stroke ,
|
|
agg_ellipse ,
|
|
agg_vertex_source ;
|
|
|
|
{ TYPES DEFINITION }
|
|
type
|
|
simple_polygon_vertex_source = object(vertex_source )
|
|
m_polygon : double_ptr;
|
|
|
|
m_num_points ,
|
|
m_vertex : unsigned;
|
|
|
|
m_roundoff ,
|
|
m_close : boolean;
|
|
|
|
constructor Construct(polygon : double_ptr; np : unsigned; roundoff : boolean = false; close : boolean = true );
|
|
|
|
procedure close_(f : boolean );
|
|
function _close : boolean;
|
|
|
|
procedure rewind(path_id : unsigned ); virtual;
|
|
function vertex(x ,y : double_ptr ) : unsigned; virtual;
|
|
|
|
end;
|
|
|
|
interactive_polygon = object(vertex_source )
|
|
m_polygon : double_ptr;
|
|
m_num_points : unsigned;
|
|
|
|
m_node ,
|
|
m_edge : int;
|
|
|
|
m_vs : simple_polygon_vertex_source;
|
|
|
|
m_stroke : conv_stroke;
|
|
m_ellipse : ellipse;
|
|
|
|
m_point_radius : double;
|
|
m_status : unsigned;
|
|
|
|
m_dx ,
|
|
m_dy : double;
|
|
|
|
constructor Construct(np : unsigned; point_radius : double );
|
|
destructor Destruct; virtual;
|
|
|
|
function num_points : unsigned;
|
|
|
|
function xn(n : unsigned ) : double;
|
|
function yn(n : unsigned ) : double;
|
|
|
|
function xn_ptr(n : unsigned ) : double_ptr;
|
|
function yn_ptr(n : unsigned ) : double_ptr;
|
|
|
|
function polygon : double_ptr;
|
|
|
|
function _node : int;
|
|
procedure node_(n : int );
|
|
|
|
function _close : boolean;
|
|
procedure close_(f : boolean );
|
|
|
|
procedure rewind(path_id : unsigned ); virtual;
|
|
function vertex(x ,y : double_ptr ) : unsigned; virtual;
|
|
|
|
function on_mouse_move(x ,y : double ) : boolean;
|
|
|
|
function on_mouse_button_down(x ,y : double ) : boolean;
|
|
function on_mouse_button_up(x ,y : double ) : boolean;
|
|
|
|
function check_edge(i : unsigned; x ,y : double ) : boolean;
|
|
|
|
function point_in_polygon(tx ,ty : double ) : boolean;
|
|
|
|
end;
|
|
|
|
{ GLOBAL PROCEDURES }
|
|
|
|
|
|
IMPLEMENTATION
|
|
{ LOCAL VARIABLES & CONSTANTS }
|
|
{ UNIT IMPLEMENTATION }
|
|
{ CONSTRUCT }
|
|
constructor simple_polygon_vertex_source.Construct;
|
|
begin
|
|
inherited Construct;
|
|
|
|
m_polygon :=polygon;
|
|
m_num_points:=np;
|
|
|
|
m_vertex :=0;
|
|
m_roundoff:=roundoff;
|
|
m_close :=close;
|
|
|
|
end;
|
|
|
|
{ CLOSE_ }
|
|
procedure simple_polygon_vertex_source.close_;
|
|
begin
|
|
m_close:=f;
|
|
|
|
end;
|
|
|
|
{ _CLOSE }
|
|
function simple_polygon_vertex_source._close;
|
|
begin
|
|
result:=m_close;
|
|
|
|
end;
|
|
|
|
{ REWIND }
|
|
procedure simple_polygon_vertex_source.rewind;
|
|
begin
|
|
m_vertex:=0;
|
|
|
|
end;
|
|
|
|
{ VERTEX }
|
|
function simple_polygon_vertex_source.vertex;
|
|
begin
|
|
if m_vertex > m_num_points then
|
|
begin
|
|
result:=path_cmd_stop;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
if m_vertex = m_num_points then
|
|
begin
|
|
inc(m_vertex );
|
|
|
|
if m_close then
|
|
result:=path_cmd_end_poly or path_flags_close
|
|
else
|
|
result:=path_cmd_end_poly or 0;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
x^:=double_ptr(ptrcomp(m_polygon ) + (m_vertex * 2 ) * sizeof(double ) )^;
|
|
y^:=double_ptr(ptrcomp(m_polygon ) + (m_vertex * 2 + 1 ) * sizeof(double ) )^;
|
|
|
|
if m_roundoff then
|
|
begin
|
|
x^:=Floor(x^ ) + 0.5;
|
|
y^:=Floor(y^ ) + 0.5;
|
|
|
|
end;
|
|
|
|
inc(m_vertex );
|
|
|
|
if m_vertex = 1 then
|
|
result:=path_cmd_move_to
|
|
else
|
|
result:=path_cmd_line_to;
|
|
|
|
end;
|
|
|
|
{ CONSTRUCT }
|
|
constructor interactive_polygon.Construct;
|
|
begin
|
|
inherited Construct;
|
|
|
|
agg_getmem(pointer(m_polygon ) ,np * 2 * sizeof(double ) );
|
|
|
|
m_num_points:=np;
|
|
|
|
m_node:=-1;
|
|
m_edge:=-1;
|
|
|
|
m_vs.Construct (m_polygon ,m_num_points ,false );
|
|
m_stroke.Construct(@m_vs );
|
|
m_ellipse.Construct;
|
|
|
|
m_point_radius:=point_radius;
|
|
m_status :=0;
|
|
|
|
m_dx:=0.0;
|
|
m_dy:=0.0;
|
|
|
|
m_stroke.width_(1.0 );
|
|
|
|
end;
|
|
|
|
{ DESTRUCT }
|
|
destructor interactive_polygon.Destruct;
|
|
begin
|
|
agg_freemem(pointer(m_polygon ) ,m_num_points * 2 * sizeof(double ) );
|
|
|
|
m_stroke.Destruct;
|
|
|
|
end;
|
|
|
|
{ NUM_POINTS }
|
|
function interactive_polygon.num_points;
|
|
begin
|
|
result:=m_num_points;
|
|
|
|
end;
|
|
|
|
{ XN }
|
|
function interactive_polygon.xn;
|
|
begin
|
|
result:=double_ptr(ptrcomp(m_polygon ) + (n * 2 ) * sizeof(double ) )^;
|
|
|
|
end;
|
|
|
|
{ YN }
|
|
function interactive_polygon.yn;
|
|
begin
|
|
result:=double_ptr(ptrcomp(m_polygon ) + (n * 2 + 1 ) * sizeof(double ) )^;
|
|
|
|
end;
|
|
|
|
{ XN_PTR }
|
|
function interactive_polygon.xn_ptr;
|
|
begin
|
|
result:=double_ptr(ptrcomp(m_polygon ) + (n * 2 ) * sizeof(double ) );
|
|
|
|
end;
|
|
|
|
{ YN_PTR }
|
|
function interactive_polygon.yn_ptr;
|
|
begin
|
|
result:=double_ptr(ptrcomp(m_polygon ) + (n * 2 + 1 ) * sizeof(double ) );
|
|
|
|
end;
|
|
|
|
{ POLYGON }
|
|
function interactive_polygon.polygon;
|
|
begin
|
|
result:=m_polygon;
|
|
|
|
end;
|
|
|
|
{ _NODE }
|
|
function interactive_polygon._node;
|
|
begin
|
|
result:=m_node;
|
|
|
|
end;
|
|
|
|
{ NODE_ }
|
|
procedure interactive_polygon.node_;
|
|
begin
|
|
m_node:=n;
|
|
|
|
end;
|
|
|
|
{ _CLOSE }
|
|
function interactive_polygon._close;
|
|
begin
|
|
result:=m_vs._close;
|
|
|
|
end;
|
|
|
|
{ CLOSE_ }
|
|
procedure interactive_polygon.close_;
|
|
begin
|
|
m_vs.close_(f );
|
|
|
|
end;
|
|
|
|
{ REWIND }
|
|
procedure interactive_polygon.rewind;
|
|
begin
|
|
m_status:=0;
|
|
|
|
m_stroke.rewind(0 );
|
|
|
|
end;
|
|
|
|
{ VERTEX }
|
|
function interactive_polygon.vertex;
|
|
var
|
|
r : double;
|
|
|
|
cmd : unsigned;
|
|
|
|
begin
|
|
cmd:=path_cmd_stop;
|
|
r :=m_point_radius;
|
|
|
|
if m_status = 0 then
|
|
begin
|
|
cmd:=m_stroke.vertex(x ,y );
|
|
|
|
if not is_stop(cmd ) then
|
|
begin
|
|
result:=cmd;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
if (m_node >= 0 ) and
|
|
(m_node = int(m_status ) ) then
|
|
r:=r * 1.2;
|
|
|
|
m_ellipse.init(xn(m_status ) ,yn(m_status ) ,r ,r ,32 );
|
|
|
|
inc(m_status );
|
|
|
|
end;
|
|
|
|
cmd:=m_ellipse.vertex(x ,y );
|
|
|
|
if not is_stop(cmd ) then
|
|
begin
|
|
result:=cmd;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
if m_status >= m_num_points then
|
|
begin
|
|
result:=path_cmd_stop;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
if (m_node >= 0 ) and
|
|
(m_node = int(m_status ) ) then
|
|
r:=r * 1.2;
|
|
|
|
m_ellipse.init(xn(m_status ) ,yn(m_status ) ,r ,r ,32 );
|
|
|
|
inc(m_status );
|
|
|
|
result:=m_ellipse.vertex(x ,y );
|
|
|
|
end;
|
|
|
|
{ ON_MOUSE_MOVE }
|
|
function interactive_polygon.on_mouse_move;
|
|
var
|
|
ret : boolean;
|
|
|
|
i ,n1 ,n2 : unsigned;
|
|
|
|
dx ,dy : double;
|
|
|
|
begin
|
|
ret:=false;
|
|
|
|
if m_node = int(m_num_points ) then
|
|
begin
|
|
dx:=x - m_dx;
|
|
dy:=y - m_dy;
|
|
|
|
for i:=0 to m_num_points - 1 do
|
|
begin
|
|
xn_ptr(i )^:=xn_ptr(i )^ + dx;
|
|
yn_ptr(i )^:=yn_ptr(i )^ + dy;
|
|
|
|
end;
|
|
|
|
m_dx:=x;
|
|
m_dy:=y;
|
|
|
|
ret:=true;
|
|
|
|
end
|
|
else
|
|
if m_edge >= 0 then
|
|
begin
|
|
n1:=m_edge;
|
|
n2:=(n1 + m_num_points - 1 ) mod m_num_points;
|
|
|
|
dx:=x - m_dx;
|
|
dy:=y - m_dy;
|
|
|
|
xn_ptr(n1 )^:=xn_ptr(n1 )^ + dx;
|
|
yn_ptr(n1 )^:=yn_ptr(n1 )^ + dy;
|
|
xn_ptr(n2 )^:=xn_ptr(n2 )^ + dx;
|
|
yn_ptr(n2 )^:=yn_ptr(n2 )^ + dy;
|
|
|
|
m_dx:=x;
|
|
m_dy:=y;
|
|
|
|
ret:=true;
|
|
|
|
end
|
|
else
|
|
if m_node >= 0 then
|
|
begin
|
|
xn_ptr(m_node )^:=x - m_dx;
|
|
yn_ptr(m_node )^:=y - m_dy;
|
|
|
|
ret:=true;
|
|
|
|
end;
|
|
|
|
result:=ret;
|
|
|
|
end;
|
|
|
|
{ ON_MOUSE_BUTTON_DOWN }
|
|
function interactive_polygon.on_mouse_button_down;
|
|
var
|
|
i : unsigned;
|
|
|
|
ret : boolean;
|
|
|
|
begin
|
|
ret:=false;
|
|
|
|
m_node:=-1;
|
|
m_edge:=-1;
|
|
|
|
for i:=0 to m_num_points - 1 do
|
|
if Sqrt((x - xn(i ) ) * (x - xn(i ) ) + (y - yn(i ) ) * (y - yn(i ) ) ) < m_point_radius then
|
|
begin
|
|
m_dx:=x - xn(i );
|
|
m_dy:=y - yn(i );
|
|
|
|
m_node:=int(i );
|
|
|
|
ret:=true;
|
|
|
|
break;
|
|
|
|
end;
|
|
|
|
if not ret then
|
|
for i:=0 to m_num_points - 1 do
|
|
if check_edge(i ,x ,y ) then
|
|
begin
|
|
m_dx:=x;
|
|
m_dy:=y;
|
|
|
|
m_edge:=int(i );
|
|
|
|
ret:=true;
|
|
|
|
break;
|
|
|
|
end;
|
|
|
|
if not ret then
|
|
if point_in_polygon(x ,y ) then
|
|
begin
|
|
m_dx:=x;
|
|
m_dy:=y;
|
|
|
|
m_node:=int(m_num_points );
|
|
|
|
ret:=true;
|
|
|
|
end;
|
|
|
|
result:=ret;
|
|
|
|
end;
|
|
|
|
{ ON_MOUSE_BUTTON_UP }
|
|
function interactive_polygon.on_mouse_button_up;
|
|
var
|
|
ret : boolean;
|
|
|
|
begin
|
|
ret:=(m_node >= 0 ) or (m_edge >= 0 );
|
|
|
|
m_node:=-1;
|
|
m_edge:=-1;
|
|
result:=ret;
|
|
|
|
end;
|
|
|
|
{ CHECK_EDGE }
|
|
function interactive_polygon.check_edge;
|
|
var
|
|
ret : boolean;
|
|
|
|
n1 ,n2 : unsigned;
|
|
|
|
x1 ,y1 ,x2 ,y2 ,dx ,dy ,x3 ,y3 ,x4 ,y4 ,den ,u1 ,xi ,yi : double;
|
|
|
|
begin
|
|
ret:=false;
|
|
|
|
n1:= i;
|
|
n2:= (i + m_num_points - 1 ) mod m_num_points;
|
|
|
|
x1:=xn(n1 );
|
|
y1:=yn(n1 );
|
|
x2:=xn(n2 );
|
|
y2:=yn(n2 );
|
|
|
|
dx:=x2 - x1;
|
|
dy:=y2 - y1;
|
|
|
|
if Sqrt(dx * dx + dy * dy ) > 0.0000001 then
|
|
begin
|
|
x3:=x;
|
|
y3:=y;
|
|
x4:=x3 - dy;
|
|
y4:=y3 + dx;
|
|
|
|
den:=(y4 - y3 ) * (x2 - x1 ) - (x4 - x3 ) * (y2 - y1 );
|
|
u1 :=((x4 - x3 ) * (y1 - y3 ) - (y4 - y3 ) * (x1 - x3 ) ) / den;
|
|
|
|
xi:=x1 + u1 * (x2 - x1);
|
|
yi:=y1 + u1 * (y2 - y1);
|
|
|
|
dx:=xi - x;
|
|
dy:=yi - y;
|
|
|
|
if (u1 > 0.0 ) and
|
|
(u1 < 1.0 ) and
|
|
(Sqrt(dx * dx + dy * dy ) <= m_point_radius ) then
|
|
ret:=true;
|
|
|
|
end;
|
|
|
|
result:=ret;
|
|
|
|
end;
|
|
|
|
{ POINT_IN_POLYGON }
|
|
//======= Crossings Multiply algorithm of InsideTest ========================
|
|
//
|
|
// By Eric Haines, 3D/Eye Inc, erich@eye.com
|
|
//
|
|
// This version is usually somewhat faster than the original published in
|
|
// Graphics Gems IV; by turning the division for testing the X axis crossing
|
|
// into a tricky multiplication test this part of the test became faster,
|
|
// which had the additional effect of making the test for "both to left or
|
|
// both to right" a bit slower for triangles than simply computing the
|
|
// intersection each time. The main increase is in triangle testing speed,
|
|
// which was about 15% faster; all other polygon complexities were pretty much
|
|
// the same as before. On machines where division is very expensive (not the
|
|
// case on the HP 9000 series on which I tested) this test should be much
|
|
// faster overall than the old code. Your mileage may (in fact, will) vary,
|
|
// depending on the machine and the test data, but in general I believe this
|
|
// code is both shorter and faster. This test was inspired by unpublished
|
|
// Graphics Gems submitted by Joseph Samosky and Mark Haigh-Hutchinson.
|
|
// Related work by Samosky is in:
|
|
//
|
|
// Samosky, Joseph, "SectionView: A system for interactively specifying and
|
|
// visualizing sections through three-dimensional medical image data",
|
|
// M.S. Thesis, Department of Electrical Engineering and Computer Science,
|
|
// Massachusetts Institute of Technology, 1993.
|
|
//
|
|
// Shoot a test ray along +X axis. The strategy is to compare vertex Y values
|
|
// to the testing point's Y and quickly discard edges which are entirely to one
|
|
// side of the test ray. Note that CONVEX and WINDING code can be added as
|
|
// for the CrossingsTest() code; it is left out here for clarity.
|
|
//
|
|
// Input 2D polygon _pgon_ with _numverts_ number of vertices and test point
|
|
// _point_, returns 1 if inside, 0 if outside.
|
|
function interactive_polygon.point_in_polygon;
|
|
var
|
|
j ,k : unsigned;
|
|
|
|
yflag0 ,yflag1 ,inside_flag : int;
|
|
|
|
vtx0 ,vty0 ,vtx1 ,vty1 : double;
|
|
|
|
begin
|
|
if m_num_points < 3 then
|
|
begin
|
|
result:=false;
|
|
|
|
exit;
|
|
|
|
end;
|
|
|
|
vtx0:=xn(m_num_points - 1 );
|
|
vty0:=yn(m_num_points - 1 );
|
|
|
|
// get test bit for above/below X axis
|
|
yflag0:=int(vty0 >= ty );
|
|
|
|
vtx1:=xn(0 );
|
|
vty1:=yn(0 );
|
|
|
|
inside_flag:=0;
|
|
|
|
for j:=1 to m_num_points do
|
|
begin
|
|
yflag1:=int(vty1 >= ty );
|
|
|
|
// Check if endpoints straddle (are on opposite sides) of X axis
|
|
// (i.e. the Y's differ); if so, +X ray could intersect this edge.
|
|
// The old test also checked whether the endpoints are both to the
|
|
// right or to the left of the test point. However, given the faster
|
|
// intersection point computation used below, this test was found to
|
|
// be a break-even proposition for most polygons and a loser for
|
|
// triangles (where 50% or more of the edges which survive this test
|
|
// will cross quadrants and so have to have the X intersection computed
|
|
// anyway). I credit Joseph Samosky with inspiring me to try dropping
|
|
// the "both left or both right" part of my code.
|
|
if yflag0 <> yflag1 then
|
|
// Check intersection of pgon segment with +X ray.
|
|
// Note if >= point's X; if so, the ray hits it.
|
|
// The division operation is avoided for the ">=" test by checking
|
|
// the sign of the first vertex wrto the test point; idea inspired
|
|
// by Joseph Samosky's and Mark Haigh-Hutchinson's different
|
|
// polygon inclusion tests.
|
|
if int((vty1 - ty ) * (vtx0 - vtx1 ) >= (vtx1 - tx ) * (vty0 - vty1 ) ) = yflag1 then
|
|
inside_flag:=inside_flag xor 1;
|
|
|
|
// Move to the next pair of vertices, retaining info as possible.
|
|
yflag0:=yflag1;
|
|
|
|
vtx0:=vtx1;
|
|
vty0:=vty1;
|
|
|
|
if j >= m_num_points then
|
|
k:=j - m_num_points
|
|
else
|
|
k:=j;
|
|
|
|
vtx1:=xn(k );
|
|
vty1:=yn(k );
|
|
|
|
end;
|
|
|
|
result:=inside_flag <> 0;
|
|
|
|
end;
|
|
|
|
END.
|