/*
3DHOP - 3D Heritage Online Presenter
Copyright (c) 2014-2023, Visual Computing Lab, ISTI - CNR
All rights reserved.

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/>.
*/

SpiderGL.openNamespace();

//----------------------------------------------------------------------------------------
// CONSTANTS
//----------------------------------------------------------------------------------------
// version
const HOP_VERSION             = "4.3.6";
// selectors
const HOP_ALL                 = 256;
// starting debug mode
const HOP_DEBUGMODE           = false;
// default light direction
const HOP_DEFAULTLIGHT        = [0, 0, -1];
// sgltrackball
const SGL_TRACKBALL_NO_ACTION = 0;
const SGL_TRACKBALL_ROTATE    = 1;
const SGL_TRACKBALL_PAN       = 2;
const SGL_TRACKBALL_DOLLY     = 3;
const SGL_TRACKBALL_SCALE     = 4;

Presenter = function (canvas) {
	this._supportsWebGL = sglHandleCanvas(canvas, this, { stencil: true });
	console.log("3DHOP version: " + this.version);
};

Presenter.prototype = {
//----------------------------------------------------------------------------------------
// PARSING FUNCTIONS
//----------------------------------------------------------------------------------------
_parseScene : function (options) {
	options = options || { };
	var r = {
		background     : this._parseBackground(options.background),
		meshes         : this._parseMeshes(options.meshes),
		texturedQuads  : this._parseTexturedQuads(options.texturedQuads),
		modelInstances : this._parseModelInstances(options.modelInstances),
		spots          : this._parseSpots(options.spots),
		trackball      : this._parseTrackball(options.trackball),
		space          : this._parseSpace(options.space),
		config         : this._parseConfig(options.config)
	};
	return r;
},

_parseBackground : function (options) {
	var r = sglGetDefaultObject({
		image         : null,
		isEnvironment : false,
		color         : [ 0.0, 0.0, 0.0, 0.0 ]
	}, options);
	if (r.image) { this._objectsToProcess++; this._objectsToLoad++; }
	return r;
},

_parseMeshes : function (options) {
	var r = { };
	for (var m in options) {
		r[m] = this._parseMesh(options[m]);
	}
	return r;
},

_parseMesh : function (options) {
	var r = sglGetDefaultObject({
		url       : null,
		mType     : null,
		transform : null
	}, options);
	r.transform = this._parseTransform(r.transform);
	if (r.url) { this._objectsToProcess++; this._objectsToLoad++; }
	return r;
},

_parseTexturedQuads : function (options) {
	var r = { };
	for (var m in options) {
		r[m] = this._parseTexturedQuad(options[m]);
	}
	return r;
},

_parseTexturedQuad : function (options) {
	var r = sglGetDefaultObject({
		url       : null
	}, options);
	if (r.url) { this._objectsToProcess++; this._objectsToLoad++; }
	return r;
},

_parseModelInstances : function (options) {
	var r = { };
	for (var m in options) {
		r[m] = this._parseModelInstance(options[m]);
	}
	return r;
},

_parseModelInstance : function (options) {
	var r = sglGetDefaultObject({
		mesh            : null,
		rendermode      : null,
		color           : [ 1.0, 1.0, 1.0 ],
		useSolidColor   : false,
		specularColor   : [ 0.0, 0.0, 0.0, 32.0 ],
		backfaceColor   : [ 0.7, 0.4, 0.4, 0.0 ],
		alpha           : 0.5,
		useTransparency : false,
		useLighting     : true,
		cursor          : "default",
		ID              : 0,
		transform       : null,
		visible         : true,
		tags            : [ ],
		clippable       : true,
		measurable      : true,
	}, options);
	if (r.color[3]) //3DHOP 2.0 backward compatibility
	{
		r.alpha = r.color[3];
		r.color = [r.color[0], r.color[1], r.color[2]]
	}
	r.transform = this._parseTransform(r.transform);
	r.ID = this._instancesProgressiveID;
	this._instancesProgressiveID++;
	return r;
},

_parseSpots : function (options) {
	var r = { };
	for (var m in options) {
		r[m] = this._parseSpot(options[m]);
	}
	return r;
},

_parseSpot : function (options) {
	var r = sglGetDefaultObject({
		mesh            : null,
		rendermode      : null,
		color           : [ 0.0, 0.25, 1.0 ],
		useSolidColor   : true,
		alpha           : 0.5,
		alphaHigh       : 0.8,
		useTransparency : true,
		useStencil      : false,
		cursor          : "pointer",
		ID              : 0,
		transform       : null,
		visible         : true,
		tags            : [ ]
	}, options);
	if (r.color[3]) //3DHOP 2.0 backward compatibility
	{
		r.alpha = r.color[3];
		r.color = [r.color[0], r.color[1], r.color[2]]
	}
	r.transform = this._parseTransform(r.transform);
	r.ID = this._spotsProgressiveID;
	this._spotsProgressiveID++;
	return r;
},

_parseTrackball : function (options) {
	var r = sglGetDefaultObject({
		type         : TurnTableTrackball,
		trackOptions : {},
		locked       : false,
		dragSpeed    : 1.0
	}, options);
	return r;
},

_parseSpace : function (options) {
	var r = sglGetDefaultObject({
		centerMode       : "first",
		radiusMode       : "first",
		whichInstanceCenter  : "",
		whichInstanceRadius  : "",
		explicitCenter   : [0.0, 0.0, 0.0],
		explicitRadius   : 1.0,
		transform        : null,
		cameraFOV        : 60.0,
		cameraNearFar    : [0.01, 10.0],
		cameraType       : "perspective",
		sceneLighting      : true,
	}, options);
	r.transform = this._parseTransform(r.transform);
	if(r.cameraFOV < 2.0)  r.cameraFOV = 2.0;
	if(r.cameraFOV > 88.0) r.cameraFOV = 88.0;
	if((r.cameraType != "perspective") && (r.cameraType != "orthographic")) r.cameraType = "perspective";
	return r;
},

_parseConfig : function (options) {
	var r = sglGetDefaultObject({
		pickedpointColor    : [1.0, 0.0, 1.0],
		measurementColor    : [0.5, 1.0, 0.5],
		showClippingPlanes  : true,
		showClippingBorder  : false,
		clippingBorderSize  : 0.5,
		clippingBorderColor : [0.0, 1.0, 1.0],
		pointSize           : 1.0,
		pointSizeMinMax     : [0.0, 2.0],
		autoSaveScreenshot  : true,
		screenshotBaseName  : "screenshot",
		screenshotTime      : true,
	}, options);
	return r;
},

_parseTransform : function (options) {
	var r = sglGetDefaultObject({
		matrix      : SglMat4.identity(),
		rotation    : [0.0, 0.0, 0.0],
		translation : [0.0, 0.0, 0.0],
		scale       : [1.0, 1.0, 1,0],
	}, options);

	// if any of rotation, translation or scale are defined, matrix is overwritten
	var overwrite = false;
	var matrixT = SglMat4.identity();
	var matrixR = SglMat4.identity();
	var matrixS = SglMat4.identity();
	if((r.translation[0] != 0.0)||(r.translation[1] != 0.0)||(r.translation[2] != 0.0))
	{
		matrixT = SglMat4.translation(r.translation);
		overwrite = true;
	}
	if((r.rotation[0] != 0.0)||(r.rotation[1] != 0.0)||(r.rotation[2] != 0.0))
	{
		var mrX = SglMat4.rotationAngleAxis(sglDegToRad(r.rotation[0]), [1.0, 0.0, 0.0]);
		var mrY = SglMat4.rotationAngleAxis(sglDegToRad(r.rotation[1]), [0.0, 1.0, 0.0]);
		var mrZ = SglMat4.rotationAngleAxis(sglDegToRad(r.rotation[2]), [0.0, 0.0, 1.0]);
		matrixR = SglMat4.mul(SglMat4.mul(mrZ, mrY), mrX);
		overwrite = true;
	}
	if((r.scale[0] != 1.0)||(r.scale[1] != 1.0)||(r.scale[2] != 1.0))
	{
		matrixS = SglMat4.scaling(r.scale);
		overwrite = true;
	}
	if (overwrite)
		r.matrix = SglMat4.mul(SglMat4.mul(matrixT, matrixR), matrixS);

	return r;
},

//----------------------------------------------------------------------------------------
//SHADERS RELATED FUNCTIONS
//----------------------------------------------------------------------------------------
// standard program for points rendering
_createStandardPointsProgram : function () {
	var gl = this.ui.gl;
	var pointsVertexShader = new SglVertexShader(gl, "\
		precision highp float;													\n\
																				\n\
		uniform   mat4 uWorldViewProjectionMatrix;								\n\
		uniform   mat3 uViewSpaceNormalMatrix;									\n\
		uniform   mat4 uWorldViewMatrix;										\n\
		uniform   mat4 uModelMatrix;											\n\
		uniform   float uPointSize;												\n\
																				\n\
		attribute vec3 aPosition;												\n\
		attribute vec3 aNormal;													\n\
		attribute vec4 aColor;													\n\
		attribute float aPointSize;												\n\
																				\n\
		varying   vec3 vNormal;													\n\
		varying   vec4 vColor;													\n\
		varying   vec4 vModelPos;												\n\
		varying   vec4 vModelViewPos;											\n\
																				\n\
		void main(void)															\n\
		{																		\n\
			vNormal       = normalize(uViewSpaceNormalMatrix * aNormal);		\n\
			vColor        = aColor;												\n\
			vModelPos     = uModelMatrix * vec4(aPosition, 1.0);				\n\
			vModelViewPos = uWorldViewMatrix * vec4(aPosition, 1.0);			\n\
																				\n\
			gl_Position  = uWorldViewProjectionMatrix * vec4(aPosition, 1.0);	\n\
			gl_PointSize = uPointSize * aPointSize;								\n\
		}																		\n\
	");
	if(this._isDebugging)
		console.log("STD POINTS Vertex Shader Log:\n" + pointsVertexShader.log);

	var pointsFragmentShader = new SglFragmentShader(gl, "\
		#extension GL_EXT_frag_depth : enable									\n\
		precision highp float;													\n\
																				\n\
		uniform   vec3 uViewSpaceLightDirection;								\n\
		uniform   float uAlpha;													\n\
		uniform   bool uUseSolidColor;											\n\
		uniform   bool uUseLighting;											\n\
		uniform   vec3 uSolidColor;												\n\
		uniform   vec4 uSpecularColor;											\n\
		uniform   vec3 uClipPoint;												\n\
		uniform   vec3 uClipAxis;												\n\
		uniform   vec4 uClipPlane;												\n\
		uniform   vec3 uClipColor;												\n\
		uniform   float uClipColorSize;											\n\
																				\n\
		varying   vec3 vNormal;													\n\
		varying   vec4 vColor;													\n\
		varying   vec4 vModelPos;												\n\
		varying   vec4 vModelViewPos;											\n\
																				\n\
		void main(void)															\n\
		{																		\n\
			if(length(uClipPlane.xyz) > 0.0)									\n\
				if( dot(vModelPos, uClipPlane) > 0.0) discard;					\n\
			if(length(uClipAxis) > 0.0)											\n\
			{																	\n\
				if( uClipAxis[0] * (vModelPos[0] - uClipPoint[0]) > 0.0) discard;	\n\
				if( uClipAxis[1] * (vModelPos[1] - uClipPoint[1]) > 0.0) discard;	\n\
				if( uClipAxis[2] * (vModelPos[2] - uClipPoint[2]) > 0.0) discard;	\n\
			}																	\n\
																				\n\
			vec2 cxy = 2.0 * gl_PointCoord - 1.0;								\n\
			float r = dot(cxy, cxy);											\n\
			if (r > 1.0) { discard; }											\n\
																				\n\
			vec3  renderColor = vec3(1.0, 1.0, 1.0);							\n\
			vec3  diffuse = vColor.rgb;											\n\
			float lambert = 1.0;												\n\
			vec3  specular = vec3(0.0, 0.0, 0.0);								\n\
																				\n\
			if(uUseSolidColor) {												\n\
			  if(uSolidColor.r + uSolidColor.g + uSolidColor.b == -3.0)			\n\
				diffuse = vColor.aaa;											\n\
			  else																\n\
				diffuse = uSolidColor;											\n\
			}																	\n\
																				\n\
			if((uUseLighting)&&(length(vNormal) > 0.0))							\n\
			{																	\n\
			  float nDotL   = dot(vNormal, -uViewSpaceLightDirection);			\n\
			  lambert = max(0.0, nDotL);										\n\
																				\n\
              vec3 halfV = normalize(-uViewSpaceLightDirection -vModelViewPos.xyz);\n\
              float spc = pow(max(dot(vNormal, halfV),0.0), uSpecularColor.a);	\n\
              specular = spc * uSpecularColor.rgb;								\n\
			}																	\n\
																				\n\
			renderColor = (diffuse * lambert) + specular;						\n\
																				\n\
			if((length(uClipPlane.xyz) > 0.0)&&(uClipColorSize>0.0))			\n\
				if( dot(vModelPos, uClipPlane) > -uClipColorSize) renderColor = uClipColor;	\n\
			if((length(uClipAxis) > 0.0)&&(uClipColorSize>0.0))					\n\
			{																	\n\
				if( uClipAxis[0] * (vModelPos[0] - uClipPoint[0] + (uClipAxis[0]*uClipColorSize)) > 0.0) renderColor = uClipColor;	\n\
				if( uClipAxis[1] * (vModelPos[1] - uClipPoint[1] + (uClipAxis[1]*uClipColorSize)) > 0.0) renderColor = uClipColor;	\n\
				if( uClipAxis[2] * (vModelPos[2] - uClipPoint[2] + (uClipAxis[2]*uClipColorSize)) > 0.0) renderColor = uClipColor;	\n\
			}																	\n\
																				\n\
			gl_FragColor = vec4(renderColor, uAlpha);							\n\
			gl_FragDepthEXT = gl_FragCoord.z + 0.0001*(pow(r, 2.0));			\n\
		}																		\n\
	");
	if(this._isDebugging)
		console.log("STD POINTS	Fragment Shader Log:\n" + pointsFragmentShader.log);

	var program = new SglProgram(gl, {
		shaders    : [
			pointsVertexShader,
			pointsFragmentShader
		],
		attributes : {
			"aPosition" : 0,
			"aNormal"   : 1,
			"aColor"    : 2,
			"aPointSize": 4
		},
		uniforms   : {
			"uWorldViewProjectionMatrix" : SglMat4.identity(),
			"uViewSpaceNormalMatrix"     : SglMat3.identity(),
			"uWorldViewMatrix"           : SglMat4.identity(),
			"uModelMatrix"               : SglMat4.identity(),
			"uViewSpaceLightDirection"   : [0.0, 0.0, -1.0],
			"uPointSize"                 : 1.0,
			"uAlpha"                     : 1.0,
			"uUseSolidColor"             : false,
			"uUseLighting"               : true,
			"uSolidColor"                : [1.0, 1.0, 1.0],
			"uSpecularColor"             : [0.0, 0.0, 0.0, 32.0],
			"uClipPoint"                 : [0.0, 0.0, 0.0],
			"uClipAxis"                  : [0.0, 0.0, 0.0],
			"uClipPlane"                 : [0.0, 0.0, 0.0, 0.0],
			"uClipColor"				 : [1.0, 1.0, 1.0],
			"uClipColorSize"			 : 0.5
		}
	});
	if(this._isDebugging)
		console.log("STD POINTS Program Log:\n" + program.log);

	return program;
},

// standard program for faces rendering
_createStandardFacesProgram : function () {
	var gl = this.ui.gl;
	var facesVertexShader = new SglVertexShader(gl, "\
		precision highp float;													\n\
																				\n\
		uniform   mat4 uWorldViewProjectionMatrix;								\n\
		uniform   mat3 uViewSpaceNormalMatrix;									\n\
		uniform   mat4 uWorldViewMatrix;										\n\
		uniform   mat4 uModelMatrix;											\n\
																				\n\
		attribute vec3 aPosition;												\n\
		attribute vec3 aNormal;													\n\
		attribute vec4 aColor;													\n\
		attribute vec2 aTextureCoord;											\n\
																				\n\
		varying   vec3 vNormal;													\n\
		varying   vec4 vColor;													\n\
		varying   vec4 vModelPos;												\n\
		varying   vec4 vModelViewPos;											\n\
		varying   vec2 vTextureCoord;											\n\
																				\n\
		void main(void)															\n\
		{																		\n\
			vNormal       = normalize(uViewSpaceNormalMatrix * aNormal);		\n\
			vColor        = aColor;												\n\
			vModelPos     = uModelMatrix * vec4(aPosition, 1.0);				\n\
			vModelViewPos = uWorldViewMatrix * vec4(aPosition, 1.0);			\n\
			vTextureCoord = aTextureCoord;										\n\
																				\n\
			gl_Position = uWorldViewProjectionMatrix * vec4(aPosition, 1.0);	\n\
		}																		\n\
	");
	if(this._isDebugging)
		console.log("STD FACES Vertex Shader Log:\n" + facesVertexShader.log);

	var facesFragmentShader = new SglFragmentShader(gl, "\
		precision highp float;													\n\
																				\n\
		uniform   vec3 uViewSpaceLightDirection;								\n\
		uniform   float uAlpha;													\n\
		uniform   bool uUseSolidColor;											\n\
		uniform   bool uUseLighting;											\n\
		uniform   vec4 uBackFaceColor;											\n\
		uniform   vec3 uSolidColor;												\n\
		uniform   vec4 uSpecularColor;											\n\
		uniform   vec3 uClipPoint;												\n\
		uniform   vec3 uClipAxis;												\n\
		uniform   vec4 uClipPlane;												\n\
		uniform   vec3 uClipColor;												\n\
		uniform   float uClipColorSize;											\n\
		uniform   sampler2D uSampler;											\n\
																				\n\
		varying   vec3 vNormal;													\n\
		varying   vec4 vColor;													\n\
		varying   vec4 vModelPos;												\n\
		varying   vec4 vModelViewPos;											\n\
		varying   vec2 vTextureCoord;											\n\
																				\n\
		void main(void)															\n\
		{																		\n\
			if(length(uClipPlane.xyz) > 0.0)									\n\
				if( dot(vModelPos, uClipPlane) > 0.0) discard;					\n\
			if(length(uClipAxis) > 0.0)											\n\
			{																	\n\
				if( uClipAxis[0] * (vModelPos[0] - uClipPoint[0]) > 0.0) discard;	\n\
				if( uClipAxis[1] * (vModelPos[1] - uClipPoint[1]) > 0.0) discard;	\n\
				if( uClipAxis[2] * (vModelPos[2] - uClipPoint[2]) > 0.0) discard;	\n\
			}																	\n\
																				\n\
			vec3  renderColor = vec3(1.0, 1.0, 1.0);							\n\
			vec3  diffuse = vColor.rgb;											\n\
			float lambert = 1.0;												\n\
			vec3  specular = vec3(0.0, 0.0, 0.0);								\n\
																				\n\
			if(vTextureCoord.x != 0.0)											\n\
			  diffuse = texture2D(uSampler, vTextureCoord).xyz;					\n\
																				\n\
			if(uUseSolidColor) {												\n\
			  if(uSolidColor.r + uSolidColor.g + uSolidColor.b == -3.0)			\n\
				diffuse = vColor.aaa;											\n\
			  else if(uSolidColor.r + uSolidColor.g + uSolidColor.b == -6.0)	\n\
				diffuse = vColor.rgb;											\n\
			  else																\n\
				diffuse = uSolidColor;											\n\
			}																	\n\
																				\n\
			if((uUseLighting)&&(length(vNormal) > 0.0))							\n\
			{																	\n\
			  float nDotL   = dot(vNormal, -uViewSpaceLightDirection);			\n\
			  lambert = max(0.0, gl_FrontFacing? nDotL : -nDotL);				\n\
																				\n\
              vec3 halfV = normalize(-uViewSpaceLightDirection -vModelViewPos.xyz);\n\
              float spc = pow(max(dot(vNormal, halfV),0.0), uSpecularColor.a);	\n\
              specular = spc * uSpecularColor.rgb;								\n\
			}																	\n\
																				\n\
			renderColor = (diffuse * lambert) + specular;						\n\
			if(gl_FrontFacing==false)											\n\
			{																	\n\
				if (uBackFaceColor[3]==0.0) renderColor = renderColor * uBackFaceColor.rgb;	\n\
				else if(uBackFaceColor[3]==1.0) renderColor = uBackFaceColor.rgb;			\n\
				else if(uBackFaceColor[3]==2.0) discard;									\n\
				else if(uBackFaceColor[3]==3.0) renderColor = (uBackFaceColor.rgb * lambert)+specular;\n\
			}																	\n\
																				\n\
			if((length(uClipPlane.xyz) > 0.0)&&(uClipColorSize>0.0))			\n\
				if( dot(vModelPos, uClipPlane) > -uClipColorSize) renderColor = uClipColor;	\n\
			if((length(uClipAxis) > 0.0)&&(uClipColorSize>0.0))					\n\
			{																	\n\
				if( uClipAxis[0] * (vModelPos[0] - uClipPoint[0] + (uClipAxis[0]*uClipColorSize)) > 0.0) renderColor = uClipColor;	\n\
				if( uClipAxis[1] * (vModelPos[1] - uClipPoint[1] + (uClipAxis[1]*uClipColorSize)) > 0.0) renderColor = uClipColor;	\n\
				if( uClipAxis[2] * (vModelPos[2] - uClipPoint[2] + (uClipAxis[2]*uClipColorSize)) > 0.0) renderColor = uClipColor;	\n\
			}																	\n\
																				\n\
			gl_FragColor = vec4(renderColor, uAlpha);							\n\
		}																		\n\
	");
	if(this._isDebugging)
		console.log("STD FACES Fragment Shader Log:\n" + facesFragmentShader.log);

	var program = new SglProgram(gl, {
		shaders    : [
			facesVertexShader,
			facesFragmentShader
		],
		attributes : {
			"aPosition"     : 0,
			"aNormal"       : 1,
			"aColor"        : 2,
			"aTextureCoord" : 3
		},
		uniforms   : {
			"uWorldViewProjectionMatrix" : SglMat4.identity(),
			"uViewSpaceNormalMatrix"     : SglMat3.identity(),
			"uWorldViewMatrix"           : SglMat4.identity(),
			"uModelMatrix"               : SglMat4.identity(),
			"uViewSpaceLightDirection"   : [0.0, 0.0, -1.0],
			"uAlpha"                     : 1.0,
			"uUseSolidColor"             : false,
			"uUseLighting"               : true,
			"uBackFaceColor"             : [0.4, 0.3, 0.3, 0.0],
			"uSolidColor"                : [1.0, 1.0, 1.0],
			"uSpecularColor"             : [0.0, 0.0, 0.0, 32.0],
			"uClipPoint"                 : [0.0, 0.0, 0.0],
			"uClipAxis"                  : [0.0, 0.0, 0.0],
			"uClipPlane"                 : [0.0, 0.0, 0.0, 0.0],
			"uClipColor"                 : [1.0, 1.0, 1.0],
			"uClipColorSize"             : 0.5,
			"uSampler"                   : 0
		}
	});
	if(this._isDebugging)
		console.log("STD FACE Program Log:\n" + program.log);

	return program;
},

// utils program for XYZ picking and color coded rendering
_createUtilsProgram : function () {
	var gl = this.ui.gl;
	var utilsVertexShader = new SglVertexShader(gl, "\
		precision highp float;                                                \n\
																			  \n\
		uniform   mat4 uWorldViewProjectionMatrix;                            \n\
		uniform   mat4 uModelMatrix;                                          \n\
		uniform   float uPointSize;                                           \n\
																			  \n\
		attribute vec3 aPosition;                                             \n\
		attribute vec3 aNormal;                                               \n\
		attribute vec4 aColor;                                                \n\
		attribute float aPointSize;                                           \n\
																			  \n\
		varying   vec4 vModelPos;                                             \n\
																			  \n\
		void main(void)                                                       \n\
		{                                                                     \n\
			vModelPos = uModelMatrix * vec4(aPosition, 1.0);                  \n\
			gl_Position = uWorldViewProjectionMatrix * vec4(aPosition, 1.0);  \n\
			gl_PointSize = uPointSize * aPointSize;							  \n\
		}                                                                     \n\
	");
	if(this._isDebugging)
		console.log("UTILS Vertex Shader Log:\n" + utilsVertexShader.log);

	var utilsFragmentShader = new SglFragmentShader(gl, "\
		precision highp float;													\n\
																				\n\
		uniform   vec3 uClipPoint;												\n\
		uniform   vec3 uClipAxis;												\n\
		uniform   vec4 uClipPlane;												\n\
		uniform   vec4 uBackFaceColor;											\n\
		uniform   vec4 uColorID;												\n\
		uniform   float uMode;													\n\
																				\n\
		varying   vec4 vModelPos;												\n\
																				\n\
		vec4 pack_depth(const in float depth)											\n\
		{																				\n\
			const vec4 bit_shift = vec4(255.0*255.0*255.0, 255.0*255.0, 255.0, 1.0);	\n\
			const vec4 bit_mask  = vec4(0.0, 1.0/255.0, 1.0/255.0, 1.0/255.0);			\n\
			vec4 res = fract(depth * bit_shift);										\n\
			res -= res.xxyz * bit_mask;													\n\
			return res;																	\n\
		}																				\n\
																				\n\
		void main(void)															\n\
		{																		\n\
			if(length(uClipPlane.xyz) > 0.0)									\n\
				if( dot(vModelPos, uClipPlane) > 0.0) discard;					\n\
			if(length(uClipAxis) > 0.0)											\n\
			{																	\n\
				if( uClipAxis[0] * (vModelPos[0] - uClipPoint[0]) > 0.0) discard;	\n\
				if( uClipAxis[1] * (vModelPos[1] - uClipPoint[1]) > 0.0) discard;	\n\
				if( uClipAxis[2] * (vModelPos[2] - uClipPoint[2]) > 0.0) discard;	\n\
			}																	\n\
			if((gl_FrontFacing==false) && (uBackFaceColor[3]==2.0)) discard;	\n\
																				\n\
			vec4 outColor;														\n\
			if(uMode == 1.0)	//xyx picking									\n\
				outColor = pack_depth(gl_FragCoord.z);							\n\
			else if(uMode == 2.0)	//color coded								\n\
				outColor = uColorID;											\n\
																				\n\
			gl_FragColor = outColor;											\n\
		}																		\n\
	");
	if(this._isDebugging)
		console.log("UTILS Fragment Shader Log:\n" + utilsFragmentShader.log);

	var utilsProgram = new SglProgram(gl, {
		shaders    : [
			utilsVertexShader,
			utilsFragmentShader
		],
		attributes : {
			"aPosition" : 0,
			"aNormal"   : 1,
			"aColor"    : 2,
			"aPointSize": 4
		},
		uniforms   : {
			"uWorldViewProjectionMatrix" : SglMat4.identity(),
			"uModelMatrix"               : SglMat4.identity(),
			"uClipPoint"                 : [0.0, 0.0, 0.0],
			"uClipAxis"                  : [0.0, 0.0, 0.0],
			"uClipPlane"                 : [0.0, 0.0, 0.0, 0.0],
			"uBackFaceColor"             : [0.4, 0.3, 0.3, 0.0],
			"uColorID"                   : [1.0, 0.5, 0.0, 1.0],
			"uPointSize"                 : 1.0,
			"uMode"                      : 1.0
		}
	});
	if(this._isDebugging)
		console.log("UTILS Program Log:\n" + utilsProgram.log);

	return utilsProgram;
},

// single-color barely-shaded program for spot and planes rendering
_createColorShadedProgram : function () {
	var gl = this.ui.gl;
	var colorShadedVertexShader = new SglVertexShader(gl, "\
		precision highp float;                                                \n\
																			  \n\
		uniform   mat4 uWorldViewProjectionMatrix;                            \n\
		uniform   mat3 uViewSpaceNormalMatrix;                                \n\
		uniform   float uPointSize;                                           \n\
																			  \n\
		attribute vec3 aPosition;                                             \n\
		attribute vec3 aNormal;                                               \n\
		attribute float aPointSize;                                           \n\
																			  \n\
		varying   vec3 vNormal;                                               \n\
																			  \n\
		void main(void)                                                       \n\
		{                                                                     \n\
			vNormal     = uViewSpaceNormalMatrix * aNormal;                   \n\
																			  \n\
			gl_Position = uWorldViewProjectionMatrix * vec4(aPosition, 1.0);  \n\
			gl_PointSize = uPointSize * aPointSize;							  \n\
		}                                                                     \n\
	");
	if(this._isDebugging)
		console.log("COLOR SHADED Vertex Shader Log:\n" + colorShadedVertexShader.log);

	var colorShadedFragmentShader = new SglFragmentShader(gl, "\
		precision highp float;                                                \n\
																			  \n\
		uniform   vec3 uViewSpaceLightDirection;                              \n\
		uniform   vec4 uColorID;                                              \n\
																			  \n\
		varying   vec3 vNormal;                                               \n\
																			  \n\
		void main(void)                                                       \n\
		{                                                                     \n\
			if(uColorID[3] == 0.0) discard;                                   \n\
			vec3  diffuse = vec3(uColorID[0], uColorID[1], uColorID[2]);      \n\
																			  \n\
			if(vNormal[0] != 0.0 || vNormal[1] != 0.0 || vNormal[2] != 0.0) { \n\
				vec3  normal    = normalize(vNormal);                         \n\
				float nDotL     = dot(normal, -uViewSpaceLightDirection);     \n\
				float lambert   = max(-nDotL, nDotL);                         \n\
																			  \n\
				diffuse = (diffuse * 0.5) + (diffuse * lambert * 0.5);        \n\
			}                                                                 \n\
			gl_FragColor = vec4(diffuse, uColorID[3]);                       \n\
		}                                                                     \n\
	");
	if(this._isDebugging)
		console.log("COLOR SHADED Fragment Shader Log:\n" + colorShadedFragmentShader.log);

	var program = new SglProgram(gl, {
		shaders    : [
			colorShadedVertexShader,
			colorShadedFragmentShader
		],
		attributes : {
			"aPosition" : 0,
			"aNormal"   : 1,
			"aPointSize": 4
		},
		uniforms   : {
			"uWorldViewProjectionMatrix" : SglMat4.identity(),
			"uViewSpaceNormalMatrix"     : SglMat3.identity(),
			"uViewSpaceLightDirection"   : [0.0, 0.0, -1.0],
			"uColorID"                   : [1.0, 0.5, 0.0, 1.0],
			"uPointSize"                 : 1.0
		}
	});
	if(this._isDebugging)
		console.log("COLOR SHADED Program Log:\n" + program.log);

	return program;
},

// standard technique for PLY points rendering
_createStandardPointsTechnique : function () {
	var gl = this.ui.gl;
	var technique = new SglTechnique(gl, {
		program  : this._createStandardPointsProgram(),
		vertexStreams : {
			"aNormal"    : [ 0.0, 0.0, 0.0 ],
			"aColor"     : [ 0.8, 0.8, 0.8, 1.0 ],
			"aPointSize" : 1.0
		},
		globals : {
			"uWorldViewProjectionMatrix" : { semantic : "uWorldViewProjectionMatrix", value : SglMat4.identity() },
			"uViewSpaceNormalMatrix"     : { semantic : "uViewSpaceNormalMatrix",     value : SglMat3.identity() },
			"uWorldViewMatrix"           : { semantic : "uWorldViewMatrix",           value : SglMat4.identity() },
			"uModelMatrix"               : { semantic : "uModelMatrix",               value : SglMat4.identity() },
			"uViewSpaceLightDirection"   : { semantic : "uViewSpaceLightDirection",   value : [ 0.0, 0.0, -1.0 ] },
			"uPointSize"                 : { semantic : "uPointSize",                 value : 1.0 },
			"uAlpha"                     : { semantic : "uAlpha",                     value : 1.0 },
			"uUseSolidColor"             : { semantic : "uUseSolidColor",             value : false },
			"uUseLighting"               : { semantic : "uUseLighting",               value : true },
			"uSolidColor"                : { semantic : "uSolidColor",                value : [ 1.0, 1.0, 1.0 ] },
			"uBackFaceColor"             : { semantic : "uBackFaceColor",             value : [0.4, 0.3, 0.3, 0.0] },
			"uSpecularColor"             : { semantic : "uSpecularColor",             value : [0.5, 0.2, 0.8, 32.0] },
			"uClipPoint"                 : { semantic : "uClipPoint",                 value : [ 0.0, 0.0, 0.0 ] },
			"uClipAxis"                  : { semantic : "uClipAxis",                  value : [ 0.0, 0.0, 0.0 ] },
			"uClipPlane"                 : { semantic : "uClipPlane",                 value : [ 0.0, 0.0, 0.0, 0.0 ] },
			"uClipColor"                 : { semantic : "uClipColor",                 value : [ 1.0, 1.0, 1.0 ]},
			"uClipColorSize"             : { semantic : "uClipColorSize",             value : 0.5 }
		}
	});

	return technique;
},

// standard technique for PLY faces rendering
_createStandardFacesTechnique : function () {
	var gl = this.ui.gl;
	var technique = new SglTechnique(gl, {
		program  : this._createStandardFacesProgram(),
		vertexStreams : {
			"aNormal"       : [ 0.0, 0.0, 0.0 ],
			"aColor"        : [ 0.8, 0.8, 0.8, 1.0 ],
			"aTextureCoord" : [ 0.0, 0.0 ]
		},
		globals : {
			"uWorldViewProjectionMatrix" : { semantic : "uWorldViewProjectionMatrix", value : SglMat4.identity() },
			"uViewSpaceNormalMatrix"     : { semantic : "uViewSpaceNormalMatrix",     value : SglMat3.identity() },
			"uWorldViewMatrix"           : { semantic : "uWorldViewMatrix",           value : SglMat4.identity() },
			"uModelMatrix"               : { semantic : "uModelMatrix",               value : SglMat4.identity() },
			"uViewSpaceLightDirection"   : { semantic : "uViewSpaceLightDirection",   value : [ 0.0, 0.0, -1.0 ] },
			"uAlpha"                     : { semantic : "uAlpha",                     value : 1.0 },
			"uUseSolidColor"             : { semantic : "uUseSolidColor",             value : false },
			"uUseLighting"               : { semantic : "uUseLighting",               value : true },
			"uSolidColor"                : { semantic : "uSolidColor",                value : [ 1.0, 1.0, 1.0 ] },
			"uBackFaceColor"             : { semantic : "uBackFaceColor",             value : [0.4, 0.3, 0.3, 0.0] },
			"uSpecularColor"             : { semantic : "uSpecularColor",             value : [0.5, 0.2, 0.8, 32.0] },
			"uClipPoint"                 : { semantic : "uClipPoint",                 value : [ 0.0, 0.0, 0.0 ] },
			"uClipAxis"                  : { semantic : "uClipAxis",                  value : [ 0.0, 0.0, 0.0 ] },
			"uClipPlane"                 : { semantic : "uClipPlane",                 value : [ 0.0, 0.0, 0.0, 0.0 ] },
			"uClipColor"                 : { semantic : "uClipColor",                 value : [ 1.0, 1.0, 1.0 ]},
			"uClipColorSize"             : { semantic : "uClipColorSize",             value : 0.5 },
			"uSampler"                   : { semantic : "uSampler",                   value : 0 }
		}
	});

	return technique;
},

// utils technique for PLY picking and color coded rendering
_createUtilsTechnique : function () {
	var gl = this.ui.gl;
	var technique = new SglTechnique(gl, {
		program  : this._createUtilsProgram(),
		vertexStreams : {
			"aNormal"    : [ 0.0, 0.0, 0.0 ],
			"aColor"     : [ 0.8, 0.8, 0.8, 1.0 ],
			"aPointSize" : 1.0,
		},
		globals : {
			"uWorldViewProjectionMatrix" : { semantic : "uWorldViewProjectionMatrix", value : SglMat4.identity() },
			"uModelMatrix"               : { semantic : "uModelMatrix",               value : SglMat4.identity() },
			"uClipPoint"                 : { semantic : "uClipPoint",                 value : [ 0.0, 0.0, 0.0 ] },
			"uClipAxis"                  : { semantic : "uClipAxis",                  value : [ 0.0, 0.0, 0.0 ] },
			"uClipPlane"                 : { semantic : "uClipPlane",                 value : [ 0.0, 0.0, 0.0, 0.0 ] },
			"uBackFaceColor"             : { semantic : "uBackFaceColor",             value : [0.4, 0.3, 0.3, 0.0] },
			"uColorID"                   : { semantic : "uColorID",                   value : [1.0, 0.5, 0.0, 1.0] },
			"uPointSize"                 : { semantic : "uPointSize",                 value : 1.0 },
			"uMode"                      : { semantic : "uMode",                      value : 1.0 }
		}
	});

	return technique;
},

// single-color barely-shaded technique for PLY spot and planes rendering
_createColorShadedTechnique : function () {
	var gl = this.ui.gl;
	var technique = new SglTechnique(gl, {
		program  : this._createColorShadedProgram(),
		vertexStreams : {
			"aNormal"    : [ 0.0, 0.0, 0.0 ],
			"aPointSize" : 1.0
		},
		globals : {
			"uWorldViewProjectionMatrix" : { semantic : "uWorldViewProjectionMatrix", value : SglMat4.identity() },
			"uViewSpaceNormalMatrix"     : { semantic : "uViewSpaceNormalMatrix",     value : SglMat3.identity() },
			"uViewSpaceLightDirection"   : { semantic : "uViewSpaceLightDirection",   value : [ 0.0, 0.0, -1.0 ] },
			"uColorID"                   : { semantic : "uColorID",                   value : [1.0, 0.5, 0.25, 1.0] },
			"uPointSize"                 : { semantic : "uPointSize",                 value : 1.0 }
		}
	});

	return technique;
},

// 2 points line rendering
_createSimpleLinetechnique : function () {
	var gl = this.ui.gl;
	var technique = new SglTechnique(gl, {
		vertexShader : "\
			precision highp float;                                                \n\
																				  \n\
			uniform   mat4 uWorldViewProjectionMatrix;                            \n\
			uniform vec3 uPointA;                                                 \n\
			uniform vec3 uPointB;                                                 \n\
																				  \n\
			attribute vec3 aPosition;                                             \n\
			attribute vec3 aNormal;                                               \n\
			attribute vec4 aColor;                                                \n\
																				  \n\
			void main(void)                                                       \n\
			{                                                                     \n\
				vec3 newPos;                                                      \n\
				if(aPosition[0]==0.0)                                             \n\
					newPos = uPointA;                                             \n\
				else                                                              \n\
					newPos = uPointB;                                             \n\
				gl_Position = uWorldViewProjectionMatrix * vec4(newPos, 1.0);     \n\
				gl_PointSize = 8.0;	                 							  \n\
			}                                                                     \n\
		",
		fragmentShader : "\
			precision highp float;                                                \n\
																				  \n\
			uniform   vec4 uLineColor;                                            \n\
																				  \n\
			void main(void)                                                       \n\
			{                                                                     \n\
				gl_FragColor = uLineColor;                                     \n\
			}                                                                     \n\
		",
		vertexStreams : {
			"aNormal" : [ 0.0, 0.0, 0.0 ],
			"aColor"  : [ 0.8, 0.8, 0.8, 1.0 ]
		},
		globals : {
			"uWorldViewProjectionMatrix" : { semantic : "uWorldViewProjectionMatrix", value : SglMat4.identity() },
			"uLineColor"                 : { semantic : "uLineColor",                 value : [0.0, 1.0, 0.5, 1.0] },
			"uPointA"                    : { semantic : "uPointA",                    value : [0.0, 0.0, 0.0] },
			"uPointB"                    : { semantic : "uPointB",                    value : [1.0, 1.0, 1.0] }
		}
	});

	return technique;
},

// multiple lines/points rendering
_createMultiLinesPointstechnique : function () {
	var gl = this.ui.gl;
	var technique = new SglTechnique(gl, {
		vertexShader : "\
			precision highp float;                                                \n\
																				  \n\
			uniform   mat4 uWorldViewProjectionMatrix;                            \n\
			uniform   float uPointSize;  				                          \n\
																				  \n\
			attribute vec3 aPosition;                                             \n\
			attribute vec3 aNormal;                                               \n\
			attribute vec4 aColor;                                                \n\
																				  \n\
			varying   vec3 vNormal;                                               \n\
			varying   vec4 vColor;                                                \n\
																				  \n\
			void main(void)                                                       \n\
			{                                                                     \n\
				vColor      = aColor;                                             \n\
				gl_Position = uWorldViewProjectionMatrix * vec4(aPosition, 1.0);  \n\
				gl_PointSize = uPointSize;				 					 	  \n\
			}                                                                     \n\
		",
		fragmentShader : "\
		#extension GL_EXT_frag_depth : enable									  \n\
		precision highp float;													  \n\
																				  \n\
			uniform   vec4 uColorID;											  \n\
			uniform   float uZOff;												  \n\
																				  \n\
			varying   vec4 vColor;                                                \n\
																				  \n\
			void main(void)                                                       \n\
			{                                                                     \n\
				gl_FragColor = uColorID;                                          \n\
				gl_FragDepthEXT = gl_FragCoord.z - uZOff;						  \n\
			}                                                                     \n\
		",
		vertexStreams : {
			"aNormal" : [ 0.0, 0.0, 0.0 ],
			"aColor"  : [ 0.8, 0.8, 0.8, 1.0 ]
		},
		globals : {
			"uWorldViewProjectionMatrix" : { semantic : "uWorldViewProjectionMatrix", value : SglMat4.identity() },
			"uColorID"                   : { semantic : "uColorID",                   value : [1.0, 0.5, 0.25, 1.0] },
			"uPointSize"                 : { semantic : "uPointSize",                 value : 1.0 },
			"uZOff"                      : { semantic : "uZOff",                      value : 0.0 },
		}
	});

	return technique;
},

//----------------------------------------------------------------------------------------
// SUPPORT FUNCTIONS
//----------------------------------------------------------------------------------------
_onMeshReady : function () {
	this._objectLoaded();
},

_onTextureReady : function () {
	this._objectLoaded();
},

_onBackgroundReady : function () {
	this._objectLoaded();
},

_objectLoaded : function () {
	this._objectsToLoad--;
	this._testReady();
},

_testReady : function () {
	if (this._objectsToLoad != 0) return;
	this._sceneReady = this._scenePrepare();
	this.repaint();
},

_scenePrepare : function () {
	var meshes    = this._scene.meshes;
	var instances = this._scene.modelInstances;
	var spots     = this._scene.spots;
	for (var mesh in meshes) {
		if (!meshes[mesh].renderable) continue;
		for (var inst in instances)
			if (mesh == instances[inst].mesh)
					instances[inst].rendermode = meshes[mesh].renderable.renderMode[0];
		for (var spt in spots)
			if (mesh == spots[spt].mesh)
					spots[spt].rendermode = meshes[mesh].renderable.renderMode[0];
	}
	return true;
},

_isSceneReady : function () {
	var r = (this._scene && this._sceneParsed && this._sceneReady);
	return r;
},

_pickingRefresh: function(x,y) {
	this._pickpoint[0] = x;
	this._pickpoint[1] = y;

	var pickedPixel;
	var ID, cursor;

	if(this._onEndPickingPoint||this._onEndMeasurement||this._onPickedSpot||this._onEnterSpot||this._onLeaveSpot){
		pickedPixel = this._drawScenePickingSpots();
		ID = this._Color2ID(pickedPixel);
		if(this._lastSpotID != ID){
			var spots = this._scene.spots;
			if(ID != 0){
				for (var spt in spots) {
					if (spots[spt].ID == ID) {
						this._pickedSpot = spt;
						if(this._onHover){
							cursor = spots[spt].cursor;
							if(/*!this._movingLight ||*/ !this._isMeasuring){
								this._lastCursor = document.getElementById(this.ui.canvas.id).style.cursor;
								document.getElementById(this.ui.canvas.id).style.cursor = cursor;
							}
							if(this._onLeaveSpot && this._lastPickedSpot!=null)  this._onLeaveSpot(this._lastPickedSpot);
							if(this._onEnterSpot && this._pickedSpot!=null) this._onEnterSpot(this._pickedSpot);
							this.repaint();
						}
						this._lastPickedSpot = spt;
						break;
					}
				}
				this._lastSpotID = ID;
			}
			else{
				this._pickedSpot = null;
				if(this._onHover){
					if(/*!this._movingLight ||*/ !this._isMeasuring) document.getElementById(this.ui.canvas.id).style.cursor = "default";
					if(this._onLeaveSpot && this._lastPickedSpot!=null) this._onLeaveSpot(this._lastPickedSpot);
					//if(this._onEnterSpot) this._onEnterSpot(this._pickedSpot);
					this._lastPickedSpot = null;
					this.repaint();
				}
				this._lastSpotID = ID;
			}
		}
	}

	if(this._onEndPickingPoint||this._onEndMeasurement||this._onPickedInstance||this._onEnterInstance||this._onLeaveInstance){
		pickedPixel = this._drawScenePickingInstances();
		ID = this._Color2ID(pickedPixel);
		if(this._lastInstanceID == ID && !(this._onPickedSpot||this._onEnterSpot||this._onLeaveSpot)) return;
		if(ID != 0){
			var instances = this._scene.modelInstances;
			for (var inst in instances) {
				if (instances[inst].ID == ID) {
					this._pickedInstance = inst;
					if(this._onHover){
						cursor = instances[inst].cursor;
						if(/*!this._movingLight ||*/ !this._isMeasuring){
							this._lastCursor = cursor;
							if(this._pickedSpot==null)document.getElementById(this.ui.canvas.id).style.cursor = cursor;
							this.repaint();
						}
						if(this._onLeaveInstance && this._lastPickedInstance!=null)  this._onLeaveInstance(this._lastPickedInstance);
						if(this._onEnterInstance && this._pickedInstance!=null) this._onEnterInstance(this._pickedInstance);
					}
					this._lastPickedInstance = inst;
					break;
				}
			}
		}
		else{
			this._pickedInstance = null;
			if(this._onHover){
				this._lastCursor = "default";
				if((/*!this._movingLight ||*/ !this._isMeasuring) && this._pickedSpot==null) document.getElementById(this.ui.canvas.id).style.cursor = "default";
				if(this._onLeaveInstance && this._lastPickedInstance!=null)  this._onLeaveInstance(this._lastPickedInstance);
				//if(this._onEnterInstance) this._onEnterInstance(this._pickedInstance);
				this.repaint();
			}
			this._lastPickedInstance = null;
		}
		this._lastInstanceID = ID;
	}
},

_measureRefresh : function (button, x, y, e) {
//	if(e.target.id!=this.ui.gl.canvas.id) return;
	if(this._isMeasuringDistance){
		this._pickpoint[0] = x;
		this._pickpoint[1] = y;
		var ppoint = this._drawScenePickingXYZ();
		if ((ppoint!=null)&&(this._measurementStage != 2)) {
			this._pointA = ppoint;
			this._measurementStage=2;
			this.repaint();
		}
		else if ((ppoint!=null)&&(this._measurementStage == 2)) {
			this._pointB = ppoint;
			this.measurement = SglVec3.length(SglVec3.sub(this._pointA, this._pointB));
			this._measurementStage=3;
			this.repaint();
			if(this._onEndMeasurement)
				this._onEndMeasurement(this.measurement, [this._pointA[0], this._pointA[1], this._pointA[2]], [this._pointB[0], this._pointB[1], this._pointB[2]]);
		}
	}
},

_startMeasurement  : function () {
	if (this._isMeasuringDistance) return;
	this._isMeasuring = this._isMeasuringDistance = true;
	this._measurementStage = 1; // 0=inactive 1=picking pointA 2=picking pointB 3=measurement ready
	this._pointA = [0.0, 0.0, 0.0];
	this._pointB = [0.0, 0.0, 0.0];
	this.measurement = 0.0;
	this.repaint();
},

_stopMeasurement  : function () {
	this._isMeasuringDistance = false;
	if (!this._isMeasuringPickpoint) this._isMeasuring = this._isMeasuringDistance;
	this._measurementStage = 0; // 0=inactive 1=picking pointA 2=picking pointB 3=measurement ready
	this._pointA = [0.0, 0.0, 0.0];
	this._pointB = [0.0, 0.0, 0.0];
	this.measurement = 0.0;
	this.repaint();
},

_pickpointRefresh : function (button, x, y, e) {
//	if(e.target.id!=this.ui.gl.canvas.id) return;
	if(this._isMeasuringPickpoint){
		this._pickpoint[0] = x;
		this._pickpoint[1] = y;
		var ppoint = this._drawScenePickingXYZ();
		if (ppoint!=null)
		{
			this._pickedPoint = ppoint;
			this._pickValid = true;
			if(this._onEndPickingPoint) this._onEndPickingPoint([this._pickedPoint[0], this._pickedPoint[1], this._pickedPoint[2]]);
			this.repaint();
		}
	}
},

_startPickPoint : function () {
	if (this._isMeasuringPickpoint) return;
	this._isMeasuring = this._isMeasuringPickpoint = true;
	this._pickValid = false;
	this._pickedPoint = [0.0, 0.0, 0.0];
	this.repaint();
},

_stopPickPoint : function () {
	this._isMeasuringPickpoint = false;
	if (!this._isMeasuringDistance) this._isMeasuring = this._isMeasuringPickpoint;
	this._pickValid = false;
	this._pickedPoint = [0.0, 0.0, 0.0];
	this.repaint();
},

//----------------------------------------------------------------------------------------
// DRAWING SUPPORT FUNCTIONS
//----------------------------------------------------------------------------------------
_setSceneCenterRadius : function () {
	var meshes    = this._scene.meshes;
	var instances = this._scene.modelInstances;
	this.sceneCenter = [0.0, 0.0, 0.0];
	this.sceneRadiusInv = 1.0;

	if(this._scene.space.centerMode == "explicit")
	{
		this.sceneCenter = this._scene.space.explicitCenter;
	}
	else if(this._scene.space.centerMode == "specific"  && this._scene.space.whichInstanceCenter != "")
	{
		var mesh = meshes[instances[this._scene.space.whichInstanceCenter].mesh];
		if((mesh)&&(mesh.renderable)){
			var instCenter = SglVec3.to4(mesh.renderable.datasetCenter,1);

			instCenter = SglMat4.mul4(mesh.transform.matrix, instCenter);
			instCenter = SglMat4.mul4(instances[this._scene.space.whichInstanceCenter].transform.matrix, instCenter);
			instCenter = SglMat4.mul4(this._scene.space.transform.matrix, instCenter);

			instCenter = SglVec4.to3(instCenter);

			this.sceneCenter = instCenter;
		}
	}
	else if(this._scene.space.centerMode == "scene")
	{
		var smin = SglVec3.maxNumber();
		var smax = SglVec3.minNumber();
		var imin = [0.0, 0.0, 0.0];
		var imax = [0.0, 0.0, 0.0];

		for (var inst in instances) {
			var mesh = meshes[instances[inst].mesh];
			if((mesh)&&(mesh.renderable)){
				var instCenter = SglVec3.to4(mesh.renderable.datasetCenter,1);
				instCenter = SglMat4.mul4(mesh.transform.matrix, instCenter);
				instCenter = SglMat4.mul4(instances[inst].transform.matrix, instCenter);
				instCenter = SglMat4.mul4(this._scene.space.transform.matrix, instCenter);
				instCenter = SglVec4.to3(instCenter);

				var radius = mesh.renderable.datasetRadius;
				var vector111 = SglVec3.one();
				vector111 = SglMat3.mul3(SglMat4.to33(mesh.transform.matrix), vector111);
				vector111 = SglMat3.mul3(SglMat4.to33(instances[inst].transform.matrix), vector111);
				vector111 = SglMat3.mul3(SglMat4.to33(this._scene.space.transform.matrix), vector111);
				var scalefactor = SglVec3.length(vector111) / SglVec3.length([1,1,1]);
				radius = radius*scalefactor;

				imin[0] = instCenter[0] - radius;
				imin[1] = instCenter[1] - radius;
				imin[2] = instCenter[2] - radius;
				imax[0] = instCenter[0] + radius;
				imax[1] = instCenter[1] + radius;
				imax[2] = instCenter[2] + radius;

				if(imin[0] < smin[0]) smin[0] = imin[0];
				if(imin[1] < smin[1]) smin[1] = imin[1];
				if(imin[2] < smin[2]) smin[2] = imin[2];
				if(imax[0] > smax[0]) smax[0] = imax[0];
				if(imax[1] > smax[1]) smax[1] = imax[1];
				if(imax[2] > smax[2]) smax[2] = imax[2];
			}
		}

		this.sceneCenter = [ (smax[0] + smin[0])/2.0, (smax[1] + smin[1])/2.0, (smax[2] + smin[2])/2.0 ];
	}
	else //if(this._scene.space.centerMode == "first")
	{
		for (var inst in instances) {
			var mesh = meshes[instances[inst].mesh];
			if((mesh)&&(mesh.renderable)){
				var instCenter = SglVec3.to4(mesh.renderable.datasetCenter,1);

				instCenter = SglMat4.mul4(mesh.transform.matrix, instCenter);
				instCenter = SglMat4.mul4(instances[inst].transform.matrix, instCenter);
				instCenter = SglMat4.mul4(this._scene.space.transform.matrix, instCenter);

				instCenter = SglVec4.to3(instCenter);

				this.sceneCenter = instCenter;
				break;
			}
		}
	}

	if(this._scene.space.radiusMode == "explicit")
	{
		this.sceneRadiusInv = 1.0 / this._scene.space.explicitRadius;
	}
	else if(this._scene.space.radiusMode == "specific" && this._scene.space.whichInstanceRadius != "")
	{
		var mesh = meshes[instances[this._scene.space.whichInstanceRadius].mesh];
		if((mesh)&&(mesh.renderable)){
			var radius = mesh.renderable.datasetRadius;
			var vector111 = SglVec3.one();

			vector111 = SglMat3.mul3(SglMat4.to33(mesh.transform.matrix), vector111);
			vector111 = SglMat3.mul3(SglMat4.to33(instances[this._scene.space.whichInstanceRadius].transform.matrix), vector111);
			vector111 = SglMat3.mul3(SglMat4.to33(this._scene.space.transform.matrix), vector111);

			var scalefactor = SglVec3.length(vector111) / SglVec3.length([1,1,1]);

			this.sceneRadiusInv = 1.0 / (radius*scalefactor);
		}
	}
	else if(this._scene.space.radiusMode == "scene")
	{
		var smin = SglVec3.maxNumber();
		var smax = SglVec3.minNumber();
		var imin = [0.0, 0.0, 0.0];
		var imax = [0.0, 0.0, 0.0];

		for (var inst in instances) {
			var mesh = meshes[instances[inst].mesh];
			if((mesh)&&(mesh.renderable)){
				var instCenter = SglVec3.to4(mesh.renderable.datasetCenter,1);
				instCenter = SglMat4.mul4(mesh.transform.matrix, instCenter);
				instCenter = SglMat4.mul4(instances[inst].transform.matrix, instCenter);
				instCenter = SglMat4.mul4(this._scene.space.transform.matrix, instCenter);
				instCenter = SglVec4.to3(instCenter);

				var radius = mesh.renderable.datasetRadius;
				var vector111 = SglVec3.one();
				vector111 = SglMat3.mul3(SglMat4.to33(mesh.transform.matrix), vector111);
				vector111 = SglMat3.mul3(SglMat4.to33(instances[inst].transform.matrix), vector111);
				vector111 = SglMat3.mul3(SglMat4.to33(this._scene.space.transform.matrix), vector111);

				var scalefactor = SglVec3.length(vector111) / SglVec3.length([1,1,1]);
				radius = radius*scalefactor;

				imin[0] = instCenter[0] - radius;
				imin[1] = instCenter[1] - radius;
				imin[2] = instCenter[2] - radius;
				imax[0] = instCenter[0] + radius;
				imax[1] = instCenter[1] + radius;
				imax[2] = instCenter[2] + radius;

				if(imin[0] < smin[0]) smin[0] = imin[0];
				if(imin[1] < smin[1]) smin[1] = imin[1];
				if(imin[2] < smin[2]) smin[2] = imin[2];
				if(imax[0] > smax[0]) smax[0] = imax[0];
				if(imax[1] > smax[1]) smax[1] = imax[1];
				if(imax[2] > smax[2]) smax[2] = imax[2];
			}
		}
		var scenter = [ (smax[0] + smin[0])/2.0, (smax[1] + smin[1])/2.0, (smax[2] + smin[2])/2.0 ]
		this.sceneRadiusInv = 1.0 / SglVec3.length(SglVec3.sub(smax, scenter));
	}
	else //if(this._scene.space.radiusMode == "first")
	{
		for (var inst in instances) {
			var mesh = meshes[instances[inst].mesh];
			if((mesh)&&(mesh.renderable)){
				var radius = mesh.renderable.datasetRadius;
				var vector111 = SglVec3.one();

				vector111 = SglMat3.mul3(SglMat4.to33(mesh.transform.matrix), vector111);
				vector111 = SglMat3.mul3(SglMat4.to33(instances[inst].transform.matrix), vector111);
				vector111 = SglMat3.mul3(SglMat4.to33(this._scene.space.transform.matrix), vector111);

				var scalefactor = SglVec3.length(vector111) / SglVec3.length([1,1,1]);

				this.sceneRadiusInv = 1.0 / (radius*scalefactor);
				break;
			}
		}
	}
},

_destroyPickFramebuffer : function () {
	if (this.pickFramebuffer) {
		this.pickFramebuffer.destroy();
		this.pickFramebuffer = null;
	}
	if (this.pickColorTexture) {
		this.pickColorTexture.destroy();
		this.pickColorTexture = null;
	}
	if (this.pickDepthRenderbuffer) {
		this.pickDepthRenderbuffer.destroy();
		this.pickDepthRenderbuffer = null;
	}
},

_createPickFramebuffer : function (width, height) {
	if (this.pickFramebuffer && (this.pickFramebuffer.width == width) && (this.pickFramebuffer.height == height))
		return;
	else
		this._destroyPickFramebuffer();

	var gl = this.ui.gl;

	this.pickColorTexture = new SglTexture2D(gl, {
		internalFormat : gl.RGBA,
		width          : width,
		height         : height
	});

	this.pickDepthRenderbuffer = new SglRenderbuffer(gl, {
		internalFormat : gl.DEPTH_COMPONENT16,
		width          : width,
		height         : height
	});

	this.pickFramebuffer = new SglFramebuffer(gl, {
		color : this.pickColorTexture,
		depth : this.pickDepthRenderbuffer,
		autoViewport : true
	});
},

_setupDraw : function () {
	var width    = this.ui.width;
	var height   = this.ui.height;
	var xform    = this.xform;
	var space    = this._scene.space;

	this.ui.gl.viewport(0, 0, width, height);

	var FOV   = space.cameraFOV;
	var nearP = space.cameraNearFar[0];
	var farP  = space.cameraNearFar[1];

	// getting scale/center for scene
	this._setSceneCenterRadius();

	xform.projection.loadIdentity();

	if(space.cameraType == "orthographic")
	{
		//default camera distance in orthographic view is "as large as scene size"
		// then, if the trackball is able to provide a better value, we use it
		var cDistance = 1.0;
		if(typeof this.trackball.distance != "undefined")
			cDistance = this.trackball.distance;
		var a = cDistance * SpiderGL.Math.tan(sglDegToRad(FOV) / 2);
		var b = a * width/height;

		xform.projection.ortho([-b, -a, nearP], [b, a, farP]);
	}
	else
	{
		xform.projection.perspective(sglDegToRad(FOV), width/height, nearP, farP);
	}

	xform.view.loadIdentity();
	xform.view.lookAt([0.0, 0.0, 0.0], [0.0, 0.0, -1.0], [0.0, 1.0, 0.0]);
	this.viewMatrix = xform.viewMatrix;

	xform.model.loadIdentity();
	xform.model.multiply(this.trackball.matrix);

	// scale to unit box + recenter
	xform.model.scale([this.sceneRadiusInv, this.sceneRadiusInv, this.sceneRadiusInv]);
	xform.model.translate(SglVec3.neg(this.sceneCenter));

	Nexus.beginFrame(this.ui.gl, this.ui.framesPerSecond);
},

_ID2Color : function (ID) {
	var intID = ID | 0;
	var IDr = intID % 10;
	var IDg = ((intID-IDr) / 10) % 10;
	var IDb = ((((intID-IDr) / 10) - IDg) / 10) % 10;

	var colorID = [IDr * 0.1, IDg * 0.1, IDb * 0.1, 1.0];
	return colorID;
},

_Color2ID : function (color) {
	var IDr =  Math.round(((color[0])/255.0) / 0.1)        | 0;
	var IDg = (Math.round(((color[1])/255.0) / 0.1) * 10)  | 0;
	var IDb = (Math.round(((color[2])/255.0) / 0.1) * 100) | 0;

	var ID = (IDr + IDg + IDb) | 0;
	return ID;
},

_onPlyLoaded : function (req, thatmesh, gl) {
	var plyData = req.buffer;
	var modelDescriptor = importPly(plyData);
	var TMR = thatmesh.renderable = new SglModel(gl, modelDescriptor);

	TMR.renderMode  = modelDescriptor.extra.renderMode;
	TMR.boundingBox = modelDescriptor.extra.boundingBox;

	// center and radius
	TMR.datasetCenter = [0.0, 0.0, 0.0];
	TMR.datasetRadius = 1.0;

	TMR.datasetCenter[0] = (TMR.boundingBox.max[0] + TMR.boundingBox.min[0]) / 2.0;
	TMR.datasetCenter[1] = (TMR.boundingBox.max[1] + TMR.boundingBox.min[1]) / 2.0;
	TMR.datasetCenter[2] = (TMR.boundingBox.max[2] + TMR.boundingBox.min[2]) / 2.0;

	TMR.datasetRadius = Math.sqrt( Math.pow((TMR.boundingBox.max[0] - TMR.boundingBox.min[0]),2) +
								   Math.pow((TMR.boundingBox.max[1] - TMR.boundingBox.min[1]),2) +
								   Math.pow((TMR.boundingBox.max[2] - TMR.boundingBox.min[2]),2) ) / 2.0;

	// texture
	if(modelDescriptor.extra.textureUrl) {
		var that = this;
		var texUrl = "";
		var tmpUrl = req._url.split("/");
		for (var j=0; j<tmpUrl.length-1; ++j) texUrl += tmpUrl[j].concat("/");
		texUrl = texUrl.concat(modelDescriptor.extra.textureUrl);
		TMR.texture = new SglTexture2D(gl, {
			url       : texUrl,
			onError   : function () { TMR.hasTexture = false; that._onMeshReady(); },
			onSuccess : function () { TMR.hasTexture = true; that._onMeshReady(); }
		});
	}
	else this._onMeshReady();
},

//----------------------------------------------------------------------------------------
// DRAWING SCENE FUNCTIONS
//----------------------------------------------------------------------------------------
_drawScene : function () {
	var gl       = this.ui.gl;
	var width    = this.ui.width;
	var height   = this.ui.height;
	var xform    = this.xform;
	var renderer = this.renderer;
	var CurrFacesProgram    = this.facesProgram;
	var CurrPointsProgram   = this.pointsProgram;
	var CurrFacesTechnique  = this.faceTechnique;
	var CurrPointsTechnique = this.pointTechnique;
	var CCProgram          = this.colorShadedProgram;
	var CCTechnique        = this.colorShadedTechnique;
	var lineTechnique      = this.simpleLineTechnique;
	var entitiesTechnique  = this.multiLinesPointsTechnique;
	var meshes    = this._scene.meshes;
	var instances = this._scene.modelInstances;
	var entities  = this._scene.entities;
	var spots     = this._scene.spots;
	var space     = this._scene.space;
	var config    = this._scene.config;
	var bkg       = this._scene.background.color;

	// basic setup, matrices for projection & view
	this._setupDraw();

	// clear buffer
	gl.clearColor(bkg[0], bkg[1], bkg[2], bkg[3]);
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
	gl.enable(gl.DEPTH_TEST);

	// draw non-transparent geometries
	for (var inst in instances) {
		var instance = instances[inst];
		var mesh     = meshes[instance.mesh];
		if (!mesh) continue;
		var renderable = mesh.renderable;
		if (!renderable) continue;
		if (!instance.visible) continue;
		if (instance.useTransparency) continue;

		// GLstate setup
		xform.model.push();
		xform.model.multiply(space.transform.matrix);
		xform.model.multiply(instance.transform.matrix);
		xform.model.multiply(mesh.transform.matrix);

		var modelMatrix = SglMat4.identity();
		modelMatrix = SglMat4.mul(modelMatrix, space.transform.matrix);
		modelMatrix = SglMat4.mul(modelMatrix, instance.transform.matrix);
		modelMatrix = SglMat4.mul(modelMatrix, mesh.transform.matrix);
		var thisClipAxis = instance.clippable?this._clipAxis:[0.0, 0.0, 0.0];
		var thisClipPlane = instance.clippable?this._clipPlane:[0.0, 0.0, 0.0, 0.0];
		var thisClipBordersize = config.showClippingBorder?config.clippingBorderSize:0.0;

		var uniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uViewSpaceNormalMatrix"     : xform.viewSpaceNormalMatrix,
			"uModelMatrix"               : modelMatrix,
			"uWorldViewMatrix"           : xform.modelViewMatrix,
			"uViewSpaceLightDirection"   : this._lightDirection,
			"uPointSize"                 : config.pointSize,
			"uAlpha"                     : 1.0,
			"uUseSolidColor"             : instance.useSolidColor,
			"uUseLighting"               : space.sceneLighting && instance.useLighting,
			"uBackFaceColor"             : instance.backfaceColor,
			"uSpecularColor"             : instance.specularColor,
			"uSolidColor"                : instance.color,
			"uClipPoint"                 : this._clipPoint,
			"uClipAxis"                  : thisClipAxis,
			"uClipPlane"                 : thisClipPlane,
			"uClipColor"                 : config.clippingBorderColor,
			"uClipColorSize"             : thisClipBordersize
		};

		if(mesh.mType === "nexus") {
			if (!renderable.isReady) continue;

			var nexus = renderable;
			nexus.updateView([0, 0, width, height], xform.projectionMatrix, xform.modelViewMatrix);

			var program;
			if(instance.rendermode=="FILL")
				program = CurrFacesProgram;
			else
				program = CurrPointsProgram;
			program.setUniforms(uniforms);
			program.bind();
				nexus.setPrimitiveMode(instance.rendermode);
				nexus.render();
			program.unbind();
		}
		else if(mesh.mType === "ply") {
			var technique;
			if(instance.rendermode=="FILL")
				technique = CurrFacesTechnique;
			else
				technique = CurrPointsTechnique;
			renderer.begin();
				renderer.setTechnique(technique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode(instance.rendermode);
				renderer.setGlobals(uniforms);
				renderer.setModel(renderable);
				renderer.setTexture(0, renderable.texture);
				renderer.renderModel();
			renderer.end();
		}

		// GLstate cleanup
		xform.model.pop();
	}

	// draw transparent geometries
	for (var inst in instances) {
		var instance = instances[inst];
		var mesh     = meshes[instance.mesh];
		if (!mesh) continue;
		var renderable = mesh.renderable;
		if (!renderable) continue;
		if (!instance.visible) continue;
		if (!instance.useTransparency) continue;

		// GLstate setup
		gl.depthMask(false);
		gl.enable(gl.BLEND);
		gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

		xform.model.push();
		xform.model.multiply(space.transform.matrix);
		xform.model.multiply(instance.transform.matrix);
		xform.model.multiply(mesh.transform.matrix);

		var modelMatrix = SglMat4.identity();
		modelMatrix = SglMat4.mul(modelMatrix, space.transform.matrix);
		modelMatrix = SglMat4.mul(modelMatrix, instance.transform.matrix);
		modelMatrix = SglMat4.mul(modelMatrix, mesh.transform.matrix);
		var thisClipAxis = instance.clippable?this._clipAxis:[0.0, 0.0, 0.0];
		var thisClipPlane = instance.clippable?this._clipPlane:[0.0, 0.0, 0.0, 0.0];
		var thisClipBordersize = config.showClippingBorder?config.clippingBorderSize:0.0;

		var uniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uViewSpaceNormalMatrix"     : xform.viewSpaceNormalMatrix,
			"uModelMatrix"               : modelMatrix,
			"uWorldViewMatrix"           : xform.modelViewMatrix,
			"uViewSpaceLightDirection"   : this._lightDirection,
			"uPointSize"                 : config.pointSize,
			"uAlpha"                     : instance.alpha,
			"uUseSolidColor"             : instance.useSolidColor,
			"uUseLighting"               : space.sceneLighting && instance.useLighting,
			"uBackFaceColor"             : instance.backfaceColor,
			"uSpecularColor"             : instance.specularColor,
			"uSolidColor"                : instance.color,
			"uClipPoint"                 : [0.0, 0.0, 0.0],
			"uClipPoint"                 : this._clipPoint,
			"uClipAxis"                  : thisClipAxis,
			"uClipPlane"                 : thisClipPlane,
			"uClipColor"                 : config.clippingBorderColor,
			"uClipColorSize"             : thisClipBordersize
		};

		if(mesh.mType === "nexus") {
			if (!renderable.isReady) continue;

			var nexus = renderable;
			nexus.updateView([0, 0, width, height], xform.projectionMatrix, xform.modelViewMatrix);

			var program;
			if(instance.rendermode=="FILL")
				program = CurrFacesProgram;
			else
				program = CurrPointsProgram;
			program.setUniforms(uniforms);
			program.bind();
				nexus.setPrimitiveMode(instance.rendermode);
				nexus.render();
			program.unbind();
		}
		else if(mesh.mType === "ply") {
			var technique;
			if(instance.rendermode=="FILL")
				technique = CurrFacesTechnique;
			else
				technique = CurrPointsTechnique;
			renderer.begin();
				renderer.setTechnique(technique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode(instance.rendermode);
				renderer.setGlobals(uniforms);
				renderer.setModel(renderable);
				renderer.setTexture(0, renderable.texture);
				renderer.renderModel();
			renderer.end();
		}

		// GLstate cleanup
		gl.disable(gl.BLEND);
		gl.depthMask(true);

		xform.model.pop();
	}

	// draw picked point (if valid)
	if (this._pickValid) {
		// GLstate setup
		gl.depthFunc(gl.LESS);
		xform.model.push();

		var lineUniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uLineColor"                 : [config.pickedpointColor[0], config.pickedpointColor[1], config.pickedpointColor[2], 1.0],
			"uPointA"                    : this._pickedPoint,
			"uPointB"                    : this._pickedPoint
		};

		//drawing points
		renderer.begin();
			renderer.setTechnique(lineTechnique);
			renderer.setDefaultGlobals();
			renderer.setGlobals(lineUniforms);
			renderer.setPrimitiveMode("POINT");
			renderer.setModel(this.simpleLineModel);
			renderer.renderModel();
		renderer.end();

		lineUniforms["uLineColor"] = [config.pickedpointColor[0] * 0.4, config.pickedpointColor[1] * 0.5, config.pickedpointColor[2] * 0.6, 0.5];

		gl.depthFunc(gl.GREATER);
		gl.depthMask(false);
		gl.enable(gl.BLEND);
		gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

		//drawing points and line
		renderer.begin();
			renderer.setTechnique(lineTechnique);
			renderer.setDefaultGlobals();
			renderer.setGlobals(lineUniforms);
			renderer.setPrimitiveMode("POINT");
			renderer.setModel(this.simpleLineModel);
			renderer.renderModel();
		renderer.end();

		// GLstate cleanup
		gl.disable(gl.BLEND);
		gl.depthMask(true);
		gl.depthFunc(gl.LESS);
		xform.model.pop();
	}

	// draw measurement line (if any)
	if (this._measurementStage >= 2) {// 0=inactive 1=picking pointA 2=picking pointB 3=measurement ready
		// GLstate setup
		gl.depthFunc(gl.LESS);
		xform.model.push();

		var lineUniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uLineColor"                 : [config.measurementColor[0], config.measurementColor[1], config.measurementColor[2], 1.0],
			"uPointA"                    : this._pointA,
			"uPointB"                    : (this._measurementStage==2)?this._pointA:this._pointB,
		};

		//drawing points and line
		renderer.begin();
			renderer.setTechnique(lineTechnique);
			renderer.setDefaultGlobals();
			renderer.setGlobals(lineUniforms);
			renderer.setPrimitiveMode("LINE");
			renderer.setModel(this.simpleLineModel);
			renderer.renderModel();
			renderer.setPrimitiveMode("POINT");
			renderer.setModel(this.simpleLineModel);
			renderer.renderModel();
		renderer.end();

		lineUniforms["uLineColor"] = [config.measurementColor[0] * 0.4, config.measurementColor[1] * 0.5, config.measurementColor[2] * 0.6, 0.5];

		gl.depthFunc(gl.GREATER);
		gl.depthMask(false);
		gl.enable(gl.BLEND);
		gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

		//drawing points and line
		renderer.begin();
			renderer.setTechnique(lineTechnique);
			renderer.setDefaultGlobals();
			renderer.setGlobals(lineUniforms);
			renderer.setPrimitiveMode("LINE");
			renderer.setModel(this.simpleLineModel);
			renderer.renderModel();
			renderer.setPrimitiveMode("POINT");
			renderer.setModel(this.simpleLineModel);
			renderer.renderModel();
		renderer.end();

		// GLstate cleanup
		gl.disable(gl.BLEND);
		gl.depthMask(true);
		gl.depthFunc(gl.LESS);
		xform.model.pop();
	}

	// draw entities
	for (var ent in entities) {
		var entity = entities[ent];
		if (!entity.visible) continue;
		if (!entity.renderable) continue;
			
		xform.model.push();
		xform.model.multiply(space.transform.matrix);
		xform.model.multiply(entity.transform.matrix);
		
		var entityUniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uPointSize"                 : entity.pointSize,
			"uColorID"                   : entity.color,
			"uZOff"                      : entity.zOff,
		};

		if(entity.useTransparency)
		{
			gl.depthMask(false);
			gl.enable(gl.BLEND);
			gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
		}
		else
			entityUniforms["uColorID"][3] = 1.0;	// if no transparency, use full alpha, otherwise the entity cancels the background

		//drawing entity
		renderer.begin();
			renderer.setTechnique(entitiesTechnique);
			if (entity.type == "points")
				renderer.setPrimitiveMode("POINT");
			else if (entity.type == "lines" || entity.type == "lineStrip" || entity.type == "lineLoop")
				renderer.setPrimitiveMode("LINE");
			else if (entity.type == "triangles" || entity.type == "triangleStrip" || entity.type == "triangleFan")
				renderer.setPrimitiveMode("FILL");
			renderer.setDefaultGlobals();
			renderer.setGlobals(entityUniforms);
			renderer.setModel(entity.renderable);
			renderer.renderModel();
		renderer.end();

		if(entity.useSeethrough)	// draw seethrough
		{
			gl.depthFunc(gl.GREATER);
			gl.depthMask(false);
			gl.enable(gl.BLEND);
			gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
	
			entityUniforms["uColorID"] = [entity.color[0] * 0.4, entity.color[1] * 0.4, entity.color[2] * 0.4,  entity.color[3] * 0.4];	// seethrough color
	
			renderer.begin();
				renderer.setTechnique(entitiesTechnique);
				if (entity.type == "points")
					renderer.setPrimitiveMode("POINT");
				else if (entity.type == "lines" || entity.type == "lineStrip" || entity.type == "lineLoop")
					renderer.setPrimitiveMode("LINE");
				else if (entity.type == "triangles" || entity.type == "triangleStrip" || entity.type == "triangleFan")
					renderer.setPrimitiveMode("FILL");
				renderer.setDefaultGlobals();
				renderer.setGlobals(entityUniforms);
				renderer.setModel(entity.renderable);
				renderer.renderModel();
			renderer.end();
	
			// GLstate cleanup
			gl.disable(gl.BLEND);
			gl.depthMask(true);
			gl.depthFunc(gl.LESS);
		}
		
		if(entity.useTransparency)
		{
			gl.depthMask(true);
			gl.disable(gl.BLEND);
		}
		xform.model.pop();
	}

	// draw transparent spot geometries
	for (var spt in spots) {
		var spot = spots[spt];
		var mesh = meshes[spot.mesh];
		if (!mesh) continue;
		var renderable = mesh.renderable;
		if (!renderable) continue;
		if (!spot.visible) continue;

		// GLstate setup
		gl.depthMask(false);
		gl.enable(gl.BLEND);
		gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

		xform.model.push();
		xform.model.multiply(space.transform.matrix);
		xform.model.multiply(spot.transform.matrix);
		xform.model.multiply(mesh.transform.matrix);

		var uniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uViewSpaceNormalMatrix"     : xform.viewSpaceNormalMatrix,
			"uViewSpaceLightDirection"   : this._lightDirection,
			"uPointSize"                 : config.pointSize,
			"uColorID"                   : [spot.color[0], spot.color[1], spot.color[2], (spt == this._pickedSpot)?spot.alphaHigh:spot.alpha]
		}

		if(mesh.mType === "nexus") {
			if (!renderable.isReady) continue;
			var nexus = renderable;
			nexus.updateView([0, 0, width, height], xform.projectionMatrix, xform.modelViewMatrix);

			var program = CCProgram;
			program.setUniforms(uniforms);
			program.bind();
				nexus.setPrimitiveMode(spot.rendermode);
				nexus.render();
			program.unbind();
		}
		else if(mesh.mType === "ply") {
			renderer.begin();
				renderer.setTechnique(CCTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode(spot.rendermode);
				renderer.setGlobals(uniforms);
				renderer.setModel(renderable);
				if(spot.useStencil)
				{
					gl.clear(gl.STENCIL_BUFFER_BIT); //reset stencil
					//first pass
					gl.colorMask(false, false, false, false);
					gl.enable(gl.STENCIL_TEST);
					gl.stencilFunc(gl.ALWAYS, 0, 255);
					gl.stencilOp(gl.KEEP, gl.KEEP, gl.INVERT);
					renderer.renderModel();

					//second pass
					gl.colorMask(true, true, true, true);
					gl.stencilOp(gl.KEEP, gl.KEEP, gl.INVERT); // Don't change the stencil buffer...
					gl.stencilFunc(gl.EQUAL, 1, 0x01); // The stencil buffer contains the shadow values...
					renderer.renderModel();

					gl.disable(gl.STENCIL_TEST);
				}
				else
				{
					renderer.renderModel();
				}
			renderer.end();
		}

		// GLstate cleanup
		gl.disable(gl.BLEND);
		gl.depthMask(true);
		xform.model.pop();
	}

	// draw clipping plane (if any)
	if(config.showClippingPlanes)
	{
		// GLstate setup
		gl.depthMask(false);
		gl.enable(gl.BLEND);
		gl.blendFunc(gl.SRC_ALPHA, gl.ONE);

		if(SglVec3.length([this._clipPlane[0], this._clipPlane[1], this._clipPlane[2]]) > 0.0) {
			var planepoint = [0.0, 0.0, 0.0];

			var k = SglVec3.dot(this._sceneBboxCenter, [this._clipPlane[0], this._clipPlane[1], this._clipPlane[2]]) + this._clipPlane[3];
			planepoint[0] = this._sceneBboxCenter[0] - (this._clipPlane[0] * k);
			planepoint[1] = this._sceneBboxCenter[1] - (this._clipPlane[1] * k);
			planepoint[2] = this._sceneBboxCenter[2] - (this._clipPlane[2] * k);

			var rotm = SglMat4.identity();
			rotm = SglMat4.mul(rotm, SglMat4.rotationAngleAxis(sglDegToRad(this._clipPlaneAH), [0.0, -1.0, 0.0]));
			rotm = SglMat4.mul(rotm, SglMat4.rotationAngleAxis(sglDegToRad(this._clipPlaneAV), [0.0, 0.0, 1.0]));

			var psize = this._sceneBboxDiag;

			xform.model.push();
			xform.model.translate(planepoint);
			xform.model.multiply(rotm);
			xform.model.scale([psize, psize, psize]);

			var QuadUniforms = {
				"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
				"uViewSpaceNormalMatrix"     : xform.viewSpaceNormalMatrix,
				"uViewSpaceLightDirection"   : this._lightDirection,
				"uColorID"                   : [1.0, 0.0, 1.0, 0.25]
			};

			renderer.begin();
				renderer.setTechnique(CCTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode("FILL");
				renderer.setGlobals(QuadUniforms);
				renderer.setModel(this.simpleQuadXModel);
				renderer.renderModel();
			renderer.end();

			xform.model.pop();
		}

		if(this._clipAxis[0] != 0.0) {
			xform.model.push();
			xform.model.translate([this._clipPoint[0], this._sceneBboxCenter[1], this._sceneBboxCenter[2]]);
			xform.model.scale([(this._sceneBboxMax[0] - this._sceneBboxMin[0]),
							   (this._sceneBboxMax[1] - this._sceneBboxMin[1]),
							   (this._sceneBboxMax[2] - this._sceneBboxMin[2])]);

			var QuadUniforms = {
				"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
				"uViewSpaceNormalMatrix"     : xform.viewSpaceNormalMatrix,
				"uViewSpaceLightDirection"   : this._lightDirection,
				"uColorID"                   : [1.0, 0.0, 0.0, 0.25]
			};

			renderer.begin();
				renderer.setTechnique(CCTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode("FILL");
				renderer.setGlobals(QuadUniforms);
				renderer.setModel(this.simpleQuadXModel);
				renderer.renderModel();
			renderer.end();

			xform.model.pop();
		}

		if(this._clipAxis[1] != 0.0) {
			xform.model.push();
			xform.model.translate([this._sceneBboxCenter[0], this._clipPoint[1], this._sceneBboxCenter[2]]);
			xform.model.scale([(this._sceneBboxMax[0] - this._sceneBboxMin[0]),
							   (this._sceneBboxMax[1] - this._sceneBboxMin[1]),
							   (this._sceneBboxMax[2] - this._sceneBboxMin[2])]);

			var QuadUniforms = {
				"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
				"uViewSpaceNormalMatrix"     : xform.viewSpaceNormalMatrix,
				"uViewSpaceLightDirection"   : this._lightDirection,
				"uColorID"                   : [0.0, 1.0, 0.0, 0.25]
			};

			renderer.begin();
				renderer.setTechnique(CCTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode("FILL");
				renderer.setGlobals(QuadUniforms);
				renderer.setModel(this.simpleQuadYModel);
				renderer.renderModel();
			renderer.end();

			xform.model.pop();
		}

		if(this._clipAxis[2] != 0.0) {
			xform.model.push();
			xform.model.translate([this._sceneBboxCenter[0], this._sceneBboxCenter[1], this._clipPoint[2]]);
			xform.model.scale([(this._sceneBboxMax[0] - this._sceneBboxMin[0]),
							   (this._sceneBboxMax[1] - this._sceneBboxMin[1]),
							   (this._sceneBboxMax[2] - this._sceneBboxMin[2])]);

			var QuadUniforms = {
				"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
				"uViewSpaceNormalMatrix"     : xform.viewSpaceNormalMatrix,
				"uViewSpaceLightDirection"   : this._lightDirection,
				"uColorID"                   : [0.0, 0.0, 1.0, 0.25]
			};

			renderer.begin();
				renderer.setTechnique(CCTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode("FILL");
				renderer.setGlobals(QuadUniforms);
				renderer.setModel(this.simpleQuadZModel);
				renderer.renderModel();
			renderer.end();

			xform.model.pop();
		}

		// GLstate cleanup
		gl.disable(gl.BLEND);
		gl.depthMask(true);
	}
	Nexus.endFrame(this.ui.gl);

	// saving image, if necessary
	if(this.isCapturingScreenshot){
	    this.isCapturingScreenshot = false;
		this.screenshotData = this.ui._canvas.toDataURL('image/png',1).replace("image/png", "image/octet-stream");
		if(this._scene.config.autoSaveScreenshot)
		{
			var currentdate = new Date();
			var fName = this._scene.config.screenshotBaseName;
			if(this._scene.config.screenshotTime) fName += "_" + String(currentdate.getHours()).padStart(2, '0') + String(currentdate.getMinutes()).padStart(2, '0') + String(currentdate.getSeconds()).padStart(2, '0');
			fName += ".png";
			
			this._saveImage(fName);
		}
	}
},

_saveImage : function(fName){
	if(this.ui._canvas.msToBlob) // IE or EDGEhtml
	{
		console.error("IE and EDGEhtml cannot save images");
		var blob = this.ui._canvas.msToBlob();
		window.navigator.msSaveBlob(blob, fName);
	}
	else // every other browser
	{
		var a  = document.createElement('a');
		a.href = this.screenshotData;
		a.download = fName;
		a.target="_blank";
		a.click();
	}
},

_drawScenePickingXYZ : function () {
	var gl       = this.ui.gl;
	var width    = this.ui.width;
	var height   = this.ui.height;
	var xform    = this.xform;
	var renderer = this.renderer;
	var CurrProgram   = this.utilsProgram;
	var CurrTechnique = this.utilsTechnique;
	var meshes    = this._scene.meshes;
	var instances = this._scene.modelInstances;
	var space = this._scene.space;
	var pixel = new Uint8Array(4);

	// picking FB
	this._createPickFramebuffer(width, height);

	// basic setup, matrices for projection & view
	this._setupDraw();

	// clear buffer
	this.pickFramebuffer.bind();
	gl.clearColor(0.0, 0.0, 0.0, 0.0);
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
	gl.enable(gl.DEPTH_TEST);
	this.pickFramebuffer.unbind();

	for (var inst in instances) {
		var instance = instances[inst];
		var mesh     = meshes[instance.mesh];
		if (!mesh) continue;
		var renderable = mesh.renderable;
		if (!renderable) continue;
		if (!instance.visible) continue;
		if (!instance.measurable) continue;

		// GLstate setup
		xform.model.push();
		xform.model.multiply(space.transform.matrix);
		xform.model.multiply(instance.transform.matrix);
		xform.model.multiply(mesh.transform.matrix);

		var modelMatrix = SglMat4.identity();
		modelMatrix = SglMat4.mul(modelMatrix, space.transform.matrix);
		modelMatrix = SglMat4.mul(modelMatrix, instance.transform.matrix);
		modelMatrix = SglMat4.mul(modelMatrix, mesh.transform.matrix);
		var thisClipAxis = instance.clippable?this._clipAxis:[0.0, 0.0, 0.0];
		var thisClipPlane = instance.clippable?this._clipPlane:[0.0, 0.0, 0.0, 0.0];

		var uniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uModelMatrix"               : modelMatrix,
			"uBackFaceColor"             : instance.backfaceColor,
			"uClipPoint"                 : this._clipPoint,
			"uClipAxis"                  : thisClipAxis,
			"uClipPlane"                 : thisClipPlane,
			"uPointSize"                 : this._scene.config.pointSize,
			"uMode"                      : 1.0
		};

		if(mesh.mType === "nexus") {
			if (!renderable.isReady) continue;
			var nexus = renderable;
			nexus.updateView([0, 0, width, height], xform.projectionMatrix, xform.modelViewMatrix);

			this.pickFramebuffer.bind();

			var program = CurrProgram;
			program.setUniforms(uniforms);
			program.bind();
				nexus.setPrimitiveMode(instance.rendermode);
				nexus.render();
			program.unbind();

			this.pickFramebuffer.unbind();
		}
		else if(mesh.mType === "ply") {
			renderer.begin();
				renderer.setFramebuffer(this.pickFramebuffer);
				renderer.setTechnique(CurrTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode(instance.rendermode);
				renderer.setGlobals(uniforms);
				renderer.setModel(renderable);
				renderer.renderModel();
				renderer.setFramebuffer(null);
			renderer.end();
		}

		// GLstate cleanup
		xform.model.pop();
	}

	this.pickFramebuffer.readPixels(pixel, {
		x      : this._pickpoint[0],
		y      : this._pickpoint[1],
		width  : 1,
		height : 1,
		format : gl.RGBA,
		type   : gl.UNSIGNED_BYTE
	});

	var rr = pixel[0] / 255.0;
	var gg = pixel[1] / 255.0;
	var bb = pixel[2] / 255.0;
	var aa = pixel[3] / 255.0;
	var depth = aa  + ( bb / (255.0)) + ( gg / (255.0*255.0)) + ( rr / (255.0*255.0*255.0));

	var ppointc;

	if((rr==0.0) && (gg==0.0) && (bb==0.0))
		return(null);
	else
		ppointc = xform.unproject([this._pickpoint[0]/width,this._pickpoint[1]/height,depth]);

	return([ppointc[0], ppointc[1], ppointc[2]]);
},

_drawScenePickingInstances : function () {
	var gl       = this.ui.gl;
	var width    = this.ui.width;
	var height   = this.ui.height;
	var xform    = this.xform;
	var renderer = this.renderer;
	var CurrProgram   = this.utilsProgram;
	var CurrTechnique = this.utilsTechnique;
	var meshes    = this._scene.meshes;
	var instances = this._scene.modelInstances;
	var space = this._scene.space;
	var pixel = new Uint8Array(4);

	// picking FB
	this._createPickFramebuffer(width, height);

	// basic setup, matrices for projection & view
	this._setupDraw();

	// clear buffer
	this.pickFramebuffer.bind();
	gl.clearColor(0.0, 0.0, 0.0, 0.0);
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
	gl.enable(gl.DEPTH_TEST);
	this.pickFramebuffer.unbind();

	for (var inst in instances) {
		var instance = instances[inst];
		var mesh     = meshes[instance.mesh];
		if (!mesh) continue;
		var renderable = mesh.renderable;
		if (!renderable) continue;
		if (!instance.visible) continue;

		// GLstate setup
		xform.model.push();
		xform.model.multiply(space.transform.matrix);
		xform.model.multiply(instance.transform.matrix);
		xform.model.multiply(mesh.transform.matrix);

		var modelMatrix = SglMat4.identity();
		modelMatrix = SglMat4.mul(modelMatrix, space.transform.matrix);
		modelMatrix = SglMat4.mul(modelMatrix, instance.transform.matrix);
		modelMatrix = SglMat4.mul(modelMatrix, mesh.transform.matrix);
		var thisClipAxis = instance.clippable?this._clipAxis:[0.0, 0.0, 0.0];
		var thisClipPlane = instance.clippable?this._clipPlane:[0.0, 0.0, 0.0, 0.0];

		var colorID = this._ID2Color(instance.ID);
		var uniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uModelMatrix"               : modelMatrix,
			"uClipPoint"                 : this._clipPoint,
			"uClipAxis"                  : thisClipAxis,
			"uClipPlane"                 : thisClipPlane,
			"uPointSize"                 : this._scene.config.pointSize,
			"uColorID"                   : colorID,
			"uMode"                      : 2.0
		};

		if(mesh.mType === "nexus") {
			if (!renderable.isReady) continue;
			var nexus = renderable;
			nexus.updateView([0, 0, width, height], xform.projectionMatrix, xform.modelViewMatrix);

			this.pickFramebuffer.bind();

			var program = CurrProgram;
			program.setUniforms(uniforms);
			program.bind();
				nexus.setPrimitiveMode(instance.rendermode);
				nexus.render();
			program.unbind();

			this.pickFramebuffer.unbind();
		}
		else if(mesh.mType === "ply") {
			renderer.begin();
				renderer.setFramebuffer(this.pickFramebuffer);
				renderer.setTechnique(CurrTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode(instance.rendermode);
				renderer.setGlobals(uniforms);
				renderer.setModel(renderable);
				renderer.renderModel();
				renderer.setFramebuffer(null);
			renderer.end();
		}

		// GLstate cleanup
		xform.model.pop();
	}

	this.pickFramebuffer.readPixels(pixel, {
		x      : this._pickpoint[0],
		y      : this._pickpoint[1],
		width  : 1,
		height : 1,
		format : gl.RGBA,
		type   : gl.UNSIGNED_BYTE
	});

	return pixel;
},

_drawScenePickingSpots : function () {
	var gl       = this.ui.gl;
	var width    = this.ui.width;
	var height   = this.ui.height;
	var xform    = this.xform;
	var renderer = this.renderer;
	var CurrProgram   = this.utilsProgram;
	var CurrTechnique = this.utilsTechnique;
	var meshes    = this._scene.meshes;
	var spots = this._scene.spots;
	var instances = this._scene.modelInstances;
	var space = this._scene.space;
	var pixel = new Uint8Array(4);

	// picking FB
	this._createPickFramebuffer(width, height);

	// basic setup, matrices for projection & view
	this._setupDraw();

	// clear buffer
	this.pickFramebuffer.bind();
	gl.clearColor(0.0, 0.0, 0.0, 0.0);
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
	gl.enable(gl.DEPTH_TEST);
	this.pickFramebuffer.unbind();

	// first pass, draw invisible instances, for occlusion
	for (var inst in instances) {
		var instance = instances[inst];
		var mesh     = meshes[instance.mesh];
		if (!mesh) continue;
		var renderable = mesh.renderable;
		if (!renderable) continue;
		if (!instance.visible) continue;

		// GLstate setup
		xform.model.push();
		xform.model.multiply(space.transform.matrix);
		xform.model.multiply(instance.transform.matrix);
		xform.model.multiply(mesh.transform.matrix);

		var uniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uPointSize"                 : this._scene.config.pointSize,
			"uColorID"                   : [0.0, 0.0, 0.0, 0.0],
			"uMode"                      : 2.0
		};

		if(mesh.mType === "nexus") {
			if (!renderable.isReady) continue;
			var nexus = renderable;
			nexus.updateView([0, 0, width, height], xform.projectionMatrix, xform.modelViewMatrix);

			this.pickFramebuffer.bind();

			var program = CurrProgram;
			program.setUniforms(uniforms);
			program.bind();
				nexus.setPrimitiveMode(instance.rendermode);
				nexus.render();
			program.unbind();

			this.pickFramebuffer.unbind();
		}
		else if(mesh.mType === "ply") {
			renderer.begin();
				renderer.setFramebuffer(this.pickFramebuffer);
				renderer.setTechnique(CurrTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode(instance.rendermode);
				renderer.setGlobals(uniforms);
				renderer.setModel(renderable);
				renderer.renderModel();
				renderer.setFramebuffer(null);
			renderer.end();
		}

		// GLstate cleanup
		xform.model.pop();
	}

	// second pass, draw color coded spots, for picking
	for (var spt in spots) {
		var spot = spots[spt];
		var mesh     = meshes[spot.mesh];
		if (!mesh) continue;
		var renderable = mesh.renderable;
		if (!renderable) continue;
		if (!spot.visible) continue;

		// GLstate setup
		xform.model.push();
		xform.model.multiply(space.transform.matrix);
		xform.model.multiply(spot.transform.matrix);
		xform.model.multiply(mesh.transform.matrix);

		var colorID = this._ID2Color(spot.ID);
		var uniforms = {
			"uWorldViewProjectionMatrix" : xform.modelViewProjectionMatrix,
			"uPointSize"                 : this._scene.config.pointSize,
			"uColorID"                   : colorID,
			"uMode"                      : 2.0
		};

		if(mesh.mType === "nexus") {
			if (!renderable.isReady) continue;
			var nexus = renderable;
			nexus.updateView([0, 0, width, height], xform.projectionMatrix, xform.modelViewMatrix);

			this.pickFramebuffer.bind();

			var program = CurrProgram;
			program.setUniforms(uniforms);
			program.bind();
				nexus.setPrimitiveMode(spot.rendermode);
				nexus.render();
			program.unbind();

			this.pickFramebuffer.unbind();
		}
		else if(mesh.mType === "ply") {
			renderer.begin();
				renderer.setFramebuffer(this.pickFramebuffer);
				renderer.setTechnique(CurrTechnique);
				renderer.setDefaultGlobals();
				renderer.setPrimitiveMode(spot.rendermode);
				renderer.setGlobals(uniforms);
				renderer.setModel(renderable);
				renderer.renderModel();
				renderer.setFramebuffer(null);
			renderer.end();
		}

		// GLstate cleanup
		xform.model.pop();
		gl.depthMask(true);
	}

	this.pickFramebuffer.readPixels(pixel, {
		x      : this._pickpoint[0],
		y      : this._pickpoint[1],
		width  : 1,
		height : 1,
		format : gl.RGBA,
		type   : gl.UNSIGNED_BYTE
	});

	return pixel;
},

_drawNull : function () {
	var gl = this.ui.gl;
	gl.clearColor(0.0, 0.0, 0.0, 0.0);
	gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
},

// creates mesh models
_createMeshModels : function () {
	var that = this;
	var gl = this.ui.gl;

	for(var keys = Object.keys(this._scene.meshes), i = keys.length-1; i >= 0; i--) {
		var m = keys[i];

		var mesh = this._scene.meshes[m];

		if (!mesh.url) continue;

		if (this._objectsToProcess == 0) this._testReady();
		else {
			this._objectsToProcess--;

			if(mesh.mType == null)
			{
				var ext = mesh.url.split('.').pop().split(/\#|\?/)[0].toLowerCase();
				if((ext === "nxs") || (ext === "nxz")) 
					mesh.mType = "nexus";
				else if(ext === "ply")
					mesh.mType = "ply";
			}

			if(mesh.mType === "nexus") {
				var nexus_instance = new Nexus.Renderer(gl);
				nexus_instance.onLoad = function () { that._onMeshReady(); };
				nexus_instance.onUpdate = this.ui.postDrawEvent;

				mesh.renderable = nexus_instance;
				nexus_instance.open(mesh.url);
			}
			else if(mesh.mType === "ply") {
				mesh.renderable = null;
				sglRequestBinary(mesh.url, {
					onSuccess : (function(m){ return function (req) { that._onPlyLoaded(req, m, gl); }; })(mesh)
				});
			}
		}
	}
},

// creates simple 2-point line model
_createLineModel : function () {
	var gl = this.ui.gl;
	this.simpleLineModel = new SglModel(gl, {
		vertices : {
			position : [ 0,0,0,
						 1,1,1 ],
			normal : [ 0,0,0,
					   0,0,0 ],
			color : {value : [ 1.0, 0.0, 0.0 ]}
		},
		primitives : ["lines","points"]
	});
},

// creates simple quad model
_createQuadModels : function () {
	var gl = this.ui.gl;
	this.simpleQuadXModel = new SglModel(gl, {
		vertices : {
			position : [ 0.0, 0.5, 0.5,
						 0.0,-0.5, 0.5,
						 0.0,-0.5,-0.5,
						 0.0, 0.5,-0.5,
						 0.0,-0.5,-0.5,
						 0.0, 0.5, 0.5],
			normal : [ 1.0,0.0,0.0,
					   1.0,0.0,0.0,
					   1.0,0.0,0.0,
					   1.0,0.0,0.0,
					   1.0,0.0,0.0,
					   1.0,0.0,0.0 ],
			color : {value : [ 1.0, 0.0, 0.0 ]}
		},
		primitives : ["triangles"]
	});
	this.simpleQuadYModel = new SglModel(gl, {
		vertices : {
			position : [  0.5, 0.0, 0.5,
						 -0.5, 0.0, 0.5,
						 -0.5, 0.0,-0.5,
						  0.5, 0.0,-0.5,
						 -0.5, 0.0,-0.5,
						  0.5, 0.0, 0.5],
			normal : [ 0.0,1.0,0.0,
					   0.0,1.0,0.0,
					   0.0,1.0,0.0,
					   0.0,1.0,0.0,
					   0.0,1.0,0.0,
					   0.0,1.0,0.0 ],
			color : {value : [ 0.0, 1.0, 0.0 ]}
		},
		primitives : ["triangles"]
	});
	this.simpleQuadZModel = new SglModel(gl, {
		vertices : {
			position : [  0.5, 0.5, 0.0,
						 -0.5, 0.5, 0.0,
						 -0.5,-0.5, 0.0,
						  0.5,-0.5, 0.0,
						 -0.5,-0.5, 0.0,
						  0.5, 0.5, 0.0],
			normal : [ 0.0,0.0,1.0,
					   0.0,0.0,1.0,
					   0.0,0.0,1.0,
					   0.0,0.0,1.0,
					   0.0,0.0,1.0,
					   0.0,0.0,1.0 ],
			color : {value : [ 0.0, 0.0, 1.0 ]}
		},
		primitives : ["triangles"]
	});
},

//----------------------------------------------------------------------------------------
// EVENTS HANDLERS
//----------------------------------------------------------------------------------------
onInitialize : function () {
	var gl = this.ui.gl;

	gl.getExtension('EXT_frag_depth');
	gl.clearColor(0.5, 0.5, 0.5, 1.0);
	gl.clearStencil(0);
	gl.depthFunc(gl.LESS);

	// vertex attrib defaults
	gl.vertexAttrib3f(1.0, 0.0, 0.0, 0.0);		//1 aNormal
	gl.vertexAttrib4f(2.0, 0.8, 0.8, 0.8, 1.0);	//2 aColor
	gl.vertexAttrib2f(3.0, 0.0, 0.0);			//3 aTextureCoord
	gl.vertexAttrib1f(4.0, 1.0);				//4 aPointSize

	// scene rendering support data
	this.renderer   = new SglModelRenderer(gl);
	this.xform      = new SglTransformationStack();
	this.viewMatrix = SglMat4.identity();

	// nexus parameters
	this.setNexusTargetError(1.0);
	this.setNexusMinFps(15.0);
	this.setNexusMaxCacheSize(512*(1<<20)); //512MB

	// debug mode
	this._isDebugging = HOP_DEBUGMODE;

	// shaders
	this.installDefaultShaders();

	// screenshot support
	this.isCapturingScreenshot = false;
	this.screenshotData = null;

	// handlers
	this._onPickedInstance  = 0;
	this._onPickedSpot      = 0;
	this._onEnterInstance   = 0;
	this._onEnterSpot       = 0;
	this._onLeaveInstance   = 0;
	this._onLeaveSpot       = 0;
	this._onEndPickingPoint = 0;
	this._onEndMeasurement  = 0;

	// animation
	this.ui.animateRate = 0;

	// current cursor XY position normalized [-1 1] on canvas size, and delta
	this.x	= 0.0;
	this.y	= 0.0;
	
	// scene data
	this._scene         = null;
	this._sceneParsed   = false;
	this._sceneReady    = false;
	this._objectsToProcess = 0;
	this._objectsToLoad = 0;

	this._instancesProgressiveID = 1;
	this._spotsProgressiveID     = 1;

	this._lightDirection = HOP_DEFAULTLIGHT;

	this.sceneCenter = [0.0, 0.0, 0.0];
	this.sceneRadiusInv = 1.0;

	this._targetInstanceName = null;
	this._targetHotSpotName  = null;

	this._animating      = false;
	this._movingLight    = false;

	this._resizable      = true;

	this._clickable      = false;
	this._onHover        = false;

	this._lastCursor     = "default";
	this._pickedInstance = null;
	this._pickedSpot     = null;
	this._lastPickedInstance = null;
	this._lastPickedSpot     = null;
	this._lastInstanceID = 0;
	this._lastSpotID     = 0;
	this._pickpoint      = [1, 1];

	this._keycombo = false;

	// global measurement data
	this._isMeasuring = false;

	// point2point measurement data
	this._isMeasuringDistance = false;
	this._measurementStage = 0; // 0=inactive 1=picking pointA 2=picking pointB 3=measurement ready
	this._pointA = [0.0, 0.0, 0.0];
	this._pointB = [0.0, 0.0, 0.0];
	this.measurement = 0;

	// point picking measurement data
	this._isMeasuringPickpoint = false;
	this._pickValid = false;
	this._pickedPoint = [0.0, 0.0, 0.0];

	// plane section
	this._clipPoint = [0.0, 0.0, 0.0];
	this._clipAxis  = [0.0, 0.0, 0.0];
	this._clipPlane = [0.0, 0.0, 0.0, 0.0];
	this._sceneBboxMin = [0.0, 0.0, 0.0]
	this._sceneBboxMax = [0.0, 0.0, 0.0];
	this._sceneBboxCenter = [0.0, 0.0, 0.0];
	this._sceneBboxDiag = 0.0;
},

installDefaultShaders : function () {
	this.facesProgram = this._createStandardFacesProgram();
	this.pointsProgram = this._createStandardPointsProgram();
	this.utilsProgram = this._createUtilsProgram();
	this.colorShadedProgram = this._createColorShadedProgram();

	this.faceTechnique = this._createStandardFacesTechnique();
	this.pointTechnique = this._createStandardPointsTechnique();
	this.utilsTechnique = this._createUtilsTechnique();
	this.colorShadedTechnique = this._createColorShadedTechnique();

	this.simpleLineTechnique = this._createSimpleLinetechnique();
	this.multiLinesPointsTechnique = this._createMultiLinesPointstechnique();
},

onDrag : function (button, x, y, e) {
	var ui = this.ui;
	this.x = (x / (ui.width  - 1)) * 2.0 - 1.0;
	this.y = (y / (ui.height - 1)) * 2.0 - 1.0;

	if(this._clickable) this._clickable = false;

	if(this._movingLight && ui.isMouseButtonDown(0)){
		this.rotateLight(this.x/2.0, this.y/2.0);
		return;
	}

	// if locked trackball, just return. we check AFTER the light-trackball test
	if (this._scene.trackball.locked) return;

	var action = SGL_TRACKBALL_NO_ACTION;
	if ((ui.isMouseButtonDown(0) && ui.isKeyDown(17)) || ui.isMouseButtonDown(1) || ui.isMouseButtonDown(2)) {
		action = SGL_TRACKBALL_PAN;
	}
	else if (ui.isMouseButtonDown(0)) {
		action = SGL_TRACKBALL_ROTATE;
	}

	var testMatrix = this.trackball._matrix.slice();

	this.trackball.action = action;
	this.trackball.track(this.viewMatrix, this.x*this._scene.trackball.dragSpeed, this.y*this._scene.trackball.dragSpeed, 0.0);

	var diff;
	for(var i=0; i<testMatrix.length; i++) {
		if(testMatrix[i]!=this.trackball._matrix[i]) {diff=true; break;}
	}
	if(diff) this.repaint();
},

onMouseButtonUp : function (x, y, e) {
	this.trackball.action = SGL_TRACKBALL_NO_ACTION;	
},

onMouseMove : function (x, y, e) {
	if(e.target.id!=this.ui.gl.canvas.id) return;
	if(this._onHover)
		if (!this.ui.isDragging(0) && !this.ui.isDragging(1) && !this.ui.isDragging(2)) this._pickingRefresh(x, y);
},

onMouseOut : function (x, y, e) {
	if(this._onHover) this._pickingRefresh(-1,-1);
	this._clickable = false;
},

onMouseButtonDown : function (button, x, y, e) {
	if(this._onHover) this._pickingRefresh(x,y);
	if(button==0) this._clickable = true;
},

onClick : function (button, x, y, e) {
	var xy = this.ui._getMouseClientPos(e);

	this.ui._clickPrevPos = this.ui._clickPos;
	this.ui._clickPos = xy;

	var clickDeltaPos = [this.ui._clickPos[0] - this.ui._clickPrevPos[0], this.ui._clickPos[1] - this.ui._clickPrevPos[1]];
	var clickDeltaTime = this.ui._clickPos[3] - this.ui._clickPrevPos[3];

	var clickDeltaDist = SpiderGL.Math.Vec2.length(clickDeltaPos);

	if (this._clickable) {
		this._pickingRefresh(x, y);

		if(clickDeltaDist <= 30 && clickDeltaTime <= 250) {
			if(this.trackball.recenter){
				var ppoint = this._drawScenePickingXYZ();
				if (ppoint!=null) {
					this.ui.animateRate = 30;
					this.trackball.recenter(ppoint);
					this.repaint();
				}
			}
		}

		if(this._onPickedSpot && this._pickedSpot!=null) this._onPickedSpot(this._pickedSpot);
		if(this._onPickedInstance && this._pickedInstance!=null) this._onPickedInstance(this._pickedInstance);
		if(this._isMeasuringPickpoint) this._pickpointRefresh(0, x, y, e);
		if(this._isMeasuringDistance) this._measureRefresh(0, x, y, e);
	}
	this._clickable = false;
},

onKeyDown : function (key, e) {
	if (e.ctrlKey) {
		if (e.key == 'p') // ctrl-p to save screenshot
		{
			if(e.preventDefault) e.preventDefault();
			this.isCapturingScreenshot = true;
			this.repaint();
		}
	}
},

onKeyPress : function (key, e) {
	if(this._isDebugging) { // DEBUGGING-AUTHORING keys
		if (e.charCode == '49') { // key "1" to show nexus patches
			Nexus.Debug.nodes =! Nexus.Debug.nodes;
			this.repaint();
		}
		else if (e.charCode == '50') { // key "2" to toggle camera perspective/orthographic
			this.toggleCameraType();
			this.repaint();
		}
		else if((e.charCode == '80') || (e.charCode == '112')) // key "P" to print trackball
			console.log(this.trackball.getState());
	}
},

onKeyUp : function (key, e) {
	if(this._keycombo && e.keyCode == '18') {
		if(e.preventDefault) e.preventDefault();
		this._keycombo = false;
	}
},

onMouseWheel: function (wheelDelta, x, y, e) {
	var diff = false;

	if(e && e.altKey) { // key "ALT" + MOUSE WHEEL to change pointclouds point set size
		this._keycombo = true;

		var testValue = this._scene.config.pointSize;

		this._scene.config.pointSize += wheelDelta/10;

		if (this._scene.config.pointSize < this._scene.config.pointSizeMinMax[0]) this._scene.config.pointSize = this._scene.config.pointSizeMinMax[0];
		else if (this._scene.config.pointSize > this._scene.config.pointSizeMinMax[1]) this._scene.config.pointSize = this._scene.config.pointSizeMinMax[1];

		if(testValue!=this._scene.config.pointSize) {
			diff=true;
		}
	}
	else {
		// if locked trackball, just return.
		if (this._scene.trackball.locked) return;
		
		var action = SGL_TRACKBALL_SCALE;

		var factor = wheelDelta > 0.0 ? (0.90) : (1.10);

		var testMatrix = this.trackball._matrix.slice();

		this.trackball.action = action;
		this.trackball.track(this.viewMatrix, 0.0, 0.0, factor);
		this.trackball.action = SGL_TRACKBALL_NO_ACTION;

		for(var i=0; i<testMatrix.length; i++) {
			if(testMatrix[i]!=this.trackball._matrix[i]) {diff=true; break;}
		}
	}

	if(diff) this.repaint();
},

onAnimate : function (dt) {
	if (this._isSceneReady()) {
		// animate trackball
		if(this.trackball.tick(dt)) {
			this.repaint();
		}
		else {
			this.ui.animateRate = 0;
			if(this._onHover && !this.ui._cursorPos[2]) this._pickingRefresh(this.ui._cursorPos[0], this.ui._cursorPos[1]);
		}
	}
},

onDraw : function () {
	if (this._isSceneReady())
		this._drawScene();
	else
		this._drawNull();
},

//----------------------------------------------------------------------------------------
// EXPOSED FUNCTIONS
//----------------------------------------------------------------------------------------
setScene : function (options) {
	if (!options) return;

	// scene reset, if already present
	if(this._scene != null)
	{
		this._scene         = null;
		this._sceneParsed   = false;
		this._sceneReady    = false;
		this._instancesProgressiveID = 1;
		this._spotsProgressiveID     = 1;
		this._objectsToProcess = 0;
		this._objectsToLoad = 0;
		this._stopMeasurement();
		this._stopPickPoint();
		this._clipAxis = [0.0, 0.0, 0.0];
		this._clipPoint = [0.0, 0.0, 0.0];
		this.enableLightTrackball(false);
	}

	// scene parsing
	var scene = this._parseScene(options);
	if (!scene) return;
	this._scene = scene;

	// trackball creation
	this.trackball  = new scene.trackball.type();
	this.trackball.setup(scene.trackball.trackOptions, this);
	this.trackball.track(SglMat4.identity(), 0.0, 0.0, 0.0);

	// mesh models creation
	this._createMeshModels();

	// point-to-point line model creation
	this._createLineModel();

	// quad models creation
	this._createQuadModels();

	// create a space for other entities
	this._scene.entities = {};

	this._sceneParsed = true;
},

get version() {
	return HOP_VERSION;
},

supportsWebGL : function () {
	return this._supportsWebGL;
},

toggleDebugMode : function () {
	this._isDebugging = !this._isDebugging;
},

repaint : function () {
	this.ui.postDrawEvent();
},

saveScreenshot : function () {
	this.isCapturingScreenshot = true;
	this.repaint();
},

//------entities-------------------
createEntity : function (eName, type, verticesList) {
	// type: points, lines, lineStrip, lineLoop, triangles, triangleStrip, triangleFan.
	var nEntity = {};
	nEntity.visible = true;
	nEntity.type = type;
	nEntity.renderable = null;
	nEntity.transform = {};
	nEntity.transform.matrix = SglMat4.identity();
	nEntity.color = [1.0, 0.0, 1.0, 1.0];
	nEntity.useTransparency = false;
	nEntity.useSeethrough = false;
	nEntity.pointSize = 6.0;
	nEntity.zOff = 0.0;

	var modelDescriptor = {};
	modelDescriptor.primitives = [type];
	modelDescriptor.vertices = {};
	modelDescriptor.vertices.position = [];
	modelDescriptor.vertices.normal = [];
	modelDescriptor.vertices.color = {value : [ 1.0, 0.0, 1.0, 1.0 ]};
	var numVerts = verticesList.length;

	for (vInd = 0; vInd < numVerts; vInd++)
	{
		modelDescriptor.vertices.position.push(verticesList[vInd][0]);
		modelDescriptor.vertices.position.push(verticesList[vInd][1]);
		modelDescriptor.vertices.position.push(verticesList[vInd][2]);
		
		modelDescriptor.vertices.normal.push(0.0);
		modelDescriptor.vertices.normal.push(0.0);
		modelDescriptor.vertices.normal.push(0.0);
	}
	var gl = this.ui.gl;
	nEntity.renderable = new SglModel(gl, modelDescriptor);
	
	// setting
	this._scene.entities[eName] = {};
	this._scene.entities[eName] = nEntity;;
	this.repaint();	
	return this._scene.entities[eName];
},

deleteEntity : function (eName) {
	delete this._scene.entities[eName];
	this.repaint();	
},

clearEntities : function () {
	this._scene.entities = {};
	this.repaint();
},

//-----------------------------------------------------------------------------
// nexus

setNexusTargetError: function(error) {
	Nexus.setTargetError(this.ui.gl, error);
},

getNexusTargetError: function() {
	return Nexus.getTargetError(this.ui.gl);
},

setNexusMinFps: function(fps) {
	Nexus.setMinFps(this.ui.gl, fps);
},

getNexusMinFps: function() {
	return Nexus.getMinFps(this.ui.gl);
},

setNexusMaxCacheSize: function(size) {
	Nexus.setMaxCacheSize(this.ui.gl, size);
},

getNexusMaxCacheSize: function() {
	return Nexus.getMaxCacheSize(this.ui.gl);
},

//-----------------------------------------------------------------------------
// trackball

resetTrackball : function () {
	this.trackball.reset();
	this.trackball.track(SglMat4.identity(), 0.0, 0.0, 0.0);
	this._lightDirection = HOP_DEFAULTLIGHT; // also reset lighting
	this.repaint();
},

getTrackballPosition : function () {
	return this.trackball.getState();
},

setTrackballPosition : function (newposition) {
	this.trackball.setState(newposition);
	this.repaint();
},

//-----------------------------------------------------------------------------
// camera animations

animateToTrackballPosition : function (newposition, newtime) {
	this.ui.animateRate = 30;
	this.trackball.animateToState(newposition, newtime);
	this.repaint();
},

isAnimate : function () {
	if(this.ui.animateRate > 0) this._animating = true;
	else this._animating = false;
	return this._animating;
},

//-----------------------------------------------------------------------------
// dynamic center/radius mode

setCenterModeFirst : function () {
	this._scene.space.centerMode = "first";
	this.repaint();
},

setCenterModeScene : function () {
	this._scene.space.centerMode = "scene";
	this.repaint();
},

setCenterModeSpecific : function (instancename) {
	if(this._scene.modelInstances[instancename])
	{
		this._scene.space.centerMode = "specific";
		this._scene.space.whichInstanceCenter = instancename;
		this.repaint();
	}
	else
		return "ERROR - No such instance";
},

setCenterModeExplicit : function (newcenter) {
	if((newcenter.constructor === Array)&&(newcenter.length = 3)&&(isFinite(String(newcenter[0])))&&(isFinite(String(newcenter[1])))&&(isFinite(String(newcenter[2]))))
	{
		this._scene.space.centerMode = "explicit";
		this._scene.space.explicitCenter = newcenter;
		this.repaint();
	}
	else
		return "ERROR - Not a point";
},

setRadiusModeFirst : function () {
	this._scene.space.radiusMode = "first";
	this.repaint();
},

setRadiusModeScene : function () {
	this._scene.space.radiusMode = "scene";
	this.repaint();
},

setRadiusModeSpecific : function (instancename) {
	if(this._scene.modelInstances[instancename])
	{
		this._scene.space.radiusMode = "specific";
		this._scene.space.whichInstanceRadius = instancename;
		this.repaint();
	}
	else
		return "ERROR - No such instance";
},

setRadiusModeExplicit : function (newradius) {
	if((isFinite(String(newradius)))&&(newradius>0.0))
	{
		this._scene.space.radiusMode = "explicit";
		this._scene.space.explicitRadius = newradius;
		this.repaint();
	}
	else
		return "ERROR - Not a radius";
},

//-----------------------------------------------------------------------------
// instance solid color

setInstanceSolidColorByName : function (name, newState, redraw, newColor) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL) {
		for (var inst in instances) {
			instances[inst].useSolidColor = newState;
			if(newColor)
				instances[inst].color = newColor;
		}
	}
	else {
		if(instances[name]) { // if an instance with that name exists
			instances[name].useSolidColor = newState;
			if(newColor)
				instances[name].color = newColor;
		}
	}
	if(redraw)
		this.repaint();
},

setInstanceSolidColor : function (tag, newState, redraw, newColor) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL) {
			instances[inst].useSolidColor = newState;
			if(newColor)
				instances[inst].color = newColor;
		}
		else {
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag){
					instances[inst].useSolidColor = newState;
					if(newColor)
						instances[inst].color = newColor;
				}
			}
		}
	}
	if(redraw)
		this.repaint();
},

