#  Copyright (c) 1997-2007
#  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: visual.rules 7578 2007-01-21 22:48:26Z gawrilow $


package Visual::Polyhedron;

use Struct (
   [ '@ISA' => 'Visual::PointSet' ],
   [ '@Facets' => 'check_points(#%)', default => 'croak("Facets missing")' ],
   '@FacetNeighbors',
   [ '$FacetLabels' => 'unify_labels(#%)' ],
   [ '$FacetColor' => 'unify_decor(#%)', merge => '$this->merge_decor(#%)', default => '$Visual::Color::facets' ],
   [ '$FacetStyle' => 'unify_decor(#%)', merge => '$this->merge_decor(#%)' ],
   [ '$EdgeColor' => '$Visual::Color::edges' ],
   '$EdgeStyle',
   '$Closed',
);

declare %decorations=( %Visual::Polygon::decorations, FacetLabels => enum('hidden') );

package Visual::Polytope;

use Struct (
   [ '@ISA' => 'Visual::Container' ],
   '$Polytope',
);

method basis_solid { $_[0]->elements->[0] }

##########################################################################################
#
# LP-related supplements

# Illustrate the behavior of a linear objective function on the polytope.
# Draw the facets contained in @see MAXIMAL_FACE and @see MINIMAL_FACE in distinct colors.
#
# option: min => minimal face color (default: yellow)
# option: max => maximal face color (default: red)

user_method MIN_MAX_FACE({ min => $Visual::Color::min, max => $Visual::Color::max }) {
   my ($self, $decor)=@_;
   my $lp=$self->Polytope;
   my ($max_filter, $min_filter)=map {
      my $re=$_ ? "\\s* (?:\\{\\s*)? " . join("\\s+", /\d+/g) . " \\s* (?:\\}\\s*)?" : "-";
      qr{ ^ $re $ }x
   } ($lp->MAXIMAL_FACE, $lp->MINIMAL_FACE);
   my ($min, $max)=(-1, -1);
   my $i=0;
   foreach my $facet (@{$self->Polytope->VERTICES_IN_FACETS}) {
      if ($max<0 && $facet =~ $max_filter) {
	 $max=$i;
	 last if $min>=0;
      } elsif ($min<0 && $facet =~ $min_filter) {
	 $min=$i;
	 last if $max>=0;
      }
      ++$i;
   }
   if ($min>=0 || $max>=0) {
      $self->basis_solid->merge( FacetColor => sub { $_[0]==$min ? $decor->{min} :
						     $_[0]==$max ? $decor->{max} : undef } );
   }
   visualize($self);
}


# Illustrate the behavior of a linear objective function on the polytope.
# Color the vertices according to the values of the objective function
# (@see Polytope.VERTEX_COLORS)

user_method VERTEX_COLORS {
   my ($self)=@_;
   my $lp=$self->Polytope;
   $self->basis_solid->VertexColor=$lp->VERTEX_COLORS;
   visualize($self);
}


# Illustrate the behavior of a linear objective function on the polytope.
# Superpose the drawing with the directed graph induced by the objective function.

user_method DIRECTED_GRAPH {
   my ($self)=@_;
   my $lp=$self->Polytope;
   push @{$self->elements},
        new Visual::Graph(
			  Name => "DIRECTED_GRAPH of " . $self->Polytope->name,
			  Graph => $lp->DIRECTED_GRAPH,
			  Coord => $self->basis_solid->Vertices,
			  NodeStyle => "hidden",
			  Directed => 1,
			 );
   visualize($self);
}

##########################################################################################
#
#  Triangulation as supplement

my @simplex_faces_3d=([ "{0 2 1}", "{0 1 3}", "{0 3 2}", "{1 2 3}" ],	# +
		      [ "{0 1 2}", "{0 3 1}", "{0 2 3}", "{1 3 2}" ]);	# -
my @simplex_faces_2d=([ "{0 1 2}" ],	# +
		      [ "{0 2 1}" ]);	# -
my @simplex_neighbor_faces=([ "{1 2 3}", "{2 0 3}", "{0 1 3}", "{1 0 2}" ],	# +
			    [ "{2 1 3}", "{0 2 3}", "{1 0 3}", "{0 1 2}" ]);	# -


# Add the triangulation to the drawing.  The facets of the whole polytope become transparent.
#
# You may specify any triangulation of the current polytope.
# Per default, the @see Polytope.TRIANGULATION property is taken.
# (Currently there is only one possible alternative triangulation: @see TRIANGULATION_INT).
#
# <b>Hint:</b> Use the method <i>Method -> Effect -> Explode Group of Geometries</i>
# of @see external.JavaView for better insight in the internal structure.

