misc/Graph.pm
#========================================================================================
#  Graph.pm
#  Copyright (C) 2003-2019 Makoto Kamada
#
#  This file is part of the XEiJ (X68000 Emulator in Java).
#  You can use, modify and redistribute the XEiJ if the conditions are met.
#  Read the XEiJ License for more details.
#  https://stdkmd.net/xeij/
#========================================================================================

package Graph;

use strict;  #厳密な文法に従う
use warnings;  #警告を表示する
use utf8;  #UTF-8で記述する
#binmode STDOUT, ':encoding(cp932)';  #Shift_JISの代わりにcp932を使う
#binmode STDERR, ':encoding(cp932)';  #(Shift_JISでは'~'(\uff5e)が使えない)

use Math::Complex;  #Im

sub import {
  no strict 'refs';
  eval ('$' . __PACKAGE__ . '::OVERLOAD{""} = ""');
  *{__PACKAGE__ . '::()'} = sub {};
  *{__PACKAGE__ . '::(""'} = \&str;  #文字列化
  use strict 'refs';
}

#========================================================================

my $PI = 3.14159265358979323846264338328;

sub floor {
  my $x = int $_[0];
  $x <= $_[0] ? $x : $x - 1;
}

sub ceil {
  -floor (-$_[0]);
}

sub round {
  floor ($_[0] + 0.5);
}

sub rad {
  $_[0] * $PI / 180;
}