toggleInstanceSolidColorByName : function (name, redraw) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL)
		for (var inst in instances)
			instances[inst].useSolidColor = !instances[inst].useSolidColor;
	else
		if(instances[name]) // if an instance with that name exists
			instances[name].useSolidColor = !instances[name].useSolidColor;
	if(redraw)
		this.repaint();
},

toggleInstanceSolidColor : function (tag, redraw) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL)
			instances[inst].useSolidColor = !instances[inst].useSolidColor;
		else
			for (var tg in instances[inst].tags)
				if(instances[inst].tags[tg] == tag)
					instances[inst].useSolidColor = !instances[inst].useSolidColor;
	}
	if(redraw)
		this.repaint();
},

isInstanceSolidColorEnabledByName : function (name) {
	var solidcolor = false;
	var instances = this._scene.modelInstances;

	if(!name || name==HOP_ALL) {
		for (var inst in instances) {
			if(instances[inst].useSolidColor){
				solidcolor = true;
				return solidcolor;
			}
		}
	}
	else {
		if(instances[name]) { // if an instance with that name exists
			if(instances[name].useSolidColor){
				solidcolor = true;
				return solidcolor;
			}
		 }
	}
	return solidcolor;
},

isInstanceSolidColorEnabled : function (tag) {
	var solidcolor = false;
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(!tag || tag==HOP_ALL){
			if(instances[inst].useSolidColor){
				solidcolor = true;
				return solidcolor;
			}
		}
		else{
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag){
					if(instances[inst].useSolidColor){
						solidcolor = true;
						return solidcolor;
					}
				 }
			}
		}
	}
	return solidcolor;
},

