#  Copyright (c) 1997-2004
#  Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Berlin, Germany)
#  http://www.math.tu-berlin.de/polymake,  mailto:polymake@math.tu-berlin.de
#
#  This program is free software; you can redistribute it and/or modify it
#  under the terms of the GNU General Public License as published by the
#  Free Software Foundation; either version 2, or (at your option) any
#  later version: http://www.gnu.org/licenses/gpl.txt.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#-----------------------------------------------------------------------------
#  $Project: polymake $$Id: schlegel.rules,v 1.17 2004/11/12 13:38:35 gawrilow Exp $


object Polytope;

# category: Visualization
# Set of stored parameters to create a Schlegel diagram.
# This is a temporary solution until the hierarchical objects are fully implemented.
#
property SCHLEGEL_PARAMS
$type="text";


# A Schlegel diagram of a polytope
object SchlegelDiagram


# A reference to the polytope being visualized.
# As soon as the object hierarchy is properly implemented, this property becomes the
# regular 'parent' link.
#
property Polytope
$type="object";

# The facet number in the original polytope, giving the projection plane.
property FACET
$type="cardinal";
$accept=sub : method {
   my ($this, $facet)=@_;
   if ($facet<0 || $facet>=$this->Polytope->N_FACETS) {
      die "facet index out of valid range 0..", $this->Polytope->N_FACETS-1, "\n";
   }
   return $facet;
};


# Zoom factor.
property ZOOM
$type="scalar";
$accept=sub {
   no integer; my $z=eval $_[0];	# FIXME: until Rational are directly available in perl
   die "zoom factor not in (0..1]\n" if $z<=0 || $z>1;
   return $_[0];		# return the original format
};


# The intersection point of the projection facet and the view ray.
property FACET_POINT
$type="vector";


# A point on the view ray lying inside the polytope.
property INNER_POINT
$type="vector";


# Create the default set of parameters for the schlegel diagram
FACET_POINT, INNER_POINT : Polytope, FACET
### Polytope.{VERTICES, VERTICES_IN_FACETS, VERTEX_BARYCENTER}
   client("schlegel_params", $this, $this->Polytope);


# Matrix of a projective transformation mapping the whole polytope into the @see FACET
# The points belonging to this facet stay fixed.

property TRANSFORM
$type="matrix";


# Coordinates in affine 3-space of the vertices which
# correspond to a 3-dimensional (Schlegel-) projection of a 4-polytope.

property VERTICES
$type="matrix";
$temporary=1;


TRANSFORM : Polytope, FACET, ZOOM, FACET_POINT, INNER_POINT
### Polytope.{VERTICES, FACETS, DUAL_GRAPH}
### PRECONDITION: Polytope.BOUNDED
###   $BOUNDED
### BODY:
   client("schlegel_transform", $this, $this->Polytope);


sub Apps::polytope::SchlegelDiagram::compute_vertices : method {
   my ($this, $result, @sections)=@_;
   client("schlegel_vertices", $this, $this->Polytope, $result, @sections);
}

VERTICES : Polytope, TRANSFORM, FACET
### Polytope.{VERTICES, FACETS}
   $this->compute_vertices("VERTICES", "VERTICES");


##################################################################################
# visualization

package Visual::Schlegel;
use Struct (
   [ '@ISA' => 'Visual::Graph' ],
   '$Schlegel',
);

# Color of the Schlegel projection facet (RGB)
custom $proj_facet="0 0 0";

# Illustrate the behavior of a linear objective function on the polytope.
# Superpose the drawing with the directed graph induced by the objective function.
# args: [ LPobject ]

user_method DIRECTED_GRAPH {
   my ($this, $lp)=@_;
   $lp ||= $this->Schlegel->Polytope;
   $this->EdgeStyle="thickness 1";
   my $DG=new Visual::Graph(
      Name => "DIRECTED_GRAPH of " . $this->Schlegel->Polytope->name,
      Graph => $lp->DIRECTED_GRAPH,
      Coord => $this->Coord,
      NodeStyle => "hidden",
      EdgeColor => $this->EdgeColor,
      Directed => 1,
   );
   visualize($this, $DG);
}

# Illustrate the behavior of a linear objective function on the polytope.
# Color the vertices according to the values of the objective function.
# args: [ LPobject ]

user_method VERTEX_COLORS {
   my ($this, $lp)=@_;
   $lp ||= $this->Schlegel->Polytope;
   $this->NodeColor=$lp->VERTEX_COLORS;
   visualize($this);
}

method append_points {
   my ($this, $points)=@_;
   my $coord=$this->Coord;
   my $emb=detect_dynamic($coord);
   my $n=$emb->get_n_vertices;
   if ($coord==$emb) {
      # the schlegel VERTICES was the lone section so far
      $this->Coord=slice($emb,0,$n-1);
   }
   $emb->push($points);
   return slice($emb, $n, $n+@$points);
}

