Commits

Michele Bini committed 9278196

Made Math::Quat::mul functional; add quaternion operations; add view variables operations; maintain rotation angles within (-360..360]

  • Participants
  • Parent commits 843638c
  • Branches dev

Comments (0)

Files changed (1)

 sub deg2deg { fmod(shift(), 360) }
 sub rad2rad { fmod(shift(), $pi2) }
 
+sub UNIVERSAL::peekobj { require Data::Dumper; print STDERR caller() . ": " . Data::Dumper::Dumper($_[0]); $_[0] }
+sub UNIVERSAL::peek { require Data::Dumper; print STDERR caller() . ": $_[0]\n"; $_[0] }
+
+sub Math::Quat::new { my $p = shift; bless [ @_ ], $p }
+sub Math::Quat::unit() { Math::Quat->new(1,0,0,0) }
+sub Math::Quat::mul {
+  my ($x, $y) = @_;
+  Math::Quat->new(
+		  $$x[0] * $$y[0] - $$x[1] * $$y[1] - $$x[2] * $$y[2] - $$x[3] * $$y[3],
+		  $$x[0] * $$y[1] + $$x[1] * $$y[0] + $$x[2] * $$y[3] - $$x[3] * $$y[2],
+		  $$x[0] * $$y[2] - $$x[1] * $$y[3] + $$x[2] * $$y[0] + $$x[3] * $$y[1],
+		  $$x[0] * $$y[3] + $$x[1] * $$y[2] - $$x[2] * $$y[1] + $$x[3] * $$y[0]
+		 );
+}
+sub Math::Quat::div { my ($x, $y) = @_; $x->mul($y->inv) }
+
+# Return just the first coefficient of the product quaternion
+sub Math::Quat::mul_by_conjugate_a {
+  my ($x) = shift;
+  $$x[0]**2 + $$x[1]**2 + $$x[2]**2 + $$x[3]**2
+}
+sub Math::Quat::subtract {
+  my ($x, $y) = @_;
+  @$x = ($$x[0] - $$y[0],
+	 $$x[1] - $$y[1],
+	 $$x[2] - $$y[2],
+	 $$x[3] - $$y[3]);
+  $x
+}
+sub Math::Quat::mul_by_real {
+  my ($x, $y) = @_;
+  @$x = map { $_ * $y } @$x;
+  $x
+}
+sub Math::Quat::div_by_real {
+  my ($x, $y) = @_;
+  @$x = map { $_ / $y } @$x;
+  $x
+}
+sub Math::Quat::coniug {
+  my ($w, $x, $y, $z) = @{ shift() };
+  Math::Quat->new($w, -$x, -$y, -$z);
+}
+sub Math::Quat::inv {
+  my $q = shift;
+  $q->coniug->div_by_real($$q[0]**2 + $$q[1]**2 + $$q[2]**2 + $$q[3]**2);
+}
+sub Math::Quat::abs() {
+  my $x = shift;
+  sqrt($$x[0]**2 + $$x[1]**2 + $$x[2]**2 + $$x[3]**2);
+}
+sub Math::Quat::unitarize() {
+  my $q = shift;
+  $q->div_by_real($q->abs());
+}
+sub Math::Quat::sqr { my $x = shift(); $x->mul($x) }
+sub rotation_by_axis {
+    my ($a, $x, $y, $z) = @_; $a *= 0.5;
+    my $m = sqrt($x**2 + $y**2 + $z**2);
+    my $s = sin($a)/$m;
+    Math::Quat->new(cos($a), $x*$s, $y*$s, $z*$s)
+}
+
+sub Math::Quat::rotate_by_axis {
+    shift()->mul(rotation_by_axis(@_))
+}
+
+# Math::Quat->new(0,1,0,0)->rotate_by_axis(deg2rad(90),1,0,0)->peekobj();
+# die "rotate_by_axis";
+
+sub Math::Quat::matrix3x3 {
+  my ($w, $x, $y, $z) = @{ shift() };
+  die "Internal error (matrix3x3)" unless defined($w) && defined($x) && defined($y) && defined($z);
+  my ($x2, $y2, $z2) = map { ($_**2)*2 } ($x, $y, $z);
+  my $xy = ($x*$y)*2;
+  my $xz = ($x*$z)*2;
+  my $yz = ($y*$z)*2;
+  my $wx = ($w*$x)*2;
+  my $wz = ($w*$z)*2;
+  my $wy = ($w*$y)*2;
+  1 - $y2 - $z2, $xy + $wz, $xy - $wy,
+  $xy - $wz, 1 - $x2 - $z2, $yz + $wx,
+  $xz + $wy, $yz - $wx, 1 - $x2 - $y2
+}
+
+sub Math::Quat::matrix4x4 {
+  @_ = (shift->Math::Quat::matrix3x3);
+  $_[0], $_[1], $_[2], 0,
+  $_[3], $_[4], $_[5], 0,
+  $_[6], $_[7], $_[8], 0,
+  0,0,0,1
+}
+
+# Returns rho, phi, and psi of a quaternion
+sub Math::Quat::angles {
+  my ($w, $x, $y, $z) = @{ shift() };
+  my $ps = atan2($z, $y);
+  my $q = $y * $y + $z * $z;
+  my $ph = atan2(sqrt($q), $x);
+  $q += $x * $x;
+  my $rh = atan2(sqrt($q), $w);
+  ($rh, $ph, $ps)
+}
+
+# Returns a unit quaternion with the specified rho, phi, psi
+sub Math::Quat::from_angles {
+  my ($rh, $ph, $ps) = @_;
+  my $w = cos($rh);
+  my $q = sin($rh);
+  my $x = $q * cos($ph);
+  $q *= sin($ph);
+  my $y = $q * cos($ps);
+  my $z = $q * sin($ps);
+  Math::Quat->new($w, $x, $y, $z);
+}
+
+sub Math::Quat::log {
+  my $q = shift;
+  my ($a, $b, $c, $d) = @$q;
+  my $sqr_a = $a**2;
+  my $v = Math::Quat->new(0, $b, $c, $d);
+  my $sqr_abs_v = $b**2 + $c**2 + $d**2;
+  my $abs_v = sqrt($sqr_abs_v);
+  my $abs_q = sqrt($sqr_a + $sqr_abs_v);
+  $v = $v->mul_by_real(atan2($abs_v, $a) / $abs_v) if $abs_v > 0;
+  $$v[0] += log($abs_q);
+  $v
+}
+
+# Verify operation:
+# Math::Quat::log(Math::Quat::unit);
+# Theta is an angle describing the amount of rotation effected by a quaternion
+sub Math::Quat::theta {
+  my $q = shift;
+  my ($a, $b, $c, $d) = @$q;
+  atan2(sqrt($b**2 + $c**2 + $d**2), $a);
+}
+
+sub Math::Quat::exp {
+  my $q = shift;
+  my ($a, $b, $c, $d) = @$q;
+  my $v = Math::Quat->new(0, $b, $c, $d);
+  my $abs_v = sqrt($b**2 + $c**2 + $d**2);
+  my $exp_a = exp($a);
+  $v = $v->mul_by_real($exp_a * sin($abs_v) / $abs_v) if $abs_v > 0;
+  $$v[0] += $exp_a * cos($abs_v);
+  $v
+}
+
 sub canvas {
   my $i = shift;
   
     warn "splitting pixels into textures" if $PhotoCrop::debug;
     my $texdim = $i->{texdim} ||= 256;
     my $filler = "\x00" x $bytespp;
-    [
-    map {
+    [ map {
        [ reverse map { PhotoCrop::Texture->new($texdim, $texdim, $_) } splithpixels($_, $pitch, $texdim*$bytespp, $filler) ]
      } splitpixels($pixels, $texdim*$pitch, $filler) ]
   };
     my $textures = glGenTextures(1);
     die "Could not generate textures" unless $textures->[0];
     glBindTexture(GL_TEXTURE_2D, $textures->[0]);
-    glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
-    glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+    glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, ($quality > 5) ? GL_LINEAR : GL_NEAREST);
+    glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, ($quality > 5) ? GL_LINEAR : GL_NEAREST);
     glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
     glTexParameter(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
     #vec(substr($d, int(rand(length($d))), 1), 0, 8) ^= int(rand(0x100)) for (0..5000);
 
 my $VERSION = "0.0.101";
 
+# View 3-D rotation variables:
+my $turn = 0;
+my $tilt = 0;
+my $rotation = 0;
+my $viewrotation = 0;
+
+my $view_quaternion;
+my $view_quaternion_aux;
+my @view_axis_rot; # Additional axis rotations
+sub view_quaternion() {
+  if (defined $view_quaternion) {
+    $view_quaternion;
+  } else {
+    my $q = Math::Quat->unit;
+
+    $q = $q->mul($view_quaternion_aux)
+      if defined $view_quaternion_aux;
+
+    $q = $q
+      ->rotate_by_axis(deg2rad($_->[0]), 0, 0, 1)
+	->rotate_by_axis(deg2rad($_->[1]), 0, 1, 0)
+	  ->rotate_by_axis(deg2rad($_->[2]), -1, 0, 0)
+	    for @view_axis_rot;
+    $q = $q
+      ->rotate_by_axis(deg2rad($viewrotation),0,0,1)
+      ->rotate_by_axis(deg2rad($turn),0,1,0)
+	->rotate_by_axis(deg2rad($tilt),-1,0,0)
+	  ->rotate_by_axis(deg2rad($rotation),0,0,1);
+
+    $q
+  }
+}
+
+sub view_quaternion_compose() {
+  $view_quaternion = view_quaternion();
+  undef $view_quaternion_aux;
+  undef @view_axis_rot;
+  $viewrotation = 0;
+  $turn = 0;
+  $tilt = 0;
+  $rotation = 0;
+}
+
+# Decompose the quaternion into rotations by axes
+# decomposition is not fully working, as theta turns out to be > 0.5 deg for certain angles; however we can save the difference rotation quaternion for accuracy, or use additional by-axis rotations in view_quaternion_axis_rot (when the accuracy argument is provided)
+sub view_quaternion_decompose() {
+  my $accuracy = shift;
+  my $vq = view_quaternion();
+  my $q = $vq;
+  my $deg = 90;
+  my $rt = ($q->log)->div(rotation_by_axis(deg2rad($deg),0,0,1)->log)->[0]*$deg;
+  $q = $q->rotate_by_axis(-deg2rad($rt),0,0,1);
+  my $tl = ($q->log)->div(rotation_by_axis(deg2rad($deg),-1,0,0)->log)->[0]*$deg;
+  $q = $q->rotate_by_axis(-deg2rad($tl),-1,0,0);
+  my $tu = ($q->log)->div(rotation_by_axis(deg2rad($deg),0,1,0)->log)->[0]*$deg;
+  $q = $q->rotate_by_axis(-deg2rad($tu),0,1,0);
+  my $vr = ($q->log)->div(rotation_by_axis(deg2rad($deg),0,0,1)->log)->[0]*$deg;
+  $q = $q->rotate_by_axis(-deg2rad($vr),0,0,1);
+  # warn "theta: " . rad2deg($q->theta);
+  ($rotation, $tilt, $turn, $viewrotation) = ($rt, $tl, $tu, $vr);
+  @view_axis_rot = ();
+  undef $view_quaternion;
+  undef $view_quaternion_aux;
+
+  # Now it's time to increase the accuracy!
+  if (defined $accuracy) {
+    my $prev_theta;
+    while (1) {
+      $q->div(view_quaternion());
+      my $theta = rad2deg($q->theta);
+      last if $theta < $accuracy;
+      if (defined($prev_theta)) {
+	last unless $theta < $prev_theta;
+	$prev_theta = $theta;
+      }
+      my $x = ($q->log)->div(rotation_by_axis(deg2rad($deg),-1,0,0)->log)->[0]*$deg;
+      $q = $q->rotate_by_axis(-deg2rad($x),-1,0,0);
+      my $y = ($q->log)->div(rotation_by_axis(deg2rad($deg),0,1,0)->log)->[0]*$deg;
+      $q = $q->rotate_by_axis(-deg2rad($y),0,1,0);
+      my $z = ($q->log)->div(rotation_by_axis(deg2rad($deg),0,0,1)->log)->[0]*$deg;
+      $q = $q->rotate_by_axis(-deg2rad($z),0,0,1);
+      push @view_axis_rot, [ $z, $y, $x ];
+    }
+  }
+  $view_quaternion_aux = $vq->div(view_quaternion());
+  # $q->mul(rotation_by_axis(deg2rad(1),0,0,1)->inv())->peekobj();
+  # my $rotation = rad2deg($q->mul(rotation_by_axis(deg2rad(1),0,0,1)->inv())->[3]);
+  # my $rotation = 
+  # warn "rotation: $rt; tilt: $tl; turn: $tu; vr: $vr; theta: " . rad2deg($q->theta);
+}
+
+# Verify operation:
+# ($rotation, $tilt, $turn, $viewrotation) = (30, 90, 50, 10);
+# view_quaternion()->peekobj();
+# view_quaternion_decompose();
+# view_quaternion()->peekobj();
+# die "view_quaternion_decompose $rotation $tilt $turn $viewrotation";
+
 use SDL::App;
 use SDL::OpenGL;
 use SDL::Event;
 my $notile;
 my $viewangle = 30;
 #my $viewangley = 30;
-#my $viewrotation; # unsupported at the moment
-my $turn = 0;
-my $tilt = 0;
-my $rotation = 0;
-my $viewrotation = 0;
 my $savebmp;
 my $savepovray;
 my $scaleup=2;
 my $savepng;
 my $rungimp;
 my $crosshair = 1;
+my $buttons = 1;
+my $cube = 1;
+my $grid = 1;
 our $debug = 0;
 die "Unrecognized option. Try $0 --help for more information" unless
 GetOptions
   --delay MS        Delay frames MS milliseconds
   --fullscreen      Use full screen display
   --[no-]crosshair  [De]Activate a blinking, angle measuring crosshair
-  --[no-]buttons    [De]Activate button interface (default: on).
+  --[no-]cube 	    Place the viewer inside a wireframe cube (default: on)
+  --[no-]buttons    [De]Activate button interface (default: on)
 
 Animation options:
   --notile     Do not tile main texture! (may fail on some systems)
 Keyboard input:
 (a, z):  Zoom (in, out)
 (c, w):  Rotate image (clockwise, counterclockwise)
+(e, n):  Resize aperture angle (enlarge, restrict)
 Arrow keys: tilting
 RETURN:  Write output file[s] and exit
 ESCAPE:  Abort program
 "notile" => \$notile,
 "debug!" => \$debug,
 "debug-level=s" => \$debug,
+"crosshair" => \$crosshair,
+"cube!" => \$cube,
+"grid!" => \$grid,
+"buttons!" => \$buttons
 ;
 
 #die "1 rad = " . rad2deg(1) . " deg" if $debug;
 
 $angle = $photo->angle;
 
-my $app = SDL::App->new( -title => "$filename - Photocrop $VERSION", -width => $screenw, -height => $screenh, -gl => 1, -fullscreen => ($fullscreen?1:0),
+my $app = SDL::App->new(-title => "$filename - Photocrop $VERSION", -width => $screenw, -height => $screenh, -gl => 1, -fullscreen => ($fullscreen?1:0),
 			 ($noscreenresize?():( -resizeable => 1 ))
 );
 
-SDL::ShowCursor(0);
+SDL::ShowCursor($buttons);
 
 my $event = SDL::Event->new;
 $event->set(SDL_SYSWMEVENT, SDL_IGNORE);
 sub setview {
   glEnable(GL_TEXTURE_2D);
 
-  if ($crosshair) {
+  if ($crosshair || $buttons) {
     glEnable(GL_LINE_SMOOTH) if $quality > 7;
   }
 
   setperspective();
 }
 
-sub Math::Quat::new { my $p = shift; bless \@_, $p }
-sub Math::Quat::unit() { Math::Quat->new(1,0,0,0) }
-sub Math::Quat::mul {
-  my ($x, $y) = @_;
-  @$x = (
-	 $$x[0] * $$y[0] - $$x[1] * $$y[1] - $$x[2] * $$y[2] - $$x[3] * $$y[3],
-	 $$x[0] * $$y[1] + $$x[1] * $$y[0] + $$x[2] * $$y[3] - $$x[3] * $$y[2],
-	 $$x[0] * $$y[2] - $$x[1] * $$y[3] + $$x[2] * $$y[0] + $$x[3] * $$y[1],
-	 $$x[0] * $$y[3] + $$x[1] * $$y[2] - $$x[2] * $$y[1] + $$x[3] * $$y[0]
-	);
-  $x
-}
-sub Math::Quat::sqr { my $x = shift(); $x->mul($x) }
-
-sub rotation_by_axis {
-    my ($a, $x, $y, $z) = @_; $a *= 0.5; my $s = sin($a);
-    my $m = sqrt($x**2 + $y**2 + $z**2);
-    $x /= $m; $y /= $m; $z /= $m;
-    Math::Quat->new(cos($a), $x*$s, $y*$s, $z*$s)
-}
-
-sub Math::Quat::rotate_by_axis {
-    shift()->mul(rotation_by_axis(@_))
-}
-
-sub Math::Quat::matrix3x3 {
-  my ($w, $x, $y, $z) = @{ shift() };
-  die "Internal error (matrix3x3)" unless defined($w) && defined($x) && defined($y) && defined($z);
-  my ($x2, $y2, $z2) = map { ($_**2)*2 } ($x, $y, $z);
-  my $xy = ($x*$y)*2;
-  my $xz = ($x*$z)*2;
-  my $yz = ($y*$z)*2;
-  my $wx = ($w*$x)*2;
-  my $wz = ($w*$z)*2;
-  my $wy = ($w*$y)*2;
-  1 - $y2 - $z2, $xy + $wz, $xy - $wy,
-  $xy - $wz, 1 - $x2 - $z2, $yz + $wx,
-  $xz + $wy, $yz - $wx, 1 - $x2 - $y2
-}
-
-sub Math::Quat::matrix4x4 {
-  @_ = (shift->Math::Quat::matrix3x3);
-  $_[0], $_[1], $_[2], 0,
-  $_[3], $_[4], $_[5], 0,
-  $_[6], $_[7], $_[8], 0,
-  0,0,0,1
-}
-
-sub UNIVERSAL::peekobj { require Data::Dumper; print STDERR caller() . ": " . Data::Dumper::Dumper($_[0]); $_[0] }
-sub UNIVERSAL::peek { require Data::Dumper; print STDERR caller() . ": $_[0]\n"; $_[0] }
-
 setview();
 
 use POSIX qw(fmod);
   // angle ($viewangle // this is ok when screenw==screenh
   angle $viewanglew // angle in povray refers to the horizontal angle, while viewangle is the vertical one if screenw>screenh
   // angle ".($viewangle*(($by>$bx)?($by/$bx):($bx/$by)))." // viewangle
-  rotate ".$viewrotation." // viewrotation
+" .
+  ($viewrotation ? (
+    join("", map {
+      "rotate " . $_->[0] . "*z\n".
+      "rotate " . $_->[1] . "*y\n".
+      "rotate " . $_->[2] . "*x\n"
+    } @view_axis_rot)
+    .
+    "rotate ".$viewrotation."*z //viewrotation") : "")
+    . "
   rotate ".$turn."*y //turn
   rotate ".$tilt."*x //tilt
   rotate ".$rotation."*z // rotation
 
 // angle ".$angle."
 // render with: povray +W".($screenw*$scaleup)." +H".($screenh*$scaleup)."
-"  
+"
 }
 
 sub savepovray {
     $app->blit(undef, $s, undef);
     $app->unlock if $l;
     unless ($s->save_bmp($savebmp)) {
-      warn "Could not save screeen!";	
+      warn "Could not save screen!";
     }
   }
 }
   if (defined $angle) {
     my $oldangle = $angle; $keyboard->control(0.01, [SDLK_e], [SDLK_n], sub { my $i = shift; $angle = atan2(tan($oldangle/2) * (1.001 ** $i), 1) * 2; $photo->angle($angle); $i });
   }
-  my $oldrotation = $rotation; $keyboard->control(0.01, [SDLK_w], [SDLK_c], sub { my $i = shift; $rotation = $oldrotation + $i; deg2deg($i) });
-  my $oldtilt = $tilt; $keyboard->control(0.01, [SDLK_u, SDLK_UP], [SDLK_d, SDLK_DOWN], sub { my $i = shift; $tilt = $oldtilt + $i; deg2deg($i) });
-  my $oldturn = $turn; $keyboard->control(0.01, [SDLK_r, SDLK_RIGHT], [SDLK_l, SDLK_LEFT], sub { my $i = shift; $turn = $oldturn + $i; deg2deg($i) });
-  my $oldviewrotation = $viewrotation; $keyboard->control(0.01, [SDLK_k], [SDLK_v], sub { my $i = shift; $viewrotation = $oldviewrotation + $i; deg2deg($i) });
+  my $oldrotation = $rotation; $keyboard->control(0.01, [SDLK_w], [SDLK_c], sub { my $i = shift; $rotation = deg2deg($oldrotation + $i); deg2deg($i) });
+  my $oldtilt = $tilt; $keyboard->control(0.01, [SDLK_u, SDLK_UP], [SDLK_d, SDLK_DOWN], sub { my $i = shift; $tilt = deg2deg($oldtilt + $i); deg2deg($i) });
+  my $oldturn = $turn; $keyboard->control(0.01, [SDLK_r, SDLK_RIGHT], [SDLK_l, SDLK_LEFT], sub { my $i = shift; $turn = deg2deg($oldturn + $i); deg2deg($i) });
+  my $oldviewrotation = $viewrotation; $keyboard->control(0.01, [SDLK_k], [SDLK_v], sub { my $i = shift; $viewrotation = deg2deg($oldviewrotation + $i); deg2deg($i) });
 };
 my $frame = 0;
 my %e = (SDL_QUIT() => \&quit,
 	 SDL_VIDEORESIZE() => sub { my $e = shift; return if $noscreenresize; $app->resize($screenw = $e->resize_w, $screenh = $e->resize_h); setview() }
 	 );
 