//-----------------------------------------------------------------------------
// instance transparency

setInstanceTransparencyByName : function (name, newState, redraw, newAlpha) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL) {
		for (var inst in instances)
			instances[inst].useTransparency = newState;
			if(newAlpha)
				instances[inst].alpha = newAlpha;
	}
	else {
		if(instances[name]) { // if an instance with that name exists
			instances[name].useTransparency = newState;
			if(newAlpha)
				instances[name].alpha = newAlpha;
		}
	}
	if(redraw)
		this.repaint();
},

setInstanceTransparency : function (tag, newState, redraw, newAlpha) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL) {
			instances[inst].useTransparency = newState;
			if(newAlpha)
				instances[inst].alpha = newAlpha;
		}
		else {
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag){
					instances[inst].useTransparency = newState;
					if(newAlpha)
						instances[inst].alpha = newAlpha;
				}
			}
		}
	}
	if(redraw)
		this.repaint();
},

toggleInstanceTransparencyByName : function (name, redraw) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL) {
		for (var inst in instances)
			instances[inst].useTransparency = !instances[inst].useTransparency;
	}
	else {
		if(instances[name]) // if an instance with that name exists
			instances[name].useTransparency = !instances[name].useTransparency;
	}
	if(redraw)
		this.repaint();
},