# Draw the edges of the @see TRIANGULATION_BOUNDARY
user_method TRIANGULATION_BOUNDARY {
   my ($this)=@_;
   my $P=$this->Schlegel->Polytope;
   visualize("Triangulation Boundary of ".$P->name,
	     $this,
	     new Visual::Graph(
		Name => $this->Name."-inner-edges",
		Graph => $P->TriangulationBoundaryInnerEdges($this),
                Coord => $this->Coord,
                NodeStyle => "hidden",
                EdgeStyle => "thickness 1",
                EdgeColor => $this->EdgeColor,
	    ));
}

#################################################################################
package Visual::SchlegelEmbedding;
@ISA=qw( Visual::Embedding );

sub compute {
   $_[0]->unbless;
   my ($Schlegel, $n_vert)=splice @{$_[0]}, 0, 2;
   $Schlegel->compute_vertices($_[0], @{$_[0]});
   $_[0];
}

sub get_n_vertices {
   retrieve($_[0],1);
}

sub push {
   my $self=shift;
   my $class=ref($self);
   $self->unbless;
   $self->[1] += @_==1 ? scalar(@{$_[0]}) : $_[0];
   push @$self, $_[-1];
   bless $self, $class;
}

package Visual::LazySchlegelEmbedding;
@ISA=qw( Visual::SchlegelEmbedding Visual::LazyEmbedding );

sub unbless {
   bless $_[0], "Visual::SchlegelEmbedding";
}

#################################################################################

object SchlegelDiagram;

function schlegel_embedding {
   new Visual::LazySchlegelEmbedding(@_);
}

function detect_dynamic(Visual::SchlegelEmbedding) {
   $_[0]->unbless;
}

function dim(Visual::SchlegelEmbedding) { 3 }

# Draw the Schlegel diagram
# return: Visual::Schlegel

user_method VISUAL(%Visual::Graph::decorations) {
   my ($this, $decor)=@_;
   my $P=$this->Polytope;
   if (!$P->BOUNDED) {
      die "polytope ", $P->name, " is not BOUNDED and therefore cannot be visualized\n";
   }
   my %proj_facet=map { $_ => 1 } $P->VERTICES_IN_FACETS->[$this->FACET] =~ /\d+/g;

   visualize( "Schlegel diagram of " . $this->Polytope->name,
	      new Visual::Schlegel(
		 Schlegel => $this,
		 Name => $P->name,
		 Graph => $P->GRAPH,
		 VertexLabels => $P->lookup("VERTEX_LABELS"),
		 Coord => schlegel_embedding($this, $P->N_VERTICES, "VERTICES"),
		 EdgeColor => sub {
		                 my ($s, $t)=(shift)->incident_nodes;
				 $proj_facet{$s} && $proj_facet{$t}
				 ? $Visual::Schlegel::proj_facet
				 : $Visual::Color::facets
			      },
		 $decor
	    ));
}

#################################################################################

object Polytope;

declare %make_schlegel_params=( FACET => undef, ZOOM => undef );

# category: Visualization
# Create a SchlegelDiagram object.
#
# The projection ray will join the vertex barycenters of the polytope
# and the projection facet.
#
# Default zoom factor is 9/10.
#
# option: FACET => projection facet index  (default: 0)
# option: ZOOM => zoom factor (rational or double)
#
# return: SchlegelDiagram

user_method makeSchlegelDiagram(%make_schlegel_params) {
   my ($this, $params)=@_;
   my @props=(Polytope => $this, %$params);

   my $stored_params=$this->lookup("SCHLEGEL_PARAMS");
   if (my @stored_params= $stored_params =~ /\A\s* (\d+) \s+ (\S+) \s* ^(.*)$ \s* ^(.*)$/xms) {
      if (!exists $params->{FACET}) {
	 push @props, (FACET => $stored_params[0],
		       FACET_POINT => $stored_params[2],
		       INNER_POINT => $stored_params[3]);
      }
      if (!exists $params->{ZOOM}) {
	 push @props, (ZOOM => $stored_params[1]);
      }
   } else {
      if (!exists $params->{FACET}) {
	 push @props, (FACET => 0);
      }
      if (!exists $params->{ZOOM}) {
	 push @props, (ZOOM => "9/10");
      }
   }

   new Apps::polytope::SchlegelDiagram(@props);
}

# category: Visualization
# Create a Schlegel diagram and draw it.
# return: Visual::Schlegel

user_method SCHLEGEL(%Visual::Graph::decorations, %make_schlegel_params) {
   my ($this, $decor, $params)=@_;
   $this->makeSchlegelDiagram($params)->VISUAL($decor);
}


# Local Variables:
# mode: perl
# c-basic-offset:3
# End:
