Graphics-Penplotter-GcodeXY
view release on metacpan or search on metacpan
lib/Graphics/Penplotter/GcodeXY/Geometry2D.pm view on Meta::CPAN
sub _transform ($self, $npts, $ptsref) {
foreach my $k ( 0 .. $npts - 1 ) {
my $tmp =
$self->{CTM}->[0][0] * $ptsref->[ 2 * $k ] +
$self->{CTM}->[0][1] * $ptsref->[ 2 * $k + 1 ] +
$self->{CTM}->[0][2];
$ptsref->[ 2 * $k + 1 ] =
$self->{CTM}->[1][0] * $ptsref->[ 2 * $k ] +
$self->{CTM}->[1][1] * $ptsref->[ 2 * $k + 1 ] +
$self->{CTM}->[1][2];
$ptsref->[ 2 * $k ] = $tmp;
}
return 1;
}
# ===========================================================================
# ARC / FILLET GEOMETRY HELPERS
# ===========================================================================
# Signed cross product of two 2-D vectors.
sub _cross2 ($self, $v1x, $v1y, $v2x, $v2y) {
return ( $v1x * $v2y - $v2x * $v1y );
}
# Angle subtended by two vectors: cos(a) = u·v / (âuâ·âvâ).
sub _dot2 ($self, $ux, $uy, $vx, $vy) {
my $d = sqrt( ( $ux*$ux + $uy*$uy ) * ( $vx*$vx + $vy*$vy ) );
return 0.0 if $d == 0.0;
return acos( ( $ux*$vx + $uy*$vy ) / $d );
}
# Find a,b,c for Ax + By + C = 0 through p1 and p2.
sub _linecoefs ($self, $p1x, $p1y, $p2x, $p2y) {
my $c = ( $p2x * $p1y ) - ( $p1x * $p2y );
my $a = $p2y - $p1y;
my $b = $p1x - $p2x;
return ( $a, $b, $c );
}
# Return signed distance from line Ax + By + C = 0 to point P.
sub _linetopoint ($self, $a, $b, $c, $px, $py) {
my $d = sqrt( $a*$a + $b*$b );
return 0.0 if $d == 0.0;
return ( $a*$px + $b*$py + $c ) / $d;
}
# Given line ax + by + c = 0 and point p, find the foot of the perpendicular from p.
sub _pointperp ($self, $a, $b, $c, $px, $py) {
my ( $x, $y ) = ( 0.0, 0.0 );
my $d = $a*$a + $b*$b;
my $cp = $a*$py - $b*$px;
if ( $d != 0.0 ) {
$x = ( -$a*$c - $b*$cp ) / $d;
$y = ( $a*$cp - $b*$c ) / $d;
}
return ( $x, $y );
}
# Compute a circular arc fillet between lines L1 (p1..p2) and L2 (p3..p4) with radius $r.
# Returns the 8 clipped endpoint coordinates plus the arc centre and angle, or undef on failure.
# Miller, "Joining Two Lines with a Circular Arc Fillet," Graphics Gems III.
sub _fillet ($self, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $p4x, $p4y, $r) {
my ( $a1, $b1, $c1, $a2, $b2, $c2, $c1p, $c2p, $d1, $d2, $xa, $xb, $ya, $yb, $d, $rr );
my ( $mpx, $mpy, $pcx, $pcy, $gv1x, $gv1y, $gv2x, $gv2y, $xc, $yc, $pa, $aa );
( $a1, $b1, $c1 ) = $self->_linecoefs( $p1x, $p1y, $p2x, $p2y );
( $a2, $b2, $c2 ) = $self->_linecoefs( $p3x, $p3y, $p4x, $p4y );
if ( ( $a1 * $b2 ) == ( $a2 * $b1 ) ) { return (undef) } # parallel or coincident
$mpx = ( $p3x + $p4x ) / 2.0;
$mpy = ( $p3y + $p4y ) / 2.0;
$d1 = $self->_linetopoint( $a1, $b1, $c1, $mpx, $mpy );
if ( $d1 == 0.0 ) { return (undef) x 12 }
$mpx = ( $p1x + $p2x ) / 2.0;
$mpy = ( $p1y + $p2y ) / 2.0;
$d2 = $self->_linetopoint( $a2, $b2, $c2, $mpx, $mpy );
if ( $d2 == 0.0 ) { return (undef) x 12 }
$rr = ( $d1 <= 0.0 ) ? -$r : $r;
$c1p = $c1 - $rr * sqrt( $a1*$a1 + $b1*$b1 );
$rr = ( $d2 <= 0.0 ) ? -$r : $r;
$c2p = $c2 - $rr * sqrt( $a2*$a2 + $b2*$b2 );
$d = $a1*$b2 - $a2*$b1;
$xc = ( $c2p*$b1 - $c1p*$b2 ) / $d;
$yc = ( $c1p*$a2 - $c2p*$a1 ) / $d;
$pcx = $xc;
$pcy = $yc;
( $xa, $ya ) = $self->_pointperp( $a1, $b1, $c1, $pcx, $pcy );
( $xb, $yb ) = $self->_pointperp( $a2, $b2, $c2, $pcx, $pcy );
$p2x = $xa; $p2y = $ya;
$p3x = $xb; $p3y = $yb;
$gv1x = $xa - $xc; $gv1y = $ya - $yc;
$gv2x = $xb - $xc; $gv2y = $yb - $yc;
$pa = atan2( $gv1y, $gv1x );
$aa = $self->_dot2( $gv1x, $gv1y, $gv2x, $gv2y );
if ( $self->_cross2( $gv1x, $gv1y, $gv2x, $gv2y ) < 0.0 ) { $aa = -$aa }
return ( $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $p4x, $p4y,
$xc, $yc, $pa * $R2D, $aa * $R2D );
}
# ===========================================================================
# FLOATING-POINT UTILITIES
# ===========================================================================
# Floating-point absolute value.
sub _fabs ($val) {
return ( $val >= 0.0 ) ? $val : -$val;
}
# Floating-point equality within $EPSILON.
sub _feq ($x, $y) {
return ( _fabs( $x - $y ) < $EPSILON ) ? 1 : 0;
}
# Square of the Euclidean distance between two points.
sub _calc_sq_distance ($x1, $y1, $x2, $y2) {
return ( $x2 - $x1 )**2 + ( $y2 - $y1 )**2;
}
# ===========================================================================
# UNIT CONVERSION
lib/Graphics/Penplotter/GcodeXY/Geometry2D.pm view on Meta::CPAN
# ===========================================================================
# ANGLE CONVERSION (public utility functions)
# ===========================================================================
# Convert degrees to radians.
sub radians ($deg) { return $deg * $D2R }
# Convert radians to degrees.
sub degrees ($rad) { return $rad / $D2R }
# ===========================================================================
# SEGMENT INTERSECTION (Cramer's Rule)
# ===========================================================================
# Return the parametric intersection parameter t of two line segments,
# or 0 if they do not intersect. Touching segments are not considered to intersect.
#
# Theory (parametric line representation):
# P = P1 + k(P2-P1) Q = P3 + l(P4-P3)
# Intersection when P=Q:
# k = [ (x4-x3)(y1-y3) - (y4-y3)(x1-x3) ] / denom
# l = [ (x2-x1)(y1-y3) - (y2-y1)(x1-x3) ] / denom
# where denom = (y4-y3)(x2-x1) - (x4-x3)(y2-y1)
# Intersection is valid when both k and l are in (0,1).
sub _getsegintersect ($self, $p0x, $p0y, $p1x, $p1y, $p2x, $p2y, $p3x, $p3y) {
my ( $s02x, $s02y, $s10x, $s10y, $s32x, $s32y, $s_numer, $t_numer, $denom );
$s10x = $p1x - $p0x;
$s10y = $p1y - $p0y;
$s32x = $p3x - $p2x;
$s32y = $p3y - $p2y;
$denom = $s10x * $s32y - $s32x * $s10y;
return 0 if $denom == 0; # collinear
my $denomPositive = ( $denom > 0 );
$s02x = $p0x - $p2x;
$s02y = $p0y - $p2y;
$s_numer = $s10x * $s02y - $s10y * $s02x;
return 0 if ( ( $s_numer < 0 ) == $denomPositive );
$t_numer = $s32x * $s02y - $s32y * $s02x;
return 0 if ( ( $t_numer < 0 ) == $denomPositive );
return 0 if ( ( $s_numer > $denom ) == $denomPositive
|| ( $t_numer > $denom ) == $denomPositive );
return $t_numer / $denom;
}
# ===========================================================================
# LIANG-BARSKY LINE CLIPPING
# ===========================================================================
# Clip a line segment to an axis-aligned rectangle.
#
# ($x1,$y1,$x2,$y2,$info) =
# $obj->_LiangBarsky($botx,$boty,$topx,$topy, $x0src,$y0src,$x1src,$y1src);
#
# $info values:
# 1 entire segment inside boundary
# 2 entirely outside (returns -1,-1,-1,-1,2)
# 3 start inside, end clipped
# 4 end inside, start clipped
# 5 both endpoints outside but interior intersects
#
# Algorithm by Daniel White (skytopia.com), bug-fixed and translated to Perl.
sub _LiangBarsky ($self, $botx, $boty, $topx, $topy, $x0src, $y0src, $x1src, $y1src) {
my $t0 = 0.0;
my $t1 = 1.0;
my $xdelta = $x1src - $x0src;
my $ydelta = $y1src - $y0src;
my ( $p, $q, $r );
my $info = 0;
foreach my $edge ( 0 .. 3 ) {
if ( $edge == 0 ) { $p = -$xdelta; $q = -( $botx - $x0src ) }
if ( $edge == 1 ) { $p = $xdelta; $q = $topx - $x0src }
if ( $edge == 2 ) { $p = -$ydelta; $q = -( $boty - $y0src ) }
if ( $edge == 3 ) { $p = $ydelta; $q = $topy - $y0src }
if ( $p == 0 && $q < 0 ) {
return ( -1, -1, -1, -1, 2 ); # parallel and outside
}
if ( $p < 0 ) {
$r = 1.0 * $q / $p;
return ( -1, -1, -1, -1, 2 ) if $r > $t1;
$t0 = $r if $r > $t0; # clip start
}
elsif ( $p > 0 ) {
$r = 1.0 * $q / $p;
return ( -1, -1, -1, -1, 2 ) if $r < $t0;
$t1 = $r if $r < $t1; # clip end
}
}
$info = 1 if $t0 == 0.0 && $t1 == 1.0;
$info = 3 if $t0 == 0.0 && $t1 != 1.0;
$info = 4 if $t0 != 0.0 && $t1 == 1.0;
$info = 5 if $t0 != 0.0 && $t1 != 1.0;
return (
$x0src + $t0 * $xdelta,
$y0src + $t0 * $ydelta,
$x0src + $t1 * $xdelta,
$y0src + $t1 * $ydelta,
$info,
);
}
1;
__END__
=head1 NAME
Graphics::Penplotter::GcodeXY::Geometry2D - Geometric primitives and transformations for GcodeXY
=head1 SYNOPSIS
$g->moveto(10, 20);
$g->line(30, 40);
$g->circle(50, 50, 10);
$g->rotate(45);
$g->arc(0, 0, 5, 0, 180);
=head1 DESCRIPTION
( run in 1.959 second using v1.01-cache-2.11-cpan-8f98c5d2c55 )