toggleInstanceTransparency : function (tag, redraw) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL)
		{
			instances[inst].useTransparency = !instances[inst].useTransparency;
		}
		else
		{
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag)
					instances[inst].useTransparency = !instances[inst].useTransparency;
			}
		}
	}
	if(redraw)
		this.repaint();
},


isInstanceTransparencyEnabledByName : function (name) {
	var transparency = false;
	var instances = this._scene.modelInstances;

	if(!name || name==HOP_ALL) {
		for (var inst in instances) {
			if(instances[inst].useTransparency){
				transparency = true;
				return transparency;
			}
		}
	}
	else {
		if(instances[name]) { // if an instance with that name exists
			if(instances[name].useTransparency){
				transparency = true;
				return transparency;
			}
		 }
	}
	return transparency;
},

isInstanceTransparencyEnabled : function (tag) {
	var transparency = false;
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(!tag || tag==HOP_ALL){
			if(instances[inst].useTransparency){
				transparency = true;
				return transparency;
			}
		}
		else{
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag){
					if(instances[inst].useTransparency){
						transparency = true;
						return transparency;
					}
				 }
			}
		}
	}
	return transparency;
},

//-----------------------------------------------------------------------------
// instance shading
//----specular

setInstanceSpecularityByName : function (name, color, hardness, redraw) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL) {
		for (var inst in instances)
			instances[inst].specularColor = [color[0], color[1], color[2], hardness];
	}
	else {
		if(instances[name]) // if an instance with that name exists
			instances[name].specularColor = [color[0], color[1], color[2], hardness];
	}
	if(redraw)
		this.repaint();
},

