/*******************************************************************************
 * parstxtr.cpp
 *
 * This module parses textures and atmosphere effects.
 *
 * ---------------------------------------------------------------------------
 * Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
 * Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
 *
 * POV-Ray is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * POV-Ray 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * ---------------------------------------------------------------------------
 * POV-Ray 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.
 * ---------------------------------------------------------------------------
 * $File: //depot/public/povray/3.x/source/backend/parser/parstxtr.cpp $
 * $Revision: #1 $
 * $Change: 6069 $
 * $DateTime: 2013/11/06 11:59:40 $
 * $Author: chrisc $
 *******************************************************************************/

// frame.h must always be the first POV file included (pulls in platform config)
#include "backend/frame.h"
#include "backend/parser/parse.h"

#include "backend/math/vector.h"
#include "backend/colour/colour.h"
#include "backend/interior/interior.h"
#include "backend/math/matrices.h"
#include "backend/interior/media.h"
#include "backend/texture/normal.h"
#include "backend/texture/pigment.h"
#include "backend/texture/texture.h"
#include "backend/shape/isosurf.h"
#include "backend/vm/fncode.h"
#include "backend/vm/fnpovfpu.h"
#include "backend/scene/objects.h"
#include "base/image/image.h"
#include "backend/support/imageutil.h"
#include "backend/support/fileutil.h"

#include "lightgrp.h" // TODO

#ifdef SYS_IMAGE_HEADER
#include SYS_IMAGE_HEADER
#endif

// this must be the last file included
#include "base/povdebug.h"