user_method TRIANGULATION(;$="TRIANGULATION", %Visual::Polyhedron::decorations) {
   my ($self, $TR, $decor)=@_;
   my $d=$self->Polytope->AMBIENT_DIM;
   if ($d > 3) {
      die "don't know how to visualize the triangulation of a $d-d polytope\n";
   }
   my $skeleton=$self->basis_solid;
   $skeleton->VertexStyle="hidden";
   $skeleton->FacetStyle="hidden";

   my ($Points, $Signs);
   if ($TR eq "TRIANGULATION") {
      $Points=$skeleton->Vertices;
      $Signs=$self->Polytope->TRIANGULATION_SIGNS;
   } elsif ($TR eq "TRIANGULATION_INT") {
      $Points=[ $self->Polytope->dehomogenize($self->Polytope->POINTS) ];
      $Signs=$self->Polytope->TRIANGULATION_INT_SIGNS;
   } else {
      die "Visualization of arbitrary triangulations is not yet implemented\n";
   }

   my @signs = map { ($_ < 0)+0 } split /\s+/, $Signs;	# 0: positive, 1: negative
   my $vertex_labels= delete $decor->{VertexLabels} || $skeleton->VertexLabels;
   undef $vertex_labels if !is_array($vertex_labels);
   my $simplex_faces=  $d==3 ? \@simplex_faces_3d : \@simplex_faces_2d;

   my @simplexes=map {
      my @points = /\d+/g;
      my ($name) = /(.*)$/;
      my $s=shift @signs;
      my @params=( Name => $self->Polytope->name."-$name",
		   Vertices => subset($Points, @points),
		   Facets => $simplex_faces->[$s],
		   VertexLabels => ref($vertex_labels) ? subset($vertex_labels, @points) : $vertex_labels || \@points,
		   $decor
		 );
      if ($d==3) {
	 push @params, FacetNeighbors => $simplex_neighbor_faces[$s];
      }
      new Visual::Polyhedron(@params);
   } @{$self->Polytope->$TR};

   $self->Name="Triangulation of ".$self->Polytope->name;
   push @{$self->elements}, \@simplexes;

   visualize($self);
}


# Draw the edges of the @see Polytope.TRIANGULATION_BOUNDARY.
# The facets are made transparent.

user_method TRIANGULATION_BOUNDARY {
   my ($self)=@_;
   my $P=$self->basis_solid;
   $P->FacetStyle="hidden";
   $P->EdgeStyle="thickness 3";
   $self->Name="Triangulation Boundary of ".$self->Polytope->name;
   push @{$self->elements},
        new Visual::Graph(
			  Name => $self->Polytope->name."-inner-edges",
			  Graph => $self->Polytope->TriangulationBoundaryInnerEdges($self->Polytope->GRAPH),
			  Coord => $P->Vertices,
			  NodeStyle => "hidden",
			  EdgeStyle => "thickness 1",
			 );
   visualize($self);
}
##################################################################################################

object Polytope;

# category: Visualization
# Visualize a polytope as a solid object (when 2-d or 3-d), or as a Schlegel diagram (4-d).
# return: Visual::Polytope

user_method VISUAL(%Visual::Polyhedron::decorations) {
   my ($this, $decor)=@_;
   my $d=$this->AMBIENT_DIM;
   if ($d>=2 && $d<=4) {
      if (!$this->BOUNDED) {
	 die "polytope ", $this->name, " is not BOUNDED and therefore cannot be visualized\n";
      }
      if ($d==4) {
	 $this->SCHLEGEL($decor);
      } else {
	 my $P= $d==2
	        ? new Visual::Polygon(
				      Name => $this->name,
				      Vertices => [ $this->dehomogenize($this->VERTICES) ],
				      VertexLabels => $this->lookup("VERTEX_LABELS"),
				      Facet => $this->VIF_CYCLIC_NORMAL->[0],
				      $decor
				     )
		: new Visual::Polyhedron(
					 Name => $this->name,
					 Vertices => [ $this->dehomogenize($this->VERTICES) ],
					 VertexLabels => $this->lookup("VERTEX_LABELS"),
					 Facets => $this->VIF_CYCLIC_NORMAL,
					 FacetNeighbors => $this->NEIGHBOR_FACETS_CYCLIC_NORMAL,
					 FacetLabels => $this->lookup("FACET_LABELS"),
					 Closed => 1,
					 $decor
					);
	 visualize( new Visual::Polytope(Name => $this->name, Polytope => $this, $P));
      }
   } else {
      die "no visualization method known for $d-dimensional polytope\n";
   }
}