setInstanceSpecularity : function (tag, color, hardness, redraw) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL) {
			instances[inst].specularColor = [color[0], color[1], color[2], hardness];
		}
		else {
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag)
					instances[inst].specularColor = [color[0], color[1], color[2], hardness];
			}
		}
	}
	if(redraw)
		this.repaint();
},

//----backface

setInstanceBackfaceByName : function (name, color, mode, redraw) {
	var instances = this._scene.modelInstances;
	var modecode = 0.0;
	if (mode == "tint") modecode = 0.0;
	else if (mode == "fill") modecode = 1.0;
	else if (mode == "cull") modecode = 2.0;

	if(name == HOP_ALL) {
		for (var inst in instances)
			instances[inst].backfaceColor = [color[0], color[1], color[2], modecode];
	}
	else {
		if(instances[name]) // if an instance with that name exists
			instances[name].backfaceColor = [color[0], color[1], color[2], modecode];
	}
	if(redraw)
		this.repaint();
},

setInstanceBackface : function (tag, color, mode, redraw) {
	var instances = this._scene.modelInstances;
	var modecode = 0.0;
	if (mode == "tint") modecode = 0.0;
	else if (mode == "fill") modecode = 1.0;
	else if (mode == "cull") modecode = 2.0;

	for (var inst in instances) {
		if(tag == HOP_ALL) {
			instances[inst].backfaceColor = [color[0], color[1], color[2], modecode];
		}
		else {
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag)
					instances[inst].backfaceColor = [color[0], color[1], color[2], modecode];
			}
		}
	}
	if(redraw)
		this.repaint();
},

