/****************************************************************************
*                parstxtr.c
*
*  This module parses textures and atmosphere effects.
*
*  from Persistence of Vision(tm) Ray Tracer
*  Copyright 1996 Persistence of Vision Team
*---------------------------------------------------------------------------
*  NOTICE: This source code file is provided so that users may experiment
*  with enhancements to POV-Ray and to port the software to platforms other
*  than those supported by the POV-Ray Team.  There are strict rules under
*  which you are permitted to use this file.  The rules are in the file
*  named POVLEGAL.DOC which should be distributed with this file. If
*  POVLEGAL.DOC is not available or for more info please contact the POV-Ray
*  Team Coordinator by leaving a message in CompuServe's Graphics Developer's
*  Forum.  The latest version of POV-Ray may be found there as well.
*
* This program is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
*
*****************************************************************************/

#include "frame.h"
#include "vector.h"
#include "povproto.h"
#include "parse.h"
#include "parstxtr.h"
#include "colour.h"
#include "express.h"
#include "gif.h"
#include "halos.h"
#include "iff.h"
#include "image.h"
#include "matrices.h" 
#include "normal.h"   
#include "pigment.h"  
#include "povray.h"
#include "pgm.h"
#include "ppm.h"
#include "targa.h"
#include "png_pov.h"
#include "texture.h"
#include "tokenize.h"


/*****************************************************************************
* Local preprocessor defines
******************************************************************************/

#define ADD_TNORMAL if (Tnormal == NULL) {if ((Default_Texture->Tnormal) != NULL) \
 Tnormal = Copy_Tnormal ((Default_Texture->Tnormal)); else Tnormal = Create_Tnormal ();\
 Texture->Tnormal=Tnormal;};

/*****************************************************************************
* Local typedefs
******************************************************************************/



/*****************************************************************************
* Local variables
******************************************************************************/

TEXTURE *Default_Texture;



/*****************************************************************************
* Static functions
******************************************************************************/

static void Parse_Bump_Map PARAMS((TNORMAL *Tnormal));
static void Parse_Image_Map PARAMS((PIGMENT *Pigment));
static void Parse_Pattern PARAMS((TPATTERN *New, int TPat_Type));
static TEXTURE *Parse_Vers1_Texture PARAMS((void));
static TEXTURE *Parse_Tiles PARAMS((void));
static TEXTURE *Parse_Material_Map PARAMS((void));
static void Parse_Texture_Transform PARAMS((TEXTURE *Texture));
static TURB *Check_Turb PARAMS((WARP **Warps_Ptr));
static void Parse_Warp PARAMS((WARP **Warp_Ptr));
static void Check_BH_Parameters PARAMS((BLACK_HOLE *bh));



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

