Graphics-Penplotter-GcodeXY
view release on metacpan or search on metacpan
# Centre of image (s=0, t=0): looking at phi=pi, beta=pi/4
# Ray from (5,0,5) in dir (-cos45,0,-cos45) = (-s2,0,-s2)
# Cylinder hit at t=4*sqrt2: M=(1,0,1)
# Normal at M = (1,0,0)
# Reflected: (-s2,0,-s2) -> (s2,0,-s2) [z unchanged for vertical cylinder]
# Paper hit: t_paper = -1/(-s2) = sqrt2; P=(1+1,0,0) = (2,0)
{
my %cfg = ana('_ana_build_config', 0,0, 1);
# Image bounding box 0..100 x 0..100; centre maps to (s=0,t=0)
my ($px, $py) = ana('_ana_transform_point',
50, 50, # image point at centre
0, 0, 100, 100,
%cfg);
ok defined $px, 'transform: centre of image returns defined px';
ok defined $py, 'transform: centre of image returns defined py';
ok abs($px - 2.0) < 1e-6, 'transform: image centre maps to px=2';
ok abs($py - 0.0) < 1e-6, 'transform: image centre maps to py=0';
# Different horizontal positions give symmetric (same px, opposite py) results
my ($px_l, $py_l) = ana('_ana_transform_point',
25, 50, 0,0,100,100, %cfg); # left quarter
my ($px_r, $py_r) = ana('_ana_transform_point',
75, 50, 0,0,100,100, %cfg); # right quarter
ok defined $px_l && defined $px_r, 'transform: off-centre points return values';
ok abs($py_l + $py_r) < 1e-6, 'transform: left/right image points antisymmetric in y';
# The centre point (px=2,py=0) is beyond the cylinder surface (R=1), so
# the reflected ray travelled outward -- verify px > R
ok $px > 1.0, 'transform: paper point is beyond cylinder surface';
# A point at top of image (v=0 => t=-0.5) looks more upward => may still hit
my ($px_top) = ana('_ana_transform_point',
50, 0, 0,0,100,100, %cfg);
ok defined $px_top, 'transform: top of image maps to valid point';
# Degenerate bbox: should return ()
my @nil = ana('_ana_transform_point', 5,5, 0,0,0,0, %cfg);
ok !@nil, 'transform: zero-size bbox returns empty list';
# Very wide angle (s = 10) should miss the cylinder
# Build a config with very large ang_rad so s=0.5 points sideways
my %cfg_wide = ana('_ana_build_config', 0,0, 1, angle_range => 360);
my @far = ana('_ana_transform_point', 95, 50, 0,0,100,100, %cfg_wide);
# With 360 degree range and s=0.45 the ray goes nearly sideways -- may or
# may not hit depending on geometry; just confirm the function doesn't die
ok 1, 'transform: extreme angle handled without error';
# Reflected ray going upward: cylinder directly above observer? Hard to
# trigger naturally, but we at least confirm return-type consistency.
my @result = ana('_ana_transform_point', 50, 50, 0,0,100,100, %cfg);
ok scalar(@result) == 0 || scalar(@result) == 2,
'transform: return is always 0 or 2 values';
}
# ==========================================================================
# SECTION 5: _ana_resample_segment
# ==========================================================================
{
# Trivial: identical points returns the endpoint
my @r0 = ana('_ana_resample_segment', 5,5, 5,5, 1.0);
is scalar @r0, 1, 'resample: zero-length returns 1 point';
ok abs($r0[0][0] - 5) < 1e-9, 'resample: zero-length point x=5';
# Segment (0,0)-(10,0) step=3: ceil(10/3)=4 intervals => 4 points
my @r1 = ana('_ana_resample_segment', 0,0, 10,0, 3);
is scalar @r1, 4, 'resample: 10/step=3 gives 4 pts';
ok abs($r1[-1][0] - 10) < 1e-9, 'resample: last point is endpoint';
ok abs($r1[0][0] - 2.5) < 1e-9, 'resample: first sample at 10/4=2.5';
# Step larger than segment => 1 point (just the endpoint)
my @r2 = ana('_ana_resample_segment', 0,0, 3,4, 100);
is scalar @r2, 1, 'resample: step>length gives 1 point';
ok abs($r2[0][0] - 3) < 1e-9, 'resample: endpoint x=3';
ok abs($r2[0][1] - 4) < 1e-9, 'resample: endpoint y=4';
# Diagonal: length=5, step=1 => 5 points, all on the line y=x*(4/3)
my @r3 = ana('_ana_resample_segment', 0,0, 3,4, 1);
is scalar @r3, 5, 'resample: diagonal 5 pts at step=1';
for my $pt (@r3) {
# Each point should satisfy y = (4/3)*x (the line from (0,0) to (3,4))
my $expected_y = ($pt->[0] / 3.0) * 4.0;
ok abs($pt->[1] - $expected_y) < 1e-9,
'resample: diagonal pt on line';
}
}
# ==========================================================================
# SECTION 6: anamorphic() integration
# ==========================================================================
{
# ------------------------------------------------------------------
# Build a diagonal path from (0,0) to (100,50), giving a well-defined
# 100x50 bounding box. Two 'l' segments so resampling is exercised.
# ------------------------------------------------------------------
my $g = MockPlotter->new;
$g->_addpath('m', 0, 0, 0, 0);
$g->_addpath('l', 0, 0, 50, 25);
$g->_addpath('l', 50, 25,100, 50);
$g->log_clear;
my $ret = $g->anamorphic(0, 0, 1, obs_dist => 10, obs_height => 10, step => 10);
is $ret, 1, 'anamorphic: returns 1 on success';
my @log = $g->log_get;
ok grep({ $_ eq 'STROKE' } @log), 'anamorphic: calls stroke';
# After stroke() the mock does not clear psegments (no real newpath),
# so we can inspect what anamorphic() wrote back before stroke() emptied it.
# We capture via a stroke override during the call instead -- but that is
# complex. Instead we verify structural output via a spy on psegments
# written inside anamorphic BEFORE stroke() is called, by using a
# subclassed mock that captures the snapshot.
# Simpler: since MockPlotter::stroke just logs 'STROKE' and does NOT clear
# psegments (no newpath call), psegments still holds the transformed path
# after the method returns.
my @ns = @{ $g->{psegments} };
ok @ns >= 1, 'anamorphic: produces transformed segments';
ok grep({ $_->{key} eq 'm' } @ns), 'anamorphic: output contains a moveto segment';
ok grep({ $_->{key} eq 'l' } @ns), 'anamorphic: output contains line segments';
# All transformed coordinates must be finite numbers
my $all_finite = 1;
for my $s (@ns) {
for my $k (qw(sx sy dx dy)) {
unless (defined $s->{$k} && $s->{$k} == $s->{$k}) {
$all_finite = 0; last;
}
}
last unless $all_finite;
}
ok $all_finite, 'anamorphic: all output coords are finite';
# ------------------------------------------------------------------
( run in 0.803 second using v1.01-cache-2.11-cpan-524268b4103 )