//-----------------------------------------------------------------------------
// instance visibility

setInstanceVisibilityByName : function (name, newState, redraw) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL) {
		for (var inst in instances)
			instances[inst].visible = newState;
	}
	else {
		if(instances[name]) // if an instance with that name exists
			instances[name].visible = newState;
	}
	if(redraw)
		this.repaint();
},

setInstanceVisibility : function (tag, newState, redraw) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL) {
			instances[inst].visible = newState;
		}
		else {
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag)
					instances[inst].visible = newState;
			}
		}
	}
	if(redraw)
		this.repaint();
},

toggleInstanceVisibilityByName : function (name, redraw) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL) {
		for (var inst in instances)
			instances[inst].visible = !instances[inst].visible;
	}
	else {
		if(instances[name]) // if an instance with that name exists
			instances[name].visible = !instances[name].visible;
	}
	if(redraw)
		this.repaint();
},

toggleInstanceVisibility : function (tag, redraw) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL)
		{
			instances[inst].visible = !instances[inst].visible;
		}
		else
		{
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag)
					instances[inst].visible = !instances[inst].visible;
			}
		}
	}
	if(redraw)
		this.repaint();
},

isInstanceVisibilityEnabledByName : function (name) {
	var visibility = false;
	var instances = this._scene.modelInstances;

	if(!name || name==HOP_ALL) {
		for (var inst in instances) {
			if(instances[inst].visible){
				visibility = true;
				return visibility;
			}
		}
	}
	else {
		if(instances[name]) { // if an instance with that name exists
			if(instances[name].visible){
				visibility = true;
				return visibility;
			}
		 }
	}
	return visibility;
},

