function MeShadeViewer() {
	this._onload = null;
}

MeShadeViewer.prototype = {
	get drawTriad() {
		return this.doTriad;
	},

	set drawTriad(enabled) {
		this.doTriad = enabled;
		this.refresh();
	},

	get drawBBox() {
		return this.doBBox;
	},

	set drawBBox(enabled) {
		this.doBBox = enabled;
		this.refresh();
	},

	setupLines : function () {
		var gl = this.ui.gl;

		// triad mesh
		/*****************************************/
		var triadPos = new Float32Array([
			0.0, 0.0, 0.0,
			1.0, 0.0, 0.0,
			0.0, 0.0, 0.0,
			0.0, 1.0, 0.0,
			0.0, 0.0, 0.0,
			0.0, 0.0, 1.0
		]);

		var triadClr = new Float32Array([
			1.0, 0.0, 0.0,
			1.0, 0.0, 0.0,
			0.0, 1.0, 0.0,
			0.0, 1.0, 0.0,
			0.0, 0.0, 1.0,
			0.0, 0.0, 1.0
		]);

		var triadMesh = new SglMeshGL(gl);
		triadMesh.addVertexAttribute("position", 3, triadPos);
		triadMesh.addVertexAttribute("color",    3, triadClr);
		triadMesh.addArrayPrimitives("lines", gl.LINES, 0, 6);
		this.triadMesh = triadMesh;
		/*****************************************/

		// bbox mesh
		/*****************************************/
		var boxPositions = new Float32Array([
			-0.5, -0.5,  0.5,
			 0.5, -0.5,  0.5,
			-0.5,  0.5,  0.5,
			 0.5,  0.5,  0.5,
			-0.5, -0.5, -0.5,
			 0.5, -0.5, -0.5,
			-0.5,  0.5, -0.5,
			 0.5,  0.5, -0.5
		]);

		var boxColors = new Float32Array([
			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,
			0.0, 1.0, 0.0,
			0.0, 1.0, 0.0
		]);

		var boxEdgesIndices = new Uint16Array([
			0, 1, 1, 3, 3, 2, 2, 0,  // front
			5, 4, 4, 6, 6, 7, 7, 5,  // back
			0, 4, 1, 5, 3, 7, 2, 6   // middle
		]);

		var box = new SglMeshGL(gl);
		box.addVertexAttribute("position", 3, boxPositions);
		box.addVertexAttribute("color",    3, boxColors);
		box.addIndexedPrimitives("lines", gl.LINES, boxEdgesIndices);
		this.boxMesh = box;
		/*****************************************/

		// lines program
		/*****************************************/
		var vsrc = "";
		vsrc += "#ifdef GL_ES\n";
		vsrc += "precision highp float;\n";
		vsrc += "#endif\n";
		vsrc += "uniform   mat4 u_modelViewProjectionMatrix;\n";
		vsrc += "attribute vec4 a_position;\n";
		vsrc += "attribute vec3 a_color;\n";
		vsrc += "varying   vec3 v_color;\n";
		vsrc += "void main(void)\n";
		vsrc += "{\n";
		vsrc += "  v_color     = a_color;\n";
		vsrc += "  gl_Position = u_modelViewProjectionMatrix * a_position;\n";
		vsrc += "}\n";

		var fsrc = "";
		fsrc += "#ifdef GL_ES\n";
		fsrc += "precision highp float;\n";
		fsrc += "#endif\n";
		fsrc += "varying vec3 v_color;\n";
		fsrc += "void main(void)\n";
		fsrc += "{\n";
		fsrc += "  gl_FragColor  = vec4(v_color, 1.0);\n";
		fsrc += "}\n";

		this.lineProgram = new SglProgram(gl, [vsrc], [fsrc]);
		/*****************************************/
	},

	loadScene : function (sceneURL) {
		var that = this;
		sglLoadFile(sceneURL, function (txt) {
			var obj = JSON.parse(txt);
			that.bkgColor = obj.backgroundColor;
			that.loadMesh(obj.modelURL);
			that.setProgramSources(unescape(obj.vertexShader), unescape(obj.fragmentShader));
			for (var u in obj.uniforms) {
				if (that.uniforms[u]) {
					that.uniforms[u].value = obj.uniforms[u];
				}
			}
			that.setUniforms(that.uniforms);
			/*
			for (var s in obj.samplers) {
				if (that.samplers[s]) {
					that.samplers[s].url = obj.samplers[s];
				}
			}
			that.setSamplers(that.samplers);
			*/
			that.setSamplers(obj.samplers);
		});
	},

	get glContext() {
		return this.ui.gl;
	},

	signalOnLoad : function () {
		if (this._onload) {
			this._onload();
		}
	},

	get onload() {
		return this._onload;
	},

	set onload(f) {
		this._onload = f;
	},

	setupDefaultMesh : function () {
		this.setupMesh(null);
	},

	setupMesh : function (m, url) {
		if (this.mesh) {
			this.meshURL = "";
			this.mesh.destroy();
			this.mesh = null;
			this.bbox = new SglBox3();
			this.meshInfo.vertexAttributes = [ ];
			this.meshInfo.verticesCount    = 0;
			this.meshInfo.trianglesCount   = 0;
		}
		if (!m) return;

		this.meshURL = url;

		for (var v in m.vertices.attributes) {
			this.meshInfo.vertexAttributes.push(v);
		}
		this.meshInfo.verticesCount  = m.vertices.length;
		this.meshInfo.trianglesCount = m.connectivity.primitives["triangles"].length / 3;
		this.mesh = m.toPackedMeshGL(this.ui.gl, "triangles", 65000);
		this.bbox = m.calculateBoundingBox("position");
		var bc = this.bbox.center;
		var bs = this.bbox.size;
		var s  = 1.0 / this.bbox.diagonal;
		this.centerMatrix = sglMulM4(sglScalingM4C(s, s, s), sglTranslationM4C(-bc[0], -bc[1], -bc[2]));
		//this.boxMatrix    = sglMulM4(sglTranslationM4C(bc[0], bc[1], bc[2]), sglScalingM4C(1.0/bs[0], 1.0/bs[1], 1.0/bs[2]));
		//this.boxMatrix    = sglScalingM4C(s*bs[0], s*bs[1], s*bs[2]);
		//this.boxMatrix    = sglIdentityM4();
		this.boxMatrix    = sglScalingM4C(s*bs[0], s*bs[1], s*bs[2]);
		this.trackball.reset();

		this.signalOnLoad();
		this.refresh();
	},

	setupDefaultProgram : function () {
		var vsrc = "";
		vsrc += "#ifdef GL_ES\n";
		vsrc += "precision highp float;\n";
		vsrc += "#endif\n";
		vsrc += "uniform   mat4 u_modelViewProjectionMatrix;\n";
		vsrc += "uniform   mat3 u_viewSpaceNormalMatrix;\n";
		vsrc += "attribute vec4 a_position;\n";
		vsrc += "attribute vec3 a_normal;\n";
		vsrc += "varying   vec3 v_normal;\n";
		vsrc += "void main(void)\n";
		vsrc += "{\n";
		vsrc += "  v_normal    = u_viewSpaceNormalMatrix * a_normal;\n";
		vsrc += "  gl_Position = u_modelViewProjectionMatrix * a_position;\n";
		vsrc += "}\n";

		/*
		var fsrc = "";
		fsrc += "void main(void)\n";
		fsrc += "{\n";
		fsrc += "  gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0);\n";
		fsrc += "}\n";
		*/

		var fsrc = "";
		fsrc += "#ifdef GL_ES\n";
		fsrc += "precision highp float;\n";
		fsrc += "#endif\n";
		fsrc += "uniform vec3 u_color;\n";
		fsrc += "varying vec3 v_normal;\n";
		fsrc += "void main(void)\n";
		fsrc += "{\n";
		fsrc += "  const vec3 lightDir = vec3(0.0, 0.0, -1.0);\n";
		fsrc += "  vec3  normal  = normalize(v_normal);\n";
		fsrc += "  float lambert = max(dot(normal, -lightDir), 0.0);\n";
		fsrc += "  vec3  color   = u_color * lambert;\n";
		fsrc += "  gl_FragColor  = vec4(color, 1.0);\n";
		fsrc += "}\n";

		this.setupProgram(vsrc, fsrc);

		this.uniforms["u_color"].value = [ 0.8, 0.8, 0.8 ];
		this.setUniforms(this.uniforms);
	},

	setupProgram : function (vsrc, fsrc) {
		var gl = this.ui.gl;

		var ret = {
			status : false,
			log    : ""
		};

		if (this.program) {
			this.program.destroy();
			this.vsSrc   = "";
			this.fsSrc   = "";
			this.program = null;
		}

		var prevUniforms = this.uniforms;
		var prevSamplers = this.samplers;

		this.attributes = { };
		this.uniforms   = { };
		this.samplers   = { };
		this.vsSrc      = vsrc;
		this.fsSrc      = fsrc;
		this.program    = new SglProgram(this.ui.gl, [vsrc], [fsrc]);

		ret.status = this.program.isValid;
		ret.log    = this.program.log;

		if (!ret.status) {
			return ret;
		}

		for (var a in this.program.attributes) {
			var src = this.program.attributes[a];
			var dst = {
				name     : src._info.name,
				type     : src._info.nativeType,
				value    : ""
			};
			this.attributes[a] = dst;
		}

		this.program.bind();
		for (var u in this.program.uniforms) {
			var src = this.program.uniforms[u];
			var sem = (this.semantics[src._info.name] != undefined);
			var dst = {
				name     : src._info.name,
				type     : src._info.nativeType,
				value    : null,
				desc     : ((sem) ? (this.semantics[src._info.name].desc) : ("")),
				builtin  : sem
			};

			var type = src._info.nativeType;

			if (!dst.builtin) {
				var updated = false;

				if (u in prevUniforms) {
					dst.desc = prevUniforms[u].desc;
					if (prevUniforms[u].type == type) {
						dst.value = prevUniforms[u].value;
						updated = true;
					}
				}

				if (!updated) {
					if (type == gl.BOOL) {
						dst.value = false;
					}
					else if (type == gl.BOOL_VEC2) {
						dst.value = [ false, false ];
					}
					else if (type == gl.BOOL_VEC3) {
						dst.value = [ false, false, false ];
					}
					else if (type == gl.BOOL_VEC4) {
						dst.value = [ false, false, false, false ];
					}
					else if (type == gl.INT) {
						dst.value = 0;
					}
					else if (type == gl.INT_VEC2) {
						dst.value = [ 0, 0 ];
					}
					else if (type == gl.INT_VEC3) {
						dst.value = [ 0, 0, 0 ];
					}
					else if (type == gl.INT_VEC4) {
						dst.value = [ 0, 0, 0, 0 ];
					}
					else if (type == gl.FLOAT) {
						dst.value = 0.0;
					}
					else if (type == gl.FLOAT_VEC2) {
						dst.value = [ 0.0, 0.0, ];
					}
					else if (type == gl.FLOAT_VEC3) {
						dst.value = [ 0.0, 0.0, 0.0 ];
					}
					else if (type == gl.FLOAT_VEC4) {
						dst.value = [ 0.0, 0.0, 0.0, 0.0 ];
					}
					else if (type == gl.FLOAT_MAT2) {
						dst.value = [
							1.0, 0.0,
							1.0, 0.0
						];
					}
					else if (type == gl.FLOAT_MAT3) {
						dst.value = [
							1.0, 0.0, 0.0,
							0.0, 1.0, 0.0,
							0.0, 0.0, 1.0
						];
					}
					else if (type == gl.FLOAT_MAT4) {
						dst.value = [
							1.0, 0.0, 0.0, 0.0,
							0.0, 1.0, 0.0, 0.0,
							0.0, 0.0, 1.0, 0.0,
							0.0, 0.0, 0.0, 1.0
						];
					}
					else {
						dst.value = 0;
					}
				}

				if (dst.value != null) {
					src.value = dst.value;
				}
			}

			this.uniforms[u] = dst;
		}
		this.program.unbind();

						if (u in prevUniforms) {
					dst.desc = prevUniforms[u].desc;
					if (prevUniforms[u].type == type) {
						dst.value = prevUniforms[u].value;
						updated = true;
					}
				}


		for (var s in this.program.samplers) {
			var src = this.program.samplers[s];
			var dst = {
				name     : src._info.name,
				type     : src._info.nativeType,
				value    : src.unit,
				url      : ((s in prevSamplers) ? (prevSamplers[s].url) : ("")),
				tex      : null
			};
			this.samplers[s] = dst;
		}

		return ret;
	},

	setupTransform : function () {
		this.xform      = new SglTransformStack();
		this.viewMatrix = sglLookAtM4C(0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
		this.trackball  = new SglTrackball();
	},

	setupSemantics : function () {
		var gl   = this.ui.gl;
		var that = this;

		this.semantics = {
			u_viewport : {
				type : gl.INT_VEC4,
				desc : "Viewport Rect (x, y, width, height)",
				func : function () { return [ 0, 0, that.ui.width, that.ui.height ]; },
			},
			u_meshBBoxDiagonal : {
				type : gl.FLOAT,
				desc : "Mesh Bounding Box Diagonal",
				func : function () { return that.bbox.diagonal; }
			},
			u_modelSpaceViewerPosition : {
				type : gl.FLOAT_VEC3,
				desc : "Model-Space Viewer Position",
				func : function () { return that.xform.modelSpaceViewerPosition; }
			},
			u_modelSpaceViewDirection : {
				type : gl.FLOAT_VEC3,
				desc : "Model-Space View Direction",
				func : function () { return that.xform.modelSpaceViewDirection; }
			},
			u_worldSpaceViewerPosition : {
				type : gl.FLOAT_VEC3,
				desc : "World-Space Viewer Position",
				func : function () { return that.xform.worldSpaceViewerPosition; }
			},
			u_worldSpaceViewDirection : {
				type : gl.FLOAT_VEC3,
				desc : "World-Space View Direction",
				func : function () { return that.xform.worldSpaceViewDirection; }
			},
			u_meshBBoxMin : {
				type : gl.FLOAT_VEC3,
				desc : "Mesh Bounding Box Minimum",
				func : function () { return that.bbox.min; }
			},
			u_meshBBoxMax : {
				type : gl.FLOAT_VEC3,
				desc : "Mesh Bounding Box Maximum",
				func : function () { return that.bbox.max; }
			},
			u_meshBBoxCenter : {
				type : gl.FLOAT_VEC3,
				desc : "Mesh Bounding Box Center",
				func : function () { return that.bbox.center; }
			},
			u_meshBBoxSize : {
				type : gl.FLOAT_VEC3,
				desc : "Mesh Bounding Box Size",
				func : function () { return that.bbox.size; }
			},
			u_worldSpaceNormalMatrix : {
				type : gl.FLOAT_MAT3,
				desc : "World-Space Normal Matrix",
				func : function () { return that.xform.worldSpaceNormalMatrix; }
			},
			u_viewSpaceNormalMatrix : {
				type : gl.FLOAT_MAT3,
				desc : "View-Space Normal Matrix",
				func : function () { return that.xform.viewSpaceNormalMatrix; }
			},
			u_modelMatrix : {
				type : gl.FLOAT_MAT4,
				desc : "Model Matrix",
				func : function () { return that.xform.modelMatrix; }
			},
			u_modelMatrixInverse : {
				type : gl.FLOAT_MAT4,
				desc : "Model Matrix Inverse",
				func : function () { return that.xform.modelMatrixInverse; }
			},
			u_modelMatrixTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "Model Matrix Transpose",
				func : function () { return that.xform.modelMatrixTranspose; }
			},
			u_modelMatrixInverseTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "Model Matrix Inverse Transpose",
				func : function () { return that.xform.modelMatrixInverseTranspose; }
			},
			u_viewMatrix : {
				type : gl.FLOAT_MAT4,
				desc : "View Matrix",
				func : function () { return that.xform.viewMatrix; }
			},
			u_viewMatrixInverse : {
				type : gl.FLOAT_MAT4,
				desc : "View Matrix Inverse",
				func : function () { return that.xform.viewMatrixInverse; }
			},
			u_viewMatrixTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "View Matrix Transpose",
				func : function () { return that.xform.viewMatrixTranspose; }
			},
			u_viewMatrixInverseTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "View Matrix Inverse Transpose",
				func : function () { return that.xform.viewMatrixInverseTranspose; }
			},
			u_projectionMatrix : {
				type : gl.FLOAT_MAT4,
				desc : "Projection Matrix",
				func : function () { return that.xform.projectionMatrix; }
			},
			u_projectionMatrixInverse : {
				type : gl.FLOAT_MAT4,
				desc : "Projection Matrix Inverse",
				func : function () { return that.xform.projectionMatrixInverse; }
			},
			u_projectionMatrixTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "Projection Matrix Transpose",
				func : function () { return that.xform.projectionMatrixTranspose; }
			},
			u_projectionMatrixInverseTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "Projection Matrix Inverse Transpose",
				func : function () { return that.xform.projectionMatrixInverseTranspose; }
			},
			u_modelViewMatrix : {
				type : gl.FLOAT_MAT4,
				desc : "Model-View Matrix",
				func : function () { return that.xform.modelViewMatrix; }
			},
			u_modelViewMatrixInverse : {
				type : gl.FLOAT_MAT4,
				desc : "Model-View Matrix Inverse",
				func : function () { return that.xform.modelViewMatrixInverse; }
			},
			u_modelViewMatrixTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "Model-View Matrix Transpose",
				func : function () { return that.xform.modelViewMatrixTranspose; }
			},
			u_modelViewMatrixInverseTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "Model-View Matrix Inverse Transpose",
				func : function () { return that.xform.modelViewMatrixInverseTranspose; }
			},
			u_viewProjectionMatrix : {
				type : gl.FLOAT_MAT4,
				desc : "View-Projection Matrix",
				func : function () { return that.xform.viewProjectionMatrix; }
			},
			u_viewProjectionMatrixInverse : {
				type : gl.FLOAT_MAT4,
				desc : "View-Projection Matrix Inverse",
				func : function () { return that.xform.viewProjectionMatrixInverse; }
			},
			u_viewProjectionMatrixTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "View-Projection Matrix Transpose",
				func : function () { return that.xform.viewProjectionMatrixTranspose; }
			},
			u_viewProjectionMatrixInverseTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "View-Projection Matrix Inverse Transpose",
				func : function () { return that.xform.viewProjectionMatrixInverseTranspose; }
			},
			u_modelViewProjectionMatrix : {
				type : gl.FLOAT_MAT4,
				desc : "Model-View-Projection Matrix",
				func : function () { return that.xform.modelViewProjectionMatrix; }
			},
			u_modelViewProjectionMatrixInverse : {
				type : gl.FLOAT_MAT4,
				desc : "Model-View-Projection Matrix Inverse",
				func : function () { return that.xform.modelViewProjectionMatrixInverse; }
			},
			u_modelViewProjectionMatrixTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "Model-View-Projection Matrix Transpose",
				func : function () { return that.xform.modelViewProjectionMatrixTranspose; }
			},
			u_modelViewProjectionMatrixInverseTranspose : {
				type : gl.FLOAT_MAT4,
				desc : "Model-View-Projection Matrix Inverse Transpose",
				func : function () { return that.xform.modelViewProjectionMatrixInverseTranspose; }
			}
		};
	},

	setupEnvironment : function () {
		this.bkgColor = [ 0.2, 0.2, 0.2, 1.0 ];
	},

	setBackgroundColor : function (r, g, b) {
		this.bkgColor = [ r, g, b, 1.0 ];
		this.refresh();
	},

	getBackgroundColor : function () {
		return this.bkgColor.slice(0, 3);
	},

	resetView : function () {
		this.trackball.reset();
		this.refresh();
	},

	loadMesh : function (url) {
		var that   = this;
		var meshJS = new SglMeshJS();

		meshJS.importOBJ(url, true, function(m, url) {
			that.setupMesh(meshJS, url);
		});
	},

	unloadMesh : function () {
		this.setupDefaultMesh();
		this.refresh();
	},

	get meshLoaded() {
		return ((this.mesh) ? (true) : (false));
	},

	getMeshInfo : function () {
		return this.meshInfo;
	},

	validateProgramSources : function (vsrc, fsrc) {
		var ret = {
			status : false,
			log    : ""
		};

		var prog = new SglProgram(this.ui.gl, [vsrc], [fsrc]);
		ret.status = prog.isValid;
		ret.log    = prog.log;
		prog.destroy();

		return ret;
	},

	setProgramSources : function (vsrc, fsrc) {
		var r = this.setupProgram(vsrc, fsrc);
		if (!r.status) {
			this.setupDefaultProgram();
		}
		this.refresh();
		return r;
	},

	getProgramSources : function () {
		return [ this.vsSrc, this.fsSrc ];
	},

	get programCompiled() {
		return this.program.isValid;
	},

	getProgramLog : function () {
		return this.program.log;
	},

	getProgramUniforms : function () {
		return this.uniforms;
	},

	getProgramSamplers : function () {
		return this.samplers;
	},

	getPredefinedUniforms : function () {
		return this.semantics;
	},

	getPredefinedAttributes : function () {
		var attribs = { };
		if (this.mesh) {
			for (var v in this.mesh.vertices.attributes) {
				var attr = {
					name : "a_" + v,
					size : this.mesh.vertices.attributes[v].size
				};
				attribs[attr.name] = attr;
			}
		}
		return attribs;
	},

	setUniforms : function (uniforms) {
		for (var u in uniforms) {
			if (this.uniforms[u] != undefined) {
				if (!this.uniforms[u].bulitin) {
					this.uniforms[u].value = uniforms[u].value;
				}
			}
		}
		this.refresh();
	},

	setSamplers : function (samplers) {
		var gl = this.ui.gl;
		var that = this;

		for (var s in samplers) {
			var url = samplers[s];
			if (s in this.samplers) {
				if (this.samplers[s].tex) {
					this.samplers[s].tex.destroy();
					this.samplers[s].tex = null;
				}
				this.samplers[s].url = url;
				var opt = {
					onload : function() { that.refresh(); }
				};
				this.samplers[s].tex = new SglTexture2D(gl, url, opt);
			}
		}

		this.refresh();
	},

	buildPageJSON : function() {
		var code = "";

		code += "{\n";

		code += "  \"backgroundColor\" : [" + this.bkgColor.join(", ") + "],\n";
		code += "  \"modelURL\"        : \"" + this.meshURL + "\",\n";
		code += "  \"vertexShader\"    : \"" + escape(this.vsSrc.replace(/[ \t\n\r]+/g, " ")) + "\",\n";
		code += "  \"fragmentShader\"  : \"" + escape(this.fsSrc.replace(/[ \t\n\r]+/g, " ")) + "\",\n";

		var uniformsCount = 0;
		for (var u in this.uniforms) {
			var src = this.uniforms[u];
			if (!src.builtin) {
				uniformsCount++;
			}
		}

		code += "  \"uniforms\"        : {\n";
		var count = 0;
		for (var u in this.uniforms) {
			var src = this.uniforms[u];
			if (!src.builtin) {
				count++;
				code += "    \"" + u + "\" : ";
				if (src.value instanceof Array) {
					code += "[" + src.value.join(",") + "]";
				}
				else {
					code += src.value;
				}
				if (count < uniformsCount) {
					code += ",";
				}
				code += "\n";
			}
		}
		code += "  },\n";

		var samplersCount = 0;
		for (var s in this.samplers) {
			var src = this.samplers[s];
			if (src) {
				samplersCount++;
			}
		}

		code += "  \"samplers\"        : {\n";
		count = 0;
		for (var s in this.samplers) {
			var src = this.samplers[s];
			if (src) {
				count++;
				code += "    \"" + s + "\" : \"" + src.url + "\"";
				if (count < samplersCount) {
					code += ",";
				}
				code += "\n";
			}
		}
		code += "  }\n";

		code += "}\n";

		return code;
	},

	buildPageHTML : function() {
		var code = "";
		code += "<script type=\"text/javascript\" src=\"spidergl.js\"></script>\n";
		code += "<script type=\"text/javascript\" src=\"meshadeviewer.js\"></script>\n";
		code += "<script type=\"text/javascript\">MeShadeSceneViewer(\"__CANVAS_NAME__\", \"__JSON_URL__\");</script>\n";
		return code;
	},

	buildPage : function() {
		var ret = {
			srcJSON : this.buildPageJSON(),
			srcHTML : this.buildPageHTML()
		};
		return ret;
	},

	refresh : function () {
		this.ui.requestDraw();
	},

	load : function(gl) {
		//log("SpiderGL Version : " + SGL_VERSION_STRING + "\n");

		// misc
		/*********************************************************************************/
		this.doTriad     = false;
		this.triadMesh   = null;
		this.doBBox      = false;
		this.boxMesh     = null;
		this.lineProgram = null;
		this.setupLines();
		/*********************************************************************************/


		// semantics
		/*********************************************************************************/
		this.semantics = { };
		this.setupSemantics();
		/*********************************************************************************/


		// program
		/*********************************************************************************/
		this.vsSrc      = "";
		this.fsSrc      = "";
		this.attributes = { };
		this.uniforms   = { };
		this.samplers   = { };
		this.program    = null;
		this.setupDefaultProgram();
		/*********************************************************************************/


		// mesh
		/*********************************************************************************/
		this.meshURL = "";
		this.mesh = null;
		this.bbox = new SglBox3();
		this.meshInfo = {
			vertexAttributes : [ ],
			verticesCount    : 0,
			trianglesCount   : 0
		};
		this.centerMatrix = sglIdentityM4();
		this.boxMatrix    = sglIdentityM4();
		this.setupDefaultMesh();
		/*********************************************************************************/


		// transformation
		/*********************************************************************************/
		this.xform      = new SglTransformStack();
		this.viewMatrix = sglLookAtM4C(0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
		this.trackball  = new SglTrackball();
		this.setupTransform();
		/*********************************************************************************/


		// environment
		/*********************************************************************************/
		this.bkgColor = [ 0.0, 0.0, 0.0, 1.0 ];
		this.setupEnvironment();
		/*********************************************************************************/
	},

	unload : function(gl) {
		;
	},

	keyDown : function(gl, keyCode, keyString) {
		;
	},

	keyUp : function(gl, keyCode, keyString) {
		;
	},

	keyPress : function(gl, keyCode, keyString) {
		if (keyString == "R") {
			this.trackball.reset();
		}
	},

	mouseDown : function(gl, button, x, y) {
		;
	},

	mouseUp : function(gl, button, x, y) {
		;
	},

	mouseMove : function(gl, x, y) {
		var ui = this.ui;
		var ax1 = (x / (ui.width  - 1)) * 2.0 - 1.0;
		var ay1 = (y / (ui.height - 1)) * 2.0 - 1.0;
		var action = SGL_TRACKBALL_NO_ACTION;
		if ((ui.mouseButtonsDown[0] && ui.keysDown[17]) || ui.mouseButtonsDown[1]) {
			action = SGL_TRACKBALL_PAN;
		}
		else if (ui.mouseButtonsDown[0]) {
			action = SGL_TRACKBALL_ROTATE;
		}
		this.trackball.action = action;
		this.trackball.track(this.viewMatrix, ax1, ay1, 0.0);
		this.trackball.action = SGL_TRACKBALL_NO_ACTION;
	},

	mouseWheel: function(gl, wheelDelta, x, y) {
		var action = (this.ui.keysDown[16]) ? (SGL_TRACKBALL_DOLLY) : (SGL_TRACKBALL_SCALE);
		var factor = (action == SGL_TRACKBALL_DOLLY) ? (wheelDelta * 0.3) : ((wheelDelta < 0.0) ? (1.10) : (0.90));
		this.trackball.action = action;
		this.trackball.track(this.viewMatrix, 0.0, 0.0, factor);
		this.trackball.action = SGL_TRACKBALL_NO_ACTION;
	},

	click : function(gl, button, x, y) {
		;
	},

	dblClick : function(gl, button, x, y) {
		;
	},

	resize : function(gl, width, height) {
		;
	},

	update : function(gl, dt) {
		;
	},

	draw : function(gl) {
		var w = this.ui.width;
		var h = this.ui.height;

		gl.clearColor(this.bkgColor[0], this.bkgColor[1], this.bkgColor[2], this.bkgColor[3]);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

		gl.viewport(0, 0, w, h);

		this.xform.projection.loadIdentity();
		this.xform.projection.perspective(sglDegToRad(45.0), w/h, 0.1, 10.0);

		this.xform.view.load(this.viewMatrix);

		this.xform.model.load(this.trackball.matrix);

		if (this.mesh && this.program.isValid) {
			gl.enable(gl.DEPTH_TEST);
			gl.enable(gl.CULL_FACE);

			this.xform.model.push();
				this.xform.model.multiply(this.centerMatrix);

				var attributes = { };
				/*
				for (var a in this.attributes) {
					var src = this.attributes[a];
					attributes[a] = src.value;
				}
				*/

				var uniforms = { };
				for (var u in this.uniforms) {
					var src   = this.uniforms[u];
					var value = null;
					if (this.semantics[src.name] != undefined) {
						value = this.semantics[src.name].func();
					}
					else {
						value = src.value;
					}
					uniforms[u] = value;
				}

				var samplers = { };
				for (var s in this.samplers) {
					var src     = this.samplers[s];
					if (src.tex) {
						if (src.tex.isValid) {
							samplers[s] = src.tex;
						}
					}
				}

				sglRenderMeshGLPrimitives(this.mesh, "triangles", this.program, attributes, uniforms, samplers);
			this.xform.model.pop();

			if (this.doBBox) {
				this.xform.model.push();
					this.xform.model.multiply(this.boxMatrix);
					sglRenderMeshGLPrimitives(this.boxMesh, "lines", this.lineProgram, null, { u_modelViewProjectionMatrix : this.xform.modelViewProjectionMatrix });
				this.xform.model.pop();
			}

			gl.disable(gl.DEPTH_TEST);
			gl.disable(gl.CULL_FACE);
		}

		if (this.doTriad) {
			sglRenderMeshGLPrimitives(this.triadMesh, "lines", this.lineProgram, null, { u_modelViewProjectionMatrix : this.xform.modelViewProjectionMatrix });
		}
	}
};

function MeShadeSceneViewer(canvasID, sceneURL) {
	window.addEventListener("load", function() {
		var canvas = document.getElementById(canvasID);
		canvas.width  = canvas.clientWidth;
		canvas.height = canvas.clientHeight;

		var viewer = new MeShadeViewer();
		sglRegisterLoadedCanvas(canvasID, viewer, 0.0);
		viewer.loadScene(sceneURL);
	},
	false);
}