namespace pov
{

/*****************************************************************************
* 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;};


/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Ronald Parker, changes by Thorsten Froehlich
*   
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Parser::Make_Pattern_Image(ImageData *image, FUNCTION_PTR fn, int token)
{
	int i = 0;
	int j = 0;
	DBL val = 0;
	FunctionCode *f = sceneData->functionVM->GetFunction(*fn);
	VECTOR point;

	image->iwidth  = image->width;
	image->iheight = image->height;
	if(token == FUNCT_ID_TOKEN)
	{
		image->data =Image::Create(image->iwidth, image->iheight, Image::Gray_Int16);

		point[Z] = 0;

		for(i = 0; i < image->iheight; i++)
		{
			point[Y] = ((DBL)i / (image->height - 1));

			for( j = 0; j < image->iwidth; j++ )
			{
				point[X] = ((DBL)j / (image->width - 1));

				fnVMContext->SetLocal(X, point[X]);
				fnVMContext->SetLocal(Y, point[Y]);
				fnVMContext->SetLocal(Z, point[Z]);

				image->data->SetGrayValue(j, i, float(POVFPU_Run(fnVMContext, *fn)));
			}
		}
	}
	else if((token == VECTFUNCT_ID_TOKEN) && (f->return_size == 5))
	{
		image->data =Image::Create(image->iwidth, image->iheight, Image::RGBA_Int16);
		image->data->SetPremultiplied(false); // We're storing the data in non-premultiplied alpha format, as this preserves all the data we're getting from the function.

		point[Z] = 0;

		for(i = 0; i < image->iheight; i++)
		{
			point[Y] = ((DBL)i / (image->height - 1));

			for(j = 0; j < image->iwidth; j++)
			{
				point[X] = ((DBL)j / (image->width - 1));

				fnVMContext->SetLocal(X + f->return_size, point[X]);
				fnVMContext->SetLocal(Y + f->return_size, point[Y]);
				fnVMContext->SetLocal(Z + f->return_size, point[Z]);

				(void)POVFPU_Run(fnVMContext, *fn);

				image->data->SetRGBFTValue(j, i,
				                          float(fnVMContext->GetLocal(pRED)),
				                          float(fnVMContext->GetLocal(pGREEN)),
				                          float(fnVMContext->GetLocal(pBLUE)),
				                          float(fnVMContext->GetLocal(pFILTER)), // N.B. pFILTER component is currently ignored by SetRGBFTValue (matches 3.6 behavior)
				                          float(fnVMContext->GetLocal(pTRANSM)));
			}
		}
	}
	else
		Error("Unsupported function type in function image.");

	Destroy_Function(fn);
}

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

ImageData *Parser::Parse_Image(int Legal, bool GammaCorrect)
{
	ImageData *image = NULL;
	VECTOR Local_Vector;
	char *Name = NULL;
	int token_id;
	int filetype = NO_FILE;
	UCS2String ign;
	pov::FUNCTION_PTR fnPtr;

	image = Create_Image();

	if(Legal & GRAD_FILE)
	{
		EXPECT
			CASE_VECTOR
				Warning(150, "Old style orientation vector or map type not supported. Ignoring value.");
				Parse_Vector(Local_Vector);
			END_CASE

			OTHERWISE
				UNGET
				EXIT
			END_CASE
		END_EXPECT
	}

	EXPECT
		CASE (FUNCTION_TOKEN)
			image->width = (SNGL)int(Parse_Float() + 0.5);
			Parse_Comma();
			image->height = (SNGL)int(Parse_Float() + 0.5);

			Get_Token();
			if(Token.Token_Id != LEFT_CURLY_TOKEN)
				Found_Instead_Error("Missing { after", "expression");
			Unget_Token();

			fnPtr = Parse_DeclareFunction(&token_id, NULL, false);
			Make_Pattern_Image(image, fnPtr, token_id);
			EXIT
		END_CASE

		CASE (IFF_TOKEN)
			filetype = IFF_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (GIF_TOKEN)
			filetype = GIF_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (POT_TOKEN)
			filetype = POT_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (SYS_TOKEN)
			filetype = SYS_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (TGA_TOKEN)
			filetype = TGA_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (PNG_TOKEN)
			filetype = PNG_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (PGM_TOKEN)
			filetype = PGM_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (PPM_TOKEN)
			filetype = PPM_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (JPEG_TOKEN)
			filetype = JPEG_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (TIFF_TOKEN)
			Experimental_Flag |= EF_TIFF;
			filetype = TIFF_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (BMP_TOKEN)
			filetype = BMP_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (EXR_TOKEN)
			filetype = EXR_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE (HDR_TOKEN)
			filetype = HDR_FILE;
			Name = Parse_C_String(true);
			EXIT
		END_CASE

		CASE5 (STRING_LITERAL_TOKEN,CHR_TOKEN,SUBSTR_TOKEN,STR_TOKEN,VSTR_TOKEN)
		CASE4 (CONCAT_TOKEN,STRUPR_TOKEN,STRLWR_TOKEN,DATETIME_TOKEN)
			{
				UNGET
				Name = Parse_C_String(true);
				UCS2String filename = ASCIItoUCS2String(Name);
				UCS2String ext = GetFileExtension(Path(filename));

				if (ext.empty() == false)
				{
					filetype = gFile_Type_To_Mask [InferFileTypeFromExt(ext)];
					if (filetype == NO_FILE)
					{
						POV_FREE(Name);
						Error("Could not determine valid image file type from extension.");
					}
				}
				else
				{
					switch (sceneData->defaultFileType)
					{
						case kPOVList_FileType_Targa:
						case kPOVList_FileType_CompressedTarga:
							filetype = TGA_FILE;
							break;
						case kPOVList_FileType_PNG:
							filetype = PNG_FILE;
							break;
						case kPOVList_FileType_JPEG:
							filetype = JPEG_FILE;
							break;
						case kPOVList_FileType_PPM:
							filetype = PPM_FILE;
							break;
						case kPOVList_FileType_BMP:
							filetype = BMP_FILE;
							break;
						case kPOVList_FileType_OpenEXR:
							filetype = EXR_FILE;
							break;
						case kPOVList_FileType_RadianceHDR:
							filetype = HDR_FILE;
							break;
						case kPOVList_FileType_System:
							filetype = SYS_FILE;
							break;
						default:
							POV_FREE(Name);
							Error("Could not determine valid input file type from output type.");
					}
				}
			}
			EXIT
		END_CASE

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

	if(Name != NULL)
	{
		if(!(filetype & Legal))
			Error("File type not supported here.");

		UCS2String filename = ASCIItoUCS2String(Name);
		Image::ReadOptions options;

		switch (sceneData->gammaMode)
		{
			case kPOVList_GammaMode_None:
				options.gammacorrect = false;
				options.defaultGamma.reset();
				options.workingGamma.reset();
				break;
			case kPOVList_GammaMode_AssumedGamma36:
				if (GammaCorrect)
				{
					options.gammacorrect = GammaCorrect;
					options.defaultGamma = SimpleGammaCurvePtr(sceneData->workingGamma);
					options.workingGamma = SimpleGammaCurvePtr(sceneData->workingGamma);
				}
				else
				{
					options.gammacorrect = false;
					options.defaultGamma.reset();
					options.workingGamma.reset();
				}
				break;
			case kPOVList_GammaMode_AssumedGamma37:
			case kPOVList_GammaMode_AssumedGamma37Implied:
				if (GammaCorrect)
				{
					options.gammacorrect = GammaCorrect;
					options.defaultGamma.reset();
					options.workingGamma = SimpleGammaCurvePtr(sceneData->workingGamma);
				}
				else
				{
					options.gammacorrect = false;
					options.defaultGamma.reset();
					options.workingGamma.reset();
				}
				break;
			default:
				throw POV_EXCEPTION_STRING("Unknown gamma handling mode in Parse_Image()");
		}

		EXPECT
			CASE (GAMMA_TOKEN)
				// User wants to override decoding gamma for this particular file; let them have their will.
				options.gammacorrect = true;
				options.gammaOverride = true;
				options.defaultGamma = Parse_Gamma();
				if (GammaCorrect)
					options.workingGamma = SimpleGammaCurvePtr(sceneData->workingGamma);
				else
					options.workingGamma.reset();
			END_CASE
			CASE (PREMULTIPLIED_TOKEN)
				// User wants to override alpha premultiplication setting for this particular file; let them have their will.
				options.premultiplyOverride = true;
				options.premultiply = ((int)Parse_Float() != 0);
			END_CASE
			OTHERWISE
				UNGET
				EXIT
			END_CASE
		END_EXPECT

		if (options.gammacorrect && !options.gammaOverride && (filetype == PNG_FILE) && (sceneData->EffectiveLanguageVersion() < 370))
		{
			PossibleError("PNG input image default gamma handling has changed for pre POV-Ray 3.7 scenes;\n"
			              "results may differ from original intention. See the documentation for more\n"
			              "details.");
		}

		if (!GammaCorrect)
		{
			// context typically implies that gamma correction is not desired (e.g. height_field)
			if (options.gammacorrect && !options.gammaOverride)
				Warning(0, "input image gamma not specified for height_field, bump_map or image_pattern;\n"
				           "no gamma adjustment performed on input image; results may differ from intention\n"
				           "in rare cases. See the documentation for details.\n"
				           "To get rid of this warning, explicitly specify \"gamma 1.0\".");
		}

		// beta-test feature
		// if we end up keeping vidcap, it will probably get its own filetype.
		// for now, avoid the need to change the SDL by using the sys file type with the below prefix
		if (filetype == SYS_FILE && strncmp(Name, ":vidcap:", 8) == 0)
		{
#ifdef POV_VIDCAP_IMPL
			image->VidCap = new POV_VIDCAP_IMPL();
			// note the second ':' gets passed since it's the option prefix
			// e.g. ":vidcap:source=/dev/video0:w=640:h=480:fps=5"
			image->data = image->VidCap->Init(Name + 7, options, true);
			Beta_Feature_Flag |= BF_VIDCAP;
#else
			Error("Beta-test video capture feature not implemented on this platform.");
#endif
		}
		else
			image->data = Read_Image(this, sceneData, filetype, filename.c_str(), options);

		if (!options.warnings.empty())
			for (vector<string>::iterator it = options.warnings.begin(); it != options.warnings.end(); it++)
				Warning (0, "%s: %s", Name, it->c_str()) ;

		POV_FREE(Name);
	}

	if(image->data == NULL)
		Error("Cannot read image.");

	image->iwidth = image->data->GetWidth();
	image->iheight = image->data->GetHeight();
	image->width = (SNGL) image->iwidth;
	image->height = (SNGL) image->iheight;

	return image;
}


SimpleGammaCurvePtr Parser::Parse_Gamma (void)
{
	SimpleGammaCurvePtr gamma;
	EXPECT
		CASE (COLOUR_KEY_TOKEN)
			if (Token.Function_Id != SRGB_TOKEN)
			{
				UNGET
				EXIT
				END_CASE
			}
			gamma = SRGBGammaCurve::Get();
			EXIT
		END_CASE
		OTHERWISE
		{
			UNGET
			DBL value = Parse_Float();
			if (value <= 0.0)
				Error ("gamma value must be positive.");
			else if ((value < 1.0) && (sceneData->gammaMode != kPOVList_GammaMode_None))
				// (we're not warning about below-1.0-values in non-gamma-corrected scenes, as it is likely to make sense there)
				PossibleError("Suspicious value %g specified for gamma. Did you mean %g?\n", value, 1.0/value);
			gamma = PowerLawGammaCurve::GetByDecodingGamma(value);
			EXIT
		}
		END_CASE
	END_EXPECT
	return gamma;
}

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

void Parser::Parse_Image_Map (PIGMENT *Pigment)
{
	int reg;
	ImageData *image;

	Parse_Begin();

	image = Parse_Image (IMAGE_FILE, true);
	image->Use = USE_COLOUR; // was true [trf]

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

		CASE (INTERPOLATE_TOKEN)
			image->Interpolation_Type = (int)Parse_Float();
			switch(image->Interpolation_Type)
			{
				case BILINEAR:
				case BICUBIC:
				case NORMALIZED_DIST:
					break;

				default:
					Error("Invalid interpolate value.");
					break;
			}
		END_CASE

		CASE (MAP_TYPE_TOKEN)
			image->Map_Type = (int) Parse_Float ();
			switch(image->Map_Type)
			{
				case PLANAR_MAP:
				case SPHERICAL_MAP:
				case CYLINDRICAL_MAP:
				case TORUS_MAP:
					break;

				default:
					Error("Invalid map_type value.");
					break;
			}
		END_CASE

		CASE (USE_COLOUR_TOKEN)
			image->Use = USE_COLOUR; // was true [trf]
		END_CASE

		CASE (USE_INDEX_TOKEN)
			image->Use = USE_NONE; // was false [trf]
		END_CASE

		CASE (REPEAT_TOKEN)
			UV_VECT Repeat;
			Parse_UV_Vect (Repeat);
			if ((Repeat[0]<=0.0) || (Repeat[1]<=0.0))
				Error("Zero or Negative Image Repeat Vector.");
			image->width  =  (DBL)image->iwidth  * Repeat[0];
			image->height =  (DBL)image->iheight * Repeat[1];
		END_CASE

		CASE (OFFSET_TOKEN)
			Parse_UV_Vect (image->Offset);
			image->Offset[U] *= (DBL)-image->iwidth;
			image->Offset[V] *= (DBL)-image->iheight;
		END_CASE

		CASE (ALPHA_TOKEN)
			Warning(155, "Keyword ALPHA discontinued. Use FILTER instead.");
			// FALLTHROUGH

		CASE (COLOUR_KEY_TOKEN)
			switch(Token.Function_Id)
			{
				case FILTER_TOKEN:
					EXPECT
						CASE (ALL_TOKEN)
							{
								DBL filter;
								filter = Parse_Float();
								image->AllFilter = filter;
								if (image->data->IsIndexed())
								{
									if (image->data->HasFilterTransmit() == false)
									{
										vector<Image::RGBFTMapEntry> map ;
										image->data->GetColourMap (map) ;
										image->data->SetColourMap (map) ;
									}
									for(reg = 0 ; reg < image->data->GetColourMapSize(); reg++)
									{
										float r, g, b, f, t;

										image->data->GetRGBFTIndexedValue(reg, r, g, b, f, t);
										image->data->SetRGBFTIndexedValue(reg, r, g, b, filter, t);
									}
								}
							}
							EXIT
						END_CASE

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

							Parse_Comma();
							{
								float r, g, b, f, t;

								if (image->data->HasFilterTransmit() == false)
								{
									vector<Image::RGBFTMapEntry> map ;
									image->data->GetColourMap (map) ;
									image->data->SetColourMap (map) ;
								}
								image->data->GetRGBFTIndexedValue(reg, r, g, b, f, t);
								image->data->SetRGBFTIndexedValue(reg, r, g, b, Parse_Float(), t);
							}
							EXIT
						END_CASE

					END_EXPECT
					Pigment->Flags |= HAS_FILTER;
					break;

				case TRANSMIT_TOKEN:
					EXPECT
						CASE (ALL_TOKEN)
							{
								DBL transmit;
								transmit = Parse_Float();
								image->AllTransmit = transmit;
								if (image->data->IsIndexed())
								{
									if (image->data->HasFilterTransmit() == false)
									{
										vector<Image::RGBFTMapEntry> map ;
										image->data->GetColourMap (map) ;
										image->data->SetColourMap (map) ;
									}
									for(reg = 0 ; reg < image->data->GetColourMapSize(); reg++)
									{
										float r, g, b, f, t;

										image->data->GetRGBFTIndexedValue(reg, r, g, b, f, t);
										image->data->SetRGBFTIndexedValue(reg, r, g, b, f, transmit);
									}
								}
							}
							EXIT
						END_CASE

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

							Parse_Comma();
							{
								float r, g, b, f, t;

								if (image->data->HasFilterTransmit() == false)
								{
									vector<Image::RGBFTMapEntry> map ;
									image->data->GetColourMap (map) ;
									image->data->SetColourMap (map) ;
								}
								image->data->GetRGBFTIndexedValue(reg, r, g, b, f, t);
								image->data->SetRGBFTIndexedValue(reg, r, g, b, f, Parse_Float());
							}
							EXIT
						END_CASE

					END_EXPECT
					Pigment->Flags |= HAS_FILTER;
					break;

				default:
					UNGET
					Expectation_Error ("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
*    Scott Manley Added use of repeat vector
*
******************************************************************************/

void Parser::Parse_Bump_Map (TNORMAL *Tnormal)
{
	ImageData *image;

	Parse_Begin();

	image = Parse_Image(IMAGE_FILE);
	image->Use = USE_COLOUR; // was true [trf]

	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 = USE_COLOUR; // was true [trf]
		END_CASE

		CASE (USE_INDEX_TOKEN)
			image->Use = USE_NONE; // was false [trf]
		END_CASE

		CASE (REPEAT_TOKEN)
			UV_VECT Repeat;
			Parse_UV_Vect (Repeat);
			if ((Repeat[0]<=0.0) || (Repeat[1]<=0.0))
				Error("Zero or Negative Image Repeat Vector.");
			image->width  =  (DBL)image->iwidth  * Repeat[0];
			image->height =  (DBL)image->iheight * Repeat[1];
		END_CASE

		CASE (OFFSET_TOKEN)
			Parse_UV_Vect (image->Offset);
			image->Offset[U] *= (DBL)-image->iwidth;
			image->Offset[V] *= (DBL)-image->iheight;
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	Tnormal->Vals.image = image;

	Parse_End();
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Nathan Kopp
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Parser::Parse_Image_Pattern (TPATTERN *TPattern)
{
	ImageData *image;

	Parse_Begin();

	image = Parse_Image(IMAGE_FILE);
	image->Use = USE_COLOUR; // was true [trf]

	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 (USE_COLOUR_TOKEN)
			image->Use = USE_COLOUR;
		END_CASE

		CASE (USE_INDEX_TOKEN)
			image->Use = USE_INDEX;
		END_CASE

		CASE (USE_ALPHA_TOKEN)
			image->Use = USE_ALPHA;
		END_CASE

		CASE (REPEAT_TOKEN)
			UV_VECT Repeat;
			Parse_UV_Vect (Repeat);
			if ((Repeat[0]<=0.0) || (Repeat[1]<=0.0))
				Error("Zero or Negative Image Repeat Vector.");
			image->width  =  (DBL)image->iwidth  * Repeat[0];
			image->height =  (DBL)image->iheight * Repeat[1];
		END_CASE

		CASE (OFFSET_TOKEN)
			Parse_UV_Vect (image->Offset);
			image->Offset[U] *= (DBL)-image->iwidth;
			image->Offset[V] *= (DBL)-image->iheight;
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	TPattern->Vals.image = image;

	Parse_End();
}

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

void Parser::Parse_Pigment (PIGMENT **Pigment_Ptr)
{
	EXPECT            /* Look for [pigment_id] */
		CASE (PIGMENT_ID_TOKEN)
			Destroy_Pigment(*Pigment_Ptr);
			*Pigment_Ptr = Copy_Pigment ((PIGMENT *) Token.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))
	{
		Warning(155, "Pigment type unspecified or not 1st item.");
	}
}



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

void Parser::Parse_Pattern (TPATTERN *New, int TPat_Type)
{
	VECTOR Local_Vector;
	MATRIX Local_Matrix;
	TRANSFORM Local_Trans;
	TURB *Local_Turb;
	unsigned short Old_Type=New->Type;
	ImageData *Old_Image = NULL;
	DENSITY_FILE *Old_Density_File = NULL;
	int i;
	SNGL Tot_Len;
	TraceThreadData *Thread = GetParserDataPtr();
	UCS2String ign;

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

	if (Old_Type==DENSITY_FILE_PATTERN)
	{
		Old_Density_File=New->Vals.Density_File;
	}

	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 (FUNCTION_TOKEN)
			New->Type = FUNCTION_PATTERN;
			New->Vals.Function.vm = sceneData->functionVM;
			New->Vals.Function.Data = sceneData->functionPatternCount;
			sceneData->functionPatternCount++;
			GetParserDataPtr()->functionPatternContext.resize(sceneData->functionPatternCount);
			New->Vals.Function.Fn = Parse_Function();
			EXIT
		END_CASE

		CASE(PIGMENT_PATTERN_TOKEN)
			Parse_Begin();
			New->Type = PIGMENT_PATTERN;
			New->Vals.Pigment = Create_Pigment();
			Parse_Pigment(&(New->Vals.Pigment));
			Post_Pigment(New->Vals.Pigment);
			Parse_End();
			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.Fractal.Iterations = (int)Parse_Float();
			New->Vals.Fractal.interior_type = DEFAULT_FRACTAL_INTERIOR_TYPE;
			New->Vals.Fractal.exterior_type = DEFAULT_FRACTAL_EXTERIOR_TYPE;
			New->Vals.Fractal.efactor = DEFAULT_FRACTAL_EXTERIOR_FACTOR;
			New->Vals.Fractal.ifactor = DEFAULT_FRACTAL_INTERIOR_FACTOR;
			New->Vals.Fractal.Exponent = 2;
			EXIT
		END_CASE

		CASE (JULIA_TOKEN)
			New->Type = JULIA_PATTERN;
			Parse_UV_Vect(New->Vals.Fractal.Coord);
			Parse_Comma();
			New->Vals.Fractal.Iterations = (int)Parse_Float();
			New->Vals.Fractal.interior_type = DEFAULT_FRACTAL_INTERIOR_TYPE;
			New->Vals.Fractal.exterior_type = DEFAULT_FRACTAL_EXTERIOR_TYPE;
			New->Vals.Fractal.efactor = DEFAULT_FRACTAL_EXTERIOR_FACTOR;
			New->Vals.Fractal.ifactor = DEFAULT_FRACTAL_INTERIOR_FACTOR;
			New->Vals.Fractal.Exponent = 2;
			EXIT
		END_CASE

		CASE (MAGNET_TOKEN)
			New->Type = NO_PATTERN;
			New->Vals.Fractal.interior_type = DEFAULT_FRACTAL_INTERIOR_TYPE;
			New->Vals.Fractal.exterior_type = DEFAULT_FRACTAL_EXTERIOR_TYPE;
			New->Vals.Fractal.efactor = DEFAULT_FRACTAL_EXTERIOR_FACTOR;
			New->Vals.Fractal.ifactor = DEFAULT_FRACTAL_INTERIOR_FACTOR;
			New->Vals.Fractal.Exponent = 0;
			i = (int)Parse_Float();
			EXPECT
				CASE (MANDEL_TOKEN)
					switch(i)
					{
						case 1:
							New->Type = MAGNET1M_PATTERN;
							break;
						case 2:
							New->Type = MAGNET2M_PATTERN;
							break;
						default:
							Error("Invalid magnet-mandel pattern type found. Valid types are 1 and 2.");
							break;
					}
					EXIT
				END_CASE
				CASE (JULIA_TOKEN)
					switch(i)
					{
						case 1:
							New->Type = MAGNET1J_PATTERN;
							break;
						case 2:
							New->Type = MAGNET2J_PATTERN;
							break;
						default:
							Error("Invalid magnet-julia pattern type found. Valid types are 1 and 2.");
							break;
					}
					Parse_UV_Vect(New->Vals.Fractal.Coord);
					Parse_Comma();
					EXIT
				END_CASE
				OTHERWISE
					Error("Invalid magnet pattern found. Valid types are 'mandel' and 'julia'.");
				END_CASE
			END_EXPECT
			New->Vals.Fractal.Iterations = (int)Parse_Float();
			EXIT
		END_CASE

		CASE (ONION_TOKEN)
			New->Type = ONION_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);
			VNormalizeEq(New->Vals.Gradient);
			EXIT
		END_CASE

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