isInstanceVisibilityEnabled : function (tag) {
	var visibility = false;
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(!tag || tag==HOP_ALL){
			if(instances[inst].visible){
				visibility = true;
				return visibility;
			}
		}
		else{
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag){
					if(instances[inst].visible){
						visibility = true;
						return visibility;
					}
				 }
			}
		}
	}
	return visibility;
},

//-----------------------------------------------------------------------------
// spot visibility

setSpotVisibilityByName : function (name, newState, redraw) {
	var spots = this._scene.spots;

	if(name == HOP_ALL) {
		for (var spt in spots)
			spots[spt].visible = newState;
	}
	else {
		if(spots[name]) // if an hotspot with that name exists
			spots[name].visible = newState;
	}
	if(redraw)
		this.repaint();
},

setSpotVisibility : function (tag, newState, redraw) {
	var spots = this._scene.spots;
	for (var spt in spots) {
		if(tag == HOP_ALL)
		{
			spots[spt].visible = newState;
		}
		else
		{
			for (var tg in spots[spt].tags){
				if(spots[spt].tags[tg] == tag)
					spots[spt].visible = newState;
			}
		}
	}
	if(redraw)
		this.repaint();
},

toggleSpotVisibilityByName : function (name, redraw) {
	var spots = this._scene.spots;
	if(name == HOP_ALL) {
		for (var spt in spots)
			spots[spt].visible = !spots[spt].visible;
	}
	else {
		if(spots[name]) // if an hotspot with that name exists
			spots[name].visible = !spots[name].visible;
	}
	if(redraw)
		this.repaint();
},