# category: Visualization
# Visualize the dual polytope as a solid 3-d object. The polytope must be @see BOUNDED and @see CENTERED.
#
# Returns: @c Visual::Polyhedron

user_method VISUAL_DUAL(%Visual::Polyhedron::decorations) {
   my ($this, $decor)=@_;
   my $d=$this->AMBIENT_DIM;
   if ($d<=3) {
      if (!$this->BOUNDED) {
	 die "polytope ", $this->name, " is not BOUNDED and therefore cannot be visualized\n";
      }
      if (! $this->CENTERED) {
	 die "polytope ", $this->name, " is not CENTERED, its dual cannot be visualized\n";
      }
      my $P= $d==2
             ? new Visual::Polygon(
				   Name => "dual of ".$this->name,
				   Vertices => [ $this->dehomogenize($this->FACETS) ],
				   VertexLabels => $this->lookup("FACET_LABELS"),
				   Facet => $this->FTV_CYCLIC_NORMAL->[0],
				   $decor,
				  )
	     : new Visual::Polyhedron(
				      Name => "dual of ".$this->name,
				      Vertices => [ $this->dehomogenize($this->FACETS) ],
				      VertexLabels => $this->lookup("FACET_LABELS"),
				      Facets => $this->FTV_CYCLIC_NORMAL,
				      FacetNeighbors => $this->NEIGHBOR_VERTICES_CYCLIC_NORMAL,
				      FacetLabels => $this->lookup("VERTEX_LABELS"),
				      Closed => 1,
				      $decor,
				     );
      visualize($P);
   } else {
      die "no visualization method known for the dual of a $d-dimensional polytope\n";
   }
}

##########################################################################
#
#  An awful hack until C++ data structures are available directly
#
package Graph_with_lookup;
sub has_edge {
   my ($self, $from, $to)=@_;
   $$self->[$from] =~ /\b$to\b/;
}
sub get_number_nodes {
   my $self=shift;
   return scalar(@$$self);
}

object Polytope;

# The input Graph describes the edges which are to exclude from the result
method TriangulationBoundaryInnerEdges {
   my ($this, $Graph)=@_;
   $Graph=bless \($_[1]), "Graph_with_lookup" unless is_object($Graph);
   my @inner_edges_graph=map { [ ] } 0..$Graph->get_number_nodes-1;
   foreach (@{$this->TRIANGULATION_BOUNDARY}) {
      foreach my $simplex (/\{ [^{}]+ \}/xg) {
	 foreach my $edge (all_subsets_of_k(2, $simplex =~ /\d+/g)) {
	    if (! $Graph->has_edge(@$edge)) {
	       push @{$inner_edges_graph[$edge->[0]]}, $edge->[1];
	       push @{$inner_edges_graph[$edge->[1]]}, $edge->[0];
	    }
	 }
      }
   }
   $_=[ num_sorted_uniq( sort { $a <=> $b } @$_ ) ] for @inner_edges_graph;
   return \@inner_edges_graph;
}

# category: Visualization
# Visualize the @see TRIANGULATION_BOUNDARY of the polytope.
# @b Obsolete; the preferred procedure is to create a @see SimplicialComplex using
# the @see boundary_complex client of the application 
# <a target="_top" href="../../topaz/model.html">topaz</a>
# and call its @see SimplicialComplex.VISUAL method.
# return: Visual::Polygons

user_method VISUAL_TRIANGULATION_BOUNDARY(%Visual::Polygon::decorations) {
   my ($this, $decor)=@_;
   if ($this->AMBIENT_DIM != 3) {
      die "don't know how to visualize the triangulation boundary of a ", $this->AMBIENT_DIM, "-d polytope\n";
   }
   warn_print( <<'.' );
Method VISUAL_TRIANGULATION_BOUNDARY is obsolete.
The preferred procedure is to create a SimplicialComplex using
the boundary_complex client of the application topaz
and call its VISUAL method.
.
   visualize( new Visual::Polygons(
      Name => "Triangulation Boundary of ".$this->name,
      Vertices => [ $this->dehomogenize($this->VERTICES) ],
      VertexLabels => $this->lookup("VERTEX_LABELS"),
      $decor,
      map { map { new Visual::Polygon( Facet => $_ ) } /\{ [^{}]+ \}/xg } @{$this->TRIANGULATION_BOUNDARY}
   ));
}


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