function generateGeometryId() {
		return (Math.random() * 100000)|0;
	}
******************************

	function LGraphShaderConstant()
	{
		this.addOutput("","float");

		this.properties = {
			type: "float",
			value: 0
		};

		this.addWidget("combo","type","float",null, { values: GLSL_types_const, property: "type" } );
		this.updateWidgets();
	}

	LGraphShaderConstant.title = "const";

	LGraphShaderConstant.prototype.getTitle = function()
	{
		if(this.flags.collapsed)
			return valueToGLSL( this.properties.value, this.properties.type );
		return "Const";
	}

	LGraphShaderConstant.prototype.onPropertyChanged = function(name,value)
	{
		var that = this;
		if(name == "type")
		{
			if(this.outputs[0].type != value)
			{
				this.disconnectOutput(0);
				this.outputs[0].type = value;
				this.widgets.length = 1; //remove extra widgets
				this.updateWidgets();
			}
		}
	}

	LGraphShaderConstant.prototype.updateWidgets = function( old_value )
	{
		var that = this;
		var old_value = this.properties.value;
		var options = { step: 0.01 };
		switch(this.properties.type)
		{
			case 'float': 
				this.properties.value = 0;
				this.addWidget("number","v",0,{ step:0.01, property: "value" });
				break;
			case 'vec2': 
				this.properties.value = old_value && old_value.length == 2 ? [old_value[0],old_value[1]] : [0,0,0];
				this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; }); 
				this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; }); 
				break;
			case 'vec3': 
				this.properties.value = old_value && old_value.length == 3 ? [old_value[0],old_value[1],old_value[2]] : [0,0,0];
				this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; }); 
				this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; }); 
				this.addWidget("number","z",0,options, function(v){ that.properties.value[2] = v; }); 
				break;
			case 'vec4': 
				this.properties.value = old_value && old_value.length == 4 ? [old_value[0],old_value[1],old_value[2],old_value[3]] : [0,0,0,0];
				this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; }); 
				this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; }); 
				this.addWidget("number","z",0,options, function(v){ that.properties.value[2] = v; }); 
				this.addWidget("number","w",0,options, function(v){ that.properties.value[3] = v; }); 
				break;
			default:
				console.error("unknown type for constant");
		}
	}

	LGraphShaderConstant.prototype.onGetCode = function( context )
	{
		var value = valueToGLSL( this.properties.value, this.properties.type );
		var link_name = getOutputLinkID(this,0);
		if(!link_name) //not connected
			return;

		var code = "	" + this.properties.type + " " + link_name + " = " + value + ";";
		context.addCode( "code", code, this.shader_destination );

		this.setOutputData( 0, this.properties.type );
	}

	registerShaderNode( "const/const", LGraphShaderConstant );

	function LGraphShaderVec2()
	{
		this.addInput("xy","vec2");
		this.addInput("x","float");
		this.addInput("y","float");
		this.addOutput("xy","vec2");
		this.addOutput("x","float");
		this.addOutput("y","float");

		this.properties = { x: 0, y: 0 };
	}

	LGraphShaderVec2.title = "vec2";
	LGraphShaderVec2.varmodes = ["xy","x","y"];

	LGraphShaderVec2.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderVec2.prototype.onGetCode = function( context )
	{
		var props = this.properties;

		var varname = getShaderNodeVarName(this);
		var code = "	vec2 " + varname + " = " + valueToGLSL([props.x,props.y]) + ";\n";

		for(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i)
		{
			var varmode = LGraphShaderVec2.varmodes[i];
			var inlink = getInputLinkID(this,i);
			if(!inlink)
				continue;
			code += "	" + varname + "."+varmode+" = " + inlink + ";\n";
		}

		for(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i)
		{
			var varmode = LGraphShaderVec2.varmodes[i];
			var outlink = getOutputLinkID(this,i);
			if(!outlink)
				continue;
			var type = GLSL_types_const[varmode.length - 1];
			code += "	"+type+" " + outlink + " = " + varname + "." + varmode + ";\n";
			this.setOutputData( i, type );
		}

		context.addCode( "code", code, this.shader_destination );
	}

	registerShaderNode( "const/vec2", LGraphShaderVec2 );	

	function LGraphShaderVec3()
	{
		this.addInput("xyz","vec3");
		this.addInput("x","float");
		this.addInput("y","float");
		this.addInput("z","float");
		this.addInput("xy","vec2");
		this.addInput("xz","vec2");
		this.addInput("yz","vec2");
		this.addOutput("xyz","vec3");
		this.addOutput("x","float");
		this.addOutput("y","float");
		this.addOutput("z","float");
		this.addOutput("xy","vec2");
		this.addOutput("xz","vec2");
		this.addOutput("yz","vec2");

		this.properties = { x:0, y: 0, z: 0 };
	}

	LGraphShaderVec3.title = "vec3";
	LGraphShaderVec3.varmodes = ["xyz","x","y","z","xy","xz","yz"];

	LGraphShaderVec3.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}


	LGraphShaderVec3.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderVec3.prototype.onGetCode = function( context )
	{
		var props = this.properties;

		var varname = getShaderNodeVarName(this);
		var code = "vec3 " + varname + " = " + valueToGLSL([props.x,props.y,props.z]) + ";\n";

		for(var i = 0;i < LGraphShaderVec3.varmodes.length; ++i)
		{
			var varmode = LGraphShaderVec3.varmodes[i];
			var inlink = getInputLinkID(this,i);
			if(!inlink)
				continue;
			code += "	" + varname + "."+varmode+" = " + inlink + ";\n";
		}

		for(var i = 0; i < LGraphShaderVec3.varmodes.length; ++i)
		{
			var varmode = LGraphShaderVec3.varmodes[i];
			var outlink = getOutputLinkID(this,i);
			if(!outlink)
				continue;
			var type = GLSL_types_const[varmode.length - 1];
			code += "	"+type+" " + outlink + " = " + varname + "." + varmode + ";\n";
			this.setOutputData( i, type );
		}

		context.addCode( "code", code, this.shader_destination );
	}

	registerShaderNode( "const/vec3", LGraphShaderVec3 );	


	function LGraphShaderVec4()
	{
		this.addInput("xyzw","vec4");
		this.addInput("xyz","vec3");
		this.addInput("x","float");
		this.addInput("y","float");
		this.addInput("z","float");
		this.addInput("w","float");
		this.addInput("xy","vec2");
		this.addInput("yz","vec2");
		this.addInput("zw","vec2");
		this.addOutput("xyzw","vec4");
		this.addOutput("xyz","vec3");
		this.addOutput("x","float");
		this.addOutput("y","float");
		this.addOutput("z","float");
		this.addOutput("xy","vec2");
		this.addOutput("yz","vec2");
		this.addOutput("zw","vec2");

		this.properties = { x:0, y: 0, z: 0, w: 0 };
	}

	LGraphShaderVec4.title = "vec4";
	LGraphShaderVec4.varmodes = ["xyzw","xyz","x","y","z","w","xy","yz","zw"];

	LGraphShaderVec4.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderVec4.prototype.onGetCode = function( context )
	{
		var props = this.properties;

		var varname = getShaderNodeVarName(this);
		var code = "vec4 " + varname + " = " + valueToGLSL([props.x,props.y,props.z,props.w]) + ";\n";

		for(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i)
		{
			var varmode = LGraphShaderVec4.varmodes[i];
			var inlink = getInputLinkID(this,i);
			if(!inlink)
				continue;
			code += "	" + varname + "."+varmode+" = " + inlink + ";\n";
		}

		for(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i)
		{
			var varmode = LGraphShaderVec4.varmodes[i];
			var outlink = getOutputLinkID(this,i);
			if(!outlink)
				continue;
			var type = GLSL_types_const[varmode.length - 1];
			code += "	"+type+" " + outlink + " = " + varname + "." + varmode + ";\n";
			this.setOutputData( i, type );
		}

		context.addCode( "code", code, this.shader_destination );

	}

	registerShaderNode( "const/vec4", LGraphShaderVec4 );	
	
	//*********************************

	function LGraphShaderFragColor() {
		this.addInput("color", "float,vec2,vec3,vec4");
		this.block_delete = true;
	}

	LGraphShaderFragColor.title = "FragColor";
	LGraphShaderFragColor.desc = "Pixel final color";

	LGraphShaderFragColor.prototype.onGetCode = function( context )
	{
		var link_name = getInputLinkID( this, 0 );
		if(!link_name)
			return;

		var code = link_name;
		var type = this.getInputDataType(0);
		if(type == "float")
			code = "vec4(" + code + ");";
		else if(type == "vec2")
			code = "vec4(" + code + ",0.0,1.0);";
		else if(type == "vec3")
			code = "vec4(" + code + ",1.0);";

		context.addCode("fs_code", "color = " + code + ";");
	}

	registerShaderNode( "output/fragcolor", LGraphShaderFragColor );



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

	function LGraphShaderMath()
	{
		this.addInput("A","float,vec2,vec3,vec4");
		this.addInput("B","float,vec2,vec3,vec4");
		this.addOutput("out","");
		this.properties = {
			func: "floor"
		};
		this._current = "floor";
		this.addWidget("combo","func",this.properties.func,{ property: "func", values: GLSL_functions_name });
	}

	LGraphShaderMath.title = "Math";

	LGraphShaderMath.prototype.onPropertyChanged = function(name,value)
	{
		this.graph._version++;

		if(name == "func")
		{
			var func_desc = GLSL_functions[ value ];
			if(!func_desc)
				return;

			//remove extra inputs
			for(var i = func_desc.params.length; i < this.inputs.length; ++i)
				this.removeInput(i);

			//add and update inputs
			for(var i = 0; i < func_desc.params.length; ++i)
				if( this.inputs[i] )
					this.inputs[i].name = func_desc.params[i].name;
				else
					this.addInput( func_desc.params[i].name, "float,vec2,vec3,vec4" );
		}
	}

	LGraphShaderMath.prototype.getTitle = function()
	{
		if(this.flags.collapsed)
			return this.properties.func;
		else
			return "Func";
	}

	LGraphShaderMath.prototype.onGetCode = function( context )
	{
		if(!this.isOutputConnected(0))
			return;

		var inlinks = [];
		for(var i = 0; i < 3; ++i)
			inlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || "float" } );

		var outlink = getOutputLinkID(this,0);
		if(!outlink) //not connected
			return;

		var func_desc = GLSL_functions[ this.properties.func ];
		if(!func_desc)
			return;

		//func_desc
		var base_type = inlinks[0].type;
		var return_type = func_desc.return_type;
		if( return_type == "T" )
			return_type = base_type;

		var params = [];
		for(var i = 0; i < func_desc.params.length; ++i)
		{
			var p = func_desc.params[i];
			var param_code = inlinks[i].name;
			if(param_code == null) //not plugged
			{
				param_code = "(1.0)";
				inlinks[i].type = "float";
			}
			if( (p.type == "T" && inlinks[i].type != base_type) ||
				(p.type != "T" && inlinks[i].type != base_type) )
				param_code = convertVarToGLSLType( inlinks[i].name, inlinks[i].type, base_type );
			params.push( param_code );
		}
		
		context.addCode("code", return_type + " " + outlink + " = "+func_desc.func+"("+params.join(",")+");", this.shader_destination );

		this.setOutputData( 0, return_type );
	}

	registerShaderNode( "math/func", LGraphShaderMath );



	function LGraphShaderSnippet()
	{
		this.addInput("A","float,vec2,vec3,vec4");
		this.addInput("B","float,vec2,vec3,vec4");
		this.addOutput("C","vec4");
		this.properties = {
			code:"C = A+B",
			type: "vec4"
		}
		this.addWidget("text","code",this.properties.code,{ property: "code" });
		this.addWidget("combo","type",this.properties.type,{ values:["float","vec2","vec3","vec4"], property: "type" });
	}

	LGraphShaderSnippet.title = "Snippet";

	LGraphShaderSnippet.prototype.onPropertyChanged = function(name,value)
	{
		 this.graph._version++;

		if(name == "type"&& this.outputs[0].type != value)
		{
			this.disconnectOutput(0);
			this.outputs[0].type = value;
		}
	}

	LGraphShaderSnippet.prototype.getTitle = function()
	{
		if(this.flags.collapsed)
			return this.properties.code;
		else
			return "Snippet";
	}

	LGraphShaderSnippet.prototype.onGetCode = function( context )
	{
		if(!this.isOutputConnected(0))
			return;

		var inlinkA = getInputLinkID(this,0);
		if(!inlinkA)
			inlinkA = "1.0";
		var inlinkB = getInputLinkID(this,1);
		if(!inlinkB)
			inlinkB = "1.0";
		var outlink = getOutputLinkID(this,0);
		if(!outlink) //not connected
			return;

		var inA_type = this.getInputData(0) || "float";
		var inB_type = this.getInputData(1) || "float";
		var return_type = this.properties.type;

		//cannot resolve input
		if(inA_type == "T" || inB_type == "T")
		{
			return null;
		}

		var funcname = "funcSnippet" + this.id;

		var func_code = "\n" + return_type + " " + funcname + "( " + inA_type + " A, " + inB_type + " B) {\n";
		func_code += "	" + return_type + " C = " + return_type + "(0.0);\n";
		func_code += "	" + this.properties.code + ";\n";
		func_code += "	return C;\n}\n";

		context.addCode("functions", func_code, this.shader_destination );
		context.addCode("code", return_type + " " + outlink + " = "+funcname+"("+inlinkA+","+inlinkB+");", this.shader_destination );

		this.setOutputData( 0, return_type );
	}

	registerShaderNode( "utils/snippet", LGraphShaderSnippet );


	//************************************
	function LGraphShaderRemap()
	{
		this.addInput("","T,float,vec2,vec3,vec4");
		this.addOutput("","T");
		this.properties = {
			min_value: 0,
			max_value: 1,
			min_value2: 0,
			max_value2: 1
		};
		this.addWidget("number","min",0,{ step: 0.1, property: "min_value" });
		this.addWidget("number","max",1,{ step: 0.1, property: "max_value" });
		this.addWidget("number","min2",0,{ step: 0.1, property: "min_value2"});
		this.addWidget("number","max2",1,{ step: 0.1, property: "max_value2"});
	}

	LGraphShaderRemap.title = "Remap";

	LGraphShaderRemap.prototype.onPropertyChanged = function()
	{
		 this.graph._version++;
	}

	LGraphShaderRemap.prototype.onConnectionsChange = function()
	{
		var return_type = this.getInputDataType(0);
		this.outputs[0].type = return_type || "T";
	}

	LGraphShaderRemap.prototype.onGetCode = function( context )
	{
		if(!this.isOutputConnected(0))
			return;

		var inlink = getInputLinkID(this,0);
		var outlink = getOutputLinkID(this,0);
		if(!inlink && !outlink) //not connected
			return;

		var return_type = this.getInputDataType(0);
		this.outputs[0].type = return_type;
		if(return_type == "T")
		{
			console.warn("node type is T and cannot be resolved");
			return;
		}

		if(!inlink)
		{
			context.addCode("code","	" + return_type + " " + outlink + " = " + return_type + "(0.0);\n");
			return;
		}

		var minv = valueToGLSL( this.properties.min_value );
		var maxv = valueToGLSL( this.properties.max_value );
		var minv2 = valueToGLSL( this.properties.min_value2 );
		var maxv2 = valueToGLSL( this.properties.max_value2 );

		context.addCode("code", return_type + " " + outlink + " = ( (" + inlink + " - "+minv+") / ("+ maxv+" - "+minv+") ) * ("+ maxv2+" - "+minv2+") + " + minv2 + ";", this.shader_destination );
		this.setOutputData( 0, return_type );
	}

	registerShaderNode( "math/remap", LGraphShaderRemap );

})(this);


/*
// https://blog.undefinist.com/writing-a-shader-graph/

\sin
f,Out
float->float
{1} = sin({0});

\mul
A,B,Out
T,T->T
T,float->T
{2} = {0} * {1};

\clamp
f,min,max,Out
float,float=0,float=1->float
vec2,vec2=vec2(0.0),vec2=vec2(1.0)->vec2
vec3,vec3=vec3(0.0),vec3=vec3(1.0)->vec3
vec4,vec4=vec4(0.0),vec4=vec4(1.0)->vec4
{3}=clamp({0},{1},{2});

\mix
A,B,f,Out
float,float,float->float
vec2,vec2,float->vec2
vec3,vec3,float->vec3
vec4,vec4,float->vec4
{3} = mix({0},{1},{2});