toggleSpotVisibility : function (tag, redraw) {
	var spots = this._scene.spots;
	for (var spt in spots) {
		if(tag == HOP_ALL)
		{
			spots[spt].visible = !spots[spt].visible;
		}
		else
		{
			for (var tg in spots[spt].tags){
				if(spots[spt].tags[tg] == tag)
					spots[spt].visible = !spots[spt].visible;
			}
		}
	}
	if(redraw)
		this.repaint();
},

isSpotVisibilityEnabledByName : function (name) {
	var visibility = false;
	var spots = this._scene.spots;

	if(!name || name==HOP_ALL) {
		for (var spt in spots) {
			if(spots[spt].visible){
				visibility = true;
				return visibility;
			}
		}
	}
	else {
		if(spots[name]) { // if an hotspot with that name exists
			if(spots[name].visible){
				visibility = true;
				return visibility;
			}
		 }
	}
	return visibility;
},

isSpotVisibilityEnabled : function (tag) {
	var visibility = false;
	var spots = this._scene.spots;

	for (var spt in spots) {
		if(!tag || tag==HOP_ALL){
			if(spots[spt].visible){
				visibility = true;
				return visibility;
			}
		}
		else{
			for (var tg in spots[spt].tags){
				if(spots[spt].tags[tg] == tag){
					if(spots[spt].visible){
						visibility = true;
						return visibility;
					}
				 }
			}
		}
	}
	return visibility;
},

//-----------------------------------------------------------------------------
// sections

resetClippingXYZ: function() {
	this._calculateBounding();
	this._clipAxis = [0.0, 0.0, 0.0];
	this._clipPoint = [0.0, 0.0, 0.0];
	this.repaint();
},

setClippingXYZ: function(cx, cy, cz) {
	this._calculateBounding();
	this._clipAxis = [cx,cy,cz];
	this.repaint();
},

setClippingX: function(cx) {
	this._calculateBounding();
	this._clipAxis[0] = cx;
	this.repaint();
},
setClippingY: function(cy) {
	this._calculateBounding();
	this._clipAxis[1] = cy;
	this.repaint();
},
setClippingZ: function(cz) {
	this._calculateBounding();
	this._clipAxis[2] = cz;
	this.repaint();
},

getClippingX : function () {
	return this._clipAxis[0];
},
getClippingY : function () {
	return this._clipAxis[1];
},
getClippingZ : function () {
	return this._clipAxis[2];
},

setClippingPointXYZabs: function(clx, cly, clz) {
	this._calculateBounding();
	this._clipPoint = [clx, cly, clz];
	this.repaint();
},

setClippingPointXabs: function(clx) {
	this._calculateBounding();
	this._clipPoint[0] = clx;
	this.repaint();
},
setClippingPointYabs: function(cly) {
	this._calculateBounding();
	this._clipPoint[1] = cly;
	this.repaint();
},
setClippingPointZabs: function(clz) {
	this._calculateBounding();
	this._clipPoint[2] = clz;
	this.repaint();
},

setClippingPointXYZ: function(clx, cly, clz) {
	var nClipPoint = [0.0, 0.0, 0.0];

	this._calculateBounding();

	if(clx<0.0) clx=0.0; else if(clx>1.0) clx=1.0;
	if(cly<0.0) cly=0.0; else if(cly>1.0) cly=1.0
	if(clz<0.0) clz=0.0; else if(clz>1.0) clz=1.0;

	nClipPoint[0] = this._sceneBboxMin[0] + clx * (this._sceneBboxMax[0] - this._sceneBboxMin[0]);
	nClipPoint[1] = this._sceneBboxMin[1] + cly * (this._sceneBboxMax[1] - this._sceneBboxMin[1]);
	nClipPoint[2] = this._sceneBboxMin[2] + clz * (this._sceneBboxMax[2] - this._sceneBboxMin[2]);

	this._clipPoint = nClipPoint;
	this.repaint();
},

setClippingPointX: function(clx) {
	var nClipPoint = 0.0;
	this._calculateBounding();
	if(clx<0.0) clx=0.0; else if(clx>1.0) clx=1.0;
	nClipPoint = this._sceneBboxMin[0] + clx * (this._sceneBboxMax[0] - this._sceneBboxMin[0]);
	this._clipPoint[0] = nClipPoint;
	this.repaint();
},
setClippingPointY: function(cly) {
	var nClipPoint = 0.0;
	this._calculateBounding();
	if(cly<0.0) cly=0.0; else if(cly>1.0) cly=1.0;
	nClipPoint = this._sceneBboxMin[1] + cly * (this._sceneBboxMax[1] - this._sceneBboxMin[1]);
	this._clipPoint[1] = nClipPoint;
	this.repaint();
},
setClippingPointZ: function(clz) {
	var nClipPoint = 0.0;
	this._calculateBounding();
	if(clz<0.0) clz=0.0; else if(clz>1.0) clz=1.0;
	nClipPoint = this._sceneBboxMin[2] + clz * (this._sceneBboxMax[2] - this._sceneBboxMin[2]);
	this._clipPoint[2] = nClipPoint;
	this.repaint();
},

_calculateBounding: function() {
	var meshes    = this._scene.meshes;
	var instances = this._scene.modelInstances;
	this._sceneBboxMin = SglVec3.maxNumber();
	this._sceneBboxMax = SglVec3.minNumber();
	this._sceneBboxCenter = [0.0, 0.0, 0.0];
	this._sceneBboxDiag = 0.0;
	var imin = [0.0, 0.0, 0.0];
	var imax = [0.0, 0.0, 0.0];

	for (var inst in instances) {
		var mesh = meshes[instances[inst].mesh];
		if((mesh)&&(mesh.renderable)&&(instances[inst].clippable)){
			var instCenter = SglVec3.to4(mesh.renderable.datasetCenter,1);
			instCenter = SglMat4.mul4(mesh.transform.matrix, instCenter);
			instCenter = SglMat4.mul4(instances[inst].transform.matrix, instCenter);
			instCenter = SglMat4.mul4(this._scene.space.transform.matrix, instCenter);
			instCenter = SglVec4.to3(instCenter);

			var radius = mesh.renderable.datasetRadius;
			var vector111 = SglVec3.one();
			vector111 = SglMat3.mul3(SglMat4.to33(mesh.transform.matrix), vector111);
			vector111 = SglMat3.mul3(SglMat4.to33(instances[inst].transform.matrix), vector111);
			vector111 = SglMat3.mul3(SglMat4.to33(this._scene.space.transform.matrix), vector111);
			var scalefactor = SglVec3.length(vector111) / SglVec3.length([1,1,1]);
			radius = radius*scalefactor;

			imin[0] = instCenter[0] - radius;
			imin[1] = instCenter[1] - radius;
			imin[2] = instCenter[2] - radius;
			imax[0] = instCenter[0] + radius;
			imax[1] = instCenter[1] + radius;
			imax[2] = instCenter[2] + radius;

			if(imin[0] < this._sceneBboxMin[0]) this._sceneBboxMin[0] = imin[0];
			if(imin[1] < this._sceneBboxMin[1]) this._sceneBboxMin[1] = imin[1];
			if(imin[2] < this._sceneBboxMin[2]) this._sceneBboxMin[2] = imin[2];
			if(imax[0] > this._sceneBboxMax[0]) this._sceneBboxMax[0] = imax[0];
			if(imax[1] > this._sceneBboxMax[1]) this._sceneBboxMax[1] = imax[1];
			if(imax[2] > this._sceneBboxMax[2]) this._sceneBboxMax[2] = imax[2];
		}
	}

	this._sceneBboxCenter[0] = (this._sceneBboxMin[0] + this._sceneBboxMax[0]) / 2.0;
	this._sceneBboxCenter[1] = (this._sceneBboxMin[1] + this._sceneBboxMax[1]) / 2.0;
	this._sceneBboxCenter[2] = (this._sceneBboxMin[2] + this._sceneBboxMax[2]) / 2.0;

	this._sceneBboxDiag = SglVec3.length([ this._sceneBboxMax[0]-this._sceneBboxMin[0], this._sceneBboxMax[1]-this._sceneBboxMin[1], this._sceneBboxMax[2]-this._sceneBboxMin[2]]);
},

setClippingRendermode: function(showPlanes, showBorder, borderSize, borderColor) {
	this._calculateBounding();
	this._scene.config.showClippingPlanes = showPlanes;
	this._scene.config.showClippingBorder = showBorder;
	if(borderSize>0.0)
		this._scene.config.clippingBorderSize = borderSize;
	if(borderColor)
		this._scene.config.clippingBorderColor = borderColor;
	this.repaint();
},

getClippingRendermode: function() {
	var rendermode = [this._scene.config.showClippingPlanes, this._scene.config.showClippingBorder, this._scene.config.clippingBorderSize, this._scene.config.clippingBorderColor];
	return rendermode;
},

resetClippingPlane : function () {
	this._calculateBounding();
	this._clipPlane = [0.0, 0.0, 0.0, 0.0];
	this.repaint();
},

setClippingPlaneExplicit : function (axis, offset) {
	this._calculateBounding();
	this._clipPlane = [axis[0], axis[1], axis[2], offset];
	this.repaint();
},

setClippingPlane : function (angleH, angleV, sign, delta, deltaabs) {
	this._calculateBounding();
	var axis;
	var m = SglMat4.identity();
	this._clipPlaneAH = angleH;
	this._clipPlaneAV = angleV;

	// horizontal angle
	m = SglMat4.mul(m, SglMat4.rotationAngleAxis(sglDegToRad(angleH), [0.0, -1.0, 0.0]));
	// vertical angle
	m = SglMat4.mul(m, SglMat4.rotationAngleAxis(sglDegToRad(angleV), [0.0, 0.0, 1.0]));

	axis = [sign*1.0, 0.0, 0.0, 1.0];
	axis = SglMat4.mul4(m, axis);

	var sceneOff = (this._sceneBboxDiag / 2.0) * (delta / 100.0);
	if(typeof deltaabs !== "undefined")
		sceneOff = deltaabs;
	var position = [this._sceneBboxCenter[0] + (axis[0] * sceneOff), this._sceneBboxCenter[1] + (axis[1] * sceneOff), this._sceneBboxCenter[2] + (axis[2] * sceneOff)];
	sceneOff = SglVec3.dot([axis[0], axis[1], axis[2]], position);

	this._clipPlane = [axis[0], axis[1], axis[2], -sceneOff];
	this.repaint();
},

//-----------------------------------------------------------------------------
// zoom

zoomIn: function() {
	this.onMouseWheel(1);
},

zoomOut: function() {
	this.onMouseWheel(-1);
},

//-----------------------------------------------------------------------------
// light

rotateLight: function(x, y) {
	var dx = x * 2.0;
	var dy = y * 2.0;
	var dz = 0.0;
	var r = Math.sqrt(dx*dx + dy*dy);
	if(r >= 1) {
		dx /= r;
		dy /= r;
		dz = 0.0;
	} else {
		dz = Math.sqrt(1 - r*r);
	}
	this._lightDirection = [-dx, -dy, -dz];
	this.repaint();
},

setLight: function(dir) {
	this._lightDirection = SglVec3.normalize([-dir[0], -dir[1], -dir[2]]);
	this.repaint();
},

enableLightTrackball: function(on) {
	this._movingLight = on;
	if(on && !this._scene.space.sceneLighting) this._scene.space.sceneLighting = on;
	this.repaint();
},

isLightTrackballEnabled: function() {
	return this._movingLight;
},

//-----------------------------------------------------------------------------
// onHover

enableOnHover: function(on) {
	this._onHover = on;
},

isOnHoverEnabled: function() {
	return this._onHover;
},

//-----------------------------------------------------------------------------
// linear measure

enableMeasurementTool: function(on) {
	if(on)
		this._startMeasurement();
	else
		this._stopMeasurement();
},

isMeasurementToolEnabled: function() {
	return this._isMeasuringDistance;
},

//-----------------------------------------------------------------------------
// point measure

enablePickpointMode: function(on) {
	if(on)
		this._startPickPoint();
	else
		this._stopPickPoint();
},

isPickpointModeEnabled: function() {
	return this._isMeasuringPickpoint;
},

//-----------------------------------------------------------------------------
// measurements

isAnyMeasurementEnabled: function() {
	return this._isMeasuring;
},

//-----------------------------------------------------------------------------
// camera type

toggleCameraType: function() {
	if(this._scene.space.cameraType == "orthographic")
		this._scene.space.cameraType = "perspective"
	else
		this._scene.space.cameraType = "orthographic"

	this.repaint();
},

setCameraPerspective: function() {
	this._scene.space.cameraType = "perspective";
	this.repaint();
},

setCameraOrthographic: function() {
	this._scene.space.cameraType = "orthographic";
	this.repaint();
},

getCameraType : function () {
	return this._scene.space.cameraType;
},

//-----------------------------------------------------------------------------
// trackball lock

toggleTrackballLock: function() {
	this._scene.trackball.locked = !this._scene.trackball.locked;
},

setTrackballLock: function(newState) {
	this._scene.trackball.locked = newState;
},

isTrackballLockEnabled: function() {
	return this._scene.trackball.locked;
},

//-----------------------------------------------------------------------------
// lighting

enableSceneLighting: function(on) {
	this._scene.space.sceneLighting = on;

	if(!on && this._movingLight) this._movingLight = on;

	this.repaint();
},

isSceneLightingEnabled: function() {
	return this._scene.space.sceneLighting;
},

setInstanceLightingByName : function (name, newState, redraw) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL) {
		for (var inst in instances)
			instances[inst].useLighting = newState;
	}
	else {
		if(instances[name]) // if an instance with that name exists
			instances[name].useLighting = newState;
	}
	if(redraw)
		this.repaint();
},

setInstanceLighting : function (tag, newState, redraw) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL) {
			instances[inst].useLighting = newState;
		}
		else {
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag)
					instances[inst].useLighting = newState;
			}
		}
	}
	if(redraw)
		this.repaint();
},

toggleInstanceLightingByName : function (name, redraw) {
	var instances = this._scene.modelInstances;

	if(name == HOP_ALL) {
		for (var inst in instances)
			instances[inst].useLighting = !instances[inst].useLighting;
	}
	else {
		if(instances[name]) // if an instance with that name exists
			instances[name].useLighting = !instances[name].useLighting;
	}
	if(redraw)
		this.repaint();
},

toggleInstanceLighting : function (tag, redraw) {
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(tag == HOP_ALL)
		{
			instances[inst].useLighting = !instances[inst].useLighting;
		}
		else
		{
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag)
					instances[inst].useLighting = !instances[inst].useLighting;
			}
		}
	}
	if(redraw)
		this.repaint();
},

isInstanceLightingEnabledByName : function (name) {
	var Lighting = false;
	var instances = this._scene.modelInstances;

	if(!name || name==HOP_ALL) {
		for (var inst in instances) {
			if(instances[inst].useLighting){
				Lighting = true;
				return Lighting;
			}
		}
	}
	else {
		if(instances[name]) { // if an instance with that name exists
			if(instances[name].useLighting){
				Lighting = true;
				return Lighting;
			}
		 }
	}
	return Lighting;
},

isInstanceLightingEnabled : function (tag) {
	var Lighting = false;
	var instances = this._scene.modelInstances;

	for (var inst in instances) {
		if(!tag || tag==HOP_ALL){
			if(instances[inst].useLighting){
				Lighting = true;
				return Lighting;
			}
		}
		else{
			for (var tg in instances[inst].tags){
				if(instances[inst].tags[tg] == tag){
					if(instances[inst].useLighting){
						Lighting = true;
						return Lighting;
					}
				 }
			}
		}
	}
	return Lighting;
}

}; // Presenter.prototype END

// export default Presenter;
// export { 
//     HOP_VERSION,
//     HOP_ALL,
//     HOP_DEBUGMODE,
//     HOP_DEFAULTLIGHT,
//     SGL_TRACKBALL_NO_ACTION,
//     SGL_TRACKBALL_ROTATE,
//     SGL_TRACKBALL_PAN,
//     SGL_TRACKBALL_DOLLY,
//     SGL_TRACKBALL_SCALE
// };