IMAGE *Parse_Image (Legal)
  int Legal;
  {
   IMAGE *Image;
   VECTOR Local_Vector;
   char *Name;

   Image = Create_Image ();

   Image->Image_Type = Legal;

   if (Legal & GRAD_FILE)
     {
      EXPECT
        CASE_VECTOR
          Warn(1.5, "Should use map_type keyword and/or eliminate orientation.");
          Have_Vector = FALSE;
          Parse_Vector_Float (Local_Vector);
          if (Have_Vector)
            Assign_Vector(Image->Gradient,Local_Vector);
          else
            Image->Map_Type = (int)Local_Vector[X];
        END_CASE

        OTHERWISE
          UNGET
          EXIT
        END_CASE
      END_EXPECT
     }

   EXPECT
     CASE (IFF_TOKEN)
       Image->File_Type = IFF_FILE;
       Name=Parse_String();
       Read_Iff_Image (Image, Name);
       POV_FREE(Name);
       EXIT
     END_CASE

     CASE (GIF_TOKEN)
       Image->File_Type = GIF_FILE;
       Name=Parse_String();
       Read_Gif_Image(Image, Name);
       POV_FREE(Name);
       EXIT
     END_CASE

     CASE (POT_TOKEN)
       Image->File_Type = POT_FILE;
       Name=Parse_String();
       Read_Gif_Image(Image, Name);
       POV_FREE(Name);
       EXIT
     END_CASE

     CASE (SYS_TOKEN)
       Image->File_Type = SYS_FILE;
       Name=Parse_String();
       READ_SYS_IMAGE(Image, Name);
       POV_FREE(Name);
       EXIT
     END_CASE

     CASE (TGA_TOKEN)
       Image->File_Type = TGA_FILE;
       Name=Parse_String();
       Read_Targa_Image(Image, Name);
       POV_FREE(Name);
       EXIT
     END_CASE

     CASE (PNG_TOKEN)
       Image->File_Type = PNG_FILE;
       Name=Parse_String();
       Read_Png_Image(Image, Name);
       POV_FREE(Name);
       EXIT
     END_CASE

     CASE (PGM_TOKEN)
       Image->File_Type = PGM_FILE;
       Name=Parse_String();
       Read_PGM_Image(Image, Name);
       POV_FREE(Name);
       EXIT
     END_CASE

     CASE (PPM_TOKEN)
       Image->File_Type = PPM_FILE;
       Name=Parse_String();
       Read_PPM_Image(Image, Name);
       POV_FREE(Name);
       EXIT
     END_CASE

     OTHERWISE
       Parse_Error_Str ("map file spec");
     END_CASE
   END_EXPECT

   if (!(Image->File_Type & Legal))
     Error ("File type not supported here.");
   return (Image);
  }



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static void Parse_Image_Map (Pigment)
PIGMENT *Pigment;
  {
   int reg;
   IMAGE *Image;
   
   Parse_Begin();

   Image = Parse_Image (IMAGE_FILE);
   Image->Use_Colour_Flag = TRUE;

   EXPECT                   /* Look for image_attribs */
     CASE (ONCE_TOKEN)
       Image->Once_Flag=TRUE;
     END_CASE

     CASE (INTERPOLATE_TOKEN)
       Image->Interpolation_Type = (int)Parse_Float();
     END_CASE

     CASE (MAP_TYPE_TOKEN)
       Image->Map_Type = (int) Parse_Float ();
     END_CASE

     CASE (USE_COLOUR_TOKEN)
       Image->Use_Colour_Flag = TRUE;
     END_CASE

     CASE (USE_INDEX_TOKEN)
       Image->Use_Colour_Flag = FALSE;
     END_CASE

     CASE (ALPHA_TOKEN)
       Warn(1.55, "Keyword ALPHA discontinued. Use FILTER instead.");

     CASE (COLOUR_KEY_TOKEN)
       switch(Token.Function_Id)
         {
          case FILTER_TOKEN:
            EXPECT
              CASE (ALL_TOKEN)
                {
                 DBL filter;
                 filter = Parse_Float();
                 for (reg = 0 ; reg < Image->Colour_Map_Size ; reg++)
                   Image->Colour_Map[reg].Filter
                       = (unsigned short) (filter *255.0);
                }
                EXIT
              END_CASE

              OTHERWISE
                UNGET
                reg = (int)(Parse_Float() + 0.01);
                if (Image->Colour_Map == NULL)
                  Not_With ("filter","non color-mapped image");
                if ((reg < 0) || (reg >= Image->Colour_Map_Size))
                  Error ("FILTER color register value out of range.");

                Parse_Comma();
                Image->Colour_Map[reg].Filter
                       = (unsigned short) (255.0 * Parse_Float());
                EXIT
              END_CASE

            END_EXPECT
            Pigment->Flags |= HAS_FILTER;
            break;

          case TRANSMIT_TOKEN:
            EXPECT
              CASE (ALL_TOKEN)
                {
                 DBL transmit;
                 transmit = Parse_Float();
                 for (reg = 0 ; reg < Image->Colour_Map_Size ; reg++)
                   Image->Colour_Map[reg].Transmit
                       = (unsigned short) (transmit *255.0);
                }
                EXIT
              END_CASE

              OTHERWISE
                UNGET
                reg = (int)(Parse_Float() + 0.01);
                if (Image->Colour_Map == NULL)
                  Not_With ("transmit","non color-mapped image");
                if ((reg < 0) || (reg >= Image->Colour_Map_Size))
                  Error ("TRANSMIT color register value out of range.");

                Parse_Comma();
                Image->Colour_Map[reg].Transmit
                       = (unsigned short) (255.0 * Parse_Float());
                EXIT
              END_CASE

            END_EXPECT
            Pigment->Flags |= HAS_FILTER;
            break;

          default:
            UNGET
            Parse_Error_Str ("filter or transmit");
            break;
         }
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT
   
   Pigment->Vals.Image=Image;
   Parse_End();
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static void Parse_Bump_Map (Tnormal)
TNORMAL *Tnormal;
  {
   IMAGE *Image;

   Parse_Begin();

   Image = Parse_Image(IMAGE_FILE);
   Image->Use_Colour_Flag = TRUE;

   EXPECT
     CASE (ONCE_TOKEN)
       Image->Once_Flag=TRUE;
     END_CASE

     CASE (MAP_TYPE_TOKEN)
       Image->Map_Type = (int) Parse_Float ();
     END_CASE

     CASE (INTERPOLATE_TOKEN)
       Image->Interpolation_Type = (int)Parse_Float();
     END_CASE

     CASE (BUMP_SIZE_TOKEN)
       Tnormal->Amount = Parse_Float ();
     END_CASE

     CASE (USE_COLOUR_TOKEN)
       Image->Use_Colour_Flag = TRUE;
     END_CASE

     CASE (USE_INDEX_TOKEN)
       Image->Use_Colour_Flag = FALSE;
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT

   Tnormal->Vals.Image=Image;

   Parse_End();
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Parse_Pigment (Pigment_Ptr)
  PIGMENT **Pigment_Ptr;
  {
   EXPECT            /* Look for [pigment_id] */
     CASE (PIGMENT_ID_TOKEN)
       Destroy_Pigment(*Pigment_Ptr);
       *Pigment_Ptr = Copy_Pigment ((PIGMENT *) Token.Constant_Data);
       EXIT
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT    /* End pigment_id */
   
   Parse_Pattern((TPATTERN *)(*Pigment_Ptr),PIGMENT_TYPE);

   if (Not_In_Default && ((*Pigment_Ptr)->Type == NO_PATTERN))
   {
     Warn(1.7, "Pigment type unspecified or not 1st item.");
   }
  }



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static void Parse_Pattern (New, TPat_Type)
  TPATTERN *New;
  int TPat_Type;
  {
   VECTOR Local_Vector;
   COLOUR Local_Colour;
   MATRIX Local_Matrix;
   TRANSFORM Local_Trans;
   TURB *Local_Turb;
   unsigned short Old_Type=New->Type;
   IMAGE *Old_Image = NULL;

   if (Old_Type==BITMAP_PATTERN)
   {
     Old_Image=New->Vals.Image;
   }

   EXPECT
     CASE (AGATE_TOKEN)
       New->Type = AGATE_PATTERN;
       Check_Turb(&(New->Warps));
       New->Vals.Agate_Turb_Scale = 1.0;
       EXIT
     END_CASE

     CASE (BOZO_TOKEN)
       New->Type = BOZO_PATTERN;
       EXIT
     END_CASE

     CASE (GRANITE_TOKEN)
       New->Type = GRANITE_PATTERN;
       EXIT
     END_CASE

     CASE (LEOPARD_TOKEN)
       New->Type = LEOPARD_PATTERN;
       EXIT
     END_CASE

     CASE (MARBLE_TOKEN)
       New->Type = MARBLE_PATTERN;
       New->Wave_Type = TRIANGLE_WAVE;
       EXIT
     END_CASE

     CASE (MANDEL_TOKEN)
       New->Type = MANDEL_PATTERN;
       New->Vals.Iterations = (int)Parse_Float();
       EXIT
     END_CASE

     CASE (ONION_TOKEN)
       New->Type = ONION_PATTERN;
       EXIT
     END_CASE

     CASE (PATTERN1_TOKEN)
       New->Type = PATTERN1_PATTERN;
       EXIT
     END_CASE

     CASE (PATTERN2_TOKEN)
       New->Type = PATTERN2_PATTERN;
       EXIT
     END_CASE

     CASE (PATTERN3_TOKEN)
       New->Type = PATTERN3_PATTERN;
       EXIT
     END_CASE

     CASE (BUMPY1_TOKEN)
       if (TPat_Type != NORMAL_TYPE)
       {
         Only_In("bumpy1","normal");
       }
       New->Type = BUMPY1_PATTERN;
       EXIT
     END_CASE

     CASE (BUMPY2_TOKEN)
       if (TPat_Type != NORMAL_TYPE)
       {
         Only_In("bumpy2","normal");
       }
       New->Type = BUMPY2_PATTERN;
       EXIT
     END_CASE

     CASE (BUMPY3_TOKEN)
       if (TPat_Type != NORMAL_TYPE)
       {
         Only_In("bumpy3","normal");
       }
       New->Type = BUMPY3_PATTERN;
       EXIT
     END_CASE

     CASE (SPIRAL1_TOKEN)
       New->Type = SPIRAL1_PATTERN;
       New->Vals.Arms = (short)Parse_Float ();
       New->Wave_Type = TRIANGLE_WAVE;
       EXIT
     END_CASE

     CASE (SPIRAL2_TOKEN)
       New->Type = SPIRAL2_PATTERN;
       New->Vals.Arms = (short)Parse_Float ();
       New->Wave_Type = TRIANGLE_WAVE;
       EXIT
     END_CASE

     CASE (SPOTTED_TOKEN)
       New->Type = SPOTTED_PATTERN;
       EXIT
     END_CASE

     CASE (WOOD_TOKEN)
       New->Type = WOOD_PATTERN;
       New->Wave_Type = TRIANGLE_WAVE;
       EXIT
     END_CASE

     CASE (GRADIENT_TOKEN)
       New->Type = GRADIENT_PATTERN;
       Parse_Vector (New->Vals.Gradient);
       EXIT
     END_CASE

     CASE (RADIAL_TOKEN)
       New->Type = RADIAL_PATTERN;
       EXIT
     END_CASE

     CASE (CRACKLE_TOKEN)
       New->Type = CRACKLE_PATTERN;
       EXIT
     END_CASE

     CASE_COLOUR
       if (TPat_Type != PIGMENT_TYPE)
       {
         Only_In("color","pigment");
       }
       New->Type = PLAIN_PATTERN;
       Parse_Colour (((PIGMENT *)New)->Colour);
       EXIT
     END_CASE

     CASE (CHECKER_TOKEN)
       New->Type = CHECKER_PATTERN;
       New->Frequency = 0.0;
       Destroy_Blend_Map(New->Blend_Map);
       New->Blend_Map = Parse_Blend_List(2,&Check_Default_Map,TPat_Type);
       EXIT
     END_CASE

     CASE (BRICK_TOKEN)
       if (New->Type!=BRICK_PATTERN)
       {
         Make_Vector(New->Vals.Brick.Size,8.0,3.0,4.5);
         New->Vals.Brick.Mortar=0.5-Small_Tolerance*2.0;
       New->Type = BRICK_PATTERN;
       }
       New->Frequency = 0.0;
       Destroy_Blend_Map(New->Blend_Map);
       New->Blend_Map = Parse_Blend_List(2,&Brick_Default_Map,TPat_Type);
       EXIT
     END_CASE

     CASE (HEXAGON_TOKEN)
       New->Type = HEXAGON_PATTERN;
       New->Frequency = 0.0;
       Destroy_Blend_Map(New->Blend_Map);
       New->Blend_Map = Parse_Blend_List(3,&Hex_Default_Map,TPat_Type);
       EXIT
     END_CASE

     CASE (IMAGE_MAP_TOKEN)
       if (TPat_Type != PIGMENT_TYPE)
       {
         Only_In("image_map","pigment");
       }

       if (Old_Type==BITMAP_PATTERN) 
       {
         Destroy_Image(Old_Image);
       }   

       New->Type = BITMAP_PATTERN;
       New->Frequency = 0.0;
       Parse_Image_Map ((PIGMENT *)New);
       EXIT
     END_CASE

     CASE (BUMP_MAP_TOKEN)
       if (TPat_Type != NORMAL_TYPE)
       {
         Only_In("bump_map","normal");
       }
 
       if (Old_Type==BITMAP_PATTERN) 
       {
         Destroy_Image(Old_Image);
       }   

       New->Type = BITMAP_PATTERN;
       New->Frequency = 0.0;
       Parse_Bump_Map ((TNORMAL *)New);
       EXIT
     END_CASE

     CASE (WAVES_TOKEN)
       New->Type = WAVES_PATTERN;
       EXIT
     END_CASE

     CASE (RIPPLES_TOKEN)
       New->Type = RIPPLES_PATTERN;
       EXIT
     END_CASE

     CASE (WRINKLES_TOKEN)
       New->Type = WRINKLES_PATTERN;
       EXIT
     END_CASE

     CASE (BUMPS_TOKEN)
       New->Type = BUMPS_PATTERN;
       EXIT
     END_CASE

     CASE (DENTS_TOKEN)
       New->Type = DENTS_PATTERN;
       EXIT
     END_CASE

     CASE (QUILTED_TOKEN)
       New->Type = QUILTED_PATTERN;
       New->Vals.Quilted.Control0 = 1.0;
       New->Vals.Quilted.Control1 = 1.0;
       New->Frequency = 0.0;
       EXIT
     END_CASE

     CASE (AVERAGE_TOKEN)
       New->Type = AVERAGE_PATTERN;
       EXIT
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT     /* Concludes pattern_body */

   if ((Old_Type==BITMAP_PATTERN) && (New->Type!=BITMAP_PATTERN))
   {
     Destroy_Image(Old_Image);
   }   

   if (TPat_Type == NORMAL_TYPE) 
    {
     Parse_Comma();
     ((TNORMAL *)New)->Amount = Allow_Float (((TNORMAL *)New)->Amount );
    }

   EXPECT         /* Look for pattern_modifier */
     CASE (TURBULENCE_TOKEN)
       Local_Turb=Check_Turb(&(New->Warps));
       Parse_Vector_Float(Local_Turb->Turbulence);
     END_CASE

     CASE (COLOUR_MAP_TOKEN)
       if (TPat_Type != PIGMENT_TYPE)
       {
         Only_In("color_map","pigment");
       }
       if (New->Type == CHECKER_PATTERN ||
           New->Type == BRICK_PATTERN ||
           New->Type == HEXAGON_PATTERN ||
           New->Type == PLAIN_PATTERN ||
           New->Type == BITMAP_PATTERN)
         Warn(1.5, "Cannot use color map with this pigment type.");
       Destroy_Blend_Map(New->Blend_Map);
       New->Blend_Map = Parse_Colour_Map ();
     END_CASE

     CASE (PIGMENT_MAP_TOKEN)
       if (TPat_Type != PIGMENT_TYPE)
       {
         Only_In("pigment_map","pigment");
       }
       if (New->Type == CHECKER_PATTERN ||
           New->Type == BRICK_PATTERN ||
           New->Type == HEXAGON_PATTERN ||
           New->Type == PLAIN_PATTERN ||
           New->Type == BITMAP_PATTERN)
         Not_With ("pigment_map","this pigment type");
       Destroy_Blend_Map(New->Blend_Map);
       New->Blend_Map = Parse_Blend_Map (PIGMENT_TYPE,New->Type);
     END_CASE

     CASE (SLOPE_MAP_TOKEN)
       if (TPat_Type != NORMAL_TYPE)
       {
         Only_In("slope_map","normal");
       }
       if (New->Type == CHECKER_PATTERN ||
           New->Type == BRICK_PATTERN ||
           New->Type == HEXAGON_PATTERN ||
           New->Type == PLAIN_PATTERN ||
           New->Type == AVERAGE_PATTERN ||
           New->Type == BITMAP_PATTERN)
         Not_With ("slope_map","this normal type");
       Destroy_Blend_Map(New->Blend_Map);
       New->Blend_Map = Parse_Blend_Map (SLOPE_TYPE,New->Type);
     END_CASE

     CASE (NORMAL_MAP_TOKEN)
       if (TPat_Type != NORMAL_TYPE)
       {
         Only_In("normal_map","normal");
       }
       if (New->Type == CHECKER_PATTERN ||
           New->Type == BRICK_PATTERN ||
           New->Type == HEXAGON_PATTERN ||
           New->Type == PLAIN_PATTERN ||
           New->Type == BITMAP_PATTERN)
         Not_With ("normal_map","this normal type");
       Destroy_Blend_Map(New->Blend_Map);
       New->Blend_Map = Parse_Blend_Map (NORMAL_TYPE,New->Type);
     END_CASE

     CASE (TEXTURE_MAP_TOKEN)
       if (TPat_Type != TEXTURE_TYPE)
       {
         Only_In("texture_map","texture");
       }
       if (New->Type == CHECKER_PATTERN ||
           New->Type == BRICK_PATTERN ||
           New->Type == HEXAGON_PATTERN ||
           New->Type == PLAIN_PATTERN ||
           New->Type == BITMAP_PATTERN)
         Not_With ("texture_map","this pattern type");
       Destroy_Blend_Map(New->Blend_Map);
       New->Blend_Map = Parse_Blend_Map (TEXTURE_TYPE,New->Type);
     END_CASE

     CASE (QUICK_COLOUR_TOKEN)
       if (TPat_Type != PIGMENT_TYPE)
       {
         Only_In("quick_color","pigment");
       }
       Parse_Colour (Local_Colour);
       if (opts.Quality_Flags & Q_QUICKC)
       {
          New->Type = PLAIN_PATTERN;
          Assign_Colour(((PIGMENT *)New)->Colour,Local_Colour);
       }
     END_CASE

     CASE (CONTROL0_TOKEN)
       if (New->Type == QUILTED_PATTERN) 
       {
         New->Vals.Quilted.Control0 = Parse_Float ();
       }
       else
       {
         Not_With ("control0","this normal");
       }
     END_CASE

     CASE (CONTROL1_TOKEN)
       if (New->Type == QUILTED_PATTERN)
       {
         New->Vals.Quilted.Control1 = Parse_Float ();
       }
       else
       {
         Not_With ("control1","this normal");
       }
     END_CASE

     CASE (OCTAVES_TOKEN)
       Local_Turb=Check_Turb(&(New->Warps));
       Local_Turb->Octaves = (int)Parse_Float();
       if(Local_Turb->Octaves < 1)
          Local_Turb->Octaves = 1;
       if(Local_Turb->Octaves > 10)  /* Avoid DOMAIN errors */
          Local_Turb->Octaves = 10;
     END_CASE

     CASE (OMEGA_TOKEN)
       Local_Turb=Check_Turb(&(New->Warps));
       Local_Turb->Omega = Parse_Float();
     END_CASE

     CASE (LAMBDA_TOKEN)
       Local_Turb=Check_Turb(&(New->Warps));
       Local_Turb->Lambda = Parse_Float();
     END_CASE

     CASE (FREQUENCY_TOKEN)
       New->Frequency = Parse_Float();
     END_CASE

     CASE (RAMP_WAVE_TOKEN)
       New->Wave_Type = RAMP_WAVE;
     END_CASE

     CASE (TRIANGLE_WAVE_TOKEN)
       New->Wave_Type = TRIANGLE_WAVE;
     END_CASE

     CASE (SINE_WAVE_TOKEN)
       New->Wave_Type = SINE_WAVE;
     END_CASE

     CASE (SCALLOP_WAVE_TOKEN)
       New->Wave_Type = SCALLOP_WAVE;
     END_CASE

     CASE (PHASE_TOKEN)
       New->Phase = Parse_Float();
     END_CASE

     CASE (BUMP_SIZE_TOKEN)
       if (TPat_Type != NORMAL_TYPE)
          Only_In ("bump_size","normal");
       ((TNORMAL *)New)->Amount = Parse_Float ();
     END_CASE

     CASE (AGATE_TURB_TOKEN)
       if (New->Type != AGATE_PATTERN)
          Not_With ("agate_turb","non-agate");
       New->Vals.Agate_Turb_Scale = Parse_Float();
       Check_Turb(&(New->Warps));   /* agate needs Octaves, Lambda etc. */
     END_CASE

     CASE (BRICK_SIZE_TOKEN)
       if (New->Type != BRICK_PATTERN)
          Not_With ("brick_size","non-brick");
       Parse_Vector(New->Vals.Brick.Size);
     END_CASE

     CASE (MORTAR_TOKEN)
       if (New->Type != BRICK_PATTERN)
          Not_With ("mortar","non-brick");
       New->Vals.Brick.Mortar = Parse_Float()-Small_Tolerance*2.0;
     END_CASE

     CASE (WARP_TOKEN)
       Parse_Warp(&(New->Warps));
     END_CASE

     CASE (TRANSLATE_TOKEN)
       Parse_Vector (Local_Vector);
       Translate_Tpattern (New, Local_Vector);
     END_CASE

     CASE (ROTATE_TOKEN)
       Parse_Vector (Local_Vector);
       Rotate_Tpattern (New, Local_Vector);
     END_CASE

     CASE (SCALE_TOKEN)
       Parse_Scale_Vector (Local_Vector);
       Scale_Tpattern (New, Local_Vector);
     END_CASE

     CASE (MATRIX_TOKEN)
       Parse_Matrix(Local_Matrix);
       Compute_Matrix_Transform(&Local_Trans, Local_Matrix);
       Transform_Tpattern (New, &Local_Trans);
     END_CASE

     CASE (TRANSFORM_TOKEN)
       GET(TRANSFORM_ID_TOKEN)
       Transform_Tpattern (New, (TRANSFORM *)Token.Constant_Data);
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT

   if ((TPat_Type==TEXTURE_TYPE) && (New->Type!=PLAIN_PATTERN) &&
       (New->Blend_Map==NULL))
   {
      Error("Patterned texture must have texture_map.");
   }
  }



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Parse_Tnormal (Tnormal_Ptr)
  TNORMAL **Tnormal_Ptr;
  {
   EXPECT            /* Look for [tnormal_id] */
     CASE (TNORMAL_ID_TOKEN)
       Destroy_Tnormal(*Tnormal_Ptr);
       *Tnormal_Ptr = Copy_Tnormal ((TNORMAL *) Token.Constant_Data);
       EXIT
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT    /* End [tnormal_id] */

   if (*Tnormal_Ptr == NULL)
     if ((Default_Texture->Tnormal) != NULL)
       *Tnormal_Ptr = Copy_Tnormal ((Default_Texture->Tnormal));
     else
       *Tnormal_Ptr = Create_Tnormal ();

   Parse_Pattern((TPATTERN *)*Tnormal_Ptr,NORMAL_TYPE);
  }



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Parse_Finish (Finish_Ptr)
  FINISH **Finish_Ptr;
  {
   COLOUR Temp_Colour;
   FINISH *New;
   VECTOR Local_Vector;

   Parse_Begin ();

   EXPECT        /* Look for zero or one finish_id */
     CASE (FINISH_ID_TOKEN)
       Destroy_Finish(*Finish_Ptr);
       *Finish_Ptr = Copy_Finish ((FINISH *) Token.Constant_Data);
       EXIT
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT    /* End finish_id */

   New = *Finish_Ptr;

   EXPECT        /* Look for zero or more finish_body */
     CASE (AMBIENT_TOKEN)
       Parse_Colour(Temp_Colour);
       New->Ambient[RED]   = Temp_Colour[RED];
       New->Ambient[GREEN] = Temp_Colour[GREEN];
       New->Ambient[BLUE]  = Temp_Colour[BLUE];
     END_CASE

     CASE (BRILLIANCE_TOKEN)
       New->Brilliance = Parse_Float ();
     END_CASE

     CASE (DIFFUSE_TOKEN)
       New->Diffuse = Parse_Float ();
     END_CASE

     CASE (REFLECTION_TOKEN)
       Parse_Colour(Temp_Colour);
       New->Reflection[RED]   = Temp_Colour[RED];
       New->Reflection[GREEN] = Temp_Colour[GREEN];
       New->Reflection[BLUE]  = Temp_Colour[BLUE];
     END_CASE

     CASE (REFRACTION_TOKEN)
       New->Refraction = Parse_Float ();
     END_CASE

     CASE (IOR_TOKEN)
       New->Index_Of_Refraction = Parse_Float ();
     END_CASE

     CASE (PHONG_TOKEN)
       New->Phong = Parse_Float ();
     END_CASE

     CASE (PHONG_SIZE_TOKEN)
       New->Phong_Size = Parse_Float ();
     END_CASE

     CASE (SPECULAR_TOKEN)
       New->Specular = Parse_Float ();
     END_CASE

     CASE (ROUGHNESS_TOKEN)
       New->Roughness = Parse_Float ();
       if (New->Roughness != 0.0)
       New->Roughness = 1.0/New->Roughness; /* CEY 12/92 */
       else
         Warn(0.0, "Zero roughness used.");
     END_CASE

     CASE (METALLIC_TOKEN)
       New->Metallic = 1.0;
       EXPECT
         CASE_FLOAT
           New->Metallic = Parse_Float();
           EXIT
         END_CASE

         OTHERWISE
           UNGET
           EXIT
         END_CASE
       END_EXPECT
     END_CASE

     CASE (CAUSTICS_TOKEN)
       New->Caustics = Parse_Float() * 45.0;
     END_CASE

     CASE (CRAND_TOKEN)
       New->Crand = Parse_Float();
     END_CASE

     CASE (IRID_TOKEN)                     /* DMF */
       Parse_Begin();
       New->Irid = Parse_Float();

       EXPECT
         CASE (THICKNESS_TOKEN)           /* DMF */
           New->Irid_Film_Thickness = Parse_Float();
         END_CASE

         CASE (TURBULENCE_TOKEN)                /* DMF */
           Parse_Vector_Float(Local_Vector);
           New->Irid_Turb = Local_Vector[X];
         END_CASE

         OTHERWISE
           UNGET
           EXIT
         END_CASE
       END_EXPECT
       Parse_End();
     END_CASE

     CASE (FADE_DISTANCE_TOKEN)
       New->Fade_Distance = Parse_Float();
     END_CASE

     CASE (FADE_POWER_TOKEN)
       New->Fade_Power = Parse_Float();
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT    /* End of finish_body */

   EXPECT        /* Look for finish_mods */

/*   CASE none implemented
     END_CASE     */

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT    /* End of finish_mods */

   Parse_End ();
  }



/*****************************************************************************
*
* FUNCTION
*
*   Parse_Halo
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Zsolt Szalavari
*
* DESCRIPTION
*
* CHANGES
*
*   Aug 1995 : Added and modified. [DB]
*
******************************************************************************/

void Parse_Halo (Halo_Ptr)
HALO **Halo_Ptr;
{
  int add_new_halo, new_color_map, i;
  SNGL *w;
  VECTOR Local_Vector;
  MATRIX Local_Matrix;
  TRANSFORM Local_Trans;
  HALO *New, *Next_Halo;

  Experimental_Flag |= EF_HALOS;

  add_new_halo = TRUE;

  new_color_map = FALSE;

  Next_Halo = *Halo_Ptr;

  Parse_Begin();

  /* Look for [halo_id] */

  EXPECT
    CASE (HALO_ID_TOKEN)
      add_new_halo = FALSE;
      EXIT
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  if (add_new_halo)
  {
    if ((Default_Texture->Halo) != NULL)
    {
      New = Copy_Halo((Default_Texture->Halo));
    }
    else
    {
      New = Create_Halo();
    }
  }
  else
  {
    New = Copy_Halo((HALO *)Token.Constant_Data);
  }

  EXPECT
    /* Look for halo rendering type. */

    CASE (ATTENUATING_TOKEN)
      New->Rendering_Type = HALO_ATTENUATING;
    END_CASE

    CASE (EMITTING_TOKEN)
      New->Rendering_Type = HALO_EMITTING;
    END_CASE

    CASE (GLOWING_TOKEN)
      New->Rendering_Type = HALO_GLOWING;
    END_CASE

    CASE (DUST_TOKEN)
      New->Rendering_Type = HALO_DUST;
    END_CASE

    CASE (VOLUME_RENDERED_TOKEN)
      New->Rendering_Type = HALO_VOLUME_RENDERED;
    END_CASE

    CASE (VOL_WITH_LIGHT_TOKEN)
      New->Rendering_Type = HALO_VOL_REND_WITH_LIGHT;
    END_CASE

    /* Look for halo density function. */

    CASE (CONSTANT_TOKEN)
      New->Type = HALO_CONSTANT;
    END_CASE

    CASE (LINEAR_TOKEN)
      New->Type = HALO_LINEAR;
    END_CASE

    CASE (CUBIC_TOKEN)
      New->Type = HALO_CUBIC;
    END_CASE

    CASE (POLY_TOKEN)
      New->Type = HALO_POLY;
    END_CASE

    CASE (VOLUME_OBJECT_TOKEN)
      New->Type = HALO_VOLUME_OBJECT;
    END_CASE

    /* Look for halo mapping type. */

    CASE (PLANAR_MAPPING_TOKEN)
      New->Mapping_Type = HALO_PLANAR_MAP;
    END_CASE

    CASE (SPHERICAL_MAPPING_TOKEN)
      New->Mapping_Type = HALO_SPHERICAL_MAP;
    END_CASE

    CASE (CYLINDRICAL_MAPPING_TOKEN)
      New->Mapping_Type = HALO_CYLINDRICAL_MAP;
    END_CASE

    CASE (BOX_MAPPING_TOKEN)
      New->Mapping_Type = HALO_BOX_MAP;
    END_CASE

    /* Look for halo sampling stuff. */

    CASE (SAMPLES_TOKEN)
      New->Samples = Parse_Float();
    END_CASE

    CASE (AA_LEVEL_TOKEN)
      if ((New->AA_Level = (int)Parse_Float()) < 0)
      {
        Error("Illegal anti-aliasing level value in halo.\n");
      }
    END_CASE

    CASE (AA_THRESHOLD_TOKEN)
      New->AA_Threshold = Parse_Float();
    END_CASE

    CASE (JITTER_TOKEN)
      New->Jitter = Parse_Float();
    END_CASE

    /* Look for other identifiers. */

    CASE (DUST_TYPE_TOKEN)
      New->Dust_Type = (char)Parse_Float();
      if ((New->Dust_Type < 1) || (New->Dust_Type > SCATTERING_TYPES))
      {
        Warn(0.0, "Unknown halo dust type.");
      }
    END_CASE

    CASE (MAX_VALUE_TOKEN)
      New->Max_Value = Parse_Float();
    END_CASE

    CASE (EXPONENT_TOKEN)
      New->Exponent = Parse_Float();
    END_CASE

    CASE (ECCENTRICITY_TOKEN)
      New->Eccentricity = Parse_Float();
    END_CASE

    CASE (COLOUR_MAP_TOKEN)
      Destroy_Blend_Map(New->Blend_Map);
      New->Blend_Map = Parse_Colour_Map();
      new_color_map = TRUE;
    END_CASE

    CASE (TRANSLATE_TOKEN)
      Parse_Vector (Local_Vector);
      Compute_Translation_Transform(&Local_Trans, Local_Vector);
      Translate_One_Halo (New, &Local_Trans);
     END_CASE

    CASE (ROTATE_TOKEN)
      Parse_Vector (Local_Vector);
      Compute_Rotation_Transform(&Local_Trans, Local_Vector);
      Rotate_One_Halo (New, &Local_Trans);
     END_CASE

    CASE (SCALE_TOKEN)
      Parse_Scale_Vector (Local_Vector);
      Compute_Scaling_Transform(&Local_Trans, Local_Vector);
      Scale_One_Halo (New, &Local_Trans);
    END_CASE

     CASE (MATRIX_TOKEN)
       Parse_Matrix(Local_Matrix);
       Compute_Matrix_Transform(&Local_Trans, Local_Matrix);
       Transform_One_Halo (New, &Local_Trans);
     END_CASE

    CASE (TRANSFORM_TOKEN)
      GET(TRANSFORM_ID_TOKEN)
      Transform_One_Halo (New, (TRANSFORM *)Token.Constant_Data);
    END_CASE

    CASE (TURBULENCE_TOKEN)
      if (New->Turb == NULL)
      {
        New->Turb=(TURB *)Create_Warp(CLASSIC_TURB_WARP);
      }
      Parse_Vector_Float(New->Turb->Turbulence);
    END_CASE

    CASE (OCTAVES_TOKEN)
      if (New->Turb == NULL)
      {
        New->Turb=(TURB *)Create_Warp(CLASSIC_TURB_WARP);
      }
      New->Turb->Octaves = (int)Parse_Float();
      if(New->Turb->Octaves < 1)
        New->Turb->Octaves = 1;
      if(New->Turb->Octaves > 10)
         New->Turb->Octaves = 10;
    END_CASE

    CASE (OMEGA_TOKEN)
      if (New->Turb == NULL)
      {
        New->Turb=(TURB *)Create_Warp(CLASSIC_TURB_WARP);
      }
      New->Turb->Omega = Parse_Float();
    END_CASE

    CASE (LAMBDA_TOKEN)
      if (New->Turb == NULL)
      {
        New->Turb=(TURB *)Create_Warp(CLASSIC_TURB_WARP);
      }
      New->Turb->Lambda = Parse_Float();
    END_CASE

    CASE (FREQUENCY_TOKEN)
      New->Frequency = Parse_Float();
    END_CASE

    CASE (PHASE_TOKEN)
      New->Phase = Parse_Float();
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  if (Not_In_Default && (New->Type == HALO_NO_HALO))
  {
    Warn(3.0, "Halo type unspecified.");
  }

  Parse_End();

  if (New->Blend_Map == NULL)
  {
    Error("No color map specified with halo.\n");
  }
  else
  {
    if (new_color_map)
    {
      /* Convert differential translucencies to differential opacities. */

      for (i = 0; i < New->Blend_Map->Number_Of_Entries; i++)
      {
        w = &(New->Blend_Map->Blend_Map_Entries[i].Vals.Colour[TRANSM]);

        *w = 1.0 - *w;
      }
    }
  }

  /* Link halo to exisiting halos. */

/*
  if (add_new_halo)
*/
  {
    New->Next_Halo = Next_Halo;
  }

  *Halo_Ptr = New;
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

TEXTURE *Parse_Texture ()
  {
   VECTOR Local_Vector;
   MATRIX Local_Matrix;
   TRANSFORM Local_Trans;
   TEXTURE *Texture;
   int Modified_Pnf;

   if (opts.Language_Version < 3.0)
   {
     return(Parse_Vers1_Texture());
   }

   Modified_Pnf = FALSE;

   EXPECT               /* First allow a texture identifier */
     CASE (TEXTURE_ID_TOKEN)
       Texture = Copy_Textures((TEXTURE *) Token.Constant_Data);
       Modified_Pnf = TRUE;
       EXIT
     END_CASE

     OTHERWISE
       UNGET
       Texture = Copy_Textures (Default_Texture);
       EXIT
     END_CASE
   END_EXPECT

   /* If the texture identifer or the default texture was a PLAIN_PATTERN
      then allow its pigment, normal or finish to be overridden by
      pigment identifier, normal identifier and finish identifiers.
      This is a consession to backwards compatibility so that
      "texture{PIGMENT_IDENTIFIER}" etc. is legal even though it should
      be "texture{pigment{PIGMENT_IDENTIFIER}}"
   */

   /* Look for [pnf_texture] */
   if (Texture->Type == PLAIN_PATTERN)
   {
     EXPECT   /* Look for [pnf_ids] */
       CASE (PIGMENT_ID_TOKEN)
         Warn_State(Token.Token_Id, PIGMENT_TOKEN);
         Destroy_Pigment(Texture->Pigment);
         Texture->Pigment = Copy_Pigment ((PIGMENT *) Token.Constant_Data);
         Modified_Pnf = TRUE;
       END_CASE

       CASE (TNORMAL_ID_TOKEN)
         Warn_State(Token.Token_Id, TNORMAL_TOKEN);
         Destroy_Tnormal(Texture->Tnormal);
         Texture->Tnormal = Copy_Tnormal ((TNORMAL *) Token.Constant_Data);
         Modified_Pnf = TRUE;
       END_CASE

       CASE (FINISH_ID_TOKEN)
         Warn_State(Token.Token_Id, FINISH_TOKEN);
         Destroy_Finish(Texture->Finish);
         Texture->Finish = Copy_Finish ((FINISH *) Token.Constant_Data);
         Modified_Pnf = TRUE;
       END_CASE

       CASE (HALO_ID_TOKEN)
         Warn_State(Token.Token_Id, HALO_TOKEN);
         Destroy_Halo(Texture->Halo);
         Texture->Halo = Copy_Halo ((HALO *) Token.Constant_Data);
         Modified_Pnf = TRUE;
       END_CASE

       OTHERWISE
         UNGET
         EXIT
       END_CASE
     END_EXPECT

   /* If the texture identifer or the default texture was a PLAIN_PATTERN
      then allow its pigment, normal or finish to be overridden by
      pigment, normal or finish statement.  Also allow transformations.
   */

     EXPECT   /* Modify previous pnf */
       CASE (PIGMENT_TOKEN)
         Parse_Begin ();
         Parse_Pigment ( &(Texture->Pigment) );
         Parse_End ();
         Modified_Pnf = TRUE;
       END_CASE

       CASE (TNORMAL_TOKEN)
         Parse_Begin ();
         Parse_Tnormal ( &(Texture->Tnormal) );
         Parse_End ();
         Modified_Pnf = TRUE;
       END_CASE

       CASE (FINISH_TOKEN)
         Parse_Finish ( &(Texture->Finish) );
         Modified_Pnf = TRUE;
       END_CASE

       CASE (HALO_TOKEN)
         Parse_Halo(&(Texture->Halo));
         Modified_Pnf = TRUE;
       END_CASE

       CASE (TRANSLATE_TOKEN)
         Parse_Vector (Local_Vector);
         Compute_Translation_Transform(&Local_Trans, Local_Vector);
         Translate_Textures (Texture, &Local_Trans);
         Modified_Pnf = TRUE;
       END_CASE

       CASE (ROTATE_TOKEN)
         Parse_Vector (Local_Vector);
         Compute_Rotation_Transform(&Local_Trans, Local_Vector);
         Rotate_Textures (Texture, &Local_Trans);
         Modified_Pnf = TRUE;
       END_CASE

       CASE (SCALE_TOKEN)
         Parse_Scale_Vector (Local_Vector);
         Compute_Scaling_Transform(&Local_Trans, Local_Vector);
         Scale_Textures (Texture, &Local_Trans);
         Modified_Pnf = TRUE;
       END_CASE

       CASE (MATRIX_TOKEN)
         Parse_Matrix(Local_Matrix);
         Compute_Matrix_Transform(&Local_Trans, Local_Matrix);
         Transform_Textures (Texture, &Local_Trans);
         Modified_Pnf = TRUE;
       END_CASE

       CASE (TRANSFORM_TOKEN)
         GET(TRANSFORM_ID_TOKEN)
         Transform_Textures (Texture, (TRANSFORM *)Token.Constant_Data);
         Modified_Pnf = TRUE;
       END_CASE

       OTHERWISE
         UNGET
         EXIT
       END_CASE
     END_EXPECT
    }
    else
    {
    /* Here it is not a PLAIN_PATTERN texture and since default textures
       must be plain then this was a texture identifier that was a special 
       texture.  Allow transforms.  The "if(!Modified_Pnf)..." below
       will always fail if we came here.  So we return after the
       transforms. */
      Parse_Texture_Transform(Texture);
    }

    /* If we've modified the default texture with a p,n, or f then this
       has to stay a PLAIN_PATTERN pnf texture.  We won't allow
       a texture_map or pattern.  Therefore quit now.
     */

    if (!Modified_Pnf)
    {
     /* At this point we've either got a texture statement that had
        no p, n or f.  Nor any texture identifier.  Its probably 
        a patterned texture_map texture. */
       
       EXPECT
         CASE (TILES_TOKEN)
           Destroy_Textures (Texture);
           Texture = Parse_Tiles();
           Parse_Texture_Transform(Texture);
           EXIT
         END_CASE

         CASE (MATERIAL_MAP_TOKEN)
           Destroy_Textures (Texture);
           Texture = Parse_Material_Map ();
           Parse_Texture_Transform(Texture);
           EXIT
         END_CASE

         OTHERWISE
           UNGET;
           Parse_Pattern((TPATTERN *)Texture,TEXTURE_TYPE);
           /* If it was a patterned texture_map the destroy its
              p, n, f & h.  If not, it must be a totally empty
              default texture.  Allow it to be transformed. */
           if (Texture->Type != PLAIN_PATTERN)
           {
             Destroy_Pigment(Texture->Pigment);
             Destroy_Tnormal(Texture->Tnormal);
             Destroy_Finish(Texture->Finish);
             Destroy_Halo(Texture->Halo);
             Texture->Pigment = NULL;
             Texture->Tnormal = NULL;
             Texture->Finish  = NULL;
             Texture->Halo    = NULL;
           }
           else
           {
             Parse_Texture_Transform(Texture);
           }
           EXIT
         END_CASE
      END_EXPECT
    }

    return (Texture);
  }



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static TEXTURE *Parse_Tiles()
{
  TEXTURE *Texture, *Local_Texture;
  BLEND_MAP_ENTRY *Entry;

  Parse_Begin ();

  Texture = Create_Texture ();
  Destroy_Pigment(Texture->Pigment);
  Destroy_Tnormal(Texture->Tnormal);
  Destroy_Finish(Texture->Finish);
  Destroy_Halo(Texture->Halo);
  Texture->Pigment = NULL;
  Texture->Tnormal = NULL;
  Texture->Finish  = NULL;
  Texture->Halo    = NULL;
  Texture->Type = CHECKER_PATTERN;

  Texture->Blend_Map = Create_Blend_Map();
  Texture->Blend_Map->Number_Of_Entries = 2;
  Texture->Blend_Map->Blend_Map_Entries = Entry = Create_BMap_Entries (2);
  Texture->Blend_Map->Type = TEXTURE_TYPE;
  Entry[0].Vals.Texture=NULL;
  Entry[0].value=0.0;
  Entry[0].Same=FALSE;
  Entry[1].Vals.Texture=NULL;
  Entry[1].value=1.0;
  Entry[1].Same=FALSE;
  
  /* Note first tile is 1, 2nd tile is 0 to keep compatible with old tiles */

  EXPECT
    CASE (TEXTURE_TOKEN)
      Parse_Begin ();
      Local_Texture = Parse_Texture ();
      Link_Textures(&(Entry[1].Vals.Texture),Local_Texture);
      Parse_End ();
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  GET (TILE2_TOKEN);

  EXPECT
    CASE (TEXTURE_TOKEN)
      Parse_Begin ();
      Local_Texture = Parse_Texture ();
      Link_Textures(&(Entry[0].Vals.Texture),Local_Texture);
      Parse_End ();
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  Parse_End ();

  return (Texture);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static TEXTURE *Parse_Material_Map()
{
  TEXTURE *Texture, *Local_Texture;
  Parse_Begin ();

  Texture = Create_Texture ();
  Destroy_Pigment(Texture->Pigment);
  Destroy_Tnormal(Texture->Tnormal);
  Destroy_Finish(Texture->Finish);
  Destroy_Halo(Texture->Halo);
  Texture->Pigment = NULL;
  Texture->Tnormal = NULL;
  Texture->Finish  = NULL;
  Texture->Halo    = NULL;
  Texture->Type = BITMAP_PATTERN;

  Texture->Vals.Image = Parse_Image(MATERIAL_FILE);
  Texture->Vals.Image->Use_Colour_Flag = FALSE;

  EXPECT
    CASE (ONCE_TOKEN)
      Texture->Vals.Image->Once_Flag=TRUE;
    END_CASE

    CASE (INTERPOLATE_TOKEN)
      Texture->Vals.Image->Interpolation_Type=(int)Parse_Float();
    END_CASE

    CASE (MAP_TYPE_TOKEN)
      Texture->Vals.Image->Map_Type = (int) Parse_Float ();
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  GET (TEXTURE_TOKEN)                /* First material */
  Parse_Begin();
  Texture->Materials = Local_Texture = Parse_Texture ();
  Parse_End();
  Texture->Num_Of_Mats++;

  EXPECT                             /* Subsequent materials */
    CASE (TEXTURE_TOKEN)
      Parse_Begin();
      Local_Texture->Next_Material = Parse_Texture ();
      Parse_End();
      Local_Texture = Local_Texture->Next_Material;
      Texture->Num_Of_Mats++;
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  Parse_End ();
  
  return(Texture);
}
     


/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static TEXTURE *Parse_Vers1_Texture ()
{
   VECTOR Local_Vector;
   COLOUR Local_Colour;
   MATRIX Local_Matrix;
   TRANSFORM Local_Trans;
   TURB *Local_Turb;
   TEXTURE *Texture;
   PIGMENT *Pigment;
   TNORMAL *Tnormal;
   FINISH *Finish;

   EXPECT                      /* Look for texture_body */
     CASE (TILES_TOKEN)
       Texture = Parse_Tiles();
       EXIT
     END_CASE

     CASE (MATERIAL_MAP_TOKEN)
       Texture = Parse_Material_Map ();
       EXIT
     END_CASE

     CASE (TEXTURE_ID_TOKEN)
       Texture = Copy_Textures((TEXTURE *) Token.Constant_Data);
       EXIT
     END_CASE

     OTHERWISE
       UNGET
       Texture = Copy_Textures (Default_Texture);
       EXIT
     END_CASE
   END_EXPECT

   /* Look for [pnf_texture] */
   if (Texture->Type == PLAIN_PATTERN)
     {
       EXPECT   /* Look for [pnf_ids] */
         CASE (PIGMENT_ID_TOKEN)
           Destroy_Pigment(Texture->Pigment);
           Texture->Pigment = Copy_Pigment ((PIGMENT *) Token.Constant_Data);
         END_CASE

         CASE (TNORMAL_ID_TOKEN)
           Destroy_Tnormal(Texture->Tnormal);
           Texture->Tnormal = Copy_Tnormal ((TNORMAL *) Token.Constant_Data);
         END_CASE

         CASE (FINISH_ID_TOKEN)
           Destroy_Finish(Texture->Finish);
           Texture->Finish = Copy_Finish ((FINISH *) Token.Constant_Data);
         END_CASE

         OTHERWISE
           UNGET
           EXIT
         END_CASE
       END_EXPECT

       Pigment = Texture->Pigment;
       Tnormal = Texture->Tnormal;
       Finish  = Texture->Finish;
  /*     Halo    = Texture->Halo;*/

       EXPECT
         CASE (PIGMENT_TOKEN)
           Parse_Begin ();
           Parse_Pigment ( &(Texture->Pigment) );
           Parse_End ();
         END_CASE

         CASE (TNORMAL_TOKEN)
           Parse_Begin ();
           Parse_Tnormal ( &(Texture->Tnormal) );
           Parse_End ();
         END_CASE

         CASE (FINISH_TOKEN)
           Parse_Finish ( &(Texture->Finish) );
         END_CASE

/***********************************************************************
PIGMENT STUFF OUTSIDE PIGMENT{}
NOTE: Do not add new keywords to this section.  Use 1.0 syntax only.
***********************************************************************/
         CASE (AGATE_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = AGATE_PATTERN;
           Pigment->Vals.Agate_Turb_Scale = 1.0;
           Check_Turb(&(Pigment->Warps));   /* agate needs Octaves, Lambda etc. */
         END_CASE

         CASE (BOZO_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = BOZO_PATTERN;
         END_CASE

         CASE (GRANITE_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = GRANITE_PATTERN;
         END_CASE

         CASE (LEOPARD_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = LEOPARD_PATTERN;
         END_CASE

         CASE (MARBLE_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = MARBLE_PATTERN;
         END_CASE

         CASE (MANDEL_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = MANDEL_PATTERN;
           Pigment->Vals.Iterations = (int)Parse_Float();
         END_CASE

         CASE (ONION_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = ONION_PATTERN;
         END_CASE

         CASE (PATTERN1_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = PATTERN1_PATTERN;
         END_CASE

         CASE (PATTERN2_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = PATTERN2_PATTERN;
         END_CASE

         CASE (PATTERN3_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = PATTERN3_PATTERN;
         END_CASE

         CASE (SPOTTED_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = SPOTTED_PATTERN;
         END_CASE

         CASE (WOOD_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = WOOD_PATTERN;
         END_CASE

         CASE (GRADIENT_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = GRADIENT_PATTERN;
           Parse_Vector (Pigment->Vals.Gradient);
         END_CASE

         CASE_COLOUR
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = PLAIN_PATTERN;
           Parse_Colour (Pigment->Colour);
         END_CASE

         CASE (CHECKER_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = CHECKER_PATTERN;
           Pigment->Frequency = 0.0;
           Destroy_Blend_Map(Pigment->Blend_Map);
           Pigment->Blend_Map = Parse_Blend_List(2,&Check_Default_Map,COLOUR_TYPE);
         END_CASE

         CASE (HEXAGON_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = HEXAGON_PATTERN;
           Pigment->Frequency = 0.0;
           Destroy_Blend_Map(Pigment->Blend_Map);
           Pigment->Blend_Map = Parse_Blend_List(3,&Hex_Default_Map,COLOUR_TYPE);
         END_CASE

         CASE (IMAGE_MAP_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Pigment->Type = BITMAP_PATTERN;
           Pigment->Frequency = 0.0;
           Parse_Image_Map (Pigment);
         END_CASE

         CASE (TURBULENCE_TOKEN)
           Local_Turb=Check_Turb(&(Pigment->Warps));
           Parse_Vector_Float(Local_Turb->Turbulence);
         END_CASE

         CASE (COLOUR_MAP_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           if (Pigment->Type == CHECKER_PATTERN ||
               Pigment->Type == HEXAGON_PATTERN ||
               Pigment->Type == PLAIN_PATTERN ||
               Pigment->Type == BITMAP_PATTERN)
             Warn(1.5, "Cannot use color map with this pigment type.");
           Destroy_Blend_Map(Pigment->Blend_Map);
           Pigment->Blend_Map = Parse_Colour_Map ();
         END_CASE

         CASE (QUICK_COLOUR_TOKEN)
           Warn_State(Token.Token_Id, PIGMENT_TOKEN);
           Parse_Colour (Local_Colour);
           if (opts.Quality_Flags & Q_QUICKC)
           {
              Assign_Colour(Pigment->Colour,Local_Colour);
           }
         END_CASE

         CASE (OCTAVES_TOKEN)
           Local_Turb=Check_Turb(&(Pigment->Warps));
           Local_Turb->Octaves = (int)Parse_Float();
           if(Local_Turb->Octaves < 1)
              Local_Turb->Octaves = 1;
           if(Local_Turb->Octaves > 10)  /* Avoid DOMAIN errors */
              Local_Turb->Octaves = 10;
         END_CASE

         CASE (OMEGA_TOKEN)
           Local_Turb=Check_Turb(&(Pigment->Warps));
           Local_Turb->Omega = Parse_Float();
         END_CASE

         CASE (LAMBDA_TOKEN)
           Local_Turb=Check_Turb(&(Pigment->Warps));
           Local_Turb->Lambda = Parse_Float();
         END_CASE

/***********************************************************************
TNORMAL STUFF OUTSIDE NORMAL{}
NOTE: Do not add new keywords to this section.  Use 1.0 syntax only.
***********************************************************************/
         CASE (BUMPS_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = BUMPS_PATTERN;
           Tnormal->Amount = Parse_Float ();
         END_CASE

         CASE (BUMPY1_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = BUMPY1_PATTERN;
           Tnormal->Amount = Parse_Float ();
         END_CASE

         CASE (BUMPY2_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = BUMPY2_PATTERN;
           Tnormal->Amount = Parse_Float ();
         END_CASE

         CASE (BUMPY3_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = BUMPY3_PATTERN;
           Tnormal->Amount = Parse_Float ();
         END_CASE

         CASE (DENTS_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = DENTS_PATTERN;
           Tnormal->Amount = Parse_Float ();
         END_CASE

         CASE (RIPPLES_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = RIPPLES_PATTERN;
           Tnormal->Amount = Parse_Float ();
         END_CASE

         CASE (WAVES_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = WAVES_PATTERN;
           Tnormal->Amount = Parse_Float ();
         END_CASE

         CASE (WRINKLES_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = WRINKLES_PATTERN;
           Tnormal->Amount = Parse_Float ();
         END_CASE

         CASE (BUMP_MAP_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           Tnormal->Type = BITMAP_PATTERN;
           Tnormal->Frequency = 0.0;
           Parse_Bump_Map (Tnormal);
         END_CASE

         CASE (FREQUENCY_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           if (!(Tnormal->Type == RIPPLES_PATTERN || Tnormal->Type == WAVES_PATTERN))
             if (opts.Language_Version >= 1.5)
               Warn(1.5, "Cannot use frequency with this normal.");
           Tnormal->Frequency = Parse_Float();
         END_CASE

         CASE (PHASE_TOKEN)
           Warn_State(Token.Token_Id, TNORMAL_TOKEN);
           ADD_TNORMAL
           if (!(Tnormal->Type == RIPPLES_PATTERN || Tnormal->Type == WAVES_PATTERN))
             if (opts.Language_Version >= 1.5)
               Warn(1.5, "Cannot use phase with this normal.");
           Tnormal->Phase = Parse_Float();
         END_CASE


/***********************************************************************
FINISH STUFF OUTSIDE FINISH{}
NOTE: Do not add new keywords to this section.  Use 1.0 syntax only.
***********************************************************************/
         CASE (AMBIENT_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Ambient[RED]   =
           Finish->Ambient[GREEN] =
           Finish->Ambient[BLUE]  = Parse_Float ();
         END_CASE

         CASE (BRILLIANCE_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Brilliance = Parse_Float ();
         END_CASE

         CASE (DIFFUSE_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Diffuse = Parse_Float ();
         END_CASE

         CASE (REFLECTION_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Reflection[RED]   =
           Finish->Reflection[GREEN] =
           Finish->Reflection[BLUE]  = Parse_Float ();
         END_CASE

         CASE (REFRACTION_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Refraction = Parse_Float ();
         END_CASE

         CASE (IOR_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Index_Of_Refraction = Parse_Float ();
         END_CASE

         CASE (PHONG_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Phong = Parse_Float ();
         END_CASE

         CASE (PHONG_SIZE_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Phong_Size = Parse_Float ();
         END_CASE

         CASE (SPECULAR_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Specular = Parse_Float ();
         END_CASE

         CASE (ROUGHNESS_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Roughness = Parse_Float ();
           if (Finish->Roughness != 0.0)
           Finish->Roughness = 1.0/Finish->Roughness; /* CEY 12/92 */
           else
             Warn(0.0, "Zero roughness used.");
         END_CASE

         CASE (METALLIC_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Metallic = 1.0;
         END_CASE

         CASE (CRAND_TOKEN)
           Warn_State(Token.Token_Id, FINISH_TOKEN);
           Finish->Crand = Parse_Float();
         END_CASE

         CASE_FLOAT
           Finish->Crand = Parse_Float();
           Warn(1.5, "Should use crand keyword in finish statement.");
         END_CASE

         CASE (TRANSLATE_TOKEN)
           Parse_Vector (Local_Vector);
           Compute_Translation_Transform(&Local_Trans, Local_Vector);
           Translate_Textures (Texture, &Local_Trans);
         END_CASE

         CASE (ROTATE_TOKEN)
           Parse_Vector (Local_Vector);
           Compute_Rotation_Transform(&Local_Trans, Local_Vector);
           Rotate_Textures (Texture, &Local_Trans);
         END_CASE

         CASE (SCALE_TOKEN)
           Parse_Scale_Vector (Local_Vector);
           Compute_Scaling_Transform(&Local_Trans, Local_Vector);
           Scale_Textures (Texture, &Local_Trans);
         END_CASE

         CASE (MATRIX_TOKEN)
           Parse_Matrix(Local_Matrix);
           Compute_Matrix_Transform(&Local_Trans, Local_Matrix);
           Transform_Textures (Texture, &Local_Trans);
         END_CASE

         CASE (TRANSFORM_TOKEN)
           GET(TRANSFORM_ID_TOKEN)
           Transform_Textures (Texture, (TRANSFORM *)Token.Constant_Data);
         END_CASE

         CASE (TEXTURE_ID_TOKEN)
           Warn(0.0, "Texture identifier overwriting previous values.");
           Destroy_Textures(Texture);
           Texture = Copy_Textures((TEXTURE *) Token.Constant_Data);
           Pigment = Texture->Pigment;
           Tnormal = Texture->Tnormal;
           Finish  = Texture->Finish;
  /*         Halo    = Texture->Halo;*/
         END_CASE

         OTHERWISE
           UNGET
           EXIT
         END_CASE
/***********************************************************************/

       END_EXPECT

       if (Not_In_Default && (Texture->Pigment->Type == NO_PATTERN) &&
           !(opts.Language_Version < 1.5))
         Parse_Error(PIGMENT_ID_TOKEN);

     }

   Parse_Texture_Transform(Texture);

   return (Texture);
  }



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static void Parse_Texture_Transform (Texture)
TEXTURE *Texture;
{
   VECTOR Local_Vector;
   MATRIX Local_Matrix;
   TRANSFORM Local_Trans;

   EXPECT
     CASE (TRANSLATE_TOKEN)
       Parse_Vector (Local_Vector);
       Compute_Translation_Transform(&Local_Trans, Local_Vector);
       Translate_Textures (Texture, &Local_Trans);
     END_CASE

     CASE (ROTATE_TOKEN)
       Parse_Vector (Local_Vector);
       Compute_Rotation_Transform(&Local_Trans, Local_Vector);
       Rotate_Textures (Texture, &Local_Trans);
     END_CASE

     CASE (SCALE_TOKEN)
       Parse_Scale_Vector (Local_Vector);
       Compute_Scaling_Transform(&Local_Trans, Local_Vector);
       Scale_Textures (Texture, &Local_Trans);
     END_CASE

     CASE (MATRIX_TOKEN)
       Parse_Matrix(Local_Matrix);
       Compute_Matrix_Transform(&Local_Trans, Local_Matrix);
       Transform_Textures (Texture, &Local_Trans);
     END_CASE

     CASE (TRANSFORM_TOKEN)
       GET(TRANSFORM_ID_TOKEN)
       Transform_Textures (Texture, (TRANSFORM *)Token.Constant_Data);
     END_CASE

     OTHERWISE
       UNGET
       EXIT
     END_CASE
   END_EXPECT
}


/*****************************************************************************
*
* FUNCTION
*
*   Parse_Atmosphere
*
* INPUT
*
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   Dieter Bayer
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   Nov 1994 : Creation.
*
******************************************************************************/

ATMOSPHERE *Parse_Atmosphere()
{
  ATMOSPHERE *Atmosphere;

  Experimental_Flag |= EF_ATMOS;

  Parse_Begin();

  EXPECT
    CASE(ATMOSPHERE_ID_TOKEN)
      Atmosphere = Copy_Atmosphere((ATMOSPHERE *)Token.Constant_Data);
      EXIT
    END_CASE

    OTHERWISE
      UNGET
        Atmosphere = Create_Atmosphere();
      EXIT
    END_CASE
  END_EXPECT

  EXPECT
    CASE (TYPE_TOKEN)
      Atmosphere->Type = (int)Parse_Float();
      if ((Atmosphere->Type < 1) || (Atmosphere->Type > SCATTERING_TYPES))
      {
        Warn(0.0, "Unknown atmospheric scattering type.");
      }
    END_CASE

    CASE (DISTANCE_TOKEN)
      Atmosphere->Distance = Parse_Float();
    END_CASE

    CASE (SAMPLES_TOKEN)
      if ((Atmosphere->Samples = (int)Parse_Float()) < 1)
      {
        Error("Illegal samples value in atmosphere.\n");
      }
    END_CASE

    CASE (SCATTERING_TOKEN)
      Atmosphere->Scattering = Parse_Float();
    END_CASE

    CASE (AA_LEVEL_TOKEN)
      if ((Atmosphere->AA_Level = (int)Parse_Float()) < 0)
      {
        Error("Illegal level value in atmosphere.\n");
      }
    END_CASE

    CASE (AA_THRESHOLD_TOKEN)
      Atmosphere->AA_Threshold = Parse_Float();
    END_CASE

    CASE (JITTER_TOKEN)
      Atmosphere->Jitter = Parse_Float();
    END_CASE

    CASE (ECCENTRICITY_TOKEN)
      Atmosphere->Eccentricity = Parse_Float();
    END_CASE

    CASE_COLOUR
      Parse_Colour(Atmosphere->Colour);
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  Parse_End();

  return(Atmosphere);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

FOG *Parse_Fog()
{
  VECTOR Vector;
  MATRIX Matrix;
  TRANSFORM Trans;
  FOG *Fog;

  Parse_Begin();

  EXPECT
    CASE(FOG_ID_TOKEN)
      Fog = Copy_Fog ((FOG *) Token.Constant_Data);
      EXIT
    END_CASE

    OTHERWISE
      UNGET
      Fog = Create_Fog();
      EXIT
    END_CASE
  END_EXPECT

  EXPECT
    CASE_COLOUR
      Parse_Colour(Fog->Colour);
    END_CASE

    CASE (DISTANCE_TOKEN)
      Fog->Distance = Parse_Float();
    END_CASE

    CASE_FLOAT
      Warn(1.5, "Should use distance keyword.");
      Fog->Distance = Parse_Float();
    END_CASE

    CASE (FOG_TYPE_TOKEN)
      Fog->Type = (int)Parse_Float();
      if ((Fog->Type < ORIG_FOG) || (Fog->Type > FOG_TYPES))
      {
        Warn(0.0, "Unknown fog type.");
      }
    END_CASE

    CASE (FOG_ALT_TOKEN)
      Fog->Alt = Parse_Float();
    END_CASE

    CASE (FOG_OFFSET_TOKEN)
      Fog->Offset = Parse_Float();
    END_CASE

    CASE (TURB_DEPTH_TOKEN)
      Fog->Turb_Depth = Parse_Float();
    END_CASE

    CASE (UP_TOKEN)
      Parse_Vector(Fog->Up);
    END_CASE

    CASE (TURBULENCE_TOKEN)
      if (Fog->Turb == NULL)
      {
        Fog->Turb=(TURB *)Create_Warp(CLASSIC_TURB_WARP);
      }
      Parse_Vector_Float(Fog->Turb->Turbulence);
    END_CASE

    CASE (OCTAVES_TOKEN)
      if (Fog->Turb == NULL)
      {
        Fog->Turb=(TURB *)Create_Warp(CLASSIC_TURB_WARP);
      }
      Fog->Turb->Octaves = (int)Parse_Float();
      if(Fog->Turb->Octaves < 1)
        Fog->Turb->Octaves = 1;
      if(Fog->Turb->Octaves > 10)
         Fog->Turb->Octaves = 10;
    END_CASE

    CASE (OMEGA_TOKEN)
      if (Fog->Turb == NULL)
      {
        Fog->Turb=(TURB *)Create_Warp(CLASSIC_TURB_WARP);
      }
      Fog->Turb->Omega = Parse_Float();
    END_CASE

    CASE (LAMBDA_TOKEN)
      if (Fog->Turb == NULL)
      {
        Fog->Turb=(TURB *)Create_Warp(CLASSIC_TURB_WARP);
      }
      Fog->Turb->Lambda = Parse_Float();
    END_CASE

    CASE (ROTATE_TOKEN)
      Parse_Vector(Vector);
      Compute_Rotation_Transform(&Trans, Vector);
      MTransDirection(Fog->Up, Fog->Up, &Trans);
    END_CASE

    CASE (SCALE_TOKEN)
      Parse_Vector(Vector);
      Compute_Scaling_Transform(&Trans, Vector);
      MTransDirection(Fog->Up, Fog->Up, &Trans);
    END_CASE

    CASE (TRANSLATE_TOKEN)
      Parse_Vector(Vector);
      Warn(0.0, "A fog's up vector can't be translated.");
/*
      Compute_Translation_Transform(&Trans, Vector);
      MTransDirection(Fog->Up, Fog->Up, &Trans);
*/
    END_CASE

    CASE (MATRIX_TOKEN)
      Parse_Matrix(Matrix);
      Compute_Matrix_Transform(&Trans, Matrix);
      MTransDirection(Fog->Up, Fog->Up, &Trans);
    END_CASE

    CASE (TRANSFORM_TOKEN)
      GET(TRANSFORM_ID_TOKEN)
      MTransDirection(Fog->Up, Fog->Up, (TRANSFORM *)Token.Constant_Data);
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  Parse_End ();

  /* Make sure the up vector is normalized. */

  VNormalize(Fog->Up, Fog->Up);

  return(Fog);
}



/*****************************************************************************
*
* FUNCTION
*
*   Parse_Rainbow
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   Dieter Bayer
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   Jul 1994 : Creation.
*
*   Dec 1994 : Modified to work with multiple rainbows. [DB]
*
*   Apr 1995 : Added code for rainbow arcs. [DB]
*
******************************************************************************/

RAINBOW *Parse_Rainbow()
{
  int Angle1, Angle2;
  DBL dot;
  RAINBOW *Rainbow;

  Angle1 = Angle2 = FALSE;

  Parse_Begin();

  EXPECT
    CASE(RAINBOW_ID_TOKEN)
      Rainbow = Copy_Rainbow ((RAINBOW *) Token.Constant_Data);
      EXIT
    END_CASE

    OTHERWISE
      UNGET
      Rainbow = Create_Rainbow();
      EXIT
    END_CASE
  END_EXPECT

  EXPECT
    CASE (ANGLE_TOKEN)
      Rainbow->Angle = Parse_Float();
    END_CASE

    CASE (DIRECTION_TOKEN)
      Parse_Vector(Rainbow->Antisolar_Vector);
    END_CASE

    CASE (COLOUR_MAP_TOKEN)
      Rainbow->Pigment = Create_Pigment();
      Rainbow->Pigment->Blend_Map = Parse_Colour_Map();
      Rainbow->Pigment->Type = GRADIENT_PATTERN;
      Make_Vector (Rainbow->Pigment->Vals.Gradient,1.0,0.0,0.0);
    END_CASE

    CASE (DISTANCE_TOKEN)
      Rainbow->Distance = Parse_Float();
    END_CASE

    CASE (JITTER_TOKEN)
      Rainbow->Jitter = Parse_Float();
    END_CASE

    CASE (WIDTH_TOKEN)
      Rainbow->Width = Parse_Float();
    END_CASE

    CASE (UP_TOKEN)
      Parse_Vector(Rainbow->Up_Vector);
    END_CASE

    CASE (FALLOFF_ANGLE_TOKEN)
      Angle1 = TRUE;
      Rainbow->Falloff_Angle = Parse_Float();
      if ((Rainbow->Falloff_Angle < 0.0) || (Rainbow->Falloff_Angle > 360.0))
      {
        Error("Illegal falloff angle in rainbow (Use value from 0 to 360 degrees).\n");
      }
      Rainbow->Falloff_Angle *= M_PI_360;
    END_CASE

    CASE (ARC_ANGLE_TOKEN)
      Angle2 = TRUE;
      Rainbow->Arc_Angle = Parse_Float();
      if ((Rainbow->Arc_Angle < 0.0) || (Rainbow->Arc_Angle > 360.0))
      {
        Error("Illegal arc angle in rainbow (Use value from 0 to 360 degrees).\n");
      }
      Rainbow->Arc_Angle *= M_PI_360;
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  Parse_End();

  /* Setup falloff angle. */

  if (Angle2 && !Angle1)
  {
    Rainbow->Falloff_Angle = Rainbow->Arc_Angle;
  }

  /* Test if arc angle is greater or equal to falloff angle. */

  if (Rainbow->Arc_Angle < Rainbow->Falloff_Angle)
  {
    Error("Arc angle is smaller than falloff angle in rainbow.\n");
  }

  /* Get falloff region width.*/

  Rainbow->Falloff_Width = Rainbow->Arc_Angle - Rainbow->Falloff_Angle;

  /* Check for illegal vectors. */

  VDot(dot, Rainbow->Antisolar_Vector, Rainbow->Antisolar_Vector);

  if (fabs(dot) < EPSILON)
  {
    Error("Rainbow's direction vector is zero.\n");
  }

  VDot(dot, Rainbow->Up_Vector, Rainbow->Up_Vector);

  if (fabs(dot) < EPSILON)
  {
    Error("Rainbow's up vector is zero.\n");
  }

  VNormalizeEq(Rainbow->Antisolar_Vector);
  VNormalizeEq(Rainbow->Up_Vector);

  VDot(dot, Rainbow->Up_Vector, Rainbow->Antisolar_Vector);

  if (fabs(1.0 - fabs(dot)) < EPSILON)
  {
    Error("Rainbow's up and direction vector are co-linear.\n");
  }

  /* Make sure that up and antisolar vector are perpendicular. */

  VCross(Rainbow->Right_Vector, Rainbow->Up_Vector, Rainbow->Antisolar_Vector);

  VCross(Rainbow->Up_Vector, Rainbow->Antisolar_Vector, Rainbow->Right_Vector);

  VNormalizeEq(Rainbow->Up_Vector);
  VNormalizeEq(Rainbow->Right_Vector);

  /* Adjust rainbow angle and width. */

  Rainbow->Angle -= 0.5 * Rainbow->Width;

  Rainbow->Angle *= M_PI_180;
  Rainbow->Width *= M_PI_180;

  return(Rainbow);
}



/*****************************************************************************
*
* FUNCTION
*
*   Parse_Skysphere
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   Dieter Bayer
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   Jul 1994 : Creation.
*
*   Dec 1994 : Modified to work with multiple skyspheres. [DB]
*
******************************************************************************/

SKYSPHERE *Parse_Skysphere()
{
  VECTOR Local_Vector;
  MATRIX Local_Matrix;
  TRANSFORM Local_Trans;
  SKYSPHERE *Skysphere;

  Parse_Begin();

  EXPECT
    CASE(SKYSPHERE_ID_TOKEN)
      Skysphere = Copy_Skysphere((SKYSPHERE *)Token.Constant_Data);
      EXIT
    END_CASE

    OTHERWISE
      UNGET
      Skysphere = Create_Skysphere();
      EXIT
    END_CASE
  END_EXPECT

  EXPECT
    CASE (PIGMENT_TOKEN)
      Skysphere->Count++;
      Skysphere->Pigments = POV_REALLOC(Skysphere->Pigments, Skysphere->Count*sizeof(SKYSPHERE *), "sky-sphere pigment");
      Skysphere->Pigments[Skysphere->Count-1] = Create_Pigment();
      Parse_Begin();
      Parse_Pigment(&(Skysphere->Pigments[Skysphere->Count-1]));
      Parse_End();
    END_CASE

    CASE (TRANSLATE_TOKEN)
      Parse_Vector (Local_Vector);
      Translate_Skysphere(Skysphere, Local_Vector);
    END_CASE

    CASE (ROTATE_TOKEN)
      Parse_Vector (Local_Vector);
      Rotate_Skysphere(Skysphere, Local_Vector);
    END_CASE

    CASE (SCALE_TOKEN)
      Parse_Scale_Vector (Local_Vector);
      Scale_Skysphere(Skysphere, Local_Vector);
    END_CASE

    CASE (MATRIX_TOKEN)
      Parse_Matrix(Local_Matrix);
      Compute_Matrix_Transform(&Local_Trans, Local_Matrix);
      Transform_Skysphere(Skysphere, &Local_Trans);
    END_CASE

    CASE (TRANSFORM_TOKEN)
      GET(TRANSFORM_ID_TOKEN)
      Transform_Skysphere(Skysphere, (TRANSFORM *)Token.Constant_Data);
    END_CASE

    OTHERWISE
      UNGET
      EXIT
    END_CASE
  END_EXPECT

  Parse_End();
  
  if (Skysphere->Count==0)
  {
    Error("Empty sky_sphere statement.");
  }

  return(Skysphere);
}

/*****************************************************************************
*
* FUNCTION      : Check_BH_Parameters
*
* ARGUMENTS     : bh - pointer to Black_Hole
*
* AUTHOR        : CJC [7/95]
*
* DESCRIPTION   : Applies sanity checks to the parameters of a black hole.
*
* CHANGES
*
******************************************************************************/

static void Check_BH_Parameters (bh)
BLACK_HOLE *bh;
{
  if (bh->Repeat == FALSE) return ;

  if (bh->Repeat_Vector [X] > 0.0)
  {
    if (bh->Center [X] < bh->Radius)
      bh->Center [X] = bh->Radius ;
    if (bh->Repeat_Vector [X] < bh->Center [X] + bh->Radius + bh->Uncertainty_Vector [X])
    {
      bh->Repeat_Vector [X] = bh->Center [X] + bh->Radius + bh->Uncertainty_Vector [X] ;
      Warning (0.0, "Black Hole repeat vector X too small ; increased to %g\n", bh->Repeat_Vector [X]) ;
    } 
    if (bh->Repeat_Vector [X] < Small_Tolerance)
    {
      Warning (0.0,"Black Hole repeat vector X is less than %f ; ignored\n", (float) Small_Tolerance) ;
      bh->Repeat_Vector [X] = 0.0 ;
    }
  }

  if (bh->Repeat_Vector [Y] > 0.0)
  {
    if (bh->Center [Y] < bh->Radius)
      bh->Center [Y] = bh->Radius ;
    if (bh->Repeat_Vector [Y] < bh->Center [Y] + bh->Radius + bh->Uncertainty_Vector [Y])
    {
      bh->Repeat_Vector [Y] = bh->Center [Y] + bh->Radius + bh->Uncertainty_Vector [Y] ;
      Warning (0.0, "Black Hole repeat vector Y too small ; increased to %g\n", bh->Repeat_Vector [Y]) ;
    } 
    if (bh->Repeat_Vector [Y] < Small_Tolerance)
    {
      Warning (0.0, "Black Hole repeat vector Y is less than %f ; ignored\n", (float) Small_Tolerance) ;
      bh->Repeat_Vector [Y] = 0.0 ;
    } 
  }

  if (bh->Repeat_Vector [Z] > 0.0)
  {
    if (bh->Center [Z] < bh->Radius)
      bh->Center [Z] = bh->Radius ;
    if (bh->Repeat_Vector [Z] < bh->Center [Z] + bh->Radius + bh->Uncertainty_Vector [Z])
    {
      bh->Repeat_Vector [Z] = bh->Center [Z] + bh->Radius + bh->Uncertainty_Vector [Z] ;
      Warning (0.0, "Black Hole repeat vector Z too small ; increased to %g\n", bh->Repeat_Vector [Z]) ;
    } 
    if (bh->Repeat_Vector [Z] < Small_Tolerance)
    {
      Warning (0.0, "Black Hole repeat vector Z is less than %f ; ignored\n", (float) Small_Tolerance) ;
      bh->Repeat_Vector [Z] = 0.0 ;
    }
  }
}

/*****************************************************************************
*
* FUNCTION
*
*   Check_Turb
*
* INPUT
*
*   Warps_Ptr : Address where the root warp of a warp list
*   is stored.
*   
* OUTPUT
*
*   Warps_Ptr : If *Warps_Ptr is NULL, a classic turb warp
*   is created and a pointer to it is stored
*   
* RETURNS
*
*   A pointer to the last warp in the chain which is guarenteed
*   to be a classic turb.
*   
* AUTHOR
*
*   CEY [2/95]
*   
* DESCRIPTION   : This routine is called when a classic outside-the-warp
*  turbulence parameter is parsed.  One and only one classic turb may exist 
*  in a warp chain.  If there is one, it must be the last.  This routine 
*  traverses the warp chain and looks at the last link.  If it is not a 
*  classic turb then it adds one to the end and returns a pointer to it.
*  If the chain is empty, it creates a single link chain consisting of a 
*  classic turb link.  Future warp links get added ahead of the chain so
*  that any classic turb link is always last.
*
* CHANGES
*
******************************************************************************/

static TURB *Check_Turb (Warps_Ptr)
WARP **Warps_Ptr;
{
  WARP *Temp=*Warps_Ptr;
  
  if (Temp == NULL)
  {
    *Warps_Ptr = Temp = Create_Warp(CLASSIC_TURB_WARP);
  }
  else
  {
    while (Temp->Next_Warp != NULL)
    {
      Temp=Temp->Next_Warp;
    }
  
    if (Temp->Warp_Type != CLASSIC_TURB_WARP)
    {
      Temp->Next_Warp=Create_Warp(CLASSIC_TURB_WARP);
      Temp=Temp->Next_Warp;
    }
  }
  return((TURB *)Temp);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static void Parse_Warp (Warp_Ptr)
WARP **Warp_Ptr;
{
  WARP *New = NULL;
  TURB *Turb;
  REPEAT *Repeat;
  BLACK_HOLE *Black_Hole;
  VECTOR Local_Vector;

  Parse_Begin();

  EXPECT
    CASE(TURBULENCE_TOKEN)
      New=Create_Warp(EXTRA_TURB_WARP);
      Turb=(TURB *)New;
      Parse_Vector(Turb->Turbulence);
      EXPECT
        CASE(OCTAVES_TOKEN)
          Turb->Octaves = (int)Parse_Float();
          if(Turb->Octaves < 1)
             Turb->Octaves = 1;
          if(Turb->Octaves > 10)  /* Avoid DOMAIN errors */
             Turb->Octaves = 10;
        END_CASE

        CASE (OMEGA_TOKEN)
          Turb->Omega = Parse_Float();
        END_CASE

        CASE (LAMBDA_TOKEN)
          Turb->Lambda = Parse_Float();
        END_CASE
        
        OTHERWISE
          UNGET
          EXIT
        END_CASE
      END_EXPECT
      EXIT
    END_CASE

    CASE(REPEAT_TOKEN)
      New=Create_Warp(REPEAT_WARP);
      Repeat=(REPEAT *)New;
      Parse_Vector(Local_Vector);
      Repeat->Axis=-1;
      if (Local_Vector[X]!=0.0) 
        Repeat->Axis=X;
      if (Local_Vector[Y]!=0.0)
        if (Repeat->Axis < X)
          Repeat->Axis=Y;
        else 
          Error("Can only repeat along 1 axis.");
      if (Local_Vector[Z]!=0.0)
        if (Repeat->Axis < X)
          Repeat->Axis=Z;
        else
          Error("Can only repeat along 1 axis.");
      if (Repeat->Axis < X)
        Error("No axis specified in repeat.");
      Repeat->Width=Local_Vector[Repeat->Axis];
         
      EXPECT
        CASE(OFFSET_TOKEN)
          Parse_Vector(Repeat->Offset);
        END_CASE
        
        CASE(FLIP_TOKEN)
          Parse_Vector(Repeat->Flip);
          if (Repeat->Flip[X]!=0.0) Repeat->Flip[X]=-1.0; else Repeat->Flip[X]=1.0;
          if (Repeat->Flip[Y]!=0.0) Repeat->Flip[Y]=-1.0; else Repeat->Flip[Y]=1.0;
          if (Repeat->Flip[Z]!=0.0) Repeat->Flip[Z]=-1.0; else Repeat->Flip[Z]=1.0;
        END_CASE
        
        OTHERWISE
          UNGET
          EXIT
        END_CASE
      END_EXPECT
      EXIT
    END_CASE
    
    CASE(BLACK_HOLE_TOKEN)
      New = Create_Warp(BLACK_HOLE_WARP) ;
      Black_Hole = (BLACK_HOLE *) New ;
      Parse_Vector (Local_Vector) ;
      Assign_Vector (Black_Hole->Center, Local_Vector) ;
      Parse_Comma () ;
      Black_Hole->Radius = Parse_Float () ;
      Black_Hole->Radius_Squared = Black_Hole->Radius * Black_Hole->Radius ;
      Black_Hole->Inverse_Radius = 1.0 / Black_Hole->Radius;
      Black_Hole->Strength = 1.0 ;
      Black_Hole->Power = 2.0 ;
      Black_Hole->Inverted = FALSE ;
      Black_Hole->Type = 0 ;

      EXPECT
        CASE(STRENGTH_TOKEN)
          Black_Hole->Strength = Parse_Float () ;
        END_CASE

        CASE(FALLOFF_TOKEN)
          Black_Hole->Power = Parse_Float () ;
        END_CASE

        CASE(INVERSE_TOKEN)
          Black_Hole->Inverted = TRUE ;
        END_CASE

        CASE(TYPE_TOKEN)
          Black_Hole->Type = (int) Parse_Float () ;
        END_CASE

        CASE(REPEAT_TOKEN)
          Parse_Vector (Black_Hole->Repeat_Vector) ;
          Black_Hole->Repeat = TRUE ;
          Check_BH_Parameters (Black_Hole) ;
        END_CASE

        CASE(TURBULENCE_TOKEN)
          Parse_Vector (Black_Hole->Uncertainty_Vector) ;
          Black_Hole->Uncertain = TRUE ;
          Check_BH_Parameters (Black_Hole) ;
        END_CASE

        OTHERWISE
          UNGET
          EXIT
        END_CASE
      END_EXPECT
      EXIT
    END_CASE
      
    CASE(SPIRAL_TOKEN)
      Error("Spiral warp not yet implemented.");
    END_CASE

    OTHERWISE
      Parse_Error_Str ("warp type");
    END_CASE
  END_EXPECT

  if (New==NULL)
  {
     Error("Empty warp statement.");
  }
  
  New->Next_Warp=*Warp_Ptr;
  *Warp_Ptr=New;

  Parse_End();
}