#========================================================================
sub new {
  my ($c, $w, $h, $l, $r, $b, $t) = @_;
  $w = round ($w // 80);
  $h = round ($h // 40);
  $r = $r // $w / 20;
  $l = $l // -$r;
  $t = $t // $h / 10;
  $b = $b // -$t;
  my $sx = $w / ($r - $l);
  my $sy = $h / ($b - $t);  #<0
  my $ox = (0 - $l) * $sx;
  my $oy = (0 - $t) * $sy;
  my $m = [];
  for (my $y = 0; $y <= $h; $y++) {
    push @$m, ' ' x ($w + 1);
  }
  bless {
    m => $m,
    w => $w,
    h => $h,
    l => $l,
    r => $r,
    b => $b,
    t => $t,
    ox => $ox,
    oy => $oy,
    sx => $sx,
    sy => $sy
  };
}

sub str {
  my ($g) = @_;
  join '', grep { $_ = "    //    $_\n"; } @{$g->{'m'}};
}

sub _pset {
  my ($g, $x, $y, $c) = @_;
  $x = round ($x);
  $y = round ($y);
  $c = $c // '*';
  $x >= 0 && $x <= $g->{'w'} && $y >= 0 && $y <= $g->{'h'} and vec ($g->{'m'}->[$y], $x, 8) = ord $c;
}

sub pset {
  my ($g, $x, $y, $c) = @_;
  $x = $g->{'ox'} + $g->{'sx'} * $x;
  $y = $g->{'oy'} + $g->{'sy'} * $y;
  $g->_pset ($x, $y, $c);
}

sub line {
  my ($g, $x1, $y1, $x2, $y2, $c) = @_;
  my $ox = $g->{'ox'};
  my $oy = $g->{'oy'};
  my $sx = $g->{'sx'};
  my $sy = $g->{'sy'};
  $x1 = $ox + $sx * $x1;
  $y1 = $oy + $sy * $y1;
  $x2 = $ox + $sx * $x2;
  $y2 = $oy + $sy * $y2;
  my $dx = $x2 - $x1;
  my $dy = $y2 - $y1;
  if ($dx == 0 && $dy == 0) {  #点
    $g->pset ($x1, $y1, $c);
  } elsif (abs ($dx) >= abs ($dy)) {  #横長
    my $i1 = round ($x1);
    my $i2 = round ($x2);
    if ($i1 <= $i2) {  #右向き
      if ($dy == 0) {  #水平線
        for (my $x = $i1; $x <= $i2; $x++) {
          $g->_pset ($x, $y1, $c);
        }
      } else {
        for (my $x = $i1; $x <= $i2; $x++) {
          my $y = $y1 + $dy * ($x - $x1) / $dx;
          $g->_pset ($x, $y, $c);
        }
      }
    } else {  #左向き
      if ($dy == 0) {  #水平線
        for (my $x = $i1; $x >= $i2; $x--) {
          $g->_pset ($x, $y1, $c);
        }
      } else {
        for (my $x = $i1; $x >= $i2; $x--) {
          my $y = $y1 + $dy * ($x - $x1) / $dx;
          $g->_pset ($x, $y, $c);
        }
      }
    }
  } else {  #縦長
    my $i1 = round ($y1);
    my $i2 = round ($y2);
    if ($i1 <= $i2) {  #下向き
      if ($dx == 0) {  #垂直線
        for (my $y = $i1; $y <= $i2; $y++) {
          $g->_pset ($x1, $y, $c);
        }
      } else {
        for (my $y = $i1; $y <= $i2; $y++) {
          my $x = $x1 + $dx * ($y - $y1) / $dy;
          $g->_pset ($x, $y, $c);
        }
      }
    } else {  #上向き
      if ($dx == 0) {  #垂直線
        for (my $y = $i1; $y >= $i2; $y--) {
          $g->_pset ($x1, $y, $c);
        }
      } else {
        for (my $y = $i1; $y >= $i2; $y--) {
          my $x = $x1 + $dx * ($y - $y1) / $dy;
          $g->_pset ($x, $y, $c);
        }
      }
    }
  }
}

sub circle {
  my ($g, $x, $y, $r, $c) = @_;
  for (my $i = 0; $i < 360; $i++) {
    my $s = rad ($i);
    my $t = rad ($i + 1);
    $g->line ($x + $r * cos $s, $y + $r * sin $s, $x + $r * cos $t, $y + $r * sin $t, $c);
  }
}

my $RESO = 1000;

#  a<=x<=bの範囲でy=f(x)を描く
sub func {
  my ($g, $f, $a, $b, $c) = @_;
  my $ox = $g->{'ox'};
  my $oy = $g->{'oy'};
  my $sx = $g->{'sx'};
  my $sy = $g->{'sy'};
  $a = $a // $g->{'l'};
  $b = $b // $g->{'r'};
  my $ia = round (($ox + $sx * $a) * $RESO);
  my $ib = round (($ox + $sx * $b) * $RESO);
  for (my $i = $ia; $i <= $ib; $i++) {
    my $x = $i / $RESO;
    my $y = undef;
    eval {
      $y = $f->(($x - $ox) / $sx);
    };
    defined ($y) && !Im ($y) and $g->_pset ($x, $oy + $sy * $y, $c);
  }
}

#  a<=t<=bの範囲でx=f(t),y=g(t)を描く
sub func2 {
  my ($G, $f, $g, $a, $b, $c) = @_;
  my $ox = $G->{'ox'};
  my $oy = $G->{'oy'};
  my $sx = $G->{'sx'};
  my $sy = $G->{'sy'};
  my $ia = round ($a * $RESO);
  my $ib = round ($b * $RESO);
  for (my $i = $ia; $i <= $ib; $i++) {
    my $t = $i / $RESO;
    my $x = undef;
    my $y = undef;
    eval {
      $x = $f->($t);
      $y = $g->($t);
    };
    defined ($x) && !Im ($x) && defined ($y) && !Im ($y) and $G->_pset ($ox + $sx * $x, $oy + $sy * $y, $c);
  }
}

sub grid {
  my ($g) = @_;
  my $l = $g->{'l'};
  my $r = $g->{'r'};
  my $b = $g->{'b'};
  my $t = $g->{'t'};
  my $ox = $g->{'ox'};
  my $oy = $g->{'oy'};
  my $sx = $g->{'sx'};
  my $sy = $g->{'sy'};
  for (my $x = ceil ($l / 4) * 4; $x <= $r; $x += 4) {
    $g->line ($x, $b, $x, $t, '|');
    for (my $y = ceil ($b); $y <= $t; $y++) {
      $g->pset ($x, $y, '+');
    }
  }
  for (my $y = ceil ($b / 4) * 4; $y <= $t; $y += 4) {
    $g->line ($l, $y, $r, $y, '-');
    for (my $x = ceil ($l); $x <= $r; $x++) {
      $g->pset ($x, $y, '+');
    }
  }
}

sub test {
  my $g = new Graph ();
  $g->line (5, 5, 35, 15);
  $g->circle (20, 10, 8);
  $g;
}

1;

__END__