+
 while (!$done) {
   warn "Frame $frame" if $debug;
   warn "Redrawing!" if $debug > 1;
   glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   my $time = int(time);
+
+  if (1) {
+    glLoadMatrix(view_quaternion()->matrix4x4());
+  } elsif (1) {
+    #my $q = rotation_by_axis(deg2rad($turn),0,1,0);
+    #$q->rotate_by_axis(deg2rad($tilt),-1,0,0);
+    #$q->rotate_by_axis(deg2rad($rotation),0,0,1);
+    #glLoadMatrix([$q->matrix4x4]);
+    #glLoadMatrix(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
+    glLoadMatrix(Math::Quat->unit
+		 ->rotate_by_axis(deg2rad($turn),0,1,0)
+		 ->rotate_by_axis(deg2rad($tilt),-1,0,0)
+		 ->rotate_by_axis(deg2rad($rotation),0,0,1)
+		 ->matrix4x4);
+  } else {
+    glLoadIdentity();
+    #glColor(1,1,1);
+    glRotate($turn,0.0,1.0,0.0) if $turn;
+    glRotate($tilt,-1.0,0.0,0.0) if $tilt;
+    glRotate($rotation,0.0,0.0,1.0) if $rotation;
+  }
+
+  if ($cube) {
+    glLineWidth(0.1);
+    glColor(1,0,1,1);
+    glBegin(GL_LINES);
+
+    # Draw wireframe cube
+    glVertex(+1.0,+1.0,+1.0); glVertex(+1.0,+1.0,-1.0);
+    glVertex(+1.0,+1.0,+1.0); glVertex(+1.0,-1.0,+1.0);
+    glVertex(+1.0,+1.0,+1.0); glVertex(-1.0,+1.0,+1.0);
+    glVertex(-1.0,-1.0,+1.0); glVertex(-1.0,-1.0,-1.0);
+    glVertex(-1.0,-1.0,+1.0); glVertex(-1.0,+1.0,+1.0);
+    glVertex(-1.0,-1.0,+1.0); glVertex(+1.0,-1.0,+1.0);
+
+    glVertex(+1.0,-1.0,-1.0); glVertex(+1.0,-1.0,+1.0);
+    glVertex(+1.0,-1.0,-1.0); glVertex(+1.0,+1.0,-1.0);
+    glVertex(+1.0,-1.0,-1.0); glVertex(-1.0,-1.0,-1.0);
+    glVertex(-1.0,+1.0,-1.0); glVertex(-1.0,+1.0,+1.0);
+    glVertex(-1.0,+1.0,-1.0); glVertex(-1.0,-1.0,-1.0);
+    glVertex(-1.0,+1.0,-1.0); glVertex(+1.0,+1.0,-1.0);
+
+    # glVertex(+1.0,+1.0,+1.0); glVertex(-1.0,-1.0,+1.0);
+    # glVertex(+1.0,-1.0,+1.0); glVertex(-1.0,+1.0,+1.0);
+    # glVertex(+1.0,+1.0,-1.0); glVertex(-1.0,-1.0,-1.0);
+    # glVertex(+1.0,-1.0,-1.0); glVertex(-1.0,+1.0,-1.0);
+
+    # glVertex(+1.0,+1.0,+1.0); glVertex(-1.0,+1.0,-1.0);
+    # glVertex(-1.0,+1.0,+1.0); glVertex(+1.0,+1.0,-1.0);
+    # glVertex(+1.0,-1.0,+1.0); glVertex(-1.0,-1.0,-1.0);
+    # glVertex(-1.0,-1.0,+1.0); glVertex(+1.0,-1.0,-1.0);
+
+    # glVertex(+1.0,+1.0,+1.0); glVertex(+1.0,-1.0,-1.0);
+    # glVertex(+1.0,+1.0,-1.0); glVertex(+1.0,-1.0,+1.0);
+    # glVertex(-1.0,+1.0,+1.0); glVertex(-1.0,-1.0,-1.0);
+    # glVertex(-1.0,+1.0,-1.0); glVertex(-1.0,-1.0,+1.0);
+
+    glEnd();
+  }
+
+  if (1) {
+    glColor(1,0,1,1);
+    glBegin(GL_TRIANGLES);
+    glVertex(+0.0,+0.0,-1.0);
+    glVertex(+1.0,+0.0,-1.0);
+    glVertex(+1.0,+1.0,-1.0);
+    glEnd();
+  }
+
+  glLineWidth(1.0);
+  glColor(1,1,1,1);
+  $photo->gldraw;
+
+  if ($grid) {
+    glLineWidth(1.0);
+    glColor(1,0,1,1);
+    glBegin(GL_LINES);
+
+    for (-5.0, -2.5, +0.0, +2.5, +5.0) {
+      glVertex($_,+5.0,-10.0); glVertex($_,-5.0,-10.0);
+      glVertex(+5.0,$_,-10.0); glVertex(-5.0,$_,-10.0);
+    }
+
+    glEnd();
+  }
+
   if ($crosshair && ($time & 1)) {
     glLoadIdentity();
     # my $w = deg2rad(0.2);
     glColor(1,1,1);
     glLineWidth(1);
   }
-  if (1) {
-    glLoadMatrix(Math::Quat->unit
-		 ->rotate_by_axis(deg2rad($viewrotation),0,0,1)
-		 ->rotate_by_axis(deg2rad($turn),0,1,0)
-		 ->rotate_by_axis(deg2rad($tilt),-1,0,0)
-		 ->rotate_by_axis(deg2rad($rotation),0,0,1)
-		 ->matrix4x4);
-  } elsif (1) {
-    #my $q = rotation_by_axis(deg2rad($turn),0,1,0);
-    #$q->rotate_by_axis(deg2rad($tilt),-1,0,0);
-    #$q->rotate_by_axis(deg2rad($rotation),0,0,1);
-    #glLoadMatrix([$q->matrix4x4]);
-    #glLoadMatrix(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1);
-    glLoadMatrix(Math::Quat->unit
-		 ->rotate_by_axis(deg2rad($turn),0,1,0)
-		 ->rotate_by_axis(deg2rad($tilt),-1,0,0)
-		 ->rotate_by_axis(deg2rad($rotation),0,0,1)
-		 ->matrix4x4);
-  } else {
-    glLoadIdentity();
-    #glColor(1,1,1);
-    glRotate($turn,0.0,1.0,0.0) if $turn;
-    glRotate($tilt,-1.0,0.0,0.0) if $tilt;
-    glRotate($rotation,0.0,0.0,1.0) if $rotation;
-  }
-  if (1) {
-    glColor(1,0,1,1);
-    glBegin(GL_TRIANGLES);
-    glVertex(+0.0,+10.0,-10.0);
-    glVertex(+0.0,+0.0,-10.0);
-    glVertex(+10.0,+0.0,-10.0);
-    glEnd();
-    glColor(1,1,1,1);
-  }
-  glLineWidth(1.0);
-  $photo->gldraw;
+
   #glTranslate(0,0,-6);
   #glBegin(GL_QUADS);
   #glVertex(-1.0,+1.0,+0.0);
   $app->delay($delay);
   warn "Processing events" if $debug;
   $event->pump;
+
   while ($anim ? $event->poll : $event->wait) {
     my $type = $event->type;
     my $a = $e{$type};
     redo if $event->poll;
     last
   }
+
   $keyboard->frame;
   $anim = $keyboard->needanim;
   $frame++;