/*
 *	filterCommon.cpp - Helper routines for filter classes
 *	Copyright (C) 2018, D Haley 

 *	This program is free software: you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation, either version 3 of the License, or
 *	(at your option) any later version.

 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.

 *	You should have received a copy of the GNU General Public License
 *	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "../filter.h"
#include "filterCommon.h"

#include "common/colourmap.h"
#include "common/stringFuncs.h"
#include "wx/wxcommon.h"


enum
{
	//free-slice parameters	
	KEY_VOXAPPEAR_VOXEL_SLICE_OFFSET3D,
	KEY_VOXAPPEAR_VOXEL_SLICE_NORMAL3D,
	
	KEY_VOXAPPEAR_VOXEL_TRANSFER_FUNCTION,

	KEY_VOXAPPEAR_SPOTSIZE,
	KEY_VOXAPPEAR_TRANSPARENCY,
	KEY_VOXAPPEAR_COLOUR,
	KEY_VOXAPPEAR_ISOLEVEL,
	KEY_VOXAPPEAR_VOXEL_REPRESENTATION_MODE,

	KEY_VOXAPPEAR_VOXEL_BUILTIN_COLOURMAP,
	KEY_VOXAPPEAR_VOXEL_SLICE_COLOURAUTO,
	KEY_VOXAPPEAR_MAPEND,
	KEY_VOXAPPEAR_MAPSTART,
	KEY_VOXAPPEAR_SHOW_COLOURBAR,
	KEY_VOXAPPEAR_VOXEL_BUILTIN_COLOURMAP_NAME,
	KEY_VOXAPPEAR_VOXEL_SLICE_AXIS,
	KEY_VOXAPPEAR_VOXEL_SLICE_OFFSET,
	KEY_VOXAPPEAR_VOXEL_SLICE_INTERP,
	KEY_VOXAPPEAR_SHOW_BOUNDINGBOX,
	KEY_VOXAPPEAR_BOUNDINGBOX_COLOUR,
};

const char *REPRESENTATION_TYPE_STRING[] = {
		NTRANS("Point Cloud"),
		NTRANS("Isosurface"),
		NTRANS("Axial slice"),
		NTRANS("Slice"),
#ifdef ENABLE_LIBVD
		NTRANS("Volume Render"),
#endif
	};


const char *VOXELISE_SLICE_INTERP_STRING[]={
	NTRANS("None"),
	NTRANS("Linear")
	};


using std::ostream;
using std::vector;
using std::endl;
using std::string;

void writeVectorsXML(ostream &f,const char *containerName,
		const vector<Point3D> &vectorParams, unsigned int depth)
{
	f << tabs(depth) << "<" << containerName << ">" << endl;
	for(unsigned int ui=0; ui<vectorParams.size(); ui++)
	{
		f << tabs(depth+1) << "<point3d x=\"" << vectorParams[ui][0] << 
			"\" y=\"" << vectorParams[ui][1] << "\" z=\"" << vectorParams[ui][2] << "\"/>" << endl;
	}
	f << tabs(depth) << "</" << containerName << ">" << endl;
}

void writeIonsEnabledXML(ostream &f, const char *containerName, 
		const vector<bool> &enabledState, const vector<string> &names, 
			unsigned int depth)
{
	if(enabledState.size()!=names.size())
		return;

	f << tabs(depth) << "<" << containerName << ">"  << endl;
	for(size_t ui=0;ui<enabledState.size();ui++)
	{
		f<< tabs(depth+1) << "<ion enabled=\"" << (int)enabledState[ui] 
			<< "\" name=\"" << names[ui] << "\"/>" <<  std::endl; 
	}
	f << tabs(depth) << "</" << containerName << ">"  << endl;
}

void readIonsEnabledXML(xmlNodePtr nodePtr,  vector<bool> &enabledStatus,vector<string> &ionNames)
{
	//skip conatainer name
	nodePtr=nodePtr->xmlChildrenNode;

	if(!nodePtr)
		return;

	enabledStatus.clear();
	while(!XMLHelpFwdToElem(nodePtr,"ion"))
	{
		int enabled;
		if(!XMLGetAttrib(nodePtr,enabled,"enabled"))
			return ;

		std::string tmpName;
		if(!XMLGetAttrib(nodePtr,tmpName,"name"))
			return;
	
		enabledStatus.push_back(enabled);
		
		ionNames.push_back(tmpName);
	}
	
}
bool readVectorsXML(xmlNodePtr nodePtr,	std::vector<Point3D> &vectorParams) 
{
	nodePtr=nodePtr->xmlChildrenNode;
	vectorParams.clear();
	
	while(!XMLHelpFwdToElem(nodePtr,"point3d"))
	{
		std::string tmpStr;
		xmlChar* xmlString;
		float x,y,z;
		//--Get X value--
		xmlString=xmlGetProp(nodePtr,(const xmlChar *)"x");
		if(!xmlString)
			return false;
		tmpStr=(char *)xmlString;
		xmlFree(xmlString);

		//Check it is streamable
		if(stream_cast(x,tmpStr))
			return false;

		//--Get Z value--
		xmlString=xmlGetProp(nodePtr,(const xmlChar *)"y");
		if(!xmlString)
			return false;
		tmpStr=(char *)xmlString;
		xmlFree(xmlString);

		//Check it is streamable
		if(stream_cast(y,tmpStr))
			return false;

		//--Get Y value--
		xmlString=xmlGetProp(nodePtr,(const xmlChar *)"z");
		if(!xmlString)
			return false;
		tmpStr=(char *)xmlString;
		xmlFree(xmlString);

		//Check it is streamable
		if(stream_cast(z,tmpStr))
			return false;

		vectorParams.emplace_back(Point3D(x,y,z));
	}

	return true;
}

bool parseXMLColour(xmlNodePtr &nodePtr, ColourRGBAf &rgba)
{
	xmlChar *xmlString;

	float r,g,b,a;
	std::string tmpStr;
	//--red--
	xmlString=xmlGetProp(nodePtr,(const xmlChar *)"r");
	if(!xmlString)
		return false;
	tmpStr=(char *)xmlString;

	//convert from string to digit
	if(stream_cast(r,tmpStr))
		return false;

	//disallow negative or values gt 1.
	if(r < 0.0f || r > 1.0f)
		return false;
	xmlFree(xmlString);


	//--green--
	xmlString=xmlGetProp(nodePtr,(const xmlChar *)"g");
	if(!xmlString)
		return false;
	tmpStr=(char *)xmlString;

	//convert from string to digit
	if(stream_cast(g,tmpStr))
	{
		xmlFree(xmlString);
		return false;
	}
	
	xmlFree(xmlString);

	//disallow negative or values gt 1.
	if(g < 0.0f || g > 1.0f)
		return false;

	//--blue--
	xmlString=xmlGetProp(nodePtr,(const xmlChar *)"b");
	if(!xmlString)
		return false;
	tmpStr=(char *)xmlString;

	//convert from string to digit
	if(stream_cast(b,tmpStr))
	{
		xmlFree(xmlString);
		return false;
	}
	xmlFree(xmlString);
	
	//disallow negative or values gt 1.
	if(b < 0.0f || b > 1.0f)
		return false;

	//--Alpha--
	xmlString=xmlGetProp(nodePtr,(const xmlChar *)"a");
	if(!xmlString)
		return false;
	tmpStr=(char *)xmlString;

	//convert from string to digit
	if(stream_cast(a,tmpStr))
	{
		xmlFree(xmlString);
		return false;
	}
	xmlFree(xmlString);

	//disallow negative or values gt 1.
	if(a < 0.0f || a > 1.0f)
		return false;
	rgba.r(r);
	rgba.g(g);
	rgba.b(b);
	rgba.a(a);

	return true;
}

const RangeFile *getRangeFile(const std::vector<const FilterStreamData*> &dataIn)
{
	for(size_t ui=0;ui<dataIn.size();ui++)
	{
		if(dataIn[ui]->getStreamType() == STREAM_TYPE_RANGE)
			return ((const RangeStreamData*)(dataIn[ui]))->rangeFile;
	}

	ASSERT(false);
}

size_t getTotalSizeByType(const std::vector<const FilterStreamData*> &dataIn, unsigned int streamTypeMask)
{
	size_t totalSize=0;
	for (size_t i = 0; i < dataIn.size(); i++)
	{
		if(dataIn[i]->getStreamType() & streamTypeMask)
		    totalSize+=dataIn[i]->getNumBasicObjects();
	}
	return totalSize;
}



DrawColourBarOverlay *makeColourBar(float minV, float maxV,size_t nColours,size_t colourMap, bool reverseMap, float alpha) 
{
	//Set up the colour bar. Place it in a draw stream type
	DrawColourBarOverlay *dc = new DrawColourBarOverlay;

	vector<float> r,g,b;
	r.resize(nColours);
	g.resize(nColours);
	b.resize(nColours);

	for (unsigned int ui=0;ui<nColours;ui++)
	{
		unsigned char rgb[3]; //RGB array
		float value;
		value = (float)(ui)*(maxV-minV)/(float)nColours + minV;
		//Pick the desired colour map
		colourMapWrap(colourMap,rgb,value,minV,maxV,reverseMap);
		r[ui]=rgb[0]/255.0f;
		g[ui]=rgb[1]/255.0f;
		b[ui]=rgb[2]/255.0f;
	}

	dc->setColourVec(r,g,b);

	dc->setSize(0.08,0.6);
	dc->setPosition(0.1,0.1);
	dc->setMinMax(minV,maxV);
	dc->setAlpha(alpha);

	return dc;
}



std::string transferFunctionString(const std::vector<pair<float,ColourRGBA> >  &colWithOffset)
{
	vector<pair<float,ColourRGBA> > tmp;
	tmp=colWithOffset;
	//Ensure the transfer function x-value is monotonically increasing
	ComparePairFirst cmpF;
	std::sort(tmp.begin(),tmp.end(),cmpF);

	std::string res,tmpS;
	for(unsigned int ui=0; ui<tmp.size();ui++)
	{
		stream_cast(tmpS,tmp[ui].first);
		res+=tmpS+ ",";
		for(unsigned int uj=0;uj<4;uj++)
		{
			stream_cast(tmpS,tmp[ui].second[uj]);
			res+=tmpS;

			if(uj !=3)
				res+=",";
		}
	
		if(ui != colWithOffset.size()-1)
			res+="|";
	}

	return res;
}

bool fromTransferFunctionString(const std::string &s , std::vector<pair<float,ColourRGBA> > &colWithOffset)
{
	vector<string> components;
	splitStrsRef(s.c_str(),'|',components);

	if(!components.size())
		return false;

	pair<float,ColourRGBA> colPair;
	for(unsigned int ui=0;ui<components.size();ui++)
	{
		vector<string> thisCompStrVec;
		splitStrsRef(components[ui].c_str(),',',thisCompStrVec);

		//Should be 5 vector
		if(thisCompStrVec.size() %5)
			return false;

		for(unsigned int uj=0;uj<thisCompStrVec.size(); uj++)
		{
			if(uj == 0)
			{
				if(stream_cast(colPair.first,thisCompStrVec[uj]))
					return false;
			}
			else
			{
				unsigned int u;
				if(stream_cast(u,thisCompStrVec[uj]))
					return false;
				colPair.second[uj-1] = u;
			}

		}
		colWithOffset.push_back(colPair);

	}

	return true;
}

std::string VoxelAppearanceProperties::getRepresentTypeString(int type) {
	ASSERT(type<VOXEL_REPRESENT_END);
	return  std::string(TRANS(REPRESENTATION_TYPE_STRING[type]));
}

VoxelAppearanceProperties::VoxelAppearanceProperties(Filter *p)
{
	COMPILE_ASSERT(THREEDEP_ARRAYSIZE(REPRESENTATION_TYPE_STRING) == VOXEL_REPRESENT_END);
	parent=p;
	
	splatSize=1.0f;
	rgba=ColourRGBAf(0.5,0.5,0.5,0.9f);
	isoLevel=0.5;

	representation=VOXEL_REPRESENT_POINTCLOUD;

	builtinColourmap=true;
	colourMap=COLOURMAP_VIRIDIS;
	autoColourMap=true;
	colourMapBounds[0]=0;
	colourMapBounds[1]=1;

	sliceInterpolate=VOX_INTERP_NONE;
	sliceAxis=0;
	sliceOffset=0.5;

	freeSlicePlane=Point3D(0.5,0.5,0.5);
	freeSliceNormal=Point3D(1,1,1);
	freeSliceNormal.normalise();

	showColourBar=false;
	showBoundBox=false;

	boundColour=ColourRGBAf(0,0,1);
	nColours=256;	
}

void VoxelAppearanceProperties::operator=(const VoxelAppearanceProperties &oth)
{
	sliceInterpolate=oth.sliceInterpolate;
	sliceAxis=oth.sliceAxis;
	sliceOffset=oth.sliceOffset;
	freeSlicePlane=oth.freeSlicePlane;
	freeSliceNormal=oth.freeSliceNormal;

	rgba=oth.rgba;

	splatSize=oth.splatSize;

	isoLevel=oth.isoLevel;

	transferFunction=oth.transferFunction;
	
	representation=oth.representation;
	
	builtinColourmap=oth.builtinColourmap;
	colourMap=oth.colourMap;
	nColours=oth.nColours;
	showColourBar=oth.showColourBar;
	autoColourMap=oth.autoColourMap;
	colourMapBounds[0]=oth.colourMapBounds[0];
	colourMapBounds[1]=oth.colourMapBounds[1];
	
	showBoundBox=oth.showBoundBox;
	boundColour=oth.boundColour;

	//Do not copy parent, as we may be deleting the other filter!
}

void VoxelAppearanceProperties::getProperties(FilterPropGroup &propertyList, size_t &curGroup, unsigned int keyOffset) const 
{
	FilterProperty p;

	//start a new group for the visual representation
	//----------------------------
	vector<pair<unsigned int, string> > choices;
	string tmpStr;

	choices.clear();
	for(unsigned int ui=0;ui<VOXEL_REPRESENT_END;ui++)
	{
		tmpStr=getRepresentTypeString(ui);
		choices.emplace_back((unsigned int)ui,tmpStr);
	}

	FilterProperty pRepresentation(keyOffset+KEY_VOXAPPEAR_VOXEL_REPRESENTATION_MODE, TRANS("Representation"),
		 choices,representation,TRANS("3D display method"));
	propertyList.addProperty(pRepresentation,curGroup);
	

	switch(representation)
	{
		case VOXEL_REPRESENT_POINTCLOUD:
		{
			FilterProperty pSplatSize(keyOffset+KEY_VOXAPPEAR_SPOTSIZE, TRANS("Spot size"),
				 splatSize,TRANS("Size of the spots to use for display"));
			propertyList.addProperty(pSplatSize,curGroup);


			FilterProperty pTransparency(keyOffset+KEY_VOXAPPEAR_TRANSPARENCY, TRANS("Transparency"),
				 (float)(1.0-rgba.a()),TRANS("How \"see through\" each point is (0 - opaque, 1 - invisible)"));
			propertyList.addProperty(pTransparency,curGroup);

			FilterProperty pShowBar(keyOffset+KEY_VOXAPPEAR_SHOW_COLOURBAR, TRANS("Show Bar"),
				 showColourBar,TRANS("Display colour bar in 3D view?"));
			propertyList.addProperty(pShowBar,curGroup);
			
			FilterProperty pAutoBounds(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_COLOURAUTO, TRANS("Auto Scale"),
				 autoColourMap,TRANS("Auto-compute min/max values (colour limits)")); 
			propertyList.addProperty(pAutoBounds,curGroup);

			if(!autoColourMap)
			{
				FilterProperty pMapStart(keyOffset+KEY_VOXAPPEAR_MAPSTART, TRANS("Map start"),
					 colourMapBounds[0],TRANS("Assign points with this value to the first colour in map")); 
				propertyList.addProperty(pMapStart,curGroup);
				
				FilterProperty pMapEnd(keyOffset+KEY_VOXAPPEAR_MAPEND, TRANS("Map end"),
					 colourMapBounds[1],TRANS("Assign points with this value to the last colour in map")); 
				propertyList.addProperty(pMapEnd,curGroup);
			}
			
			break;
		}
		case VOXEL_REPRESENT_ISOSURF:
		{
			//-- Isosurface parameters --
			propertyList.setGroupTitle(curGroup,TRANS("Surf. param."));

			FilterProperty pIsoLevel(keyOffset+KEY_VOXAPPEAR_ISOLEVEL, TRANS("Isovalue"),
				 isoLevel,TRANS("Scalar value to show as isosurface")); 
			propertyList.addProperty(pIsoLevel,curGroup);
		
			//-- 
			propertyList.setGroupTitle(curGroup,TRANS("Surface"));	
			curGroup++;

			//-- Isosurface appearance --
			FilterProperty pColour(keyOffset+KEY_VOXAPPEAR_COLOUR, TRANS("Colour"),
				 rgba,TRANS("Colour of isosurface")); 
			propertyList.addProperty(pColour,curGroup);
			p.name=TRANS("Colour");

			FilterProperty pTransparency(keyOffset+KEY_VOXAPPEAR_TRANSPARENCY, TRANS("Transparency"),
				 (float)(1.0-rgba.a()),TRANS("How \"see through\" each facet is (0 - opaque, 1 - invisible)"));
			propertyList.addProperty(pTransparency,curGroup);
			
			//----
			
			break;
		}
		case VOXEL_REPRESENT_AXIAL_SLICE:
		{
			//-- Slice parameters --
			propertyList.setGroupTitle(curGroup,TRANS("Slice param."));

			vector<pair<unsigned int, string> > choices;
			
			
			choices.emplace_back(0,"x");	
			choices.emplace_back(1,"y");	
			choices.emplace_back(2,"z");	
			p.name=TRANS("Slice Axis");
			FilterProperty pSliceAxis(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_AXIS, TRANS("Slice Axis"),
				 choices,sliceAxis,TRANS("Normal for the planar slice")); 
			propertyList.addProperty(pSliceAxis,curGroup);
			choices.clear();
			

			FilterProperty pSliceOffset(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_OFFSET, TRANS("Slice Coord"),
				 sliceOffset,TRANS("Fractional coordinate that slice plane passes through"));
			propertyList.addProperty(pSliceOffset,curGroup);
		
			
			for(unsigned int ui=0;ui<VOX_INTERP_ENUM_END;ui++)
			{
				choices.emplace_back(ui,
					TRANS(VOXELISE_SLICE_INTERP_STRING[ui]));
			}
			FilterProperty pSliceInterp(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_INTERP, TRANS("Interp Mode"),
				 choices,sliceInterpolate,TRANS("Interpolation mode for direction normal to slice"));
			propertyList.addProperty(pSliceInterp,curGroup);
			choices.clear();
			// ---	
			propertyList.setGroupTitle(curGroup,TRANS("Surface"));	
			curGroup++;

			

			//-- Slice visualisation parameters --
			for(unsigned int ui=0;ui<COLOURMAP_ENUM_END; ui++)
				choices.emplace_back(ui,getColourMapName(ui));

			FilterProperty pSliceColour(keyOffset+KEY_VOXAPPEAR_VOXEL_BUILTIN_COLOURMAP_NAME, TRANS("Colour Mode"),
				 choices,colourMap,TRANS("Colour scheme used to assign points colours by value"));
			propertyList.addProperty(pSliceColour,curGroup);
			
			FilterProperty pTransparency(keyOffset+KEY_VOXAPPEAR_TRANSPARENCY, TRANS("Transparency"),
				 (float)(1.0-rgba.a()),TRANS("How \"see through\" each facet is (0 - opaque, 1 - invisible)"));
			propertyList.addProperty(pTransparency,curGroup);

			FilterProperty pShowBar(keyOffset+KEY_VOXAPPEAR_SHOW_COLOURBAR, TRANS("Show Bar"),
				 showColourBar,TRANS("Display colour bar in 3D view?"));
			propertyList.addProperty(pShowBar,curGroup);
			
			FilterProperty pAutoBounds(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_COLOURAUTO, TRANS("Auto Bounds"),
				 autoColourMap,TRANS("Auto-compute min/max values (colour limits)")); 
			propertyList.addProperty(pAutoBounds,curGroup);

			if(!autoColourMap)
			{
				FilterProperty pMapStart(keyOffset+KEY_VOXAPPEAR_MAPSTART, TRANS("Map start"),
					 colourMapBounds[0],TRANS("Assign points with this value to the first colour in map")); 
				propertyList.addProperty(pMapStart,curGroup);
				
				FilterProperty pMapEnd(keyOffset+KEY_VOXAPPEAR_MAPEND, TRANS("Map end"),
					 colourMapBounds[1],TRANS("Assign points with this value to the last colour in map")); 
				propertyList.addProperty(pMapEnd,curGroup);
			}
			// ---	

			break;
		}
		case VOXEL_REPRESENT_SLICE:
		{
			//-- Slice parameters --
			propertyList.setGroupTitle(curGroup,TRANS("Slice param."));

			FilterProperty pSliceOffset(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_OFFSET3D, TRANS("Slice Origin"),
				 freeSlicePlane,TRANS("Point on plane for slice, in 0->1 coordinates"));
			propertyList.addProperty(pSliceOffset,curGroup);

			FilterProperty pSliceNormal(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_NORMAL3D, TRANS("Slice Normal"),
				 freeSliceNormal,TRANS("Normal vector for slice plane"));
			propertyList.addProperty(pSliceNormal,curGroup);
			
			// ---	
			propertyList.setGroupTitle(curGroup,TRANS("Surface"));	
			curGroup++;

			

			//-- Slice visualisation parameters --
			choices.clear();
			for(unsigned int ui=0;ui<COLOURMAP_ENUM_END; ui++)
				choices.emplace_back(ui,getColourMapName(ui));
			
			FilterProperty pSliceColour(keyOffset+KEY_VOXAPPEAR_VOXEL_BUILTIN_COLOURMAP_NAME, TRANS("Colour Mode"),
				 choices,colourMap,TRANS("Colour scheme used to assign points colours by value"));
			propertyList.addProperty(pSliceColour,curGroup);
			
			FilterProperty pTransparency(keyOffset+KEY_VOXAPPEAR_TRANSPARENCY, TRANS("Transparency"),
				 (float)(1.0-rgba.a()),TRANS("How \"see through\" each facet is (0 - opaque, 1 - invisible)"));
			propertyList.addProperty(pTransparency,curGroup);

			FilterProperty pShowBar(keyOffset+KEY_VOXAPPEAR_SHOW_COLOURBAR, TRANS("Show Bar"),
				 showColourBar,TRANS("Display colour bar in 3D view?"));
			propertyList.addProperty(pShowBar,curGroup);
			
			FilterProperty pAutoBounds(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_COLOURAUTO, TRANS("Auto Bounds"),
				 autoColourMap,TRANS("Auto-compute min/max values (colour limits)")); 
			propertyList.addProperty(pAutoBounds,curGroup);

			if(!autoColourMap)
			{
				FilterProperty pMapStart(keyOffset+KEY_VOXAPPEAR_MAPSTART, TRANS("Map start"),
					 colourMapBounds[0],TRANS("Assign points with this value to the first colour in map")); 
				propertyList.addProperty(pMapStart,curGroup);
				
				FilterProperty pMapEnd(keyOffset+KEY_VOXAPPEAR_MAPEND, TRANS("Map end"),
					 colourMapBounds[1],TRANS("Assign points with this value to the last colour in map")); 
				propertyList.addProperty(pMapEnd,curGroup);
			}

			p.name=TRANS("Colour mode");
			// ---	


			break;
		}
#ifdef ENABLE_LIBVD
		case VOXEL_REPRESENT_VOLUME_RENDER:
		{
		
			FilterProperty pBuiltinMap(keyOffset+KEY_VOXAPPEAR_VOXEL_BUILTIN_COLOURMAP, TRANS("Built-in map"),
				 builtinColourmap,TRANS("True if you want to use a built-in colour map, false to specify your own")); 
			propertyList.addProperty(pBuiltinMap,curGroup);


			//Switch between built-in maps and transfer editor
			choices.clear();
			if(builtinColourmap)
			{
			
				//-- volume visualisation parameters --
				for(unsigned int ui=0;ui<COLOURMAP_ENUM_END; ui++)
					choices.emplace_back(ui,getColourMapName(ui));

				FilterProperty pColourMap(keyOffset+KEY_VOXAPPEAR_VOXEL_BUILTIN_COLOURMAP_NAME, TRANS("Colour Mode"),
					 choices,colourMap,TRANS("Colour scheme used to assign colours and transparency by value"));
				propertyList.addProperty(pColourMap,curGroup);
			
				FilterProperty pTransparency(keyOffset+KEY_VOXAPPEAR_TRANSPARENCY, TRANS("Transparency"),
				 (float)(1.0-rgba.a()),TRANS("How \"see through\" each facet is (0 - opaque, 1 - invisible)"));
				propertyList.addProperty(pTransparency,curGroup);
			}
			else
			{
				FilterProperty pTransferFunc(keyOffset+KEY_VOXAPPEAR_VOXEL_TRANSFER_FUNCTION, PROPERTY_TYPE_TRANSFER_FUNCTION,
					TRANS("Transfer Function"), transferFunctionString(transferFunction),
						TRANS("Colour scheme used to assign colours and transparency by value"));
				propertyList.addProperty(pTransferFunc,curGroup);
			}


			FilterProperty pShowBar(keyOffset+KEY_VOXAPPEAR_SHOW_COLOURBAR, TRANS("Show Bar"),
				 showColourBar,TRANS("Display colour bar in 3D view?"));
			propertyList.addProperty(pShowBar,curGroup);
			
			FilterProperty pAutoBounds(keyOffset+KEY_VOXAPPEAR_VOXEL_SLICE_COLOURAUTO, TRANS("Auto Bounds"),
				 autoColourMap,TRANS("Auto-compute min/max values (colour limits)")); 
			propertyList.addProperty(pAutoBounds,curGroup);

			if(!autoColourMap)
			{
				FilterProperty pMapStart(keyOffset+KEY_VOXAPPEAR_MAPSTART, TRANS("Map start"),
					 colourMapBounds[0],TRANS("Assign points with this value to the first colour in map")); 
				propertyList.addProperty(pMapStart,curGroup);
				
				FilterProperty pMapEnd(keyOffset+KEY_VOXAPPEAR_MAPEND, TRANS("Map end"),
					 colourMapBounds[1],TRANS("Assign points with this value to the last colour in map")); 
				propertyList.addProperty(pMapEnd,curGroup);
			}

			// ---	
			break;
		}
#endif
		default:
			ASSERT(false);
			;
	}

	FilterProperty pShowBounds(keyOffset+KEY_VOXAPPEAR_SHOW_BOUNDINGBOX,TRANS("Show Bounds"), 
			showBoundBox, TRANS("Display the voxel's bounding box"));
	propertyList.addProperty(pShowBounds,curGroup);

	if(showBoundBox)
	{
		FilterProperty pBoundColour(keyOffset+KEY_VOXAPPEAR_BOUNDINGBOX_COLOUR,TRANS("Bound Colour"), 
				boundColour, TRANS("Set Voxel's bounding box colour"));
		propertyList.addProperty(pBoundColour,curGroup);
	}

	propertyList.setGroupTitle(curGroup,TRANS("Appearance"));	
	curGroup++;
}


bool VoxelAppearanceProperties::setProperty(unsigned int keyOffset, unsigned int key,
						bool &handled, const string &value, bool &cacheOK,
						bool &needUpdate,vector<FilterStreamData *> &filterOutputs)
{
	//Assume, unless otherwise contradicted that we handled the key.
	handled=true;
	switch(key-keyOffset)
	{
		case KEY_VOXAPPEAR_SPOTSIZE:
		{
			float f;
			if(stream_cast(f,value))
				return false;
			if(f <= 0.0f)
				return false;
			if(f !=splatSize)
			{
				splatSize=f;
				needUpdate=true;

				//Go in and manually adjust the cached
				//entries to have the new value, rather
				//than doing a full recomputation
				if(cacheOK)
				{
					for(unsigned int ui=0;ui<filterOutputs.size();ui++)
					{
						if(filterOutputs[ui]->getStreamType() == STREAM_TYPE_VOXEL)
						{
							VoxelStreamData *d;
							d=(VoxelStreamData*)filterOutputs[ui];
							d->splatSize=splatSize;
						}
					}
				}

			}
			break;
		}
		case KEY_VOXAPPEAR_TRANSPARENCY:
		{
			float f;
			if(stream_cast(f,value))
				return false;
			if(f < 0.0f || f > 1.0)
				return false;
			needUpdate=true;
			//Alpha is opacity, which is 1-transparancy
			rgba.a(1.0f-f);
			//Go in and manually adjust the cached
			//entries to have the new value, rather
			//than doing a full recomputation
			if(cacheOK)
			{
				for(unsigned int ui=0;ui<filterOutputs.size();ui++)
				{
					if(filterOutputs[ui]->getStreamType() == STREAM_TYPE_VOXEL)

					{
						VoxelStreamData *d;
						d=(VoxelStreamData*)filterOutputs[ui];
						d->a=rgba.a();
					}
				}
			}
			break;
		}
		case KEY_VOXAPPEAR_ISOLEVEL:
		{
			float f;
			if(stream_cast(f,value))
				return false;
			if(f <= 0.0f)
				return false;
			needUpdate=true;
			isoLevel=f;
			//Go in and manually adjust the cached
			//entries to have the new value, rather
			//than doing a full recomputation
			if(cacheOK)
			{
				for(unsigned int ui=0;ui<filterOutputs.size();ui++)
				{
					if(filterOutputs[ui]->getStreamType() == STREAM_TYPE_VOXEL)

					{
						VoxelStreamData *d;
						d=(VoxelStreamData*)filterOutputs[ui];
						d->isoLevel=isoLevel;
					}
				}
			}
			break;
		}
		case KEY_VOXAPPEAR_COLOUR:
		{
			ColourRGBA tmpRGBA;

			if(!tmpRGBA.parse(value))
				return false;

			if(tmpRGBA.toRGBAf() != rgba)
			{
				rgba=tmpRGBA.toRGBAf();
				needUpdate=true;
			}

			//Go in and manually adjust the cached
			//entries to have the new value, rather
			//than doing a full recomputation
			if(cacheOK)
			{
				for(unsigned int ui=0;ui<filterOutputs.size();ui++)
				{
					if(filterOutputs[ui]->getStreamType() == STREAM_TYPE_VOXEL)

					{
						VoxelStreamData *d;
						d=(VoxelStreamData*)filterOutputs[ui];
						d->r=rgba.r();
						d->g=rgba.g();
						d->b=rgba.b();
					}
				}
			}
			break;
		}
		case KEY_VOXAPPEAR_VOXEL_REPRESENTATION_MODE:
		{
			unsigned int i;
			for (i = 0; i < VOXEL_REPRESENT_END; i++)
				if (value == getRepresentTypeString(i)) break;
			if (i == VOXEL_REPRESENT_END)
				return false;
			needUpdate=true;
			representation=i;

			//Remove cached objects, but not the internal voxel cache
			parent->Filter::clearCache();
			
			break;
		}
		case KEY_VOXAPPEAR_VOXEL_SLICE_COLOURAUTO:
		{
			bool b;
			if(!boolStrDec(value,b))
				return false;

			//if the result is different, the
			//cache should be invalidated
			if(b!=autoColourMap)
			{
				needUpdate=true;
				autoColourMap=b;
				//Clear the generic filter cache, but
				// not the voxel cache
				parent->Filter::clearCache();
			}
			break;
		}	
		case KEY_VOXAPPEAR_VOXEL_SLICE_AXIS:
		{
			unsigned int i;
			string axisLabels[3]={"x","y","z"};
			for (i = 0; i < 3; i++)
				if( value == axisLabels[i]) break;
				
			if( i >= 3)
				return false;

			if(i != sliceAxis)
			{
				needUpdate=true;
				//clear the generic filter cache (i.e. cached outputs)
				//but not the voxel cache
				parent->Filter::clearCache();
				sliceAxis=i;
			}
			break;
		}
		case KEY_VOXAPPEAR_VOXEL_SLICE_INTERP:
		{
			unsigned int i;
			for (i = 0; i < VOX_INTERP_ENUM_END; i++)
				if( value == TRANS(VOXELISE_SLICE_INTERP_STRING[i])) break;
				
			if( i >= VOX_INTERP_ENUM_END)
				return false;

			if(i != sliceInterpolate)
			{
				needUpdate=true;
				//clear the generic filter cache (i.e. cached outputs)
				//but not the voxel cache
				parent->Filter::clearCache();
				sliceInterpolate=i;
			}
			break;
		}
		case KEY_VOXAPPEAR_VOXEL_SLICE_OFFSET:
		{
			float f;
			if(stream_cast(f,value))
				return false;

			if(f < 0.0f || f > 1.0f)
				return false;


			if( f != sliceOffset)
			{
				needUpdate=true;
				//clear the generic filter cache (i.e. cached outputs)
				//but not the voxel cache
				parent->Filter::clearCache();
				sliceOffset=f;
			}

			break;
		}
		case KEY_VOXAPPEAR_VOXEL_BUILTIN_COLOURMAP_NAME:
		{
			unsigned int tmpMap;
			tmpMap=(unsigned int)-1;
			for(unsigned int ui=0;ui<COLOURMAP_ENUM_END;ui++)
			{
				if(value== getColourMapName(ui))
				{
					tmpMap=ui;
					break;
				}
			}

			if(tmpMap >=COLOURMAP_ENUM_END|| tmpMap ==colourMap)
				return false;

			//clear the generic filter cache (i.e. cached outputs)
			//but not the voxel cache
			parent->Filter::clearCache();
			
			needUpdate=true;
			colourMap=tmpMap;
			break;
		}
		case KEY_VOXAPPEAR_SHOW_COLOURBAR:
		{
			bool b;
			if(!boolStrDec(value,b))
				return false;

			//if the result is different, the
			//cache should be invalidated
			if(b!=showColourBar)
			{
				needUpdate=true;
				showColourBar=b;
				//clear the generic filter cache (i.e. cached outputs)
				//but not the voxel cache
				parent->Filter::clearCache();
			}
			break;
		}	
		case KEY_VOXAPPEAR_MAPSTART:
		{
			float f;
			if(stream_cast(f,value))
				return false;
			if(f >= colourMapBounds[1])
				return false;
		
			if(f!=colourMapBounds[0])
			{
				needUpdate=true;
				colourMapBounds[0]=f;
				//clear the generic filter cache (i.e. cached outputs)
				//but not the voxel cache
				parent->Filter::clearCache();
			}
			break;
		}
		case KEY_VOXAPPEAR_MAPEND:
		{
			float f;
			if(stream_cast(f,value))
				return false;
			if(f <= colourMapBounds[0])
				return false;
		
			if(f!=colourMapBounds[1])
			{
				needUpdate=true;
				colourMapBounds[1]=f;
				//clear the generic filter cache (i.e. cached outputs)
				//but not the voxel cache
				parent->Filter::clearCache();
			}
			break;
		}
		case KEY_VOXAPPEAR_VOXEL_SLICE_OFFSET3D:
		{
			Point3D p;
			if(!p.parse(value))
				return false;
			if(p == freeSlicePlane)
				return false;
			freeSlicePlane=p;

			//Clear only the object cache, not the internal filter cache
			parent->Filter::clearCache();

			break;

		}
		case KEY_VOXAPPEAR_VOXEL_SLICE_NORMAL3D:
		{
			Point3D p;
			if(!p.parse(value))
				return false;
			if(p == freeSliceNormal)
				return false;
			freeSliceNormal=p;

			//Clear only the object cache, not the internal filter cache
			parent->Filter::clearCache();

			break;

		}
		case KEY_VOXAPPEAR_VOXEL_TRANSFER_FUNCTION:
		{
			//Implement me
			if(!fromTransferFunctionString(value,transferFunction))
				return false;

			needUpdate=true;
			parent->Filter::clearCache();  //Clear general filter cache, but not internal cache
			break;

		}
		case KEY_VOXAPPEAR_VOXEL_BUILTIN_COLOURMAP:
		{
			//This is a bit of a hack - it uses this class' data,
			//but also alters the parents state.
			if(!parent->applyPropertyNow(builtinColourmap,value,needUpdate))
				return false;
			break;
		}
		case KEY_VOXAPPEAR_SHOW_BOUNDINGBOX:
		{
			if(!parent->applyPropertyNow(showBoundBox,value,needUpdate))
				return false;
			break;
		}
		case KEY_VOXAPPEAR_BOUNDINGBOX_COLOUR:
		{
			//TODO: Cache introspection
			ColourRGBA tmpRgb;
			tmpRgb.parse(value);

			if(tmpRgb.toRGBAf() != boundColour)
			{
				boundColour=tmpRgb.toRGBAf();
				needUpdate=true;

				//Clear general cache, but not internal voxel cache	
				parent->Filter::clearCache();
			}
		
			break;
		}
		default:
			//we did not recognise the key
			handled=false;
			return false;	
	}

	return true;
}


void VoxelAppearanceProperties::writeState(ostream &f, unsigned int format, unsigned int depth) const
{
	//We have only implemented XML writing
	ASSERT(format == STATE_FORMAT_XML);

	f << tabs(depth+1) << "<representation value=\""<<representation << "\"/>" << endl;
	f << tabs(depth+1) << "<bound show=\""<< boolStrEnc(showBoundBox) << "\" r=\"" <<  boundColour.r()<< "\" g=\"" <<
			boundColour.g() << "\" b=\"" <<boundColour.b() << "\" a=\"" << boundColour.a() << "\"/>" <<endl;

	f << tabs(depth+1) << "<isovalue value=\""<<isoLevel << "\"/>" << endl;
	f << tabs(depth+1) << "<colour r=\"" <<  rgba.r()<< "\" g=\"" << rgba.g() << "\" b=\"" <<rgba.b()
			<< "\" a=\"" << rgba.a() << "\"/>" <<endl;

		f << tabs(depth+2) << "<colourmap builtin=\"" << boolStrEnc(builtinColourmap) <<
				"\" value=\"" << colourMap << "\" transferfunction=\"" << transferFunctionString(transferFunction) << "\"/>" << endl;
		f << tabs(depth+2) << "<colourbar show=\""<<boolStrEnc(showColourBar)<<
				"\" auto=\"" << boolStrEnc(autoColourMap)<< "\" min=\"" <<
				colourMapBounds[0] << "\" max=\"" <<  colourMapBounds[1] << "\" map=\"" << colourMap << "\"/>" << endl;

		f << tabs(depth+1) << "<axialslice>" << endl;
		f << tabs(depth+2) << "<offset value=\""<<sliceOffset<< "\"/>" << endl;
		f << tabs(depth+2) << "<interpolate value=\""<<sliceInterpolate<< "\"/>" << endl;
		f << tabs(depth+2) << "<axis value=\""<<sliceAxis<< "\"/>" << endl;
	f << tabs(depth+1) << "</axialslice>" << endl;

}

//FIXME: This modifes the calling filter through the parent pointer
// although this class is const, it still performs modfiications through its
// member pointers - this is bad design and should be fixed
unsigned int VoxelAppearanceProperties::refresh(Voxels<float> &voxelData, 
				vector<const FilterStreamData *> &getOut, 
				std::vector<SelectionDevice *> &devices) const
{
	Point3D pb1,pb2;
	voxelData.getBounds(pb1,pb2);

	BoundCube lastBounds;
	lastBounds.setBounds(pb1,pb2);
	switch(representation)
	{
		case VOXEL_REPRESENT_ISOSURF:
		case VOXEL_REPRESENT_POINTCLOUD:
#ifdef ENABLE_LIBVD
		case VOXEL_REPRESENT_VOLUME_RENDER:
#endif
		{
			VoxelStreamData *vs = new VoxelStreamData();
			vs->parent=parent;
			std::swap(*(vs->data),voxelData);
			vs->colourMap=colourMap;
			if(builtinColourmap)
				vs->colourMapMode = COLOURMAP_MODE_BUILTIN; 
			else
			{
				vs->colourMapMode= COLOURMAP_MODE_CUSTOM;
				vs->customColourMap=transferFunction;
			}
			vs->representationType= representation;
			vs->splatSize = splatSize;
			vs->isoLevel=isoLevel;
			vs->r=rgba.r();
			vs->g=rgba.g();
			vs->b=rgba.b();
			vs->a=rgba.a();

			vs->showBBox=showBoundBox;
			
			vs->bBoxR=boundColour.r();
			vs->bBoxG=boundColour.g();
			vs->bBoxB=boundColour.b();
			vs->bBoxA=boundColour.a();

			parent->cacheAsNeeded(vs);

			bool wantColourMapRep=false;
#ifdef ENABLE_LIBVD
			wantColourMapRep|=(representation == VOXEL_REPRESENT_VOLUME_RENDER);
#endif
			wantColourMapRep|=(representation == VOXEL_REPRESENT_POINTCLOUD);

			if(wantColourMapRep)
			{
				float minB,maxB;
				if(autoColourMap)
				{
					minB=vs->data->min();
					maxB=vs->data->max();
				}
				else
				{
					minB=colourMapBounds[0];
					maxB=colourMapBounds[1];
				}
				vs->colourMin =minB;
				vs->colourMax =maxB;

				if(showColourBar)
				{
					DrawStreamData *d = new DrawStreamData;
					d->parent=parent;
					//Set the bounds for the colour bar, either from data or from user limits
					
					//Create the colour bar
					d->drawables.emplace_back(makeColourBar(minB,maxB,255,
									colourMap));
					
					parent->cacheAsNeeded(d);

					getOut.push_back(d);
				}
			}

			//Store the voxels on the output
			getOut.push_back(vs);
			break;
		}
		case VOXEL_REPRESENT_AXIAL_SLICE:
		{
			DrawStreamData *d = new DrawStreamData;

			//Create the voxel slice
			float minV,maxV;
			{
			DrawTexturedQuad *dq = new DrawTexturedQuad();

			getTexturedSlice(voxelData,sliceAxis,sliceOffset,
						sliceInterpolate,minV,maxV,*dq);

			dq->setColour(1,1,1,rgba.a());
			dq->canSelect=true;


			SelectionDevice *s = new SelectionDevice(parent);
			SelectionBinding b;
			//Bind translation to left click
			b.setBinding(SELECT_BUTTON_LEFT,0,DRAW_QUAD_BIND_ORIGIN,
					BINDING_PLANE_ORIGIN,dq->getOrigin(),dq);
			b.setInteractionMode(BIND_MODE_POINT3D_TRANSLATE);
			s->addBinding(b);

			devices.push_back(s);

			d->drawables.push_back(dq);
			}
			
			
			d->parent=parent;
			if(showColourBar)
				d->drawables.emplace_back(makeColourBar(minV,maxV,255,
							colourMap));
			
			if(showBoundBox)
			{
				DrawRectPrism *dr=new DrawRectPrism;

				BoundCube b;
				voxelData.getBounds(b);
				dr->setAxisAligned(b);
				dr->setColour(boundColour.r(),
					boundColour.g(),boundColour.b(),
					boundColour.a());
				dr->wantsLight=false;
				d->drawables.push_back(dr);

			}

			d->cached=0;
			
			getOut.push_back(d);
	
			parent->Filter::clearCache();
			break;
		}
		case VOXEL_REPRESENT_SLICE:
		{
			Point3D p;
			p = freeSlicePlane*(pb2-pb1) + pb1;
			vector<Point3D> vPts;
			lastBounds.getPlaneIntersectVertices(p,freeSliceNormal, vPts);

			if(vPts.size() < 3)
				break;

			RandNumGen rng;
			rng.initTimer();

			//Compute average side-length, then convert this into
			// average bins per unit area
			Point3D voxPitch = voxelData.getPitch();
			float meanLenPerBin=0;
			for(unsigned int ui=0;ui<3;ui++)
				meanLenPerBin+=voxPitch[ui]; //length/bin
			meanLenPerBin/=3; //average


			//Per Stephenson, PhD Thesis, Sydney University, 2009
			// Appendix B, CSR nearest neighbour
			// calculations for D-dimensional spatial data

			// in 2D, 1NN distance.
			//Gamma(1+ 1/2)^2
			const float GAMMA_1P5_SQR = 0.78540;

			const float density = 2/M_PI*(GAMMA_1P5_SQR/(meanLenPerBin*meanLenPerBin));


			//scatter some points in the plane
			vector<Point3D> vRes;
			scatterPointsInPolygon(vPts,
				freeSliceNormal,density,rng,vRes);

			//Add the bounding edges
			for(unsigned int ui=0;ui<vPts.size();ui++)
				vRes.push_back(vPts[ui]);

			//Create buffer for delanuay
			double *dBuf = new double[vRes.size()*2];
			for(unsigned int ui=0;ui<vRes.size();ui++)
			{
				dBuf[ui*2] = vRes[ui][0];
				dBuf[ui*2+1] = vRes[ui][1];
			}
			//Compute delaunay
			vector<size_t> tA,tB,tC; //triangle linkages
			do2DDelaunay(dBuf,vRes.size(),tA,tB,tC);
			delete[] dBuf;

				
			//Create triangles that form surface
			DrawStreamData *d = new DrawStreamData(parent);
			d->cached=0;

			float minV,maxV;
			voxelData.minMax(minV,maxV);

			DrawTriangleMesh *tm = new DrawTriangleMesh();
			tm->canSelect=true;
			tm->setAlpha(rgba.a());


			vector<TRI_IDX> idx;
			idx.resize(tA.size());
			for(unsigned int ui=0;ui<tA.size();ui++)
			{
				//Assign linkages
				idx[ui].idx[0]=tA[ui];
				idx[ui].idx[1]=tB[ui];
				idx[ui].idx[2]=tC[ui];

				//Per-vertex colouring
				unsigned char rgb[3];
				for(unsigned int uj=0;uj<3;uj++)
				{
					float f;
					//obtain voxel intensity
					voxelData.getInterpolatedData(vRes[idx[ui].idx[uj]],f);
					//convert to colour
					colourMapWrap(colourMap,rgb,
							f,minV,maxV,false);
				
					for(unsigned int uk=0;uk<3;uk++)	
						idx[ui].col[uj].v[uk] =rgb[uk]/255.0f;
				}
			}
			tm->setData(vRes,idx);


			//Create the user interaction bindings
			//---
			SelectionDevice *s = new SelectionDevice(parent);
			SelectionBinding b[2];
			//Bind translation to sphere left click
			//	-
			BoundCube bMesh;
			tm->getBoundingBox(bMesh);

			b[0].setBinding(SELECT_BUTTON_LEFT,0,DRAW_TRIMESH_BIND_ORIGIN,
					BINDING_PLANE_ORIGIN,bMesh.getCentroid(),tm);
			b[0].setInteractionMode(BIND_MODE_POINT3D_TRANSLATE);
			s->addBinding(b[0]);
			//	-

			//Bind orientation to left click on vector
			//	-
			const float DRAWSCALE=10.0f;
			DrawVector *dV  = new DrawVector;
			dV->setOrigin(p);
			dV->setVector(freeSliceNormal*DRAWSCALE);
			dV->wantsLight=true;
			dV->canSelect=true;
			d->drawables.push_back(dV);


			b[1].setBinding(SELECT_BUTTON_LEFT,0,DRAW_VECTOR_BIND_ORIENTATION,
				BINDING_PLANE_DIRECTION, freeSlicePlane,dV);
			b[1].setInteractionMode(BIND_MODE_POINT3D_ROTATE);
			b[1].setFloatLimits(0,std::numeric_limits<float>::max());
			s->addBinding(b[1]);
			//	-
			
			devices.push_back(s);
			//---


			d->drawables.push_back(tm);

			if(showColourBar)
				d->drawables.emplace_back(makeColourBar(minV,maxV,
							255,colourMap));

			if(showBoundBox)
			{
				DrawRectPrism *dr=new DrawRectPrism;

				BoundCube b;
				voxelData.getBounds(b);
				dr->setAxisAligned(b);
				dr->setColour(boundColour.r(),boundColour.g(),
						boundColour.b(),boundColour.a());
				dr->wantsLight=false;
				d->drawables.push_back(dr);

			}
			getOut.push_back(d);	
			break;
		}
		default:
			ASSERT(false);

	}

	return 0;
}

void VoxelAppearanceProperties::getTexturedSlice(const Voxels<float> &v, 
			size_t axis,float offset, size_t interpolateMode,
			float &minV,float &maxV,DrawTexturedQuad &texQ) const
{
	ASSERT(axis < 3);


	size_t dim[3]; //dim0 and 2 are the in-plane axes. dim3 is the normal axis
	v.getSize(dim[0],dim[1],dim[2]);

	switch(axis)
	{
		//x-normal
		case 0:
			rotate3(dim[0],dim[1],dim[2]);
			std::swap(dim[0],dim[1]);
			break;
		//y-normal
		case 1:
			rotate3(dim[2],dim[1],dim[0]);
			break;
		//z-normal
		case 2:
			std::swap(dim[0],dim[1]);
			break;
			
	}


	ASSERT(dim[0] >0 && dim[1] >0);
	
	texQ.resize(dim[0],dim[1],3);

	//Generate the texture from the voxel data
	//---
	float *data = new float[dim[0]*dim[1]];
	

	ASSERT(offset >=0 && offset <=1.0f);

	v.getInterpSlice(axis,offset,data,interpolateMode);

	if(autoColourMap)
	{
		minV=minValue(data,dim[0]*dim[1]);
		maxV=maxValue(data,dim[0]*dim[1]);
	}
	else
	{
		minV=colourMapBounds[0];
		maxV=colourMapBounds[1];

	}
	ASSERT(minV <=maxV);

	unsigned char rgb[3];
	for(size_t ui=0;ui<dim[0];ui++)
	{
		for(size_t uj=0;uj<dim[1];uj++)
		{
			colourMapWrap(colourMap,rgb, data[ui*dim[1] + uj],
					minV,maxV,false);
			
			texQ.setData(ui,uj,rgb);	
		}
	}

	delete[] data;
	//---
	
	
	
	//Set the vertices of the quad
	//--
	//compute the real position of the plane
	float minPos,maxPos,offsetRealPos;
	v.getAxisBounds(axis,minPos,maxPos);
	offsetRealPos = ((float)offset)*(maxPos-minPos) + minPos; 
	
	
	Point3D verts[4];
	v.getBounds(verts[0],verts[2]);
	//set opposite vertices to upper and lower bounds of quad
	verts[0][axis]=verts[2][axis]=offsetRealPos;
	//set other vertices to match, then shift them in the axis plane
	verts[1]=verts[0]; 
	verts[3]=verts[2];


	unsigned int shiftAxis=(axis+1)%3;
	verts[1][shiftAxis] = verts[2][shiftAxis];
	verts[3][shiftAxis] = verts[0][shiftAxis];

	//Correction for y texture orientation
	if(axis==1)
		std::swap(verts[1],verts[3]);

	texQ.setVertices(verts);
	//--


}
unsigned int VoxelAppearanceProperties::readState(xmlNodePtr &nodePtr)
{
	//Retrieve representation
	if(!XMLGetNextElemAttrib(nodePtr,representation,"representation","value"))
		return false;
	
	//Option did not exist Under 0.0.21 or earlier
	xmlNodePtr tmpPtr;
	tmpPtr =nodePtr;
	if(!XMLGetNextElemAttrib(nodePtr,showBoundBox,"bound","show"))
	{
		showBoundBox=false;
		nodePtr=tmpPtr;	
	}
	else
	{
		//Parse colour attrib
		ColourRGBAf tmpRgba;
		if(!parseXMLColour(nodePtr,tmpRgba))
			return false;

		boundColour=tmpRgba;
	}

	if(representation >=VOXEL_REPRESENT_END)
	{
		//Under 0.0.21, there are two possible compilation modes, with and without libvd
		// thus it is possible to have something above the volume representation
		// supported.
		// TODO: Add a method of emitting warnings
		representation= VOXEL_REPRESENT_POINTCLOUD;
	}

	//-------	
	//Retrieve representation
	if(!XMLGetNextElemAttrib(nodePtr,isoLevel,"isovalue","value"))
		return false;

	//Retrieve colour
	//====
	if(XMLHelpFwdToElem(nodePtr,"colour"))
		return false;
	ColourRGBAf tmpRgba;
	if(!parseXMLColour(nodePtr,tmpRgba))
		return false;
	rgba=tmpRgba;

	//====

	tmpPtr=nodePtr;
	if(!XMLHelpFwdToElem(tmpPtr,"colourmap"))
	{


		if(!XMLGetAttrib(tmpPtr,builtinColourmap,"builtin"))
			return false;
		
		if(!XMLGetAttrib(tmpPtr,colourMap,"value"))
			return false;

		string tFunc;
		if(!XMLGetAttrib(tmpPtr,tFunc,"transferfunction"))
			return false;

		if(tFunc.size() && !fromTransferFunctionString(tFunc,transferFunction))
			return false;

		//Obtain  colour bar attribute
		//---
		if(!XMLGetNextElemAttrib(tmpPtr,autoColourMap,"colourbar","auto"))
			return false;
		
		if(!XMLGetAttrib(tmpPtr,showColourBar,"show"))
			return false;

		if(!XMLGetAttrib(tmpPtr,colourMapBounds[0],"min"))
			return false;

		if(!XMLGetAttrib(tmpPtr,colourMapBounds[1],"max"))
			return false;

		if(colourMapBounds[0] >= colourMapBounds[1])
			return false;

		//---

		
	}
	else
	{
		colourMap=COLOURMAP_VIRIDIS;
		transferFunction.clear();
		builtinColourmap=true;
		autoColourMap=true;
		colourMapBounds[0]=0;
		colourMapBounds[1]=1;

	}

	//try to retrieve slice, where possible
	if(!XMLHelpFwdToElem(nodePtr,"axialslice"))
	{
		xmlNodePtr sliceNodes;
		sliceNodes=nodePtr->xmlChildrenNode;

		if(!sliceNodes)
			return false;

		if(!XMLGetNextElemAttrib(sliceNodes,sliceOffset,"offset","value"))
			return false;

		sliceOffset=std::min(sliceOffset,1.0f);
		sliceOffset=std::max(sliceOffset,0.0f);
		
		
		if(!XMLGetNextElemAttrib(sliceNodes,sliceInterpolate,"interpolate","value"))
			return false;

		if(sliceInterpolate >=VOX_INTERP_ENUM_END)
			return false;

		if(!XMLGetNextElemAttrib(sliceNodes,sliceAxis,"axis","value"))
			return false;

		if(sliceAxis > 2)
			sliceAxis=2;
	
	}
	


	return true;
}

#ifdef DEBUG
bool testCommon()
{
	//IMPLEMENT ME
	return true;
}
#endif