		CASE (CRACKLE_TOKEN)
			New->Type = CRACKLE_PATTERN;
			New->Vals.Crackle.IsSolid = 0;
			New->Vals.Crackle.Form[X] = -1;
			New->Vals.Crackle.Form[Y] = 1;
			New->Vals.Crackle.Form[Z] = 0;
			New->Vals.Crackle.Metric = 2;
			New->Vals.Crackle.Offset = 0;
			New->Vals.Crackle.Dim = 3;
			EXIT
		END_CASE

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

		CASE (UV_MAPPING_TOKEN)
			New->Type = UV_MAP_PATTERN;
			Destroy_Blend_Map(New->Blend_Map);
			New->Blend_Map = Parse_Item_Into_Blend_List(TPat_Type);
			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);
			if (TPat_Type == NORMAL_TYPE)
				((TNORMAL *)New)->Delta = 0.02;
			EXIT
		END_CASE

		CASE (OBJECT_TOKEN)
		{
			Parse_Begin();
			vector<ObjectPtr> tempObjects;
			Parse_Bound_Clip(tempObjects, false);
			if(tempObjects.size() != 1)
				Error ("object or object identifier expected.");
			New->Vals.Object = tempObjects[0];
			New->Type = OBJECT_PATTERN;
			New->Frequency = 0.0;
			Destroy_Blend_Map(New->Blend_Map);
			New->Blend_Map = Parse_Blend_List(2, &Check_Default_Map, TPat_Type);
			Parse_End();
			EXIT
		}
		END_CASE

		CASE (CELLS_TOKEN)
			New->Type = CELLS_PATTERN;
			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-EPSILON*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);
			if (TPat_Type == NORMAL_TYPE)
				((TNORMAL *)New)->Delta = 0.02;
			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);
			if (TPat_Type == NORMAL_TYPE)
				((TNORMAL *)New)->Delta = 0.02;
			EXIT
		END_CASE

		CASE (SQUARE_TOKEN)
			New->Type = SQUARE_PATTERN;
			New->Frequency = 0.0;
			Destroy_Blend_Map(New->Blend_Map);
			New->Blend_Map = Parse_Blend_List(4,&Square_Default_Map,TPat_Type);
			if (TPat_Type == NORMAL_TYPE)
				((TNORMAL *)New)->Delta = 0.02;
			EXIT
		END_CASE

		CASE (TRIANGULAR_TOKEN)
			New->Type = TRIANGULAR_PATTERN;
			New->Frequency = 0.0;
			Destroy_Blend_Map(New->Blend_Map);
			New->Blend_Map = Parse_Blend_List(6,&Triangular_Default_Map,TPat_Type);
			if (TPat_Type == NORMAL_TYPE)
				((TNORMAL *)New)->Delta = 0.02;
			EXIT
		END_CASE

		// JN2007: Cubic pattern
		CASE (CUBIC_TOKEN)
			New->Type = CUBIC_PATTERN;
			New->Frequency = 0.0;
			Destroy_Blend_Map(New->Blend_Map);
			New->Blend_Map = Parse_Blend_List(6,&Cubic_Default_Map,TPat_Type);
			if (TPat_Type == NORMAL_TYPE)
				((TNORMAL *)New)->Delta = 0.02;
			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 (FACETS_TOKEN)
			if (TPat_Type != NORMAL_TYPE)
			{
				Only_In("facets","normal");
			}
			New->Type = FACETS_PATTERN;
			New->Vals.Facets.Size = 0.1;
			New->Vals.Facets.UseCoords = 0;
			New->Vals.Facets.Metric = 2;
			EXIT
		END_CASE

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

		CASE (IMAGE_PATTERN_TOKEN)
			if ((Old_Type==BITMAP_PATTERN) || (Old_Type==IMAGE_PATTERN))
			{
				Destroy_Image(Old_Image);
			}

			New->Type = IMAGE_PATTERN;
			New->Frequency = 0.0;
			Parse_Image_Pattern (New);
			EXIT
		END_CASE

		CASE (PLANAR_TOKEN)
			New->Type = PLANAR_PATTERN;
			EXIT
		END_CASE

		CASE (BOXED_TOKEN)
			New->Type = BOXED_PATTERN;
			EXIT
		END_CASE

		CASE (SPHERICAL_TOKEN)
			New->Type = SPHERICAL_PATTERN;
			EXIT
		END_CASE

		CASE (CYLINDRICAL_TOKEN)
			New->Type = CYLINDRICAL_PATTERN;
			EXIT
		END_CASE

		CASE (DENSITY_FILE_TOKEN)
			if (Old_Type==DENSITY_FILE_PATTERN)
			{
				Destroy_Density_File(Old_Density_File);
			}
			New->Type = DENSITY_FILE_PATTERN;
			New->Vals.Density_File = Create_Density_File();
			GET(DF3_TOKEN);
			New->Vals.Density_File->Data->Name = Parse_C_String(true);
			{
				IStream *dfile = Locate_File(this, sceneData, ASCIItoUCS2String(New->Vals.Density_File->Data->Name).c_str(), POV_File_Data_DF3, ign, true);
				if(dfile == NULL)
					Error("Cannot read media density file.");
				Read_Density_File(dfile, New->Vals.Density_File);
			}
			EXIT
		END_CASE

		CASE (SLOPE_TOKEN)
			New->Type = SLOPE_PATTERN;
			New->Vals.Slope.Altit_Vector[X] = 0.0;
			New->Vals.Slope.Altit_Vector[Y] = 0.0;
			New->Vals.Slope.Altit_Vector[Z] = 0.0;
			New->Vals.Slope.Slope_Mod[U]    = 0.0;
			New->Vals.Slope.Altit_Mod[U]    = 0.0;
			New->Vals.Slope.Slope_Mod[V]    = 0.0;
			New->Vals.Slope.Altit_Mod[V]    = 0.0;
			New->Vals.Slope.Point_At        = false;

			EXPECT
				/* simple syntax */
				CASE_EXPRESS
					Parse_Vector (New->Vals.Slope.Slope_Vector);
					EXIT
				END_CASE

				/* new syntax */
				CASE(LEFT_CURLY_TOKEN)
					UNGET
					Parse_Begin();

					EXPECT_ONE
						CASE(POINT_AT_TOKEN)
							New->Vals.Slope.Point_At = true;
							EXIT
						END_CASE
						OTHERWISE
							UNGET
							EXIT
						END_CASE
					END_EXPECT

					/* parse the vector */
					Parse_Vector (New->Vals.Slope.Slope_Vector);

					/* allow low slope, high slope */
					Parse_Comma();
					New->Vals.Slope.Slope_Mod[0] = Allow_Float(0.0);
					Parse_Comma();
					New->Vals.Slope.Slope_Mod[1] = Allow_Float(0.0);


					if (New->Vals.Slope.Slope_Mod[V] != 0.0 &&
					    New->Vals.Slope.Slope_Mod[V] == New->Vals.Slope.Slope_Mod[U])
					{
						New->Vals.Slope.Slope_Mod[U] = 0.0;
						New->Vals.Slope.Slope_Mod[V] = 0.0;
						Warning (0, "Zero slope range, ignoring.");
					}
					else
					{
						New->Vals.Slope.Slope_Mod[V] -= New->Vals.Slope.Slope_Mod[U];
					}

					EXPECT
						CASE(ALTITUDE_TOKEN)
							if (New->Vals.Slope.Point_At)
							{
								Error("Keyword 'altitude' cannot be used with 'point_at' in slope pattern.");
							}
							else
							{
								Experimental_Flag |= EF_SLOPEM; // this feature is experimental
								Parse_Vector (New->Vals.Slope.Altit_Vector);

								/* allow low alt, high alt */
								Parse_Comma();
								New->Vals.Slope.Altit_Mod[0] = Allow_Float(0.0);
								Parse_Comma();
								New->Vals.Slope.Altit_Mod[1] = Allow_Float(0.0);

								if (New->Vals.Slope.Altit_Mod[V] != 0.0 &&
								    New->Vals.Slope.Altit_Mod[V] == New->Vals.Slope.Altit_Mod[U])
								{
									New->Vals.Slope.Altit_Mod[U] = 0.0;
									New->Vals.Slope.Altit_Mod[V] = 0.0;
									Warning (0, "Zero gradient range, ignoring.");
								}
								else
								{
									New->Vals.Slope.Altit_Mod[V] -= New->Vals.Slope.Altit_Mod[U];
								}
							}
						END_CASE

						OTHERWISE
							UNGET
							EXIT
						END_CASE
					END_EXPECT


					Parse_End();
					EXIT
				END_CASE

				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT

			if (New->Vals.Slope.Point_At)
			{
				New->Vals.Slope.Slope_Len = 1.0;
				New->Vals.Slope.Altit_Len = 0.0;
			}
			else
			{
				VLength(New->Vals.Slope.Slope_Len, New->Vals.Slope.Slope_Vector);
				VLength(New->Vals.Slope.Altit_Len, New->Vals.Slope.Altit_Vector);

				if (New->Vals.Slope.Slope_Len != 0.0) VNormalizeEq(New->Vals.Slope.Slope_Vector);
				if (New->Vals.Slope.Altit_Len != 0.0) VNormalizeEq(New->Vals.Slope.Altit_Vector);

				Tot_Len = New->Vals.Slope.Slope_Len + New->Vals.Slope.Altit_Len;

				if (Tot_Len != 0.0)
				{
					New->Vals.Slope.Slope_Len /= Tot_Len;
					New->Vals.Slope.Altit_Len  = 1.0 - New->Vals.Slope.Slope_Len;
				}
				else
				{
					Error ("Zero length for both slope and gradient.");
				}
			}

			/* pre-process some stuff (look for shortcuts) for speed rendering speed */
			New->Vals.Slope.Slope_Base = New->Vals.Slope.Altit_Base = 0; /* mark unused */
			if (!New->Vals.Slope.Point_At)
			{
				for (i=X; i<=Z; i++)
				{
					if      (New->Vals.Slope.Slope_Vector[i] == 1.0) { New->Vals.Slope.Slope_Base = i+1; break; }
					else if (New->Vals.Slope.Slope_Vector[i] == -1.0) { New->Vals.Slope.Slope_Base = -(i+1); break; }
				}
				for (i=X; i<=Z; i++)
				{
					if      (New->Vals.Slope.Altit_Vector[i] == 1.0) { New->Vals.Slope.Altit_Base = i+1; break; }
					else if (New->Vals.Slope.Altit_Vector[i] == -1.0) { New->Vals.Slope.Altit_Base = -(i+1); break; }
				}
			}
			EXIT
		END_CASE

		CASE (AOI_TOKEN)
			New->Type = AOI_PATTERN;
			EXIT
		END_CASE

		CASE (PAVEMENT_TOKEN)
			New->Type = PAVEMENT_PATTERN;
			New->Vals.Pavement.Side = 3;
			New->Vals.Pavement.Tile = 1;
			New->Vals.Pavement.Number = 1;
			New->Vals.Pavement.Exterior = 0;
			New->Vals.Pavement.Interior = 0;
			New->Vals.Pavement.Form = 0;
			EXIT
		END_CASE

		CASE (TILING_TOKEN)
			New->Type = TILING_PATTERN;
			New->Vals.Tiling.Pattern = (unsigned char)Parse_Float();
			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 ((Old_Type==DENSITY_FILE_PATTERN) && (New->Type!=DENSITY_FILE_PATTERN))
	{
		Destroy_Density_File(Old_Density_File);
	}

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

	EXPECT         /* Look for pattern_modifier */
		CASE (ACCURACY_TOKEN)
			if(TPat_Type != NORMAL_TYPE)
			{
				Error("accuracy can only be used with normal patterns.");
			}
			((TNORMAL *)New)->Delta = Parse_Float();
		END_CASE

		CASE (SOLID_TOKEN)
			if (New->Type != CRACKLE_PATTERN )
			{
				Only_In("solid", "crackle");
			}
			New->Vals.Crackle.IsSolid = 1;
		END_CASE

		CASE (EXTERIOR_TOKEN)
			if(!((New->Type == MANDEL_PATTERN) || (New->Type == MANDEL3_PATTERN) ||
			     (New->Type == MANDEL4_PATTERN) || (New->Type == MANDELX_PATTERN) ||
			     (New->Type == JULIA_PATTERN) || (New->Type == JULIA3_PATTERN) ||
			     (New->Type == JULIA4_PATTERN) || (New->Type == JULIAX_PATTERN) ||
			     (New->Type == MAGNET1M_PATTERN) || (New->Type == MAGNET2M_PATTERN) ||
			     (New->Type == MAGNET1J_PATTERN) || (New->Type == MAGNET2J_PATTERN) ||
			     (New->Type == PAVEMENT_PATTERN)))
			{
				Only_In("exterior", "mandel, julia, magnet or pavement");
			}
			else if (New->Type == PAVEMENT_PATTERN)
			{
				New->Vals.Pavement.Exterior = (unsigned char)Parse_Float();
			}
			else
			{
				New->Vals.Fractal.exterior_type = (int)Parse_Float();
				if((New->Vals.Fractal.exterior_type < 0) || (New->Vals.Fractal.exterior_type > 8))
					Error("Invalid fractal pattern exterior type. Valid types are 0 to 8.");
				Parse_Comma();
				New->Vals.Fractal.efactor = Parse_Float();
			}
		END_CASE

		CASE (INTERIOR_TOKEN)
			if(!((New->Type == MANDEL_PATTERN) || (New->Type == MANDEL3_PATTERN) ||
			     (New->Type == MANDEL4_PATTERN) || (New->Type == MANDELX_PATTERN) ||
			     (New->Type == JULIA_PATTERN) || (New->Type == JULIA3_PATTERN) ||
			     (New->Type == JULIA4_PATTERN) || (New->Type == JULIAX_PATTERN) ||
			     (New->Type == MAGNET1M_PATTERN) || (New->Type == MAGNET2M_PATTERN) ||
			     (New->Type == MAGNET1J_PATTERN) || (New->Type == MAGNET2J_PATTERN) ||
			     (New->Type == PAVEMENT_PATTERN)))
			{
				Only_In("exterior", "mandel, julia, magnet or pavement");
			}
			else if (New->Type == PAVEMENT_PATTERN)
			{
				New->Vals.Pavement.Interior = (unsigned char)Parse_Float();
			}
			else
			{
				New->Vals.Fractal.interior_type = (int)Parse_Float();
				if((New->Vals.Fractal.interior_type < 0) || (New->Vals.Fractal.interior_type > 6))
					Error("Invalid fractal pattern interior type. Valid types are 0 to 6.");
				Parse_Comma();
				New->Vals.Fractal.ifactor = Parse_Float();
			}
		END_CASE

		CASE (EXPONENT_TOKEN)
			if(!((New->Type == MANDEL_PATTERN) || (New->Type == MANDEL3_PATTERN) ||
			     (New->Type == MANDEL4_PATTERN) || (New->Type == MANDELX_PATTERN) ||
			     (New->Type == JULIA_PATTERN) || (New->Type == JULIA3_PATTERN) ||
			     (New->Type == JULIA4_PATTERN) || (New->Type == JULIAX_PATTERN)))
			{
				Only_In("exponent", "mandel or julia");
			}

			if((New->Type == JULIA_PATTERN) || (New->Type == JULIA3_PATTERN) || (New->Type == JULIA4_PATTERN))
			{
				New->Vals.Fractal.Exponent = i = (int)Parse_Float();
				switch(i)
				{
					case 2:
						New->Type = JULIA_PATTERN;
						break;
					case 3:
						New->Type = JULIA3_PATTERN;
						break;
					case 4:
						New->Type = JULIA4_PATTERN;
						break;
					default:
						if((i > 4) && (i <= 33))
						{
							New->Type = JULIAX_PATTERN;
						}
						else
						{
							New->Type = JULIA_PATTERN;
							Warning(0, "Invalid julia pattern exponent found. Supported exponents are 2 to 33.\n"
							           "Using default exponent 2.");
						}
						break;
				}
			}
			else if((New->Type == MANDEL_PATTERN) || (New->Type == MANDEL3_PATTERN) || (New->Type == MANDEL4_PATTERN))
			{
				New->Vals.Fractal.Exponent = i = (int)Parse_Float();
				switch(i)
				{
					case 2:
						New->Type = MANDEL_PATTERN;
						break;
					case 3:
						New->Type = MANDEL3_PATTERN;
						break;
					case 4:
						New->Type = MANDEL4_PATTERN;
						break;
					default:
						if((i > 4) && (i <= 33))
						{
							New->Type = MANDELX_PATTERN;
						}
						else
						{
							New->Type = MANDEL_PATTERN;
							Warning(0, "Invalid mandel pattern exponent found. Supported exponents are 2 to 33.\n"
							           "Using default exponent 2.");
						}
						break;
				}
			}
		END_CASE

		CASE (COORDS_TOKEN)
			if (New->Type != FACETS_PATTERN )
			{
				Only_In("coords", "facets");
			}
			New->Vals.Facets.UseCoords = Parse_Float();
		END_CASE

		CASE (SIZE_TOKEN)
			if (New->Type == FACETS_PATTERN )
			{
				New->Vals.Facets.Size = Parse_Float();
			}
			else
			{
				Only_In("size", "facets");
			}
		END_CASE

		CASE (METRIC_TOKEN)
			if (New->Type == FACETS_PATTERN )
			{
				Parse_Vector(Local_Vector);
				New->Vals.Facets.Metric = Local_Vector[X];
			}
			else
			{
				if ( New->Type == CRACKLE_PATTERN )
				{
					// Vector for backwards compatibility
					// the only component used was always X.
					Parse_Vector(Local_Vector);
					New->Vals.Crackle.Metric = Local_Vector[X];
				}
				else
				{
					Only_In("metric", "facets or crackle");
				}
			}
		END_CASE

		CASE (FORM_TOKEN)
			if ((New->Type != CRACKLE_PATTERN) && (New->Type != PAVEMENT_PATTERN))
			{
				Only_In("form", "crackle or pavement");
			}
			else if (New->Type == CRACKLE_PATTERN)
			{
				Parse_Vector( New->Vals.Crackle.Form );
			}
			else
			{
				New->Vals.Pavement.Form = ((unsigned char)Parse_Float());
			}
		END_CASE

		CASE (OFFSET_TOKEN)
			if (New->Type != CRACKLE_PATTERN )
			{
				Only_In("offset", "crackle");
			}
			New->Vals.Crackle.Offset = Parse_Float();
		END_CASE

		CASE (TURBULENCE_TOKEN)
			Local_Turb=Check_Turb(&(New->Warps));
			Parse_Vector(Local_Turb->Turbulence);
		END_CASE

		CASE (COLOUR_MAP_TOKEN)
			if ((TPat_Type != PIGMENT_TYPE) && (TPat_Type != DENSITY_TYPE))
			{
				Only_In("color_map","pigment");
			}
			if (New->Type == CHECKER_PATTERN ||
			    New->Type == BRICK_PATTERN ||
			    New->Type == HEXAGON_PATTERN ||
			    New->Type == SQUARE_PATTERN ||
			    New->Type == TRIANGULAR_PATTERN ||
			    New->Type == CUBIC_PATTERN || // JN2007: Cubic pattern
			    New->Type == PLAIN_PATTERN ||
			    New->Type == AVERAGE_PATTERN ||
			    New->Type == OBJECT_PATTERN ||
			    New->Type == BITMAP_PATTERN)
			{
				Error("Cannot use color_map with this pattern 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 == SQUARE_PATTERN ||
			    New->Type == TRIANGULAR_PATTERN ||
			    New->Type == CUBIC_PATTERN || // JN2007: Cubic pattern
			    New->Type == PLAIN_PATTERN ||
			    New->Type == OBJECT_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 (DENSITY_MAP_TOKEN)
			if (TPat_Type != DENSITY_TYPE)
			{
				Only_In("density_map","density");
			}
			if (New->Type == CHECKER_PATTERN ||
			    New->Type == BRICK_PATTERN ||
			    New->Type == HEXAGON_PATTERN ||
			    New->Type == SQUARE_PATTERN ||
			    New->Type == TRIANGULAR_PATTERN ||
			    New->Type == CUBIC_PATTERN || // JN2007: Cubic pattern
			    New->Type == PLAIN_PATTERN ||
			    New->Type == OBJECT_PATTERN ||
			    New->Type == BITMAP_PATTERN)
				Not_With ("density_map","this density type");
			Destroy_Blend_Map(New->Blend_Map);
			New->Blend_Map = Parse_Blend_Map (DENSITY_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 == SQUARE_PATTERN ||
			    New->Type == TRIANGULAR_PATTERN ||
			    New->Type == CUBIC_PATTERN || // JN2007: Cubic pattern
			    New->Type == PLAIN_PATTERN ||
			    New->Type == AVERAGE_PATTERN ||
			    New->Type == OBJECT_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 == FACETS_PATTERN ||
			    New->Type == HEXAGON_PATTERN ||
			    New->Type == SQUARE_PATTERN ||
			    New->Type == TRIANGULAR_PATTERN ||
			    New->Type == CUBIC_PATTERN || // JN2007: Cubic pattern
			    New->Type == PLAIN_PATTERN ||
			    New->Type == OBJECT_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 == SQUARE_PATTERN ||
			    New->Type == TRIANGULAR_PATTERN ||
			    New->Type == CUBIC_PATTERN || // JN2007: Cubic pattern
			    New->Type == PLAIN_PATTERN ||
			    New->Type == OBJECT_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 (((PIGMENT *)New)->Quick_Colour);
		END_CASE

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

		CASE (CONTROL1_TOKEN)
			if (New->Type == QUILTED_PATTERN)
			{
				New->Vals.Quilted.Control1 = Parse_Float ();
			}
			else
			{
				Not_With ("control1","this pattern");
			}
		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 (CUBIC_WAVE_TOKEN)
			New->Wave_Type = CUBIC_WAVE;
		END_CASE

		CASE (POLY_WAVE_TOKEN)
			New->Wave_Type = POLY_WAVE;
			New->Exponent  = Allow_Float(New->Exponent);
		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 (NOISE_GENERATOR_TOKEN)
		{
			int noise_generator;
			noise_generator = (int) Parse_Float();
			if (noise_generator < 0 || noise_generator > 3)
				Error ("Value for noise_generator must be 0, 1, 2, or 3.");
			New->Flags |= noise_generator * NOISE_FLAG_1;
		}
		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()-EPSILON*2.0;
		END_CASE

		CASE (INTERPOLATE_TOKEN)
			if (New->Type != DENSITY_FILE_PATTERN)
				Not_With ("interpolate","non-density_file");
			New->Vals.Density_File->Interpolation = (int)Parse_Float();
		END_CASE

		CASE (NUMBER_OF_SIDES_TOKEN)
			if (New->Type != PAVEMENT_PATTERN)
				Only_In("number_of_sides","pavement");
			New->Vals.Pavement.Side=(unsigned char)Parse_Float();
		END_CASE

		CASE (NUMBER_OF_TILES_TOKEN)
			if (New->Type != PAVEMENT_PATTERN)
				Only_In("number_of_tiles","pavement");
			New->Vals.Pavement.Tile=(unsigned char)Parse_Float();
		END_CASE

		CASE (PATTERN_TOKEN)
			if (New->Type != PAVEMENT_PATTERN)
				Only_In("pattern","pavement");
			New->Vals.Pavement.Number=(unsigned char)Parse_Float();
		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)
			Transform_Tpattern (New, Parse_Transform(&Local_Trans));
		END_CASE

		CASE (NO_BUMP_SCALE_TOKEN)
			Set_Flag(New,DONT_SCALE_BUMPS_FLAG);
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	if ((New->Type==AVERAGE_PATTERN) && (New->Blend_Map==NULL))
	{
		Error("Average must have map.");
	}

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

	if ((New->Type==PAVEMENT_PATTERN))
	{
		const int valid6[]={1,1,3,7,22};
		const int valid4[]={1,1,2,5,12,35};
		const int valid3[]={1,1,1,3,4,12};
		New->Vals.Pavement.Interior %= 3;
		New->Vals.Pavement.Exterior %= 3;
		switch(New->Vals.Pavement.Side)
		{
			case 4:
				if ((!(New->Vals.Pavement.Tile)) || (New->Vals.Pavement.Tile>6))
				{
					Error("number_of_tiles of pavement must be between 1 and 6.");
				}
				else if ((!(New->Vals.Pavement.Number)) || (New->Vals.Pavement.Number > valid4[New->Vals.Pavement.Tile - 1]))
				{
					Error("pattern value for pavement is out of range. max per tile:(1,1,2,5,12,35)");
				}
				New->Vals.Pavement.Form %=4;
				break;

			case 6:
				if ((!(New->Vals.Pavement.Tile)) || (New->Vals.Pavement.Tile > 5))
				{
					Error("number_of_tiles of pavement must be between 1 and 5.");
				}
				else if ((!(New->Vals.Pavement.Number)) || (New->Vals.Pavement.Number > valid6[New->Vals.Pavement.Tile - 1]))
				{
					Error("pattern value for pavement is out of range. max per tile:(1,1,3,7,22)");
				}
				New->Vals.Pavement.Form %= 3;
				break;

			case 3:
				if ((!(New->Vals.Pavement.Tile)) || (New->Vals.Pavement.Tile > 6))
				{
					Error("number_of_tiles of pavement must be between 1 and 6.");
				}
				else if ((!(New->Vals.Pavement.Number)) || (New->Vals.Pavement.Number > valid3[New->Vals.Pavement.Tile - 1]))
				{
					Error("pattern value for pavement is out of range. max per tile:(1,1,1,3,4,12)");
				}
				New->Vals.Pavement.Form %= 3;
				break;

			default:
				Error("number_of_sides of pavement must be 3, 4 or 6.");
		}

	}

	if ((New->Type==TILING_PATTERN) && ((New->Vals.Tiling.Pattern < 1) || (New->Vals.Tiling.Pattern > 27)))
	{
		Error("Tiling number must be between 1 and 27.");
	}
}



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

void Parser::Parse_Tnormal (TNORMAL **Tnormal_Ptr)
{
	EXPECT            /* Look for [tnormal_id] */
		CASE (TNORMAL_ID_TOKEN)
			Destroy_Tnormal(*Tnormal_Ptr);
			*Tnormal_Ptr = Copy_Tnormal ((TNORMAL *) Token.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 Parser::Parse_Finish (FINISH **Finish_Ptr)
{
	Colour Temp_Colour;
	FINISH *New;
	VECTOR Local_Vector;
	bool diffuseAdjust = false;
	bool phongAdjust = false;
	bool specularAdjust = false;

	Parse_Begin ();

	EXPECT        /* Look for zero or one finish_id */
		CASE (FINISH_ID_TOKEN)
			Destroy_Finish(*Finish_Ptr);
			*Finish_Ptr = Copy_Finish ((FINISH *) Token.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 (CONSERVE_ENERGY_TOKEN)
			New->Conserve_Energy = true;
		END_CASE

		CASE (AMBIENT_TOKEN)
			Parse_Colour(New->Ambient);
		END_CASE

		CASE (EMISSION_TOKEN)
			Parse_Colour(New->Emission);
		END_CASE

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

		CASE (DIFFUSE_TOKEN)
			EXPECT
				CASE (ALBEDO_TOKEN)
					diffuseAdjust = true;
				END_CASE
				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT

			New->Diffuse = Parse_Float ();
			Parse_Comma();
			New->DiffuseBack = Allow_Float(0.0);
			if (New->DiffuseBack != 0.0)
				Experimental_Flag |= EF_BACKILL;
		END_CASE

		CASE (REFLECTION_TOKEN)
		{
			bool found_second_color = false;
			EXPECT
				/* old syntax */
				CASE_EXPRESS
					Parse_Colour(New->Reflection_Max);
					New->Reflection_Min = New->Reflection_Max;
					New->Reflection_Falloff = 1;
					New->Reflection_Type = 0;
					EXIT
				END_CASE

				/* new syntax */
				CASE(LEFT_CURLY_TOKEN)
					UNGET
					Parse_Begin();

					/* parse the first colour */
					Parse_Colour(New->Reflection_Min);
					Parse_Comma();

					/* look for a second color */
					EXPECT
						CASE_EXPRESS
							Parse_Colour(New->Reflection_Max);
							found_second_color = true;
							EXIT
						END_CASE

						OTHERWISE
							UNGET
							/* by default, use reflection min = reflection max */
							New->Reflection_Max = New->Reflection_Min;
							EXIT
						END_CASE
					END_EXPECT

					/* look for a other options */
					EXPECT
						CASE(FRESNEL_TOKEN)
							New->Reflection_Type = (int) Allow_Float(1.0);
							if (New->Reflection_Type < 0 || New->Reflection_Type > 1)
							{
								Error("fresnel must be true or false");
							}

							/* for fresnel reflectivity, if the user didn't specify a min & max,
							   then we will set the min to zero - otherwise, this setting would
							   have no effect */
							if(!found_second_color && New->Reflection_Type > 0)
							{
								New->Reflection_Min.clear();
							}
						END_CASE

						CASE (FALLOFF_TOKEN)
							New->Reflection_Falloff = Parse_Float();
						END_CASE

						CASE (EXPONENT_TOKEN)
							New->Reflect_Exp = 1.0 / Parse_Float ();
						END_CASE

						CASE (METALLIC_TOKEN)
							New->Reflect_Metallic = Allow_Float(1.0);
						END_CASE

						OTHERWISE
							UNGET
							EXIT
						END_CASE
					END_EXPECT

					Parse_End();
					EXIT
				END_CASE

				OTHERWISE
					UNGET
					EXIT
				END_CASE

			END_EXPECT
		}
		END_CASE

		CASE (REFLECTION_EXPONENT_TOKEN)
			New->Reflect_Exp = 1.0 / Parse_Float ();
		END_CASE

		CASE (PHONG_TOKEN)
			EXPECT
				CASE (ALBEDO_TOKEN)
					phongAdjust = true;
				END_CASE
				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT

			New->Phong = Parse_Float ();
		END_CASE

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

		CASE (SPECULAR_TOKEN)
			EXPECT
				CASE (ALBEDO_TOKEN)
					specularAdjust = true;
				END_CASE
				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT

			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
				Warning(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 (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(Local_Vector);
					New->Irid_Turb = Local_Vector[X];
				END_CASE

				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT
			Parse_End();
		END_CASE

		CASE (IOR_TOKEN)
			New->Temp_IOR = Parse_Float();
			Warn_Compat(0, "Index of refraction value should be specified in 'interior{...}' statement.");
		END_CASE

		CASE (CAUSTICS_TOKEN)
			New->Temp_Caustics = Parse_Float();
			Warn_Compat(0, "Caustics value should be specified in 'interior{...}' statement.");
		END_CASE

		CASE (REFRACTION_TOKEN)
			New->Temp_Refract = Parse_Float();
			Warn_Compat(0, "Refraction value unnecessary to turn on refraction.\nTo attenuate, the fade_power and fade_distance keywords should be specified in 'interior{...}' statement.");
		END_CASE

		CASE (SUBSURFACE_TOKEN)
			Experimental_Flag |= EF_SSLT;
			New->UseSubsurface = true;
			Parse_Begin();
			EXPECT
				CASE (ANISOTROPY_TOKEN)
					Parse_Colour(New->SubsurfaceAnisotropy);
				END_CASE
				CASE (TRANSLUCENCY_TOKEN)
					Parse_Colour(New->SubsurfaceTranslucency);
				END_CASE
				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT
			Parse_End();
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE

	END_EXPECT    /* End of finish_body */

#if 0
	EXPECT        /* Look for finish_mods */

/*		CASE none implemented - if you implement one remember to remove the #if 0 and the #endif 
		around this block of code
		END_CASE     */

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

	// If requested by the user via the "albedo" keyword,
	// adjust diffuse, phong and/or specular intensity parameters
	// so that a user-specified value of 1.0 corresponds to a
	// backscattering of 100% of the incoming light
	New->RawDiffuse     = New->Diffuse;
	New->RawDiffuseBack = New->DiffuseBack;
	if (diffuseAdjust)
	{
		New->Diffuse     *= (New->Brilliance + 1.0) / 2.0;
		New->DiffuseBack *= (New->Brilliance + 1.0) / 2.0;
	}
	if (phongAdjust)
		New->Phong *= (New->Phong_Size + 1.0) / 2.0;
	if (specularAdjust)
		New->Specular *= (New->Roughness + 2.0) / (4.0 * ( 2.0 - pow( 2.0, -New->Roughness / 2.0 ) ) );

	Parse_End ();
}



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

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

	if (sceneData->languageVersion < 300)
	{
		return(Parse_Vers1_Texture());
	}

	Modified_Pnf = false;

	EXPECT               /* First allow a texture identifier */
		CASE (TEXTURE_ID_TOKEN)
			Texture = Copy_Textures((TEXTURE *) Token.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.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.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.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 (TRANSLATE_TOKEN)
				Parse_Vector (Local_Vector);
				Compute_Translation_Transform(&Local_Trans, Local_Vector);
				Transform_Textures (Texture, &Local_Trans);
				Modified_Pnf = true;
			END_CASE

			CASE (ROTATE_TOKEN)
				Parse_Vector (Local_Vector);
				Compute_Rotation_Transform(&Local_Trans, Local_Vector);
				Transform_Textures (Texture, &Local_Trans);
				Modified_Pnf = true;
			END_CASE

			CASE (SCALE_TOKEN)
				Parse_Scale_Vector (Local_Vector);
				Compute_Scaling_Transform(&Local_Trans, Local_Vector);
				Transform_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)
				Transform_Textures (Texture, Parse_Transform(&Local_Trans));
				Modified_Pnf = true;
			END_CASE

			CASE (NO_BUMP_SCALE_TOKEN)
				Set_Flag(Texture,DONT_SCALE_BUMPS_FLAG);
			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();
					if (Texture->Blend_Map->Blend_Map_Entries[1].Vals.Texture == NULL)
						Error("First texture missing from 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;
					Destroy_Pigment(Texture->Pigment);
					Destroy_Tnormal(Texture->Tnormal);
					Destroy_Finish(Texture->Finish);
					Texture->Pigment = NULL;
					Texture->Tnormal = NULL;
					Texture->Finish  = NULL;
					Parse_Pattern((TPATTERN *)Texture,TEXTURE_TYPE);
					/* if following is true, parsed "texture{}" so restore
					   default texture.
					 */
					if (Texture->Type <= PLAIN_PATTERN)
					{
						Destroy_Textures(Texture);
						Texture = Copy_Textures (Default_Texture);
					}
					EXIT
				END_CASE
			END_EXPECT
		}

		return (Texture);
}



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

TEXTURE *Parser::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);
	Texture->Pigment = NULL;
	Texture->Tnormal = NULL;
	Texture->Finish  = 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
*
******************************************************************************/

TEXTURE *Parser::Parse_Material_Map()
{
	TEXTURE *Texture, *Local_Texture;
	Parse_Begin ();

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

	Texture->Vals.image = Parse_Image(MATERIAL_FILE);
	Texture->Vals.image->Use = USE_NONE; // was false [trf]

	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

		CASE (REPEAT_TOKEN)
			UV_VECT Repeat;
			Parse_UV_Vect (Repeat);
			if ((Repeat[0]<=0.0) || (Repeat[1]<=0.0))
				Error("Zero or Negative Image Repeat Vector.");
			Texture->Vals.image->width  =  (DBL)Texture->Vals.image->iwidth  * Repeat[0];
			Texture->Vals.image->height =  (DBL)Texture->Vals.image->iheight * Repeat[1];
		END_CASE

		CASE (OFFSET_TOKEN)
			Parse_UV_Vect (Texture->Vals.image->Offset);
			Texture->Vals.image->Offset[U] *= (DBL)-Texture->Vals.image->iwidth;
			Texture->Vals.image->Offset[V] *= (DBL)-Texture->Vals.image->iheight;
		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
*
******************************************************************************/

TEXTURE *Parser::Parse_Vers1_Texture ()
{
	VECTOR Local_Vector;
	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();
			if (Texture->Blend_Map->Blend_Map_Entries[1].Vals.Texture == NULL)
				Error("First texture missing from tiles");
			EXIT
		END_CASE

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

		CASE (TEXTURE_ID_TOKEN)
			Texture = Copy_Textures((TEXTURE *) Token.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.Data);
				END_CASE

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

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

				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT

			Pigment = Texture->Pigment;
			Tnormal = Texture->Tnormal;
			Finish  = Texture->Finish;

			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 (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);
					VNormalizeEq(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 (SQUARE_TOKEN)
					Warn_State(Token.Token_Id, PIGMENT_TOKEN);
					Pigment->Type = SQUARE_PATTERN;
					Pigment->Frequency = 0.0;
					Destroy_Blend_Map(Pigment->Blend_Map);
					Pigment->Blend_Map = Parse_Blend_List(4,&Square_Default_Map,COLOUR_TYPE);
				END_CASE

				CASE (TRIANGULAR_TOKEN)
					Warn_State(Token.Token_Id, PIGMENT_TOKEN);
					Pigment->Type = TRIANGULAR_PATTERN;
					Pigment->Frequency = 0.0;
					Destroy_Blend_Map(Pigment->Blend_Map);
					Pigment->Blend_Map = Parse_Blend_List(6,&Triangular_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(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 == SQUARE_PATTERN ||
					    Pigment->Type == TRIANGULAR_PATTERN ||
					    Pigment->Type == CUBIC_PATTERN || // JN2007: Cubic pattern
					    Pigment->Type == PLAIN_PATTERN ||
					    Pigment->Type == BITMAP_PATTERN)
						Warning(150, "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 (Pigment->Quick_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 (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 (sceneData->languageVersion >= 150)
							Warning(150, "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 (sceneData->languageVersion >= 150)
							Warning(150, "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 = RGBColour(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_Max = RGBColour(Parse_Float ());
					Finish->Reflection_Min = Finish->Reflection_Max;
					Finish->Reflection_Falloff = 1;
				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
						Warning(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();
					Warning(150, "Should use crand keyword in finish statement.");
				END_CASE

				CASE (IOR_TOKEN)
					Warn_State(Token.Token_Id, INTERIOR_TOKEN);
					Finish->Temp_IOR = Parse_Float();
					Warn_Compat(0, "Index of refraction value should be specified in 'interior{...}' statement.");
				END_CASE

				CASE (REFRACTION_TOKEN)
					Warn_State(Token.Token_Id, INTERIOR_TOKEN);
					Finish->Temp_Refract = Parse_Float();
					Warn_Compat(0, "Refraction value unnecessary to turn on refraction.\nTo attenuate, the fade_power and fade_distance keywords should be specified in 'interior{...}' statement.");
				END_CASE

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

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

				CASE (SCALE_TOKEN)
					Parse_Scale_Vector (Local_Vector);
					Compute_Scaling_Transform(&Local_Trans, Local_Vector);
					Transform_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)
					Transform_Textures (Texture, Parse_Transform(&Local_Trans));
				END_CASE

				CASE (TEXTURE_ID_TOKEN)
					Warning(0, "Texture identifier overwriting previous values.");
					Destroy_Textures(Texture);
					Texture = Copy_Textures((TEXTURE *) Token.Data);
					Pigment = Texture->Pigment;
					Tnormal = Texture->Tnormal;
					Finish  = Texture->Finish;
				END_CASE

				OTHERWISE
					UNGET
					EXIT
				END_CASE

/***********************************************************************/

			END_EXPECT

			if (Not_In_Default && (Texture->Pigment->Type == NO_PATTERN) &&
			    !(sceneData->languageVersion < 150))
				Parse_Error(PIGMENT_ID_TOKEN);

		}

	Parse_Texture_Transform(Texture);

	return (Texture);
}



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

void Parser::Parse_Texture_Transform (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);
			Transform_Textures (Texture, &Local_Trans);
		END_CASE

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

		CASE (SCALE_TOKEN)
			Parse_Scale_Vector (Local_Vector);
			Compute_Scaling_Transform(&Local_Trans, Local_Vector);
			Transform_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)
			Transform_Textures (Texture, Parse_Transform(&Local_Trans));
		END_CASE

		CASE (NO_BUMP_SCALE_TOKEN)
			Set_Flag(Texture,DONT_SCALE_BUMPS_FLAG);
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT
}


/*****************************************************************************
*
* FUNCTION
*
*   Parse_Media
*
* INPUT
*
* OUTPUT
*
* RETURNS
*   
* AUTHOR
*
*   Dieter Bayer
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   Dec 1996 : Creation.
*
******************************************************************************/

void Parser::Parse_Media(vector<Media>& medialist)
{
	Media IMediaObj;
	Media *IMedia = &IMediaObj;
	TRANSFORM Local_Trans;
	VECTOR Local_Vector;
	MATRIX Local_Matrix;

	Parse_Begin();

	EXPECT
		CASE(MEDIA_ID_TOKEN)
			IMediaObj = *((Media *)Token.Data);
			EXIT
		END_CASE

		OTHERWISE
			UNGET
			/* with version 3.5+, the default media method is now 3 */
			if(sceneData->languageVersion >= 350)
			{
				IMedia->Intervals = 1;
				IMedia->Min_Samples = 10;
				IMedia->Max_Samples = 10;
				IMedia->Sample_Method = 3;
			}
			EXIT
		END_CASE
	END_EXPECT

	EXPECT
		CASE (INTERVALS_TOKEN)
			if ((IMedia->Intervals = (int)Parse_Float()) < 1)
			{
				Error("At least one interval is needed in media.");
			}
		END_CASE

		CASE (SAMPLES_TOKEN)
			IMedia->Min_Samples = (int)Parse_Float();
			Parse_Comma();
			IMedia->Max_Samples = (int)Allow_Float(IMedia->Min_Samples);
			if (IMedia->Min_Samples < 1)
			{
				Error("At least one sample per interval is needed in media.");
			}
			if (IMedia->Max_Samples < IMedia->Min_Samples)
			{
				Error("Maximum number of samples per interval smaller than minimum number.");
			}
		END_CASE

		CASE (METHOD_TOKEN)
			IMedia->Sample_Method = (int)Parse_Float();
			if (IMedia->Sample_Method != 1 && IMedia->Sample_Method!= 2 && IMedia->Sample_Method!= 3)
			{
				Error("Sample method choices are 1, 2, or 3.");
			}
		END_CASE

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

		CASE (AA_THRESHOLD_TOKEN)
			IMedia->AA_Threshold = Parse_Float();
			if(IMedia->AA_Threshold<=0)
			{
				Error("aa_threshold in media must be greater than zero.");
			}
		END_CASE

		CASE (AA_LEVEL_TOKEN)
			IMedia->AA_Level = (int)Parse_Float();
			if(IMedia->AA_Level<1)
				Error("aa_level in media must be at least one.");
		END_CASE

		CASE (COLLECT_TOKEN)
			IMedia->ignore_photons = !(Allow_Float(1.0) > 0.0);
		END_CASE

		CASE (ABSORPTION_TOKEN)
			Parse_Colour(IMedia->Absorption);
		END_CASE

		CASE (EMISSION_TOKEN)
			Parse_Colour(IMedia->Emission);
		END_CASE

		CASE (SCATTERING_TOKEN)
			Parse_Begin();
			IMedia->Type = (int)Parse_Float();
			if ((IMedia->Type < 1) || (IMedia->Type > SCATTERING_TYPES))
			{
				Warning(0, "Unknown atmospheric scattering type.");
			}
			Parse_Comma();
			Parse_Colour(IMedia->Scattering);

			EXPECT
				CASE (ECCENTRICITY_TOKEN)
					if (IMedia->Type != HENYEY_GREENSTEIN_SCATTERING)
					{
						Error("Eccentricity cannot be used with this scattering type.");
					}
					IMedia->Eccentricity = Parse_Float();
				END_CASE

				CASE (EXTINCTION_TOKEN)
					IMedia->sc_ext = Parse_Float();
				END_CASE

				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT

			Parse_End();
		END_CASE

		CASE (CONFIDENCE_TOKEN)
			IMedia->Confidence = Parse_Float();
			if ((IMedia->Confidence < 0.0) || (IMedia->Confidence >= 1.0))
			{
				Error("Illegal confidence value in media.");
			}
		END_CASE

		CASE (VARIANCE_TOKEN)
			IMedia->Variance = Parse_Float();
		END_CASE

		CASE (RATIO_TOKEN)
			IMedia->Ratio = Parse_Float();
		END_CASE

		CASE (DENSITY_TOKEN)
			Parse_Begin();
			Parse_Media_Density_Pattern(&(IMedia->Density));
			Parse_End();
		END_CASE

		CASE (TRANSLATE_TOKEN)
			Parse_Vector (Local_Vector);
			Compute_Translation_Transform(&Local_Trans, Local_Vector);
			Transform_Density (IMedia->Density, &Local_Trans);
		END_CASE

		CASE (ROTATE_TOKEN)
			Parse_Vector (Local_Vector);
			Compute_Rotation_Transform(&Local_Trans, Local_Vector);
			Transform_Density (IMedia->Density, &Local_Trans);
		END_CASE

		CASE (SCALE_TOKEN)
			Parse_Scale_Vector (Local_Vector);
			Compute_Scaling_Transform(&Local_Trans, Local_Vector);
			Transform_Density (IMedia->Density, &Local_Trans);
		END_CASE

		CASE (MATRIX_TOKEN)
			Parse_Matrix(Local_Matrix);
			Compute_Matrix_Transform(&Local_Trans, Local_Matrix);
			Transform_Density (IMedia->Density, &Local_Trans);
		END_CASE

		CASE (TRANSFORM_TOKEN)
			Transform_Density (IMedia->Density, Parse_Transform(&Local_Trans));
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	Parse_End();

	medialist.push_back(IMediaObj);
}



/*****************************************************************************
*
* FUNCTION
*
*   Parse_Interior
*
* INPUT
*
* OUTPUT
*
* RETURNS
*   
* AUTHOR
*
*   Dieter Bayer
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   Jan 1997 : Creation.
*   Sep 1999 : Fade_Colour added - Edward Coffey
*
******************************************************************************/

void Parser::Parse_Interior(Interior **Interior_Ptr)
{
	Interior *interior = NULL;

	Parse_Begin();

	EXPECT
		CASE(INTERIOR_ID_TOKEN)
			Destroy_Interior(*Interior_Ptr);
			if(Token.Data != NULL)
				*Interior_Ptr = new Interior(*(Interior *)Token.Data);
			else
				*Interior_Ptr = new Interior();
			EXIT
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	if(*Interior_Ptr == NULL)
		*Interior_Ptr = new Interior();

	interior = *Interior_Ptr;

	EXPECT
		CASE (IOR_TOKEN)
			interior->IOR = Parse_Float();
		END_CASE

		CASE (DISPERSION_TOKEN)
			interior->Dispersion = Parse_Float();
		END_CASE

		CASE (DISPERSION_SAMPLES_TOKEN)
			interior->Disp_NElems = (int)Parse_Float();
			if (interior->Disp_NElems < 2)
				Error("Dispersion samples minimum is 2.");
		END_CASE

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

		CASE (FADE_COLOUR_TOKEN)
			Parse_Colour(interior->Fade_Colour);
		END_CASE

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

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

		CASE (MEDIA_TOKEN)
			Parse_Media(interior->media);
		END_CASE

		CASE (REFRACTION_TOKEN)
			interior->Old_Refract = Parse_Float();
			Warn_Compat(0, "Refraction value unnecessary to turn on refraction.\nTo attenuate, the fade_power and fade_distance keywords should be specified in 'interior{...}' statement.");
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	Parse_End();
}



/*****************************************************************************
*
* FUNCTION
*
*   Parse_Media_Density_Pattern
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Dieter Bayer
*
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   Dez 1996 : Creation.
*
******************************************************************************/

void Parser::Parse_Media_Density_Pattern(PIGMENT **Density_Ptr)
{
	PIGMENT *New;

	EXPECT
		CASE (DENSITY_ID_TOKEN)
			New = Copy_Pigment ((PIGMENT *) Token.Data);
			EXIT
		END_CASE

		OTHERWISE
			New = Create_Pigment();
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	Parse_Pattern((TPATTERN *)New,DENSITY_TYPE);

	New->Next = (TPATTERN *)(*Density_Ptr);
	*Density_Ptr = New;
}







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

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

	Parse_Begin();

	EXPECT
		CASE(FOG_ID_TOKEN)
			Fog = Copy_Fog ((FOG *) Token.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
			Warning(150, "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))
			{
				Warning(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(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);
			Warning(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)
			MTransDirection(Fog->Up, Fog->Up, Parse_Transform(&Trans));
		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 *Parser::Parse_Rainbow()
{
	int Angle1, Angle2;
	DBL dot;
	RAINBOW *Rainbow;

	Angle1 = Angle2 = false;

	Parse_Begin();

	EXPECT
		CASE(RAINBOW_ID_TOKEN)
			Rainbow = Copy_Rainbow ((RAINBOW *) Token.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).");
			}
			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).");
			}
			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.");
	}

	/* 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.");
	}

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

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

	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.");
	}

	/* 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 *Parser::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.Data);
			EXIT
		END_CASE

		OTHERWISE
			UNGET
			Skysphere = Create_Skysphere();
			EXIT
		END_CASE
	END_EXPECT

	EXPECT
		CASE (PIGMENT_TOKEN)
			Skysphere->Count++;
			Skysphere->Pigments = (PIGMENT **)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 (EMISSION_TOKEN)
			Parse_Colour(Skysphere->Emission);
		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)
			Transform_Skysphere(Skysphere, Parse_Transform(&Local_Trans));
		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
*
******************************************************************************/

void Parser::Check_BH_Parameters (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, "Black Hole repeat vector X too small ; increased to %g", bh->Repeat_Vector [X]) ;
		}
		if (bh->Repeat_Vector [X] < EPSILON)
		{
			Warning (0,"Black Hole repeat vector X is less than %f ; ignored", (float) EPSILON) ;
			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, "Black Hole repeat vector Y too small ; increased to %g", bh->Repeat_Vector [Y]) ;
		}
		if (bh->Repeat_Vector [Y] < EPSILON)
		{
			Warning (0, "Black Hole repeat vector Y is less than %f ; ignored", (float) EPSILON) ;
			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, "Black Hole repeat vector Z too small ; increased to %g", bh->Repeat_Vector [Z]) ;
		}
		if (bh->Repeat_Vector [Z] < EPSILON)
		{
			Warning (0, "Black Hole repeat vector Z is less than %f ; ignored", (float) EPSILON) ;
			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
*
******************************************************************************/

TURB *Parser::Check_Turb (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->Next_Warp->Prev_Warp = Temp;
			Temp = Temp->Next_Warp;
		}
	}
	return((TURB *)Temp);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
* CHANGES
*    Talious 10/24/1998: Added SPherical/Cylindrical/Toroidaal warps
*
******************************************************************************/

void Parser::Parse_Warp (WARP **Warp_Ptr)
{
	WARP *New = NULL;
	TURB *Turb;
	REPEAT *Repeat;
	BLACK_HOLE *Black_Hole;
	VECTOR Local_Vector;
	CYLW *CylW;
	SPHEREW *SphereW;
	TOROIDAL *Toroidal;
	PLANARW *PlanarW;

	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(CYLINDRICAL_TOKEN)
			New = Create_Warp(CYLINDRICAL_WARP);
			CylW = (CYLW *) New ;
			EXPECT
				CASE(ORIENTATION_TOKEN)
					Parse_Vector (Local_Vector) ;
					VNormalizeEq(Local_Vector);
					Assign_Vector (CylW->Orientation_Vector, Local_Vector) ;
				END_CASE

				CASE(DIST_EXP_TOKEN)
					CylW->DistExp = Parse_Float () ;
				END_CASE

				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT
			EXIT
		END_CASE

		CASE(SPHERICAL_TOKEN)
			New = Create_Warp(SPHERICAL_WARP);
			SphereW = (SPHEREW *) New ;
			EXPECT
				CASE(ORIENTATION_TOKEN)
					Parse_Vector (Local_Vector) ;
					VNormalizeEq(Local_Vector);
					Assign_Vector (SphereW->Orientation_Vector, Local_Vector) ;
				END_CASE

				CASE(DIST_EXP_TOKEN)
					SphereW->DistExp = Parse_Float () ;
				END_CASE

				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT
			EXIT
		END_CASE

		CASE(PLANAR_TOKEN)
			New = Create_Warp(PLANAR_WARP);
			PlanarW = (PLANARW *) New ;
			if(Allow_Vector(Local_Vector))
			{
					VNormalizeEq(Local_Vector);
					Assign_Vector(PlanarW->Orientation_Vector,Local_Vector);
					Parse_Comma();
					PlanarW->OffSet=Parse_Float();
			}
			EXIT
		END_CASE

		CASE(TOROIDAL_TOKEN)
			New = Create_Warp(TOROIDAL_WARP);
			Toroidal = (TOROIDAL *) New ;
			EXPECT
				CASE(ORIENTATION_TOKEN)
					Parse_Vector (Local_Vector) ;
					VNormalizeEq(Local_Vector);
					Assign_Vector (Toroidal->Orientation_Vector, Local_Vector) ;
				END_CASE

				CASE(DIST_EXP_TOKEN)
					Toroidal->DistExp = Parse_Float () ;
				END_CASE

				CASE(MAJOR_RADIUS_TOKEN)
					Toroidal->MajorRadius = Parse_Float () ;
				END_CASE

				OTHERWISE
					UNGET
					EXIT
				END_CASE
			END_EXPECT
			EXIT
		END_CASE

		// JN2007: Cubic warp
		CASE(CUBIC_TOKEN)
			New = Create_Warp(CUBIC_WARP);
			EXIT
		END_CASE

		OTHERWISE
			Expectation_Error ("warp type");
		END_CASE
	END_EXPECT

	if (New==NULL)
	{
		Error("Empty warp statement.");
	}

	New->Next_Warp = *Warp_Ptr;
	if(*Warp_Ptr != NULL)
		(*Warp_Ptr)->Prev_Warp = New;
	*Warp_Ptr = New;

	Parse_End();
}


void Parser::Parse_Material(MATERIAL *Material)
{
	MATERIAL *Temp;
	TEXTURE *Texture;
	TEXTURE * Int_Texture;
	VECTOR Local_Vector;
	MATRIX Local_Matrix;
	TRANSFORM Local_Trans;

	Parse_Begin();

	EXPECT
		CASE(MATERIAL_ID_TOKEN)
			Temp = (MATERIAL *)Token.Data;
			Texture = Copy_Textures(Temp->Texture);
			Int_Texture = Copy_Textures(Temp->Interior_Texture);
			Link_Textures(&(Material->Texture),Texture);
			Link_Textures(&(Material->Interior_Texture),Int_Texture);
			Destroy_Interior(Material->interior);
			if (Temp->interior != NULL)
				Material->interior = new Interior(*(Temp->interior));
			else
				Material->interior = NULL;
			EXIT
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	EXPECT
		CASE (TEXTURE_TOKEN)
			Parse_Begin ();
			Texture = Parse_Texture ();
			Parse_End ();
			Link_Textures(&(Material->Texture),Texture);
		END_CASE

		CASE (INTERIOR_TEXTURE_TOKEN)
			Parse_Begin ();
			Int_Texture = Parse_Texture ();
			Parse_End ();
			Link_Textures(&(Material->Interior_Texture), Int_Texture);
		END_CASE

		CASE (INTERIOR_TOKEN)
			Parse_Interior((Interior **)(&(Material->interior)));
		END_CASE

		CASE (TRANSLATE_TOKEN)
			Parse_Vector (Local_Vector);
			Compute_Translation_Transform(&Local_Trans, Local_Vector);
			Transform_Textures (Material->Texture, &Local_Trans);
			if(Material->interior!= NULL)
				Material->interior->Transform(&Local_Trans);
		END_CASE

		CASE (ROTATE_TOKEN)
			Parse_Vector (Local_Vector);
			Compute_Rotation_Transform(&Local_Trans, Local_Vector);
			Transform_Textures (Material->Texture, &Local_Trans);
			if(Material->interior!= NULL)
				Material->interior->Transform(&Local_Trans);
		END_CASE

		CASE (SCALE_TOKEN)
			Parse_Scale_Vector (Local_Vector);
			Compute_Scaling_Transform(&Local_Trans, Local_Vector);
			Transform_Textures (Material->Texture, &Local_Trans);
			if(Material->interior!= NULL)
				Material->interior->Transform(&Local_Trans);
		END_CASE

		CASE (MATRIX_TOKEN)
			Parse_Matrix(Local_Matrix);
			Compute_Matrix_Transform(&Local_Trans, Local_Matrix);
			Transform_Textures (Material->Texture, &Local_Trans);
			if(Material->interior!= NULL)
				Material->interior->Transform(&Local_Trans);
		END_CASE

		CASE (TRANSFORM_TOKEN)
			Parse_Transform(&Local_Trans);
			Transform_Textures (Material->Texture, &Local_Trans);
			if(Material->interior!= NULL)
				Material->interior->Transform(&Local_Trans);
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	Parse_End();
}


/*****************************************************************************
 *
 * FUNCTION
 *
 *   Parse_PatternFunction
 *
 * INPUT
 *
 * OUTPUT
 *
 * RETURNS
 *
 * AUTHOR
 *
 * DESCRIPTION
 *
 *   -
 *
 * CHANGES
 *   
 *   -
 *
 ******************************************************************************/

void Parser::Parse_PatternFunction(TPATTERN *New)
{
	VECTOR Local_Vector;
	MATRIX Local_Matrix;
	TRANSFORM Local_Trans;
	TURB *Local_Turb;
	unsigned short Old_Type=New->Type;
	ImageData *Old_Image = NULL;
	DENSITY_FILE *Old_Density_File = NULL;
	int i;
	SceneThreadData *Thread = GetParserDataPtr();
	UCS2String ign;

	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(PIGMENT_PATTERN_TOKEN)
			Parse_Begin();
			New->Type = PIGMENT_PATTERN;
			New->Vals.Pigment = Create_Pigment();
			Parse_Pigment(&(New->Vals.Pigment));
			Post_Pigment(New->Vals.Pigment);
			Parse_End();
		END_CASE

		CASE (FUNCTION_TOKEN)
			New->Type = FUNCTION_PATTERN;
			New->Vals.Function.vm = sceneData->functionVM;
			New->Vals.Function.Data = sceneData->functionPatternCount;
			sceneData->functionPatternCount++;
			GetParserDataPtr()->functionPatternContext.resize(sceneData->functionPatternCount);
			New->Vals.Function.Fn = Parse_Function();
			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.Fractal.Iterations = (int)Parse_Float();
			New->Vals.Fractal.interior_type = DEFAULT_FRACTAL_INTERIOR_TYPE;
			New->Vals.Fractal.exterior_type = DEFAULT_FRACTAL_EXTERIOR_TYPE;
			New->Vals.Fractal.efactor = DEFAULT_FRACTAL_EXTERIOR_FACTOR;
			New->Vals.Fractal.ifactor = DEFAULT_FRACTAL_INTERIOR_FACTOR;
			EXIT
		END_CASE

		CASE (JULIA_TOKEN)
			New->Type = JULIA_PATTERN;
			Parse_UV_Vect(New->Vals.Fractal.Coord);
			Parse_Comma();
			New->Vals.Fractal.Iterations = (int)Parse_Float();
			New->Vals.Fractal.interior_type = DEFAULT_FRACTAL_INTERIOR_TYPE;
			New->Vals.Fractal.exterior_type = DEFAULT_FRACTAL_EXTERIOR_TYPE;
			New->Vals.Fractal.efactor = DEFAULT_FRACTAL_EXTERIOR_FACTOR;
			New->Vals.Fractal.ifactor = DEFAULT_FRACTAL_INTERIOR_FACTOR;
			EXIT
		END_CASE

		CASE (MAGNET_TOKEN)
			New->Type = NO_PATTERN;
			New->Vals.Fractal.interior_type = DEFAULT_FRACTAL_INTERIOR_TYPE;
			New->Vals.Fractal.exterior_type = DEFAULT_FRACTAL_EXTERIOR_TYPE;
			New->Vals.Fractal.efactor = DEFAULT_FRACTAL_EXTERIOR_FACTOR;
			New->Vals.Fractal.ifactor = DEFAULT_FRACTAL_INTERIOR_FACTOR;
			i = (int)Parse_Float();
			EXPECT
				CASE (MANDEL_TOKEN)
					switch(i)
					{
						case 1:
							New->Type = MAGNET1M_PATTERN;
							break;
						case 2:
							New->Type = MAGNET2M_PATTERN;
							break;
						default:
							Error("Invalid magnet-mandel pattern type found. Valid types are 1 and 2.");
							break;
					}
					EXIT
				END_CASE
				CASE (JULIA_TOKEN)
					switch(i)
					{
						case 1:
							New->Type = MAGNET1J_PATTERN;
							break;
						case 2:
							New->Type = MAGNET2J_PATTERN;
							break;
						default:
							Error("Invalid magnet-julia pattern type found. Valid types are 1 and 2.");
							break;
					}
					Parse_UV_Vect(New->Vals.Fractal.Coord);
					Parse_Comma();
					EXIT
				END_CASE
				OTHERWISE
					Error("Invalid magnet pattern found. Valid types are 'mandel' and 'julia'.");
				END_CASE
			END_EXPECT
			New->Vals.Fractal.Iterations = (int)Parse_Float();
			EXIT
		END_CASE

		CASE (ONION_TOKEN)
			New->Type = ONION_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);
			VNormalizeEq(New->Vals.Gradient);
			EXIT
		END_CASE

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

		CASE (CRACKLE_TOKEN)
			New->Type = CRACKLE_PATTERN;
			New->Vals.Crackle.IsSolid = 0;
			New->Vals.Crackle.Form[X] = -1;
			New->Vals.Crackle.Form[Y] = 1;
			New->Vals.Crackle.Form[Z] = 0;
			New->Vals.Crackle.Metric = 2;
			New->Vals.Crackle.Offset = 0;
			New->Vals.Crackle.Dim = 3;
			EXIT
		END_CASE

		CASE (CHECKER_TOKEN)
			New->Type = CHECKER_PATTERN;
			New->Frequency = 0.0;
		END_CASE

		CASE (OBJECT_TOKEN)
		{
			Parse_Begin();
			vector<ObjectPtr> tempObjects;
			Parse_Bound_Clip(tempObjects, false);
			if(tempObjects.size() != 1)
				Error ("object or object identifier expected.");
			New->Vals.Object = tempObjects[0];
			New->Type = OBJECT_PATTERN;
			New->Frequency = 0.0;
			Parse_End();
			EXIT
		}
		END_CASE

		CASE (CELLS_TOKEN)
			New->Type = CELLS_PATTERN;
			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-EPSILON*2.0;
				New->Type = BRICK_PATTERN;
			}
			New->Frequency = 0.0;
			EXIT
		END_CASE

		CASE (HEXAGON_TOKEN)
			New->Type = HEXAGON_PATTERN;
			New->Frequency = 0.0;
			EXIT
		END_CASE

		CASE (CUBIC_TOKEN) // JN2007: Cubic pattern
			New->Type = CUBIC_PATTERN;
			New->Frequency = 0.0;
			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 (FACETS_TOKEN)
			if (TPat_Type != NORMAL_TYPE)
			{
				Only_In("facets","normal");
			}
			New->Type = FACETS_PATTERN;
			New->Vals.Facets.Size = 0.1;
			New->Vals.Facets.UseCoords = 0;
			New->Vals.Facets.Metric = 2;
			EXIT
		END_CASE
*/
		CASE (IMAGE_PATTERN_TOKEN)
			New->Type = IMAGE_PATTERN;
			New->Frequency = 0.0;
			Parse_Image_Pattern (New);
			EXIT
		END_CASE

		CASE (PLANAR_TOKEN)
			New->Type = PLANAR_PATTERN;
			EXIT
		END_CASE

		CASE (BOXED_TOKEN)
			New->Type = BOXED_PATTERN;
			EXIT
		END_CASE

		CASE (SPHERICAL_TOKEN)
			New->Type = SPHERICAL_PATTERN;
			EXIT
		END_CASE

		CASE (CYLINDRICAL_TOKEN)
			New->Type = CYLINDRICAL_PATTERN;
			EXIT
		END_CASE

		CASE (DENSITY_FILE_TOKEN)
			New->Type = DENSITY_FILE_PATTERN;
			New->Vals.Density_File = Create_Density_File();
			GET(DF3_TOKEN);
			New->Vals.Density_File->Data->Name = Parse_C_String(true);
			{
				IStream *dfile = Locate_File(this, sceneData, ASCIItoUCS2String(New->Vals.Density_File->Data->Name).c_str(), POV_File_Data_DF3, ign, true);
				if(dfile == NULL)
					Error("Cannot read media density file.");
				Read_Density_File(dfile, New->Vals.Density_File);
			}
			EXIT
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT

	EXPECT
		CASE (SOLID_TOKEN)
			if (New->Type != CRACKLE_PATTERN )
				Only_In("solid", "crackle");
			New->Vals.Crackle.IsSolid = 1;
		END_CASE

		CASE (EXTERIOR_TOKEN)
			if(!((New->Type == MANDEL_PATTERN) || (New->Type == MANDEL3_PATTERN) ||
			     (New->Type == MANDEL4_PATTERN) || (New->Type == MANDELX_PATTERN) ||
			     (New->Type == JULIA_PATTERN) ||  (New->Type == JULIA3_PATTERN) ||
			     (New->Type == JULIA4_PATTERN) || (New->Type == JULIAX_PATTERN) ||
			     (New->Type == MAGNET1M_PATTERN) || (New->Type == MAGNET2M_PATTERN) ||
			     (New->Type == MAGNET1J_PATTERN) || (New->Type == MAGNET2J_PATTERN)))
			{
				Only_In("exterior", "mandel, julia or magnet");
			}
			New->Vals.Fractal.exterior_type = (int)Parse_Float();
			if((New->Vals.Fractal.exterior_type < 0) || (New->Vals.Fractal.exterior_type > 6))
				Error("Invalid fractal pattern exterior type. Valid types are 0 to 6.");
			Parse_Comma();
			New->Vals.Fractal.efactor = Parse_Float();
		END_CASE

		CASE (INTERIOR_TOKEN)
			if(!((New->Type == MANDEL_PATTERN) || (New->Type == MANDEL3_PATTERN) ||
			     (New->Type == MANDEL4_PATTERN) || (New->Type == MANDELX_PATTERN) ||
			     (New->Type == JULIA_PATTERN) || (New->Type == JULIA3_PATTERN) ||
			     (New->Type == JULIA4_PATTERN) || (New->Type == JULIAX_PATTERN) ||
			     (New->Type == MAGNET1M_PATTERN) || (New->Type == MAGNET2M_PATTERN) ||
			     (New->Type == MAGNET1J_PATTERN) || (New->Type == MAGNET2J_PATTERN)))
			{
				Only_In("exterior", "mandel, julia or magnet");
			}
			New->Vals.Fractal.interior_type = (int)Parse_Float();
			if((New->Vals.Fractal.interior_type < 0) || (New->Vals.Fractal.interior_type > 6))
				Error("Invalid fractal pattern interior type. Valid types are 0 to 6.");
			Parse_Comma();
			New->Vals.Fractal.ifactor = Parse_Float();
		END_CASE

		CASE (EXPONENT_TOKEN)
			if(!((New->Type == MANDEL_PATTERN) || (New->Type == MANDEL3_PATTERN) ||
			     (New->Type == MANDEL4_PATTERN) || (New->Type == MANDELX_PATTERN) ||
			     (New->Type == JULIA_PATTERN) || (New->Type == JULIA3_PATTERN) ||
			     (New->Type == JULIA4_PATTERN) || (New->Type == JULIAX_PATTERN)))
			{
				Only_In("exponent", "mandel or julia");
			}

			if((New->Type == JULIA_PATTERN) || (New->Type == JULIA3_PATTERN) ||
			   (New->Type == JULIA4_PATTERN) || (New->Type == JULIAX_PATTERN))
			{
				New->Vals.Fractal.Exponent = i = (int)Parse_Float();
				switch(i)
				{
					case 2:
						New->Type = JULIA_PATTERN;
						break;
					case 3:
						New->Type = JULIA3_PATTERN;
						break;
					case 4:
						New->Type = JULIA4_PATTERN;
						break;
					default:
						if((i > 4) && (i <= 33))
						{
							New->Type = JULIAX_PATTERN;
						}
						else
						{
							New->Type = JULIA_PATTERN;
							Warning(0, "Invalid julia pattern exponent found. Supported exponents are 2 to 33.\n"
							           "Using default exponent 2.");
						}
						break;
				}
			}
			else if((New->Type == MANDEL_PATTERN) || (New->Type == MANDEL3_PATTERN) ||
			        (New->Type == MANDEL4_PATTERN) || (New->Type == MANDELX_PATTERN))
			{
				New->Vals.Fractal.Exponent = i = (int)Parse_Float();
				switch(i)
				{
					case 2:
						New->Type = MANDEL_PATTERN;
						break;
					case 3:
						New->Type = MANDEL3_PATTERN;
						break;
					case 4:
						New->Type = MANDEL4_PATTERN;
						break;
					default:
						if((i > 4) && (i <= 33))
						{
							New->Type = MANDELX_PATTERN;
						}
						else
						{
							New->Type = MANDEL_PATTERN;
							Warning(0, "Invalid mandel pattern exponent found. Supported exponents are 2 to 33.\n"
							           "Using default exponent 2.");
						}
						break;
				}
			}
		END_CASE

		CASE (COORDS_TOKEN)
			if (New->Type != FACETS_PATTERN )
				Only_In("coords", "facets");
			New->Vals.Facets.UseCoords = Parse_Float();
		END_CASE

		CASE (SIZE_TOKEN)
			if (New->Type != FACETS_PATTERN )
				Only_In("size", "facets");
			New->Vals.Facets.Size = Parse_Float();
		END_CASE

		CASE (METRIC_TOKEN)
			if (New->Type == FACETS_PATTERN )
			{
				Parse_Vector(Local_Vector);
				New->Vals.Facets.Metric = Local_Vector[X];
			}
			else if ( New->Type == CRACKLE_PATTERN )
			{
				Parse_Vector(Local_Vector);
				New->Vals.Crackle.Metric = Local_Vector[X];
			}
			else
				Only_In("metric", "facets or crackle");
		END_CASE

		CASE (FORM_TOKEN)
			if (New->Type != CRACKLE_PATTERN )
				Only_In("form", "crackle");
			Parse_Vector( New->Vals.Crackle.Form );
		END_CASE

		CASE (OFFSET_TOKEN)
			if (New->Type != CRACKLE_PATTERN )
				Only_In("offset", "crackle");
			New->Vals.Crackle.Offset = Parse_Float();
		END_CASE

		CASE (TURBULENCE_TOKEN)
			Local_Turb=Check_Turb(&(New->Warps));
			Parse_Vector(Local_Turb->Turbulence);
		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 (CONTROL0_TOKEN)
			if (New->Type != QUILTED_PATTERN)
				Not_With ("control0","this pattern");
			New->Vals.Quilted.Control0 = Parse_Float ();
		END_CASE

		CASE (CONTROL1_TOKEN)
			if (New->Type != QUILTED_PATTERN)
				Not_With ("control1","this pattern");
			New->Vals.Quilted.Control1 = Parse_Float ();
		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 (CUBIC_WAVE_TOKEN)
			New->Wave_Type = CUBIC_WAVE;
		END_CASE

		CASE (POLY_WAVE_TOKEN)
			New->Wave_Type = POLY_WAVE;
			New->Exponent  = Allow_Float(New->Exponent);
		END_CASE

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

		CASE (NOISE_GENERATOR_TOKEN)
		{
			int noise_generator;
			noise_generator = (int) Parse_Float();
			if (noise_generator < 0 || noise_generator > 3)
				Error ("Value for noise_generator must be 0, 1, 2, or 3.");
			New->Flags |= noise_generator * NOISE_FLAG_1;
		}
		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()-EPSILON*2.0;
		END_CASE

		CASE (INTERPOLATE_TOKEN)
			if (New->Type != DENSITY_FILE_PATTERN)
				Not_With ("interpolate","non-density_file");
			New->Vals.Density_File->Interpolation = (int)Parse_Float();
		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)
			Transform_Tpattern (New, Parse_Transform(&Local_Trans));
		END_CASE

		CASE (NO_BUMP_SCALE_TOKEN)
			Set_Flag(New,DONT_SCALE_BUMPS_FLAG);
		END_CASE

		OTHERWISE
			UNGET
			EXIT
		END_CASE
	END_EXPECT
}

}
