#  Copyright (c) 1997-2021
#  Ewgenij Gawrilow, Michael Joswig, and the polymake team
#  Technische Universität Berlin, Germany
#  https://polymake.org
#
#  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.
#-------------------------------------------------------------------------------


# @topic objects/Visual::Polytope
# @category Visualization
# Visualization of a polytope as a graph (if 1d), or as a solid object (if 2d or 3d),
# or as a Schlegel diagram (4d).
# @super Visual::Container
# @relates objects/Polytope

package Visual::Polytope;

use Polymake::Struct (
   [ '@ISA' => 'Container' ],
   [ '$Polytope' => '#%', default => 'undef' ],
);

# where to keep the view transformation matrix etc.
method representative { $_[0]->Polytope }

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

method add_faces {
   my $self=shift;
   my %to_merge;
   while (my ($face, $decor)=splice @_, 0, 2) {
      my $f=0;
      foreach my $facet (@{$self->Polytope->VERTICES_IN_FACETS}) {
         my $rel=incl($facet,$face);
         if ($rel==0) {
            my $has_facet_decor;
            foreach my $decor_key (grep { /^Facet/ } keys %$decor) {
               $to_merge{$decor_key}->{$f}=$decor->{$decor_key};
               $has_facet_decor=1;
            }
            last if $has_facet_decor;
         }
         if ($rel!=2) {
            foreach my $decor_key (grep { /^Vertex/ } keys %$decor) {
               $to_merge{$decor_key}->{$_}=$decor->{$decor_key} for @$face;
            }
            last;
         }
         ++$f;
      }
   }
   basis_solid($self)->merge(%to_merge);
}

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

# Illustrate the behavior of a linear objective function on the polytope.
# Draw the facets contained in [[MAXIMAL_FACE]] and [[MINIMAL_FACE]] in distinct colors.
# @param LinearProgram lp a LinearProgram object attached to the polytope.
# @option [complete color] Color min minimal face decoration (default: yellow vertices and/or facets)
# @option [complete color] Color max maximal face decoration (default: red vertices and/or facets)
# @return Visual::Polytope
# @example Attaches a linear program to the threedimensional cube and displays the minimal/maximal faces in a different color, choosing purple instead of the default red for the maximal face
# > $p = cube(3);
# > $p->LP = new LinearProgram(LINEAR_OBJECTIVE=>[0,1,0,0]);
# > $p->VISUAL->MIN_MAX_FACE(max=>"purple");

user_method MIN_MAX_FACE(; LinearProgram { min => $Visual::Color::min, max => $Visual::Color::max }) {
   my ($self, $lp, $decor)=@_;
   if (defined $lp) {
      if ($lp->parent != $self->Polytope) {
         croak("LinearProgram object does not belong to this Polytope");
      }
   } else {
      $lp=$self->Polytope->LP;
   }
   foreach (values %$decor) {
      unless (is_like_hash($_)) {
         my $c = get_RGB($_);
         $_ = { VertexColor => $c, FacetColor => $c };
      }
   }

   $self->add_faces($lp->MINIMAL_FACE, $decor->{min}, $lp->MAXIMAL_FACE, $decor->{max});
   visualize($self);
}


# Illustrate the behavior of a linear objective function on the polytope.
# Color the vertices according to the values of the objective function.
# @param LinearProgram lp a LinearProgram object attached to the polytope
# @option [complete color] Color min minimal vertex color (default: yellow)
# @option [complete color] Color max maximal vertex color (default: red)
# @return Visual::Polytope
# @example Attaches a linear program to the threedimensional cube and displays the minimal/maximal vertices in a different color, choosing purple instead of the default red for the maximal vertices
# > $p = cube(3);
# > $p->LP = new LinearProgram(LINEAR_OBJECTIVE=>[0,1,0,0]);
# > $p->VISUAL->VERTEX_COLORS(max=>"purple");

user_method VERTEX_COLORS(; LinearProgram { min => $Visual::Color::min, max => $Visual::Color::max }) {
   my ($self, $lp, $decor)=@_;
   if (defined $lp) {
      if ($lp->parent != $self->Polytope) {
         croak("LinearProgram object does not belong to this Polytope");
      }
   } else {
      $lp=$self->Polytope->LP;
   }
   $_=get_RGB($_) for values %$decor;
   $self->basis_solid->VertexColor=vertex_colors($self->Polytope, $lp, $decor);
   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.
# @param LinearProgram lp a Linear Program object attached to the polytope
# @return Visual::Polytope
# @example Attaches a linear program to the 3-dimensional cube and visualizes the directed graph, giving the cube a blue facet color
# > $p = cube(3);
# > $p->LP = new LinearProgram(LINEAR_OBJECTIVE=>[0,0,0,1]);
# > $p->VISUAL(FacetColor=>"blue")->DIRECTED_GRAPH;

user_method DIRECTED_GRAPH(; LinearProgram) {
   my ($self, $lp)=@_;
   if (defined $lp) {
      if ($lp->parent != $self->Polytope) {
         croak("LinearProgram object does not belong to this Polytope");
      }
   } else {
      $lp=$self->Polytope->LP;
   }
   $self->basis_solid->EdgeStyle="hidden";
   push @{$self->elements},
        new Visual::Graph( Name => "GRAPH directed with LP " . $lp->name,
                           Graph => $lp->DIRECTED_GRAPH,
                           Coord => $self->basis_solid->Vertices,
                           NodeStyle => "hidden",
                         );
   visualize($self);
}

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


# Add the triangulation to the drawing.  
#
# You may specify any triangulation of the current polytope.
# Per default, the [[Cone::TRIANGULATION|TRIANGULATION]] property is taken.
# (Currently there is only one possible alternative triangulation: [[Cone::TRIANGULATION_INT|TRIANGULATION_INT]]).
#
# **Hint:** Use the method __Method -> Effect -> Explode Group of Geometries__
# of [[wiki:external_software#JavaView|JavaView]] for better insight in the internal structure.
# @param Array<Set<Int>> t facets of the triangulation
# @options %Visual::Polygons::decorations
# @return Visual::Polytope
# @example Displays a triangulation of the threedimensional cube. Facets are made transparent and vertices are hidden.
# > cube(3)->VISUAL->TRIANGULATION(FacetTransparency=>0.7,VertexStyle=>"hidden");

user_method TRIANGULATION(; $=$_[0]->Polytope->TRIANGULATION->FACETS, %Visual::Polygons::decorations) {
   my ($self, $TR, $decor)=@_;
   my $d=$self->Polytope->CONE_AMBIENT_DIM-1;
   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 $vertex_labels= delete $decor->{VertexLabels};
   if ($vertex_labels ne "hidden") {
      $vertex_labels=$skeleton->VertexLabels;
   } else {
      undef $vertex_labels;
   }
   my $Points=$skeleton->Vertices;
   my $Signs=triang_sign($TR, $self->Polytope->VERTICES);

   $self->Name="Triangulation of ".$self->Polytope->name;
   push @{$self->elements}, [ $self->Polytope->VisualSimplices($TR,$Points,$Signs,$vertex_labels,$decor) ];

   visualize($self);
}

# Draw the edges of the [[Visual::Polytope::TRIANGULATION_BOUNDARY|TRIANGULATION_BOUNDARY]].
# The facets are made transparent.
# @options %Visual::Graph::decorations
# @return Visual::Polytope
# @example Displays the boundary triangulation of the threedimensional cube.
# > cube(3)->VISUAL->TRIANGULATION_BOUNDARY;
# @example For a slightly different visualization of essentially the same:
# > cube(3)->TRIANGULATION->BOUNDARY->VISUAL;

user_method TRIANGULATION_BOUNDARY(%Visual::Graph::decorations) {
   my ($self, $decor)=@_;
   my $P=$self->basis_solid;
   $P->FacetTransparency ||= 0.5;
   $P->EdgeThickness ||= 1;
   $self->Name="Triangulation Boundary of ".$self->Polytope->name;
   my $graph = new graph::Graph(ADJACENCY =>  $self->Polytope->TRIANGULATION->BOUNDARY->GRAPH->ADJACENCY - $self->Polytope->GRAPH->ADJACENCY);
   push @{$self->elements},
        new Visual::Graph( Name => $self->Polytope->name."-inner-edges",
                           Graph => $graph,
                           Coord => $P->Vertices,
                           NodeStyle => "hidden",
                           EdgeThickness => 0.5,
                           $decor
                         );
   visualize($self);
}

# ATTENTION: A similar function for PointConfiguration was deleted, on purpose!
# Beware of the role of points in the triangulation which are not in the boundary.

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

object Polytope {

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=([ [2,3,1], [0,3,2], [1,3,0], [0,2,1] ],     # +
                            [ [1,3,2], [2,3,0], [0,3,1], [1,2,0] ]);    # -

# utility for visualization methods: converts an array of simplices to an array of Visual::Polygons
method VisualSimplices {
   my ($self, $Simplices, $Points, $Signs, $vertex_labels, $decor)=@_;
   my $d=$Points->cols;
   my $simplex_faces=  $d==3 ? \@simplex_faces_3d : \@simplex_faces_2d;

   my $i=0;
   map {
      my $s=(1-$Signs->[$i++])/2;               # 0: positive, 1: negative
      my @face=@$_;
      new Visual::Polygons( Name => $self->name."-{@face}",
			    Vertices => $Points->minor($_,All),
			    VertexLabels => $vertex_labels && sub { $vertex_labels->($face[$_[0]]) },
			    Facets => $simplex_faces->[$s],
			    $d==3 ? (FacetNeighbors => $simplex_neighbor_faces[$s]) : (),
			    NEdges => $d==3? 6 : 3,
			    $decor
			  );
   } @$Simplices;
}

sub apply_bounding_facets($;$) {
   my ($this,$bbox) = @_; # Polytope object, Matrix
   my $bounded=$this->type->construct->($this->name."_bounded", INEQUALITIES => $this->FACETS / $bbox, EQUATIONS=>$this->AFFINE_HULL);
   if (!$bounded->BOUNDED) {
      die "apply_bounding_facets: Invalid bounding facets! There is an unbounded object.";
   }
   return $bounded;
}

sub map_to_deco {
   my ($map,$mapkeys,$deco,$default) = @_;
   # deco should be defined and not "hidden" (as long as not all backends handle "hidden" or undef returns of this specific code decor) 
   my @new_array;
   if (is_code($deco)) {
      @new_array=map { exists($map->{$_}) ? $deco->($map->{$_}) : $default } @$mapkeys;
   } elsif (is_like_array($deco)) {
      @new_array=map { exists($map->{$_}) ? $deco->[$map->{$_}] : $default } @$mapkeys;
   } elsif (is_like_hash($deco)) {
      @new_array=map { exists($map->{$_}) ? $deco->{$map->{$_}} : $default } @$mapkeys;
   } else {
      @new_array=map { exists($map->{$_}) ? $deco : $default } @$mapkeys;
   }
   return \@new_array;
}

sub convert_this_decor_to_bounded_decor {
   my ($this,$bounded,$decor) = @_;
   # this assumes that the user treats VertexDecor according to $this->VERTICES
   # TODO?: VertexDecor for far points is currently thrown away. There are also cases of polytopes with lineality that even throw away bounded VERTICES and its decor
   # In general the current visualization alters quite some combinatorics, which can be counterintuitive especially for new polymake users
   # a more combinatorically friendly visualization could be a (common) bounding sphere
   # and the distribution of decor of far points in $this->VERTICES to all intersections (of edges containing it) with the bounding box/sphere
   # this could be an attempt to visualize combinatorics of rays and lineality space
   # TODO?: this sub does not work for labels separated by spaces, 
   # like in the old code, we ask for $bounded->FACETS before $bounded->VERTICES in order to trigger "cdd.convex_hull.canon : FACETS, LINEAR_SPAN :
   # INEQUALITIES" in a prefer("lrs") environment otherwise we get a different permutation of VERTICES and FACETS 
   my $facet_map=new Map<Vector<Scalar>, Int>;
   my $j = 0;
   $facet_map->{$_}=$j++ for @{$this->FACETS};
   
   if ($bounded->CONE_DIM==4) {
      # mark artificial facets 
      my $fc = $decor->{FacetColor} // $Visual::Color::facets;
      $decor->{FacetColor} = map_to_deco($facet_map,\@{$bounded->FACETS},$fc,$Visual::Color::cutoff);
      
      my $fl = $decor->{FacetLabels};
      if (defined ($fl) && $fl !~ $Visual::hidden_re) {
         $decor->{FacetLabels} = map_to_deco($facet_map,\@{$bounded->FACETS},$fl," ");
      }
   }

   my $vertex_map=new Map<Vector<Scalar>, Int>;
   my $i = 0;
   $vertex_map->{$_}=$i++ for @{$this->VERTICES};
   
   my $vl = $decor->{VertexLabels};
   if ($vl !~ $Visual::hidden_re) {
      if (defined($vl)) {
         $decor->{VertexLabels} = map_to_deco($vertex_map,\@{$bounded->VERTICES},$vl," ");
      } else {
         # we provide labels if they're undefined in decor and VERTEX_LABELS
         my @newarray = map { $vertex_map->{$_} // " " } @{$bounded->VERTICES};
         $decor->{VertexLabels} = \@newarray;
      }
   }

   my $vt = $decor->{VertexThickness} // 1;
   $decor->{VertexThickness} = map_to_deco($vertex_map,\@{$bounded->VERTICES},$vt,0) if ($vt);

   my $vc = $decor->{VertexColor};
   if (defined ($vc)) {
      $decor->{VertexColor} = map_to_deco($vertex_map,\@{$bounded->VERTICES},$vc,$Visual::Color::cutoff);
   }
}

# @category Visualization
# Visualize a polytope as a graph (if 1d), or as a solid object (if 2d or 3d),
# or as a Schlegel diagram (4d).
# @options %Visual::Polygons::decorations
# @options %Visual::Wire::decorations
# @options %Visual::PointSet::decorations
# @options %geometric_options
# @return Visual::Polytope

user_method VISUAL(%Visual::Polygons::decorations, %geometric_options) : CONE_DIM {
   my ($this, $decor, $geom)=@_;
   my $P=new Visual::PointSet( Name => $this->name,
                               Points => Visual::transform_float(dehomogenize(convert_to<Float>($this->VERTICES)),$geom->{Transformation},$geom->{Offset}),
                               PointLabels => $this->lookup("VERTEX_LABELS"),
                               $decor
                             );
   visualize($P);
}
precondition : CONE_DIM { $this->CONE_DIM==1 }
precondition : CONE_AMBIENT_DIM { $this->CONE_AMBIENT_DIM<=4 }


# @hide
user_method VISUAL(%Visual::Polygons::decorations, %geometric_options) : CONE_DIM {
   my ($this, $decor, $geom)=@_;
   my $G=$this->GRAPH->VISUAL(Name=>$this->name, Coord=>Visual::transform_float(dehomogenize(convert_to<Float>($this->VERTICES)),$geom->{Transformation},$geom->{Offset}), $decor);
   visualize( new Visual::Polytope(Name => $this->name, Polytope => $this, $G) );
}
precondition : CONE_DIM { $this->CONE_DIM==2 }
precondition : CONE_AMBIENT_DIM { $this->CONE_AMBIENT_DIM<=4 }
precondition : BOUNDED;

# @hide
user_method VISUAL(%Visual::Polygons::decorations, %geometric_options) : VERTICES, VIF_CYCLIC_NORMAL {
   my ($this, $decor, $geom)=@_;
   $decor->{VertexLabels} //= $this->lookup("VERTEX_LABELS");
   my $to_vis=$this;
   $decor->{FacetLabels} //= $this->lookup("FACET_LABELS") // "hidden" if ($this->CONE_DIM==4);

   
   # we also allow to bound BOUNDED polytopes
   my $bbox = $geom->{BoundingFacets};
   if (defined($bbox)) {
      my $offsets = $this->VERTICES*transpose($bbox);
      OUTER: foreach my $row (@$offsets) {
         foreach my $entry (@$row) {
            if ($entry < 0) {
               #only apply bounding facets if they have an effect
               $to_vis = apply_bounding_facets($this,$geom->{BoundingFacets});
               convert_this_decor_to_bounded_decor($this,$to_vis,$decor);
               last outer;
            }
         }
      }
   }
   # visual boundings should not alter CONE_DIM (use the full_dim option in polytope::bounding_[box]_facets)
   my $vis = $this->CONE_DIM==3
       ? new Visual::Polygon( Name => $to_vis->name,
                              Vertices => Visual::transform_float(dehomogenize(convert_to<Float>($to_vis->VERTICES)),$geom->{Transformation},$geom->{Offset}),
                              Facet => $to_vis->VIF_CYCLIC_NORMAL->[0],
                              NEdges => $to_vis->N_EDGES,
                              $decor
       )
       : new Visual::Polygons( Name => $to_vis->name,
                               Vertices => Visual::transform_float(dehomogenize(convert_to<Float>($to_vis->VERTICES)),$geom->{Transformation},$geom->{Offset}),
                               Facets => $to_vis->VIF_CYCLIC_NORMAL,
                               FacetNormals => Visual::transform_float_facets(convert_to<Float>($to_vis->FACETS),$geom->{Transformation},$geom->{Offset}),
                               FacetNeighbors => $to_vis->NEIGHBOR_FACETS_CYCLIC_NORMAL,
                               Closed => 1,
                               NEdges => $to_vis->N_EDGES,
                               $decor
       );
   visualize( new Visual::Polytope(Name => $this->name, Polytope => $this, $vis));
}
precondition : CONE_DIM { $this->CONE_DIM>=3 }
precondition : CONE_AMBIENT_DIM { $this->CONE_AMBIENT_DIM<=4 }
precondition : BOUNDED;


# @hide @notest
user_method VISUAL(%Visual::Polygons::decorations) : CONE_AMBIENT_DIM {
   my ($this, $decor)=@_;
   $this->SCHLEGEL({},{},$decor);
}
precondition : CONE_DIM { $this->CONE_DIM==5 }
precondition : CONE_AMBIENT_DIM { $this->CONE_AMBIENT_DIM==5 }
precondition : BOUNDED;


# @hide
user_method VISUAL(%Visual::Polygons::decorations, %geometric_options) : CONE_DIM {
   my ($this, $decor, $geom)=@_;
   # if no facets are specified we simply make a box
   # the surplus of 6/5 for the bounding box is just a random choice
   # we delete the bbox from $geom so that it doesn't get applied a second time in $bounded->VISUAL
   my $bbox = delete $geom->{BoundingFacets} // bounding_box_facets($this->VERTICES, surplus_k=>6/5, fulldim=>1);
   my $bounded = apply_bounding_facets($this,$bbox);
   $decor->{VertexLabels} //= $this->lookup("VERTEX_LABELS"); 
   $decor->{FacetLabels} //= $this->lookup("FACET_LABELS") // "hidden" if ($this->CONE_DIM==4);
   convert_this_decor_to_bounded_decor($this,$bounded,$decor);
	visualize($bounded->VISUAL($decor, $geom));
}
precondition : CONE_AMBIENT_DIM { $this->CONE_AMBIENT_DIM<=4 }
precondition : !BOUNDED;


# @hide
user_method VISUAL(%geometric_options) : FEASIBLE {
   croak("Cannot visualize an empty polytope\n");
}
precondition : !FEASIBLE;

# @hide
user_method VISUAL(%geometric_options) : CONE_AMBIENT_DIM {
   croak("Cannot visualize polytope with ambient dimension >= 5\n");
}
precondition : CONE_AMBIENT_DIM { $this->CONE_AMBIENT_DIM >= 6 }


# @category Visualization
# Visualize the dual polytope as a solid 3-d object. The polytope must be [[BOUNDED]] and [[CENTERED]].
# @options %Visual::Polygons::decorations
# @options %geometric_options
# @return Visual::Object

user_method VISUAL_DUAL(%Visual::Polygons::decorations, %geometric_options) : FACETS, FTV_CYCLIC_NORMAL {
   my ($this, $decor, $geom)=@_;
   my $P= $this->CONE_AMBIENT_DIM==3
          ? new Visual::Polygon( Name => "dual of ".$this->name,
				 Vertices => Visual::transform_float(dehomogenize(convert_to<Float>($this->FACETS)),$geom->{Transformation},$geom->{Offset}),
				 VertexLabels => $this->lookup("FACET_LABELS"),
				 Facet => $this->FTV_CYCLIC_NORMAL->[0],
				 NEdges => $this->N_EDGES,
				 $decor,
			       )
	  : new Visual::Polygons( Name => "dual of ".$this->name,
				  Vertices => Visual::transform_float(dehomogenize(convert_to<Float>($this->FACETS)),$geom->{Transformation},$geom->{Offset}),
				  VertexLabels => $this->lookup("FACET_LABELS"),
				  Facets => $this->FTV_CYCLIC_NORMAL,
                                  FacetNormals => Visual::transform_float_facets(convert_to<Float>($this->VERTICES),$geom->{Transformation},$geom->{Offset}),
				  FacetNeighbors => $this->NEIGHBOR_VERTICES_CYCLIC_NORMAL,
				  FacetLabels => $this->lookup("VERTEX_LABELS") || "hidden",
				  Closed => 1,
				  NEdges => $this->N_EDGES,
				  $decor,
				);
   visualize($P);
}
precondition : CONE_AMBIENT_DIM { $this->CONE_AMBIENT_DIM==3 || $this->CONE_AMBIENT_DIM==4 }
precondition : BOUNDED;
precondition : CENTERED;

}

# @topic objects/Visual::Cone
# @category Visualization
# Visualization of a Cone as a graph (if 1d), or as a solid object (if 2d or 3d)
# @super Visual::Container
# @relates objects/Polytope

package Visual::Cone;
use Polymake::Struct (
   [ '@ISA' => 'Container' ],
   [ '$Cone' => '#%' ],
);

object Cone {

# @category Visualization
# Visualizes the cone, intersected with the unit ball.
# @options %Visual::Polygons::decorations
# @options %geometric_options_linear
# @return Visual::Cone

user_method VISUAL(%Visual::Polygons::decorations, %geometric_options_linear, {CutOff => $Visual::Color::cutoff}, {BoundingFacets => undef}) : RAYS, LINEALITY_SPACE {
   my ($this,$decor,$geom,$cutoff_decor, $bb)=@_;
   $decor->{VertexStyle} ||= "hidden";
   $cutoff_decor=$cutoff_decor->{CutOff};
   my $cutoff_decor3d=$cutoff_decor;
   my $cutoff_decor2d=$cutoff_decor;
   unless (is_hash($cutoff_decor)) {
     $cutoff_decor3d={ FacetColor => $cutoff_decor };
     $cutoff_decor2d={ EdgeColor => $cutoff_decor };
   }
   my $d=$this->RAYS->cols;
   my $rays=normalized(convert_to<Float>($this->RAYS));
   my $lineality = convert_to<Float>($this->LINEALITY_SPACE);
   if(!(defined $bb->{BoundingFacets})) {
       my $bbox = $lineality->rows > 0 ?
           polytope::bounding_box_facets<Float>(ones_vector<Float>() | $rays, surplus_k=>6/5, fulldim=>1)
           : undef;
       $bb->{BoundingFacets} = $bbox;
   }
   my $zero=new Vector<Float>($d);
   my @cells_visual = ();

   my $all_ones=ones_vector<Float>($this->N_RAYS+1);
   my $v= $all_ones | ($rays / $zero);

   my $ftv=new IncidenceMatrix($this->FACETS_THRU_RAYS->rows()+1,$this->N_RAYS+1);
   $ftv->minor(~[$this->FACETS_THRU_RAYS->rows()],~[$this->RAYS->rows()])=$this->FACETS_THRU_RAYS;
   for(my $j=0; $j < $ftv->rows()-1;++$j){
       $ftv->row($j) += $this->RAYS->rows();
   }
   $ftv->row($this->FACETS_THRU_RAYS->rows()) += new Set(0..$this->RAYS->rows()-1);

   my $p=new polytope::Polytope<Float>(VERTICES => $v, LINEALITY_SPACE => zero_vector<Float>() | $lineality, FACETS_THRU_VERTICES=>$ftv);
   
   my $pv= ($lineality->rows > 0 && $this->CONE_DIM > 1) ?
      $p->VISUAL( $decor , $geom, BoundingFacets=>$bb->{BoundingFacets} ) :
      $p->VISUAL( $decor , $geom);
   my (@cutoff_attrs);
   my $dim=$p->DIM;
   if ($dim==3) {
     while (my @kv=each %$cutoff_decor3d) {
       my ($attr, $value)=@kv;
       push @cutoff_attrs, $attr, sub { $_[0] < $p->FACETS->rows && $p->FACETS->[shift]->[0] ? $value : undef };
     }
   }
   $pv->basis_solid->merge(@cutoff_attrs);
   
   visualize(new Visual::Cone( Name => "fan:".$this->name,
                               Cone => $this,
                                  $pv));
}

}

# Local Variables:
# mode: perl
# cperl-indent-level:3
# indent-tabs-mode:nil
# End:
