"use strict";

var Ops=Ops || {};
Ops.Ui=Ops.Ui || {};
Ops.Gl=Ops.Gl || {};
Ops.Exp=Ops.Exp || {};
Ops.Math=Ops.Math || {};
Ops.Json=Ops.Json || {};
Ops.Html=Ops.Html || {};
Ops.Vars=Ops.Vars || {};
Ops.Anim=Ops.Anim || {};
Ops.Audio=Ops.Audio || {};
Ops.Value=Ops.Value || {};
Ops.Patch=Ops.Patch || {};
Ops.Array=Ops.Array || {};
Ops.Json3d=Ops.Json3d || {};
Ops.Exp.Gl=Ops.Exp.Gl || {};
Ops.String=Ops.String || {};
Ops.Boolean=Ops.Boolean || {};
Ops.Trigger=Ops.Trigger || {};
Ops.Devices=Ops.Devices || {};
Ops.WebAudio=Ops.WebAudio || {};
Ops.TimeLine=Ops.TimeLine || {};
Ops.Gl.Phong=Ops.Gl.Phong || {};
Ops.Gl.Meshes=Ops.Gl.Meshes || {};
Ops.Gl.Matrix=Ops.Gl.Matrix || {};
Ops.Gl.Shader=Ops.Gl.Shader || {};
Ops.Deprecated=Ops.Deprecated || {};
Ops.Gl.Geometry=Ops.Gl.Geometry || {};
Ops.Gl.Textures=Ops.Gl.Textures || {};
Ops.Math.Compare=Ops.Math.Compare || {};
Ops.Json3d.Bones=Ops.Json3d.Bones || {};
Ops.Devices.Mouse=Ops.Devices.Mouse || {};
Ops.Gl.ShaderEffects=Ops.Gl.ShaderEffects || {};
Ops.Deprecated.Json3d=Ops.Deprecated.Json3d || {};
Ops.Gl.TextureEffects=Ops.Gl.TextureEffects || {};
Ops.Gl.TextureEffects.Noise=Ops.Gl.TextureEffects.Noise || {};

//----------------



// **************************************************************
// 
// Ops.Gl.MainLoop
// 
// **************************************************************

Ops.Gl.MainLoop = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const fpsLimit=op.inValue("FPS Limit",0);
const trigger=op.outFunction("trigger");
const width=op.outValue("width");
const height=op.outValue("height");
const reduceLoadingFPS=op.inValueBool("Reduce FPS loading");
const clear=op.inValueBool("Clear",true);
const fullscreen=op.inValueBool("Fullscreen Button",false);
const active=op.inValueBool("Active",true);
const hdpi=op.inValueBool("Hires Displays",false);

hdpi.onChange=function()
{
    if(hdpi.get()) op.patch.cgl.pixelDensity=window.devicePixelRatio;
        else op.patch.cgl.pixelDensity=1;
        
    op.patch.cgl.updateSize();
    if(CABLES.UI) gui.setLayout();
};


var cgl=op.patch.cgl;
var rframes=0;
var rframeStart=0;

if(!op.patch.cgl) op.uiAttr( { 'error': 'No webgl cgl context' } );

var identTranslate=vec3.create();
vec3.set(identTranslate, 0,0,0);
var identTranslateView=vec3.create();
vec3.set(identTranslateView, 0,0,-2);

fullscreen.onChange=updateFullscreenButton;
setTimeout(updateFullscreenButton,100);
var fsElement=null;

function updateFullscreenButton()
{
    function onMouseEnter()
    {
        if(fsElement)fsElement.style.display="block";
    }

    function onMouseLeave()
    {
        if(fsElement)fsElement.style.display="none";
    }
    
    op.patch.cgl.canvas.addEventListener('mouseleave', onMouseLeave);
    op.patch.cgl.canvas.addEventListener('mouseenter', onMouseEnter);

    if(fullscreen.get())
    {
        if(!fsElement) 
        {
            fsElement = document.createElement('div');

            var container = op.patch.cgl.canvas.parentElement;
            if(container)container.appendChild(fsElement);
    
            fsElement.addEventListener('mouseenter', onMouseEnter);
            fsElement.addEventListener('click', function(e)
            {
                if(CABLES.UI && !e.shiftKey) gui.cycleRendererSize();
                    else
                    {
                        cgl.fullScreen();
                    }
            });
        }

        fsElement.style.padding="10px";
        fsElement.style.position="absolute";
        fsElement.style.right="5px";
        fsElement.style.top="5px";
        fsElement.style.width="20px";
        fsElement.style.height="20px";
        // fsElement.style.opacity="1.0";
        fsElement.style.cursor="pointer";
        fsElement.style['border-radius']="40px";
        fsElement.style.background="#444";
        fsElement.style["z-index"]="9999";
        fsElement.style.display="none";
        fsElement.innerHTML='<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 490 490" style="width:20px;height:20px;" xml:space="preserve" width="512px" height="512px"><g><path d="M173.792,301.792L21.333,454.251v-80.917c0-5.891-4.776-10.667-10.667-10.667C4.776,362.667,0,367.442,0,373.333V480     c0,5.891,4.776,10.667,10.667,10.667h106.667c5.891,0,10.667-4.776,10.667-10.667s-4.776-10.667-10.667-10.667H36.416     l152.459-152.459c4.093-4.237,3.975-10.99-0.262-15.083C184.479,297.799,177.926,297.799,173.792,301.792z" fill="#FFFFFF"/><path d="M480,0H373.333c-5.891,0-10.667,4.776-10.667,10.667c0,5.891,4.776,10.667,10.667,10.667h80.917L301.792,173.792     c-4.237,4.093-4.354,10.845-0.262,15.083c4.093,4.237,10.845,4.354,15.083,0.262c0.089-0.086,0.176-0.173,0.262-0.262     L469.333,36.416v80.917c0,5.891,4.776,10.667,10.667,10.667s10.667-4.776,10.667-10.667V10.667C490.667,4.776,485.891,0,480,0z" fill="#FFFFFF"/><path d="M36.416,21.333h80.917c5.891,0,10.667-4.776,10.667-10.667C128,4.776,123.224,0,117.333,0H10.667     C4.776,0,0,4.776,0,10.667v106.667C0,123.224,4.776,128,10.667,128c5.891,0,10.667-4.776,10.667-10.667V36.416l152.459,152.459     c4.237,4.093,10.99,3.975,15.083-0.262c3.992-4.134,3.992-10.687,0-14.82L36.416,21.333z" fill="#FFFFFF"/><path d="M480,362.667c-5.891,0-10.667,4.776-10.667,10.667v80.917L316.875,301.792c-4.237-4.093-10.99-3.976-15.083,0.261     c-3.993,4.134-3.993,10.688,0,14.821l152.459,152.459h-80.917c-5.891,0-10.667,4.776-10.667,10.667s4.776,10.667,10.667,10.667     H480c5.891,0,10.667-4.776,10.667-10.667V373.333C490.667,367.442,485.891,362.667,480,362.667z" fill="#FFFFFF"/></g></svg>';
    }
    else
    {
        if(fsElement)
        {
            fsElement.style.display="none";
            fsElement.remove();
            fsElement=null;
        }
    }
}


fpsLimit.onChange=function()
{
    op.patch.config.fpsLimit=fpsLimit.get()||0;
};

op.onDelete=function()
{
    cgl.gl.clearColor(0,0,0,0);
    cgl.gl.clear(cgl.gl.COLOR_BUFFER_BIT | cgl.gl.DEPTH_BUFFER_BIT);

    op.patch.removeOnAnimFrame(op);
};


op.patch.loading.setOnFinishedLoading(function(cb)
{
    op.patch.config.fpsLimit=fpsLimit.get();
});



op.onAnimFrame=function(time)
{
    if(!active.get())return;
    if(cgl.aborted || cgl.canvas.clientWidth===0 || cgl.canvas.clientHeight===0)return;

    if(op.patch.loading.getProgress()<1.0 && reduceLoadingFPS.get())
    {
        op.patch.config.fpsLimit=5;
    }

    if(cgl.canvasWidth==-1)
    {
        cgl.setCanvas(op.patch.config.glCanvasId);
        return;
    }

    if(cgl.canvasWidth!=width.get() || cgl.canvasHeight!=height.get())
    {
        // cgl.canvasWidth=cgl.canvas.clientWidth;
        width.set(cgl.canvasWidth);
        // cgl.canvasHeight=cgl.canvas.clientHeight;
        height.set(cgl.canvasHeight);
    }

    if(CABLES.now()-rframeStart>1000)
    {
        CGL.fpsReport=CGL.fpsReport||[];
        if(op.patch.loading.getProgress()>=1.0 && rframeStart!==0)CGL.fpsReport.push(rframes);
        rframes=0;
        rframeStart=CABLES.now();
    }
    CGL.MESH.lastShader=null;
    CGL.MESH.lastMesh=null;

    cgl.renderStart(cgl,identTranslate,identTranslateView);

    if(clear.get())
    {
        cgl.gl.clearColor(0,0,0,1);
        cgl.gl.clear(cgl.gl.COLOR_BUFFER_BIT | cgl.gl.DEPTH_BUFFER_BIT);
    }

    trigger.trigger();


    if(CGL.MESH.lastMesh)CGL.MESH.lastMesh.unBind();


    if(CGL.Texture.previewTexture)
    {
        if(!CGL.Texture.texturePreviewer) CGL.Texture.texturePreviewer=new CGL.Texture.texturePreview(cgl);
        CGL.Texture.texturePreviewer.render(CGL.Texture.previewTexture);
    }
    cgl.renderEnd(cgl);
    
    
    // cgl.printError('mainloop end');
    
    

    if(!cgl.frameStore.phong)cgl.frameStore.phong={};
    rframes++;
};


};

Ops.Gl.MainLoop.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Sequence
// 
// **************************************************************

Ops.Sequence = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));
const exes=[];
const triggers=[];
const num=16;
exe.onTriggered=triggerAll;

function triggerAll()
{
    for(var i=0;i<triggers.length;i++) triggers[i].trigger();
}

for(var i=0;i<num;i++)
{
    triggers.push( op.addOutPort(new Port(op,"trigger "+i,OP_PORT_TYPE_FUNCTION)) );
    
    if(i<num-1)
    {
        var newExe=op.addInPort(new Port(op,"exe "+i,OP_PORT_TYPE_FUNCTION));
        newExe.onTriggered=triggerAll;
        exes.push( newExe );
    }
}


};

Ops.Sequence.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Render2Texture
// 
// **************************************************************

Ops.Gl.Render2Texture = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var cgl=op.patch.cgl;

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var msaa=op.inValueSelect("MSAA",["none","2x","4x","8x"],"none");
var useVPSize=op.addInPort(new Port(op,"use viewport size",OP_PORT_TYPE_VALUE,{ display:'bool' }));

var width=op.inValueInt("texture width");
var height=op.inValueInt("texture height");

var tfilter=op.addInPort(new Port(op,"filter",OP_PORT_TYPE_VALUE,{display:'dropdown',values:['nearest','linear','mipmap']}));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
// var tex=op.addOutPort(new Port(op,"texture",OP_PORT_TYPE_TEXTURE,{preview:true}));
// var texDepth=op.addOutPort(new Port(op,"textureDepth",OP_PORT_TYPE_TEXTURE));

var tex=op.outTexture("texture");
var texDepth=op.outTexture("textureDepth");

var fpTexture=op.inValueBool("HDR");
var depth=op.inValueBool("Depth",true);
var clear=op.inValueBool("Clear",true);

var fb=null;

width.set(512);
height.set(512);
useVPSize.set(true);
tfilter.set('linear');
var reInitFb=true;


// todo why does it only work when we render a mesh before>?>?????
// only happens with matcap material with normal map....

useVPSize.onChange=updateVpSize;
function updateVpSize()
{
    if(useVPSize.get())
    {
        width.setUiAttribs({hidePort:true,greyout:true});
        height.setUiAttribs({hidePort:true,greyout:true});
    }
    else
    {
        width.setUiAttribs({hidePort:false,greyout:false});
        height.setUiAttribs({hidePort:false,greyout:false});
    }
}

fpTexture.onChange=function()
{
    reInitFb=true;
};

depth.onChange=function()
{
    reInitFb=true;
};

clear.onChange=function()
{
    reInitFb=true;
};

var onFilterChange=function()
{
    reInitFb=true;
};

msaa.onChange=function()
{
    reInitFb=true;
};

function doRender()
{
    if(!fb || reInitFb)
    {
        if(fb) fb.delete();
        if(cgl.glVersion>=2) 
        {
            var ms=true;
            var msSamples=4;
            
            if(msaa.get()=="none")
            {
                msSamples=0;
                ms=false;
            }
            if(msaa.get()=="2x")msSamples=2;
            if(msaa.get()=="4x")msSamples=4;
            if(msaa.get()=="8x")msSamples=8;
            
            fb=new CGL.Framebuffer2(cgl,8,8,
            {
                isFloatingPointTexture:fpTexture.get(),
                multisampling:ms,
                depth:depth.get(),
                multisamplingSamples:msSamples,
                clear:clear.get()
            });
        }
        else
        {
            fb=new CGL.Framebuffer(cgl,8,8,{isFloatingPointTexture:fpTexture.get()});
        }

        if(tfilter.get()=='nearest') fb.setFilter(CGL.Texture.FILTER_NEAREST);
            else if(tfilter.get()=='linear') fb.setFilter(CGL.Texture.FILTER_LINEAR);
            else if(tfilter.get()=='mipmap') fb.setFilter(CGL.Texture.FILTER_MIPMAP);



        
        texDepth.set( fb.getTextureDepth() );
        reInitFb=false;
    }

    if(useVPSize.val)
    {
        width.set( cgl.getViewPort()[2] );
        height.set( cgl.getViewPort()[3] );
    }

    if(fb.getWidth()!=Math.ceil(width.get()) || fb.getHeight()!=Math.ceil(height.get()) )
    {
        fb.setSize( width.get(),height.get() );
    }



    fb.renderStart(cgl);
    // mesh.render(cgl.getShader());


    trigger.trigger();
    // cgl.printError("start r2t");
    fb.renderEnd(cgl);

    cgl.resetViewPort();

    tex.set( fb.getTextureColor() );
}


render.onTriggered=doRender;


tfilter.onValueChange(onFilterChange);
updateVpSize();

};

Ops.Gl.Render2Texture.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.RandomCluster
// 
// **************************************************************

Ops.Gl.RandomCluster = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));
var num=op.addInPort(new Port(op,"num"));
var size=op.addInPort(new Port(op,"size"));
var seed=op.addInPort(new Port(op,"random seed"));
var scaleX=op.addInPort(new Port(op,"scaleX",OP_PORT_TYPE_VALUE,{ display:'range' }));
var scaleY=op.addInPort(new Port(op,"scaleY",OP_PORT_TYPE_VALUE,{ display:'range' }));
var scaleZ=op.addInPort(new Port(op,"scaleZ",OP_PORT_TYPE_VALUE,{ display:'range' }));
var round=op.inValueBool('round',false);

var trigger=op.outTrigger("trigger");
var idx=op.addOutPort(new Port(op,"index")) ;
var rnd=op.addOutPort(new Port(op,"rnd")) ;

var rotX=op.inValueSlider("Rotate X",1);
var rotY=op.inValueSlider("Rotate Y",1);
var rotZ=op.inValueSlider("Rotate Z",1);

var scrollX=op.inValue("Scroll X",0);

var cgl=op.patch.cgl;
var randoms=[];
var origRandoms=[];
var randomsRot=[];
var randomsFloats=[];

scaleX.set(1);
scaleY.set(1);
scaleZ.set(1);

var transVec=vec3.create();
var mat=mat4.create();

function doRender()
{
    // console.log(doRender);
    if(op.instanced(exe))return;

    if(CABLES.UI && CABLES.UI.renderHelper)
    {
        CABLES.GL_MARKER.drawCube(op,
            size.get()/2*scaleX.get(),
            size.get()/2*scaleY.get(),
            size.get()/2*scaleZ.get());
    }

    op.patch.instancing.pushLoop(randoms.length);
    
    if(scrollX.get()!=0)
    {
        for(var i=0;i<origRandoms.length;i++)
        {
            randoms[i][0]=origRandoms[i][0]+scrollX.get();
            randoms[i][0]=(randoms[i][0]%size.get())-(size.get()/2);
        }
    }

    for(var i=0;i<randoms.length;i++)
    {
        cgl.pushModelMatrix();

        mat4.translate(cgl.mMatrix,cgl.mMatrix, randoms[i]);

        mat4.rotateX(cgl.mMatrix,cgl.mMatrix, randomsRot[i][0]);
        mat4.rotateY(cgl.mMatrix,cgl.mMatrix, randomsRot[i][1]);
        mat4.rotateZ(cgl.mMatrix,cgl.mMatrix, randomsRot[i][2]);

        idx.set(i);
        rnd.set(randomsFloats[i]);

        trigger.trigger();
        op.patch.instancing.increment();

        cgl.popModelMatrix();
    }
    op.patch.instancing.popLoop();

}

exe.onTriggered=doRender;

function getRandomPos()
{
    return vec3.fromValues(
        scaleX.get()*(Math.seededRandom()-0.5)*size.get(),
        scaleY.get()*(Math.seededRandom()-0.5)*size.get(),
        scaleZ.get()*(Math.seededRandom()-0.5)*size.get()
        );
}


function reset()
{
    randoms.length=0;
    randomsRot.length=0;
    randomsFloats.length=0;
    origRandoms.length=0;

    Math.randomSeed=seed.get();
    
    var makeRound=round.get();

    for(var i=0;i<num.get();i++)
    {
        randomsFloats.push(Math.seededRandom());

        var v=getRandomPos();
        
        if(makeRound)
            while(vec3.len(v)>size.get()/2)
                v=getRandomPos();

        origRandoms.push( [ v[0],v[1],v[2] ]);
        randoms.push(v);

        randomsRot.push(vec3.fromValues(
            Math.seededRandom()*360*CGL.DEG2RAD*rotX.get(),
            Math.seededRandom()*360*CGL.DEG2RAD*rotY.get(),
            Math.seededRandom()*360*CGL.DEG2RAD*rotZ.get()
            ));
    }
}

size.set(20);
seed.set(1);
seed.onChange=reset;
num.onChange=reset;
size.onChange=reset;
scaleX.onChange=reset;
scaleZ.onChange=reset;
scaleY.onChange=reset;
round.onChange=reset;
rotX.onChange=reset;
rotY.onChange=reset;
rotZ.onChange=reset;

num.set(100);

};

Ops.Gl.RandomCluster.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Pyramid
// 
// **************************************************************

Ops.Gl.Meshes.Pyramid = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="Pyramid";

var render=op.inFunction("Render");

var sizeW=op.inValue("Width",1);
var sizeL=op.inValue("Length",1);
var sizeH=op.inValue("Height",2);

var inDraw=op.inValueBool("Draw",true);

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var geomOut=op.outObject("geometry");

var geom=null;
var cgl=op.patch.cgl;
var mesh=null;

sizeW.onChange=create;
sizeH.onChange=create;
sizeL.onChange=create;

create();

render.onTriggered=function()
{
    if(inDraw.get())mesh.render(cgl.getShader());
    trigger.trigger();
};


function create()
{
    if(!geom)geom=new CGL.Geometry();
    var w=sizeW.get();
    var h=sizeH.get();
    var l=sizeL.get();
    
    geom.vertices = [
        // -w,-l,0,
        // w,-l,0,
        // w,l,0,
        // -w,l,0,
        // 0,0,h,
        -w,0,-l,
        w,0,-l,
        w,0,l,
        -w,0,l,
        0,h,0
    ];

    geom.vertexNormals = [
         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
    ];

    geom.texCoords = [
         0.5,  0.0,
         1.0,  1.0,
         0.0,  1.0,
         0.0,  1.0,
         0.0,  1.0,
    ];

    geom.verticesIndices = [
        0,1,2, 
        0,2,3, // bottom
        
        4,1,0,
        4,3,2,
        0,3,4,
        4,2,1
    ];


geom.unIndex();
    geom.calculateNormals({forceZUp:false});
    

    mesh=new CGL.Mesh(cgl,geom);
    geomOut.set(null);
    geomOut.set(geom);

}


};

Ops.Gl.Meshes.Pyramid.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.Scale
// 
// **************************************************************

Ops.Gl.Matrix.Scale = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
const scale=op.addInPort(new Port(op,"scale"));
const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

const cgl=op.patch.cgl;
const vScale=vec3.create();
scale.onChange=scaleChanged;
scale.set(1.0);
scaleChanged();

render.onTriggered=function()
{
    cgl.pushModelMatrix();
    mat4.scale(cgl.mMatrix,cgl.mMatrix, vScale);
    trigger.trigger();
    cgl.popModelMatrix();
};

function scaleChanged()
{
    vec3.set(vScale, scale.get(),scale.get(),scale.get());
}



};

Ops.Gl.Matrix.Scale.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ClearColor
// 
// **************************************************************

Ops.Gl.ClearColor = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
const r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true' }));
const g=op.inValueSlider("g",0.1);
const b=op.inValueSlider("b",0.1);
const a=op.inValueSlider("a",1);

r.set(0.1);
const cgl=op.patch.cgl;

render.onTriggered=function()
{
    cgl.gl.clearColor(r.get(),g.get(),b.get(),a.get());
    cgl.gl.clear(cgl.gl.COLOR_BUFFER_BIT | cgl.gl.DEPTH_BUFFER_BIT);
    trigger.trigger();
};


};

Ops.Gl.ClearColor.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Cube
// 
// **************************************************************

Ops.Gl.Meshes.Cube = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='Cube';

var render=op.inFunction('render');
var width=op.inValue('width');
var height=op.inValue('height');
var lengt=op.inValue('length');
var center=op.inValueBool('center');

var active=op.inValueBool('Active',true);

var trigger=op.outFunction('trigger');
var geomOut=op.outObject("geometry");


var cgl=op.patch.cgl;
var geom=null;
var mesh=null;
width.set(1.0);
height.set(1.0);
lengt.set(1.0);
center.set(true);

render.onTriggered=function()
{
    if(active.get() && mesh) mesh.render(cgl.getShader());
    trigger.trigger();
};

op.preRender=function()
{
    buildMesh();
    mesh.render(cgl.getShader());
};


function buildMesh()
{
    if(!geom)geom=new CGL.Geometry("cubemesh");
    geom.clear();

    var x=width.get();
    var nx=-1*width.get();
    var y=lengt.get();
    var ny=-1*lengt.get();
    var z=height.get();
    var nz=-1*height.get();

    if(!center.get())
    {
        nx=0;
        ny=0;
        nz=0;
    }
    else
    {
        x*=0.5;
        nx*=0.5;
        y*=0.5;
        ny*=0.5;
        z*=0.5;
        nz*=0.5;
    }

    geom.vertices = [
        // Front face
        nx, ny,  z,
        x, ny,  z,
        x,  y,  z,
        nx,  y,  z,
        // Back face
        nx, ny, nz,
        nx,  y, nz,
        x,  y, nz,
        x, ny, nz,
        // Top face
        nx,  y, nz,
        nx,  y,  z,
        x,  y,  z,
        x,  y, nz,
        // Bottom face
        nx, ny, nz,
        x, ny, nz,
        x, ny,  z,
        nx, ny,  z,
        // Right face
        x, ny, nz,
        x,  y, nz,
        x,  y,  z,
        x, ny,  z,
        // zeft face
        nx, ny, nz,
        nx, ny,  z,
        nx,  y,  z,
        nx,  y, nz
        ];

    geom.setTexCoords( [
          // Front face
          0.0, 1.0,
          1.0, 1.0,
          1.0, 0.0,
          0.0, 0.0,
          // Back face
          1.0, 1.0,
          1.0, 0.0,
          0.0, 0.0,
          0.0, 1.0,
          // Top face
          0.0, 0.0,
          0.0, 1.0,
          1.0, 1.0,
          1.0, 0.0,
          // Bottom face
          1.0, 0.0,
          0.0, 0.0,
          0.0, 1.0,
          1.0, 1.0,
          // Right face
          1.0, 1.0,
          1.0, 0.0,
          0.0, 0.0,
          0.0, 1.0,
          // Left face
          0.0, 1.0,
          1.0, 1.0,
          1.0, 0.0,
          0.0, 0.0,
        ]);

    geom.vertexNormals = [
        // Front face
         0.0,  0.0,  1.0,
         0.0,  0.0,  1.0,
         0.0,  0.0,  1.0,
         0.0,  0.0,  1.0,

        // Back face
         0.0,  0.0, -1.0,
         0.0,  0.0, -1.0,
         0.0,  0.0, -1.0,
         0.0,  0.0, -1.0,

        // Top face
         0.0,  1.0,  0.0,
         0.0,  1.0,  0.0,
         0.0,  1.0,  0.0,
         0.0,  1.0,  0.0,

        // Bottom face
         0.0, -1.0,  0.0,
         0.0, -1.0,  0.0,
         0.0, -1.0,  0.0,
         0.0, -1.0,  0.0,

        // Right face
         1.0,  0.0,  0.0,
         1.0,  0.0,  0.0,
         1.0,  0.0,  0.0,
         1.0,  0.0,  0.0,

        // Left face
        -1.0,  0.0,  0.0,
        -1.0,  0.0,  0.0,
        -1.0,  0.0,  0.0,
        -1.0,  0.0,  0.0
    ];


    geom.verticesIndices = [
        0, 1, 2,      0, 2, 3,    // Front face
        4, 5, 6,      4, 6, 7,    // Back face
        8, 9, 10,     8, 10, 11,  // Top face
        12, 13, 14,   12, 14, 15, // Bottom face
        16, 17, 18,   16, 18, 19, // Right face
        20, 21, 22,   20, 22, 23  // Left face
    ];

    mesh=new CGL.Mesh(cgl,geom);
    geomOut.set(null);
    geomOut.set(geom);

}

width.onValueChanged=buildMesh;
height.onValueChanged=buildMesh;
lengt.onValueChanged=buildMesh;
center.onValueChanged=buildMesh;



buildMesh();

};

Ops.Gl.Meshes.Cube.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Devices.Mouse.Mouse
// 
// **************************************************************

Ops.Devices.Mouse.Mouse = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var outMouseX=op.addOutPort(new Port(op,"x",OP_PORT_TYPE_VALUE));
var outMouseY=op.addOutPort(new Port(op,"y",OP_PORT_TYPE_VALUE));
var mouseDown=op.addOutPort(new Port(op,"button down",OP_PORT_TYPE_VALUE));
var mouseClick=op.addOutPort(new Port(op,"click",OP_PORT_TYPE_FUNCTION));
var mouseUp=op.addOutPort(new Port(op,"Button Up",OP_PORT_TYPE_FUNCTION));
var mouseClickRight=op.addOutPort(new Port(op,"click right",OP_PORT_TYPE_FUNCTION));
var mouseOver=op.addOutPort(new Port(op,"mouseOver",OP_PORT_TYPE_VALUE));
var relative=op.addInPort(new Port(op,"relative",OP_PORT_TYPE_VALUE,{display:'bool'}));
var normalize=op.addInPort(new Port(op,"normalize",OP_PORT_TYPE_VALUE,{display:'bool'}));
var active=op.inValueBool("Active",true);
var smooth=op.addInPort(new Port(op,"smooth",OP_PORT_TYPE_VALUE,{display:'bool'}));
var smoothSpeed=op.addInPort(new Port(op,"smoothSpeed",OP_PORT_TYPE_VALUE));
var area=op.addInPort(new Port(op,"Area",OP_PORT_TYPE_VALUE,{display:'dropdown',values:['Canvas','Document','Parent Element']}));
var outButton=op.addOutPort(new Port(op,"button",OP_PORT_TYPE_VALUE));
var multiply=op.addInPort(new Port(op,"multiply",OP_PORT_TYPE_VALUE));
var flipY=op.inValueBool("flip y",true);

area.set("Canvas");

multiply.set(1.0);
var smoothTimer=0;
smoothSpeed.set(20);

var cgl=op.patch.cgl;
var listenerElement=null;

function setValue(x,y)
{
    if(normalize.get())
    {
        var w=cgl.canvas.width;
        var h=cgl.canvas.height;
        if(listenerElement==document.body)
        {
            w=listenerElement.clientWidth;
            h=listenerElement.clientHeight;
        }
        outMouseX.set( (x/w*2.0-1.0)*multiply.get() );
        outMouseY.set( (y/h*2.0-1.0)*multiply.get() );
    }
    else
    {
        outMouseX.set( x*multiply.get() );
        outMouseY.set( y*multiply.get() );
    }
}

smooth.onValueChanged=function()
{
    if(smooth.get()) smoothTimer = setInterval(updateSmooth, 5);
        else if(smoothTimer)clearTimeout(smoothTimer);
};

var smoothX,smoothY;
var lineX=0,lineY=0;

var mouseX=cgl.canvas.width/2;
var mouseY=cgl.canvas.height/2;
lineX=mouseX;
lineY=mouseY;

outMouseX.set(mouseX);
outMouseY.set(mouseY);

var relLastX=0;
var relLastY=0;
var offsetX=0;
var offsetY=0;
addListeners();

area.onValueChanged=addListeners;

var speed=0;

function updateSmooth()
{
    speed=smoothSpeed.get();
    if(speed<=0)speed=0.01;
    var distanceX = Math.abs(mouseX - lineX);
    var speedX = Math.round( distanceX / speed, 0 );
    lineX = (lineX < mouseX) ? lineX + speedX : lineX - speedX;

    var distanceY = Math.abs(mouseY - lineY);
    var speedY = Math.round( distanceY / speed, 0 );
    lineY = (lineY < mouseY) ? lineY + speedY : lineY - speedY;

    setValue(lineX,lineY);
}

var onMouseEnter = function(e)
{
    mouseDown.set(false);
    mouseOver.set(true);
    speed=smoothSpeed.get();
};

var onMouseDown = function(e)
{
    outButton.set(e.which);
    mouseDown.set(true);
};

var onMouseUp = function(e)
{
    outButton.set(0);
    mouseDown.set(false);
    mouseUp.trigger();
};

var onClickRight= function(e)
{
    mouseClickRight.trigger();
    e.preventDefault();
};

function onmouseclick(e)
{
    mouseClick.trigger();
}


function onMouseLeave(e)
{
    relLastX=0;
    relLastY=0;

    speed=100;
    
    if(area.get()!='Document')
    {
        // leave anim
        if(smooth.get())
        {
            mouseX=cgl.canvas.width/2;
            mouseY=cgl.canvas.height/2;
        }
        
    }
    mouseOver.set(false);
    mouseDown.set(false);
}

relative.onChange=function()
{
    offsetX=0;
    offsetY=0;
}

function onmousemove(e)
{
    mouseOver.set(true);
    
    if(!relative.get())
    {
        if(area.get()!="Document")
        {
            offsetX=e.offsetX;
            offsetY=e.offsetY;
        }
        else
        {
            offsetX=e.clientX;
            offsetY=e.clientY;
        }

        if(smooth.get())
        {
            mouseX=offsetX;
            
            if(flipY.get()) mouseY=listenerElement.clientHeight-offsetY;
                else mouseY=offsetY;
        }
        else
        {
            if(flipY.get()) setValue(offsetX,listenerElement.clientHeight-offsetY);
                else setValue(offsetX,offsetY);
        }
    }
    else
    {
        if(relLastX!=0 && relLastY!=0)
        {
            offsetX=e.offsetX-relLastX;
            offsetY=e.offsetY-relLastY;
        }
        else
        {

        }

        relLastX=e.offsetX;
        relLastY=e.offsetY;

        mouseX+=offsetX;
        mouseY+=offsetY;
        
        if(mouseY>460)mouseY=460;
    }
};

function ontouchstart(event)
{
    mouseDown.set(true);

    if(event.touches && event.touches.length>0) onMouseDown(event.touches[0]);
};

function ontouchend(event)
{
    mouseDown.set(false);
    onMouseUp();
};

function removeLiseteners()
{
    listenerElement.removeEventListener('touchend', ontouchend);
    listenerElement.removeEventListener('touchstart', ontouchstart);

    listenerElement.removeEventListener('click', onmouseclick);
    listenerElement.removeEventListener('mousemove', onmousemove);
    listenerElement.removeEventListener('mouseleave', onMouseLeave);
    listenerElement.removeEventListener('mousedown', onMouseDown);
    listenerElement.removeEventListener('mouseup', onMouseUp);
    listenerElement.removeEventListener('mouseenter', onMouseEnter);
    listenerElement.removeEventListener('contextmenu', onClickRight);
    listenerElement=null;
}

function addListeners()
{
    if(listenerElement)removeLiseteners();

    listenerElement=cgl.canvas;
    if(area.get()=='Document') listenerElement=document.body;
    if(area.get()=='Parent Element') listenerElement=cgl.canvas.parentElement;

    listenerElement.addEventListener('touchend', ontouchend);
    listenerElement.addEventListener('touchstart', ontouchstart);

    listenerElement.addEventListener('click', onmouseclick);
    listenerElement.addEventListener('mousemove', onmousemove);
    listenerElement.addEventListener('mouseleave', onMouseLeave);
    listenerElement.addEventListener('mousedown', onMouseDown);
    listenerElement.addEventListener('mouseup', onMouseUp);
    listenerElement.addEventListener('mouseenter', onMouseEnter);
    listenerElement.addEventListener('contextmenu', onClickRight);
}

active.onChange=function()
{
    if(listenerElement)removeLiseteners();
    if(active.get())addListeners();
}

op.onDelete=function()
{
    removeLiseteners();
};

addListeners();


};

Ops.Devices.Mouse.Mouse.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Random2
// 
// **************************************************************

Ops.Math.Random2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var exe=op.inFunctionButton('Generate');
var min=op.inValue("min",0);
var max=op.inValue("max",1);
var result=op.outValue("result");
var inInteger=op.inValueBool("Integer",false);

exe.onTriggered=genRandom;
max.onChange=genRandom;
min.onChange=genRandom;
inInteger.onChange=genRandom;

genRandom();

function genRandom()
{
    var r=(Math.random()*(max.get()-min.get()))+min.get();
    if(inInteger.get())r=Math.round(r);
    result.set(r);
}


};

Ops.Math.Random2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Sum
// 
// **************************************************************

Ops.Math.Sum = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var result=op.addOutPort(new Port(op,"result"));
var number1=op.inValue("number1");
var number2=op.inValue("number2");

function exec()
{
    var v=parseFloat(number1.get())+parseFloat(number2.get());
    if(!isNaN(v)) result.set( v );
}

number1.onChange=exec;
number2.onChange=exec;

number1.set(1);
number2.set(1);


};

Ops.Math.Sum.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.ImageCompose
// 
// **************************************************************

Ops.Gl.TextureEffects.ImageCompose = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
const useVPSize=op.addInPort(new Port(op,"use viewport size",OP_PORT_TYPE_VALUE,{ display:'bool' }));
const width=op.inValueInt("width");
const height=op.inValueInt("height");

const tfilter=op.inValueSelect("filter",['nearest','linear','mipmap'],"linear");
const twrap=op.inValueSelect("wrap",['clamp to edge','repeat','mirrored repeat']);
const bgAlpha=op.inValueSlider("Background Alpha",1);
const fpTexture=op.inValueBool("HDR");

const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
const texOut=op.outTexture("texture_out");

const outRatio=op.outValue("Aspect Ratio");

texOut.set(null);
var cgl=op.patch.cgl;
var effect=null;
var tex=null;

var w=8,h=8;
var prevViewPort=[0,0,0,0];
var reInitEffect=true;

var bgFrag=''
    .endl()+'uniform float a;'
    .endl()+'void main()'
    .endl()+'{'
    .endl()+'   gl_FragColor = vec4(0.0,0.0,0.0,a);'
    .endl()+'}';
var bgShader=new CGL.Shader(cgl,'imgcompose bg');
bgShader.setSource(bgShader.getDefaultVertexShader(),bgFrag);
var uniBgAlpha=new CGL.Uniform(bgShader,'f','a',bgAlpha);

var selectedFilter=CGL.Texture.FILTER_LINEAR;
var selectedWrap=CGL.Texture.WRAP_CLAMP_TO_EDGE;

function initEffect()
{
    if(effect)effect.delete();
    if(tex)tex.delete();

    effect=new CGL.TextureEffect(cgl,{"isFloatingPointTexture":fpTexture.get()});

    tex=new CGL.Texture(cgl,
        {
            "name":"image compose",
            "isFloatingPointTexture":fpTexture.get(),
            "filter":selectedFilter,
            "wrap":selectedWrap,
            "width": Math.ceil(width.get()),
            "height": Math.ceil(height.get()),
        });

    effect.setSourceTexture(tex);
    texOut.set(null);
    // texOut.set(effect.getCurrentSourceTexture());

    reInitEffect=false;

    // op.log("reinit effect");
    // tex.printInfo();
}

fpTexture.onChange=function()
{
    reInitEffect=true;
};


function updateResolution()
{
    if(!effect)initEffect();

    if(useVPSize.get())
    {
        w=cgl.getViewPort()[2];
        h=cgl.getViewPort()[3];
    }
    else
    {
        w=Math.ceil(width.get());
        h=Math.ceil(height.get());
    }

    if((w!=tex.width || h!= tex.height) && (w!==0 && h!==0))
    {
        height.set(h);
        width.set(w);
        tex.setSize(w,h);
        outRatio.set(w/h);
        effect.setSourceTexture(tex);
    }

    if(texOut.get())
        if(!texOut.get().isPowerOfTwo() )
        {
            if(!op.uiAttribs.hint)
                op.uiAttr(
                    {
                        hint:'texture dimensions not power of two! - texture filtering will not work.',
                        warning:null
                    });
        }
        else
        if(op.uiAttribs.hint)
        {
            op.uiAttr({hint:null,warning:null}); //todo only when needed...
        }

}


function updateSizePorts()
{
    if(useVPSize.get())
    {
        width.setUiAttribs({hidePort:true,greyout:true});
        height.setUiAttribs({hidePort:true,greyout:true});
    }
    else
    {
        width.setUiAttribs({hidePort:false,greyout:false});
        height.setUiAttribs({hidePort:false,greyout:false});
    }
}


useVPSize.onValueChanged=function()
{
    updateSizePorts();
    if(useVPSize.get())
    {
        width.onValueChanged=null;
        height.onValueChanged=null;
    }
    else
    {
        width.onValueChanged=updateResolution;
        height.onValueChanged=updateResolution;
    }
    updateResolution();
    
};


op.preRender=function()
{
    doRender();
    bgShader.bind();
};


var doRender=function()
{
    if(!effect || reInitEffect)
    {
        initEffect();
    }
    var vp=cgl.getViewPort();
    prevViewPort[0]=vp[0];
    prevViewPort[1]=vp[1];
    prevViewPort[2]=vp[2];
    prevViewPort[3]=vp[3];


    cgl.gl.blendFunc(cgl.gl.SRC_ALPHA, cgl.gl.ONE_MINUS_SRC_ALPHA);
    // cgl.gl.blendFunc(cgl.gl.SRC_ALPHA,cgl.gl.ONE_MINUS_SRC_ALPHA);



    updateResolution();

    cgl.currentTextureEffect=effect;
    effect.setSourceTexture(tex);

    effect.startEffect();

    // render background color...
    cgl.setShader(bgShader);
    cgl.currentTextureEffect.bind();
    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();

    texOut.set(effect.getCurrentSourceTexture());
    // texOut.set(effect.getCurrentTargetTexture());
    
    
    // if(effect.getCurrentSourceTexture.filter==CGL.Texture.FILTER_MIPMAP)
    // {
    //         this._cgl.gl.bindTexture(this._cgl.gl.TEXTURE_2D, effect.getCurrentSourceTexture.tex);
    //         effect.getCurrentSourceTexture.updateMipMap();
    //     // else
    //     // {
    //     //     this._cgl.gl.bindTexture(this._cgl.gl.TEXTURE_2D, this._textureSource.tex);;
    //     //     this._textureSource.updateMipMap();
    //     // }
        
    //     this._cgl.gl.bindTexture(this._cgl.gl.TEXTURE_2D, null);
    // }

    effect.endEffect();

    cgl.setViewPort(prevViewPort[0],prevViewPort[1],prevViewPort[2],prevViewPort[3]);


    cgl.gl.blendFunc(cgl.gl.SRC_ALPHA,cgl.gl.ONE_MINUS_SRC_ALPHA);

    cgl.currentTextureEffect=null;
};


function onWrapChange()
{
    if(twrap.get()=='repeat') selectedWrap=CGL.Texture.WRAP_REPEAT;
    if(twrap.get()=='mirrored repeat') selectedWrap=CGL.Texture.WRAP_MIRRORED_REPEAT;
    if(twrap.get()=='clamp to edge') selectedWrap=CGL.Texture.WRAP_CLAMP_TO_EDGE;

    reInitEffect=true;
    updateResolution();
}

twrap.set('clamp to edge');
twrap.onValueChanged=onWrapChange;

function onFilterChange()
{
    if(tfilter.get()=='nearest') selectedFilter=CGL.Texture.FILTER_NEAREST;
    if(tfilter.get()=='linear')  selectedFilter=CGL.Texture.FILTER_LINEAR;
    if(tfilter.get()=='mipmap')  selectedFilter=CGL.Texture.FILTER_MIPMAP;

    reInitEffect=true;
    updateResolution();
    // effect.setSourceTexture(tex);
    // updateResolution();
}

tfilter.set('linear');
tfilter.onValueChanged=onFilterChange;

useVPSize.set(true);
render.onTriggered=doRender;

width.set(640);
height.set(360);
updateSizePorts();

};

Ops.Gl.TextureEffects.ImageCompose.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.DrawImage
// 
// **************************************************************

Ops.Gl.TextureEffects.DrawImage = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["drawimage_frag"]="#ifdef HAS_TEXTURES\n  IN vec2 texCoord;\n  UNI sampler2D tex;\n  UNI sampler2D image;\n#endif\n\nIN mat3 transform;\nUNI float rotate;\n{{BLENDCODE}}\n\n#ifdef HAS_TEXTUREALPHA\n   UNI sampler2D imageAlpha;\n#endif\n\nUNI float amount;\n\nvoid main()\n{\n   vec4 blendRGBA=vec4(0.0,0.0,0.0,1.0);\n   #ifdef HAS_TEXTURES\n       vec2 tc=texCoord;\n\n       #ifdef TEX_FLIP_X\n           tc.x=1.0-tc.x;\n       #endif\n       #ifdef TEX_FLIP_Y\n           tc.y=1.0-tc.y;\n       #endif\n\n       #ifdef TEX_TRANSFORM\n           vec3 coordinates=vec3(tc.x, tc.y,1.0);\n           tc=(transform * coordinates ).xy;\n       #endif\n\n       blendRGBA=texture2D(image,tc);\n\n       vec3 blend=blendRGBA.rgb;\n       vec4 baseRGBA=texture2D(tex,texCoord);\n       vec3 base=baseRGBA.rgb;\n\n       vec3 colNew=_blend(base,blend);\n\n       #ifdef REMOVE_ALPHA_SRC\n           blendRGBA.a=1.0;\n       #endif\n\n       #ifdef HAS_TEXTUREALPHA\n           vec4 colImgAlpha=texture2D(imageAlpha,texCoord);\n           float colImgAlphaAlpha=colImgAlpha.a;\n\n           #ifdef ALPHA_FROM_LUMINANCE\n               vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), colImgAlpha.rgb ));\n               colImgAlphaAlpha=(gray.r+gray.g+gray.b)/3.0;\n           #endif\n\n           blendRGBA.a=colImgAlphaAlpha*blendRGBA.a;\n           \n           #ifdef INVERT_ALPHA\n           blendRGBA.a=1.0-blendRGBA.a;\n           #endif\n       #endif\n\n\n   #endif\n\n   blendRGBA.rgb=mix( colNew, base ,1.0-blendRGBA.a*amount);\n   blendRGBA.a=1.0;\n\n\n   gl_FragColor = blendRGBA;\n}";
attachments["drawimage_vert"]="IN vec3 vPosition;\nIN vec2 attrTexCoord;\nIN vec3 attrVertNormal;\nOUT vec2 texCoord;\nOUT vec3 norm;\nUNI mat4 projMatrix;\nUNI mat4 mvMatrix;\n\nUNI float posX;\nUNI float posY;\nUNI float scale;\nUNI float rotate;\n\nOUT mat3 transform;\n\nvoid main()\n{\n    texCoord=attrTexCoord;\n    norm=attrVertNormal;\n\n    #ifdef TEX_TRANSFORM\n    vec3 coordinates=vec3(attrTexCoord.x, attrTexCoord.y,1.0);\n    float angle = radians( rotate );\n    vec2 scale= vec2(scale,scale);\n    vec2 translate= vec2(posX,posY);\n\n    transform = mat3(   scale.x * cos( angle ), scale.x * sin( angle ), 0.0,\n                        - scale.y * sin( angle ), scale.y * cos( angle ), 0.0,\n                        - 0.5 * scale.x * cos( angle ) + 0.5 * scale.y * sin( angle ) - 0.5 * translate.x*2.0 + 0.5,  - 0.5 * scale.x * sin( angle ) - 0.5 * scale.y * cos( angle ) - 0.5 * translate.y*2.0 + 0.5, 1.0);\n    #endif\n\n    gl_Position = projMatrix * mvMatrix * vec4(vPosition,  1.0);\n}";
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var amount=op.addInPort(new Port(op,"amount",OP_PORT_TYPE_VALUE,{ display:'range' }));

var image=op.addInPort(new Port(op,"image",OP_PORT_TYPE_TEXTURE,{preview:true }));
var blendMode=CGL.TextureEffect.AddBlendSelect(op,"blendMode");

var imageAlpha=op.addInPort(new Port(op,"imageAlpha",OP_PORT_TYPE_TEXTURE,{preview:true }));
var alphaSrc=op.inValueSelect("alphaSrc",['alpha channel','luminance']);
var removeAlphaSrc=op.addInPort(new Port(op,"removeAlphaSrc",OP_PORT_TYPE_VALUE,{ display:'bool' }));

var invAlphaChannel=op.addInPort(new Port(op,"invert alpha channel",OP_PORT_TYPE_VALUE,{ display:'bool' }));


var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

blendMode.set('normal');
var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl,'drawimage');

amount.set(1.0);




var srcFrag=attachments.drawimage_frag.replace('{{BLENDCODE}}',CGL.TextureEffect.getBlendCode());


shader.setSource(attachments.drawimage_vert,srcFrag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var textureDisplaceUniform=new CGL.Uniform(shader,'t','image',1);
var textureAlpha=new CGL.Uniform(shader,'t','imageAlpha',2);

invAlphaChannel.onValueChanged=function()
{
    if(invAlphaChannel.get()) shader.define('INVERT_ALPHA');
        else shader.removeDefine('INVERT_ALPHA');
};

removeAlphaSrc.onValueChanged=function()
{
    if(removeAlphaSrc.get()) shader.define('REMOVE_ALPHA_SRC');
        else shader.removeDefine('REMOVE_ALPHA_SRC');
};
removeAlphaSrc.set(true);

alphaSrc.onValueChanged=function()
{
    if(alphaSrc.get()=='luminance') shader.define('ALPHA_FROM_LUMINANCE');
        else shader.removeDefine('ALPHA_FROM_LUMINANCE');
};

alphaSrc.set("alpha channel");


{
    //
    // texture flip
    //
    var flipX=op.addInPort(new Port(op,"flip x",OP_PORT_TYPE_VALUE,{ display:'bool' }));
    var flipY=op.addInPort(new Port(op,"flip y",OP_PORT_TYPE_VALUE,{ display:'bool' }));

    flipX.onValueChanged=function()
    {
        if(flipX.get()) shader.define('TEX_FLIP_X');
            else shader.removeDefine('TEX_FLIP_X');
    };

    flipY.onValueChanged=function()
    {
        if(flipY.get()) shader.define('TEX_FLIP_Y');
            else shader.removeDefine('TEX_FLIP_Y');
    };
}

{
    //
    // texture transform
    //
    var scale=op.addInPort(new Port(op,"scale",OP_PORT_TYPE_VALUE,{ display:'range' }));
    var posX=op.addInPort(new Port(op,"pos x",OP_PORT_TYPE_VALUE, {}));
    var posY=op.addInPort(new Port(op,"pos y",OP_PORT_TYPE_VALUE, {}));
    var rotate=op.addInPort(new Port(op,"rotate",OP_PORT_TYPE_VALUE, {}));

    scale.set(1.0);

    var uniScale=new CGL.Uniform(shader,'f','scale',scale.get());
    var uniPosX=new CGL.Uniform(shader,'f','posX',posX.get());
    var uniPosY=new CGL.Uniform(shader,'f','posY',posY.get());
    var uniRotate=new CGL.Uniform(shader,'f','rotate',rotate.get());

    function updateTransform()
    {
        if(scale.get()!=1.0 || posX.get()!=0.0 || posY.get()!=0.0 || rotate.get()!=0.0 )
        {
            if(!shader.hasDefine('TEX_TRANSFORM')) shader.define('TEX_TRANSFORM');
            uniScale.setValue( parseFloat(scale.get()) );
            uniPosX.setValue( posX.get() );
            uniPosY.setValue( posY.get() );
            uniRotate.setValue( rotate.get() );
        }
        else
        {
            // shader.removeDefine('TEX_TRANSFORM');
        }
    }

    scale.onChange=updateTransform;
    posX.onChange=updateTransform;
    posY.onChange=updateTransform;
    rotate.onChange=updateTransform;
}

blendMode.onValueChanged=function()
{
    CGL.TextureEffect.onChangeBlendSelect(shader,blendMode.get());
};


var amountUniform=new CGL.Uniform(shader,'f','amount',amount);

var oldHadImageAlpha=false;


function doRender()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;


    if(imageAlpha.get() && !oldHadImageAlpha || !imageAlpha.get() && oldHadImageAlpha)
    {
        if(imageAlpha.get() && imageAlpha.get().tex)
        {
            shader.define('HAS_TEXTUREALPHA');
            oldHadImageAlpha=true;
        }
        else
        {
            shader.removeDefine('HAS_TEXTUREALPHA');
            oldHadImageAlpha=false;
        }
        
    }



    if(image.get() && image.get().tex && amount.get()>0.0)
    {
        cgl.setShader(shader);
        cgl.currentTextureEffect.bind();

        cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
        
        if(image.get() && image.get().tex) cgl.setTexture(1, image.get().tex );
            else cgl.setTexture(1, null);

        if(imageAlpha.get() && imageAlpha.get().tex) cgl.setTexture(2, imageAlpha.get().tex );
            else cgl.setTexture(2,null);

        cgl.currentTextureEffect.finish();
        cgl.setPreviousShader();
    }

    trigger.trigger();
}

render.onTriggered=doRender;


};

Ops.Gl.TextureEffects.DrawImage.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.EdgeDetection
// 
// **************************************************************

Ops.Gl.TextureEffects.EdgeDetection = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["edgedetect_frag"]="\nprecision highp float;\nIN vec2 texCoord;\nuniform sampler2D tex;\nuniform float amount;\n\nuniform float texWidth;\nuniform float texHeight;\n\nuniform float mulColor;\n\n\nconst vec4 lumcoeff = vec4(0.299,0.587,0.114, 0.);\n\nvec3 desaturate(vec3 color)\n{\n    return vec3(dot(vec3(0.2126,0.7152,0.0722), color));\n}\n\n\nvoid main()\n{\n    vec4 col=vec4(1.0,0.0,0.0,1.0);\n    \n    float pixelX=amount/texWidth;\n    float pixelY=amount/texHeight;\n\n    // col=texture2D(tex,texCoord);\n    \n    float count=1.0;\n    \n\tvec4 horizEdge = vec4( 0.0 );\n\thorizEdge -= texture2D( tex, vec2( texCoord.x - pixelX, texCoord.y - pixelY ) ) * 1.0;\n\thorizEdge -= texture2D( tex, vec2( texCoord.x - pixelX, texCoord.y     ) ) * 2.0;\n\thorizEdge -= texture2D( tex, vec2( texCoord.x - pixelX, texCoord.y + pixelY ) ) * 1.0;\n\thorizEdge += texture2D( tex, vec2( texCoord.x + pixelX, texCoord.y - pixelY ) ) * 1.0;\n\thorizEdge += texture2D( tex, vec2( texCoord.x + pixelX, texCoord.y     ) ) * 2.0;\n\thorizEdge += texture2D( tex, vec2( texCoord.x + pixelX, texCoord.y + pixelY ) ) * 1.0;\n\tvec4 vertEdge = vec4( 0.0 );\n\tvertEdge -= texture2D( tex, vec2( texCoord.x - pixelX, texCoord.y - pixelY ) ) * 1.0;\n\tvertEdge -= texture2D( tex, vec2( texCoord.x    , texCoord.y - pixelY ) ) * 2.0;\n\tvertEdge -= texture2D( tex, vec2( texCoord.x + pixelX, texCoord.y - pixelY ) ) * 1.0;\n\tvertEdge += texture2D( tex, vec2( texCoord.x - pixelX, texCoord.y + pixelY ) ) * 1.0;\n\tvertEdge += texture2D( tex, vec2( texCoord.x    , texCoord.y + pixelY ) ) * 2.0;\n\tvertEdge += texture2D( tex, vec2( texCoord.x + pixelX, texCoord.y + pixelY ) ) * 1.0;\n\n\n\tvec3 edge = sqrt((horizEdge.rgb/count * horizEdge.rgb/count) + (vertEdge.rgb/count * vertEdge.rgb/count));\n// \tedge=vec3(atan(edge.x,edge.y));\n\t\n// \tif(edge.r>1.1)edge=vec3(1.0,1.0,1.0);\n// \telse edge=vec3(0.0,0.0,0.0);\n\n// edge*=5.0;\n\n\nedge=desaturate(edge);\n\n    if(mulColor>0.0)\n        edge*=texture2D( tex, texCoord ).rgb*mulColor*4.0;\n    edge=max(min(edge,1.0),0.0);\n    gl_FragColor = vec4(edge,1.0);\n\n}\n    \n ";
op.name="EdgeDetection";

var render=op.inFunction("Render");
var trigger=op.outFunction("Trigger");

var amount=op.inValueSlider("amount",1);

var mulColor=op.inValueSlider("Mul Color",0);

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);
//op.onLoaded=shader.compile;



shader.setSource(shader.getDefaultVertexShader(),attachments.edgedetect_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var amountUniform=new CGL.Uniform(shader,'f','amount',amount);

var uniWidth=new CGL.Uniform(shader,'f','texWidth',128);
var uniHeight=new CGL.Uniform(shader,'f','texHeight',128);
var uniMulColor=new CGL.Uniform(shader,'f','mulColor',mulColor);


render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    uniWidth.setValue(cgl.currentTextureEffect.getCurrentSourceTexture().width);
    uniHeight.setValue(cgl.currentTextureEffect.getCurrentSourceTexture().height);

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};

};

Ops.Gl.TextureEffects.EdgeDetection.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.DepthTexture
// 
// **************************************************************

Ops.Gl.TextureEffects.DepthTexture = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var image=op.addInPort(new Port(op,"image",OP_PORT_TYPE_TEXTURE));
var farPlane=op.addInPort(new Port(op,"farplane",OP_PORT_TYPE_VALUE));
var nearPlane=op.addInPort(new Port(op,"nearplane",OP_PORT_TYPE_VALUE));
var inInv=op.inValueBool("Invert",false);

farPlane.set(100.0);
nearPlane.set(0.1);

var cgl=op.patch.cgl;
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var shader=new CGL.Shader(cgl);
//op.onLoaded=shader.compile;

var srcFrag=''
    .endl()+'#ifdef HAS_TEXTURES'
    .endl()+'  IN vec2 texCoord;'
    .endl()+'  uniform sampler2D image;'
    .endl()+'#endif'
    .endl()+'uniform float n;'
    .endl()+'uniform float f;'
    .endl()+''
    .endl()+'void main()'
    .endl()+'{'
    .endl()+'   vec4 col=vec4(0.0,0.0,0.0,1.0);'
    .endl()+'   #ifdef HAS_TEXTURES'
    .endl()+'       col=texture2D(image,texCoord);'
    .endl()+'       float z=col.r;'
    .endl()+'       float c=(2.0*n)/(f+n-z*(f-n));'

    .endl()+'       #ifdef INVERT'
    .endl()+'       c=1.0-c;'
    .endl()+'       #endif'

    .endl()+'       col=vec4(c,c,c,1.0);'

    .endl()+'   #endif'

    .endl()+'   gl_FragColor = col;'
    .endl()+'}';

shader.setSource(shader.getDefaultVertexShader(),srcFrag);
var textureUniform=new CGL.Uniform(shader,'t','image',0);

var uniFarplane=new CGL.Uniform(shader,'f','f',farPlane);
var uniNearplane=new CGL.Uniform(shader,'f','n',nearPlane);

inInv.onChange=function()
{
    if(inInv.get())shader.define("INVERT");
        else shader.removeDefine("INVERT");
};

render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;

    if(image.val && image.val.tex)
    {
        cgl.setShader(shader);
        cgl.currentTextureEffect.bind();

        /* --- */cgl.setTexture(0, image.get().tex );
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, image.get().tex );

        cgl.currentTextureEffect.finish();
        cgl.setPreviousShader();
    }

    trigger.trigger();
};

};

Ops.Gl.TextureEffects.DepthTexture.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Posterize
// 
// **************************************************************

Ops.Gl.TextureEffects.Posterize = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["posterize_frag"]="uniform sampler2D tex;\nIN vec2 texCoord;\nuniform float levels;\n\nvoid main(void)\n{\n    vec3 srcPixel = texture2D(tex, texCoord  ).rgb;\n    vec3 amountPerLevel = vec3(1.0/levels);\n    vec3 numOfLevels = floor(srcPixel/amountPerLevel);\n    vec3 col = numOfLevels * (vec3(1.0) / (vec3(levels) - vec3(1.0)));\n    gl_FragColor = vec4(col,1.0);\n}\n\n";
op.name="Posterize";

var render=op.inFunction("Render");
var trigger=op.outFunction("Trigger");

var levels=op.inValue("levels",2);

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);
//op.onLoaded=shader.compile;

shader.setSource(shader.getDefaultVertexShader(),attachments.posterize_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var levelsUniform=new CGL.Uniform(shader,'f','levels',levels);

var uniWidth=new CGL.Uniform(shader,'f','texWidth',128);
var uniHeight=new CGL.Uniform(shader,'f','texHeight',128);


render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    uniWidth.setValue(cgl.currentTextureEffect.getCurrentSourceTexture().width);
    uniHeight.setValue(cgl.currentTextureEffect.getCurrentSourceTexture().height);

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Posterize.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.CircleMovement
// 
// **************************************************************

Ops.Gl.Matrix.CircleMovement = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));

var segments=op.addInPort(new Port(op,"segments"));
var radius=op.addInPort(new Port(op,"radius"));
var mulX=op.addInPort(new Port(op,"mulX"));
var mulY=op.addInPort(new Port(op,"mulY"));
var percent=op.addInPort(new Port(op,"percent",OP_PORT_TYPE_VALUE,{display:'range'}));

var offset=op.addInPort(new Port(op,"offset"));

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var index=op.addOutPort(new Port(op,"index"));

var outX=op.addOutPort(new Port(op,"X"));
var outY=op.addOutPort(new Port(op,"Y"));


var speed=op.inValue("speed",1);

var startTime=CABLES.now()/1000;
var cgl=op.patch.cgl;
var animX=new CABLES.TL.Anim();
var animY=new CABLES.TL.Anim();
var pos=[];
animX.loop=true;
animY.loop=true;

segments.set(40);
radius.set(1);
mulX.set(1);
mulY.set(1);

segments.onValueChanged=calc;

calc();

render.onTriggered=function()
{
    cgl.pushModelMatrix();


    var time=(CABLES.now()/1000-startTime)*speed.get()+Math.round(segments.get())*0.1*percent.get();

    var x=animX.getValue(time+offset.get())*mulX.get()*radius.get();
    var y=animY.getValue(time+offset.get())*mulY.get()*radius.get();

    outX.set(x);
    outY.set(y);

    mat4.translate(cgl.mvMatrix,cgl.mvMatrix, [
        x,
        y,
        0] );

    trigger.trigger();

    cgl.popModelMatrix();
};

function calc()
{
    pos.length=0;
    var i=0,degInRad=0;
    animX.clear();
    animY.clear();

    for (i=0; i <= Math.round(segments.get()); i++)
    {
        index.set(i);
        degInRad = (360/Math.round(segments.get()))*i*CGL.DEG2RAD;
        animX.setValue(i*0.1,Math.cos(degInRad));
        animY.setValue(i*0.1,Math.sin(degInRad));
    }
}



};

Ops.Gl.Matrix.CircleMovement.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Perspective
// 
// **************************************************************

Ops.Gl.Perspective = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
// http://stackoverflow.com/questions/5504635/computing-fovx-opengl

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var fovY=op.addInPort(new Port(op,"fov y",OP_PORT_TYPE_VALUE ));
var zNear=op.addInPort(new Port(op,"frustum near",OP_PORT_TYPE_VALUE ));
var zFar=op.addInPort(new Port(op,"frustum far",OP_PORT_TYPE_VALUE ));
var autoAspect=op.inValueBool("Auto Aspect Ratio",true);
var aspect=op.inValue("Aspect Ratio");

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));


var cgl=op.patch.cgl;
zNear.set(0.01);
fovY.set(45);
zFar.set(500.0);

fovY.onValueChanged=changed;
zFar.onValueChanged=changed;
zNear.onValueChanged=changed;
changed();

var asp=0;

render.onTriggered=function()
{
    asp=cgl.getViewPort()[2]/cgl.getViewPort()[3];
    if(!autoAspect.get())asp=aspect.get();
    
    cgl.pushPMatrix();
    mat4.perspective(
        cgl.pMatrix,
        fovY.get()*0.0174533, 
        asp, 
        zNear.get(), 
        zFar.get());

    trigger.trigger();

    cgl.popPMatrix();
};

function changed()
{
    cgl.frameStore.perspective=
    {
        fovy:fovY.get(),
        zFar:zFar.get(),
        zNear:zNear.get(),
    };
}



};

Ops.Gl.Perspective.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.FullscreenRectangle
// 
// **************************************************************

Ops.Gl.Meshes.FullscreenRectangle = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["shader_frag"]="\nUNI sampler2D tex;\nIN vec2 texCoord;\n\nprecision highp float;\n\nvoid main()\n{\n   gl_FragColor = texture2D(tex,vec2(texCoord.x,(1.0-texCoord.y)));\n}\n";
attachments["shader_vert"]="{{MODULES_HEAD}}\n\nIN vec3 vPosition;\nUNI mat4 projMatrix;\nUNI mat4 mvMatrix;\n\nOUT vec2 texCoord;\nIN vec2 attrTexCoord;\n\nvoid main()\n{\n   vec4 pos=vec4(vPosition,  1.0);\n\n   texCoord=attrTexCoord;\n\n\n   gl_Position = projMatrix * mvMatrix * pos;\n}\n";


var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var centerInCanvas=op.addInPort(new Port(op,"Center in Canvas",OP_PORT_TYPE_VALUE,{display:'bool'}));
var flipY=op.inValueBool("Flip Y");

var inTexture=op.inTexture("Texture");


var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var mesh=null;
var geom=new CGL.Geometry("fullscreen rectangle");
var x=0,y=0,z=0,w=0,h=0;

centerInCanvas.onChange=rebuild;
flipY.onChange=rebuild;

var shader=null;
render.onTriggered=doRender;

inTexture.onChange=function()
{
    var tex=inTexture.get();
    // shader=null;
    if(tex && !shader)
    {
        shader=new CGL.Shader(cgl,'fullscreenrectangle');
        shader.setModules(['MODULE_VERTEX_POSITION','MODULE_COLOR','MODULE_BEGIN_FRAG']);

        shader.setSource(attachments.shader_vert,attachments.shader_frag);
        shader.fullscreenRectUniform=new CGL.Uniform(shader,'t','tex',0);
    }
    
    if(!tex)
    {
        shader=null;
    }
};

op.preRender=function()
{
    if(shader)shader.bind();
    if(mesh)mesh.render(shader);
    doRender();
    
};

function doRender()
{
    if( cgl.getViewPort()[2]!=w || cgl.getViewPort()[3]!=h )
    {
        rebuild();
    }

    cgl.pushPMatrix();
    mat4.identity(cgl.pMatrix);
    mat4.ortho(cgl.pMatrix, 0, w,h, 0, -10.0, 1000);

    cgl.pushModelMatrix();
    mat4.identity(cgl.mvMatrix);

    cgl.pushViewMatrix();
    mat4.identity(cgl.vMatrix);

    if(centerInCanvas.get())
    {
        var x=0;
        var y=0;
        if(w<cgl.canvasWidth) x=(cgl.canvasWidth-w)/2;
        if(h<cgl.canvasHeight) y=(cgl.canvasHeight-h)/2;

        cgl.setViewPort(x,y,w,h);
    }

    if(shader)
    {
        if(inTexture.get())
        {
            /* --- */cgl.setTexture(0,inTexture.get().tex);
            // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, inTexture.get().tex);
        }

        mesh.render(shader);
    }
    else
    {
        mesh.render(cgl.getShader());
    }

    cgl.gl.clear(cgl.gl.DEPTH_BUFFER_BIT);

    cgl.popPMatrix();
    cgl.popModelMatrix();
    cgl.popViewMatrix();

    trigger.trigger();
}


function rebuild()
{

    var currentViewPort=cgl.getViewPort();
    if(currentViewPort[2]==w && currentViewPort[3]==h)return;

    var xx=0,xy=0;

    w=currentViewPort[2];
    h=currentViewPort[3];

    geom.vertices = new Float32Array([
         xx+w, xy+h,  0.0,
         xx,   xy+h,  0.0,
         xx+w, xy,    0.0,
         xx,   xy,    0.0
    ]);

    if(flipY.get())
        geom.setTexCoords( new Float32Array([
             1.0, 0.0,
             0.0, 0.0,
             1.0, 1.0,
             0.0, 1.0
        ]));
    else
        geom.setTexCoords(new Float32Array([
             1.0, 1.0,
             0.0, 1.0,
             1.0, 0.0,
             0.0, 0.0
        ]));

    geom.verticesIndices = new Float32Array([
        0, 1, 2,
        3, 1, 2
    ]);


    if(!mesh) mesh=new CGL.Mesh(cgl,geom);
        else mesh.setGeom(geom);
}


};

Ops.Gl.Meshes.FullscreenRectangle.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.Timer2
// 
// **************************************************************

Ops.Anim.Timer2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const playPause=op.inValueBool("Play",true);
const reset=op.inFunctionButton("Reset");
const outTime=op.outValue("Time");
const inSpeed=op.inValue("Speed",1);

var timer=new CABLES.Timer();
var lastTime=0;
var time=0;

playPause.onChange=setState;
setState();

function setState()
{
    if(playPause.get())
    {
        timer.play();
        op.patch.addOnAnimFrame(op);
    }
    else
    {
        timer.pause();
        op.patch.removeOnAnimFrame(op);
    }
}

reset.onTriggered=function()
{
    time=0;
    lastTime=0;
    timer.setTime(0);
    outTime.set(0);
};

op.onAnimFrame=function()
{
    if(timer.isPlaying())
    {
        timer.update();
        if(lastTime===0)
        {
            lastTime=timer.get();
            return;
        }

        const t=timer.get()-lastTime;
        lastTime=timer.get();
        time+=t*inSpeed.get();
        if(time!=time)time=0;
        outTime.set(time);
    }
};


};

Ops.Anim.Timer2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.BrightnessContrast
// 
// **************************************************************

Ops.Gl.TextureEffects.BrightnessContrast = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["brightness_contrast_frag"]="\n#ifdef HAS_TEXTURES\n  IN vec2 texCoord;\n  uniform sampler2D tex;\n#endif\nuniform float amount;\nuniform float amountbright;\n\n\nvoid main()\n{\n   vec4 col=vec4(1.0,0.0,0.0,1.0);\n   #ifdef HAS_TEXTURES\n       col=texture2D(tex,texCoord);\n\n       // appl y contrast\n      col.rgb = ((col.rgb - 0.5) * max(amount*2.0, 0.0))+0.5;\n\n       // appl y brightness\n       col.rgb *= amountbright*2.0;\n\n   #endif\n   gl_FragColor = col;\n}";
var cgl=op.patch.cgl;


var render=op.inFunction("render");
var amount=op.inValueSlider('contrast');
var amountBright=op.inValueSlider('brightness');

var trigger=op.outFunction('trigger');

var shader=new CGL.Shader(cgl);

amountBright.set(0.5);
amount.set(0.5);

shader.setSource(shader.getDefaultVertexShader(),attachments.brightness_contrast_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var amountUniform=new CGL.Uniform(shader,'f','amount',amount);
var amountBrightUniform=new CGL.Uniform(shader,'f','amountbright',amountBright);


render.onTriggered=function()
{
    if(!cgl.currentTextureEffect.getCurrentSourceTexture()) return;
    if(!cgl.currentTextureEffect)return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};

};

Ops.Gl.TextureEffects.BrightnessContrast.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Icosahedron
// 
// **************************************************************

Ops.Gl.Meshes.Icosahedron = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="Icosahedron";

// from: http://blog.andreaskahler.com/search/label/3D

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var smooth=op.addInPort(new Port(op,"smooth",OP_PORT_TYPE_VALUE,{ display:'bool' }));

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var geomOut=op.addOutPort(new Port(op,"geometry",OP_PORT_TYPE_OBJECT));

geomOut.ignoreValueSerialize=true;

smooth.onValueChanged=generate;

var mesh=null;
var cgl=op.patch.cgl;
smooth.set(false);

render.onTriggered=function()
{
    if(mesh) mesh.render(cgl.getShader());
    trigger.trigger();
};

function generate()
{
    var t = Math.sqrt(5.0) / 2;
    var tc=[];
    var verts=[];
    verts.push(-1,  t,  0);
    verts.push( 1,  t,  0);
    verts.push(-1, -t,  0);
    verts.push( 1, -t,  0);
    
    verts.push( 0, -1,  t);
    verts.push( 0,  1,  t);
    verts.push( 0, -1, -t);
    verts.push( 0,  1, -t);
    
    verts.push( t,  0, -1);
    verts.push( t,  0,  1);
    verts.push(-t,  0, -1);
    verts.push(-t,  0,  1);
    
    var geom=new CGL.Geometry();
    
    geom.vertices=verts;
    geom.verticesIndices = [];
    
    // 5 faces around point 0
    geom.verticesIndices.push(0, 11, 5);
    geom.verticesIndices.push(0, 5, 1);
    geom.verticesIndices.push(0, 1, 7);
    geom.verticesIndices.push(0, 7, 10);
    geom.verticesIndices.push(0, 10, 11);
    
    // 5 adjacent faces 
    geom.verticesIndices.push(1, 5, 9);
    geom.verticesIndices.push(5, 11, 4);
    geom.verticesIndices.push(11, 10, 2);
    geom.verticesIndices.push(10, 7, 6);
    geom.verticesIndices.push(7, 1, 8);
    
    // 5 faces around point 3
    geom.verticesIndices.push(3, 9, 4);
    geom.verticesIndices.push(3, 4, 2);
    geom.verticesIndices.push(3, 2, 6);
    geom.verticesIndices.push(3, 6, 8);
    geom.verticesIndices.push(3, 8, 9);
    
    // 5 adjacent faces 
    geom.verticesIndices.push(4, 9, 5);
    geom.verticesIndices.push(2, 4, 11);
    geom.verticesIndices.push(6, 2, 10);
    geom.verticesIndices.push(8, 6, 7);
    geom.verticesIndices.push(9, 8, 1);
    

    
    geom.texCoords=tc;
    
    geom.calcNormals(smooth.get());
    mesh=new CGL.Mesh(cgl,geom);
    geomOut.set(geom);

}

generate();

};

Ops.Gl.Meshes.Icosahedron.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ShaderEffects.PerlinAreaDeform
// 
// **************************************************************

Ops.Gl.ShaderEffects.PerlinAreaDeform = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["perlin_deformer_vert"]="\nUNI bool MOD_smooth;\nUNI float MOD_x,MOD_y,MOD_z;\nUNI float MOD_strength;\nUNI float MOD_size;\nUNI float MOD_scale;\nUNI float MOD_scrollx;\nUNI float MOD_scrolly;\nUNI float MOD_scrollz;\n\n    \n\nfloat Interpolation_C2( float x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }   //  6x^5-15x^4+10x^3\t( Quintic Curve.  As used by Perlin in Improved Noise.  http://mrl.nyu.edu/~perlin/paper445.pdf )\nvec2 Interpolation_C2( vec2 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec3 Interpolation_C2( vec3 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec4 Interpolation_C2( vec4 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec4 Interpolation_C2_InterpAndDeriv( vec2 x ) { return x.xyxy * x.xyxy * ( x.xyxy * ( x.xyxy * ( x.xyxy * vec2( 6.0, 0.0 ).xxyy + vec2( -15.0, 30.0 ).xxyy ) + vec2( 10.0, -60.0 ).xxyy ) + vec2( 0.0, 30.0 ).xxyy ); }\nvec3 Interpolation_C2_Deriv( vec3 x ) { return x * x * (x * (x * 30.0 - 60.0) + 30.0); }\n\n\nvoid FAST32_hash_3D( \tvec3 gridcell,\n                        out vec4 lowz_hash_0,\n                        out vec4 lowz_hash_1,\n                        out vec4 lowz_hash_2,\n                        out vec4 highz_hash_0,\n                        out vec4 highz_hash_1,\n                        out vec4 highz_hash_2\t)\t\t//\tgenerates 3 random numbers for each of the 8 cell corners\n{\n    //    gridcell is assumed to be an integer coordinate\n\n    //\tTODO: \tthese constants need tweaked to find the best possible noise.\n    //\t\t\tprobably requires some kind of brute force computational searching or something....\n    const vec2 OFFSET = vec2( 50.0, 161.0 );\n    const float DOMAIN = 69.0;\n    const vec3 SOMELARGEFLOATS = vec3( 635.298681, 682.357502, 668.926525 );\n    const vec3 ZINC = vec3( 48.500388, 65.294118, 63.934599 );\n\n    //\ttruncate the domain\n    gridcell.xyz = gridcell.xyz - floor(gridcell.xyz * ( 1.0 / DOMAIN )) * DOMAIN;\n    vec3 gridcell_inc1 = step( gridcell, vec3( DOMAIN - 1.5 ) ) * ( gridcell + 1.0 );\n\n    //\tcalculate the noise\n    vec4 P = vec4( gridcell.xy, gridcell_inc1.xy ) + OFFSET.xyxy;\n    P *= P;\n    P = P.xzxz * P.yyww;\n    vec3 lowz_mod = vec3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell.zzz * ZINC.xyz ) );\n    vec3 highz_mod = vec3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell_inc1.zzz * ZINC.xyz ) );\n    lowz_hash_0 = fract( P * lowz_mod.xxxx );\n    highz_hash_0 = fract( P * highz_mod.xxxx );\n    lowz_hash_1 = fract( P * lowz_mod.yyyy );\n    highz_hash_1 = fract( P * highz_mod.yyyy );\n    lowz_hash_2 = fract( P * lowz_mod.zzzz );\n    highz_hash_2 = fract( P * highz_mod.zzzz );\n}\n\n//\n//\tPerlin Noise 3D  ( gradient noise )\n//\tReturn value range of -1.0->1.0\n//\thttp://briansharpe.files.wordpress.com/2011/11/perlinsample.jpg\n//\nfloat Perlin3D( vec3 P )\n{\n    //\testablish our grid cell and unit position\n    vec3 Pi = floor(P);\n    vec3 Pf = P - Pi;\n    vec3 Pf_min1 = Pf - 1.0;\n\n#if 1\n    //\n    //\tclassic noise.\n    //\trequires 3 random values per point.  with an efficent hash function will run faster than improved noise\n    //\n\n    //\tcalculate the hash.\n    //\t( various hashing methods listed in order of speed )\n    vec4 hashx0, hashy0, hashz0, hashx1, hashy1, hashz1;\n    FAST32_hash_3D( Pi, hashx0, hashy0, hashz0, hashx1, hashy1, hashz1 );\n    //SGPP_hash_3D( Pi, hashx0, hashy0, hashz0, hashx1, hashy1, hashz1 );\n\n    //\tcalculate the gradients\n    vec4 grad_x0 = hashx0 - 0.49999;\n    vec4 grad_y0 = hashy0 - 0.49999;\n    vec4 grad_z0 = hashz0 - 0.49999;\n    vec4 grad_x1 = hashx1 - 0.49999;\n    vec4 grad_y1 = hashy1 - 0.49999;\n    vec4 grad_z1 = hashz1 - 0.49999;\n    vec4 grad_results_0 = inversesqrt( grad_x0 * grad_x0 + grad_y0 * grad_y0 + grad_z0 * grad_z0 ) * ( vec2( Pf.x, Pf_min1.x ).xyxy * grad_x0 + vec2( Pf.y, Pf_min1.y ).xxyy * grad_y0 + Pf.zzzz * grad_z0 );\n    vec4 grad_results_1 = inversesqrt( grad_x1 * grad_x1 + grad_y1 * grad_y1 + grad_z1 * grad_z1 ) * ( vec2( Pf.x, Pf_min1.x ).xyxy * grad_x1 + vec2( Pf.y, Pf_min1.y ).xxyy * grad_y1 + Pf_min1.zzzz * grad_z1 );\n\n#if 1\n    //\tClassic Perlin Interpolation\n    vec3 blend = Interpolation_C2( Pf );\n    vec4 res0 = mix( grad_results_0, grad_results_1, blend.z );\n    vec4 blend2 = vec4( blend.xy, vec2( 1.0 - blend.xy ) );\n    float final = dot( res0, blend2.zxzx * blend2.wwyy );\n    final *= 1.1547005383792515290182975610039;\t\t//\t(optionally) scale things to a strict -1.0->1.0 range    *= 1.0/sqrt(0.75)\n    return final;\n#else\n    //\tClassic Perlin Surflet\n    //\thttp://briansharpe.wordpress.com/2012/03/09/modifications-to-classic-perlin-noise/\n    Pf *= Pf;\n    Pf_min1 *= Pf_min1;\n    vec4 vecs_len_sq = vec4( Pf.x, Pf_min1.x, Pf.x, Pf_min1.x ) + vec4( Pf.yy, Pf_min1.yy );\n    float final = dot( Falloff_Xsq_C2( min( vec4( 1.0 ), vecs_len_sq + Pf.zzzz ) ), grad_results_0 ) + dot( Falloff_Xsq_C2( min( vec4( 1.0 ), vecs_len_sq + Pf_min1.zzzz ) ), grad_results_1 );\n    final *= 2.3703703703703703703703703703704;\t\t//\t(optionally) scale things to a strict -1.0->1.0 range    *= 1.0/cube(0.75)\n    return final;\n#endif\n\n#else\n    //\n    //\timproved noise.\n    //\trequires 1 random value per point.  Will run faster than classic noise if a slow hashing function is used\n    //\n\n    //\tcalculate the hash.\n    //\t( various hashing methods listed in order of speed )\n    vec4 hash_lowz, hash_highz;\n    FAST32_hash_3D( Pi, hash_lowz, hash_highz );\n    //BBS_hash_3D( Pi, hash_lowz, hash_highz );\n    //SGPP_hash_3D( Pi, hash_lowz, hash_highz );\n\n    //\n    //\t\"improved\" noise using 8 corner gradients.  Faster than the 12 mid-edge point method.\n    //\tKen mentions using diagonals like this can cause \"clumping\", but we'll live with that.\n    //\t[1,1,1]  [-1,1,1]  [1,-1,1]  [-1,-1,1]\n    //\t[1,1,-1] [-1,1,-1] [1,-1,-1] [-1,-1,-1]\n    //\n    hash_lowz -= 0.5;\n    vec4 grad_results_0_0 = vec2( Pf.x, Pf_min1.x ).xyxy * sign( hash_lowz );\n    hash_lowz = abs( hash_lowz ) - 0.25;\n    vec4 grad_results_0_1 = vec2( Pf.y, Pf_min1.y ).xxyy * sign( hash_lowz );\n    vec4 grad_results_0_2 = Pf.zzzz * sign( abs( hash_lowz ) - 0.125 );\n    vec4 grad_results_0 = grad_results_0_0 + grad_results_0_1 + grad_results_0_2;\n\n    hash_highz -= 0.5;\n    vec4 grad_results_1_0 = vec2( Pf.x, Pf_min1.x ).xyxy * sign( hash_highz );\n    hash_highz = abs( hash_highz ) - 0.25;\n    vec4 grad_results_1_1 = vec2( Pf.y, Pf_min1.y ).xxyy * sign( hash_highz );\n    vec4 grad_results_1_2 = Pf_min1.zzzz * sign( abs( hash_highz ) - 0.125 );\n    vec4 grad_results_1 = grad_results_1_0 + grad_results_1_1 + grad_results_1_2;\n\n    //\tblend the gradients and return\n    vec3 blend = Interpolation_C2( Pf );\n    vec4 res0 = mix( grad_results_0, grad_results_1, blend.z );\n    vec4 blend2 = vec4( blend.xy, vec2( 1.0 - blend.xy ) );\n    return dot( res0, blend2.zxzx * blend2.wwyy ) * (2.0 / 3.0);\t//\t(optionally) mult by (2.0/3.0) to scale to a strict -1.0->1.0 range\n#endif\n}\n\nvec3 MOD_deform(vec3 pos)\n{\n    // vec3 MOD_pos=vec3();\n    vec3 modelPos=pos;\n    vec3 forcePos=vec3(MOD_x,MOD_y,MOD_z);\n    \n\n    vec3 vecToOrigin=modelPos-forcePos;\n    float dist=abs(length(vecToOrigin));\n    float distAlpha = (MOD_size - dist) / MOD_size;\n\n    if(MOD_smooth) distAlpha=smoothstep(0.0,MOD_size,distAlpha);\n\n    \n    vec3 ppos=vec3(pos*MOD_scale);\n    ppos.x+=MOD_scrollx;\n    ppos.y+=MOD_scrolly;\n    ppos.z+=MOD_scrollz;\n    \n    float p=Perlin3D(ppos)*MOD_strength*distAlpha;\n    \n    vec3 pnorm=normalize(pos.xyz);\n    \n    pos.x+=p*pnorm.x;\n    pos.y+=p*pnorm.y;\n    pos.z+=p*pnorm.z;\n\n    return pos;\n}\n\n\nvec3 MOD_calcNormal(vec3 pos)\n{\n    float theta = .0001; \n    vec3 vecTangent = normalize(cross(pos, vec3(1.0, 0.0, 0.0))\n     + cross(pos, vec3(0.0, 1.0, 0.0)));\n    vec3 vecBitangent = normalize(cross(vecTangent, pos));\n    vec3 ptTangentSample = MOD_deform(normalize(pos + theta * normalize(vecTangent)));\n    vec3 ptBitangentSample = MOD_deform(normalize(pos + theta * normalize(vecBitangent)));\n\n    return normalize(cross(ptTangentSample - pos, ptBitangentSample - pos));\n}\n";
attachments["perlin_deformer_body_vert"]="\nvec3 MOD_newPos;\n#ifndef MOD_WORLDSPACE\n   MOD_newPos=MOD_deform(pos.xyz);\n   norm=MOD_calcNormal(pos.xyz);\n#endif\n\n#ifdef MOD_WORLDSPACE\n   MOD_newPos=MOD_deform( (mMatrix*pos).xyz );\n#endif\n\n#ifdef MOD_DO_X\n    pos.x=MOD_newPos.x;\n#endif\n#ifdef MOD_DO_Y\n    pos.y=MOD_newPos.y;\n#endif\n#ifdef MOD_DO_Z\n    pos.z=MOD_newPos.z;\n#endif\n\n";

var cgl=op.patch.cgl;

op.render=op.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
op.trigger=op.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));

var inScale=op.inValue("Scale",1);
var inSize=op.inValue("Size",1);
var inStrength=op.inValue("Strength",1);
var inSmooth=op.inValueBool("Smooth",true);

var x=op.inValue("x");
var y=op.inValue("y");
var z=op.inValue("z");

var scrollx=op.inValue("Scroll X");
var scrolly=op.inValue("Scroll Y");
var scrollz=op.inValue("Scroll Z");

var doX=op.inValueBool("Deform X",true);
var doY=op.inValueBool("Deform Y",true);
var doZ=op.inValueBool("Deform Z",true);


var shader=null;

var inWorldSpace=op.inValueBool("WorldSpace");


var srcHeadVert=attachments.perlin_deformer_vert;

doX.onChange=updateAxis;
doY.onChange=updateAxis;
doZ.onChange=updateAxis;


var srcBodyVert=attachments.perlin_deformer_body_vert;
    // .endl()+'#ifndef MOD_WORLDSPACE'
    // .endl()+'   pos=MOD_deform(pos);'
    // .endl()+'#endif'
    // .endl()+'#ifdef MOD_WORLDSPACE'
    // .endl()+'   pos=MOD_deform(mMatrix*pos);'
    // .endl()+'#endif'
    // .endl();

var moduleVert=null;

function removeModule()
{
    if(shader && moduleVert) shader.removeModule(moduleVert);
    shader=null;
}


op.render.onLinkChanged=removeModule;


inWorldSpace.onChange=updateWorldspace;

function updateWorldspace()
{
    if(!shader)return;
    if(inWorldSpace.get()) shader.define(moduleVert.prefix+"WORLDSPACE");
        else shader.removeDefine(moduleVert.prefix+"WORLDSPACE");
}

function updateAxis()
{
    if(!shader)return;
    shader.removeDefine(moduleVert.prefix+"DO_X");
    shader.removeDefine(moduleVert.prefix+"DO_Y");
    shader.removeDefine(moduleVert.prefix+"DO_Z");
    if(doX.get()) shader.define(moduleVert.prefix+"DO_X");
    if(doY.get()) shader.define(moduleVert.prefix+"DO_Y");
    if(doZ.get()) shader.define(moduleVert.prefix+"DO_Z");
        
}


op.render.onTriggered=function()
{
    if(!cgl.getShader())
    {
         op.trigger.trigger();
         return;
    }
    
    if(CABLES.UI && CABLES.UI.renderHelper)
    {
        cgl.pushModelMatrix();
        mat4.translate(cgl.mvMatrix,cgl.mvMatrix,[x.get(),y.get(),z.get()]);
        CABLES.GL_MARKER.drawSphere(op,inSize.get());
        cgl.popModelMatrix();
    }


    if(CABLES.UI && gui.patch().isCurrentOp(op)) 
        gui.setTransformGizmo(
            {
                posX:x,
                posY:y,
                posZ:z
            });

    if(cgl.getShader()!=shader)
    {
        if(shader) removeModule();
        shader=cgl.getShader();

        moduleVert=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_VERTEX_POSITION',
                srcHeadVert:srcHeadVert,
                srcBodyVert:srcBodyVert
            });

        inSize.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'size',inSize);
        inStrength.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'strength',inStrength);
        inSmooth.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'smooth',inSmooth);
        inScale.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'scale',inScale);

        scrollx.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'scrollx',scrollx);
        scrolly.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'scrolly',scrolly);
        scrollz.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'scrollz',scrollz);

        x.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'x',x);
        y.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'y',y);
        z.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'z',z);
        
        updateWorldspace();
        updateAxis();
    }
    
    
    if(!shader)return;

    op.trigger.trigger();
};















};

Ops.Gl.ShaderEffects.PerlinAreaDeform.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Blur
// 
// **************************************************************

Ops.Gl.TextureEffects.Blur = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["blur_frag"]="IN vec2 texCoord;\nUNI sampler2D tex;\nUNI float dirX;\nUNI float dirY;\nUNI float amount;\n\n#ifdef HAS_MASK\n    UNI sampler2D imageMask;\n#endif\n\nfloat random(vec3 scale, float seed)\n{\n    return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);\n}\n\nvoid main()\n{\n    vec4 color = vec4(0.0);\n    float total = 0.0;\n\n    float am=amount;\n    #ifdef HAS_MASK\n        am=amount*texture2D(imageMask,texCoord).r;\n        if(am<=0.02)\n        {\n            gl_FragColor=texture2D(tex, texCoord);\n            return;\n        }\n    #endif\n\n   vec2 delta=vec2(dirX*am*0.01,dirY*am*0.01);\n\n\n    \n    float offset = random(vec3(12.9898, 78.233, 151.7182), 0.0);\n\n\n    #ifndef FASTBLUR\n    const float range=20.0;\n    #endif\n    #ifdef FASTBLUR\n    const float range=5.0;\n    #endif\n\n    for (float t = -range; t <= range; t++)\n    {\n        float percent = (t + offset - 0.5) / range;\n        float weight = 1.0 - abs(percent);\n        vec4 smpl = texture2D(tex, texCoord + delta * percent);\n        \n        smpl.rgb *= smpl.a;\n\n        color += smpl * weight;\n        total += weight;\n    }\n\n    gl_FragColor = color / total;\n\n    gl_FragColor.rgb /= gl_FragColor.a + 0.00001;\n}";
var cgl=op.patch.cgl;

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var fast=op.inValueBool("Fast",true);




var amount=op.addInPort(new Port(op,"amount",OP_PORT_TYPE_VALUE));
amount.set(10);

var shader=new CGL.Shader(cgl);
//op.onLoaded=shader.compile;

shader.define("FASTBLUR");

fast.onChange=function()
{
    if(fast.get()) shader.define("FASTBLUR");
        else shader.removeDefine("FASTBLUR");
};

shader.setSource(shader.getDefaultVertexShader(),attachments.blur_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

var uniDirX=new CGL.Uniform(shader,'f','dirX',0);
var uniDirY=new CGL.Uniform(shader,'f','dirY',0);

var uniWidth=new CGL.Uniform(shader,'f','width',0);
var uniHeight=new CGL.Uniform(shader,'f','height',0);

var uniAmount=new CGL.Uniform(shader,'f','amount',amount.get());
amount.onValueChange(function(){ uniAmount.setValue(amount.get()); });

var textureAlpha=new CGL.Uniform(shader,'t','imageMask',1);


var direction=op.addInPort(new Port(op,"direction",OP_PORT_TYPE_VALUE,{display:'dropdown',values:['both','vertical','horizontal']}));
var dir=0;
direction.set('both');
direction.onValueChange(function()
{
    if(direction.get()=='both')dir=0;
    if(direction.get()=='horizontal')dir=1;
    if(direction.get()=='vertical')dir=2;
});

var mask=op.addInPort(new Port(op,"mask",OP_PORT_TYPE_TEXTURE,{preview:true }));

mask.onValueChanged=function()
{
    if(mask.get() && mask.get().tex) shader.define('HAS_MASK');
        else shader.removeDefine('HAS_MASK');
};



render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);

    uniWidth.setValue(cgl.currentTextureEffect.getCurrentSourceTexture().width);
    uniHeight.setValue(cgl.currentTextureEffect.getCurrentSourceTexture().height);

    // first pass
    if(dir===0 || dir==2)
    {

        cgl.currentTextureEffect.bind();
        /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

        if(mask.get() && mask.get().tex)
        {
            /* --- */cgl.setTexture(1, mask.get().tex );
            // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, mask.get().tex );
        }


        uniDirX.setValue(0.0);
        uniDirY.setValue(1.0);

        cgl.currentTextureEffect.finish();
    }

    // second pass
    if(dir===0 || dir==1)
    {

        cgl.currentTextureEffect.bind();
        /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

        if(mask.get() && mask.get().tex)
        {
            /* --- */cgl.setTexture(1, mask.get().tex );
            // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, mask.get().tex );
        }

        uniDirX.setValue(1.0);
        uniDirY.setValue(0.0);

        cgl.currentTextureEffect.finish();
    }

    cgl.setPreviousShader();
    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Blur.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Value.Value
// 
// **************************************************************

Ops.Value.Value = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var v=op.addInPort(new Port(op,"value",OP_PORT_TYPE_VALUE));
var result=op.addOutPort(new Port(op,"result"));

var exec=function()
{
    result.set(parseFloat(v.get()));
};

v.onValueChanged=exec;


};

Ops.Value.Value.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.FXAA
// 
// **************************************************************

Ops.Gl.TextureEffects.FXAA = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
// shader from: https://github.com/mattdesl/glsl-fxaa

op.name='FXAA';
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var fxaa_span=op.addInPort(new Port(op,"span",OP_PORT_TYPE_VALUE,{display:'dropdown',values:[0,2,4,8,16,32,64]}));
var fxaa_reduceMin=op.addInPort(new Port(op,"reduceMin",OP_PORT_TYPE_VALUE));
var fxaa_reduceMul=op.addInPort(new Port(op,"reduceMul",OP_PORT_TYPE_VALUE));

var useVPSize=op.inValueBool("use viewport size",true);

var texWidth=op.addInPort(new Port(op,"width",OP_PORT_TYPE_VALUE));
var texHeight=op.addInPort(new Port(op,"height",OP_PORT_TYPE_VALUE));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);
//op.onLoaded=shader.compile;

var srcFrag=''
    .endl()+'precision highp float;'
    .endl()+'#ifdef HAS_TEXTURES'
    .endl()+'  IN vec2 texCoord;'
    .endl()+'  uniform sampler2D tex;'
    .endl()+'#endif'
    .endl()+'    uniform float FXAA_SPAN_MAX;'

    .endl()+'    uniform float FXAA_REDUCE_MUL;'
    .endl()+'    uniform float FXAA_REDUCE_MIN;'
    .endl()+'    uniform float width;'
    .endl()+'    uniform float height;'

    .endl()+'vec4 getColorFXAA(vec2 coord)'
    .endl()+'{'
    .endl()+'    vec2 invtexsize=vec2(1.0/width,1.0/height);'
    .endl()+''
    .endl()+'    float step=1.0;'
    .endl()+''
    .endl()+'    vec3 rgbNW = texture2D(tex, coord.xy + (vec2(-step, -step)*invtexsize )).xyz;'
    .endl()+'    vec3 rgbNE = texture2D(tex, coord.xy + (vec2(+step, -step)*invtexsize )).xyz;'
    .endl()+'    vec3 rgbSW = texture2D(tex, coord.xy + (vec2(-step, +step)*invtexsize )).xyz;'
    .endl()+'    vec3 rgbSE = texture2D(tex, coord.xy + (vec2(+step, +step)*invtexsize )).xyz;'
    .endl()+'    vec3 rgbM  = texture2D(tex, coord.xy).xyz;'
    .endl()+''
    .endl()+'    vec3 luma = vec3(0.299, 0.587, 0.114);'
    .endl()+'    float lumaNW = dot(rgbNW, luma);'
    .endl()+'    float lumaNE = dot(rgbNE, luma);'
    .endl()+'    float lumaSW = dot(rgbSW, luma);'
    .endl()+'    float lumaSE = dot(rgbSE, luma);'
    .endl()+'    float lumaM  = dot( rgbM, luma);'
    .endl()+''
    .endl()+'    float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));'
    .endl()+'    float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));'
    .endl()+''
    .endl()+'    vec2 dir;'
    .endl()+'    dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));'
    .endl()+'    dir.y =  ((lumaNW + lumaSW) - (lumaNE + lumaSE));'
    .endl()+''
    .endl()+'    float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);'
    .endl()+''
    .endl()+'    float rcpDirMin = 1.0/(min(abs(dir.x), abs(dir.y)) + dirReduce);'
    .endl()+''
    .endl()+'    dir = min(vec2(FXAA_SPAN_MAX,  FXAA_SPAN_MAX),'
    .endl()+'          max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin))*invtexsize ;'
    .endl()+''
    .endl()+'    vec3 rgbA = (1.0/2.0) * ('
    .endl()+'                texture2D(tex, coord.xy + dir * (1.0/3.0 - 0.5)).xyz +'
    .endl()+'                texture2D(tex, coord.xy + dir * (2.0/3.0 - 0.5)).xyz);'
    .endl()+'    vec3 rgbB = rgbA * (1.0/2.0) + (1.0/4.0) * ('
    .endl()+'                texture2D(tex, coord.xy + dir * (0.0/3.0 - 0.5)).xyz +'
    .endl()+'                texture2D(tex, coord.xy + dir * (3.0/3.0 - 0.5)).xyz);'
    .endl()+'    float lumaB = dot(rgbB, luma);'
    .endl()+''
    .endl()+'    vec4 color=texture2D(tex,coord).rgba;'
    .endl()+''
    .endl()+'    if((lumaB < lumaMin) || (lumaB > lumaMax)){'
    .endl()+'      color.xyz=rgbA;'
    .endl()+'    } else {'
    .endl()+'      color.xyz=rgbB;'
    .endl()+'    }'
    .endl()+'    return color;'
    .endl()+'}'
    .endl()+''
    .endl()+'void main()'
    .endl()+'{'
    .endl()+'   vec4 col=vec4(1.0,0.0,0.0,1.0);'
    .endl()+'   gl_FragColor = getColorFXAA(texCoord);'
    .endl()+'}';

shader.setSource(shader.getDefaultVertexShader(),srcFrag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;
    cgl.setShader(shader);

    if(cgl.getViewPort()[2]!=texWidth.get() || cgl.getViewPort()[3]!=texHeight.get())
    {
        changeRes();
    }

    cgl.currentTextureEffect.bind();
    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();

    cgl.setPreviousShader();

    trigger.trigger();
};


var uniformSpan=new CGL.Uniform(shader,'f','FXAA_SPAN_MAX',0);
var uniformMul=new CGL.Uniform(shader,'f','FXAA_REDUCE_MUL',0);
var uniformMin=new CGL.Uniform(shader,'f','FXAA_REDUCE_MIN',0);

fxaa_span.onValueChanged=function()
{
    uniformSpan.setValue(parseInt(fxaa_span.get(),10));
};

var uWidth=new CGL.Uniform(shader,'f','width',0);
var uHeight=new CGL.Uniform(shader,'f','height',0);

function changeRes()
{
    if(useVPSize.get())
    {
        var w=cgl.getViewPort()[2];
        var h=cgl.getViewPort()[3];
        uWidth.setValue(w);
        uHeight.setValue(h);
        // texWidth.set(w);
        // texHeight.set(h);
    }
    else
    {
        uWidth.setValue(texWidth.get());
        uHeight.setValue(texHeight.get());
    }
}

texWidth.onValueChanged=changeRes;
texHeight.onValueChanged=changeRes;
useVPSize.onValueChanged=changeRes;
op.onResize=changeRes;

fxaa_span.set(8);
// texWidth.set(1920);
// texHeight.set(1080);

fxaa_reduceMul.onValueChanged=function()
{
    uniformMul.setValue(1.0/fxaa_reduceMul.get());
};

fxaa_reduceMin.onValueChanged=function()
{
    uniformMin.setValue(1.0/fxaa_reduceMin.get());
};

fxaa_reduceMul.set(8);
fxaa_reduceMin.set(128);


};

Ops.Gl.TextureEffects.FXAA.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.AverageInterpolation
// 
// **************************************************************

Ops.Anim.AverageInterpolation = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var divisor=5;

var exec=op.inFunction("Update");
var inVal=op.inValue("Value");

var next=op.outFunction("Next");
var inDivisor=op.inValue("Divisor",divisor);
var result=op.outValue("Result",0);

var val=0;
var goal=0;

var oldVal=0;
var lastTrigger=0;

inVal.onChange=function()
{
    goal=inVal.get();
};

inDivisor.onChange=function()
{
    divisor=inDivisor.get();
    if(divisor<=0)divisor=5;
};


exec.onTriggered=function()
{
    var tm=1;
    if(CABLES.now()-lastTrigger>500 || lastTrigger==0)val=inVal.get();
    else tm=(CABLES.now()-lastTrigger)/16;
    lastTrigger=CABLES.now();
    

    if(divisor<=0)divisor=0.0001;
    val=val+(goal-val)/(divisor*tm);

    if(val>0 && val<0.000000001)val=0;
    if(divisor!=divisor)val=0;
    if(val!=val|| val== -Infinity || val==Infinity)val=inVal.get();
    
    if(oldVal!=val)
    {
        result.set(val);
        oldVal=val;
    }
    
    next.trigger();
};

};

Ops.Anim.AverageInterpolation.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.Transform
// 
// **************************************************************

Ops.Gl.Matrix.Transform = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));


const posX=op.addInPort(new Port(op,"posX"),0);
const posY=op.addInPort(new Port(op,"posY"),0);
const posZ=op.addInPort(new Port(op,"posZ"),0);

const scale=op.addInPort(new Port(op,"scale"));

const rotX=op.addInPort(new Port(op,"rotX"));
const rotY=op.addInPort(new Port(op,"rotY"));
const rotZ=op.addInPort(new Port(op,"rotZ"));

op.setPortGroup([rotX,rotY,rotZ]);
op.setPortGroup([posX,posY,posZ]);


var cgl=op.patch.cgl;
var vPos=vec3.create();
var vScale=vec3.create();
var transMatrix = mat4.create();
mat4.identity(transMatrix);

var doScale=false;
var doTranslate=false;

var translationChanged=true;
var scaleChanged=true;
var rotChanged=true;

scale.setUiAttribs({"divider":true});

render.onTriggered=function()
{
    var updateMatrix=false;
    if(translationChanged)
    {
        updateTranslation();
        updateMatrix=true;
    }
    if(scaleChanged)
    {
        updateScale();
        updateMatrix=true;
    }
    if(rotChanged)
    {
        updateMatrix=true;
    }
    if(updateMatrix)doUpdateMatrix();

    cgl.pushModelMatrix();
    mat4.multiply(cgl.mMatrix,cgl.mMatrix,transMatrix);

    trigger.trigger();
    cgl.popModelMatrix();
    
    if(CABLES.UI && gui.patch().isCurrentOp(op)) 
        gui.setTransformGizmo(
            {
                posX:posX,
                posY:posY,
                posZ:posZ,
            });

    
};

op.transform3d=function()
{
    return {
            pos:[posX,posY,posZ]
        };
    
};

var doUpdateMatrix=function()
{
    mat4.identity(transMatrix);
    if(doTranslate)mat4.translate(transMatrix,transMatrix, vPos);

    if(rotX.get()!==0)mat4.rotateX(transMatrix,transMatrix, rotX.get()*CGL.DEG2RAD);
    if(rotY.get()!==0)mat4.rotateY(transMatrix,transMatrix, rotY.get()*CGL.DEG2RAD);
    if(rotZ.get()!==0)mat4.rotateZ(transMatrix,transMatrix, rotZ.get()*CGL.DEG2RAD);

    if(doScale)mat4.scale(transMatrix,transMatrix, vScale);
    rotChanged=false;
};

function updateTranslation()
{
    doTranslate=false;
    if(posX.get()!==0.0 || posY.get()!==0.0 || posZ.get()!==0.0) doTranslate=true;
    vec3.set(vPos, posX.get(),posY.get(),posZ.get());
    translationChanged=false;
}

function updateScale()
{
    doScale=false;
    if(scale.get()!==0.0)doScale=true;
    vec3.set(vScale, scale.get(),scale.get(),scale.get());
    scaleChanged=false;
}

var translateChanged=function()
{
    translationChanged=true;
};

var scaleChanged=function()
{
    scaleChanged=true;
};

var rotChanged=function()
{
    rotChanged=true;
};


rotX.onChange=rotChanged;
rotY.onChange=rotChanged;
rotZ.onChange=rotChanged;

scale.onChange=scaleChanged;

posX.onChange=translateChanged;
posY.onChange=translateChanged;
posZ.onChange=translateChanged;

rotX.set(0.0);
rotY.set(0.0);
rotZ.set(0.0);

scale.set(1.0);

posX.set(0.0);
posY.set(0.0);
posZ.set(0.0);

doUpdateMatrix();



};

Ops.Gl.Matrix.Transform.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.TriggerCounter
// 
// **************************************************************

Ops.Trigger.TriggerCounter = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const exe=op.inFunctionButton("exe");
const reset=op.inFunctionButton("reset");//op.addInPort(new Port(op,"reset",OP_PORT_TYPE_FUNCTION));
const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
const num=op.addOutPort(new Port(op,"timesTriggered",OP_PORT_TYPE_VALUE));

var n=0;

exe.onTriggered= function()
{
    n++;
    num.set(n);
    trigger.trigger();
};

reset.onTriggered= function()
{
    n=0;
    num.set(n);
};


};

Ops.Trigger.TriggerCounter.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Multiply
// 
// **************************************************************

Ops.Math.Multiply = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const number1=op.addInPort(new Port(op,"number1"));
const number2=op.addInPort(new Port(op,"number2"));
const result=op.addOutPort(new Port(op,"result"));

function update()
{
    const n1=number1.get();
    const n2=number2.get();

    if(isNaN(n1))n1=0;
    if(isNaN(n2))n2=0;

    result.set( n1*n2 );
}

number1.onValueChanged=update;
number2.onValueChanged=update;

number1.set(1);
number2.set(2);


};

Ops.Math.Multiply.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Circle
// 
// **************************************************************

Ops.Gl.Meshes.Circle = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const render=op.inFunction("render");
const segments=op.inValueInt('segments',40);
const radius=op.inValue('radius',0.5);
const innerRadius=op.inValueSlider('innerRadius',0);
const percent=op.inValueSlider('percent');
const steps=op.inValue('steps',0);
const invertSteps=op.inValueBool('invertSteps',false);
const doRender=op.inValueBool('Render',true);


const trigger=op.outFunction('trigger');
const geomOut=op.addOutPort(new Port(op,"geometry",OP_PORT_TYPE_OBJECT));

geomOut.ignoreValueSerialize=true;
var cgl=op.patch.cgl;

var drawSpline=op.addInPort(new Port(op,"Spline",OP_PORT_TYPE_VALUE,{ display:'bool' }));
drawSpline.set(false);

var oldPrim=0;
var shader=null;


op.preRender=
render.onTriggered=function()
{
    // if(op.instanced(render))return;
    
    shader=cgl.getShader();
    if(!shader)return;
    oldPrim=shader.glPrimitive;
    
    if(drawSpline.get()) shader.glPrimitive=cgl.gl.LINE_STRIP;

    if(doRender.get())mesh.render(shader);
    trigger.trigger();

    shader.glPrimitive=oldPrim;
};

percent.set(1);

var geom=new CGL.Geometry("circle");
var mesh=null;
var lastSegs=-1;
function calc()
{
    var segs=Math.max(3,Math.floor(segments.get()));
    
    geom.clear();

    var faces=[];
    var texCoords=[];
    var vertexNormals=[];

    var i=0,degInRad=0;
    var oldPosX=0,oldPosY=0;
    var oldPosXTexCoord=0,oldPosYTexCoord=0;

    var oldPosXIn=0,oldPosYIn=0;
    var oldPosXTexCoordIn=0,oldPosYTexCoordIn=0;

    var posxTexCoordIn=0,posyTexCoordIn=0;
    var posxTexCoord=0,posyTexCoord=0;
    var posx=0,posy=0;

    var verts=[];

    if(drawSpline.get())
    {
        var lastX=0;
        var lastY=0;
        var tc=[];
        for (i=0; i <= segs*percent.get(); i++)
        {
            degInRad = (360/segs)*i*CGL.DEG2RAD;
            posx=Math.cos(degInRad)*radius.get();
            posy=Math.sin(degInRad)*radius.get();
            
            posyTexCoord=0.5;

            if(i>0)
            {
                verts.push(lastX);
                verts.push(lastY);
                verts.push(0);
                posxTexCoord=1.0-(i-1)/segs;
                
                tc.push(posxTexCoord,posyTexCoord);
            }
            verts.push(posx);
            verts.push(posy);
            verts.push(0);
            
            // posxTexCoord=1.0-i/segs;
            // tc.push(posxTexCoord,posyTexCoord);

            lastX=posx;
            lastY=posy;
        }
        geom.setPointVertices(verts);
        // geom.texCoords=tc;
    }
    else
    if(innerRadius.get()<=0)
    {

        for (i=0; i <= segs*percent.get(); i++)
        {
            degInRad = (360/segs)*i*CGL.DEG2RAD;
            posx=Math.cos(degInRad)*radius.get();
            posy=Math.sin(degInRad)*radius.get();

            if(mapping.get()=='flat')
            {
                posxTexCoord=(Math.cos(degInRad)+1.0)/2;
                posyTexCoord=1.0-(Math.sin(degInRad)+1.0)/2;
                posxTexCoordIn=0.5;
                posyTexCoordIn=0.5;
            }
            else if(mapping.get()=='round')
            {
                posxTexCoord=1.0-i/segs;
                posyTexCoord=0;
                posxTexCoordIn=posxTexCoord;
                posyTexCoordIn=1;
            }

            faces.push(
                      [posx,posy,0],
                      [oldPosX,oldPosY,0],
                      [0,0,0]
                      );

            texCoords.push(posxTexCoord,posyTexCoord,oldPosXTexCoord,oldPosYTexCoord,posxTexCoordIn,posyTexCoordIn);
            vertexNormals.push(0,0,1,0,0,1,0,0,1);
            
            oldPosXTexCoord=posxTexCoord;
            oldPosYTexCoord=posyTexCoord;
            
            oldPosX=posx;
            oldPosY=posy;
        }
      
        geom=CGL.Geometry.buildFromFaces(faces);
        geom.vertexNormals=vertexNormals;
        geom.texCoords=texCoords;

    }
    else
    {
        var count=0;
        var numSteps=segs*percent.get();
        var pos=0;

        for (i=0; i <= numSteps; i++)
        {
            count++;
            
            degInRad = (360/segs)*i*CGL.DEG2RAD;
            posx=Math.cos(degInRad)*radius.get();
            posy=Math.sin(degInRad)*radius.get();
            
            var posxIn=Math.cos(degInRad)*innerRadius.get()*radius.get();
            var posyIn=Math.sin(degInRad)*innerRadius.get()*radius.get();


            if(mapping.get()=='flat')
            {
                posxTexCoord=(Math.cos(degInRad)+1.0)/2;
                posyTexCoord=1.0-(Math.sin(degInRad)+1.0)/2;
                posxTexCoordIn=((posxTexCoord-0.5)*innerRadius.get())+0.5;
                posyTexCoordIn=((posyTexCoord-0.5)*innerRadius.get())+0.5;
            }
            else if(mapping.get()=='round')
            {
                posxTexCoord=1.0-i/segs;
                posyTexCoord=0;
                posxTexCoordIn=posxTexCoord;
                posyTexCoordIn=1;
            }
            

            if(steps.get()===0.0 ||
                (count%parseInt(steps.get(),10)===0 && !invertSteps.get()) ||
                (count%parseInt(steps.get(),10)!==0 && invertSteps.get()) )
            {
                faces.push(
                          [posx,posy,0],
                          [oldPosX,oldPosY,0],
                          [posxIn,posyIn,0]
                          );

                faces.push(
                          [posxIn,posyIn,0],
                          [oldPosX,oldPosY,0],
                          [oldPosXIn,oldPosYIn,0]
                          );

                // texCoords.push(
                //     posxTexCoord,0,
                //     oldPosXTexCoord,0,
                //     posxTexCoordIn,1);

                // texCoords.push(
                //     posxTexCoord,1,
                //     oldPosXTexCoord,0,
                //     oldPosXTexCoordIn,1);

                vertexNormals.push(0,0,1,0,0,1,0,0,1);
                vertexNormals.push(0,0,1,0,0,1,0,0,1);
            }

            oldPosXTexCoordIn=posxTexCoordIn;
            oldPosYTexCoordIn=posyTexCoordIn;
            
            oldPosXTexCoord=posxTexCoord;
            oldPosYTexCoord=posyTexCoord;
            
            oldPosX=posx;
            oldPosY=posy;
            
            oldPosXIn=posxIn;
            oldPosYIn=posyIn;
        }
        geom=CGL.Geometry.buildFromFaces(faces);
        geom.vertexNormals=vertexNormals;
        // geom.texCoords=texCoords;
        geom.mapTexCoords2d();
    }

    geomOut.set(null);
    geomOut.set(geom);
    
    if(geom.vertices.length==0)return;
    if(!mesh)mesh=new CGL.Mesh(cgl,geom);
    mesh.setGeom(geom);
}

var mapping=op.addInPort(new Port(op,"mapping",OP_PORT_TYPE_VALUE,{display:'dropdown',values:['flat','round']}));
mapping.set('flat');
mapping.onValueChange(calc);

segments.onChange=calc;
radius.onChange=calc;
innerRadius.onChange=calc;
percent.onChange=calc;
steps.onChange=calc;
invertSteps.onChange=calc;
drawSpline.onChange=calc;
calc();



};

Ops.Gl.Meshes.Circle.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.String.Concat
// 
// **************************************************************

Ops.String.Concat = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var string1=op.inValueString("string1","ABC");
var string2=op.inValueString("string2","XYZ");
var newLine=op.inValueBool("New Line",false);

var result=op.outValueString("result");

function exec()
{
    var nl='';
    if(newLine.get())nl='\n';
    result.set( String(string1.get())+nl+String(string2.get()));
}

newLine.onChange=string2.onChange=string1.onChange=exec;



};

Ops.String.Concat.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.Sequence
// 
// **************************************************************

Ops.Trigger.Sequence = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));

var exes=[];
var triggers=[];

var triggerAll=function()
{
    for(var i=0;i<triggers.length;i++) triggers[i].trigger();
};

exe.onTriggered=triggerAll;

var num=16;

for(var i=0;i<num;i++)
{
    triggers.push( op.addOutPort(new Port(op,"trigger "+i,OP_PORT_TYPE_FUNCTION)) );
    
    if(i<num-1)
    {
        var newExe=op.addInPort(new Port(op,"exe "+i,OP_PORT_TYPE_FUNCTION));
        newExe.onTriggered=triggerAll;
        exes.push( newExe );
    }
}


};

Ops.Trigger.Sequence.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.ArrayGetValue
// 
// **************************************************************

Ops.Array.ArrayGetValue = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var array=op.addInPort(new Port(op, "array",OP_PORT_TYPE_ARRAY));
var index=op.inValueInt("index");

var value=op.addOutPort(new Port(op, "value",OP_PORT_TYPE_VALUE));
array.ignoreValueSerialize=true;

function update()
{
    if(array.get()) value.set( array.get()[index.get()]);
}

index.onChange=update;
array.onChange=update;


};

Ops.Array.ArrayGetValue.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Shader.BasicMaterial
// 
// **************************************************************

Ops.Gl.Shader.BasicMaterial = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["shader_frag"]="{{MODULES_HEAD}}\n\nIN vec2 texCoord;\n#ifdef HAS_TEXTURES\n    IN vec2 texCoordOrig;\n    #ifdef HAS_TEXTURE_DIFFUSE\n        uniform sampler2D tex;\n    #endif\n    #ifdef HAS_TEXTURE_OPACITY\n        uniform sampler2D texOpacity;\n   #endif\n#endif\nuniform float r;\nuniform float g;\nuniform float b;\nuniform float a;\n\nvoid main()\n{\n    {{MODULE_BEGIN_FRAG}}\n    vec4 col=vec4(r,g,b,a);\n    \n    #ifdef HAS_TEXTURES\n        #ifdef HAS_TEXTURE_DIFFUSE\n\n           col=texture2D(tex,vec2(texCoord.x,(1.0-texCoord.y)));\n\n//         col=texture2D(tex,vec2(texCoords.x*1.0,(1.0-texCoords.y)*1.0));\n           #ifdef COLORIZE_TEXTURE\n               col.r*=r;\n               col.g*=g;\n               col.b*=b;\n           #endif\n    #endif\n    col.a*=a;\n    #ifdef HAS_TEXTURE_OPACITY\n      \n            #ifdef TRANSFORMALPHATEXCOORDS\n                col.a*=texture2D(texOpacity,vec2(texCoordOrig.s,1.0-texCoordOrig.t)).g;\n            #endif\n            #ifndef TRANSFORMALPHATEXCOORDS\n                col.a*=texture2D(texOpacity,vec2(texCoord.s,1.0-texCoord.t)).g;\n            #endif\n       #endif\n       \n    #endif\n\n    {{MODULE_COLOR}}\n\n    outColor = col;\n\n    \n}\n";
attachments["shader_vert"]="{{MODULES_HEAD}}\n\nIN vec3 vPosition;\nIN vec3 attrVertNormal;\nIN vec2 attrTexCoord;\n\nOUT vec3 norm;\nOUT vec2 texCoord;\nOUT vec2 texCoordOrig;\n\nUNI mat4 projMatrix;\nUNI mat4 modelMatrix;\nUNI mat4 viewMatrix;\n\n#ifdef HAS_TEXTURES\n    #ifdef TEXTURE_REPEAT\n        UNI float diffuseRepeatX;\n        UNI float diffuseRepeatY;\n        UNI float texOffsetX;\n        UNI float texOffsetY;\n    #endif\n#endif\n\n\nvoid main()\n{\n    mat4 mMatrix=modelMatrix;\n    mat4 mvMatrix;\n    \n    texCoordOrig=attrTexCoord;\n    texCoord=attrTexCoord;\n    #ifdef HAS_TEXTURES\n        #ifdef TEXTURE_REPEAT\n            texCoord.x=texCoord.x*diffuseRepeatX+texOffsetX;\n            texCoord.y=texCoord.y*diffuseRepeatY+texOffsetY;\n        #endif\n    #endif\n\n    vec4 pos = vec4( vPosition, 1. );\n\n\n    #ifdef BILLBOARD\n       vec3 position=vPosition;\n       mvMatrix=viewMatrix*modelMatrix;\n\n       gl_Position = projMatrix * mvMatrix * vec4((\n           position.x * vec3(\n               mvMatrix[0][0],\n               mvMatrix[1][0],\n               mvMatrix[2][0] ) +\n           position.y * vec3(\n               mvMatrix[0][1],\n               mvMatrix[1][1],\n               mvMatrix[2][1]) ), 1.0);\n    #endif\n\n    {{MODULE_VERTEX_POSITION}}\n\n    #ifndef BILLBOARD\n        mvMatrix=viewMatrix * mMatrix;\n    #endif\n\n\n    #ifndef BILLBOARD\n        // gl_Position = projMatrix * viewMatrix * modelMatrix * pos;\n        gl_Position = projMatrix * mvMatrix * pos;\n    #endif\n}\n";
const render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION) );
const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
const shaderOut=op.addOutPort(new Port(op,"shader",OP_PORT_TYPE_OBJECT));
shaderOut.ignoreValueSerialize=true;

const cgl=op.patch.cgl;


var shader=new CGL.Shader(cgl,'BasicMaterial');
shader.setModules(['MODULE_VERTEX_POSITION','MODULE_COLOR','MODULE_BEGIN_FRAG']);
shader.bindTextures=bindTextures;
shader.setSource(attachments.shader_vert,attachments.shader_frag);
shaderOut.set(shader);

render.onTriggered=doRender;


function bindTextures()
{
    if(diffuseTexture.get()) cgl.setTexture(0, diffuseTexture.get().tex);
    if(op.textureOpacity.get()) cgl.setTexture(1, op.textureOpacity.get().tex);
}

op.preRender=function()
{
    shader.bind();
    doRender();
};

function doRender()
{
    if(!shader)return;

    cgl.setShader(shader);
    shader.bindTextures();

    trigger.trigger();

    cgl.setPreviousShader();
}


{
    // rgba colors
    
    var r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range',colorPick:'true' }));
    r.set(Math.random());
    r.uniform=new CGL.Uniform(shader,'f','r',r);
    
    var g=op.addInPort(new Port(op,"g",OP_PORT_TYPE_VALUE,{ display:'range'}));
    g.set(Math.random());
    g.uniform=new CGL.Uniform(shader,'f','g',g);
    
    var b=op.addInPort(new Port(op,"b",OP_PORT_TYPE_VALUE,{ display:'range' }));
    b.set(Math.random());
    b.uniform=new CGL.Uniform(shader,'f','b',b);
    
    var a=op.addInPort(new Port(op,"a",OP_PORT_TYPE_VALUE,{ display:'range'}));
    a.uniform=new CGL.Uniform(shader,'f','a',a);
    a.set(1.0);
    
}

{
    // diffuse outTexture
    
    var diffuseTexture=this.addInPort(new Port(this,"texture",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
    var diffuseTextureUniform=null;
    shader.bindTextures=bindTextures;
    
    diffuseTexture.onChange=function()
    {
        if(diffuseTexture.get())
        {
            // if(diffuseTextureUniform!==null)return;
            // shader.addveUniform('texDiffuse');
            if(!shader.hasDefine('HAS_TEXTURE_DIFFUSE'))shader.define('HAS_TEXTURE_DIFFUSE');
            if(!diffuseTextureUniform)diffuseTextureUniform=new CGL.Uniform(shader,'t','texDiffuse',0);
            updateTexRepeat();
        }
        else
        {
            shader.removeUniform('texDiffuse');
            shader.removeDefine('HAS_TEXTURE_DIFFUSE');
            diffuseTextureUniform=null;
        }
    };


    
}

{
    // opacity texture 
    op.textureOpacity=op.addInPort(new Port(op,"textureOpacity",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
    op.textureOpacityUniform=null;
    
    op.textureOpacity.onChange=function()
    {
        if(op.textureOpacity.get())
        {
            if(op.textureOpacityUniform!==null)return;
            shader.removeUniform('texOpacity');
            shader.define('HAS_TEXTURE_OPACITY');
            if(!op.textureOpacityUniform)op.textureOpacityUniform=new CGL.Uniform(shader,'t','texOpacity',1);
        }
        else
        {
            shader.removeUniform('texOpacity');
            shader.removeDefine('HAS_TEXTURE_OPACITY');
            op.textureOpacityUniform=null;
        }
    };
    
}

op.colorizeTexture=op.addInPort(new Port(op,"colorizeTexture",OP_PORT_TYPE_VALUE,{ display:'bool' }));
op.colorizeTexture.set(false);
op.colorizeTexture.onChange=function()
{
    if(op.colorizeTexture.get()) shader.define('COLORIZE_TEXTURE');
        else shader.removeDefine('COLORIZE_TEXTURE');
};


op.doBillboard=op.addInPort(new Port(op,"billboard",OP_PORT_TYPE_VALUE,{ display:'bool' }));
op.doBillboard.set(false);
op.doBillboard.onChange=function()
{
    if(op.doBillboard.get()) shader.define('BILLBOARD');
        else shader.removeDefine('BILLBOARD');
};

var texCoordAlpha=op.inValueBool("Opacity TexCoords Transform",false);

texCoordAlpha.onChange=function()
{
    if(texCoordAlpha.get()) shader.define('TRANSFORMALPHATEXCOORDS');
        else shader.removeDefine('TRANSFORMALPHATEXCOORDS');
    
};

var preMultipliedAlpha=op.addInPort(new Port(op,"preMultiplied alpha",OP_PORT_TYPE_VALUE,{ display:'bool' }));

function updateTexRepeat()
{
    if(!diffuseRepeatXUniform)
    {
        diffuseRepeatXUniform=new CGL.Uniform(shader,'f','diffuseRepeatX',diffuseRepeatX);
        diffuseRepeatYUniform=new CGL.Uniform(shader,'f','diffuseRepeatY',diffuseRepeatY);
        diffuseOffsetXUniform=new CGL.Uniform(shader,'f','texOffsetX',diffuseOffsetX);
        diffuseOffsetYUniform=new CGL.Uniform(shader,'f','texOffsetY',diffuseOffsetY);
    }

    diffuseRepeatXUniform.setValue(diffuseRepeatX.get());
    diffuseRepeatYUniform.setValue(diffuseRepeatY.get());
    diffuseOffsetXUniform.setValue(diffuseOffsetX.get());
    diffuseOffsetYUniform.setValue(diffuseOffsetY.get());
}


{
    // texture coords
    
    var diffuseRepeatX=op.addInPort(new Port(op,"diffuseRepeatX",OP_PORT_TYPE_VALUE));
    var diffuseRepeatY=op.addInPort(new Port(op,"diffuseRepeatY",OP_PORT_TYPE_VALUE));
    var diffuseOffsetX=op.addInPort(new Port(op,"Tex Offset X",OP_PORT_TYPE_VALUE));
    var diffuseOffsetY=op.addInPort(new Port(op,"Tex Offset Y",OP_PORT_TYPE_VALUE));
    
    diffuseRepeatX.onChange=updateTexRepeat;
    diffuseRepeatY.onChange=updateTexRepeat;
    diffuseOffsetY.onChange=updateTexRepeat;
    diffuseOffsetX.onChange=updateTexRepeat;
    
    var diffuseRepeatXUniform=null;
    var diffuseRepeatYUniform=null;
    var diffuseOffsetXUniform=null;
    var diffuseOffsetYUniform=null;
    
    shader.define('TEXTURE_REPEAT');
    

    diffuseOffsetX.set(0);
    diffuseOffsetY.set(0);
    diffuseRepeatX.set(1);
    diffuseRepeatY.set(1);
}


};

Ops.Gl.Shader.BasicMaterial.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.Repeat
// 
// **************************************************************

Ops.Trigger.Repeat = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));
const num=op.inValueInt("num",5);

const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
const idx=op.addOutPort(new Port(op,"index"));

exe.onTriggered=function()
{
    for(var i=Math.round(num.get())-1;i>-1;i--)
    {
        idx.set(i);
        trigger.trigger();
    }
};



};

Ops.Trigger.Repeat.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.WebAudio.Output
// 
// **************************************************************

Ops.WebAudio.Output = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='audioOutput';
op.requirements=[CABLES.Requirements.WEBAUDIO];

var audioCtx = CABLES.WEBAUDIO.createAudioContext(op);

// constants
var VOLUME_DEFAULT = 1;
var VOLUME_MIN = 0;
var VOLUME_MAX = 1;

// vars
var gainNode = audioCtx.createGain();
var destinationNode = audioCtx.destination;
gainNode.connect(destinationNode);
var masterVolume = 1;

// inputs
var audioInPort = CABLES.WEBAUDIO.createAudioInPort(op, "Audio In", gainNode);
var volumePort = op.inValueSlider("Volume", VOLUME_DEFAULT);
var mutePort = op.inValueBool("Mute", false);

// functions
// sets the volume, multiplied by master volume
function setVolume() {
    var volume = volumePort.get() * masterVolume;
    if(volume >= VOLUME_MIN && volume <= VOLUME_MAX) {
        // gainNode.gain.value = volume;
        gainNode.gain.setValueAtTime(volume, audioCtx.currentTime);
    } else {
        // gainNode.gain.value = VOLUME_DEFAULT * masterVolume;
        gainNode.gain.setValueAtTime(VOLUME_DEFAULT * masterVolume, audioCtx.currentTime);
    }
}

function mute(b) {
    if(b) {
        // gainNode.gain.value = 0;
        gainNode.gain.setValueAtTime(0, audioCtx.currentTime);
    } else {
        setVolume();
    }
}

// change listeners
mutePort.onChange = function() {
    mute(mutePort.get());
};

volumePort.onChange = function() {
    if(mutePort.get()) {
        return;
    }
    setVolume();
};

op.onMasterVolumeChanged = function(v) {
    masterVolume = v;
    setVolume();
};




};

Ops.WebAudio.Output.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.WebAudio.AudioBufferPlayer
// 
// **************************************************************

Ops.WebAudio.AudioBufferPlayer = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="AudioBufferPlayer";

var audioCtx = CABLES.WEBAUDIO.createAudioContext(op);

// input ports
var audioBufferPort = op.inObject("Audio Buffer");
var playPort = op.inValueBool("Start / Stop", false);
var startTimePort = op.inValue("Start Time", 0);
var stopTimePort = op.inValue("Stop Time", 0);
var offsetPort = op.inValue("Offset", 0);
var autoPlayPort = op.inValueBool("Autoplay", false);
var loopPort = op.inValueBool("Loop", false);
var detunePort = op.inValue("Detune", 0);
var playbackRatePort = op.inValue("Playback Rate", 1);

// output ports
var audioOutPort = op.outObject("Audio Out");

// vars
var source = null;

// change listeners
audioBufferPort.onChange = function() {
    createAudioBufferSource();
    if(
        (autoPlayPort.get() && audioBufferPort.get()) ||
    (playPort.get() && audioBufferPort.get())
    ) {
        start(startTimePort.get());
    }
};
playPort.onChange = function() {
    if(source) {
        if(playPort.get()) {
            var startTime = startTimePort.get() || 0;
            start(startTime);    
        } else {
            var stopTime = stopTimePort.get() || 0;
            stop(stopTime);    
        } 
    }
};
loopPort.onChange = function() {
    if(source) {
        source.loop = loopPort.get() ? true : false;
    }
};

detunePort.onChange = setDetune;

function setDetune() {
    if(source) {
        var detune = detunePort.get() || 0;
        if(source.detune) {
            source.detune.setValueAtTime(
                detune,
                audioCtx.currentTime    
            );
        }
    }
}

playbackRatePort.onChange = setPlaybackRate;

function setPlaybackRate() {
    if(source) {
        var playbackRate = playbackRatePort.get() || 0;
        if(playbackRate >= source.playbackRate.minValue && playbackRate <= source.playbackRate.maxValue) {
            source.playbackRate.setValueAtTime(
                playbackRate,
                audioCtx.currentTime    
            );    
        }
    }
}

// functions
function createAudioBufferSource() {
    if(source)stop(0);
    source = audioCtx.createBufferSource();
    var buffer = audioBufferPort.get();
    if(buffer) {
        source.buffer = buffer;
    }
    source.onended = onPlaybackEnded;
    source.loop = loopPort.get();
    setPlaybackRate();
    setDetune();
    audioOutPort.set(source);
}

function start(time) {
    try {
        source.start(time,offsetPort.get()); // 0 = now
    } catch(e){
        // console.log(e);
    } // already playing!?
}

function stop(time) {
    try {
        source.stop(time); // 0 = now
    } catch(e) 
    {
        // console.log(e);
    } // not playing!?
}

function onPlaybackEnded() {
    createAudioBufferSource(); // we can only play back once, so we need to create a new one
}

};

Ops.WebAudio.AudioBufferPlayer.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.TimeLine.TimeLineControls
// 
// **************************************************************

Ops.TimeLine.TimeLineControls = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var plauPause=op.outValue("Play/Stop");
var time=op.outValue("time");

op.patch.timer.onPlayPause(seek);
op.patch.timer.onTimeChange(seek);

function seek()
{
    plauPause.set(false);

    setTimeout(function()
    {
        time.set(op.patch.timer.getTime());
        plauPause.set(op.patch.timer.isPlaying());
    },10);
}

};

Ops.TimeLine.TimeLineControls.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Modulo
// 
// **************************************************************

Ops.Math.Modulo = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var result=op.addOutPort(new Port(op,"result"));
var number1=op.addInPort(new Port(op,"number1"));
var number2=op.addInPort(new Port(op,"number2"));
var pingpong=op.addInPort(new Port(op,"pingpong",OP_PORT_TYPE_VALUE,{display:'bool'}));


var doPingPong=false;

function exec()
{
    var n2=number2.get();
    var n1=number1.get();

    if(doPingPong)
    {
        var r=n1 % n2*2;
        if(r>n2) result.set( n2 * 2.0-r );
            else result.set(r);
        return;
    }
    else
    {
        var re=n1 % n2;
        if(re!=re) re=0;
        result.set(re);
    }
}

number1.onChange=exec;
number2.onChange=exec;

number1.set(1);
number2.set(2);

pingpong.onValueChange(
    function()
    {
        doPingPong=pingpong.get();
    });


};

Ops.Math.Modulo.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Ui.Comment
// 
// **************************************************************

Ops.Ui.Comment = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name=" ";
op.inTitle=op.inValueString("title",' ');
op.text=op.inValueText("text");

// op.inTitle.set('.');
op.text.set('...');

function update()
{
    if(CABLES.UI)
    {
        var uiOp=gui.patch().getUiOp(op);
        // console.log(uiOp);
        op.name=op.inTitle.get();
        op.uiAttr('title',op.inTitle.get());
        uiOp.oprect.updateComment();
    }
}

op.inTitle.onChange=update;
op.text.onChange=update;
op.onLoaded=update;


};

Ops.Ui.Comment.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Round
// 
// **************************************************************

Ops.Math.Round = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var result=op.addOutPort(new Port(op,"result"));
var number1=op.addInPort(new Port(op,"number"));
var decPlaces=op.addInPort(new Port(op,"Decimal Places"));

decPlaces.set(0);

function exec()
{
    var decm=Math.pow(10,decPlaces.get());
    result.set(Math.round(number1.get()*decm)/decm);
}

number1.onValueChanged=exec;
decPlaces.onValueChanged=exec;


};

Ops.Math.Round.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.TimeLine.TimeLineTime
// 
// **************************************************************

Ops.TimeLine.TimeLineTime = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

op.name='TimeLineTime';
var theTime=op.addOutPort(new Port(this,"time"));

theTime.set(0);

op.onAnimFrame=function(time)
{
    theTime.set(time);
};

};

Ops.TimeLine.TimeLineTime.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.WebAudio.AudioBuffer
// 
// **************************************************************

Ops.WebAudio.AudioBuffer = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="AudioBuffer";

var audioCtx = CABLES.WEBAUDIO.createAudioContext(op);

// input ports
var inUrlPort = op.addInPort( new Port( op, "URL", OP_PORT_TYPE_VALUE, { display: 'file', type: 'string', filter: 'audio'  } ));


// output ports
var audioBufferPort = op.outObject("Audio Buffer");
var finishedLoadingPort = op.outValue("Finished Loading", false);
var sampleRatePort = op.outValue("Sample Rate", 0);
var lengthPort = op.outValue("Length", 0);
var durationPort = op.outValue("Duration", 0);
var numberOfChannelsPort = op.outValue("Number of Channels", 0);

// change listeners
inUrlPort.onChange = function() {
    var url=op.patch.getFilePath(inUrlPort.get());
    if(typeof url === 'string' && url.length > 1) {
        CABLES.WEBAUDIO.loadAudioFile(op.patch, url, onLoadFinished, onLoadFailed);
    }
};

function onLoadFinished(buffer) {
    lengthPort.set(buffer.length);
    durationPort.set(buffer.duration);
    numberOfChannelsPort.set(buffer.numberOfChannels);
    sampleRatePort.set(buffer.sampleRate);
    audioBufferPort.set(buffer);
    finishedLoadingPort.set(true);
    op.log("AudioBuffer loaded: ", buffer);
}

function onLoadFailed(e) {
    op.log("Error: Loading audio file failed: ", e);
    invalidateOutPorts();
}

function invalidateOutPorts() {
    lengthPort.set(0);
    durationPort.set(0);
    numberOfChannelsPort.set(0);
    sampleRatePort.set(0);
    audioBufferPort.set(0);
    finishedLoadingPort.set(false);
}

};

Ops.WebAudio.AudioBuffer.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Compare.Equals
// 
// **************************************************************

Ops.Math.Compare.Equals = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='Equals';

var result = op.addOutPort(new Port(op,"result"));
var number1 = op.addInPort(new Port(op,"number1"));
var number2 = op.addInPort(new Port(op,"number2"));

number1.set(1);
number2.set(1);

function exec()
{
    result.set( number1.get() == number2.get() );
}

number1.onValueChanged = exec;
number2.onValueChanged = exec;
exec();


};

Ops.Math.Compare.Equals.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Value.ValueTrigger
// 
// **************************************************************

Ops.Value.ValueTrigger = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.inFunctionButton("exe");
var v=op.addInPort(new Port(op,"value",OP_PORT_TYPE_VALUE));

var next=op.outFunction("Next");
var result=op.addOutPort(new Port(op,"result"));

exe.onTriggered=exec;

result.changeAlways=true;

function exec()
{
    result.set(v.get());
    next.trigger();
}



};

Ops.Value.ValueTrigger.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Boolean.IfTrueThen
// 
// **************************************************************

Ops.Boolean.IfTrueThen = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));

var boolean=op.addInPort(new Port(op,"boolean",OP_PORT_TYPE_VALUE,{display:'bool'}));

var triggerThen=op.addOutPort(new Port(op,"then",OP_PORT_TYPE_FUNCTION));
var triggerElse=op.addOutPort(new Port(op,"else",OP_PORT_TYPE_FUNCTION));

boolean.set(false);

function execBool()
{
    if(exe.isLinked())return;
    exec();
}

function exec()
{
    if(boolean.get() || boolean.get()>=1 )
    {
        triggerThen.trigger();
    }
    else
    {
        triggerElse.trigger();
    }
}

boolean.onValueChanged=execBool;
exe.onTriggered=exec;


};

Ops.Boolean.IfTrueThen.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.DrawImageNew
// 
// **************************************************************

Ops.Gl.TextureEffects.DrawImageNew = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["drawimage_frag"]="#ifdef HAS_TEXTURES\n    IN vec2 texCoord;\n    UNI sampler2D tex;\n    UNI sampler2D image;\n#endif\n\nIN mat3 transform;\nUNI float rotate;\n{{BLENDCODE}}\n\n#ifdef HAS_TEXTUREALPHA\n   UNI sampler2D imageAlpha;\n#endif\n\nUNI float amount;\n\nvoid main()\n{\n    vec4 blendRGBA=vec4(0.0,0.0,0.0,1.0);\n    #ifdef HAS_TEXTURES\n        vec2 tc=texCoord;\n\n        #ifdef TEX_FLIP_X\n            tc.x=1.0-tc.x;\n        #endif\n        #ifdef TEX_FLIP_Y\n            tc.y=1.0-tc.y;\n        #endif\n\n        #ifdef TEX_TRANSFORM\n            vec3 coordinates=vec3(tc.x, tc.y,1.0);\n            tc=(transform * coordinates ).xy;\n        #endif\n    \n        blendRGBA=texture2D(image,tc);\n        \n        vec3 blend=blendRGBA.rgb;\n        vec4 baseRGBA=texture2D(tex,texCoord);\n        vec3 base=baseRGBA.rgb;\n        \n        vec3 colNew=_blend(base,blend);\n\n        #ifdef REMOVE_ALPHA_SRC\n            blendRGBA.a=1.0;\n        #endif\n\n        #ifdef HAS_TEXTUREALPHA\n            vec4 colImgAlpha=texture2D(imageAlpha,texCoord);\n            float colImgAlphaAlpha=colImgAlpha.a;\n\n            #ifdef ALPHA_FROM_LUMINANCE\n                vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), colImgAlpha.rgb ));\n                colImgAlphaAlpha=(gray.r+gray.g+gray.b)/3.0;\n            #endif\n\n            blendRGBA.a=colImgAlphaAlpha*blendRGBA.a;\n        #endif\n\n\n    #endif\n    \n    blendRGBA.rgb=mix( colNew, base ,1.0-blendRGBA.a*amount);\n    \n    blendRGBA.a=1.0;\n    \n    \n    gl_FragColor = blendRGBA;\n\n}";
attachments["drawimage_vert"]="IN vec3 vPosition;\nIN vec2 attrTexCoord;\nIN vec3 attrVertNormal;\n\nUNI mat4 projMatrix;\nUNI mat4 mvMatrix;\n\nUNI float posX;\nUNI float posY;\nUNI float scaleX;\nUNI float scaleY;\nUNI float rotate;\n\nOUT vec2 texCoord;\nOUT vec3 norm;\nOUT mat3 transform;\n\nvoid main()\n{\n   texCoord=attrTexCoord;\n   norm=attrVertNormal;\n\n   #ifdef TEX_TRANSFORM\n        vec3 coordinates=vec3(attrTexCoord.x, attrTexCoord.y,1.0);\n        float angle = radians( rotate );\n        vec2 scale= vec2(scaleX,scaleY);\n        vec2 translate= vec2(posX,posY);\n\n        transform = mat3(   scale.x * cos( angle ), scale.x * sin( angle ), 0.0,\n            - scale.y * sin( angle ), scale.y * cos( angle ), 0.0,\n            - 0.5 * scale.x * cos( angle ) + 0.5 * scale.y * sin( angle ) - 0.5 * translate.x*2.0 + 0.5,  - 0.5 * scale.x * sin( angle ) - 0.5 * scale.y * cos( angle ) - 0.5 * translate.y*2.0 + 0.5, 1.0);\n   #endif\n\n   gl_Position = projMatrix * mvMatrix * vec4(vPosition,  1.0);\n}\n";
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));

var blendMode=CGL.TextureEffect.AddBlendSelect(op,"blendMode");
var amount=op.inValueSlider("amount",1);

var image=op.addInPort(new Port(op,"image",OP_PORT_TYPE_TEXTURE,{preview:true }));
var imageAlpha=op.addInPort(new Port(op,"imageAlpha",OP_PORT_TYPE_TEXTURE,{preview:true }));

var alphaSrc=op.inValueSelect("alphaSrc",['alpha channel','luminance']);
var removeAlphaSrc=op.addInPort(new Port(op,"removeAlphaSrc",OP_PORT_TYPE_VALUE,{ display:'bool' }));
var invAlphaChannel=op.addInPort(new Port(op,"invert alpha channel",OP_PORT_TYPE_VALUE,{ display:'bool' }));

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

blendMode.set('normal');
var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl,'drawimage');


var srcFrag=attachments.drawimage_frag.replace('{{BLENDCODE}}',CGL.TextureEffect.getBlendCode());


    

shader.setSource(attachments.drawimage_vert,srcFrag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var textureImaghe=new CGL.Uniform(shader,'t','image',1);
var textureAlpha=new CGL.Uniform(shader,'t','imageAlpha',2);

invAlphaChannel.onValueChanged=function()
{
    if(invAlphaChannel.get()) shader.define('INVERT_ALPHA');
        else shader.removeDefine('INVERT_ALPHA');
};

removeAlphaSrc.onValueChanged=function()
{
    if(removeAlphaSrc.get()) shader.define('REMOVE_ALPHA_SRC');
        else shader.removeDefine('REMOVE_ALPHA_SRC');
};
removeAlphaSrc.set(true);

alphaSrc.onValueChanged=function()
{
    if(alphaSrc.get()=='luminance') shader.define('ALPHA_FROM_LUMINANCE');
        else shader.removeDefine('ALPHA_FROM_LUMINANCE');
};

alphaSrc.set("alpha channel");


{
    //
    // texture flip
    //
    var flipX=op.addInPort(new Port(op,"flip x",OP_PORT_TYPE_VALUE,{ display:'bool' }));
    var flipY=op.addInPort(new Port(op,"flip y",OP_PORT_TYPE_VALUE,{ display:'bool' }));

    flipX.onValueChanged=function()
    {
        if(flipX.get()) shader.define('TEX_FLIP_X');
            else shader.removeDefine('TEX_FLIP_X');
    };

    flipY.onValueChanged=function()
    {
        if(flipY.get()) shader.define('TEX_FLIP_Y');
            else shader.removeDefine('TEX_FLIP_Y');
    };
}

{
    //
    // texture transform
    //
    
    var doTransform=op.inValueBool("Transform");
    
    var scaleX=op.inValueSlider("Scale X",1);
    var scaleY=op.inValueSlider("Scale Y",1);

    var posX=op.inValue("Position X",0);
    var posY=op.inValue("Position Y",0);
    
    var rotate=op.inValue("Rotation",0);

    var uniScaleX=new CGL.Uniform(shader,'f','scaleX',scaleX);
    var uniScaleY=new CGL.Uniform(shader,'f','scaleY',scaleY);
    
    var uniPosX=new CGL.Uniform(shader,'f','posX',posX);
    var uniPosY=new CGL.Uniform(shader,'f','posY',posY);
    var uniRotate=new CGL.Uniform(shader,'f','rotate',rotate);

    doTransform.onChange=updateTransformPorts;
}

function updateTransformPorts()
{
    if(doTransform.get())
    {
        shader.define('TEX_TRANSFORM');
        scaleX.setUiAttribs({hidePort:false,greyout:false});
        scaleY.setUiAttribs({hidePort:false,greyout:false});
        posX.setUiAttribs({hidePort:false,greyout:false});
        posY.setUiAttribs({hidePort:false,greyout:false});
        rotate.setUiAttribs({hidePort:false,greyout:false});
    }
    else
    {
        shader.removeDefine('TEX_TRANSFORM');
        scaleX.setUiAttribs({hidePort:true,greyout:true});
        scaleY.setUiAttribs({hidePort:true,greyout:true});
        posX.setUiAttribs({hidePort:true,greyout:true});
        posY.setUiAttribs({hidePort:true,greyout:true});
        rotate.setUiAttribs({hidePort:true,greyout:true});
    }
}



blendMode.onValueChanged=function()
{
    CGL.TextureEffect.onChangeBlendSelect(shader,blendMode.get());
};


var amountUniform=new CGL.Uniform(shader,'f','amount',amount);



imageAlpha.onValueChanged=function()
{
    if(imageAlpha.get() && imageAlpha.get().tex)
    {
        shader.define('HAS_TEXTUREALPHA');
    }
    else
    {
        shader.removeDefine('HAS_TEXTUREALPHA');
    }
};



function doRender()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    if(image.get() && image.get().tex && amount.get()>0.0)
    {
        cgl.setShader(shader);
        cgl.currentTextureEffect.bind();

        /* --- */cgl.setTexture(0,cgl.currentTextureEffect.getCurrentSourceTexture().tex );
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

        /* --- */cgl.setTexture(1, image.get().tex );
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, image.get().tex );

        if(imageAlpha.get() && imageAlpha.get().tex)
        {
            /* --- */cgl.setTexture(2, imageAlpha.get().tex );
            // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, imageAlpha.get().tex );
        }

        cgl.currentTextureEffect.finish();
        cgl.setPreviousShader();
    }

    trigger.trigger();
}

render.onTriggered=doRender;
updateTransformPorts();

};

Ops.Gl.TextureEffects.DrawImageNew.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.LineFont
// 
// **************************************************************

Ops.Gl.LineFont = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var render=op.inFunction('render');
var string=op.inValueString('Text','cables');

var letterSpacing=op.inValue('Letter Spacing',1);
var lineWidth=op.inValue('Line Width',2);
var align=op.inValueSelect('align',['left','center','right']);

var stringWidth=0;
var meshes=[];
var vec=vec3.create();
var cgl=op.patch.cgl;
var characters=
    [
        {
            // a
            l:[
                [182.667,349.057,164.167,349.057],
                [160.333,360.557,171.333,326.89,175.333,326.89,186,360.557],
            ]    
        },
        {
            // b
            l:
            [
                [174.333,343.057,160.255,343.057],
                [160.333,326.89,175.5,326.89,178.333,330.724,178.333,340.724,174.333,343.057,180.5,346.807,180.5,357.474,176,360.557,160.167,360.557,160.333,326.89]
            ]
        },
        {
            // c
            l:
            [
                [180.583,331.307,175.917,326.807,166,326.807,160.083,332.557,160.083,354.557,165.833,360.474,175.917,360.474,180.5,355.807],
            ]
        },
        {
            // d
            l:
            [
                [160.083,327.057,160.083,360.557,175.417,360.557,180.708,355.265,180.708,332.974,175.104,327.057,160.083,327.057],
            ]
        },
        {
            // e
            l:
            [
                [175.167,343.932,160.436,343.932],
                [177.917,326.807,164.5,326.807,160.436,330.872,160.436,356.845,164.014,360.422,177.917,360.422]
            ]
        },
        {
            // f
            l:
            [
                [176.792,326.932,164.125,326.932,160.167,330.891,160.167,360.683],
                [173.458,345.599,160.167,345.599],
            ]
        },
        {
            // g
            l:
            [
                [180.455,332.395,175.391,328.33,166.194,328.33,160.167,334.357,160.167,355.933,165.792,360.557,176.038,360.557,181.62,354.976,181.62,344.811,173.122,344.811 	],
            ]
        },
        {
            //h
            l:
            [
                [160.167,326.89,160.167,360.557],
                [160.5,343.723,182.333,343.723],
                [182.333,326.89,182.333,360.974]
            ]
        },
        {
            // i
            l:
            [
                [160.167,326.807,160.167,360.641]
            ]
        },
        {
            // j
            l:
            [
                [159.833,326.89,166.833,326.89,166.833,362.057,163.962,364.928,159.833,364.928],
            ]
        },
        {
            // k
            l:
            [
                [160.167,326.807,160.167,360.974],
                [178.917,326.807,160.167,348.474],
                [164.905,342.998,180.333,360.64]
            ]
        },
        {
            //l
            l:
            [
                [160.167,326.974,160.167,360.558,176.083,360.558],
            ]
        },
        {
            l:
            [
                [160.167,360.557,160.247,326.89,164.997,326.89,175.5,360.557,178,360.557,188.58,326.89,193.33,326.89,193.25,360.557],
            ]
        },
        {
            // n
            l:
            [
                [160.167,360.599,160.167,326.933,164.629,326.933,178.333,360.599,182.083,360.599,182.083,326.933],
            ]
        },
        {
            l:
            [
                [160.283,332.448,165.764,326.967,178.405,326.967,183.668,332.23,183.668,354.365,177.434,360.599,166.367,360.599,160.167,354.399,160.283,332.448],
            ]
        },
        
        {
            //p
            l:
            [
                [160.167,360.432,160.167,327.015,175.955,327.015,179.667,330.728,179.667,341.015,175.602,345.08,160.167,345.08],
            ]
        },
        
        {
            // q
            l:
            [
                [184.504,361.693,180.517,357.706],
                [160.283,332.413,165.764,326.932,178.405,326.932,183.668,332.195,183.668,354.33,177.434,360.564,166.367,360.564,160.167,354.364,160.283,332.413],
            ]
        },
        {
            // r
            l:
            [
                [179.667,360.307,173.5,344.955],
                [160.167,360.307,160.167,326.89,175.955,326.89,179.667,330.603,179.667,340.89,175.602,344.955,160.167,344.955],
            ]
        },
        {
            // s
            l:
            [
                [179.979,326.87,165.895,326.87,160.167,332.598,160.167,338.307,179.292,349.057,179.292,355.223,173.917,360.598,160.167,360.598],
            ]
        },
        {
            // t
            l:
            [
                [170.417,326.89,170.417,360.974],
                [180.5,326.89,160.167,326.89]
            ]
        },
        {
            // u
            l:
            [
                [160.167,327.14,160.167,356.845,164.108,360.786,178.125,360.786,182.012,356.899,181.958,327.14],
            ]
        },
        {
            // v
            l:
            [
                [160.167,326.901,170.167,360.797,174.417,360.797,184.667,326.734],
            ]
        },
        {
            // w
            l:
            [
                [203.5,326.89,195.208,360.557,191.458,360.557,184,326.89,179.758,326.89,172.208,360.557,168.458,360.557,160.167,326.89],
            ]
        },
        {
            // x
            l:
            [
                [181.333,360.64,159.667,326.807],
                [159.667,360.557,181.75,326.807]
            ]
        },
        {
            // y
            l:
            [
                [160.167,326.891,168.508,347.224,173.992,347.224,182.917,326.891],
                [171.333,347.224,171.333,360.641]
            ]
        },
        {
            // z
            l:
            [
                [161.167,326.807,180.5,326.807,180.5,332.473,161.167,355.223,161.167,360.557,180.5,360.557],
            ]
        },
        
        
        {
            // 0
            l:
            [
                [167.591,326.89,173.076,326.89,180.5,334.315,180.5,353.132,173.076,360.557,167.591,360.557,160.167,353.132,160.167,334.315,167.591,326.89],
            ]
        },
        {
            // 1
            l:
            [
                [160.167,334.315,167.549,326.932,170.333,326.89,170.417,360.557],
            ]
        },
        {
            // 2
            l:
            [
                [164.066,330.415,167.591,326.89,180.5,326.89,180.5,330.603,160.167,351.224,160.167,360.557,180.5,360.599],
            ]
        },
        {
            // 3
            l:
            [
                [169.583,342.932,180.5,342.932],
                [163.129,331.353,167.591,326.89,180.5,326.89,180.5,360.557,167.591,360.557,162.837,355.803],
            ]
        },
        {
            // 4
            l:
            [
                [178.076,326.89,178.076,360.599],
                [160.167,326.89,160.167,338.474,165.104,343.412,178.5,343.412],
            ]
        },
        {
            // 5
            l:
            [
                [180.5,326.89,160.098,326.958,160.167,342.932,180.5,342.932,180.5,353.132,173.076,360.557,160.167,360.557],
            ]
        },
        {
            // 6
            l:
            [
                [173.076,326.89,167.591,326.89,160.167,333.89,160.167,353.132,167.591,360.557,173.417,360.557,180.671,353.303,180.671,342.932,160.167,342.932],
            ]
        },
        {
            // 7
            l:
            [
                [163.591,326.89,180.5,326.89,170.417,360.557],
            ]
        },
        {
            // 8
            l:
            [
                [180.5,334.315,173.076,326.89,167.591,326.89,160.167,334.315,180.5,353.132,173.076,360.557,167.591,360.557,160.167,353.132,180.5,334.315],
            ]
        },
        {
            // 9
            l:
            [
                [167.591,360.557,173.076,360.557,180.5,353.132,180.5,334.315,173.076,326.89,167.591,326.89,160.167,334.315,160.167,342.932,180.5,342.932]                
            ]
        },
        
        {
            // &
            l:
            [
                [182.496,351.137,173.076,360.557,167.591,360.557,160.167,353.132,160.167,348.087,173.922,339.533,172.229,326.89,165.167,326.89,165.052,339.515,184.292,359.432],
            ]
        },
        {
            // '
            l:
            [
        		[160.167,326.932,160.167,333.557],
        		[162.879,326.932,162.879,333.557]

            ]
        },
        {
            // ;
            l:
            [
        		[160.167,342.932,160.167,346.224],
        		[160.167,354.224,160.167,360.557]
            ]
        },
        {
            // :
            l:
            [
        		[160.167,342.932,160.167,346.224],
        		[160.167,354.224,160.167,357.974]
            ]
        },
        {
            // _
            l:
            [
        		[160.167,360.557,170.417,360.557]
            ]
        },
        {
            // +
            l:
            [
        		[160.167,342.932,170,342.932],
                [164.833,347.849,164.833,338.015]
            ]
        },
        {
            // -
            l:
            [
        		[160.167,342.932,170,342.932],
            ]
        },
        {
            // /
            l:
            [
        		[180.5,326.89,160.167,360.557],
            ]
        },
        {
            // .
            l:
            [
        		[160.167,360.599,163.417,360.599],
            ]
        },
        {
            // ,
            l:
            [
        		[165.163,360.557,160.167,365.553],
            ]
        },
        {
            // )
            l:
            [
        		[160.167,360.557,167.591,353.132,167.591,334.315,160.167,326.89],
            ]
        },
        {
            // (
            l:
            [
        		[167.591,326.89,160.167,334.315,160.167,353.132,167.591,360.557],
            ]
        },
        {
            // ?
            l:
            [
        		[170.333,363.481,170.333,368.966],
        		[160.167,334.315,167.591,326.89,173.076,326.89,180.5,334.315,180.5,342.932,170.333,353.132,170.333,360.557]
            ]
        },
        {
            // !
            l:
            [
                [160.167,353.557,160.167,326.89],
                [160.167,357.64,160.167,360.557]
            ]
        },

    ];


function translateX(w)
{
    vec3.set(vec, w,0,0);
    mat4.translate(cgl.mvMatrix,cgl.mvMatrix, vec);
}

var alignMode=0;
align.onValueChanged=function()
{
    if(align.get()=="left")alignMode=0;
    if(align.get()=="center")alignMode=1;
    if(align.get()=="right")alignMode=2;
};

var oldPrim=0;
var shader=null;
function renderChar(charIndex,simulate)
{
    shader=cgl.getShader();
    if(!shader)return;
    oldPrim=shader.glPrimitive;

    shader.glPrimitive=cgl.gl.LINE_STRIP;

    if(charIndex>=characters.length)charIndex=0;
    
    if(!simulate)
    {
        for(var m=0;m<characters[charIndex].m.length;m++)
        {
            characters[charIndex].m[m].render(op.patch.cgl.getShader());
        }
        translateX(characters[charIndex].w*letterSpacing.get());
    }
    else
    {
        stringWidth+=characters[charIndex].w*letterSpacing.get();
    }
    shader.glPrimitive=oldPrim;
}


render.onTriggered=function()
{
    stringWidth=0;
    if(!string.get())return;
    var spaceWidth=0.15;
    vec3.set(vec, 0.3,0,0);
    cgl.pushModelMatrix();

    var startCharacters=97;
    var startNumbers=48;

    var str=string.get()+'';

    cgl.gl.lineWidth(lineWidth.get());

    for(var sim=0;sim<2;sim++)
    {
        var simulate=sim===0;
        
        if(!simulate) 
        {
            if(alignMode==1) translateX(-stringWidth/2+0.04*letterSpacing.get());
            if(alignMode==2) translateX(-stringWidth+0.08*letterSpacing.get());
        }

        for(var i=0;i<str.length;i++)
        {
            var w=0;
            var charIndex=str.toLowerCase().charCodeAt(i);

            if(charIndex==38) renderChar(36,simulate); // &
            else if(charIndex==39) renderChar(37,simulate); // '
            else if(charIndex==34) renderChar(37,simulate); // '
            else if(charIndex==59) renderChar(38,simulate); // ;
            else if(charIndex==58) renderChar(39,simulate); // :
            else if(charIndex==95) renderChar(40,simulate); // _
            else if(charIndex==43) renderChar(41,simulate); // +
            else if(charIndex==45) renderChar(42,simulate); // -
            else if(charIndex==47) renderChar(43,simulate); // /
            else if(charIndex==46) renderChar(44,simulate); // .
            else if(charIndex==44) renderChar(45,simulate); // ,
            else if(charIndex==41) renderChar(46,simulate); // )
            else if(charIndex==40) renderChar(47,simulate); // ()
            else if(charIndex==63) renderChar(48,simulate); // ?
            else if(charIndex==33) renderChar(49,simulate); // !
            else
            if(charIndex>=startNumbers && charIndex<=startNumbers+10)
            {
                renderChar(charIndex-startNumbers+26,simulate);
            }
            else
            if(charIndex>=startCharacters && charIndex-startCharacters<characters.length)
            {
                renderChar(charIndex-startCharacters,simulate);
            }
            else
            if(charIndex==32)
            {
                if(simulate)stringWidth+=spaceWidth;
                else translateX(spaceWidth);
            }
            else
            {
                renderChar(48,simulate);
            }
        }
    }

    cgl.popModelMatrix();
};

function avg(which)
{
    var avgX=0,avgY=0;
    var count=0;
    for(var l=0;l<characters[which].l.length;l++)
    {
        for(var j=0;j<characters[which].l[l].length;j+=2)
        {
            avgX+=characters[which].l[l][j];
            avgY+=characters[which].l[l][j+1];
            count++;
        }
    }
    avgX/=count;
    avgY/=count;
    return [avgX,avgY];
}

function min(which)
{
    var min=9999999;

    for(var l=0;l<characters[which].l.length;l++)
    {
        for(var j=0;j<characters[which].l[l].length;j+=2)
        {
            min=Math.min(min,characters[which].l[l][j]);
        }
    }
    return min;
}

function width(which)
{
    var min=9999999;
    var max=-9999999;

    for(var l=0;l<characters[which].l.length;l++)
    {
        for(var j=0;j<characters[which].l[l].length;j+=2)
        {
            min=Math.min(min,characters[which].l[l][j]);
            max=Math.max(max,characters[which].l[l][j]);
        }
    }
    return ((max-min));
}


meshes.length=0;

var avgXY=[];
var avg1=avg(0);
var avg2=avg(1);

avgXY=[ (avg1[0]+avg2[0])/2, (avg1[1]+avg2[1])/2 ];

for(var i=0;i<characters.length;i++)
{
    characters[i].w=width(i)*(0.002);
    characters[i].m=[];
    for(var l=0;l<characters[i].l.length;l++)
    {
        var count=0;
        var indices=[];
        var vertices=[];

        for(var j=0;j<characters[i].l[l].length;j+=2)
        {
            vertices.push( (characters[i].l[l][j]-min(i))*0.005 );
            vertices.push( (characters[i].l[l][j+1]-avgXY[1])*-0.005 );
            vertices.push( 0 );
            
            indices.push(count);
            count++;
        }
        
        var geom=new CGL.Geometry();
        geom.vertices=vertices;
        geom.verticesIndices=indices;
        var mesh=new CGL.Mesh(op.patch.cgl,geom);
        characters[i].m.push(mesh);
    }
    
    characters[i].w+=0.1;
}


};

Ops.Gl.LineFont.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.OneMinus
// 
// **************************************************************

Ops.Math.OneMinus = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const inValue=op.inValue("Value");
const result=op.outValue("Result");

inValue.onChange=update;
update();

function update()
{
    result.set(1-inValue.get());
}



};

Ops.Math.OneMinus.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Vars.Variable
// 
// **************************************************************

Ops.Vars.Variable = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="Variable";

op.varName=op.inValueSelect("Variable");
var val=op.outValue("Value");

var variable=null;
op.patch.addVariableListener(init);
init();

updateVarNamesDropdown();

function updateVarNamesDropdown()
{
    if(CABLES.UI)
    {
        var varnames=[];
        var vars=op.patch.getVars();

        for(var i in vars) varnames.push(i);

        varnames.push('+ create new one');
        op.varName.uiAttribs.values=varnames;
    }
}

op.varName.onChange=function()
{
    init();
};

function init()
{
    updateVarNamesDropdown();

    if(CABLES.UI)
    {
        if(op.varName.get()=='+ create new one')
        {
            CABLES.CMD.PATCH.createVariable(op);
            return;
        }
    }

    if(variable)
    {
        variable.removeListener(onChange);
    }

    variable=op.patch.getVar(op.varName.get());

    if(variable)
    {
        variable.addListener(onChange);
        op.uiAttr({error:null,});
        op.setTitle('#'+op.varName.get());
        onChange(variable.getValue());
        // console.log("var value ",variable.getName(),variable.getValue());
    }
    else
    {
        op.uiAttr({error:"unknown variable! - there is no setVariable with this name"});
        op.setTitle('#invalid');
    }
}


function onChange(v)
{
    updateVarNamesDropdown();
    val.set(v);
}

op.onDelete=function()
{
    if(variable)
        variable.removeListener(onChange);
};


};

Ops.Vars.Variable.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Audio.MidiJson
// 
// **************************************************************

Ops.Audio.MidiJson = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var inObj=op.inObject("MidiJson");
var inTime=op.inValue("Time");

const outBeat=op.outValue("Beat");

var outNames=op.outArray("Names");
var outProgress=op.outArray("Progress");
var outVelocity=op.outArray("Velocity");

var outNumTracks=op.outValue("Num Tracks");
const outBPM=op.outValue("BPM");
const outData=op.outObject("Data");

var midi=null;
var arrNames=[];
var arrProgress=[];
var arrVelocity=[];
var data=
    {
        beat:0,
        names:arrNames,
        progress:arrProgress,
        velocity:arrVelocity,
    };

var bpm=0;

inObj.onChange=function()
{
    midi=null;
    outNumTracks.set(0);
    
    midi=inObj.get();
    if(!midi)return;
    if(!midi.tracks)return;
    
    outNumTracks.set(midi.tracks.length);
    
    arrNames.length=midi.tracks.length;

    bpm=midi.header.bpm;
    outBPM.set(midi.header.bpm);


    for(var t=0;t<midi.tracks.length;t++)
    {
        for(var n=0;n<midi.tracks[t].notes.length;n++)
        {
            var note=midi.tracks[t].notes[n];
            note.timeEnd=note.time+note.duration;
        }
    }
};

inTime.onChange=function()
{
    if(!midi)return;
    if(!midi.tracks)return;
    
    var time=inTime.get();
    outNames.set(null);
    outProgress.set(null);
    outVelocity.set(null);
    outData.set(null);
    
    for(var t=0;t<midi.tracks.length;t++)
    {
        arrNames[t]='';
        arrProgress[t]=0;
        arrVelocity[t]=0;
        
        for(var n=0;n<midi.tracks[t].notes.length;n++)
        {
            var note=midi.tracks[t].notes[n];

            if(
                time>note.time && 
                time<note.timeEnd)
            {
                arrProgress[t]=(time-note.time)/(note.duration);
                arrNames[t]=note.name;
                arrVelocity[t]=note.velocity;
            }
        }
    }
    
    outNames.set(arrNames);
    outProgress.set(arrProgress);
    outVelocity.set(arrVelocity);
    
    var beat=Math.round(inTime.get()/60*(bpm));
    data.beat=beat;
    outData.set(data);
    
    outBeat.set( beat);
};

};

Ops.Audio.MidiJson.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Vars.SetVariableObject
// 
// **************************************************************

Ops.Vars.SetVariableObject = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var val=op.inObject("Object");
op.varName=op.inValueSelect("Variable");

op.varName.onChange=updateName;
val.onChange=update;

op.patch.addVariableListener(updateVarNamesDropdown);

updateVarNamesDropdown();

function updateVarNamesDropdown()
{
    if(CABLES.UI)
    {
        var varnames=[];
        var vars=op.patch.getVars();

        for(var i in vars) varnames.push(i);

        varnames.push('+ create new one');
        op.varName.uiAttribs.values=varnames;
    }
}

function updateName()
{
    if(CABLES.UI)
    {
        if(op.varName.get()=='+ create new one')
        {
            CABLES.CMD.PATCH.createVariable(op);
            return;
        }

        op.setTitle('#'+op.varName.get());
    }
    update();
}

function update()
{
    op.patch.setVarValue(op.varName.get(),val.get());
}


};

Ops.Vars.SetVariableObject.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Vars.VariableObject
// 
// **************************************************************

Ops.Vars.VariableObject = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.varName=op.inValueSelect("Variable");
var val=op.outObject("Object");

var variable=null;
op.patch.addVariableListener(init);
init();

updateVarNamesDropdown();

function updateVarNamesDropdown()
{
    if(CABLES.UI)
    {
        var varnames=[];
        var vars=op.patch.getVars();

        for(var i in vars) varnames.push(i);

        varnames.push('+ create new one');
        op.varName.uiAttribs.values=varnames;
    }
}

op.varName.onChange=function()
{
    init();
};

function init()
{
    updateVarNamesDropdown();

    if(CABLES.UI)
    {
        if(op.varName.get()=='+ create new one')
        {
            CABLES.CMD.PATCH.createVariable(op);
            return;
        }
    }

    if(variable)
    {
        variable.removeListener(onChange);
    }

    variable=op.patch.getVar(op.varName.get());

    if(variable)
    {
        variable.addListener(onChange);
        op.uiAttr({error:null,});
        op.setTitle('#'+op.varName.get());
        onChange(variable.getValue());
        // console.log("var value ",variable.getName(),variable.getValue());
    }
    else
    {
        op.uiAttr({error:"unknown variable! - there is no setVariable with this name"});
        op.setTitle('#invalid');
    }
}


function onChange(v)
{
    updateVarNamesDropdown();
    val.set(v);
}

op.onDelete=function()
{
    if(variable)
        variable.removeListener(onChange);
};


};

Ops.Vars.VariableObject.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Delta
// 
// **************************************************************

Ops.Math.Delta = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var CHANGE_ALWAYS_DEFUALT = false;

var val=op.inValue("Value");
val.changeAlways = CHANGE_ALWAYS_DEFUALT;
var changeAlwaysPort = op.inValueBool('Change Always', CHANGE_ALWAYS_DEFUALT);
var result=op.outValue("Delta");

var oldVal=0;

changeAlwaysPort.onChange = function() {
    val.changeAlways = changeAlwaysPort.get();    
};

val.onValueChanged=function()
{
    var change=oldVal-val.get();
    oldVal=val.get();
    result.set(change);
};



};

Ops.Math.Delta.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.AddUp
// 
// **************************************************************

Ops.Math.AddUp = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var number=op.inValue("Number");
var doAdd=op.inFunctionButton("Add");
var doReset=op.inFunctionButton("Reset");

var result=op.outValue("Result");

var value=0;

doAdd.onTriggered=function()
{
    value+=number.get();
    result.set(value);
};

doReset.onTriggered=function()
{
    value=0;
    result.set(value);
};

};

Ops.Math.AddUp.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Abs
// 
// **************************************************************

Ops.Math.Abs = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='abs';
var number=op.addInPort(new Port(op,"number"));
var result=op.addOutPort(new Port(op,"result"));

number.onValueChanged=function()
{
    result.set( Math.abs(number.get()) );
};

};

Ops.Math.Abs.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.PixelDisplacement
// 
// **************************************************************

Ops.Gl.TextureEffects.PixelDisplacement = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["pixeldisplace_frag"]="\n#ifdef HAS_TEXTURES\n    IN vec2 texCoord;\n    UNI sampler2D tex;\n    UNI sampler2D displaceTex;\n#endif\nUNI float amountX;\nUNI float amountY;\n\nvoid main()\n{\n    vec4 col=vec4(1.0,0.0,0.0,1.0);\n    #ifdef HAS_TEXTURES\n        float mulX=1.0;\n        float mulY=1.0;\n        float x=mod(texCoord.x+mulX*(texture2D(displaceTex,texCoord).g-0.5)*2.0*amountX,1.0);\n        float y=mod(texCoord.y+mulY*(texture2D(displaceTex,texCoord).g-0.5)*2.0*amountY,1.0);\n\n\n        col=texture2D(tex,vec2(x,y) );\n//        col.rgb=desaturate(col.rgb,amount);\n   #endif\n   gl_FragColor = col;\n}";
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));

var amount=op.addInPort(new Port(op,"amountX",OP_PORT_TYPE_VALUE,{ display:'range' }));
var amountY=op.addInPort(new Port(op,"amountY",OP_PORT_TYPE_VALUE,{ display:'range' }));

var displaceTex=op.inTexture("displaceTex");
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;

var shader=new CGL.Shader(cgl);

shader.setSource(shader.getDefaultVertexShader(),attachments.pixeldisplace_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var textureDisplaceUniform=new CGL.Uniform(shader,'t','displaceTex',1);

var amountXUniform=new CGL.Uniform(shader,'f','amountX',amount);
var amountYUniform=new CGL.Uniform(shader,'f','amountY',amountY);

render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    if(displaceTex.get())
        cgl.setTexture(1, displaceTex.get().tex );


    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};



};

Ops.Gl.TextureEffects.PixelDisplacement.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Vars.SetVariable
// 
// **************************************************************

Ops.Vars.SetVariable = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var val=op.inValue("Value");
op.varName=op.inValueSelect("Variable");

op.varName.onChange=updateName;
val.onChange=update;
val.changeAlways=true;
val.set(false);

op.patch.addVariableListener(updateVarNamesDropdown);

updateVarNamesDropdown();

function updateVarNamesDropdown()
{
    if(CABLES.UI)
    {
        var varnames=[];
        var vars=op.patch.getVars();

        for(var i in vars) varnames.push(i);

        varnames.push('+ create new one');
        op.varName.uiAttribs.values=varnames;
    }
}

function updateName()
{
    if(CABLES.UI)
    {
        if(op.varName.get()=='+ create new one')
        {
            CABLES.CMD.PATCH.createVariable(op);
            return;
        }

        op.setTitle('#'+op.varName.get());
    }
    update();
}

function update()
{
    op.patch.setVarValue(op.varName.get(),val.get());
}


};

Ops.Vars.SetVariable.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.MapRange
// 
// **************************************************************

Ops.Math.MapRange = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var result=op.addOutPort(new Port(op,"result"));
var v=op.addInPort(new Port(op,"value"));
var old_min=op.addInPort(new Port(op,"old min"));
var old_max=op.addInPort(new Port(op,"old max"));
var new_min=op.addInPort(new Port(op,"new min"));
var new_max=op.addInPort(new Port(op,"new max"));
var easing=op.inValueSelect("Easing",["Linear","Smoothstep","Smootherstep"],"Linear");

var ease=0;
var r=0;

easing.onChange=function()
{
    if(easing.get()=="Smoothstep") ease=1;
        else if(easing.get()=="Smootherstep") ease=2;
            else ease=0;
};


function exec()
{
    if(v.get()>=Math.max( old_max.get(),old_min.get() ))
    {
        result.set(new_max.get());
        return;
    }
    else
    if(v.get()<=Math.min( old_max.get(),old_min.get() )) 
    {
        result.set(new_min.get());
        return;
    }

    var nMin=new_min.get();
    var nMax=new_max.get();
    var oMin=old_min.get();
    var oMax=old_max.get();
    var x=v.get();

    var reverseInput = false;
    var oldMin = Math.min( oMin, oMax );
    var oldMax = Math.max( oMin, oMax );
    if(oldMin!= oMin) reverseInput = true;

    var reverseOutput = false;
    var newMin = Math.min( nMin, nMax );
    var newMax = Math.max( nMin, nMax );
    if(newMin != nMin) reverseOutput = true;

    var portion=0;

    if(reverseInput) portion = (oldMax-x)*(newMax-newMin)/(oldMax-oldMin);
        else portion = (x-oldMin)*(newMax-newMin)/(oldMax-oldMin);

    if(reverseOutput) r=newMax - portion;
        else r=portion + newMin;

    if(ease===0)
    {
        result.set(r);
    }
    else
    if(ease==1)
    {
        x = Math.max(0, Math.min(1, (r-nMin)/(nMax-nMin)));
        result.set( nMin+x*x*(3 - 2*x)* (nMax-nMin) ); // smoothstep
    }
    else
    if(ease==2)
    {
        x = Math.max(0, Math.min(1, (r-nMin)/(nMax-nMin)));
        result.set( nMin+x*x*x*(x*(x*6 - 15) + 10) * (nMax-nMin) ) ; // smootherstep
    }

}

v.set(0);
old_min.set(0);
old_max.set(1);
new_min.set(-1);
new_max.set(1);


v.onValueChanged=exec;
old_min.onValueChanged=exec;
old_max.onValueChanged=exec;
new_min.onValueChanged=exec;
new_max.onValueChanged=exec;

result.set(0);

exec();

};

Ops.Math.MapRange.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Sphere
// 
// **************************************************************

Ops.Gl.Meshes.Sphere = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var inStacks=op.inValueInt("stacks",32);
var inSlices=op.inValueInt("slices",32);
var inRadius=op.addInPort(new Port(op,"radius",OP_PORT_TYPE_VALUE));
var inRender=op.inValueBool("Render",true);


var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var geomOut=op.addOutPort(new Port(op,"geometry",OP_PORT_TYPE_OBJECT));

inRadius.set(1);
geomOut.ignoreValueSerialize=true;

var cgl=op.patch.cgl;
var mesh=null;
var geom=null;
var geomVertices=[];
var geomVertexNormals=[];
var geomTexCoords=[];
var geomVerticesIndices=[];


inSlices.onChange=function(){ mesh=null; };
inStacks.onChange=function(){ mesh=null; };
inRadius.onChange=function(){ mesh=null; };

op.preRender=
render.onTriggered=function()
{
    if(!mesh) updateMesh();

    if(inRender.get()) mesh.render(cgl.getShader());
    
    trigger.trigger();
};

function updateMesh()
{
    var nslices=Math.round(inSlices.get());
    var nstacks=Math.round(inStacks.get());
    if(nslices<1)nslices=1;
    if(nstacks<1)nstacks=1;
    var r=inRadius.get();
    
    uvSphere(r, nslices, nstacks);
}

// updateMesh();

function circleTable(n,halfCircle)
{
    var i;
    /* Table size, the sign of n flips the circle direction */
    var size = Math.abs(n);

    /* Determine the angle between samples */
    var angle = (halfCircle?1:2)*Math.PI/n;// ( n === 0 ) ? 1 : n ;

    /* Allocate memory for n samples, plus duplicate of first entry at the end */
    var sint=[];
    var cost=[];

    /* Compute cos and sin around the circle */
    sint[0] = 0.0;
    cost[0] = 1.0;

    for (i=0; i<size; i++)
    {
        sint[i] = Math.sin(angle*i);
        cost[i] = Math.cos(angle*i);
    }
    
    if (halfCircle)
    {
        sint[size] =  0.0;  /* sin PI */
        cost[size] = -1.0;  /* cos PI */
    }
    else
    {
        /* Last sample is duplicate of the first (sin or cos of 2 PI) */
        sint[size] = sint[0];
        cost[size] = cost[0];
    }
    return {cost:cost,sint:sint};
}


// from http://math.hws.edu/graphicsbook/source/webgl/basic-object-models-IFS.js
function uvSphere(radius, slices, stacks)
{
    var geom=new CGL.Geometry("sphere");

    radius = radius || 0.5;
    slices = slices || 32;
    stacks = stacks || 16;
    var vertexCount = (slices+1)*(stacks+1);
    var vertices = new Float32Array( 3*vertexCount );
    var normals = new Float32Array( 3* vertexCount );
    var texCoords = new Float32Array( 2*vertexCount );
    var indices = new Uint16Array( 2*slices*stacks*3 );
    var du = 2*Math.PI/slices;
    var dv = Math.PI/stacks;
    var i,j,u,v,x,y,z;
    var indexV = 0;
    var indexT = 0;
    for (i = 0; i <= stacks; i++)
    {
        v = -Math.PI/2 + i*dv;
        for (j = 0; j <= slices; j++)
        {
            u = j*du;
            x = Math.cos(u)*Math.cos(v);
            y = Math.sin(u)*Math.cos(v);
            z = Math.sin(v);

            vertices[indexV] = radius*x;
            normals[indexV++] = x;

            vertices[indexV] = radius*y;
            normals[indexV++] = y;

            vertices[indexV] = radius*z;
            normals[indexV++] = z;

            texCoords[indexT++] = j/slices;
            texCoords[indexT++] = i/stacks;
        } 
    }
    var k = 0;
    for (j = 0; j < stacks; j++)
    {
        var row1 = j*(slices+1);
        var row2 = (j+1)*(slices+1);
        for (i = 0; i < slices; i++)
        {
            indices[k++] = row1 + i;
            indices[k++] = row2 + i;
            indices[k++] = row2 + i + 1;
         
            indices[k++] = row1 + i;
            indices[k++] = row2 + i + 1;
            indices[k++] = row1 + i + 1;

        }
    }

    geom.vertices=vertices;
    geom.vertexNormals=normals;
    geom.texCoords=texCoords;
    geom.verticesIndices=indices;

    geomOut.set(geom);

    if(!mesh)mesh=new CGL.Mesh(cgl,geom,cgl.gl.TRIANGLE_STRIP);
    mesh.setGeom(geom);

}



};

Ops.Gl.Meshes.Sphere.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Exp.Gl.MatCapMaterialNew
// 
// **************************************************************

Ops.Exp.Gl.MatCapMaterialNew = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["matcap_frag"]="\n{{MODULES_HEAD}}\n\nIN vec3 norm;\nIN vec2 texCoord;\nUNI sampler2D tex;\nIN vec2 vNorm;\nUNI mat4 viewMatrix;\n\nUNI float repeatX;\nUNI float repeatY;\nUNI float opacity;\n\nUNI float r;\nUNI float g;\nUNI float b;\n\nIN vec3 e;\n\n\n\n#ifdef HAS_DIFFUSE_TEXTURE\n   UNI sampler2D texDiffuse;\n#endif\n\n#ifdef USE_SPECULAR_TEXTURE\n   UNI sampler2D texSpec;\n   UNI sampler2D texSpecMatCap;\n#endif\n\n#ifdef HAS_AO_TEXTURE\n    UNI sampler2D texAo;\n    UNI float aoIntensity;\n#endif\n\n#ifdef HAS_NORMAL_TEXTURE\n   IN vec3 vBiTangent;\n   IN vec3 vTangent;\n\n   UNI sampler2D texNormal;\n   UNI mat4 normalMatrix;\n   \n   vec2 vNormt;\n#endif\n\n#ifdef CALC_SSNORMALS\n    // from https://www.enkisoftware.com/devlogpost-20150131-1-Normal_generation_in_the_pixel_shader\n    IN vec3 eye_relative_pos;\n#endif\n\n\nconst float normalScale=0.4;\n\nconst vec2 invAtan = vec2(0.1591, 0.3183);\nvec2 sampleSphericalMap(vec3 direction)\n{\n    vec2 uv = vec2(atan(direction.z, direction.x), asin(direction.y));\n    uv *= invAtan;\n    uv += 0.5;\n    return uv;\n}\n\n\nvoid main()\n{\n    vec2 vnOrig=vNorm;\n    vec2 vn=vNorm;\n\n\n\n    #ifdef HAS_TEXTURES\n        vec2 texCoords=texCoord;\n        {{MODULE_BEGIN_FRAG}}\n    #endif\n\n    #ifdef CALC_SSNORMALS\n    \tvec3 dFdxPos = dFdx( eye_relative_pos );\n    \tvec3 dFdyPos = dFdy( eye_relative_pos );\n    \tvec3 ssn = normalize( cross(dFdxPos,dFdyPos ));\n    \t\n        vec3 rr = reflect( e, ssn );\n        float ssm = 2. * sqrt( \n            pow(rr.x, 2.0)+\n            pow(rr.y, 2.0)+\n            pow(rr.z + 1.0, 2.0)\n        );\n\n\n        vn = (rr.xy / ssm + 0.5);\n        \n        vn.t=clamp(vn.t, 0.0, 1.0);\n        vn.s=clamp(vn.s, 0.0, 1.0);\n        \n        // float dst = dot(abs(coord-center), vec2(1.0));\n        // float aaf = fwidth(dst);\n        // float alpha = smoothstep(radius - aaf, radius, dst);\n\n    #endif\n\n   #ifdef HAS_NORMAL_TEXTURE\n        vec3 tnorm=texture2D( texNormal, vec2(texCoord.x*repeatX,texCoord.y*repeatY) ).xyz * 2.0 - 1.0;\n        \n        tnorm = normalize(tnorm*normalScale);\n        \n        vec3 tangent;\n        vec3 binormal;\n        \n        #ifdef CALC_TANGENT\n            vec3 c1 = cross(norm, vec3(0.0, 0.0, 1.0));\n//            vec3 c2 = cross(norm, vec3(0.0, 1.0, 0.0));\n//            if(length(c1)>length(c2)) tangent = c2;\n//                else tangent = c1;\n            tangent = c1;\n            tangent = normalize(tangent);\n            binormal = cross(norm, tangent);\n            binormal = normalize(binormal);\n        #endif\n\n        #ifndef CALC_TANGENT\n            tangent=normalize(vTangent);\n//            tangent.y*=-13.0;\n//            binormal=vBiTangent*norm;\n//            binormal.z*=-1.0;\n//            binormal=normalize(binormal);\n            binormal=normalize( cross( normalize(norm), normalize(vBiTangent) ));\n        // vBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );\n\n        #endif\n\n        tnorm=normalize(tangent*tnorm.x + binormal*tnorm.y + norm*tnorm.z);\n\n        // vec3 n = normalize( mat3(normalMatrix) * (norm+tnorm*normalScale) );\n        vec3 n = normalize( mat3(normalMatrix) * (norm+tnorm*normalScale) );\n\n        vec3 re = reflect( e, n );\n        float m = 2. * sqrt( \n            pow(re.x, 2.0)+\n            pow(re.y, 2.0)+\n            pow(re.z + 1.0, 2.0)\n        );\n        \n        vn = (re.xy / m + 0.5);\n        \n    #endif\n\n    vn.t=clamp(vn.t, 0.0, 1.0);\n    vn.s=clamp(vn.s, 0.0, 1.0);\n    \n    \n    vec4 col = texture2D( tex, vn );\n\n    #ifdef HAS_DIFFUSE_TEXTURE\n        col = col*texture2D( texDiffuse, vec2(texCoords.x*repeatX,texCoords.y*repeatY));\n    #endif\n\n    col.r*=r;\n    col.g*=g;\n    col.b*=b;\n\n\n    #ifdef HAS_AO_TEXTURE\n        col = col*\n            mix(\n                vec4(1.0,1.0,1.0,1.0),\n                texture2D( texAo, vec2(texCoords.x*repeatX,texCoords.y*repeatY)),\n                aoIntensity\n                );\n    #endif\n\n    #ifdef USE_SPECULAR_TEXTURE\n        vec4 spec = texture2D( texSpecMatCap, vn );\n        spec*= texture2D( texSpec, vec2(texCoords.x*repeatX,texCoords.y*repeatY) );\n        col+=spec;\n    #endif\n\n    col.a*=opacity;\n\n    {{MODULE_COLOR}}\n\n    outColor = col;\n\n}";
attachments["matcap_vert"]="\nIN vec3 vPosition;\nIN vec2 attrTexCoord;\nIN vec3 attrVertNormal;\nIN float attrVertIndex;\n\n#ifdef HAS_NORMAL_TEXTURE\n   IN vec3 attrTangent;\n   IN vec3 attrBiTangent;\n\n   OUT vec3 vBiTangent;\n   OUT vec3 vTangent;\n#endif\n\nOUT vec2 texCoord;\nOUT vec3 norm;\nUNI mat4 projMatrix;\nUNI mat4 modelMatrix;\nUNI mat4 viewMatrix;\n\n#ifndef INSTANCING\n    UNI mat4 normalMatrix;\n#endif\nOUT vec2 vNorm;\n\nOUT vec3 e;\n\n\n{{MODULES_HEAD}}\n\n#ifdef CALC_SSNORMALS\n    // from https://www.enkisoftware.com/devlogpost-20150131-1-Normal_generation_in_the_pixel_shader\n    OUT vec3 eye_relative_pos;\n    UNI vec3 camPos;\n#endif\n\n\n\nvoid main()\n{\n    texCoord=attrTexCoord;\n    norm=attrVertNormal;\n    mat4 mMatrix=modelMatrix;\n    mat4 mvMatrix;\n    \n    #ifdef HAS_NORMAL_TEXTURE\n        vTangent=attrTangent;\n        vBiTangent=attrBiTangent;\n    #endif\n\n    vec4 pos = vec4( vPosition, 1. );\n\n    {{MODULE_VERTEX_POSITION}}\n\n\n    mvMatrix= viewMatrix * mMatrix;\n\n    #ifdef INSTANCING\n        mat4 normalMatrix=inverse(transpose(mvMatrix));\n    #endif\n    \n    e = normalize( vec3( mvMatrix * pos ) );\n    vec3 n = normalize( mat3(normalMatrix) * norm );\n    \n\n    // mat3 nMatrix = transpose(inverse(mat3(mMatrix)));\n    // vec3 n = normalize( mat3(nMatrix) * norm );\n    // norm=n;\n\n    vec3 r = reflect( e, n );\n    \n    \n    \n    \n    float m = 2. * sqrt(\n        pow(r.x, 2.0)+\n        pow(r.y, 2.0)+\n        pow(r.z + 1.0, 2.0)\n    );\n    vNorm = r.xy / m + 0.5;\n\n    #ifdef DO_PROJECT_COORDS_XY\n       texCoord=(projMatrix * mvMatrix*pos).xy*0.1;\n    #endif\n\n    #ifdef DO_PROJECT_COORDS_YZ\n       texCoord=(projMatrix * mvMatrix*pos).yz*0.1;\n    #endif\n\n    #ifdef DO_PROJECT_COORDS_XZ\n        texCoord=(projMatrix * mvMatrix*pos).xz*0.1;\n    #endif\n\n    #ifdef CALC_SSNORMALS\n        eye_relative_pos = (mvMatrix * pos ).xyz - camPos;\n    #endif\n\n\n\n   gl_Position = projMatrix * mvMatrix * pos;\n\n}";


var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var textureMatcap=op.inTexture('MatCap');
var textureDiffuse=op.inTexture('Diffuse');
var textureNormal=op.inTexture('Normal');
var textureSpec=op.inTexture('Specular');
var textureSpecMatCap=op.inTexture('Specular MatCap');
var textureAo=op.inTexture('AO Texture');

var cgl=op.patch.cgl;

{
    // rgba colors
    var r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range',colorPick:'true' })); r.set(1);
    var g=op.inValueSlider('g',1);
    var b=op.inValueSlider('b',1);
}

var aoIntensity=op.inValueSlider("AO Intensity",1.0);
var repeatX=op.inValue("Repeat X",1);
var repeatY=op.inValue("Repeat Y",1);
var pOpacity=op.inValueSlider("Opacity",1);
var calcTangents = op.inValueBool("calc normal tangents",true);
var projectCoords=op.inValueSelect('projectCoords',['no','xy','yz','xz'],'no');
var ssNormals=op.inValueBool("Screen Space Normals");




var next=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var shaderOut=op.outObject("Shader");



var shader=new CGL.Shader(cgl,'MatCapMaterialNew');

var uniOpacity=new CGL.Uniform(shader,'f','opacity',pOpacity);

shader.setModules(['MODULE_VERTEX_POSITION','MODULE_COLOR','MODULE_BEGIN_FRAG']);
shader.bindTextures=bindTextures;
shader.setSource(attachments.matcap_vert,attachments.matcap_frag);
shaderOut.set(shader);

var textureMatcapUniform=null;
var textureDiffuseUniform=null;
var textureNormalUniform=null;
var textureSpecUniform=null;
var textureSpecMatCapUniform=null;
var textureAoUniform=null;
var repeatXUniform=new CGL.Uniform(shader,'f','repeatX',repeatX);
var repeatYUniform=new CGL.Uniform(shader,'f','repeatY',repeatY);
var aoIntensityUniform=new CGL.Uniform(shader,'f','aoIntensity',aoIntensity);
b.uniform=new CGL.Uniform(shader,'f','b',b);
g.uniform=new CGL.Uniform(shader,'f','g',g);
r.uniform=new CGL.Uniform(shader,'f','r',r);


calcTangents.onChange=updateDefines;
updateDefines();
updateMatcap();

function updateDefines()
{
    if(calcTangents.get()) shader.define('CALC_TANGENT');
        else shader.removeDefine('CALC_TANGENT');

}

ssNormals.onChange=function()
{
    if(ssNormals.get())
    {
        if(cgl.glVersion<2)
        {
            cgl.gl.getExtension('OES_standard_derivatives');
            shader.enableExtension('GL_OES_standard_derivatives');
        }

        shader.define('CALC_SSNORMALS');
    }
    else shader.removeDefine('CALC_SSNORMALS');
};

projectCoords.onChange=function()
{
    shader.removeDefine('DO_PROJECT_COORDS_XY');
    shader.removeDefine('DO_PROJECT_COORDS_YZ');
    shader.removeDefine('DO_PROJECT_COORDS_XZ');

    if(projectCoords.get()=='xy') shader.define('DO_PROJECT_COORDS_XY');
    else if(projectCoords.get()=='yz') shader.define('DO_PROJECT_COORDS_YZ');
    else if(projectCoords.get()=='xz') shader.define('DO_PROJECT_COORDS_XZ');
};

textureMatcap.onChange=updateMatcap;

function updateMatcap()
{
    if(textureMatcap.get())
    {
        if(textureMatcapUniform!==null)return;
        shader.removeUniform('tex');
        textureMatcapUniform=new CGL.Uniform(shader,'t','tex',0);
    }
    else
    {
        if(!CGL.defaultTextureMap)
        {
            var pixels=new Uint8Array(256*4);
            for(var x=0;x<16;x++)
            {
                for(var y=0;y<16;y++)
                {
                    var c=y*16;
                    c*=Math.min(1,(x+y/3)/8);
                    pixels[(x+y*16)*4+0]=pixels[(x+y*16)*4+1]=pixels[(x+y*16)*4+2]=c;
                    pixels[(x+y*16)*4+3]=255;
                }
            }

            CGL.defaultTextureMap=new CGL.Texture(cgl);
            CGL.defaultTextureMap.initFromData(pixels,16,16);
        }
        textureMatcap.set(CGL.defaultTextureMap);

        shader.removeUniform('tex');
        textureMatcapUniform=new CGL.Uniform(shader,'t','tex',0);
    }
}

textureDiffuse.onChange=function()
{
    if(textureDiffuse.get())
    {
        if(textureDiffuseUniform!==null)return;
        shader.define('HAS_DIFFUSE_TEXTURE');
        shader.removeUniform('texDiffuse');
        textureDiffuseUniform=new CGL.Uniform(shader,'t','texDiffuse',1);
    }
    else
    {
        shader.removeDefine('HAS_DIFFUSE_TEXTURE');
        shader.removeUniform('texDiffuse');
        textureDiffuseUniform=null;
    }
};

textureNormal.onChange=function()
{
    if(textureNormal.get())
    {
        if(textureNormalUniform!==null)return;
        shader.define('HAS_NORMAL_TEXTURE');
        shader.removeUniform('texNormal');
        textureNormalUniform=new CGL.Uniform(shader,'t','texNormal',2);
    }
    else
    {
        shader.removeDefine('HAS_NORMAL_TEXTURE');
        shader.removeUniform('texNormal');
        textureNormalUniform=null;
    }
};

textureAo.onChange=function()
{
    if(textureAo.get())
    {
        if(textureAoUniform!==null)return;
        shader.define('HAS_AO_TEXTURE');
        shader.removeUniform('texAo');
        textureAoUniform=new CGL.Uniform(shader,'t','texAo',5);
    }
    else
    {
        shader.removeDefine('HAS_AO_TEXTURE');
        shader.removeUniform('texAo');
        textureAoUniform=null;
    }
};

textureSpec.onChange=textureSpecMatCap.onChange=function()
{
    if(textureSpec.get() && textureSpecMatCap.get())
    {
        if(textureSpecUniform!==null)return;
        shader.define('USE_SPECULAR_TEXTURE');
        shader.removeUniform('texSpec');
        shader.removeUniform('texSpecMatCap');
        textureSpecUniform=new CGL.Uniform(shader,'t','texSpec',3);
        textureSpecMatCapUniform=new CGL.Uniform(shader,'t','texSpecMatCap',4);
    }
    else
    {
        shader.removeDefine('USE_SPECULAR_TEXTURE');
        shader.removeUniform('texSpec');
        shader.removeUniform('texSpecMatCap');
        textureSpecUniform=null;
        textureSpecMatCapUniform=null;
    }
};

function bindTextures()
{
    if(textureMatcap.get())     cgl.setTexture(0,textureMatcap.get().tex);
    if(textureDiffuse.get())    cgl.setTexture(1,textureDiffuse.get().tex);
    if(textureNormal.get())     cgl.setTexture(2,textureNormal.get().tex);
    if(textureSpec.get())       cgl.setTexture(3,textureSpec.get().tex);
    if(textureSpecMatCap.get()) cgl.setTexture(4,textureSpecMatCap.get().tex);
    if(textureAo.get())         cgl.setTexture(5,textureAo.get().tex);
};


op.onDelete=function()
{
    if(CGL.defaultTextureMap)
    {
        CGL.defaultTextureMap.delete();
        CGL.defaultTextureMap=null;
    }
};

op.preRender=function()
{
    shader.bind();
};

render.onTriggered=function()
{
    shader.bindTextures=bindTextures;
    cgl.setShader(shader);
    next.trigger();
    cgl.setPreviousShader();
};



};

Ops.Exp.Gl.MatCapMaterialNew.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Texture
// 
// **************************************************************

Ops.Gl.Texture = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var filename=op.addInPort(new Port(op,"file",OP_PORT_TYPE_VALUE,{ display:'file',type:'string',filter:'image' } ));
var tfilter=op.inValueSelect("filter",['nearest','linear','mipmap'],'mipmap');
var wrap=op.inValueSelect("wrap",['repeat','mirrored repeat','clamp to edge'],"clamp to edge");
var flip=op.addInPort(new Port(op,"flip",OP_PORT_TYPE_VALUE,{display:'bool'}));
var unpackAlpha=op.addInPort(new Port(op,"unpackPreMultipliedAlpha",OP_PORT_TYPE_VALUE,{display:'bool'}));


var textureOut=op.outTexture("texture");
var width=op.addOutPort(new Port(op,"width",OP_PORT_TYPE_VALUE));
var height=op.addOutPort(new Port(op,"height",OP_PORT_TYPE_VALUE));
var loading=op.addOutPort(new Port(op,"loading",OP_PORT_TYPE_VALUE));
var ratio=op.outValue("Aspect Ratio");

flip.set(false);
unpackAlpha.set(false);
unpackAlpha.hidePort();

var cgl=op.patch.cgl;
var cgl_filter=0;
var cgl_wrap=0;

flip.onChange=function(){reloadSoon();};
filename.onChange=reloadSoon;

tfilter.onChange=onFilterChange;
wrap.onChange=onWrapChange;
unpackAlpha.onChange=function(){ reloadSoon(); };

var timedLoader=0;

tfilter.set('linear');
wrap.set('repeat');

textureOut.set(CGL.Texture.getEmptyTexture(cgl));

var setTempTexture=function()
{
    var t=CGL.Texture.getTempTexture(cgl);
    textureOut.set(t);
};

// var loadingId=null;
var tex=null;
function reloadSoon(nocache)
{
    // if(!loadingId)loadingId=cgl.patch.loading.start('textureOp',filename.get());
    
    // if(timedLoader!=0)
    // {
    //     console.log('tex load canceled...');
    // }
    clearTimeout(timedLoader);
    timedLoader=setTimeout(function()
    {
        // console.log('tex load yay...');
        realReload(nocache);
    },30);
}

function realReload(nocache)
{
    // if(!loadingId)loadingId=cgl.patch.loading.start('textureOp',filename.get());
    
    var url=op.patch.getFilePath(String(filename.get()));
    if(nocache)url+='?rnd='+CABLES.generateUUID();

    if((filename.get() && filename.get().length>1))
    {
        loading.set(true);

        if(tex)tex.delete();
        tex=CGL.Texture.load(cgl,url,
            function(err)
            {
                if(err)
                {
                    setTempTexture();
                    op.uiAttr({'error':'could not load texture "'+filename.get()+'"'});
                    // cgl.patch.loading.finished(loadingId);
                    return;
                }
                else op.uiAttr({'error':null});
                textureOut.set(tex);
                width.set(tex.width);
                height.set(tex.height);
                ratio.set(tex.width/tex.height);

                if(!tex.isPowerOfTwo()) op.uiAttr(
                    {
                        hint:'texture dimensions not power of two! - texture filtering will not work.',
                        warning:null
                    });
                    else op.uiAttr(
                        {
                            hint:null,
                            warning:null
                        });

                textureOut.set(null);
                textureOut.set(tex);
                // tex.printInfo();

            },{
                wrap:cgl_wrap,
                flip:flip.get(),
                unpackAlpha:unpackAlpha.get(),
                filter:cgl_filter
            });

        textureOut.set(null);
        textureOut.set(tex);

        if(!textureOut.get() && nocache)
        {
        }
        
        // cgl.patch.loading.finished(loadingId);
    }
    else
    {
        // cgl.patch.loading.finished(loadingId);
        setTempTexture();
    }
}


function onFilterChange()
{
    if(tfilter.get()=='nearest') cgl_filter=CGL.Texture.FILTER_NEAREST;
    if(tfilter.get()=='linear') cgl_filter=CGL.Texture.FILTER_LINEAR;
    if(tfilter.get()=='mipmap') cgl_filter=CGL.Texture.FILTER_MIPMAP;

    reloadSoon();
}

function onWrapChange()
{
    if(wrap.get()=='repeat') cgl_wrap=CGL.Texture.WRAP_REPEAT;
    if(wrap.get()=='mirrored repeat') cgl_wrap=CGL.Texture.WRAP_MIRRORED_REPEAT;
    if(wrap.get()=='clamp to edge') cgl_wrap=CGL.Texture.WRAP_CLAMP_TO_EDGE;

    reloadSoon();
}

op.onFileChanged=function(fn)
{
    if(filename.get() && filename.get().indexOf(fn)>-1)
    {
        textureOut.set(null);
        textureOut.set(CGL.Texture.getTempTexture(cgl));

        realReload(true);
    }
};







};

Ops.Gl.Texture.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Phong.LambertMaterial
// 
// **************************************************************

Ops.Gl.Phong.LambertMaterial = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["lambert_frag"]="{{MODULES_HEAD}}\n\nIN vec3 norm;\nIN vec4 modelPos;\nIN mat3 normalMatrix;\nIN vec2 texCoord;\n\nIN vec3 mvNormal;\nIN vec3 mvTangent;\nIN vec3 mvBiTangent;\n\n\nUNI float r,g,b,a;\n\n\nstruct Light {\n  vec3 pos;\n  vec3 color;\n  vec3 ambient;\n  vec3 specular;\n  float falloff;\n  float radius;\n  float mul;\n};\n\nUNI Light lights[NUM_LIGHTS];\n\n\n\nfloat getfallOff(Light light,float distLight)\n{\n    float denom = distLight / light.radius + 1.0;\n    float attenuation = 1.0 / (denom*denom);\n    float t = (attenuation - 0.1) / (1.0 - 0.1);\n\n    t=t* (20.0*light.falloff*20.0*light.falloff);\n\n    return min(1.0,max(t, 0.0));\n}\n\nvoid main()\n{\n    {{MODULE_BEGIN_FRAG}}\n\n    vec4 col=vec4(0.0);\n    vec3 normal = normalize(normalMatrix*norm);\n  \n    #ifdef HAS_TEXTURE_NORMAL\n        normal = texture2D(texNormal, texCoord).rgb * 2.0 - 1.0;\n        normal=normalize(normalMatrix*normal);\n    #endif\n\n  \n    for(int l=0;l<NUM_LIGHTS;l++)\n    {\n        Light light=lights[l];\n\n        vec3 lightModelDiff=light.pos - modelPos.xyz;\n        vec3 lightDir = normalize(lightModelDiff);\n        vec3 lambert = vec3( max(dot(lightDir,normal), 0.0) );\n\n        vec3 newColor=lambert * light.color.rgb * light.mul;\n        newColor*=getfallOff(light, length(lightModelDiff));\n\n        col.rgb+=vec3(light.ambient);\n        col.rgb+=newColor;\n    }\n    \n    col.rgb*=vec3(r,g,b);\n    col.a=a;\n    \n    {{MODULE_COLOR}}\n    \n    outColor=col;\n}\n";
attachments["lambert_vert"]="\nIN vec3 vPosition;\nIN vec3 attrVertNormal;\nIN vec2 attrTexCoord;\n\nIN vec3 attrTangent;\nIN vec3 attrBiTangent;\nIN float attrVertIndex;\n\nUNI mat4 projMatrix;\nUNI mat4 modelMatrix;\nUNI mat4 viewMatrix;\n\nOUT vec3 norm;\nOUT mat4 mvMatrix;\nOUT mat3 normalMatrix;\nOUT vec4 modelPos;\nOUT vec2 texCoord;\n{{MODULES_HEAD}}\n\nmat3 transposeMat3(mat3 m)\n{\n    return mat3(m[0][0], m[1][0], m[2][0],\n        m[0][1], m[1][1], m[2][1],\n        m[0][2], m[1][2], m[2][2]);\n}\n\nmat3 inverseMat3(mat3 m)\n{\n    float a00 = m[0][0], a01 = m[0][1], a02 = m[0][2];\n    float a10 = m[1][0], a11 = m[1][1], a12 = m[1][2];\n    float a20 = m[2][0], a21 = m[2][1], a22 = m[2][2];\n    \n    float b01 = a22 * a11 - a12 * a21;\n    float b11 = -a22 * a10 + a12 * a20;\n    float b21 = a21 * a10 - a11 * a20;\n    \n    float det = a00 * b01 + a01 * b11 + a02 * b21;\n    \n    return mat3(b01, (-a22 * a01 + a02 * a21), (a12 * a01 - a02 * a11),\n        b11, (a22 * a00 - a02 * a20), (-a12 * a00 + a02 * a10),\n        b21, (-a21 * a00 + a01 * a20), (a11 * a00 - a01 * a10)) / det;\n}\n\nvoid main()\n{\n    vec4 pos = vec4( vPosition, 1. );\n    mat4 mMatrix=modelMatrix;\n\n    texCoord=attrTexCoord;\n\n    norm=attrVertNormal;\n\n    {{MODULE_VERTEX_POSITION}}\n\n    normalMatrix = transposeMat3(inverseMat3(mat3(mMatrix)));\n    mvMatrix=viewMatrix*mMatrix;\n    modelPos=mMatrix*pos;\n\n    gl_Position = projMatrix * mvMatrix * pos;\n}\n";
var execute=this.addInPort(new Port(this,"execute",OP_PORT_TYPE_FUNCTION) );
var inSpecular=op.inValueSlider("Specular",0.5);

// diffuse color
var r=this.addInPort(new Port(this,"diffuse r",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true' }));
var g=this.addInPort(new Port(this,"diffuse g",OP_PORT_TYPE_VALUE,{ display:'range' }));
var b=this.addInPort(new Port(this,"diffuse b",OP_PORT_TYPE_VALUE,{ display:'range' }));
var a=this.addInPort(new Port(this,"diffuse a",OP_PORT_TYPE_VALUE,{ display:'range' }));

var next=this.addOutPort(new Port(this,"next",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl,"LambertMaterial");
// shader.setModules(['MODULE_VERTEX_POSITION','MODULE_COLOR','MODULE_NORMAL','MODULE_BEGIN_FRAG']);

r.uniform=new CGL.Uniform(shader,'f','r',r);
g.uniform=new CGL.Uniform(shader,'f','g',g);
b.uniform=new CGL.Uniform(shader,'f','b',b);
a.uniform=new CGL.Uniform(shader,'f','a',a);

r.set(Math.random());
g.set(Math.random());
b.set(Math.random());
a.set(1.0);

var outShader=op.outObject("Shader");
outShader.set(shader);



var MAX_LIGHTS=16;
var lights=[];
for(var i=0;i<MAX_LIGHTS;i++)
{
    var count=i;
    lights[count]={};
    lights[count].pos=new CGL.Uniform(shader,'3f','lights['+count+'].pos',[0,11,0]);
    lights[count].target=new CGL.Uniform(shader,'3f','lights['+count+'].target',[0,0,0]);
    lights[count].color=new CGL.Uniform(shader,'3f','lights['+count+'].color',[1,1,1]);
    lights[count].attenuation=new CGL.Uniform(shader,'f','lights['+count+'].attenuation',0.1);
    lights[count].type=new CGL.Uniform(shader,'f','lights['+count+'].type',0);
    lights[count].cone=new CGL.Uniform(shader,'f','lights['+count+'].cone',0.8);
    lights[count].mul=new CGL.Uniform(shader,'f','lights['+count+'].mul',1);

    lights[count].ambient=new CGL.Uniform(shader,'3f','lights['+count+'].ambient',1);
    lights[count].fallOff=new CGL.Uniform(shader,'f','lights['+count+'].falloff',0);
    lights[count].radius=new CGL.Uniform(shader,'f','lights['+count+'].radius',10);
}


shader.setSource(attachments.lambert_vert,attachments.lambert_frag);

var numLights=-1;
var updateLights=function()
{
    var count=0;
    var i=0;
    var num=0;
    if(!cgl.frameStore.phong || !cgl.frameStore.phong.lights)
    {
        num=0;
    }
    else
    {
        for(i in cgl.frameStore.phong.lights)
        {
            num++;
        }
    }

    if(num!=numLights)
    {
        numLights=num;
        shader.define('NUM_LIGHTS',''+Math.max(numLights,1));
    }

    if(!cgl.frameStore.phong || !cgl.frameStore.phong.lights)
    {
        lights[count].pos.setValue([5,5,5]);
        lights[count].color.setValue([1,1,1]);
        lights[count].ambient.setValue([0.1,0.1,0.1]);
        lights[count].mul.setValue(1);
        lights[count].fallOff.setValue(0.5);


    }
    else
    {
        count=0;
        if(shader)
            for(i in cgl.frameStore.phong.lights)
            {
                lights[count].pos.setValue(cgl.frameStore.phong.lights[i].pos);
                // if(cgl.frameStore.phong.lights[i].changed)
                {
                    cgl.frameStore.phong.lights[i].changed=false;
                    if(cgl.frameStore.phong.lights[i].target) lights[count].target.setValue(cgl.frameStore.phong.lights[i].target);

                    lights[count].fallOff.setValue(cgl.frameStore.phong.lights[i].fallOff);
                    lights[count].radius.setValue(cgl.frameStore.phong.lights[i].radius);

                    lights[count].color.setValue(cgl.frameStore.phong.lights[i].color);
                    lights[count].ambient.setValue(cgl.frameStore.phong.lights[i].ambient);
                    // lights[count].specular.setValue(cgl.frameStore.phong.lights[i].specular);
                    lights[count].attenuation.setValue(cgl.frameStore.phong.lights[i].attenuation);
                    lights[count].type.setValue(cgl.frameStore.phong.lights[i].type);
                    if(cgl.frameStore.phong.lights[i].cone) lights[count].cone.setValue(cgl.frameStore.phong.lights[i].cone);
                    if(cgl.frameStore.phong.lights[i].depthTex) lights[count].texDepthTex=cgl.frameStore.phong.lights[i].depthTex;

                    lights[count].mul.setValue(cgl.frameStore.phong.lights[i].mul||1);
                }

                count++;
            }
    }
};

function updateSpecular()
{
    if(inSpecular.get()==1)inSpecular.uniform.setValue(99999);
        else inSpecular.uniform.setValue(Math.exp(inSpecular.get()*8,2));
}


execute.onTriggered=function()
{
    if(!shader)return;

    cgl.setShader(shader);
    updateLights();
    // shader.bindTextures();
    next.trigger();
    cgl.setPreviousShader();
};




};

Ops.Gl.Phong.LambertMaterial.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Depth2
// 
// **************************************************************

Ops.Gl.Depth2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
// todo:rename to depthtest

var render=op.inFunction("Render");
var enable=op.inValueBool("Enable depth testing",true);
var meth=op.inValueSelect("Depth Test Method",['never','always','less','less or equal','greater', 'greater or equal','equal','not equal'],'less or equal');
var write=op.inValueBool("Write to depth buffer",true);
var trigger=op.outFunction("Next");

var cgl=op.patch.cgl;
var compareMethod=cgl.gl.LEQUAL;

meth.onChange=updateFunc;

function updateFunc()
{
    if(meth.get()=='never') compareMethod=cgl.gl.NEVER;
    else if(meth.get()=='always') compareMethod=cgl.gl.ALWAYS;
    else if(meth.get()=='less') compareMethod=cgl.gl.LESS;
    else if(meth.get()=='less or equal') compareMethod=cgl.gl.LEQUAL;
    else if(meth.get()=='greater') compareMethod=cgl.gl.GREATER;
    else if(meth.get()=='greater or equal') compareMethod=cgl.gl.GEQUAL;
    else if(meth.get()=='equal') compareMethod=cgl.gl.EQUAL;
    else if(meth.get()=='not equal') compareMethod=cgl.gl.NOTEQUAL;
}

render.onTriggered=function()
{
    cgl.pushDepthTest(enable.get());
    cgl.pushDepthWrite(write.get());
    cgl.pushDepthFunc(compareMethod);

    trigger.trigger();

    cgl.popDepthTest();
    cgl.popDepthWrite();
    cgl.popDepthFunc();
};

};

Ops.Gl.Depth2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ClearDepth
// 
// **************************************************************

Ops.Gl.ClearDepth = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;

render.onTriggered=function()
{
    cgl.gl.clear(cgl.gl.DEPTH_BUFFER_BIT);
    trigger.trigger();
};




};

Ops.Gl.ClearDepth.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.ChromaticAberration
// 
// **************************************************************

Ops.Gl.TextureEffects.ChromaticAberration = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["chromatic_frag"]="\nIN vec2 texCoord;\nUNI sampler2D tex;\nUNI float pixel;\nUNI float onePixel;\nUNI float amount;\nUNI float lensDistort;\n\n#ifdef MASK\nUNI sampler2D texMask;\n#endif\n\nvoid main()\n{\n\n   vec4 col=texture2D(tex,texCoord);\n\n   vec2 tc=texCoord;;\n   float pix = pixel;\n   if(lensDistort>0.0)\n   {\n       float dist = distance(texCoord, vec2(0.5,0.5));\n       tc-=0.5;\n       tc *=smoothstep(-0.9,1.0*lensDistort,1.0-dist);\n       tc+=0.5;\n   }\n\n    #ifdef MASK\n        vec4 m=texture2D(texMask,texCoord);\n        pix*=m.r*m.a;\n    #endif\n\n    #ifdef SMOOTH\n        float samples=round(pix/onePixel/4.0+1.0);\n        col.r=0.0;\n        col.b=0.0;\n        // float b=0.0;\n        for(float off=0.0;off<samples;off++)\n        {\n            float diff=(pix/samples)*off;\n            col.r+=texture2D(tex,vec2(tc.x+diff,tc.y)).r/samples;\n            col.b+=texture2D(tex,vec2(tc.x-diff,tc.y)).b/samples;\n        }\n    #endif\n\n    #ifndef SMOOTH\n        col.r=texture2D(tex,vec2(tc.x+pix,tc.y)).r;\n        col.b=texture2D(tex,vec2(tc.x-pix,tc.y)).b;\n    #endif\n\n   outColor = col;\n\n}\n";
op.name="ChromaticAberration";

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var pixel=op.inValue("Pixel",5);
var lensDistort=op.inValueSlider("Lens Distort",0);

var textureMask=op.inTexture("Mask");

var doSmooth=op.inValueBool("Smooth",false);

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);


doSmooth.onChange=function()
{
    if(doSmooth.get())shader.define("SMOOTH");
        else shader.removeDefine("SMOOTH");
};

textureMask.onChange=function()
{
    if(textureMask.get())shader.define("MASK");
        else shader.removeDefine("MASK");
};


shader.setSource(shader.getDefaultVertexShader(),attachments.chromatic_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var uniPixel=new CGL.Uniform(shader,'f','pixel',0);
var uniOnePixel=new CGL.Uniform(shader,'f','onePixel',0);
var unitexMask=new CGL.Uniform(shader,'t','texMask',1);

var unilensDistort=new CGL.Uniform(shader,'f','lensDistort',lensDistort);

render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;

    var texture=cgl.currentTextureEffect.getCurrentSourceTexture();

    uniPixel.setValue(pixel.get()*(1/texture.width));
    uniOnePixel.setValue(1/texture.width);

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, texture.tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, texture.tex );
    
    if(textureMask.get())
    {
        /* --- */cgl.setTexture(1, textureMask.get().tex );
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, textureMask.get().tex );
        
    }
    
    

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.ChromaticAberration.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Levels
// 
// **************************************************************

Ops.Gl.TextureEffects.Levels = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["levels_frag"]="IN vec2 texCoord;\nUNI sampler2D tex;\nUNI float inMin;\nUNI float inMax;\nUNI float midPoint;\nUNI float outMax;\nUNI float outMin;\n\nvoid main()\n{\n   vec4 base=texture2D(tex,texCoord);\n\n   vec4 inputRange = min(max(base - vec4(inMin), vec4(0.0)) / (vec4(inMax) - vec4(inMin)), vec4(outMax));\n   inputRange = pow(inputRange, vec4(1.0 / (1.5 - midPoint)));\n\n   gl_FragColor = mix(vec4(outMin), vec4(1.0), inputRange);\n\n}";
op.name="Levels";

var render=op.addInPort(new Port(op,"Render",OP_PORT_TYPE_FUNCTION));

var inMin=op.inValueSlider("In Min",0);
var inMid=op.inValueSlider("Midpoint",0.5);
var inMax=op.inValueSlider("In Max",1);

var outMin=op.inValueSlider("Out Min",0);
var outMax=op.inValueSlider("Out Max",1);

var trigger=op.addOutPort(new Port(op,"Next",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);

var uniInMin=new CGL.Uniform(shader,'f','inMin',inMin);
var uniInMid=new CGL.Uniform(shader,'f','midPoint',inMid);
var uniInMax=new CGL.Uniform(shader,'f','inMax',inMax);

var uniOutMin=new CGL.Uniform(shader,'f','outMin',outMin);
var uniOutMax=new CGL.Uniform(shader,'f','outMax',outMax);




shader.setSource(shader.getDefaultVertexShader(),attachments.levels_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Levels.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Gradient
// 
// **************************************************************

Ops.Gl.TextureEffects.Gradient = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["gradient_frag"]="IN vec2 texCoord;\nUNI float amount;\nUNI float pos;\nUNI float width;\n\nUNI vec3 colA;\nUNI vec3 colB;\nUNI vec3 colC;\nUNI sampler2D tex;\n\n{{BLENDCODE}}\n\nvoid main()\n{\n    vec4 base=texture2D(tex,texCoord);\n    vec4 col;\n\n    float ax=texCoord.x;\n\n    #ifdef GRAD_Y\n        ax=texCoord.y;\n    #endif\n    #ifdef GRAD_XY\n        ax=(texCoord.x+texCoord.y)/2.0;\n    #endif\n    #ifdef GRAD_RADIAL\n        ax=distance(texCoord,vec2(0.5,0.5))*2.0;\n    #endif\n\n    ax=((ax-0.5)*width)+0.5;\n\n\n    #ifndef GRAD_SMOOTHSTEP\n        if(ax<=pos) col = vec4(mix(colA, colB, ax*1.0/pos),1.0);\n            else col = vec4(mix(colB, colC, min(1.0,(ax-pos)*1.0/(1.0-pos))),1.0);\n    #endif\n\n    #ifdef GRAD_SMOOTHSTEP\n        if(ax<=pos) col = vec4(mix(colA, colB, smoothstep(0.0,1.0,ax*1.0/pos)),1.0);\n            else col = vec4(mix(colB, colC, smoothstep(0.0,1.0,min(1.0,(ax-pos)*1.0/(1.0-pos)))),1.0);\n        // ax=smoothstep(0.0,1.0,ax);\n    #endif\n\n\n\n\n\n   col=vec4( _blend(base.rgb,col.rgb) ,1.0);\n   col=vec4( mix( col.rgb, base.rgb ,1.0-base.a*amount),1.0);\n\n   gl_FragColor=col;\n\n}\n";

var render=op.inFunction("Render");

var blendMode=CGL.TextureEffect.AddBlendSelect(op,"Blend Mode","normal");
var amount=op.inValueSlider("Amount",1);
var width=op.inValue("Width",1);

var gType=op.inValueSelect("Type",['X','Y','XY','Radial'],"X");

var pos1=op.inValueSlider("Pos",0.5);

var smoothStep=op.inValueBool("Smoothstep",true);
smoothStep.onChange=updateSmoothstep;


var r=op.addInPort(new Port(op,"r1",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true' }));
var g=op.addInPort(new Port(op,"g1",OP_PORT_TYPE_VALUE,{ display:'range' }));
var b=op.addInPort(new Port(op,"b1",OP_PORT_TYPE_VALUE,{ display:'range' }));

var r2=op.addInPort(new Port(op,"r2",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true' }));
var g2=op.addInPort(new Port(op,"g2",OP_PORT_TYPE_VALUE,{ display:'range' }));
var b2=op.addInPort(new Port(op,"b2",OP_PORT_TYPE_VALUE,{ display:'range' }));

var r3=op.addInPort(new Port(op,"r3",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true' }));
var g3=op.addInPort(new Port(op,"g3",OP_PORT_TYPE_VALUE,{ display:'range' }));
var b3=op.addInPort(new Port(op,"b3",OP_PORT_TYPE_VALUE,{ display:'range' }));


var next=op.outFunction("Next");


var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);
var srcFrag=attachments.gradient_frag.replace('{{BLENDCODE}}',CGL.TextureEffect.getBlendCode());
shader.setSource(shader.getDefaultVertexShader(),srcFrag );

updateSmoothstep();

var amountUniform=new CGL.Uniform(shader,'f','amount',amount);
var uniPos=new CGL.Uniform(shader,'f','pos',pos1);
var uniWidth=new CGL.Uniform(shader,'f','width',width);

var textureUniform=new CGL.Uniform(shader,'t','tex',0);

function updateSmoothstep()
{
    if(smoothStep.get()) shader.define('GRAD_SMOOTHSTEP');
        else shader.removeDefine('GRAD_SMOOTHSTEP');
}

gType.onChange=function()
{
    shader.removeDefine('GRAD_X');
    shader.removeDefine('GRAD_Y');
    shader.removeDefine('GRAD_XY');
    shader.removeDefine('GRAD_RADIAL');
    
    if(gType.get()=='XY')shader.define('GRAD_XY');
    if(gType.get()=='X')shader.define('GRAD_X');
    if(gType.get()=='Y')shader.define('GRAD_Y');
    if(gType.get()=='Radial')shader.define('GRAD_RADIAL');
    
};

blendMode.onValueChanged=function()
{
    CGL.TextureEffect.onChangeBlendSelect(shader,blendMode.get());
};

r.onChange=g.onChange=b.onChange=function()
{
    var colA=[r.get(),g.get(),b.get()];
    if(!r.uniform) r.uniform=new CGL.Uniform(shader,'3f','colA',colA);
    else r.uniform.setValue(colA);
};

r2.onChange=g2.onChange=b2.onChange=function()
{
    var colB=[r2.get(),g2.get(),b2.get()];
    if(!r2.uniform) r2.uniform=new CGL.Uniform(shader,'3f','colB',colB);
    else r2.uniform.setValue(colB);
};

r3.onChange=g3.onChange=b3.onChange=function()
{
    var colC=[r3.get(),g3.get(),b3.get()];
    if(!r3.uniform) r3.uniform=new CGL.Uniform(shader,'3f','colC',colC);
    else r3.uniform.setValue(colC);
};

render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    next.trigger();
};


};

Ops.Gl.TextureEffects.Gradient.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Interlace
// 
// **************************************************************

Ops.Gl.TextureEffects.Interlace = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["interlace_frag"]="\n#ifdef HAS_TEXTURES\n  IN vec2 texCoord;\n  UNI sampler2D tex;\n#endif\nUNI float amount;\nUNI float lum;\nUNI float add;\nUNI float lineSize;\nUNI float scroll;\nUNI float displace;\n\n\nvoid main()\n{\n   vec4 col=vec4(1.0,0.0,0.0,1.0);\n\n   col=texture2D(tex,texCoord);\n    // .endl()+'   col=clamp(col,0.0,1.0);'\n   if( mod(gl_FragCoord.y+scroll,lineSize)>=lineSize*0.5)\n   {\n       col=texture2D(tex,vec2(texCoord.x+displace*0.05,texCoord.y));\n       float gray = vec3(dot(vec3(0.2126,0.7152,0.0722), col.rgb)).r;\n       col.rgb=col.rgb*(1.0-amount) + (col.rgb*gray*gray*lum)*amount;\n   }\n   else col+=add;\n\n\n   gl_FragColor = col;\n}";
op.name="Interlace";

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var amount=op.inValueSlider("amount",0.5);//op.addInPort(new Port(op,"amount",OP_PORT_TYPE_VALUE,{display:'range'}));
var lum=op.inValueSlider("Lumi Scale",0.9);
var lineSize=op.inValue("Line Size",4);
var displace=op.inValueSlider("Displacement",0);

var add=op.inValue("Add",0.02);
var inScroll=op.inValue("scroll",0);

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);
//op.onLoaded=shader.compile;



shader.setSource(shader.getDefaultVertexShader(),attachments.interlace_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var uniAmount=new CGL.Uniform(shader,'f','amount',amount);

var uniLum=new CGL.Uniform(shader,'f','lum',lum);
var uniLineSize=new CGL.Uniform(shader,'f','lineSize',lineSize);
var uniAdd=new CGL.Uniform(shader,'f','add',add);
var uniDisplace=new CGL.Uniform(shader,'f','displace',displace);
var uniScroll=new CGL.Uniform(shader,'f','scroll',inScroll);


render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Interlace.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Grid
// 
// **************************************************************

Ops.Gl.Meshes.Grid = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.inFunction("Render");
var inNum=op.inValue("Num",10);
var inSpacing=op.inValue("Spacing",1);
var next=op.outFunction("Next");


var cgl=op.patch.cgl;
var mesh=null;

inNum.onChange=init;
inSpacing.onChange=init;

function init()
{
    var geomVertical=new CGL.Geometry();

    var space=inSpacing.get();
    var num=Math.floor(inNum.get());

    var l=space*num/2;

    for(var i=-num/2;i<num/2+1;i++)
    {
        geomVertical.vertices.push(-l);
        geomVertical.vertices.push(i*space);
        geomVertical.vertices.push(0);
    
        geomVertical.vertices.push(l);
        geomVertical.vertices.push(i*space);
        geomVertical.vertices.push(0);

        geomVertical.vertices.push(i*space);
        geomVertical.vertices.push(-l);
        geomVertical.vertices.push(0);
    
        geomVertical.vertices.push(i*space);
        geomVertical.vertices.push(l);
        geomVertical.vertices.push(0);
    }

    if(!mesh) mesh=new CGL.Mesh(cgl,geomVertical);
        else mesh.setGeom(geomVertical);
}


render.onTriggered=function()
{
    if(!mesh)init();
    var shader=cgl.getShader();
    if(!shader)return;

    var oldPrim=shader.glPrimitive;

    shader.glPrimitive=cgl.gl.LINES;

    mesh.render(shader);
    
    shader.glPrimitive=oldPrim;
    
    next.trigger();
};



};

Ops.Gl.Meshes.Grid.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.CablesLogo
// 
// **************************************************************

Ops.Gl.Meshes.CablesLogo = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="CablesLogo";

var render=op.inFunction('render');

var scale=op.inValue("Scale",1);

var trigger=op.outFunction('trigger');
var geomOut=op.outObject('Geometry');

var verts=[14.4166,163.754,0,18.4809,210.763,0,54.9491,204.346,0,42.7849,158.762,0,22.5453,257.772,0,67.1133,249.93,0,109.532,234.597,0,89.7001,191.777,0,69.8685,148.956,0,121.577,173.384,0,94.8187,134.563,0,148.336,212.204,0,185.332,186.28,0,150.011,149.481,0,116.915,116.086,0,174.431,120.383,0,135.438,94.0268,0,213.424,146.739,0,241.515,107.197,0,193.515,87.7918,0,149.882,69.1061,0,205.939,53.4095,0,159.743,42.0425,0,252.134,64.7765,0,259.112,21.5601,0,211.953,17.6223,0,164.793,13.6845,0,211.807,-19.1835,0,164.803,-15.1197,0,258.81,-23.2474,0,251.027,-67.8259,0,205.438,-55.6602,0,159.849,-43.4946,0,192.786,-90.46,0,150.007,-70.5651,0,235.565,-110.355,0,213.005,-149.583,0,174.326,-122.559,0,135.648,-95.5346,0,150.535,-150.933,0,117.14,-117.606,0,183.929,-184.259,0,149.313,-213.406,0,122.21,-174.782,0,95.1064,-136.159,0,90.1486,-193.307,0,70.1663,-150.569,0,110.131,-236.046,0,67.6335,-251.595,0,55.3747,-206.031,0,43.116,-160.467,0,18.9111,-212.474,0,14.7512,-165.479,0,23.071,-259.469,0,-22.1816,-259.545,0,-18.1173,-212.536,0,-14.0529,-165.527,0,-54.5854,-206.119,0,-42.4212,-160.536,0,-66.7496,-251.703,0,-108.973,-236.672,0,-89.239,-193.701,0,-69.5049,-150.73,0,-120.824,-175.76,0,-94.4551,-136.336,0,-147.193,-215.185,0,-183.41,-190.469,0,-148.964,-152.339,0,-116.552,-117.859,0,-173.286,-123.476,0,-135.074,-95.8,0,-211.498,-151.152,0,-239.586,-111.835,0,-192.467,-90.79,0,-149.518,-70.8793,0,-205.184,-55.8988,0,-159.38,-43.8157,0,-250.988,-67.9818,0,-258.553,-23.6913,0,-211.491,-19.5745,0,-164.429,-15.4577,0,-211.443,17.4104,0,-164.439,13.3465,0,-258.446,21.4742,0,-250.664,66.0527,0,-205.075,53.8871,0,-159.486,41.7214,0,-192.423,88.6869,0,-149.644,68.7919,0,-235.201,108.582,0,-212.641,147.81,0,-173.962,120.786,0,-135.284,93.7614,0,-150.171,149.16,0,-116.777,115.833,0,-183.566,182.486,0,-148.949,211.632,0,-121.846,173.009,0,-94.7427,134.385,0,-89.7849,191.534,0,-69.8026,148.796,0,-109.767,234.272,0,-67.2698,249.822,0,-55.011,204.258,0,-42.7523,158.694,0,-18.5474,210.7,0,-14.3875,163.705,0,-22.7074,257.695,0,22.5453,257.772,0,18.4809,210.763,0,14.4166,163.754,0,256.822,264.163,0,296.476,236.296,0,276.952,212.586,0,276.952,212.586,0,240.805,244.919,0,256.822,264.163,0,336.13,208.429,0,313.099,180.253,0,276.952,212.586,0,276.952,212.586,0,296.476,236.296,0,336.13,208.429,0,288.309,152.686,0,255.898,189.327,0,313.099,180.253,0,223.486,225.968,0,240.805,244.919,0,285.635,306.513,0,328.243,283.496,0,313.087,259.335,0,313.087,259.335,0,271.643,284.706,0,285.635,306.513,0,370.852,260.478,0,354.53,233.964,0,313.087,259.335,0,313.087,259.335,0,328.243,283.496,0,370.852,260.478,0,336.13,208.429,0,296.476,236.296,0,354.53,233.964,0,336.13,208.429,0,256.822,264.163,0,271.643,284.706,0,296.476,236.296,0,256.822,264.163,0,311.134,352.806,0,357.268,338.992,0,343.405,310.57,0,343.405,310.57,0,299.162,329.55,0,311.134,352.806,0,403.403,325.177,0,387.649,291.589,0,343.405,310.57,0,343.405,310.57,0,357.268,338.992,0,403.403,325.177,0,370.852,260.478,0,328.243,283.496,0,387.649,291.589,0,370.852,260.478,0,285.635,306.513,0,299.162,329.55,0,328.243,283.496,0,285.635,306.513,0,328.173,398.813,0,377.479,395.259,0,368.527,367.195,0,368.527,367.195,0,320.46,375.27,0,328.173,398.813,0,426.784,391.705,0,416.594,359.12,0,368.527,367.195,0,368.527,367.195,0,377.479,395.259,0,426.784,391.705,0,403.403,325.177,0,357.268,338.992,0,416.594,359.12,0,403.403,325.177,0,311.134,352.806,0,320.46,375.27,0,357.268,338.992,0,311.134,352.806,0,341.169,453.101,0,389.523,451.215,0,384.421,423.263,0,384.421,423.263,0,335.304,425.309,0,341.169,453.101,0,437.877,449.328,0,433.538,421.218,0,384.421,423.263,0,384.421,423.263,0,389.523,451.215,0,437.877,449.328,0,426.784,391.705,0,377.479,395.259,0,433.538,421.218,0,426.784,391.705,0,328.173,398.813,0,335.304,425.309,0,377.479,395.259,0,328.173,398.813,0,347.768,502.233,0,394.824,501.411,0,392.954,479.119,0,392.954,479.119,0,345.086,480.533,0,347.768,502.233,0,441.88,500.589,0,440.822,477.705,0,392.954,479.119,0,392.954,479.119,0,394.824,501.411,0,441.88,500.589,0,437.877,449.328,0,389.523,451.215,0,440.822,477.705,0,437.877,449.328,0,341.169,453.101,0,345.086,480.533,0,389.523,451.215,0,341.169,453.101,0,352.384,517.252,0,394.985,517.267,0,395.241,512.525,0,395.241,512.525,0,349.927,512.83,0,352.384,517.252,0,437.585,517.282,0,440.555,512.22,0,395.241,512.525,0,395.241,512.525,0,394.985,517.267,0,437.585,517.282,0,441.88,500.589,0,394.824,501.411,0,440.555,512.22,0,441.88,500.589,0,347.768,502.233,0,349.927,512.83,0,394.824,501.411,0,347.768,502.233,0,360.098,522.975,0,394.737,522.834,0,394.834,520.442,0,394.834,520.442,0,355.961,520.425,0,360.098,522.975,0,429.375,522.693,0,433.707,520.459,0,394.834,520.442,0,394.834,520.442,0,394.737,522.834,0,429.375,522.693,0,437.585,517.282,0,394.985,517.267,0,433.707,520.459,0,437.585,517.282,0,355.961,520.425,0,394.834,520.442,0,394.985,517.267,0,352.384,517.252,0,185.332,186.28,0,204.761,206.303,0,234.697,167.64,0,213.424,146.739,0,264.633,128.977,0,241.515,107.197,0,-255.099,-268.612,0,-294.753,-240.745,0,-275.229,-217.035,0,-275.229,-217.035,0,-239.082,-249.368,0,-255.099,-268.612,0,-334.408,-212.878,0,-311.376,-184.702,0,-275.229,-217.035,0,-275.229,-217.035,0,-294.753,-240.745,0,-334.408,-212.878,0,-286.56,-157.158,0,-254.149,-193.771,0,-311.376,-184.702,0,-221.739,-230.385,0,-239.082,-249.368,0,-283.912,-310.962,0,-326.521,-287.944,0,-311.364,-263.784,0,-311.364,-263.784,0,-269.92,-289.154,0,-283.912,-310.962,0,-369.129,-264.927,0,-352.807,-238.413,0,-311.364,-263.784,0,-311.364,-263.784,0,-326.521,-287.944,0,-369.129,-264.927,0,-334.408,-212.878,0,-294.753,-240.745,0,-352.807,-238.413,0,-334.408,-212.878,0,-255.099,-268.612,0,-269.92,-289.154,0,-294.753,-240.745,0,-255.099,-268.612,0,-309.411,-357.255,0,-355.546,-343.44,0,-341.683,-315.018,0,-341.683,-315.018,0,-297.439,-333.999,0,-309.411,-357.255,0,-401.681,-329.626,0,-385.927,-296.038,0,-341.683,-315.018,0,-341.683,-315.018,0,-355.546,-343.44,0,-401.681,-329.626,0,-369.129,-264.927,0,-326.521,-287.944,0,-385.927,-296.038,0,-369.129,-264.927,0,-283.912,-310.962,0,-297.439,-333.999,0,-326.521,-287.944,0,-283.912,-310.962,0,-326.45,-403.262,0,-375.756,-399.708,0,-366.804,-371.644,0,-366.804,-371.644,0,-318.737,-379.718,0,-326.45,-403.262,0,-425.061,-396.154,0,-414.871,-363.569,0,-366.804,-371.644,0,-366.804,-371.644,0,-375.756,-399.708,0,-425.061,-396.154,0,-401.681,-329.626,0,-355.546,-343.44,0,-414.871,-363.569,0,-401.681,-329.626,0,-309.411,-357.255,0,-318.737,-379.718,0,-355.546,-343.44,0,-309.411,-357.255,0,-339.446,-457.55,0,-387.8,-455.663,0,-382.698,-427.712,0,-382.698,-427.712,0,-333.581,-429.758,0,-339.446,-457.55,0,-436.154,-453.776,0,-431.815,-425.666,0,-382.698,-427.712,0,-382.698,-427.712,0,-387.8,-455.663,0,-436.154,-453.776,0,-425.061,-396.154,0,-375.756,-399.708,0,-431.815,-425.666,0,-425.061,-396.154,0,-326.45,-403.262,0,-333.581,-429.758,0,-375.756,-399.708,0,-326.45,-403.262,0,-346.045,-506.682,0,-393.101,-505.86,0,-391.232,-483.568,0,-391.232,-483.568,0,-343.363,-484.982,0,-346.045,-506.682,0,-440.157,-505.037,0,-439.1,-482.153,0,-391.232,-483.568,0,-391.232,-483.568,0,-393.101,-505.86,0,-440.157,-505.037,0,-436.154,-453.776,0,-387.8,-455.663,0,-439.1,-482.153,0,-436.154,-453.776,0,-339.446,-457.55,0,-343.363,-484.982,0,-387.8,-455.663,0,-339.446,-457.55,0,-350.661,-521.701,0,-393.262,-521.716,0,-393.518,-516.974,0,-393.518,-516.974,0,-348.204,-517.279,0,-350.661,-521.701,0,-435.862,-521.731,0,-438.832,-516.669,0,-393.518,-516.974,0,-393.518,-516.974,0,-393.262,-521.716,0,-435.862,-521.731,0,-440.157,-505.037,0,-393.101,-505.86,0,-438.832,-516.669,0,-440.157,-505.037,0,-346.045,-506.682,0,-348.204,-517.279,0,-393.101,-505.86,0,-346.045,-506.682,0,-358.375,-527.424,0,-393.014,-527.283,0,-393.111,-524.891,0,-393.111,-524.891,0,-354.238,-524.874,0,-358.375,-527.424,0,-427.653,-527.142,0,-431.985,-524.908,0,-393.111,-524.891,0,-393.111,-524.891,0,-393.014,-527.283,0,-427.653,-527.142,0,-435.862,-521.731,0,-393.262,-521.716,0,-431.985,-524.908,0,-435.862,-521.731,0,-354.238,-524.874,0,-393.111,-524.891,0,-393.262,-521.716,0,-350.661,-521.701,0,-183.41,-190.469,0,-202.939,-210.621,0,-232.873,-172.071,0,-211.498,-151.152,0,-262.807,-133.52,0,-239.586,-111.835,0];
var indices=[0,1,2,2,3,0,2,1,4,4,5,2,6,7,2,2,5,6,8,3,2,2,7,8,6,11,9,9,7,6,8,7,9,9,10,8,12,13,9,9,11,12,14,10,9,9,13,14,12,17,15,15,13,12,14,13,15,15,16,14,18,19,15,15,17,18,20,16,15,15,19,20,18,23,21,21,19,18,20,19,21,21,22,20,24,25,21,21,23,24,26,22,21,21,25,26,24,29,27,27,25,24,26,25,27,27,28,26,30,31,27,27,29,30,32,28,27,27,31,32,30,35,33,33,31,30,32,31,33,33,34,32,36,37,33,33,35,36,38,34,33,33,37,38,36,41,39,39,37,36,38,37,39,39,40,38,42,43,39,39,41,42,44,40,39,39,43,44,42,47,45,45,43,42,44,43,45,45,46,44,48,49,45,45,47,48,50,46,45,45,49,50,48,53,51,51,49,48,50,49,51,51,52,50,54,55,51,51,53,54,56,52,51,51,55,56,54,59,57,57,55,54,56,55,57,57,58,56,60,61,57,57,59,60,62,58,57,57,61,62,60,65,63,63,61,60,62,61,63,63,64,62,66,67,63,63,65,66,68,64,63,63,67,68,66,71,69,69,67,66,68,67,69,69,70,68,72,73,69,69,71,72,74,70,69,69,73,74,72,77,75,75,73,72,74,73,75,75,76,74,78,79,75,75,77,78,80,76,75,75,79,80,78,83,81,81,79,78,80,79,81,81,82,80,84,85,81,81,83,84,86,82,81,81,85,86,84,89,87,87,85,84,86,85,87,87,88,86,90,91,87,87,89,90,92,88,87,87,91,92,90,95,93,93,91,90,92,91,93,93,94,92,96,97,93,93,95,96,98,94,93,93,97,98,96,101,99,99,97,96,98,97,99,99,100,98,102,103,99,99,101,102,104,100,99,99,103,104,102,107,105,105,103,102,104,103,105,105,106,104,108,109,105,105,107,108,110,106,105,105,109,110,111,112,113,123,124,113,113,125,123,126,127,113,113,124,126,123,272,270,270,124,123,126,124,270,270,269,126,268,269,270,270,271,268,273,271,270,270,272,273,114,115,116,117,118,119,120,121,122,128,129,130,130,142,143,131,132,133,140,141,131,134,135,136,136,146,147,137,138,139,144,145,137,148,149,150,150,162,163,151,152,153,160,161,151,154,155,156,156,166,167,157,158,159,164,165,157,168,169,170,170,182,183,171,172,173,180,181,171,174,175,176,176,186,187,177,178,179,184,185,177,188,189,190,190,202,203,191,192,193,200,201,191,194,195,196,196,206,207,197,198,199,204,205,197,208,209,210,210,222,223,211,212,213,220,221,211,214,215,216,216,226,227,217,218,219,224,225,217,228,229,230,228,264,250,230,242,243,248,249,250,250,262,263,231,232,233,240,241,231,234,235,236,236,246,247,237,238,239,244,245,237,251,252,253,260,261,251,254,255,256,257,258,259,265,266,267,274,275,276,286,287,276,276,288,286,289,290,276,276,287,289,286,435,433,433,287,286,289,287,433,433,432,289,431,432,433,433,434,431,436,434,433,433,435,436,277,278,279,280,281,282,283,284,285,291,292,293,293,305,306,294,295,296,303,304,294,297,298,299,299,309,310,300,301,302,307,308,300,311,312,313,313,325,326,314,315,316,323,324,314,317,318,319,319,329,330,320,321,322,327,328,320,331,332,333,333,345,346,334,335,336,343,344,334,337,338,339,339,349,350,340,341,342,347,348,340,351,352,353,353,365,366,354,355,356,363,364,354,357,358,359,359,369,370,360,361,362,367,368,360,371,372,373,373,385,386,374,375,376,383,384,374,377,378,379,379,389,390,380,381,382,387,388,380,391,392,393,391,427,413,393,405,406,411,412,413,413,425,426,394,395,396,403,404,394,397,398,399,399,409,410,400,401,402,407,408,400,414,415,416,423,424,414,417,418,419,420,421,422,428,429,430];

var geom=new CGL.Geometry();
var mesh=null;
scale.onChange=build;
build();

function build()
{
    var sc=scale.get();
    
    var vertices=verts.slice(0);
    for(var i=0;i<vertices.length;i++) vertices[i]=vertices[i]/1000*sc;

    geom.vertices=vertices;
    geom.mapTexCoords2d();
    geom.verticesIndices=indices;
    
    geomOut.set(null);
    geomOut.set(geom);
    
    if(!mesh)mesh=new CGL.Mesh(op.patch.cgl,geom);
        else mesh.setGeom(geom);
}

render.onTriggered=function()
{
    mesh.render(op.patch.cgl.getShader());
    trigger.trigger();
};



};

Ops.Gl.Meshes.CablesLogo.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Value.ValueChanged
// 
// **************************************************************

Ops.Value.ValueChanged = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var val=op.addInPort(new Port(op,"Value"));
var trigger=op.addOutPort(new Port(op,"Trigger",OP_PORT_TYPE_FUNCTION));


val.onChange=function()
{
    trigger.trigger();

};



};

Ops.Value.ValueChanged.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.TriggerOnce
// 
// **************************************************************

Ops.Trigger.TriggerOnce = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.inFunctionButton("Exec");
var reset=op.inFunctionButton("Reset");
var next=op.outFunction("Next");
var outTriggered=op.outValue("Was Triggered");
var triggered=false;

reset.onTriggered=function()
{
    triggered=false;
    outTriggered.set(triggered);
};

exe.onTriggered=function()
{
    if(triggered)return;

    triggered=true;
    next.trigger();
    outTriggered.set(triggered);

};

};

Ops.Trigger.TriggerOnce.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Denoise
// 
// **************************************************************

Ops.Gl.TextureEffects.Denoise = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["denoise_frag"]="UNI sampler2D tex;\nUNI float exponent;\nUNI float strength;\nUNI vec2 texSize;\nIN vec2 texCoord;\n\nvoid main()\n{\n    vec4 center = texture2D(tex, texCoord);\n    vec4 color = vec4(0.0);\n    float total = 0.0;\n    float pixels=4.0;\n    for (float x = -pixels; x <= pixels; x += 1.0) {\n        for (float y = -pixels; y <= pixels; y += 1.0) {\n            vec4 smpl = texture2D(tex, texCoord + vec2(x, y) / texSize);\n            float weight = 1.0 - abs(dot(smpl.rgb - center.rgb, vec3(0.25)));\n            weight = pow(weight, (1.0-exponent)*50.0);\n            color += smpl * weight;\n            total += weight;\n        }\n    }\n    outColor = color / total;\n}\n";
op.name="Denoise";

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var strength=op.inValueSlider("Exponent",0.6);

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);
var tsize=[128,128];
var srcFrag=attachments.denoise_frag;

shader.setSource(shader.getDefaultVertexShader(),srcFrag );
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

var strengthUniform=new CGL.Uniform(shader,'f','exponent',strength);
var texSizeUniform=new CGL.Uniform(shader,'2f','texSize',tsize);

render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    tsize[0]=cgl.currentTextureEffect.getCurrentSourceTexture().width;
    tsize[1]=cgl.currentTextureEffect.getCurrentSourceTexture().height;
    texSizeUniform.setValue(tsize);

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0,cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Denoise.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Compare.Between
// 
// **************************************************************

Ops.Math.Compare.Between = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const number=op.inValue("value",2);
const number1=op.inValue("number1",1);
const number2=op.inValue("number2",3);
const result=op.outValue("result");

function exec()
{
    result.set
        (
            number.get() > Math.min(number1.get() , number2.get() )  &&
            number.get() < Math.max(number1.get() , number2.get() ) 
        );
}

number1.onValueChanged=exec;
number2.onValueChanged=exec;
number.onValueChanged=exec;
exec();

};

Ops.Math.Compare.Between.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.SineAnim
// 
// **************************************************************

Ops.Anim.SineAnim = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));
var result=op.addOutPort(new Port(op,"result"));

var phase=op.addInPort(new Port(op,"phase",OP_PORT_TYPE_VALUE));
var mul=op.addInPort(new Port(op,"frequency",OP_PORT_TYPE_VALUE));
var amplitude=op.addInPort(new Port(op,"amplitude",OP_PORT_TYPE_VALUE));

mul.set(1.0);
amplitude.set(1.0);
phase.set(1);
exe.onTriggered=exec;
exec();

function exec()
{
    result.set( amplitude.get() * Math.sin( (op.patch.freeTimer.get()*mul.get()) + phase.get() ));
}



};

Ops.Anim.SineAnim.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Ui.PatchInput
// 
// **************************************************************

Ops.Ui.PatchInput = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
this.name='PatchInput';

op.getPatchOp=function()
{
    for(var i in op.patch.ops)
    {
        if(op.patch.ops[i].patchId)
        {
            if(op.patch.ops[i].patchId.get()==op.uiAttribs.subPatch)
            {
                return op.patch.ops[i];
            }
        }
    }
};


};

Ops.Ui.PatchInput.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Ui.PatchOutput
// 
// **************************************************************

Ops.Ui.PatchOutput = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

this.name='PatchOutput';



};

Ops.Ui.PatchOutput.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Ui.SubPatch
// 
// **************************************************************

Ops.Ui.SubPatch = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.dyn=op.addInPort(new Port(op,"create port",OP_PORT_TYPE_DYNAMIC));
op.dynOut=op.addOutPort(new Port(op,"create port out",OP_PORT_TYPE_DYNAMIC));

var dataStr=op.addInPort(new Port(op,"dataStr",OP_PORT_TYPE_VALUE,{ display:'readonly' }));
op.patchId=op.addInPort(new Port(op,"patchId",OP_PORT_TYPE_VALUE,{ display:'readonly' }));

var data={"ports":[],"portsOut":[]};

// Ops.Ui.Patch.maxPatchId=CABLES.generateUUID();

op.patchId.onChange=function()
{
    // console.log("subpatch changed...");
    // clean up old subpatch if empty
    var oldPatchOps=op.patch.getSubPatchOps(oldPatchId);

    // console.log("subpatch has childs ",oldPatchOps.length);

    if(oldPatchOps.length==2)
    {
        for(var i=0;i<oldPatchOps.length;i++)
        {
            // console.log("delete ",oldPatchOps[i]);
            op.patch.deleteOp(oldPatchOps[i].id);
        }
    }
    else
    {
        // console.log("old subpatch has ops.,...");
    }


};

var oldPatchId=CABLES.generateUUID();
op.patchId.set(oldPatchId);

op.onLoaded=function()
{
    // op.patchId.set(CABLES.generateUUID());
};

op.onLoadedValueSet=function()
{
    data=JSON.parse(dataStr.get());
    if(!data)
    {
        data={"ports":[],"portsOut":[]};
    }
    setupPorts();


};




function loadData()
{


}




getSubPatchInputOp();
getSubPatchOutputOp();

var dataLoaded=false;
dataStr.onChange=function()
{
    if(dataLoaded)return;

    if(!dataStr.get())return;
    try
    {
        // console.log('parse subpatch data');
        loadData();
    }
    catch(e)
    {
        // op.log('cannot load subpatch data...');
        console.log(e);
    }
};

function saveData()
{
    dataStr.set(JSON.stringify(data));
}

function addPortListener(newPort,newPortInPatch)
{
    //console.log('newPort',newPort.name);
    if(newPort.direction==PORT_DIR_IN)
    {
        if(newPort.type==OP_PORT_TYPE_FUNCTION)
        {
            newPort.onTriggered=function()
            {
                if(newPortInPatch.isLinked())
                    newPortInPatch.trigger();
            };
        }
        else
        {
            newPort.onChange=function()
            {
                newPortInPatch.set(newPort.get());
            };
        }
    }
}

function setupPorts()
{
    if(!op.patchId.get())return;
    var ports=data.ports||[];
    var portsOut=data.portsOut||[];
    var i=0;

    for(i=0;i<ports.length;i++)
    {
        if(!op.getPortByName(ports[i].name))
        {
            // console.log("ports[i].name",ports[i].name);

            var newPort=op.addInPort(new Port(op,ports[i].name,ports[i].type));
            var patchInputOp=getSubPatchInputOp();

            // console.log(patchInputOp);

            var newPortInPatch=patchInputOp.addOutPort(new Port(patchInputOp,ports[i].name,ports[i].type));

// console.log('newPortInPatch',newPortInPatch);


            newPort.ignoreValueSerialize=true;
            addPortListener(newPort,newPortInPatch);
        }
    }

    for(i=0;i<portsOut.length;i++)
    {
        if(!op.getPortByName(portsOut[i].name))
        {
            var newPortOut=op.addOutPort(new Port(op,portsOut[i].name,portsOut[i].type));
            var patchOutputOp=getSubPatchOutputOp();
            var newPortOutPatch=patchOutputOp.addInPort(new Port(patchOutputOp,portsOut[i].name,portsOut[i].type));

            newPortOut.ignoreValueSerialize=true;

            addPortListener(newPortOutPatch,newPortOut);
        }
    }

    dataLoaded=true;

}



op.dyn.onLinkChanged=function()
{
    if(op.dyn.isLinked())
    {
        var otherPort=op.dyn.links[0].getOtherPort(op.dyn);
        op.dyn.removeLinks();
        otherPort.removeLinkTo(op.dyn);
        

        var newName="in"+data.ports.length+" "+otherPort.parent.name+" "+otherPort.name;

        data.ports.push({"name":newName,"type":otherPort.type});

        setupPorts();

        var l=gui.scene().link(
            otherPort.parent,
            otherPort.getName(),
            op,
            newName
            );

        // console.log('-----+===== ',otherPort.getName(),otherPort.get() );
        // l._setValue();
        // l.setValue(otherPort.get());

        dataLoaded=true;
        saveData();
    }
    else
    {
        setTimeout(function()
        {
            op.dyn.removeLinks();
            gui.patch().removeDeadLinks();
        },100);
    }

};

op.dynOut.onLinkChanged=function()
{
    if(op.dynOut.isLinked())
    {
        var otherPort=op.dynOut.links[0].getOtherPort(op.dynOut);
        op.dynOut.removeLinks();
        otherPort.removeLinkTo(op.dynOut);
        var newName="out"+data.ports.length+" "+otherPort.parent.name+" "+otherPort.name;

        data.portsOut.push({"name":newName,"type":otherPort.type});

        setupPorts();

        gui.scene().link(
            otherPort.parent,
            otherPort.getName(),
            op,
            newName
            );

        dataLoaded=true;
        saveData();
    }
    else
    {
        setTimeout(function()
        {
            op.dynOut.removeLinks();
            gui.patch().removeDeadLinks();
        },100);


        op.log('dynOut unlinked...');
    }
    gui.patch().removeDeadLinks();
};



function getSubPatchOutputOp()
{
    var patchOutputOP=op.patch.getSubPatchOp(op.patchId.get(),'Ops.Ui.PatchOutput');

    if(!patchOutputOP)
    {
        // console.log("Creating output for ",op.patchId.get());
        op.patch.addOp('Ops.Ui.PatchOutput',{'subPatch':op.patchId.get()} );
        patchOutputOP=op.patch.getSubPatchOp(op.patchId.get(),'Ops.Ui.PatchOutput');

        if(!patchOutputOP) console.warn('no patchinput2!');
    }
    return patchOutputOP;

}

function getSubPatchInputOp()
{
    var patchInputOP=op.patch.getSubPatchOp(op.patchId.get(),'Ops.Ui.PatchInput');

    if(!patchInputOP)
    {
        op.patch.addOp('Ops.Ui.PatchInput',{'subPatch':op.patchId.get()} );
        patchInputOP=op.patch.getSubPatchOp(op.patchId.get(),'Ops.Ui.PatchInput');
        if(!patchInputOP) console.warn('no patchinput2!');
    }


    return patchInputOP;
}

op.addSubLink=function(p,p2)
{
    var num=data.ports.length;

    console.log('sublink! ',p.getName(), (num-1)+" "+p2.parent.name+" "+p2.name);


    if(p.direction==PORT_DIR_IN)
    {
        var l=gui.scene().link(
            p.parent,
            p.getName(),
            getSubPatchInputOp(),
            "in"+(num-1)+" "+p2.parent.name+" "+p2.name
            );

        // console.log('- ----=====EEE ',p.getName(),p.get() );
        // console.log('- ----=====EEE ',l.getOtherPort(p).getName() ,l.getOtherPort(p).get() );
    }
    else
    {
        var l=gui.scene().link(
            p.parent,
            p.getName(),
            getSubPatchOutputOp(),
            "out"+(num)+" "+p2.parent.name+" "+p2.name
            );
    }

    var bounds=gui.patch().getSubPatchBounds(op.patchId.get());

    getSubPatchInputOp().uiAttr(
        {
            "translate":
            {
                "x":bounds.minx,
                "y":bounds.miny-100
            }
        });

    getSubPatchOutputOp().uiAttr(
        {
            "translate":
            {
                "x":bounds.minx,
                "y":bounds.maxy+100
            }
        });
    saveData();
};



op.onDelete=function()
{
    for (var i = op.patch.ops.length-1; i >=0 ; i--)
    {
        if(op.patch.ops[i].uiAttribs && op.patch.ops[i].uiAttribs.subPatch==op.patchId.get())
        {
            // console.log(op.patch.ops[i].objName);
            op.patch.deleteOp(op.patch.ops[i].id);
        }
    }



};


};

Ops.Ui.SubPatch.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Shader.MatCapMaterial
// 
// **************************************************************

Ops.Gl.Shader.MatCapMaterial = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var self=this;
var cgl=self.patch.cgl;

this.name='MatCapMaterial';
this.render=this.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
this.trigger=this.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));
this.shaderOut=this.addOutPort(new Port(this,"shader",OP_PORT_TYPE_OBJECT));
this.shaderOut.ignoreValueSerialize=true;

this.texture=this.addInPort(new Port(this,"texture",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
this.textureUniform=null;

this.textureDiffuse=this.addInPort(new Port(this,"diffuse",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
this.textureDiffuseUniform=null;

this.textureNormal=this.addInPort(new Port(this,"normal",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
this.textureNormalUniform=null;

this.normalScale=this.addInPort(new Port(this,"normalScale",OP_PORT_TYPE_VALUE,{display:'range'}));
this.normalScale.set(0.4);
this.normalScaleUniform=null;

this.textureSpec=this.addInPort(new Port(this,"specular",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
this.textureSpecUniform=null;

this.textureSpecMatCap=this.addInPort(new Port(this,"specular matcap",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
this.textureSpecMatCapUniform=null;


this.diffuseRepeatX=this.addInPort(new Port(this,"diffuseRepeatX",OP_PORT_TYPE_VALUE));
this.diffuseRepeatY=this.addInPort(new Port(this,"diffuseRepeatY",OP_PORT_TYPE_VALUE));
this.diffuseRepeatX.set(1.0);
this.diffuseRepeatY.set(1.0);

var pOpacity=op.inValueSlider("Opacity",1);


this.diffuseRepeatX.onValueChanged=function()
{
    self.diffuseRepeatXUniform.setValue(self.diffuseRepeatX.get());
};

this.diffuseRepeatY.onValueChanged=function()
{
    self.diffuseRepeatYUniform.setValue(self.diffuseRepeatY.get());
};


this.calcTangents=this.addInPort(new Port(this,"calc normal tangents",OP_PORT_TYPE_VALUE,{display:'bool'}));
this.calcTangents.onValueChanged=function()
{
    if(self.calcTangents.get()) shader.define('CALC_TANGENT');
        else shader.removeDefine('CALC_TANGENT');
};

this.projectCoords=this.addInPort(new Port(this,"projectCoords",OP_PORT_TYPE_VALUE,{display:'dropdown',values:['no','xy','yz','xz']}));
this.projectCoords.set('no');
this.projectCoords.onValueChanged=function()
{
    shader.removeDefine('DO_PROJECT_COORDS_XY');
    shader.removeDefine('DO_PROJECT_COORDS_YZ');
    shader.removeDefine('DO_PROJECT_COORDS_XZ');

    if(self.projectCoords.get()=='xy') shader.define('DO_PROJECT_COORDS_XY');
    if(self.projectCoords.get()=='yz') shader.define('DO_PROJECT_COORDS_YZ');
    if(self.projectCoords.get()=='xz') shader.define('DO_PROJECT_COORDS_XZ');
};

this.normalRepeatX=this.addInPort(new Port(this,"normalRepeatX",OP_PORT_TYPE_VALUE));
this.normalRepeatY=this.addInPort(new Port(this,"normalRepeatY",OP_PORT_TYPE_VALUE));
this.normalRepeatX.set(1.0);
this.normalRepeatY.set(1.0);

this.normalRepeatX.onValueChanged=function()
{
    self.normalRepeatXUniform.setValue(self.normalRepeatX.get());
};

this.normalRepeatY.onValueChanged=function()
{
    self.normalRepeatYUniform.setValue(self.normalRepeatY.get());
};

this.normalScale.onValueChanged=function()
{
    self.normalScaleUniform.setValue(self.normalScale.get()*2.0);
};

this.texture.onPreviewChanged=function()
{
    if(self.texture.showPreview) self.render.onTriggered=self.texture.get().preview;
        else self.render.onTriggered=self.doRender;
};

this.textureDiffuse.onPreviewChanged=function()
{
    if(self.textureDiffuse.showPreview) self.render.onTriggered=self.textureDiffuse.get().preview;
        else self.render.onTriggered=self.doRender;
};

this.textureNormal.onPreviewChanged=function()
{
    if(self.textureNormal.showPreview) self.render.onTriggered=self.textureNormal.get().preview;
        else self.render.onTriggered=self.doRender;
};

this.texture.onValueChanged=function()
{
    if(self.texture.get())
    {
        if(self.textureUniform!==null)return;
        shader.removeUniform('tex');
        self.textureUniform=new CGL.Uniform(shader,'t','tex',0);
    }
    else
    {
        shader.removeUniform('tex');
        self.textureUniform=null;
    }
};

this.textureDiffuse.onValueChanged=function()
{
    if(self.textureDiffuse.get())
    {
        if(self.textureDiffuseUniform!==null)return;
        shader.define('HAS_DIFFUSE_TEXTURE');
        shader.removeUniform('texDiffuse');
        self.textureDiffuseUniform=new CGL.Uniform(shader,'t','texDiffuse',1);
    }
    else
    {
        shader.removeDefine('HAS_DIFFUSE_TEXTURE');
        shader.removeUniform('texDiffuse');
        self.textureDiffuseUniform=null;
    }
};



this.textureNormal.onValueChanged=function()
{
    if(self.textureNormal.get())
    {
        if(self.textureNormalUniform!==null)return;
        shader.define('HAS_NORMAL_TEXTURE');
        shader.removeUniform('texNormal');
        self.textureNormalUniform=new CGL.Uniform(shader,'t','texNormal',2);
    }
    else
    {
        shader.removeDefine('HAS_NORMAL_TEXTURE');
        shader.removeUniform('texNormal');
        self.textureNormalUniform=null;
    }
};


function changeSpec()
{
    if(self.textureSpec.get() && self.textureSpecMatCap.get())
    {
        if(self.textureSpecUniform!==null)return;
        shader.define('USE_SPECULAR_TEXTURE');
        shader.removeUniform('texSpec');
        shader.removeUniform('texSpecMatCap');
        self.textureSpecUniform=new CGL.Uniform(shader,'t','texSpec',3);
        self.textureSpecMatCapUniform=new CGL.Uniform(shader,'t','texSpecMatCap',4);
    }
    else
    {
        shader.removeDefine('USE_SPECULAR_TEXTURE');
        shader.removeUniform('texSpec');
        shader.removeUniform('texSpecMatCap');
        self.textureSpecUniform=null;
        self.textureSpecMatCapUniform=null;
    }

}

this.textureSpec.onValueChanged=changeSpec;
this.textureSpecMatCap.onValueChanged=changeSpec;


function bindTextures()
{
    
    if(self.texture.get())
    {
        /* --- */cgl.setTexture(0);
        cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, self.texture.get().tex);
    }

    if(self.textureDiffuse.get())
    {
        /* --- */cgl.setTexture(1);
        cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, self.textureDiffuse.get().tex);
    }

    if(self.textureNormal.get())
    {
        /* --- */cgl.setTexture(2);
        cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, self.textureNormal.get().tex);
    }

    if(self.textureSpec.get())
    {
        /* --- */cgl.setTexture(3);
        cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, self.textureSpec.get().tex);
    }
    if(self.textureSpecMatCap.get())
    {
        /* --- */cgl.setTexture(4);
        cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, self.textureSpecMatCap.get().tex);
    }

};




this.doRender=function()
{
    shader.bindTextures=bindTextures;
    
    cgl.setShader(shader);
    self.trigger.trigger();
    cgl.setPreviousShader();



    // if(self.texture.get())
    // {
    //     /* --- */cgl.setTexture(0);
    //     cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, null);
    // }

    // if(self.textureDiffuse.get())
    // {
    //     /* --- */cgl.setTexture(1);
    //     cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, null);
    // }

    // if(self.textureNormal.get())
    // {
    //     /* --- */cgl.setTexture(2);
    //     cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, null);
    // }

    // if(self.textureSpec.get())
    // {
    //     /* --- */cgl.setTexture(3);
    //     cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, null);
    // }
    // if(self.textureSpecMatCap.get())
    // {
    //     /* --- */cgl.setTexture(4);
    //     cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, null);
    // }
};

var srcVert=''
    .endl()+'{{MODULES_HEAD}}'
    .endl()+'precision highp float;'
    .endl()+'IN vec3 vPosition;'
    .endl()+'IN vec2 attrTexCoord;'
    .endl()+'IN vec3 attrVertNormal;'

    .endl()+'#ifdef HAS_NORMAL_TEXTURE'
    .endl()+'   IN vec3 attrTangent;'
    .endl()+'   IN vec3 attrBiTangent;'

    .endl()+'   OUT vec3 vBiTangent;'
    .endl()+'   OUT vec3 vTangent;'
    .endl()+'#endif'

    .endl()+'OUT vec2 texCoord;'
    .endl()+'OUT vec3 norm;'
    .endl()+'UNI mat4 projMatrix;'
    .endl()+'UNI mat4 modelMatrix;'
    .endl()+'UNI mat4 viewMatrix;'
    .endl()+'UNI mat4 normalMatrix;'
    .endl()+'OUT vec2 vNorm;'

    .endl()+'OUT vec3 e;'

    .endl()+'void main()'
    .endl()+'{'
    .endl()+'    texCoord=attrTexCoord;'
    .endl()+'    norm=attrVertNormal;'

    .endl()+'    #ifdef HAS_NORMAL_TEXTURE'
    .endl()+'        vTangent=attrTangent;'
    .endl()+'        vBiTangent=attrBiTangent;'
    .endl()+'    #endif'

    .endl()+'    vec4 pos = vec4( vPosition, 1. );'
    .endl()+'    mat4 mMatrix=modelMatrix;'

    .endl()+'    {{MODULE_VERTEX_POSITION}}'
    .endl()+'    mat4 mvMatrix= viewMatrix * mMatrix;'
    .endl()+'    e = normalize( vec3( mvMatrix * pos ) );'
    .endl()+'    vec3 n = normalize( mat3(normalMatrix) * norm );'

    .endl()+'    vec3 r = reflect( e, n );'
    .endl()+'    float m = 2. * sqrt( '
    .endl()+'        pow(r.x, 2.0)+'
    .endl()+'        pow(r.y, 2.0)+'
    .endl()+'        pow(r.z + 1.0, 2.0)'
    .endl()+'    );'
    .endl()+'    vNorm = r.xy / m + 0.5;'

    .endl()+'   #ifdef DO_PROJECT_COORDS_XY'
    .endl()+'       texCoord=(projMatrix * mvMatrix*pos).xy*0.1;'
    .endl()+'   #endif'

    .endl()+'   #ifdef DO_PROJECT_COORDS_YZ'
    .endl()+'       texCoord=(projMatrix * mvMatrix*pos).yz*0.1;'
    .endl()+'   #endif'

    .endl()+'   #ifdef DO_PROJECT_COORDS_XZ'
    .endl()+'       texCoord=(projMatrix * mvMatrix*pos).xz*0.1;'
    .endl()+'   #endif'

    .endl()+'   gl_Position = projMatrix * mvMatrix * pos;'

    .endl()+'}';


var srcFrag=''
    .endl()+'precision highp float;'

    .endl()+'{{MODULES_HEAD}}'

    .endl()+'IN vec3 norm;'
    .endl()+'IN vec2 texCoord;'
    .endl()+'UNI sampler2D tex;'
    .endl()+'IN vec2 vNorm;'
    .endl()+'UNI mat4 viewMatrix;'

    .endl()+'UNI float diffuseRepeatX;'
    .endl()+'UNI float diffuseRepeatY;'
    .endl()+'UNI float opacity;'

    .endl()+'#ifdef HAS_DIFFUSE_TEXTURE'
    .endl()+'   UNI sampler2D texDiffuse;'
    .endl()+'#endif'

    .endl()+'#ifdef USE_SPECULAR_TEXTURE'
    .endl()+'   UNI sampler2D texSpec;'
    .endl()+'   UNI sampler2D texSpecMatCap;'
    .endl()+'#endif'


    .endl()+'#ifdef HAS_NORMAL_TEXTURE'
    .endl()+'   IN vec3 vBiTangent;'
    .endl()+'   IN vec3 vTangent;'

    .endl()+'   UNI sampler2D texNormal;'
    .endl()+'   UNI mat4 normalMatrix;'
    .endl()+'   UNI float normalScale;'
    .endl()+'   UNI float normalRepeatX;'
    .endl()+'   UNI float normalRepeatY;'
    .endl()+'   IN vec3 e;'
    .endl()+'   vec2 vNormt;'
    .endl()+'#endif'

    .endl()+''
    .endl()+'void main()'
    .endl()+'{'

    .endl()+'   vec2 vnOrig=vNorm;'
    .endl()+'   vec2 vn=vNorm;'

    .endl()+'   #ifdef HAS_TEXTURES'
    .endl()+'       vec2 texCoords=texCoord;'
    .endl()+'       {{MODULE_BEGIN_FRAG}}'
    .endl()+'   #endif'


    .endl()+'   #ifdef HAS_NORMAL_TEXTURE'
    .endl()+'       vec3 tnorm=texture2D( texNormal, vec2(texCoord.x*normalRepeatX,texCoord.y*normalRepeatY) ).xyz * 2.0 - 1.0;'

    .endl()+'       tnorm = normalize(tnorm*normalScale);'

    .endl()+'       vec3 tangent;'
    .endl()+'       vec3 binormal;'

    .endl()+'       #ifdef CALC_TANGENT'
    .endl()+'           vec3 c1 = cross(norm, vec3(0.0, 0.0, 1.0));'
    // .endl()+'           vec3 c2 = cross(norm, vec3(0.0, 1.0, 0.0));'
    // .endl()+'           if(length(c1)>length(c2)) tangent = c2;'
    // .endl()+'               else tangent = c1;'
    .endl()+'           tangent = c1;'
    .endl()+'           tangent = normalize(tangent);'
    .endl()+'           binormal = cross(norm, tangent);'
    .endl()+'           binormal = normalize(binormal);'
    .endl()+'       #endif'

    .endl()+'       #ifndef CALC_TANGENT'
    .endl()+'           tangent=normalize(vTangent);'
    // .endl()+'           tangent.y*=-13.0;'
    // .endl()+'           binormal=vBiTangent*norm;'
    // .endl()+'           binormal.z*=-1.0;'
    // .endl()+'           binormal=normalize(binormal);'
    .endl()+'           binormal=normalize( cross( normalize(norm), normalize(vBiTangent) )   );'
        // vBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );

    .endl()+'       #endif'

    .endl()+'       tnorm=normalize(tangent*tnorm.x + binormal*tnorm.y + norm*tnorm.z);'

    .endl()+'       vec3 n = normalize( mat3(normalMatrix) * (norm+tnorm*normalScale) );'

    .endl()+'       vec3 r = reflect( e, n );'
    .endl()+'       float m = 2. * sqrt( '
    .endl()+'           pow(r.x, 2.0)+'
    .endl()+'           pow(r.y, 2.0)+'
    .endl()+'           pow(r.z + 1.0, 2.0)'
    .endl()+'       );'
    .endl()+'       vn = (r.xy / m + 0.5);'

    .endl()+'       vn.t=clamp(vn.t, 0.0, 1.0);'
    .endl()+'       vn.s=clamp(vn.s, 0.0, 1.0);'
    .endl()+'    #endif'

    .endl()+'    vec4 col = texture2D( tex, vn );'

    // .endl()+'   float bias=0.1;'
    // .endl()+'    if(vn.s>1.0-bias || vn.t>1.0-bias || vn.s<bias || vn.t<bias)' //col.rgb=vec3(0.0,1.0,0.0);
    // .endl()+'    {;'
    // .endl()+'       col = texture2D( tex, vnOrig );'

    // .endl()+'    };'


    .endl()+'    #ifdef HAS_DIFFUSE_TEXTURE'
    .endl()+'       col = col*texture2D( texDiffuse, vec2(texCoords.x*diffuseRepeatX,texCoords.y*diffuseRepeatY));'
    .endl()+'    #endif'

    .endl()+'    #ifdef USE_SPECULAR_TEXTURE'
    .endl()+'       vec4 spec = texture2D( texSpecMatCap, vn );'
    .endl()+'       spec*= texture2D( texSpec, vec2(texCoords.x*diffuseRepeatX,texCoords.y*diffuseRepeatY) );'
    .endl()+'       col+=spec;'
    .endl()+'    #endif'

    .endl()+'    col.a*=opacity;'

    .endl()+'    {{MODULE_COLOR}}'

    // .endl()+'    col.xy=vn;'
    // .endl()+'    col.r=0.0;'
    // .endl()+'    col.g=0.0;'
    // .endl()+'    col.b=0.0;'

    // .endl()+'    if()col.rgb=vec3(1.0,0.0,0.0);'

    // .endl()+'    col.rgb=vec3(length(vn),0.0,0.0);'


    .endl()+'    gl_FragColor = col;'
    .endl()+''
    .endl()+'}';

var shader=new CGL.Shader(cgl,'MatCapMaterial');
var uniOpacity=new CGL.Uniform(shader,'f','opacity',pOpacity);
shader.setModules(['MODULE_VERTEX_POSITION','MODULE_COLOR','MODULE_BEGIN_FRAG']);


shader.bindTextures=bindTextures;
this.shaderOut.set(shader);
// this.onLoaded=shader.compile;
shader.setSource(srcVert,srcFrag);
this.normalScaleUniform=new CGL.Uniform(shader,'f','normalScale',self.normalScale.get());
this.normalRepeatXUniform=new CGL.Uniform(shader,'f','normalRepeatX',self.normalRepeatX.get());
this.normalRepeatYUniform=new CGL.Uniform(shader,'f','normalRepeatY',self.normalRepeatY.get());

this.diffuseRepeatXUniform=new CGL.Uniform(shader,'f','diffuseRepeatX',self.diffuseRepeatX.get());
this.diffuseRepeatYUniform=new CGL.Uniform(shader,'f','diffuseRepeatY',self.diffuseRepeatY.get());

this.render.onTriggered=this.doRender;
this.calcTangents.set(true);
this.doRender();


};

Ops.Gl.Shader.MatCapMaterial.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.BlendMode2
// 
// **************************************************************

Ops.Gl.BlendMode2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var exec=op.inFunction("Render");
var inBlend=op.inValueSelect("Blendmode",
    [
        'None',
        'Normal',
        'Add',
        'Subtract',
        'Multiply'
    ],'Normal');
var inPremul=op.inValueBool("Premultiplied");
var next=op.outFunction("Next");

var cgl=op.patch.cgl;
var blendMode=0;

inBlend.onChange=update;
update();

function update()
{
    if(inBlend.get()=="Normal")blendMode=CGL.BLEND_NORMAL;
    else if(inBlend.get()=="Add")blendMode=CGL.BLEND_ADD;
    else if(inBlend.get()=="Subtract")blendMode=CGL.BLEND_SUB;
    else if(inBlend.get()=="Multiply")blendMode=CGL.BLEND_MUL;
    else blendMode=CGL.BLEND_NONE;
}

exec.onTriggered=function()
{
    cgl.pushBlendMode(blendMode,inPremul.get());
    next.trigger();
    cgl.popBlendMode();
    
	cgl.gl.blendEquationSeparate( cgl.gl.FUNC_ADD, cgl.gl.FUNC_ADD );
	cgl.gl.blendFuncSeparate( cgl.gl.SRC_ALPHA, cgl.gl.ONE_MINUS_SRC_ALPHA, cgl.gl.ONE, cgl.gl.ONE_MINUS_SRC_ALPHA );
};


};

Ops.Gl.BlendMode2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Audio.MidiJsonNote
// 
// **************************************************************

Ops.Audio.MidiJsonNote = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const inData=op.inObject("Data");
const inNote=op.inValueString("Note");
const inChannel=op.inValueString("Channel",-1);
const inStartBeat=op.inValueInt("Beat Start",0);
const inEndBeat=op.inValueInt("Beat End",0);

const outCount=op.outValue("Count");
const outProgress=op.outValue("Progress");
const outTimeSince=op.outValue("Time since last");
const outTrigger=op.outTrigger("Trigger");
const outReset=op.outTrigger("Reseted");

var lastBeat=0;
var counter=0;
var oldNames=null;
var oldProgress=null;
var lastHit=0;

var notfound=true;

inData.onChange=function()
{
    var data=inData.get();
    if(!data)return;    
    var beat=data.beat;

    if(inStartBeat.get()!=inEndBeat.get())
    {
        if( beat<inStartBeat.get())return;
        if( beat>inEndBeat.get())return;
    }

    var names=data.names;
    var progress=data.progress;
    if(!names)return;
    if(!progress)return;

    if(beat<lastBeat)
    {
        counter=0;
        lastHit=0;
        notfound=true;
        outTimeSince.set(0);
        outReset.trigger();
    }
    lastBeat=beat;
    
    var note=inNote.get();
    
    if(!oldNames)
    {
        oldNames=[];
        oldProgress=[];
        oldNames.length=names.length;
        oldProgress.length=names.length;
    }
    
    var startChn=0;
    var endChn=names.length;
    
    if(inChannel.get()>=0)
    {
        startChn=inChannel.get();
        endChn=startChn+1;
    }
    
    var prog=0;
    var progCount=0;
    
 

    for(var i=startChn;i<endChn;i++)
    {
        if(names[i]==note)
        {
            
            // if(startChn==8)
            // {
            //     if(( oldNames[i]==note && progress[i]<oldProgress[i]) )
            //     {
            //         console.log("progressbuyg?")
            //     }
            // }
            
            if( ( oldNames[i]!=note ) || notfound ) //( oldNames[i]==note && progress[i]<oldProgress[i])
            {
                counter++;
                lastHit=CABLES.now();
                outTrigger.trigger();
            }
            
            progCount++;
            prog+=progress[i];
            notfound=false;
        }

        oldNames[i]=names[i];
        oldProgress[i]=progress[i];
    }
    
    if(progCount==0)notfound=true;
        else outProgress.set((prog/progCount)||0);
    
    // if(startChn==8)console.log(prog);

    if(lastHit!==0) outTimeSince.set( (CABLES.now()-lastHit)/1000 );

    outCount.set(counter);
    
};

};

Ops.Audio.MidiJsonNote.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.TimedSequence
// 
// **************************************************************

Ops.Trigger.TimedSequence = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var self=this;

this.name='TimedSequence';
this.exe=this.addInPort(new Port(this,"exe",OP_PORT_TYPE_FUNCTION));
this.current=this.addInPort(new Port(this,"current",OP_PORT_TYPE_VALUE));
this.current.val=0;

this.overwriteTime=this.addInPort(new Port(this,"overwriteTime",OP_PORT_TYPE_VALUE,{ display:'bool' }));
this.overwriteTime.val=false;
this.ignoreInSubPatch=this.addInPort(new Port(this,"ignoreInSubPatch",OP_PORT_TYPE_VALUE,{display:"bool"}));
this.ignoreInSubPatch.val=false;

this.triggerAlways=this.addOutPort(new Port(this,"triggerAlways",OP_PORT_TYPE_FUNCTION));
var outNames=op.outArray("Names",[]);
this.currentKeyTime=this.addOutPort(new Port(this,"currentKeyTime",OP_PORT_TYPE_VALUE));
const outCurrent=op.outValue("Current");
var triggers=[];

for(var i=0;i<32;i++)
{
    var p=this.addOutPort(new Port(this,"trigger "+i,OP_PORT_TYPE_FUNCTION));
    p.onLinkChanged=updateNames;
    triggers.push( p );
}

function updateNames()
{
    var names=[];
    for(var i=0;i<triggers.length;i++)
    {
        if(triggers[i].isLinked())
        {
            names.push(triggers[i].links[0].getOtherPort(triggers[i]).parent.uiAttribs.title);
        }
        else
        {
            names.push("none");
        }
        
    }
    outNames.set(names);
}

this.onLoaded=function()
{
    updateNames();
    var i=0;
    // console.log('TimedSequence loading---------------------------------------------');
    // for(i=0;i<triggers.length;i++)
    // {
    //     cgl.gl.clear(cgl.gl.COLOR_BUFFER_BIT | cgl.gl.DEPTH_BUFFER_BIT);
    //     triggers[i].trigger();
    // }

    // if(self.current.anim)
    // {
    //     for(i=0;i<self.current.anim.keys.length;i++)
    //     {
    //         preRenderTimes.push(self.current.anim.keys[i].time);
    //         // var ii=i;
    //         // cgl.gl.clear(cgl.gl.COLOR_BUFFER_BIT | cgl.gl.DEPTH_BUFFER_BIT);

    //         // var time=self.current.anim.keys[ii].time+0.001;
    //         // self.exe.onTriggered(time);
    //         // console.log('timed pre init...');
    //         // cgl.gl.flush();
    //     }
    // }

    // self.triggerAlways.trigger();
    // console.log('TimedSequence loaded---------------------------------------------');

};

var lastUiValue=-1;

this.exe.onTriggered=doTrigger;
// this.current.onValueChanged=doTrigger;


function doTrigger(_time)
{
    var spl=0;
    
    var outIndex=Math.round(self.current.val-0.5);
    
    if(window.gui)
    {

        if(self.current.val!=lastUiValue)
        {
            lastUiValue=parseInt(self.current.val,10);
            for(spl=0;spl<triggers.length;spl++)
            {
                if(spl==lastUiValue) triggers[spl].setUiActiveState(true);
                    else triggers[spl].setUiActiveState(false);
            }

        }
    }

    if(self.current.anim)
    {
        var time=_time;
        if(_time===undefined) time=self.current.parent.patch.timer.getTime();

        self.currentKeyTime.val=time-self.current.anim.getKey(time).time;

        if(self.current.isAnimated())
        {
            if(self.overwriteTime.val)
            {
                self.current.parent.patch.timer.overwriteTime=self.currentKeyTime.val;  // todo  why current ? why  not self ?
            }
        }
    }

    if(self.patch.gui && self.ignoreInSubPatch.val )
    {
        for(var i=0;i<triggers.length;i++)
        {
            for(spl=0;spl<triggers[i].links.length;spl++)
            {
                if(triggers[i].links[spl])
                {
                    if(triggers[i].links[spl].portIn.parent.patchId)
                    {
                        if(gui.patch().getCurrentSubPatch() == triggers[i].links[spl].portIn.parent.patchId.val)
                        {
                            self.patch.timer.overwriteTime=-1;
                            triggers[i].trigger();
                            return;
                        }
                        // console.log(triggers[i].links[spl].portIn.parent.patchId.val);
                    }
                }
            }
        }
    }

    
    if(outIndex>=0 && outIndex<triggers.length)
    {
        outCurrent.set(outIndex);
        triggers[outIndex].trigger();
    }

    self.patch.timer.overwriteTime=-1;
    self.triggerAlways.trigger();
};


};

Ops.Trigger.TimedSequence.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Json3d.Json3dMesh
// 
// **************************************************************

Ops.Json3d.Json3dMesh = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var cgl=this.patch.cgl;

var scene=new CABLES.Variable();

cgl.frameStore.currentScene=null;

var exe=op.inFunction("Render");
var filename=this.addInPort(new Port(this,"file",OP_PORT_TYPE_VALUE,{ display:'file',type:'string',filter:'3d json' } ));
var meshIndex=op.inValueInt("Mesh Index",0);


var draw=op.inValueBool("Draw",true);
var centerPivot=op.inValueBool("Center Mesh",true);


var inSize=op.inValue("Size",1);

var next=this.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));
var geometryOut=op.outObject("Geometry");

var merge=op.inValueBool("Merge",false);

var inNormals=op.inValueSelect("Calculate Normals",["no","smooth","flat"],"no");
var outScale=op.outValue("Scaling",1.0);

var geom=null;
var data=null;
var mesh=null;
var meshes=[];
var currentIndex=-1;
var transMatrix=mat4.create();
var bounds={};
var vScale=vec3.fromValues(1,1,1);

exe.onTriggered=render;
filename.onChange=reload;
centerPivot.onChange=setMeshLater;
meshIndex.onChange=setMeshLater;
inNormals.onChange=setMeshLater;
merge.onChange=setMeshLater;

inSize.onChange=updateScale;
var needSetMesh=true;


function calcNormals()
{
    if(!geom)
    {
        console.log('calc normals: no geom!');
        return;
    }

    if(inNormals.get()=='no')return;
    if(inNormals.get()=='smooth')geom.calculateNormals();
    if(inNormals.get()=='flat')
    {
        geom.unIndex();
        geom.calculateNormals();
    }
}

function render()
{
    if(needSetMesh) setMesh();

    if(draw.get())
    {
        cgl.pushModelMatrix();
        mat4.multiply(cgl.mvMatrix,cgl.mvMatrix,transMatrix);

        if(mesh) mesh.render(cgl.getShader());
        
        cgl.popModelMatrix();
        next.trigger();
    }
}

function setMeshLater()
{
    needSetMesh=true;
}


function updateScale()
{
    if(inSize.get()!==0)
    {
        var scale=inSize.get()/bounds.maxAxis;
        vec3.set(vScale,scale,scale,scale);
        outScale.set(scale);
    }
    else
    {
        vec3.set(vScale,1,1,1);
    }

    mat4.identity(transMatrix);
    mat4.scale(transMatrix,transMatrix, vScale);
}

function updateInfo(geom)
{
    if(!CABLES.UI)return;

    var nfo='<div class="panel">';

    if(data)
    {
        nfo += 'Mesh '+(currentIndex+1)+' of '+data.meshes.length+'<br/>';
        nfo += '<br/>';
    }

    if(geom)
    {
        nfo += (geom.verticesIndices||[]).length/3+' faces <br/>';
        nfo += (geom.vertices||[]).length/3+' vertices <br/>';
        nfo += (geom.texCoords||[]).length/2+' texturecoords <br/>';
        nfo += (geom.vertexNormals||[]).length/3+' normals <br/>';
        nfo += (geom.tangents||[]).length/3+' tangents <br/>';
        nfo += (geom.biTangents||[]).length/3+' bitangents <br/>';
    }

    nfo+="</div>";

    op.uiAttr({info:nfo});
}


function setMesh()
{
    mesh=null;
    var index=Math.floor(meshIndex.get());

    if(!data || index!=index || !isNumeric(index) || index<0 || index>=data.meshes.length)
    {
        op.uiAttr({warning:'mesh not found - index out of range '});
        return;
    }

    currentIndex=index;

    geom=new CGL.Geometry();

    if(merge.get())
    {
        for(var i=0;i<data.meshes.length;i++)
        {
            var jsonGeom=data.meshes[i];
            if(jsonGeom)
            {
                var geomNew=CGL.Geometry.json2geom(jsonGeom);
                geom.merge(geomNew);
            }
        }
        
        var bnd=geom.getBounds();
        
        for(var i=0;i<geom.vertices.length;i++)
        {
            geom.vertices[i]/=bnd.maxAxis;
        }
        

    }
    else
    {
        var jsonGeom=data.meshes[index];

        if(!jsonGeom)
        {
            mesh=null;
            op.uiAttr({warning:'mesh not found'});
            return;
        }

        var i=0;
        geom=CGL.Geometry.json2geom(jsonGeom);
        
        
    }

    if(centerPivot.get())geom.center();

    bounds=geom.getBounds();
    updateScale();
    updateInfo(geom);

    calcNormals();
    geometryOut.set(geom);
    mesh=new CGL.Mesh(cgl,geom);
    needSetMesh=false;
    meshes[index]=mesh;
    
    // console.log("set mesh done");
    // console.log(geom);

    op.uiAttr({'warning':null});
}

function reload()
{
    if(!filename.get())return;
    currentIndex=-1;

    function doLoad()
    {
        CABLES.ajax(
            op.patch.getFilePath(filename.get()),
            function(err,_data,xhr)
            {
                if(err)
                {
                    if(CABLES.UI)op.uiAttr({'error':'could not load file...'});

                    console.error('ajax error:',err);
                    op.patch.loading.finished(loadingId);
                    return;
                }
                else
                {
                    if(CABLES.UI)op.uiAttr({'error':null});
                }

                try
                {
                    data=JSON.parse(_data);
                }
                catch(ex)
                {
                    if(CABLES.UI)op.uiAttr({'error':'could not load file...'});
                    op.patch.loading.finished(loadingId);
                    return;
                }

                needSetMesh=true;
                op.patch.loading.finished(loadingId);
                if(CABLES.UI) gui.jobs().finish('loading3d'+loadingId);

            });
        // setMesh();
    }

    var loadingId=op.patch.loading.start('json3dMesh',filename.get());

    if(CABLES.UI) gui.jobs().start({id:'loading3d'+loadingId,title:'loading 3d data'},doLoad);
        else doLoad();
}

};

Ops.Json3d.Json3dMesh.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Stripes
// 
// **************************************************************

Ops.Gl.TextureEffects.Stripes = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["stripes_frag"]="IN vec2 texCoord;\nUNI sampler2D tex;\n\nUNI float num;\nUNI float width;\nUNI float axis;\nUNI float offset;\n\nUNI float r;\nUNI float g;\nUNI float b;\nUNI float a;\n\nvoid main()\n{\n   vec4 col=texture2D(tex,texCoord);\n    \n   float v=0.0;\n   float c=1.0;\n   if(axis==0.0) v=texCoord.y;\n   if(axis==1.0) v=texCoord.x;\n   if(axis==2.0) v=texCoord.x+texCoord.y;\n   if(axis==3.0) v=texCoord.x-texCoord.y;\n   v+=offset;\n    \n\n   float m=mod(v,1.0/num);\n   float rm=width*2.0*1.0/num/2.0;\n    \n   if(m>rm)\n       col.rgb=mix(col.rgb,vec3( r,g,b ),a);\n\n   #ifdef STRIPES_SMOOTHED    \n       m*=2.0;\n       col.rgb=vec3(  smoothstep(0.,1., abs(( ((m-rm) )/ (rm) )  ) ));\n   #endif\n    \n   gl_FragColor = col;\n}\n";

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var num=op.addInPort(new Port(op,"num",OP_PORT_TYPE_VALUE));
var width=op.addInPort(new Port(op,"width",OP_PORT_TYPE_VALUE,{display:'range'}));
var axis=op.addInPort(new Port(op,"axis",OP_PORT_TYPE_VALUE,{display:'dropdown',values:['X','Y','Diagonal','Diagonal Flip']}));

var offset=op.addInPort(new Port(op,"offset",OP_PORT_TYPE_VALUE));

var smoothed=op.inValueBool("Gradients");

var r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true'}));
var g=op.addInPort(new Port(op,"g",OP_PORT_TYPE_VALUE,{ display:'range' }));
var b=op.addInPort(new Port(op,"b",OP_PORT_TYPE_VALUE,{ display:'range' }));
var a=op.addInPort(new Port(op,"a",OP_PORT_TYPE_VALUE,{ display:'range' }));

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

smoothed.onChange=function()
{
    
    if(smoothed.get())shader.define("STRIPES_SMOOTHED");
    else shader.removeDefine("STRIPES_SMOOTHED");
};

axis.onValueChanged=function()
{
    if(axis.get()=='X')uniAxis.setValue(0);
    if(axis.get()=='Y')uniAxis.setValue(1);
    if(axis.get()=='Diagonal')uniAxis.setValue(2);
    if(axis.get()=='Diagonal Flip')uniAxis.setValue(3);
};

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl,'textureeffect stripes');
shader.setSource(shader.getDefaultVertexShader(),attachments.stripes_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

//op.onLoaded=shader.compile;
var numUniform=new CGL.Uniform(shader,'f','num',num);
var uniWidth=new CGL.Uniform(shader,'f','width',width);
var uniAxis=new CGL.Uniform(shader,'f','axis',0);
var uniOffset=new CGL.Uniform(shader,'f','offset',offset);

r.set(1.0);
g.set(1.0);
b.set(1.0);
a.set(1.0);
axis.set('X');
num.set(5);
width.set(0.5);

var uniformR=new CGL.Uniform(shader,'f','r',r);
var uniformG=new CGL.Uniform(shader,'f','g',g);
var uniformB=new CGL.Uniform(shader,'f','b',b);
var uniformA=new CGL.Uniform(shader,'f','a',a);

render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Stripes.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Invert
// 
// **************************************************************

Ops.Gl.TextureEffects.Invert = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='Invert';

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);
//op.onLoaded=shader.compile;


var srcFrag=''
    .endl()+'precision highp float;'
    .endl()+'#ifdef HAS_TEXTURES'
    .endl()+'  IN vec2 texCoord;'
    .endl()+'  uniform sampler2D tex;'
    .endl()+'#endif'
    .endl()+''
    .endl()+''
    .endl()+'void main()'
    .endl()+'{'
    .endl()+'   vec4 col=vec4(1.0,0.0,0.0,1.0);'
    .endl()+'   #ifdef HAS_TEXTURES'
    .endl()+'       col=texture2D(tex,texCoord);'
    .endl()+'       col.rgb=1.0-col.rgb;'
    .endl()+'   #endif'
    .endl()+'   gl_FragColor = col;'
    .endl()+'}\n';

shader.setSource(shader.getDefaultVertexShader(),srcFrag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Invert.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Deprecated.Json3d.TextMesh3d
// 
// **************************************************************

Ops.Deprecated.Json3d.TextMesh3d = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.inFunction("Render");
var filename=this.addInPort(new Port(this,"file",OP_PORT_TYPE_VALUE,{ display:'file',type:'string',filter:'mesh' } ));
var text=op.inValueString("Text","cables");
var inSize=op.inValue("Depth",0.2);
var inSpace=op.inValue("Spacing",0.1);

var next=this.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));
var geometryOut=op.outObject("Geometry");
var outWidth=op.outValue("Width");

var data=null;
var meshes={};
var transMatrix=mat4.create();
var vScale=vec3.fromValues(1,1,1);

exe.onTriggered=render;
filename.onChange=reload;
inSize.onChange=updateScale;

var vec=vec3.create();
vec3.set(vec,1,0,0);

var cgl=this.patch.cgl;

function render()
{
    cgl.pushModelMatrix();
    mat4.multiply(cgl.modelMatrix(),cgl.modelMatrix(),transMatrix);

    var txt=text.get()||'';
    var lastWidth=0;
    var width=0;

    cgl.pushModelMatrix();
    for(var i=0;i<txt.length;i++)
    {
        if(txt[i]==' ')
        {
            vec[0]=0.4;
            mat4.translate(cgl.modelMatrix(),cgl.modelMatrix(), vec);
        }
        else if(meshes[txt[i]])
        {
            var m=meshes[txt[i]];
            if(m)
            {
                vec[0]=lastWidth;
                mat4.translate(cgl.modelMatrix(),cgl.modelMatrix(), vec);
                
                // cgl.pushModelMatrix();
                // mat4.multiply(cgl.modelMatrix(),cgl.modelMatrix(),m.charTrans);
                
                m.render(cgl.getShader());
                // cgl.popModelMatrix();
                
                width+=lastWidth;
                lastWidth=m.charWidth+inSpace.get();
            }
        }
        
    }
    width+=lastWidth-inSpace.get();;


    outWidth.set(width);
    
    cgl.popModelMatrix();
    cgl.popModelMatrix();
    next.trigger();
}

function updateScale()
{
    vec3.set(vScale,1,1,inSize.get());
    mat4.identity(transMatrix);
    mat4.scale(transMatrix,transMatrix, vScale);
}

function addMesh(jsonMesh)
{
    if(!jsonMesh || !jsonMesh.name)return;
    var index=jsonMesh.name;  

    if(!jsonMesh)
    {
        op.uiAttr({warning:'mesh not found'});
        return;
    }
    op.uiAttribs.warning='';
    
    var i=0;

    var geom=new CGL.Geometry();
    geom.vertices=JSON.parse(JSON.stringify(jsonMesh.vertices));
    geom.vertexNormals=jsonMesh.normals||[];
    geom.tangents=jsonMesh.tangents||[];
    geom.biTangents=jsonMesh.bitangents||[];
    
    if(jsonMesh.texturecoords) geom.texCoords = jsonMesh.texturecoords[0];

    // geom.center();
    geom.verticesIndices=[];
    geom.verticesIndices.length=jsonMesh.faces.length*3;

    for(i=0;i<jsonMesh.faces.length;i++)
    {
        geom.verticesIndices[i*3]=jsonMesh.faces[i][0];
        geom.verticesIndices[i*3+1]=jsonMesh.faces[i][1];
        geom.verticesIndices[i*3+2]=jsonMesh.faces[i][2];
    }

    var bounds=geom.getBounds();
    

    // var offset=
    //     [
    //         bounds.minX+(bounds.maxX-bounds.minX)/2,
    //         bounds.minY+(bounds.maxY-bounds.minY)/2,
    //         bounds.minZ+(bounds.maxZ-bounds.minZ)/2
    //     ];

    for(i=0;i<geom.vertices.length;i+=3)
    {
        // if(geom.vertices[i+0]==geom.vertices[i+0])
        {
            geom.vertices[i+0]-=bounds.minX;
            geom.vertices[i+1]-=bounds.minY;
            geom.vertices[i+2]-=bounds.minZ;
            // geom.vertices[i+1]-=offset[1];
            // geom.vertices[i+2]-=offset[2];
        }
    }
    
    
    
    var mesh=new CGL.Mesh(cgl,geom);
    mesh.charWidth=Math.abs(bounds.maxX-bounds.minX);
    mesh.charTrans=jsonMesh.charTrans;
    // console.log(index,mesh.charWidth);

    meshes[index]=mesh;
    

    op.uiAttr({'warning':null});
}

function reload()
{
    if(!filename.get())return;

    function doLoad()
    {
        CABLES.ajax(
            op.patch.getFilePath(filename.get()),
            function(err,_data,xhr)
            {
                if(err)
                {
                    if(CABLES.UI)op.uiAttr({'error':'could not load file...'});

                    console.error('ajax error:',err);
                    op.patch.loading.finished(loadingId);
                    return;
                }
                else
                {
                    if(CABLES.UI)op.uiAttr({'error':null});
                }

                try
                {
                    data=JSON.parse(_data);
                }
                catch(ex)
                {
                    if(CABLES.UI)op.uiAttr({'error':'could not load file...'});
                    return;
                }

                for(var i=0;i<data.rootnode.children.length;i++)
                {
                    if(data.rootnode.children[i].meshes && data.rootnode.children[i].meshes.length>0)
                    {
                        var meshIndex=data.rootnode.children[i].meshes[0];
                        data.meshes[meshIndex].name=data.rootnode.children[i].name;
                        data.meshes[meshIndex].charTrans=data.rootnode.children[i].transformation;
                        mat4.transpose(data.meshes[meshIndex].charTrans,data.meshes[meshIndex].charTrans);
                        // console.log(data.meshes[meshIndex].charTrans);
                        
                        addMesh(data.meshes[meshIndex]);
                    }
                }

                updateScale();
                op.patch.loading.finished(loadingId);
                if(CABLES.UI) gui.jobs().finish('loading3d'+loadingId);

            });
    }

    var loadingId=op.patch.loading.start('json3dFile',filename.get());

    if(CABLES.UI) gui.jobs().start({id:'loading3d'+loadingId,title:'loading 3d data'},doLoad);
        else doLoad();
}

};

Ops.Deprecated.Json3d.TextMesh3d.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.OrbitControls
// 
// **************************************************************

Ops.Gl.Matrix.OrbitControls = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
const minDist=op.addInPort(new Port(op,"min distance",OP_PORT_TYPE_VALUE));
const maxDist=op.addInPort(new Port(op,"max distance",OP_PORT_TYPE_VALUE));

const minRotY=op.inValue("min rot y",0);
const maxRotY=op.inValue("max rot y",0);

const initialAxis=op.addInPort(new Port(op,"initial axis y",OP_PORT_TYPE_VALUE,{display:'range'}));
const initialX=op.addInPort(new Port(op,"initial axis x",OP_PORT_TYPE_VALUE,{display:'range'}));
const initialRadius=op.inValue("initial radius",0);



const mul=op.addInPort(new Port(op,"mul",OP_PORT_TYPE_VALUE));

const smoothness=op.inValueSlider("Smoothness",1.0);
const restricted=op.addInPort(new Port(op,"restricted",OP_PORT_TYPE_VALUE,{display:'bool'}));

const active=op.inValueBool("Active",true);

const inReset=op.inFunctionButton("Reset");

const allowPanning=op.inValueBool("Allow Panning",true);
const allowZooming=op.inValueBool("Allow Zooming",true);
const allowRotation=op.inValueBool("Allow Rotation",true);
const pointerLock=op.inValueBool("Pointerlock",false);

const speedX=op.inValue("Speed X",1);
const speedY=op.inValue("Speed Y",1);

const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
const outRadius=op.addOutPort(new Port(op,"radius",OP_PORT_TYPE_VALUE));
const outYDeg=op.addOutPort(new Port(op,"Rot Y",OP_PORT_TYPE_VALUE));
const outXDeg=op.addOutPort(new Port(op,"Rot X",OP_PORT_TYPE_VALUE));

restricted.set(true);
mul.set(1);
minDist.set(0.05);
maxDist.set(99999);

inReset.onTriggered=reset;

const cgl=op.patch.cgl;
var eye=vec3.create();
var vUp=vec3.create();
var vCenter=vec3.create();
var viewMatrix=mat4.create();
var vOffset=vec3.create();

initialAxis.set(0.5);


var mouseDown=false;
var radius=5;
outRadius.set(radius);

var lastMouseX=0,lastMouseY=0;
var percX=0,percY=0;


vec3.set(vCenter, 0,0,0);
vec3.set(vUp, 0,1,0);

var tempEye=vec3.create();
var finalEye=vec3.create();
var tempCenter=vec3.create();
var finalCenter=vec3.create();

var px=0;
var py=0;

var divisor=1;
var element=null;
updateSmoothness();

op.onDelete=unbind;

var doLockPointer=false;

pointerLock.onChange=function()
{
    doLockPointer=pointerLock.get();
    console.log("doLockPointer",doLockPointer);
};

function reset()
{
    px=px%(Math.PI*2);
    py=py%(Math.PI*2);

    percX=(initialX.get()*Math.PI*2);
    percY=(initialAxis.get()-0.5);
    radius=initialRadius.get();
    eye=circlePos( percY );
}

function updateSmoothness()
{
    divisor=smoothness.get()*10+1.0;
}

smoothness.onChange=updateSmoothness;

var initializing=true;

function ip(val,goal)
{
    if(initializing)return goal;
    return val+(goal-val)/divisor;
}

var lastPy=0;

render.onTriggered=function()
{
    cgl.pushViewMatrix();

    px=ip(px,percX);
    py=ip(py,percY);
    
    var degY=(py+0.5)*180;
    

    if(minRotY.get()!==0 && degY<minRotY.get()) 
    {
        degY=minRotY.get();
        py=lastPy;
    }
    else if(maxRotY.get()!==0 && degY>maxRotY.get()) 
    {
        degY=maxRotY.get();
        py=lastPy;
    }
    else
    {
        lastPy=py;
    }

    outYDeg.set( degY );
    // outXDeg.set( (px)*180 );
    outXDeg.set( (px)*CGL.RAD2DEG );
    

    circlePosi(eye, py );
    
    vec3.add(tempEye, eye, vOffset);
    vec3.add(tempCenter, vCenter, vOffset);

    finalEye[0]=ip(finalEye[0],tempEye[0]);
    finalEye[1]=ip(finalEye[1],tempEye[1]);
    finalEye[2]=ip(finalEye[2],tempEye[2]);
    
    finalCenter[0]=ip(finalCenter[0],tempCenter[0]);
    finalCenter[1]=ip(finalCenter[1],tempCenter[1]);
    finalCenter[2]=ip(finalCenter[2],tempCenter[2]);
    
    mat4.lookAt(viewMatrix, finalEye, finalCenter, vUp);
    mat4.rotate(viewMatrix, viewMatrix, px, vUp);
    mat4.multiply(cgl.vMatrix,cgl.vMatrix,viewMatrix);

    trigger.trigger();
    cgl.popViewMatrix();
    initializing=false;
};

function circlePosi(vec,perc)
{
    const mmul=mul.get();
    if(radius<minDist.get()*mmul)radius=minDist.get()*mmul;
    if(radius>maxDist.get()*mmul)radius=maxDist.get()*mmul;
    
    outRadius.set(radius*mmul);
    
    var i=0,degInRad=0;
    // var vec=vec3.create();
    degInRad = 360*perc/2*CGL.DEG2RAD;
    vec3.set(vec,
        Math.cos(degInRad)*radius*mmul,
        Math.sin(degInRad)*radius*mmul,
        0);
    return vec;
}


function circlePos(perc)
{
    const mmul=mul.get();
    if(radius<minDist.get()*mmul)radius=minDist.get()*mmul;
    if(radius>maxDist.get()*mmul)radius=maxDist.get()*mmul;
    
    outRadius.set(radius*mmul);
    
    var i=0,degInRad=0;
    var vec=vec3.create();
    degInRad = 360*perc/2*CGL.DEG2RAD;
    vec3.set(vec,
        Math.cos(degInRad)*radius*mmul,
        Math.sin(degInRad)*radius*mmul,
        0);
    return vec;
}

function onmousemove(event)
{
    if(!mouseDown) return;

    var x = event.clientX;
    var y = event.clientY;
    
    var movementX=(x-lastMouseX)*speedX.get();
    var movementY=(y-lastMouseY)*speedY.get();

    if(doLockPointer)
    {
        movementX=event.movementX*mul.get();
        movementY=event.movementY*mul.get();
    }

    if(event.which==3 && allowPanning.get())
    {
        vOffset[2]+=movementX*0.01*mul.get();
        vOffset[1]+=movementY*0.01*mul.get();
    }
    else
    if(event.which==2 && allowZooming.get())
    {
        radius+=movementY*0.05;
        eye=circlePos(percY);
    }
    else
    {
        if(allowRotation.get())
        {
            percX+=movementX*0.003;
            percY+=movementY*0.002;
            
            if(restricted.get())
            {
                if(percY>0.5)percY=0.5;
                if(percY<-0.5)percY=-0.5;
            }
        }
    }

    lastMouseX=x;
    lastMouseY=y;
}

function onMouseDown(event)
{
    lastMouseX = event.clientX;
    lastMouseY = event.clientY;
    mouseDown=true;
    
    if(doLockPointer)
    {
        var el=op.patch.cgl.canvas;
        el.requestPointerLock = el.requestPointerLock || el.mozRequestPointerLock || el.webkitRequestPointerLock;
        if(el.requestPointerLock) el.requestPointerLock();
        else console.log("no t found");
        // document.addEventListener("mousemove", onmousemove, false);

        document.addEventListener('pointerlockchange', lockChange, false);
        document.addEventListener('mozpointerlockchange', lockChange, false);
        document.addEventListener('webkitpointerlockchange', lockChange, false);
    }
}

function onMouseUp()
{
    mouseDown=false;
    // cgl.canvas.style.cursor='url(/ui/img/rotate.png),pointer';
            
    if(doLockPointer)
    {
        document.removeEventListener('pointerlockchange', lockChange, false);
        document.removeEventListener('mozpointerlockchange', lockChange, false);
        document.removeEventListener('webkitpointerlockchange', lockChange, false);

        if(document.exitPointerLock) document.exitPointerLock();
        document.removeEventListener("mousemove", onmousemove, false);
    }
}

function lockChange()
{
    var el=op.patch.cgl.canvas;

    if (document.pointerLockElement === el || document.mozPointerLockElement === el || document.webkitPointerLockElement === el)
    {
        document.addEventListener("mousemove", onmousemove, false);
        console.log("listening...");
    }
}

function onMouseEnter(e)
{
    // cgl.canvas.style.cursor='url(/ui/img/rotate.png),pointer';
}

initialRadius.onValueChange(function()
{
    radius=initialRadius.get();
    reset();
});

initialX.onValueChange(function()
{
    px=percX=(initialX.get()*Math.PI*2);
});

initialAxis.onValueChange(function()
{
    py=percY=(initialAxis.get()-0.5);
    eye=circlePos(percY);
});

var onMouseWheel=function(event)
{
    if(allowZooming.get())
    {
        var delta=CGL.getWheelSpeed(event)*0.06;
        radius+=(parseFloat(delta))*1.2;

        eye=circlePos(percY);
        event.preventDefault();
    }
};

var ontouchstart=function(event)
{
    doLockPointer=false;
    if(event.touches && event.touches.length>0) onMouseDown(event.touches[0]);
};

var ontouchend=function(event)
{
    doLockPointer=false;
    onMouseUp();
};

var ontouchmove=function(event)
{
    doLockPointer=false;
    if(event.touches && event.touches.length>0) onmousemove(event.touches[0]);
};

active.onChange=function()
{
    if(active.get())bind();
        else unbind();
}


this.setElement=function(ele)
{
    unbind();
    element=ele;
    bind();
}

function bind()
{
    element.addEventListener('mousemove', onmousemove);
    element.addEventListener('mousedown', onMouseDown);
    element.addEventListener('mouseup', onMouseUp);
    element.addEventListener('mouseleave', onMouseUp);
    element.addEventListener('mouseenter', onMouseEnter);
    element.addEventListener('contextmenu', function(e){e.preventDefault();});
    element.addEventListener('wheel', onMouseWheel);

    element.addEventListener('touchmove', ontouchmove);
    element.addEventListener('touchstart', ontouchstart);
    element.addEventListener('touchend', ontouchend);
}

function unbind()
{
    if(!element)return;
    
    element.removeEventListener('mousemove', onmousemove);
    element.removeEventListener('mousedown', onMouseDown);
    element.removeEventListener('mouseup', onMouseUp);
    element.removeEventListener('mouseleave', onMouseUp);
    element.removeEventListener('mouseenter', onMouseUp);
    element.removeEventListener('wheel', onMouseWheel);

    element.removeEventListener('touchmove', ontouchmove);
    element.removeEventListener('touchstart', ontouchstart);
    element.removeEventListener('touchend', ontouchend);
}

eye=circlePos(0);
this.setElement(cgl.canvas);


bind();

initialX.set(0.25);
initialRadius.set(0.05);


};

Ops.Gl.Matrix.OrbitControls.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Fract
// 
// **************************************************************

Ops.Math.Fract = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="Fract";

var value=op.inValue("Value");
var result=op.outValue("Result");

value.onChange=function()
{
    result.set(value.get()-Math.floor(value.get()));
};

};

Ops.Math.Fract.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Divide
// 
// **************************************************************

Ops.Math.Divide = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const number1 = op.addInPort(new Port(op, "number1"));
const number2 = op.addInPort(new Port(op, "number2"));
const result = op.addOutPort(new Port(op, "result"));

const exec = function() {
    result.set( number1.get() / number2.get() );
};

number1.set(1);
number2.set(1);

number1.onValueChanged = exec;
number2.onValueChanged = exec;
exec();


};

Ops.Math.Divide.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Compare.Greater
// 
// **************************************************************

Ops.Math.Compare.Greater = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='Greater';
var result=op.addOutPort(new Port(op,"result"));
var number1=op.addInPort(new Port(op,"number1"));
var number2=op.addInPort(new Port(op,"number2"));

function exec()
{
    result.set(number1.get()>number2.get());
}

number1.onValueChanged=exec;
number2.onValueChanged=exec;


};

Ops.Math.Compare.Greater.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.TransformView
// 
// **************************************************************

Ops.Gl.Matrix.TransformView = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="TransformView";

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var posX=op.addInPort(new Port(op,"posX"),0);
var posY=op.addInPort(new Port(op,"posY"),0);
var posZ=op.addInPort(new Port(op,"posZ"),0);

var scale=op.addInPort(new Port(op,"scale"));

var rotX=op.addInPort(new Port(op,"rotX"));
var rotY=op.addInPort(new Port(op,"rotY"));
var rotZ=op.addInPort(new Port(op,"rotZ"));

var cgl=op.patch.cgl;
var vPos=vec3.create();
var vScale=vec3.create();
var transMatrix = mat4.create();
mat4.identity(transMatrix);

var doScale=false;
var doTranslate=false;

var translationChanged=true;
var scaleChanged=true;
var rotChanged=true;

render.onTriggered=function()
{
    
    

    
    
    var updateMatrix=false;
    if(translationChanged)
    {
        updateTranslation();
        updateMatrix=true;
    }
    if(scaleChanged)
    {
        updateScale();
        updateMatrix=true;
    }
    if(rotChanged)
    {
        updateMatrix=true;
    }
    if(updateMatrix)doUpdateMatrix();

    cgl.pushViewMatrix();
    mat4.multiply(cgl.vMatrix,cgl.vMatrix,transMatrix);










    trigger.trigger();
    cgl.popViewMatrix();
    
    if(CABLES.UI && gui.patch().isCurrentOp(op)) 
        gui.setTransformGizmo(
            {
                posX:posX,
                posY:posY,
                posZ:posZ,
            });

    
};

op.transform3d=function()
{
    return {
            pos:[posX,posY,posZ]
        };
    
};

var doUpdateMatrix=function()
{
    mat4.identity(transMatrix);
    if(doTranslate)mat4.translate(transMatrix,transMatrix, vPos);

    if(rotX.get()!==0)mat4.rotateX(transMatrix,transMatrix, rotX.get()*CGL.DEG2RAD);
    if(rotY.get()!==0)mat4.rotateY(transMatrix,transMatrix, rotY.get()*CGL.DEG2RAD);
    if(rotZ.get()!==0)mat4.rotateZ(transMatrix,transMatrix, rotZ.get()*CGL.DEG2RAD);

    if(doScale)mat4.scale(transMatrix,transMatrix, vScale);
    rotChanged=false;
};

function updateTranslation()
{
    doTranslate=false;
    if(posX.get()!==0.0 || posY.get()!==0.0 || posZ.get()!==0.0) doTranslate=true;
    vec3.set(vPos, posX.get(),posY.get(),posZ.get());
    translationChanged=false;
}

function updateScale()
{
    doScale=false;
    if(scale.get()!==0.0)doScale=true;
    vec3.set(vScale, scale.get(),scale.get(),scale.get());
    scaleChanged=false;
}

var translateChanged=function()
{
    translationChanged=true;
};

var scaleChanged=function()
{
    scaleChanged=true;
};

var rotChanged=function()
{
    rotChanged=true;
};


rotX.onChange=rotChanged;
rotY.onChange=rotChanged;
rotZ.onChange=rotChanged;

scale.onChange=scaleChanged;

posX.onChange=translateChanged;
posY.onChange=translateChanged;
posZ.onChange=translateChanged;

rotX.set(0.0);
rotY.set(0.0);
rotZ.set(0.0);

scale.set(1.0);

posX.set(0.0);
posY.set(0.0);
posZ.set(0.0);

doUpdateMatrix();



};

Ops.Gl.Matrix.TransformView.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Star
// 
// **************************************************************

Ops.Gl.Meshes.Star = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.inFunction("render");
var segments=op.inValue('segments',40);
var radius=op.inValue('radius',1);
var shape=op.inValueSelect('Shape',['Star','Saw','Gear'],'Star');
var outerRadius=op.inValue('Length',1.5);
var percent=op.inValueSlider('percent');

var fill=op.inValueBool("Fill");

var trigger=op.outFunction('trigger');
var geomOut=op.addOutPort(new Port(op,"geometry",OP_PORT_TYPE_OBJECT));

geomOut.ignoreValueSerialize=true;
var cgl=op.patch.cgl;


var oldPrim=0;
var shader=null;
render.onTriggered=function()
{
    if(op.instanced(render))return;
    
    shader=cgl.getShader();
    if(!shader)return;
    oldPrim=shader.glPrimitive;
    

    mesh.render(shader);
    trigger.trigger();

    shader.glPrimitive=oldPrim;
};

percent.set(1);

var geom=new CGL.Geometry("circle");
var mesh=null;
var lastSegs=-1;
function calc()
{
    var segs=Math.max(3,Math.floor(segments.get()));
    
    geom.clear();

    var faces=[];
    // var texCoords=[];
    var vertexNormals=[];

    var i=0,degInRad=0;
    var oldPosX=0,oldPosY=0;
    var oldPosXTexCoord=0,oldPosYTexCoord=0;

    var oldPosXIn=0,oldPosYIn=0;
    var oldPosXTexCoordIn=0,oldPosYTexCoordIn=0;

    var posxTexCoordIn=0,posyTexCoordIn=0;
    var posxTexCoord=0,posyTexCoord=0;
    var posx=0,posy=0;

    var verts=[];
    var outX=0,outY=0;

    var imode=0;
    if(shape.get()=="Saw")imode=1;
    if(shape.get()=="Gear")imode=2;

    var cycleGear=true;    

    for (i=0; i <= segs*percent.get(); i++)
    {
        degInRad = (360/segs)*i*CGL.DEG2RAD;
        posx=Math.cos(degInRad)*radius.get();
        posy=Math.sin(degInRad)*radius.get();

        // saw mode
        cycleGear=!cycleGear;

        switch(imode)
        {
            case 0:
                outX=((posx+oldPosX)*0.5)*outerRadius.get();
                outY=((posy+oldPosY)*0.5)*outerRadius.get();
            break;

            case 1:
                outX=(posx)*outerRadius.get();
                outY=(posy)*outerRadius.get();
            break;

            case 2:
                if(cycleGear)
                {
                    outX=(posx)*outerRadius.get();
                    outY=(posy)*outerRadius.get();
    
                    degInRad = (360/segs)*(i-1)*CGL.DEG2RAD;
                    var ooutX=Math.cos(degInRad)*radius.get();
                    var ooutY=Math.sin(degInRad)*radius.get();
    
                    ooutX*=outerRadius.get();
                    ooutY*=outerRadius.get();
                    
                    faces.push(
                            [ooutX,ooutY,0],
                            [outX,outY,0],
                            [oldPosX,oldPosY,0]
                            );
                    
                }

            break;
        }


        // if(mapping.get()=='flat')
        // {
        //     posxTexCoord=(Math.cos(degInRad)+1.0)/2;
        //     posyTexCoord=1.0-(Math.sin(degInRad)+1.0)/2;
        //     posxTexCoordIn=0.5;
        //     posyTexCoordIn=0.5;
        // }
        // else if(mapping.get()=='round')
        // {
        //     posxTexCoord=1.0-i/segs;
        //     posyTexCoord=0;
        //     posxTexCoordIn=posxTexCoord;
        //     posyTexCoordIn=1;
        // }

        if(fill.get())
            faces.push(
                  [posx,posy,0],
                  [oldPosX,oldPosY,0],
                  [0,0,0]
                  );


        if(imode!=2 || cycleGear)
        {
            faces.push(
                    [posx,posy,0],
                    [oldPosX,oldPosY,0],
                    [outX,outY,0]
                    );
        }

        // oldPosXTexCoord=posxTexCoord;
        // oldPosYTexCoord=posyTexCoord;
        
        oldPosX=posx;
        oldPosY=posy;
    }
  
    geom=CGL.Geometry.buildFromFaces(faces);
    geom.vertexNormals=vertexNormals;
    // geom.texCoords=texCoords;
    geom.mapTexCoords2d();

    geomOut.set(null);
    geomOut.set(geom);
    
    if(geom.vertices.length==0)return;
    if(!mesh)mesh=new CGL.Mesh(cgl,geom);
    mesh.setGeom(geom);
}

// var mapping=op.addInPort(new Port(op,"mapping",OP_PORT_TYPE_VALUE,{display:'dropdown',values:['flat','round']}));
// mapping.set('flat');
// mapping.onValueChange(calc);

segments.onChange=calc;
radius.onChange=calc;
percent.onChange=calc;
shape.onChange=calc;
fill.onChange=calc;
outerRadius.onChange=calc;
calc();



};

Ops.Gl.Meshes.Star.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Vignette2
// 
// **************************************************************

Ops.Gl.TextureEffects.Vignette2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["vignette_frag"]="IN vec2 texCoord;\nUNI sampler2D tex;\nUNI float lensRadius1;\nUNI float aspect;\nUNI float amount;\nUNI float sharp;\n\nUNI float r,g,b;\n\nvoid main()\n{\n   vec4 vcol=vec4(r,g,b,1.0);\n   vec4 col=texture2D(tex,texCoord);\n   vec2 tcPos=vec2(texCoord.x,(texCoord.y-0.5)*aspect+0.5);\n   float dist = distance(tcPos, vec2(0.5,0.5));\n   float am = (1.0-smoothstep( (lensRadius1+0.5), (lensRadius1*0.99+0.5)*sharp, dist));\n\n   col=mix(col,vcol,am*amount);\n\n   gl_FragColor = col;\n}\n";
op.name="Vignette";

var render=op.inFunction("render");
var trigger=op.outFunction("trigger");

var amount=op.inValueSlider("Amount",1);
// var lensRadius1=op.inValue("lensRadius1",0.8);
var lensRadius1=op.inValueSlider("Radius",0.5);
var sharp=op.inValueSlider("sharp",0.25);
var aspect=op.inValue("Aspect",1);


var r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true'}));
var g=op.addInPort(new Port(op,"g",OP_PORT_TYPE_VALUE,{ display:'range' }));
var b=op.addInPort(new Port(op,"b",OP_PORT_TYPE_VALUE,{ display:'range' }));


var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);


shader.setSource(shader.getDefaultVertexShader(),attachments.vignette_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var uniLensRadius1=new CGL.Uniform(shader,'f','lensRadius1',lensRadius1);
var uniaspect=new CGL.Uniform(shader,'f','aspect',aspect);
var uniAmount=new CGL.Uniform(shader,'f','amount',amount);
var unisharp=new CGL.Uniform(shader,'f','sharp',sharp);

var unir=new CGL.Uniform(shader,'f','r',r);
var unig=new CGL.Uniform(shader,'f','g',g);
var unib=new CGL.Uniform(shader,'f','b',b);


render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Vignette2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.Array3xSubdivideNew
// 
// **************************************************************

Ops.Array.Array3xSubdivideNew = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var inArr=op.inArray("Points");
var subDivs=op.inValue("Num Subdivs",5);
var bezier=op.inValueBool("Smooth",true);
var bezierEndPoints=op.inValueBool("Bezier Start/End Points",true);

var result=op.outArray("Result");

subDivs.onChange=calc;
bezier.onChange=calc;
inArr.onChange=calc;
bezierEndPoints.onChange=calc;

function ip(x0,x1,x2,t)//Bezier 
{
    var r =(x0 * (1-t) * (1-t) + 2 * x1 * (1 - t)* t + x2 * t * t);
    return r;
}

var arr=[];

function calc()
{
    if(!inArr.get())
    {
        result.set(null);
        return;
    }
    const subd=Math.floor(subDivs.get());
    var inPoints=inArr.get();
    
    if(inPoints.length<3)return;
    
    let i=0;
    let j=0;
    let k=0;
    let count=0;

    if(subd>0 && !bezier.get())
    {
        const newLen=(inPoints.length-3)*subd+3;
        if(newLen!=arr.length)
        {
            op.log("resize subdiv arr");
            arr.length=newLen;
        }

        count=0;
        for(i=0;i<inPoints.length-3;i+=3)
        {
            for(j=0;j<subd;j++)
            {
                for(k=0;k<3;k++)
                {
                    arr[count]=
                        inPoints[i+k]+ ( inPoints[i+k+3] - inPoints[i+k] ) * j/subd ;
                    count++;
                }
            }
        }
        arr[newLen-3]=inPoints[inPoints.length-3];
        arr[newLen-2]=inPoints[inPoints.length-2];
        arr[newLen-1]=inPoints[inPoints.length-1];
    }
    else
    if(subd>0 && bezier.get() )
    {
        var newLen=(inPoints.length-6)*(subd-1);
        if(bezierEndPoints.get())newLen+=6;
        
        if(newLen!=arr.length) arr.length=Math.floor(Math.abs(newLen));
        count=0;
        
        if(bezierEndPoints.get())
        {
            arr[0]=inPoints[0];
            arr[1]=inPoints[1];
            arr[2]=inPoints[2];
            count=3;
        }

        for(i=3;i<inPoints.length-3;i+=3)
        {
            for(j=0;j<subd;j++)
            {
                for(k=0;k<3;k++)
                {
                    var p=ip(
                            (inPoints[i+k-3]+inPoints[i+k])/2,
                            inPoints[i+k+0],
                            (inPoints[i+k+3]+inPoints[i+k+0])/2,
                            j/subd
                            );
                    arr[count]=p;
                    count++;
                }
            }
        }
        
        if(bezierEndPoints.get())
        {
            arr[count-0]=inPoints[inPoints.length-3];
            arr[count+1]=inPoints[inPoints.length-2];
            arr[count+2]=inPoints[inPoints.length-1];
        }
    }

    result.set(null);
    result.set(arr);
}


};

Ops.Array.Array3xSubdivideNew.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TesselateGeometry
// 
// **************************************************************

Ops.Gl.TesselateGeometry = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var inGeom=op.inObject("Geometry");
var inTimes=op.inValueInt("Iterations");
var outGeom=op.outObject("Result");
var outVertices=op.outValue("Num Vertices");

inGeom.onChange=update;
inTimes.onChange=update;

function tesselateTC(tc, x1,y1, x2,y2,  x3,y3)
{
    tc.push( x1 );
    tc.push( y1 );

    tc.push( (x1+x2)/2 );
    tc.push( (y1+y2)/2 );

    tc.push( (x1+x3)/2 );
    tc.push( (y1+y3)/2 );

    // --

    tc.push( (x1+x2)/2 );
    tc.push( (y1+y2)/2 );

    tc.push( x2 );
    tc.push( y2 );

    tc.push( (x2+x3)/2 );
    tc.push( (y2+y3)/2 );

    // --
    
    tc.push( (x2+x3)/2 );
    tc.push( (y2+y3)/2 );

    tc.push( x3 );
    tc.push( y3 );

    tc.push( (x1+x3)/2 );
    tc.push( (y1+y3)/2 );

    // --

    tc.push( (x1+x2)/2 );
    tc.push( (y1+y2)/2 );

    tc.push( (x2+x3)/2 );
    tc.push( (y2+y3)/2 );

    tc.push( (x1+x3)/2 );
    tc.push( (y1+y3)/2 );
}

function tesselate(vertices, x1,y1,z1, x2,y2,z2, x3,y3,z3)
{
    
    vertices.push( x1 );
    vertices.push( y1 );
    vertices.push( z1 );
    
    

    vertices.push( (x1+x2)/2 );
    vertices.push( (y1+y2)/2 );
    vertices.push( (z1+z2)/2 );

    vertices.push( (x1+x3)/2 );
    vertices.push( (y1+y3)/2 );
    vertices.push( (z1+z3)/2 );

    // --

    vertices.push( (x1+x2)/2 );
    vertices.push( (y1+y2)/2 );
    vertices.push( (z1+z2)/2 );

    vertices.push( x2 );
    vertices.push( y2 );
    vertices.push( z2 );

    vertices.push( (x2+x3)/2 );
    vertices.push( (y2+y3)/2 );
    vertices.push( (z2+z3)/2 );

    // --
    
    vertices.push( (x2+x3)/2 );
    vertices.push( (y2+y3)/2 );
    vertices.push( (z2+z3)/2 );
    
    vertices.push( x3 );
    vertices.push( y3 );
    vertices.push( z3 );

    vertices.push( (x1+x3)/2 );
    vertices.push( (y1+y3)/2 );
    vertices.push( (z1+z3)/2 );

    // --

    vertices.push( (x1+x2)/2 );
    vertices.push( (y1+y2)/2 );
    vertices.push( (z1+z2)/2 );
    
    vertices.push( (x2+x3)/2 );
    vertices.push( (y2+y3)/2 );
    vertices.push( (z2+z3)/2 );
    
    vertices.push( (x1+x3)/2 );
    vertices.push( (y1+y3)/2 );
    vertices.push( (z1+z3)/2 );
}


function tesselateGeom(oldGeom)
{
    var geom=new CGL.Geometry();
    var vertices=[];
    var norms=[];
    var tc=[];
    
    var i,j,k;
    
    if(oldGeom.verticesIndices.length>0)
    {
        for(i=0;i<oldGeom.verticesIndices.length;i+=3)
        {
            
            for(j=0;j<4;j++)
            for(k=0;k<3;k++)
            {
                norms.push(
                    oldGeom.vertexNormals[oldGeom.verticesIndices[i+k]*3+0],
                    oldGeom.vertexNormals[oldGeom.verticesIndices[i+k]*3+1],
                    oldGeom.vertexNormals[oldGeom.verticesIndices[i+k]*3+2]
                    );
            }

            tesselate(vertices, 
                oldGeom.vertices[oldGeom.verticesIndices[i+0]*3+0],
                oldGeom.vertices[oldGeom.verticesIndices[i+0]*3+1],
                oldGeom.vertices[oldGeom.verticesIndices[i+0]*3+2],
        
                oldGeom.vertices[oldGeom.verticesIndices[i+1]*3+0],
                oldGeom.vertices[oldGeom.verticesIndices[i+1]*3+1],
                oldGeom.vertices[oldGeom.verticesIndices[i+1]*3+2],
        
                oldGeom.vertices[oldGeom.verticesIndices[i+2]*3+0],
                oldGeom.vertices[oldGeom.verticesIndices[i+2]*3+1],
                oldGeom.vertices[oldGeom.verticesIndices[i+2]*3+2]
                );


            tesselateTC(tc, 
                oldGeom.texCoords[oldGeom.verticesIndices[i+0]*2+0],
                oldGeom.texCoords[oldGeom.verticesIndices[i+0]*2+1],

                oldGeom.texCoords[oldGeom.verticesIndices[i+1]*2+0],
                oldGeom.texCoords[oldGeom.verticesIndices[i+1]*2+1],

                oldGeom.texCoords[oldGeom.verticesIndices[i+2]*2+0],
                oldGeom.texCoords[oldGeom.verticesIndices[i+2]*2+1]
                );
        }
    }
    else
    {
        if(oldGeom.vertices.length>0)
        {
            for(i=0;i<oldGeom.vertices.length;i+=9)
            {
                for(j=0;j<4;j++)
                for(k=0;k<9;k++)
                {
                    norms.push(
                        oldGeom.vertexNormals[i+k]
                    );
                }

                tesselate(vertices,
                    oldGeom.vertices[i+0],
                    oldGeom.vertices[i+1],
                    oldGeom.vertices[i+2],
                    
                    oldGeom.vertices[i+3],
                    oldGeom.vertices[i+4],
                    oldGeom.vertices[i+5],
                    
                    oldGeom.vertices[i+6],
                    oldGeom.vertices[i+7],
                    oldGeom.vertices[i+8]
                );

                tesselateTC(tc, 
                    oldGeom.texCoords[i/9*6+0],
                    oldGeom.texCoords[i/9*6+1],
    
                    oldGeom.texCoords[i/9*6+2],
                    oldGeom.texCoords[i/9*6+3],
    
                    oldGeom.texCoords[i/9*6+4],
                    oldGeom.texCoords[i/9*6+5]
    
                    );

            }
        }
    }
    
    // console.log('norms',norms);
    geom.vertexNormals=norms;
    geom.setVertices(vertices);
    geom.setTexCoords(tc);
    return geom;
    
}

function update()
{
    var geom=inGeom.get();
    if(!geom)return;
    
    for(var i=0;i<inTimes.get();i++)
    {
        geom=tesselateGeom(geom);
    }

    outVertices.set(geom.vertices.length/3);
    
    outGeom.set(null);
    outGeom.set(geom);

}




};

Ops.Gl.TesselateGeometry.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Depth
// 
// **************************************************************

Ops.Gl.Depth = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var cgl=op.patch.cgl;

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var clear=op.addInPort(new Port(op,"clear depth",OP_PORT_TYPE_VALUE,{ display:'bool' }));
var enable=op.addInPort(new Port(op,"enable depth testing",OP_PORT_TYPE_VALUE,{ display:'bool' }));
var write=op.addInPort(new Port(op,"write to depth buffer",OP_PORT_TYPE_VALUE,{ display:'bool' }));

var depthFunc=op.addInPort(new Port(op,"ratio",OP_PORT_TYPE_VALUE ,{display:'dropdown',values:['never','always','less','less or equal','greater', 'greater or equal','equal','not equal']} ));
var theDepthFunc=cgl.gl.LEQUAL;

depthFunc.onValueChanged=updateFunc;
depthFunc.set('less or equal');
clear.set(false);
enable.set(true);
write.set(true);

function updateFunc()
{
    if(depthFunc.get()=='never') theDepthFunc=cgl.gl.NEVER;
    if(depthFunc.get()=='always') theDepthFunc=cgl.gl.ALWAYS;
    if(depthFunc.get()=='less') theDepthFunc=cgl.gl.LESS;
    if(depthFunc.get()=='less or equal') theDepthFunc=cgl.gl.LEQUAL;
    if(depthFunc.get()=='greater') theDepthFunc=cgl.gl.GREATER;
    if(depthFunc.get()=='greater or equal') theDepthFunc=cgl.gl.GEQUAL;
    if(depthFunc.get()=='equal') theDepthFunc=cgl.gl.EQUAL;
    if(depthFunc.get()=='not equal') theDepthFunc=cgl.gl.NOTEQUAL;
}

render.onTriggered=function()
{
    if(clear.get()) cgl.gl.clear(cgl.gl.DEPTH_BUFFER_BIT);

    if(!enable.get()) cgl.gl.disable(cgl.gl.DEPTH_TEST);
        else cgl.gl.enable(cgl.gl.DEPTH_TEST);

    if(!write.get()) cgl.gl.depthMask(false);
        else cgl.gl.depthMask(true);

    cgl.gl.depthFunc(theDepthFunc);

    trigger.trigger();

    cgl.gl.enable(cgl.gl.DEPTH_TEST);
    cgl.gl.depthMask(true);
    cgl.gl.depthFunc(cgl.gl.LEQUAL);
};

};

Ops.Gl.Depth.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.LissajouseSpline
// 
// **************************************************************

Ops.Math.LissajouseSpline = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var inForm=op.inValueSelect("Formula",[1,2,3],1);

var inA=op.inValueInt("A",5);
var inB=op.inValueInt("B",4);
var inC=op.inValueInt("C",1);
var inD=op.inValueInt("D",2);

var result=op.outArray("Result");

inForm.onChange=inA.onChange=inB.onChange=inC.onChange=inD.onChange=calc;
calc();

function calc()
{
    var numPoints=13200;
    var step=40;
    
    var arr=[];
    var x=0,y=0,z=0;
    var form=parseInt(inForm.get());
    var th=0.03;

    for(var i = 0; i < numPoints; i+=step)
    {
        var index=i/step;
        
        if(form==1)
        {
            x=Math.sin( (i * inA.get()) * 0.001 );
            y=Math.cos( (i * inB.get()) * 0.001 );
            z=Math.sin( (i * inC.get()) * 0.001 );
        }
        else if(form==2)
        {
            x=(Math.cos( (i * inA.get()) * 0.001 )+Math.cos( (i * inB.get()) * 0.001 ) )/2;
            y=(Math.sin( (i * inA.get()) * 0.001 )+Math.sin( (i * inC.get()) * 0.001 ) )/2;
            z=(Math.sin( (i * inD.get()) * 0.001 ));
        }
        else if(form==3)
        {
            x=(Math.sin( (i * inA.get()) * 0.001 )*(1+Math.cos( (i * inB.get()) * 0.001 ) ))/2;
            y=(Math.sin( (i * inA.get()) * 0.001 )*(1+Math.sin( (i * inC.get()) * 0.001 ) ))/2;
            z=(Math.sin( (i * inD.get()) * 0.001 ));
        }

        arr[index*3+0] = x;
        arr[index*3+1] = y;
        arr[index*3+2] = z;

        if(index>10 && Math.abs(x-arr[0])<th && Math.abs(y-arr[1])<th && Math.abs(z-arr[2])<th)
        {
            // this method sucks but kinda works....
            break;
        }
    }

    result.set(arr);
}



};

Ops.Math.LissajouseSpline.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.ArrayMultiply
// 
// **************************************************************

Ops.Array.ArrayMultiply = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var inArray=op.inArray("In");
var inValue=op.inValue("Value");
var outArray=op.outArray("Result");


var newArr=[];
outArray.set(newArr);
inArray.onChange=
inValue.onChange=inArray.onChange=function()
{
    var arr=inArray.get();
    if(!arr)return;
    
    var mul=inValue.get();
    
    if(newArr.length!=arr.length)newArr.length=arr.length;
    
    for(var i=0;i<arr.length;i++)
    {
        newArr[i]=arr[i]*mul;
    }
    
    
    outArray.set(null);
    outArray.set(newArr);

    
    
};

};

Ops.Array.ArrayMultiply.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.RenderGeometry
// 
// **************************************************************

Ops.Gl.RenderGeometry = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));

var geometry=op.addInPort(new Port(op,"Geometry",OP_PORT_TYPE_OBJECT));
geometry.ignoreValueSerialize=true;

var updateAll=op.inValueBool('Update All',true);
var updateFaces=op.inValueBool('Update Face Indices',false);
var updateVerts=op.inValueBool('Update Vertices',false);
var updateTexcoords=op.inValueBool('Update Texcoords',false);
var vertNums=op.inValueBool('Vertex Numbers',true);

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

vertNums.onChange=
    geometry.onChange=update;

var mesh=null;

render.onTriggered=function()
{
    if(mesh) mesh.render(op.patch.cgl.getShader());
    trigger.trigger();
};


function update()
{

    var geom=geometry.get();
    
    if(geom)
    {
        // if(mesh)mesh.dispose();
        if(!mesh)
        {
            mesh=new CGL.Mesh(op.patch.cgl,geom);
            mesh.addVertexNumbers=vertNums.get();
            mesh.setGeom(geom); 
        }
        

        if(updateFaces.get() || updateAll.get())
        {
            mesh.setVertexIndices(geom.verticesIndices);
        }

        if(updateTexcoords.get() || updateAll.get())
        {
            mesh.updateTexCoords(geom);
        }

        if(updateVerts.get() || updateAll.get())
        {
            mesh.updateVertices(geom);
        }

        mesh.addVertexNumbers=vertNums.get();
        
    }
    else
    {
        mesh=null;
    }
}



};

Ops.Gl.RenderGeometry.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Exp.Gl.SplineDeform
// 
// **************************************************************

Ops.Exp.Gl.SplineDeform = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["splinedeform_vert"]="\n\n\npos.xyz=MOD_splineDeform(pos,0);\n\nnorm=normalize(norm*MOD_splineDeform(pos,1));\n";
attachments["splinedeform_head_vert"]="\nUNI vec3 MOD_points[SPLINE_POINTS];\nUNI float MOD_offset;\nUNI float MOD_size;\n\nvec3 ip(float v)\n{\n    int index0=int(abs(mod(v,max(0.0,float(SPLINE_POINTS)))));\n    int index1=int(abs(mod(v+1.0,max(0.0,float(SPLINE_POINTS)))));\n    float fr=fract(abs(mod(v,max(0.0,float(SPLINE_POINTS)))));\n    return mix( MOD_points[index0] ,MOD_points[index1] ,fr);\n}\n\nvec3 MOD_splineDeform(vec4 pos, int isNormal)\n{\n    float off=MOD_offset+( (pos.x)*MOD_size);\n\n    vec3 bezierPointPrevious=ip(off-1.0);\n    vec3 bezierPoint=ip(off);\n    vec3 bezierPointNext=ip(off+1.0);\n\n    vec3 _Up=vec3(0.0,1.0,0.0);\n    vec3 _Forward=vec3(1.0,0.0,0.0);\n    vec3 _Right=vec3(0.0,0.0,1.0);\n\n\tfloat vertexForward = pos.x * _Forward.x + pos.y * _Forward.y + pos.z * _Forward.z;\n\tfloat vertexRight = pos.x * _Right.x + pos.y * _Right.y + pos.z * _Right.z;\n\tfloat vertexUp = pos.x * _Up.x + pos.y * _Up.y + pos.z * _Up.z;\n\n\tfloat angle = atan(vertexUp,vertexRight);\n\tfloat radius = length(vec2(vertexRight, vertexUp));\n\n\tvec3 forward = normalize(bezierPointNext - bezierPoint);\n\tvec3 backward = normalize(bezierPointPrevious - bezierPoint);\n\tvec3 up = normalize(cross(forward, backward));\n\tvec3 right = normalize(cross(forward, up));\n\n    if(isNormal==0)\n    {\n        pos.xyz = bezierPoint + right * cos(angle) * radius + up * sin(angle) * radius;\n    }\n    else \n    {\n        pos.xyz = pos.xyz-( (bezierPoint) + right * cos(angle) * radius + up * sin(angle) * radius);\n    }\n\n    return pos.xyz;\n}";
op.render=op.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
op.trigger=op.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));

var inSize=op.inValue("Size",1);
var inOffset=op.inValue("offset");
var inPoints=op.inArray("Points");

var cgl=op.patch.cgl;
var shader=null;
var updateUniformPoints=true;
var pointArray=new Float32Array(99);
var srcHeadVert=attachments.splinedeform_head_vert||'';
var srcBodyVert=attachments.splinedeform_vert||'';

var moduleVert=null;

function removeModule()
{
    if(shader && moduleVert) shader.removeModule(moduleVert);
    shader=null;
}

inPoints.onChange=function()
{
    if(inPoints.get())
    {
        pointArray=inPoints.get();
        updateUniformPoints=true;
        console.log(inPoints.get().length,"points");
    }
};

op.render.onLinkChanged=removeModule;
var ready=false;
op.render.onTriggered=function()
{
    if(!cgl.getShader())
    {
         op.trigger.trigger();
         return;
    }

    if(cgl.getShader()!=shader)
    {
        if(shader) removeModule();
        shader=cgl.getShader();

        moduleVert=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_VERTEX_POSITION',
                srcHeadVert:srcHeadVert,
                srcBodyVert:srcBodyVert
            });

        inSize.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'size',inSize);
        inOffset.offset=new CGL.Uniform(shader,'f',moduleVert.prefix+'offset',inOffset);

        op.uniPoints=new CGL.Uniform(shader,'3f[]',moduleVert.prefix+'points',new Float32Array([0,0,0,0,0,0]));
        ready=false;
        updateUniformPoints=true;
    }
    
    if(shader && updateUniformPoints && pointArray && pointArray.length>=3)
    {
        if(shader.getDefine("SPLINE_POINTS")!=Math.floor(pointArray.length/3))
        {
            shader.define('SPLINE_POINTS',Math.floor(pointArray.length/3));
            console.log('SPLINE_POINTS',shader.getDefine("SPLINE_POINTS"));
        }

        op.uniPoints.setValue(pointArray);
        updateUniformPoints=false;
        ready=true;
    }

    if(!shader || !ready)return;

    op.trigger.trigger();
};


};

Ops.Exp.Gl.SplineDeform.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.NthTrigger2
// 
// **************************************************************

Ops.Trigger.NthTrigger2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var DEFAULT_NTH = 5;

// inputs
var exePort = op.inFunctionButton('Execute');
var nthPort = op.inValue('Nth', DEFAULT_NTH);

// outputs
var triggerPort = op.outFunction('Next');

var count = 0;
var nth = DEFAULT_NTH;

exePort.onTriggered = onExeTriggered;
nthPort.onChange = valueChanged;

function onExeTriggered() {
    count++;
    if(count % nth === 0) {
        count = 0;
        triggerPort.trigger();
    }
}

function valueChanged() {
    nth = nthPort.get();
    count = 0;
}

};

Ops.Trigger.NthTrigger2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Patch.LoadingStatus
// 
// **************************************************************

Ops.Patch.LoadingStatus = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var self=this;
var cgl=op.patch.cgl;
var patch=op.patch;

this.exe=this.addInPort(new Port(this,"exe",OP_PORT_TYPE_FUNCTION));
this.finished=this.addOutPort(new Port(this,"finished",OP_PORT_TYPE_FUNCTION));
var result=this.addOutPort(new Port(this,"status",OP_PORT_TYPE_VALUE));
var isFinishedPort = op.outValue('all loaded', false);
var preRenderStatus=this.addOutPort(new Port(this,"preRenderStatus",OP_PORT_TYPE_VALUE));
var preRenderTimeFrames=this.addInPort(new Port(this,"preRenderTimes",OP_PORT_TYPE_VALUE));
var preRenderOps=op.inValueBool("PreRender Ops");
preRenderStatus.set(0);
this.numAssets=this.addOutPort(new Port(this,"numAssets",OP_PORT_TYPE_VALUE));
this.loading=this.addOutPort(new Port(this,"loading",OP_PORT_TYPE_FUNCTION));
var loadingFinished=op.outFunction("loading finished");//this.addOutPort(new Port(this,"loading finished",OP_PORT_TYPE_FUNCTION));

var finishedAll=false;
var preRenderTimes=[];
var firstTime=true;

var identTranslate=vec3.create();
vec3.set(identTranslate, 0,0,0);
var identTranslateView=vec3.create();
vec3.set(identTranslateView, 0,0,-2);

document.body.classList.add("cables-loading");


var prerenderCount=0;
var preRenderAnimFrame=function(t)
{
    var time=preRenderTimes[prerenderCount];

    preRenderStatus.set(prerenderCount/(preRenderTimeFrames.anim.keys.length-1));

    self.patch.timer.setTime(time);
    cgl.renderStart(cgl,identTranslate,identTranslateView);
    
    self.finished.trigger();

    cgl.gl.clearColor(0,0,0,1);
    cgl.gl.clear(cgl.gl.COLOR_BUFFER_BIT | cgl.gl.DEPTH_BUFFER_BIT);

    self.loading.trigger();
    

    cgl.renderEnd(cgl);
    prerenderCount++;
};

this.onAnimFrame=null;
var loadingIdPrerender='';

this.onLoaded=function()
{

    if(preRenderTimeFrames.isAnimated())
    if(preRenderTimeFrames.anim)
        for(var i=0;i<preRenderTimeFrames.anim.keys.length;i++)
            preRenderTimes.push( preRenderTimeFrames.anim.keys[i].time );

    preRenderTimes.push(1);
};

function checkPreRender()
{
    
    if(patch.loading.getProgress()>=1.0)
    {
        if(preRenderTimeFrames.anim && prerenderCount>=preRenderTimeFrames.anim.keys.length)
        {
            self.onAnimFrame=function(){};
            isFinishedPort.set(true);
            finishedAll=true;
            
        }
        else
        {
            setTimeout(checkPreRender,30);
        }
    }
    else
    {
        
        setTimeout(checkPreRender,100);
    }

}



var loadingId=patch.loading.start('delayloading','delayloading');
setTimeout(function()
{
    
    patch.loading.finished(loadingId);
},100);

this.exe.onTriggered= function()
{
    result.set(patch.loading.getProgress());
    self.numAssets.set(patch.loading.getNumAssets());

    if(patch.loading.getProgress()>=1.0 && finishedAll)
    {
        if(firstTime)
        {
            if(preRenderOps.get()) op.patch.preRenderOps();
            loadingFinished.trigger();
            self.patch.timer.setTime(0);
            self.patch.timer.play();
            isFinishedPort.set(true);
            firstTime=false;
        }

        self.finished.trigger();
        document.body.classList.remove("cables-loading");
        document.body.classList.add("cables-loaded");
    }
    else
    {
        if(!preRenderTimeFrames.anim)
        {
            finishedAll=true;
            self.onAnimFrame=function(){};
        }
        
        if(preRenderTimeFrames.anim && patch.loading.getProgress()>=1.0
            && prerenderCount<preRenderTimeFrames.anim.keys.length
        )
        {
            self.onAnimFrame=preRenderAnimFrame;
            checkPreRender();
            self.loading.trigger();
        }

        if(patch.loading.getProgress()<1.0)
        {
            self.loading.trigger();
            self.patch.timer.setTime(0);
            self.patch.timer.pause();
        }
    }

};

};

Ops.Patch.LoadingStatus.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ShaderEffects.ExplodeDividedMesh
// 
// **************************************************************

Ops.Gl.ShaderEffects.ExplodeDividedMesh = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["explode_divided_mesh_vert"]="UNI float MOD_dist;\n\nUNI float MOD_x;\nUNI float MOD_y;\nUNI float MOD_z;\n\nUNI float MOD_posx;\nUNI float MOD_posy;\nUNI float MOD_posz;\nUNI float MOD_size;\n\nUNI float MOD_mulx;\nUNI float MOD_muly;\nUNI float MOD_mulz;\n\nfloat MOD_rand(vec2 co)\n{\n    return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453);\n}\n\nvec4 MOD_deform(vec4 pos,vec3 normal,float index)\n{\n    index=floor(index/3.0);\n\n    vec4 p=abs(pos);\n    p.x+=MOD_x+0.01;\n    p.y+=MOD_y+0.01;\n    p.z+=MOD_z+0.01;\n    \n    vec4 pp=vec4(normal*(MOD_rand(vec2(index)) * MOD_dist-MOD_dist/2.0),1.0) * p;\n    \n    #ifdef ABSOLUTE\n        pp=abs(pp);\n    #endif\n\n    pp.x*=MOD_mulx;\n    pp.y*=MOD_muly;\n    pp.z*=MOD_mulz;\n    \n    \n    float MOD_falloff=0.2;\n    float distMul=distance(vec3(MOD_posx,MOD_posy,MOD_posz),pos.xyz);\n    distMul=1.0-smoothstep(MOD_falloff*MOD_size,MOD_size,distMul);\n\n    \n    \n    pos.xyz += distMul*pp.xyz;\n    \n    return pos;\n}\n";

var cgl=op.patch.cgl;

op.render=op.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
op.trigger=op.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));

var inDistance=op.inValue("Distance",1);
var inAbsolute=op.inValueBool("Absolute",false);

{
    var x=op.inValue("add x");
    var y=op.inValue("add y");
    var z=op.inValue("add z");

    var mulx=op.inValue("mul x",1);
    var muly=op.inValue("mul y",1);
    var mulz=op.inValue("mul z",1);

    var posx=op.inValue("x");
    var posy=op.inValue("y");
    var posz=op.inValue("z");
}

var inSize=op.inValue("Size",1);

var shader=null;

var srcHeadVert=attachments.explode_divided_mesh_vert;

var srcBodyVert=''
    .endl()+'pos=MOD_deform(pos,attrVertNormal,attrVertIndex);'
    .endl();
    
var moduleVert=null;

function removeModule()
{
    if(shader && moduleVert) shader.removeModule(moduleVert);
    shader=null;
}

var absoluteChanged=false;

inAbsolute.onChange=function()
{
    absoluteChanged=true;
};

op.render.onLinkChanged=removeModule;

op.render.onTriggered=function()
{
    if(!cgl.getShader())
    {
         op.trigger.trigger();
         return;
    }

    if(CABLES.UI && gui.patch().isCurrentOp(op)) 
        gui.setTransformGizmo(
            {
                posX:posx,
                posY:posy,
                posZ:posz
            });

    if(cgl.getShader()!=shader)
    {
        if(shader) removeModule();
        shader=cgl.getShader();

        moduleVert=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_VERTEX_POSITION',
                srcHeadVert:srcHeadVert,
                srcBodyVert:srcBodyVert
            });

        inDistance.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'dist',inDistance);

        x.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'x',x);
        y.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'y',y);
        z.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'z',z);

        mulx.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'mulx',mulx);
        muly.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'muly',muly);
        mulz.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'mulz',mulz);

        posx.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'posx',posx);
        posy.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'posy',posy);
        posz.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'posz',posz);

        inSize.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'size',inSize);
    }
    
    if(absoluteChanged)
    {
        absoluteChanged=false;
        if(inAbsolute.get()) shader.define("ABSOLUTE");
            else shader.removeDefine("ABSOLUTE");

    }
    
    if(!shader)return;

    op.trigger.trigger();
};















};

Ops.Gl.ShaderEffects.ExplodeDividedMesh.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.FaceCulling
// 
// **************************************************************

Ops.Gl.FaceCulling = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
op.trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

op.enable=op.addInPort(new Port(op,"enable",OP_PORT_TYPE_VALUE,{ display:'bool' }));
op.enable.set(true);

op.facing=op.addInPort(new Port(op,"facing",OP_PORT_TYPE_VALUE ,{display:'dropdown',values:['back','front','both']} ));
op.facing.set('back');

var cgl=op.patch.cgl;

var whichFace=cgl.gl.BACK;
op.render.onTriggered=function()
{
    if(op.enable.get()) cgl.gl.enable(cgl.gl.CULL_FACE);
    else cgl.gl.disable(cgl.gl.CULL_FACE);
    
    cgl.gl.cullFace(whichFace);

    op.trigger.trigger();

    cgl.gl.disable(cgl.gl.CULL_FACE);
};

op.facing.onValueChanged=function()
{
    whichFace=cgl.gl.BACK;
    if(op.facing.get()=='front')whichFace=cgl.gl.FRONT;
    if(op.facing.get()=='both')whichFace=cgl.gl.FRONT_AND_BACK;
};

};

Ops.Gl.FaceCulling.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.CalculateNormals
// 
// **************************************************************

Ops.Gl.CalculateNormals = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var geometry=op.addInPort(new Port(op,"Geometry",OP_PORT_TYPE_OBJECT));
var smoothNormals=op.addInPort(new Port(op,"Smooth",OP_PORT_TYPE_VALUE,{"display":"bool"}));
var forceZUp=op.addInPort(new Port(op,"Force Z Up",OP_PORT_TYPE_VALUE,{"display":"bool"}));

var geomOut=op.addOutPort(new Port(op,"Geometry Out",OP_PORT_TYPE_OBJECT));

geomOut.ignoreValueSerialize=true;
geometry.ignoreValueSerialize=true;

geometry.onValueChanged=calc;
smoothNormals.onValueChanged=calc;
forceZUp.onValueChanged=calc;

var geom=null;




function calc()
{
    if(!geometry.get())return;

    var geom=geometry.get().copy();

    if(!smoothNormals.get())geom.unIndex();

    geom.calculateNormals({
        "forceZUp":forceZUp.get()
    });

    geomOut.set(geom);
    
}



};

Ops.Gl.CalculateNormals.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.Bang
// 
// **************************************************************

Ops.Anim.Bang = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="Bang";

var inUpdate=op.inFunction("update");
var inBang=op.inFunctionButton("Bang");
var inDuration=op.inValue("Duration",0.1);

var outValue=op.outValue("Value");

var anim=new CABLES.TL.Anim();

var startTime=CABLES.now();

inBang.onTriggered=function()
{
    startTime=CABLES.now();
    
    anim.clear();
    anim.setValue(0,1);
    anim.setValue(inDuration.get(),0);
};

inUpdate.onTriggered=function()
{
    var v=anim.getValue((CABLES.now()-startTime)/1000);
    outValue.set(v);
};

};

Ops.Anim.Bang.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.RandomArray3x
// 
// **************************************************************

Ops.Array.RandomArray3x = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var numValues=op.addInPort(new Port(op, "numValues",OP_PORT_TYPE_VALUE));
var seed=op.addInPort(new Port(op,"random seed"));
var min=op.addInPort(new Port(op,"Min"));
var max=op.addInPort(new Port(op,"Max"));
var closed=op.inValueBool("Last == First");

var values=op.addOutPort(new Port(op, "values",OP_PORT_TYPE_ARRAY));
values.ignoreValueSerialize=true;

numValues.set(100);
min.set(-1);
max.set(1);

closed.onChange=max.onChange=init;
min.onChange=init;
numValues.onChange=init;
seed.onChange=init;
values.onLinkChanged=init;

var arr=[];
init();

function init()
{
    Math.randomSeed=seed.get();

    arr.length=Math.floor(numValues.get()*3) || 300;
    for(var i=0;i<arr.length;i+=3)
    {
        arr[i+0]=Math.seededRandom() * ( max.get() - min.get() ) + min.get() ;
        arr[i+1]=Math.seededRandom() * ( max.get() - min.get() ) + min.get() ;
        arr[i+2]=Math.seededRandom() * ( max.get() - min.get() ) + min.get() ;
    }

    if(closed.get())
    {
        arr[arr.length-3+0]=arr[0];
        arr[arr.length-3+1]=arr[1];
        arr[arr.length-3+2]=arr[2];
    }
    
    values.set(null);
    values.set(arr);
}


};

Ops.Array.RandomArray3x.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.RandomArray
// 
// **************************************************************

Ops.Array.RandomArray = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const numValues=op.inValueInt("numValues");

const seed=op.addInPort(new Port(op,"random seed"));
const min=op.addInPort(new Port(op,"Min"));
const max=op.addInPort(new Port(op,"Max"));

const values=op.addOutPort(new Port(op, "values",OP_PORT_TYPE_ARRAY));
values.ignoreValueSerialize=true;

numValues.set(100);
min.set(0);
max.set(1);

max.onValueChanged=init;
min.onValueChanged=init;
numValues.onValueChanged=init;
seed.onValueChanged=init;
values.onLinkChanged=init;

var arr=[];
init();

function init()
{
    Math.randomSeed=seed.get();
    
    arr.length=Math.abs(parseInt(numValues.get())) || 100;
    for(var i=0;i<arr.length;i++)
    {
        arr[i]=Math.seededRandom()* ( max.get() - min.get() ) + parseFloat(min.get()) ;
    }
    
    values.set(null);
    values.set(arr);
}


};

Ops.Array.RandomArray.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.MeshInstancer
// 
// **************************************************************

Ops.Gl.MeshInstancer = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
// TODO: remove array3xtransformedinstanced....

var exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));

var inTransformations=op.inArray("positions");
var inScales=op.inArray("Scale Array");
var inScale=op.inValue("Scale",1);
var geom=op.inObject("geom");
geom.ignoreValueSerialize=true;

var mod=null;
var mesh=null;
var shader=null;
var uniDoInstancing=null;
var recalc=true;
var cgl=op.patch.cgl;

exe.onTriggered=doRender;
exe.onLinkChanged=removeModule;

var matrixArray= new Float32Array(1);
var m=mat4.create();
inTransformations.onChange=reset;
inScales.onChange=reset;


var srcHeadVert=''
    .endl()+'UNI float do_instancing;'
    .endl()+'UNI float MOD_scale;'
    
    .endl()+'#ifdef INSTANCING'
    .endl()+'   IN mat4 instMat;'
    .endl()+'   OUT mat4 instModelMat;'
    .endl()+'#endif';

var srcBodyVert=''
    .endl()+'#ifdef INSTANCING'
    .endl()+'   if(do_instancing==1.0)'
    .endl()+'   {'
    .endl()+'       mMatrix*=instMat;'
    .endl()+'       mMatrix[0][0]*=MOD_scale;'
    .endl()+'       mMatrix[1][1]*=MOD_scale;'
    .endl()+'       mMatrix[2][2]*=MOD_scale;'
    .endl()+'   }'
    .endl()+'#endif'
    .endl();



geom.onChange=function()
{
    if(!geom.get())
    {
        mesh=null;
        return;
    }
    mesh=new CGL.Mesh(cgl,geom.get());
    reset();
};

function removeModule()
{
    if(shader && mod)
    {
        shader.removeDefine('INSTANCING');
        shader.removeModule(mod);
        shader=null;
    }
}

function reset()
{
    recalc=true;
}

function setupArray()
{
    if(!mesh)return;
    
    var transforms=inTransformations.get();
    if(!transforms)
    {
        transforms=[0,0,0];
    }
    var num=Math.floor(transforms.length/3);
    
    
    var scales=inScales.get();
    // console.log('scales',scales);
    // console.log('setup array!');

    if(matrixArray.length!=num*16)
    {
        matrixArray=new Float32Array(num*16);
    }

    for(var i=0;i<num;i++)
    {
        mat4.identity(m);
        mat4.translate(m,m,
            [
                transforms[i*3],
                transforms[i*3+1],
                transforms[i*3+2]
            ]);
        
        if(scales && scales.length>i)
        {
            mat4.scale(m,m,[scales[i],scales[i],scales[i]]);
            // console.log('scale',scales[i]);
        }
        else
        {
            mat4.scale(m,m,[1,1,1]);
        }

        for(var a=0;a<16;a++)
        {
            matrixArray[i*16+a]=m[a];
        }
    }

// console.log('matrixArray',matrixArray.length);
// console.log('num',num);

    mesh.numInstances=num;
    mesh.addAttribute('instMat',matrixArray,16);
    recalc=false;
}

function doRender()
{
    if(recalc)setupArray();
    if(matrixArray.length<=1)return;
    if(!mesh) return;

    if(cgl.getShader() && cgl.getShader()!=shader)
    {
        if(shader && mod)
        {
            shader.removeModule(mod);
            shader=null;
        }

        shader=cgl.getShader();
        if(!shader.hasDefine('INSTANCING'))
        {
            mod=shader.addModule(
                {
                    name: 'MODULE_VERTEX_POSITION',
                    priority:-2,
                    srcHeadVert: srcHeadVert,
                    srcBodyVert: srcBodyVert
                });

            shader.define('INSTANCING');
            uniDoInstancing=new CGL.Uniform(shader,'f','do_instancing',0);
            inScale.uniform=new CGL.Uniform(shader,'f',mod.prefix+'scale',inScale);
        }
        else
        {
            uniDoInstancing=shader.getUniform('do_instancing');
        }
    }

    uniDoInstancing.setValue(1);
    mesh.render(shader);
    uniDoInstancing.setValue(0);


}


};

Ops.Gl.MeshInstancer.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.Timer
// 
// **************************************************************

Ops.Anim.Timer = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var playPause=op.inValueBool("Play",true);
var reset=op.inFunctionButton("Reset");
var outTime=op.outValue("Time");
var inSpeed=op.inValue("Speed",1);

var timer=new CABLES.Timer();

playPause.onChange=setState;
setState();

function setState()
{
    if(playPause.get())
    {
        timer.play();
        op.patch.addOnAnimFrame(op);
    }
    else
    {
        timer.pause();
        op.patch.removeOnAnimFrame(op);
    }
}

reset.onTriggered=function()
{
    timer.setTime(0);
    outTime.set(0);
};

op.onAnimFrame=function()
{
    timer.update();
    outTime.set(timer.get()*inSpeed.get());

};


};

Ops.Anim.Timer.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Phong.PointLight
// 
// **************************************************************

Ops.Gl.Phong.PointLight = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};


var exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var attachment=op.addOutPort(new Port(op,"attachment",OP_PORT_TYPE_FUNCTION));


var radius=op.inValue("Radius",100);
var fallOff=op.inValueSlider("Fall Off",0.1);
var intensity=op.inValue("Intensity",1);

var x=op.addInPort(new Port(op,"x",OP_PORT_TYPE_VALUE));
var y=op.addInPort(new Port(op,"y",OP_PORT_TYPE_VALUE));
var z=op.addInPort(new Port(op,"z",OP_PORT_TYPE_VALUE));

var r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true' }));
var g=op.addInPort(new Port(op,"g",OP_PORT_TYPE_VALUE,{ display:'range' }));
var b=op.addInPort(new Port(op,"b",OP_PORT_TYPE_VALUE,{ display:'range' }));

var ambientR=op.inValue("Ambient R",0.1);
var ambientG=op.inValue("Ambient G",0.1);
var ambientB=op.inValue("Ambient B",0.1);

var specularR=op.addInPort(new Port(op,"Specular R",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true' }));
var specularG=op.addInPort(new Port(op,"Specular G",OP_PORT_TYPE_VALUE,{ display:'range' }));
var specularB=op.addInPort(new Port(op,"Specular B",OP_PORT_TYPE_VALUE,{ display:'range' }));


ambientR.set(0);
ambientG.set(0);
ambientB.set(0);

specularR.set(1);
specularG.set(1);
specularB.set(1);

r.set(1);
g.set(1);
b.set(1);


var cgl=op.patch.cgl;


radius.onChange=updateAll;
fallOff.onChange=updateAll;
intensity.onChange=updateAll;
r.onChange=updateAll;
g.onChange=updateAll;
b.onChange=updateAll;
x.onChange=updateAll;
y.onChange=updateAll;
z.onChange=updateAll;

ambientR.onChange=updateAll;
ambientG.onChange=updateAll;
ambientB.onChange=updateAll;
specularR.onChange=updateAll;
specularG.onChange=updateAll;
specularB.onChange=updateAll;




var id=CABLES.generateUUID();
var light={};

var posVec=vec3.create();
var mpos=vec3.create();
var needsUpdate=true;

updateAll();


function updateColor()
{
    light.color=light.color||[];
    light.color[0]=r.get();
    light.color[1]=g.get();
    light.color[2]=b.get();

    light.ambient=light.ambient||[];
    light.ambient[0]=ambientR.get();
    light.ambient[1]=ambientG.get();
    light.ambient[2]=ambientB.get();
    
    light.specular=light.specular||[];
    light.specular[0]=specularR.get();
    light.specular[1]=specularG.get();
    light.specular[2]=specularB.get();
    
    light.changed=true;
}


function updatePos()
{
}

function updateAll()
{
    needsUpdate=true;
}

var transVec=vec3.create();

exe.onTriggered=function()
{
    if(needsUpdate)
    {
        if(!cgl.frameStore.phong)cgl.frameStore.phong={};
        if(!cgl.frameStore.phong.lights)cgl.frameStore.phong.lights=[];
        light=light||{};
        light.id=id;
        light.type=0;
        light.changed=true;
        light.radius=radius.get();
        light.fallOff=fallOff.get();
        light.mul=intensity.get();
    
        updatePos();
        updateColor();
        needsUpdate=false;
    }
    
    
    
    cgl.frameStore.phong.lights=cgl.frameStore.phong.lights||[];

    vec3.set(transVec,x.get(),y.get(),z.get());
    vec3.transformMat4(mpos, transVec, cgl.mvMatrix);
    light=light||{};
    
    light.pos=mpos;
    light.type=0;


    if(CABLES.UI && CABLES.UI.renderHelper)
    {
        cgl.pushModelMatrix();
        mat4.translate(cgl.mvMatrix,cgl.mvMatrix,transVec);
        CABLES.GL_MARKER.drawSphere(op,radius.get()*2);
        cgl.popModelMatrix();
    }

    if(attachment.isLinked())
    {
        cgl.pushModelMatrix();
        mat4.translate(cgl.mvMatrix,cgl.mvMatrix,transVec);
        attachment.trigger();
        cgl.popModelMatrix();
    }

    cgl.frameStore.phong.lights.push(light);
    trigger.trigger();
    cgl.frameStore.phong.lights.pop();
    
    if(CABLES.UI && gui.patch().isCurrentOp(op)) 
        gui.setTransformGizmo(
            {
                posX:x,
                posY:y,
                posZ:z
            });
};



};

Ops.Gl.Phong.PointLight.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ShaderEffects.AreaScaler
// 
// **************************************************************

Ops.Gl.ShaderEffects.AreaScaler = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["areascale_vert"]="\nUNI bool MOD_smooth;\nUNI float MOD_x,MOD_y,MOD_z;\nUNI float MOD_strength;\nUNI float MOD_size;\n\nvec4 MOD_scaler(vec4 pos,vec4 worldPos,vec3 normal)\n{\n    vec3 forcePos=vec3(MOD_x,MOD_y,MOD_z);\n    vec3 vecToOrigin=worldPos.xyz-forcePos;\n    float dist=abs(length(vecToOrigin));\n    float distAlpha = (MOD_size - dist) ;\n\n    if(MOD_smooth) distAlpha=smoothstep(0.0,MOD_size,distAlpha);\n    \n    float m=(distAlpha*MOD_strength);\n    \n    #ifndef MOD_TO_ZERO\n    m+=1.0;\n    #endif\n    \n    pos.xyz*=m;\n\n    return pos;\n}\n";

op.render=op.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
op.trigger=op.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));

var inSize=op.inValue("Size",1);
var inStrength=op.inValue("Strength",1);
var inSmooth=op.inValueBool("Smooth",true);
var inToZero=op.inValueBool("Keep Min Size",true);

const cgl=op.patch.cgl;

var x=op.inValue("x");
var y=op.inValue("y");
var z=op.inValue("z");

var needsUpdateToZero=true;


var shader=null;

var srcHeadVert=attachments.areascale_vert;

var srcBodyVert=''
    .endl()+'pos=MOD_scaler(pos,mMatrix*pos,attrVertNormal);' //modelMatrix*
    .endl();
    
var moduleVert=null;

function removeModule()
{
    if(shader && moduleVert) shader.removeModule(moduleVert);
    shader=null;
}

inToZero.onChange=updateToZero;

function updateToZero()
{
    if(!shader)
    {
        needsUpdateToZero=true;
        return;
    }
    if(inToZero.get()) shader.removeDefine(moduleVert.prefix+"TO_ZERO");
        else shader.define(moduleVert.prefix+"TO_ZERO");
        
    needsUpdateToZero=false;

}




op.render.onLinkChanged=removeModule;

op.render.onTriggered=function()
{
    if(!cgl.getShader())
    {
         op.trigger.trigger();
         return;
    }
    
    if(CABLES.UI && gui.patch().isCurrentOp(op)) 
        gui.setTransformGizmo(
            {
                posX:x,
                posY:y,
                posZ:z
            });


    if(CABLES.UI && CABLES.UI.renderHelper)
    {
        cgl.pushModelMatrix();
        mat4.translate(cgl.mvMatrix,cgl.mvMatrix,[x.get(),y.get(),z.get()]);
        CABLES.GL_MARKER.drawSphere(op,inSize.get());
        cgl.popModelMatrix();
    }


    if(cgl.getShader()!=shader)
    {
        if(shader) removeModule();
        shader=cgl.getShader();

        moduleVert=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_VERTEX_POSITION',
                srcHeadVert:srcHeadVert,
                srcBodyVert:srcBodyVert
            });

        inSize.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'size',inSize);
        inStrength.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'strength',inStrength);
        inSmooth.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'smooth',inSmooth);

        x.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'x',x);
        y.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'y',y);
        z.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'z',z);
    }
    

    if(needsUpdateToZero)updateToZero();

    if(!shader)return;

    op.trigger.trigger();
};


};

Ops.Gl.ShaderEffects.AreaScaler.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.ScaleXYZ
// 
// **************************************************************

Ops.Gl.Matrix.ScaleXYZ = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
const scaleX=op.addInPort(new Port(op,"x"));
const scaleY=op.addInPort(new Port(op,"y"));
const scaleZ=op.addInPort(new Port(op,"z"));

const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

const cgl=op.patch.cgl;
const vScale=vec3.create();

var hasChanged=true;

render.onTriggered=function()
{
    cgl.pushModelMatrix();
    mat4.scale(cgl.mMatrix,cgl.mMatrix, vScale);
    trigger.trigger();
    cgl.popModelMatrix();
};

var scaleChanged=function()
{
    hasChanged=true;
    vec3.set(vScale, scaleX.get(),scaleY.get(),scaleZ.get());
};

scaleX.set(1.0);
scaleY.set(1.0);
scaleZ.set(1.0);

scaleX.onValueChange(scaleChanged);
scaleY.onValueChange(scaleChanged);
scaleZ.onValueChange(scaleChanged);

scaleChanged();

};

Ops.Gl.Matrix.ScaleXYZ.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Subtract
// 
// **************************************************************

Ops.Math.Subtract = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var number1=op.addInPort(new Port(op,"number1"));
var number2=op.addInPort(new Port(op,"number2"));
var result=op.addOutPort(new Port(op,"result"));

number1.onValueChanged=exec;
number2.onValueChanged=exec;

number1.set(1);
number2.set(1);


function exec()
{
    var v=number1.get()-number2.get();
    if(!isNaN(v)) result.set( v );
}



};

Ops.Math.Subtract.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Hue
// 
// **************************************************************

Ops.Gl.TextureEffects.Hue = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["hue_frag"]="\n#ifdef HAS_TEXTURES\n  IN vec2 texCoord;\n  UNI sampler2D tex;\n#endif\nUNI float hue;\n\nvec3 rgb2hsv(vec3 c)\n{\n    vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);\n    vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));\n    vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));\n\n    float d = q.x - min(q.w, q.y);\n    float e = 1.0e-10;\n    return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);\n}\n\nvec3 hsv2rgb(vec3 c)\n{\n    vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);\n    vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);\n    return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);\n}\n\nvoid main()\n{\n   vec4 col=vec4(1.0,0.0,0.0,1.0);\n   #ifdef HAS_TEXTURES\n       col=texture2D(tex,texCoord);\n\n       vec3 hsv = rgb2hsv(col.rgb);\n       hsv.x=hsv.x+hue;\n       col.rgb = hsv2rgb(hsv);\n\n   #endif\n   gl_FragColor = col;\n}";

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var hue=op.addInPort(new Port(op,"hue",OP_PORT_TYPE_VALUE,{display:'range'}));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

hue.set(1.0);
var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);

shader.setSource(shader.getDefaultVertexShader(),attachments.hue_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var uniformHue=new CGL.Uniform(shader,'f','hue',1.0);

hue.onValueChanged=function(){ uniformHue.setValue(hue.get()); };

render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Hue.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.ParseArray
// 
// **************************************************************

Ops.Array.ParseArray = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var text=op.addInPort(new Port(op,"text",OP_PORT_TYPE_VALUE,{type:'string',display:'editor'}));
var separator=op.inValueString("separator",",");

var toNumber=op.inValueBool("Numbers",false);

var parsed=op.outFunction("Parsed");
var arr=op.addOutPort(new Port(op,"array",OP_PORT_TYPE_ARRAY));
var len=op.addOutPort(new Port(op,"length",OP_PORT_TYPE_VALUE));

separator.set(',');
text.set('1,2,3');

text.onValueChanged=parse;
separator.onValueChanged=parse;
toNumber.onChange=parse;

parse();

function parse()
{
    if(!text.get())return;
    
    var r=text.get().split(separator.get());
    len.set(r.length);

    if(toNumber.get())
    {
        for(var i=0;i<r.length;i++)
        {
            r[i]=Number(r[i]);
        }
    }
    
    arr.set(null);
    arr.set(r);
    parsed.trigger();
}


};

Ops.Array.ParseArray.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Incrementor
// 
// **************************************************************

Ops.Math.Incrementor = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var increment = op.inFunctionButton("Increment");
var decrement = op.inFunctionButton("Decrement");
var inLength=op.addInPort(new Port(op,"Length",OP_PORT_TYPE_VALUE));
// var reset=op.addInPort(new Port(op,"Reset",OP_PORT_TYPE_FUNCTION));
var reset=op.inFunctionButton("Reset");


var inMode=op.inValueSelect("Mode",["Rewind","Stop at Max"]);

var value=op.addOutPort(new Port(op,"Value",OP_PORT_TYPE_VALUE));

var outRestarted=op.outFunction("Restarted");

value.ignoreValueSerialize=true;
inLength.set(10);
var val=0;
value.set(0);

inLength.onTriggered=reset;
reset.onTriggered=doReset;

var MODE_REWIND=0;
var MODE_STOP=1;

var mode=MODE_REWIND;

inMode.onChange=function()
{
    if(inMode.get()=="Rewind")
    {
        mode=MODE_REWIND;
    }
    if(inMode.get()=="Stop at Max")
    {
        mode=MODE_STOP;
    }

    
};

function doReset()
{
    value.set(null);
    val=0;
    value.set(val);
    outRestarted.trigger();
}

decrement.onTriggered=function()
{
    val--;
    if(mode==MODE_REWIND && val<0)val=inLength.get()-1;
    if(mode==MODE_STOP && val<0)val=0;

    value.set(val);
};

increment.onTriggered=function()
{
    val++;
    if(mode==MODE_REWIND && val>=inLength.get())
    {
        val=0;
        outRestarted.trigger();
    }
    if(mode==MODE_STOP && val>=inLength.get())val=inLength.get()-1;
    
    value.set(val);
};


};

Ops.Math.Incrementor.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.StringTypeAnimation
// 
// **************************************************************

Ops.Anim.StringTypeAnimation = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var text=op.addInPort(new Port(op,"text",OP_PORT_TYPE_VALUE,{type:'string',display:'editor'}));
var inRestart=op.inFunctionButton("Restart");
var speed=op.inValue("Speed",500);
var speedVariation=op.inValueSlider("Speed Variation");

var outText=op.outValueString("Result");
var outChanged=op.outFunction("Changed");
var outFinished=op.outFunction("Finished");

outText.set('  \n  ');
var pos=0;
var updateInterval=0;
var cursorblink=true;
var finished=false;

function setNewTimeout()
{
    clearTimeout(updateInterval);
    var ms=speed.get()*(Math.random()*(speedVariation.get()*2-1));
    if(pos>text.get().length)ms=speed.get();
    updateInterval=setTimeout(update,speed.get()+ms);
}

inRestart.onTriggered=function()
{
    finished=false;
    pos=0;
    setNewTimeout();
};

function update()
{
    if(!text.get() || text.get()==='' || text.get()==='0' ||text.get()=='0' )
    {
        outText.set(' ');
        return;
    }

    var t=text.get().substring(0,pos);
    cursorblink=!cursorblink;

    if(pos>text.get().length && cursorblink)
    {
        if(!finished)
        {
            outFinished.trigger();
            finished=true;
        }
    }
    else
    {
        finished=false;
        t+='_';
        pos++;
    }

    outText.set( t );
    outChanged.trigger();
    setNewTimeout();
}

text.onChange=function()
{
    finished=false;
    pos=0;
    setNewTimeout();
};


};

Ops.Anim.StringTypeAnimation.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.GetValueString
// 
// **************************************************************

Ops.Array.GetValueString = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var array=op.inArray("Array");
var index=op.inValueInt("index");

var value=op.outValueString("String");

array.ignoreValueSerialize=true;

function update()
{
    if(array.get()) value.set( array.get()[index.get()]);
}

index.onChange=update;
array.onChange=update;


};

Ops.Array.GetValueString.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Boolean.TriggerChangedTrue
// 
// **************************************************************

Ops.Boolean.TriggerChangedTrue = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var val=op.inValueBool("Value",false);

var next=op.outFunction("Next");

var oldVal=0;

val.onChange=function()
{
    var newVal=val.get();
    if(!oldVal && newVal)
    {
        oldVal=true;
        next.trigger();
    }
    else
    {
        oldVal=false;
    }
};

};

Ops.Boolean.TriggerChangedTrue.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.String.Uppercase
// 
// **************************************************************

Ops.String.Uppercase = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var inStr=op.inValueString("String");
var outStr=op.outValue("Result");

inStr.onChange=function()
{
    if(!inStr.get())outStr.set('');
    else outStr.set(inStr.get().toUpperCase());
};


};

Ops.String.Uppercase.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Triangle
// 
// **************************************************************

Ops.Gl.Meshes.Triangle = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var sizeW=op.addInPort(new Port(op,"width",OP_PORT_TYPE_VALUE));
var sizeH=op.addInPort(new Port(op,"height",OP_PORT_TYPE_VALUE));
const draw=op.inValueBool("Draw",true);
var geom=new CGL.Geometry("triangle");

sizeW.set(1);
sizeH.set(1);

var geomOut=op.addOutPort(new Port(op,"geometry",OP_PORT_TYPE_OBJECT));
geomOut.ignoreValueSerialize=true;

var cgl=op.patch.cgl;
var mesh=null;

render.onTriggered=function()
{
    if(draw.get())mesh.render(cgl.getShader());
    trigger.trigger();
};

function create()
{
    geom.vertices = [
         0.0,           sizeH.get(),  0.0,
        -sizeW.get(),  -sizeH.get(),  0.0,
         sizeW.get(),  -sizeH.get(),  0.0
    ];

    geom.vertexNormals = [
         0.0,  0.0,  1.0,
         0.0,  0.0,  1.0,
         0.0,  0.0,  1.0
    ];

    geom.texCoords = [
         0.5,  0.0,
         1.0,  1.0,
         0.0,  1.0,
    ];

    geom.verticesIndices = [
        0, 1, 2
    ];

    mesh=new CGL.Mesh(cgl,geom);
    geomOut.set(null);
    geomOut.set(geom);
}

sizeW.onValueChange(create);
sizeH.onValueChange(create);

create();

};

Ops.Gl.Meshes.Triangle.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Shader.BasicMaterialNew
// 
// **************************************************************

Ops.Gl.Shader.BasicMaterialNew = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["basicmaterial_frag"]="{{MODULES_HEAD}}\n\nIN vec2 texCoord;\n#ifdef HAS_TEXTURES\n    IN vec2 texCoordOrig;\n    #ifdef HAS_TEXTURE_DIFFUSE\n        uniform sampler2D tex;\n    #endif\n    #ifdef HAS_TEXTURE_OPACITY\n        uniform sampler2D texOpacity;\n   #endif\n#endif\nuniform float r;\nuniform float g;\nuniform float b;\nuniform float a;\n\nvoid main()\n{\n    {{MODULE_BEGIN_FRAG}}\n    vec4 col=vec4(r,g,b,a);\n\n    #ifdef HAS_TEXTURES\n        #ifdef HAS_TEXTURE_DIFFUSE\n            col=texture2D(tex,vec2(texCoord.x,(1.0-texCoord.y)));\n\n//         col=texture2D(tex,vec2(texCoords.x*1.0,(1.0-texCoords.y)*1.0));\n            #ifdef COLORIZE_TEXTURE\n                col.r*=r;\n                col.g*=g;\n                col.b*=b;\n            #endif\n        #endif\n        col.a*=a;\n        #ifdef HAS_TEXTURE_OPACITY\n            #ifdef TRANSFORMALPHATEXCOORDS\n                col.a*=texture2D(texOpacity,vec2(texCoordOrig.s,1.0-texCoordOrig.t)).g;\n            #endif\n            #ifndef TRANSFORMALPHATEXCOORDS\n                \n                #ifdef ALPHA_MASK_ALPHA\n                    col.a*=texture2D(texOpacity,vec2(texCoord.s,1.0-texCoord.t)).a;\n                #endif\n                #ifdef ALPHA_MASK_LUMI\n                    col.a*=dot(vec3(0.2126,0.7152,0.0722), texture2D(texOpacity,vec2(texCoord.s,1.0-texCoord.t)).rgb);\n                #endif\n                #ifdef ALPHA_MASK_R\n                    col.a*=texture2D(texOpacity,vec2(texCoord.s,1.0-texCoord.t)).r;\n                #endif\n                #ifdef ALPHA_MASK_G\n                    col.a*=texture2D(texOpacity,vec2(texCoord.s,1.0-texCoord.t)).g;\n                #endif\n                #ifdef ALPHA_MASK_B\n                    col.a*=texture2D(texOpacity,vec2(texCoord.s,1.0-texCoord.t)).b;\n                #endif\n    \n                \n                \n                \n            #endif\n        #endif\n    #endif\n\n    {{MODULE_COLOR}}\n\n    outColor = col;\n}\n";
attachments["basicmaterial_vert"]="{{MODULES_HEAD}}\n\nIN vec3 vPosition;\nIN vec3 attrVertNormal;\nIN vec2 attrTexCoord;\n\nOUT vec3 norm;\nOUT vec2 texCoord;\nOUT vec2 texCoordOrig;\n\nUNI mat4 projMatrix;\nUNI mat4 modelMatrix;\nUNI mat4 viewMatrix;\n\n#ifdef HAS_TEXTURES\n    #ifdef TEXTURE_REPEAT\n        UNI float diffuseRepeatX;\n        UNI float diffuseRepeatY;\n        UNI float texOffsetX;\n        UNI float texOffsetY;\n    #endif\n#endif\n\n\nvoid main()\n{\n    mat4 mMatrix=modelMatrix;\n    mat4 mvMatrix;\n    \n    texCoordOrig=attrTexCoord;\n    texCoord=attrTexCoord;\n    #ifdef HAS_TEXTURES\n        #ifdef TEXTURE_REPEAT\n            texCoord.x=texCoord.x*diffuseRepeatX+texOffsetX;\n            texCoord.y=texCoord.y*diffuseRepeatY+texOffsetY;\n        #endif\n    #endif\n\n    vec4 pos = vec4( vPosition, 1. );\n\n\n    #ifdef BILLBOARD\n       vec3 position=vPosition;\n       mvMatrix=viewMatrix*modelMatrix;\n\n       gl_Position = projMatrix * mvMatrix * vec4((\n           position.x * vec3(\n               mvMatrix[0][0],\n               mvMatrix[1][0],\n               mvMatrix[2][0] ) +\n           position.y * vec3(\n               mvMatrix[0][1],\n               mvMatrix[1][1],\n               mvMatrix[2][1]) ), 1.0);\n    #endif\n\n    {{MODULE_VERTEX_POSITION}}\n\n    #ifndef BILLBOARD\n        mvMatrix=viewMatrix * mMatrix;\n    #endif\n\n\n    #ifndef BILLBOARD\n        // gl_Position = projMatrix * viewMatrix * modelMatrix * pos;\n        gl_Position = projMatrix * mvMatrix * pos;\n    #endif\n}\n";


var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION) );
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var shaderOut=op.addOutPort(new Port(op,"shader",OP_PORT_TYPE_OBJECT));
shaderOut.ignoreValueSerialize=true;

var cgl=op.patch.cgl;


var shader=new CGL.Shader(cgl,'BasicMaterialNew');
shader.setModules(['MODULE_VERTEX_POSITION','MODULE_COLOR','MODULE_BEGIN_FRAG']);
shader.bindTextures=bindTextures;
shader.setSource(attachments.basicmaterial_vert,attachments.basicmaterial_frag);
shaderOut.set(shader);

render.onTriggered=doRender;


function bindTextures()
{
    if(diffuseTexture.get())
    {
        /* --- */cgl.setTexture(0, diffuseTexture.get().tex);
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, diffuseTexture.get().tex);
    }

    if(op.textureOpacity.get())
    {
        /* --- */cgl.setTexture(1, op.textureOpacity.get().tex);
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, op.textureOpacity.get().tex);
    }
}

op.preRender=function()
{
    shader.bind();
    doRender();
}

function doRender()
{
    if(!shader)return;

    cgl.setShader(shader);
    shader.bindTextures();
    trigger.trigger();
    cgl.setPreviousShader();
}


{
    // rgba colors
    
    var r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range',colorPick:'true' }));
    r.set(Math.random());
    r.uniform=new CGL.Uniform(shader,'f','r',r);
    
    var g=op.addInPort(new Port(op,"g",OP_PORT_TYPE_VALUE,{ display:'range'}));
    g.set(Math.random());
    g.uniform=new CGL.Uniform(shader,'f','g',g);
    
    var b=op.addInPort(new Port(op,"b",OP_PORT_TYPE_VALUE,{ display:'range' }));
    b.set(Math.random());
    b.uniform=new CGL.Uniform(shader,'f','b',b);
    
    var a=op.addInPort(new Port(op,"a",OP_PORT_TYPE_VALUE,{ display:'range'}));
    a.uniform=new CGL.Uniform(shader,'f','a',a);
    a.set(1.0);
    
}

{
    // diffuse outTexture
    
    var diffuseTexture=this.addInPort(new Port(this,"texture",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
    var diffuseTextureUniform=null;
    shader.bindTextures=bindTextures;
    
    diffuseTexture.onChange=function()
    {
        if(diffuseTexture.get())
        {
            if(!shader.hasDefine('HAS_TEXTURE_DIFFUSE'))shader.define('HAS_TEXTURE_DIFFUSE');
            if(!diffuseTextureUniform)diffuseTextureUniform=new CGL.Uniform(shader,'t','texDiffuse',0);
            updateTexRepeat();
        }
        else
        {
            shader.removeUniform('texDiffuse');
            shader.removeDefine('HAS_TEXTURE_DIFFUSE');
            diffuseTextureUniform=null;
        }
    };
    
}

{
    // opacity texture 
    op.textureOpacity=op.addInPort(new Port(op,"textureOpacity",OP_PORT_TYPE_TEXTURE,{preview:true,display:'createOpHelper'}));
    op.textureOpacityUniform=null;
    
    op.alphaMaskSource=op.inValueSelect("Alpha Mask Source",["Alpha Channel","Luminance","R","G","B"],"Luminance");
    
    op.alphaMaskSource.onChange=updateAlphaMaskMethod;
    
    function updateAlphaMaskMethod()
    {
        if(op.alphaMaskSource.get()=='Alpha Channel') shader.define('ALPHA_MASK_ALPHA');
            else shader.removeDefine('ALPHA_MASK_ALPHA');

        if(op.alphaMaskSource.get()=='Luminance') shader.define('ALPHA_MASK_LUMI');
            else shader.removeDefine('ALPHA_MASK_LUMI');

        if(op.alphaMaskSource.get()=='R') shader.define('ALPHA_MASK_R');
            else shader.removeDefine('ALPHA_MASK_R');

        if(op.alphaMaskSource.get()=='G') shader.define('ALPHA_MASK_G');
            else shader.removeDefine('ALPHA_MASK_G');

        if(op.alphaMaskSource.get()=='B') shader.define('ALPHA_MASK_B');
            else shader.removeDefine('ALPHA_MASK_B');

    };
    
    
    op.textureOpacity.onChange=function()
    {
        if(op.textureOpacity.get())
        {
            if(op.textureOpacityUniform!==null)return;
            shader.removeUniform('texOpacity');
            shader.define('HAS_TEXTURE_OPACITY');
            if(!op.textureOpacityUniform)op.textureOpacityUniform=new CGL.Uniform(shader,'t','texOpacity',1);
        }
        else
        {
            shader.removeUniform('texOpacity');
            shader.removeDefine('HAS_TEXTURE_OPACITY');
            op.textureOpacityUniform=null;
        }
        updateAlphaMaskMethod();
    };
}



op.colorizeTexture=op.addInPort(new Port(op,"colorizeTexture",OP_PORT_TYPE_VALUE,{ display:'bool' }));
op.colorizeTexture.set(false);
op.colorizeTexture.onChange=function()
{
    if(op.colorizeTexture.get()) shader.define('COLORIZE_TEXTURE');
        else shader.removeDefine('COLORIZE_TEXTURE');
};


op.doBillboard=op.addInPort(new Port(op,"billboard",OP_PORT_TYPE_VALUE,{ display:'bool' }));
op.doBillboard.set(false);
op.doBillboard.onChange=function()
{
    if(op.doBillboard.get()) shader.define('BILLBOARD');
        else shader.removeDefine('BILLBOARD');
};

var texCoordAlpha=op.inValueBool("Opacity TexCoords Transform",false);

texCoordAlpha.onChange=function()
{
    if(texCoordAlpha.get()) shader.define('TRANSFORMALPHATEXCOORDS');
        else shader.removeDefine('TRANSFORMALPHATEXCOORDS');
};

var preMultipliedAlpha=op.addInPort(new Port(op,"preMultiplied alpha",OP_PORT_TYPE_VALUE,{ display:'bool' }));

function updateTexRepeat()
{
    if(!diffuseRepeatXUniform)
    {
        diffuseRepeatXUniform=new CGL.Uniform(shader,'f','diffuseRepeatX',diffuseRepeatX);
        diffuseRepeatYUniform=new CGL.Uniform(shader,'f','diffuseRepeatY',diffuseRepeatY);
        diffuseOffsetXUniform=new CGL.Uniform(shader,'f','texOffsetX',diffuseOffsetX);
        diffuseOffsetYUniform=new CGL.Uniform(shader,'f','texOffsetY',diffuseOffsetY);
    }

    diffuseRepeatXUniform.setValue(diffuseRepeatX.get());
    diffuseRepeatYUniform.setValue(diffuseRepeatY.get());
    diffuseOffsetXUniform.setValue(diffuseOffsetX.get());
    diffuseOffsetYUniform.setValue(diffuseOffsetY.get());
}


{
    // texture coords
    
    var diffuseRepeatX=op.addInPort(new Port(op,"diffuseRepeatX",OP_PORT_TYPE_VALUE));
    var diffuseRepeatY=op.addInPort(new Port(op,"diffuseRepeatY",OP_PORT_TYPE_VALUE));
    var diffuseOffsetX=op.addInPort(new Port(op,"Tex Offset X",OP_PORT_TYPE_VALUE));
    var diffuseOffsetY=op.addInPort(new Port(op,"Tex Offset Y",OP_PORT_TYPE_VALUE));
    
    diffuseRepeatX.onChange=updateTexRepeat;
    diffuseRepeatY.onChange=updateTexRepeat;
    diffuseOffsetY.onChange=updateTexRepeat;
    diffuseOffsetX.onChange=updateTexRepeat;
    
    var diffuseRepeatXUniform=null;
    var diffuseRepeatYUniform=null;
    var diffuseOffsetXUniform=null;
    var diffuseOffsetYUniform=null;
    
    shader.define('TEXTURE_REPEAT');
    

    diffuseOffsetX.set(0);
    diffuseOffsetY.set(0);
    diffuseRepeatX.set(1);
    diffuseRepeatY.set(1);
}


};

Ops.Gl.Shader.BasicMaterialNew.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.TriggerDistributeByValue
// 
// **************************************************************

Ops.Trigger.TriggerDistributeByValue = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));
var number=op.addInPort(new Port(op,"number"));
var max=op.addInPort(new Port(op,"max"));
var numOut=op.addInPort(new Port(op,"num outputs"));
var num=op.addOutPort(new Port(op,"num",OP_PORT_TYPE_VALUE));

number.set(0);
max.set(1);
numOut.set(2);
num.set(0);

var triggers=[];
var numTriggers=20;

var trigger=function()
{
    var s=max.get()/numOut.get();
    var index=Math.abs(Math.floor(number.get()/s));
    
    num.set(index);
    
    if(!isNaN(index) && index<numTriggers)
    {
        triggers[index].trigger();
    }
};

exe.onTriggered=trigger;


for(var i=0;i<numTriggers;i++)
{
    triggers.push( op.addOutPort(new Port(op,"trigger "+i,OP_PORT_TYPE_FUNCTION)) );
}


};

Ops.Trigger.TriggerDistributeByValue.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.RandomGridPlacement
// 
// **************************************************************

Ops.Gl.Matrix.RandomGridPlacement = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.inFunction("Exe");
var maxDepth=op.inValue("max Depth",4);
var deeper=op.inValueSlider("Possibility");
var seed=op.inValue("Seed",0);

var inScale=op.inValueSlider("Scale",1);

var width=op.inValue("Width",4);
var height=op.inValue("Height",3);


var next=op.outFunction("Next");
var outIndex=op.outValue("Index");
var outDepth=op.outValue("depth");


var globalScale=1;
var vPos=vec3.create();
var vScale=vec3.create();

var hhalf=0;
var whalf=0;

var cgl=op.patch.cgl;
var index=0;
function drawSquare(x,y,depth,scale)
{
    
    var godeeper=Math.seededRandom()>deeper.get()*0.9;

    if(depth>maxDepth.get() )godeeper=false;
    
    if( godeeper)
    {
        depth++;
        var st=1/(depth*depth);

        for(var _x=0;_x<2;_x++)
        {
            for(var _y=0;_y<2;_y++)
            {
                var xx=_x*scale/2;
                var yy=_y*scale/2;
                
                drawSquare(
                    x+xx-scale/4,
                    y+yy-scale/4,
                    depth,
                    scale/2*globalScale);
            }
        }
    }
    else
    {
        cgl.pushModelMatrix();
        vec3.set(vScale,scale,scale,scale);
        vec3.set(vPos,x-whalf+0.5,y-hhalf+0.5,0);
        index++;
        outIndex.set(index);
        outDepth.set(depth);
        
        mat4.translate(cgl.mvMatrix,cgl.mvMatrix,vPos);
        mat4.scale(cgl.mvMatrix,cgl.mvMatrix,vScale);
        next.trigger();
    
        cgl.popModelMatrix();
    }

}



exe.onTriggered=function()
{
    index=0;
    Math.randomSeed=seed.get();;

whalf=width.get()/2;
hhalf=height.get()/2;

globalScale=inScale.get();

    for(var x=0;x<width.get();x++)
    {
        for(var y=0;y<height.get();y++)
        {

            drawSquare(x,y,0,globalScale);
            var sc=1;


        }
    }
    
    
    
};

};

Ops.Gl.Matrix.RandomGridPlacement.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.RectangleFrame
// 
// **************************************************************

Ops.Gl.Meshes.RectangleFrame = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var render=op.addInPort(new Port(op,"Render",OP_PORT_TYPE_FUNCTION));
var width=op.addInPort(new Port(op,"Width",OP_PORT_TYPE_VALUE));
var height=op.addInPort(new Port(op,"Height",OP_PORT_TYPE_VALUE));
var thickness=op.addInPort(new Port(op,"Thickness",OP_PORT_TYPE_VALUE));
var pivotX=op.addInPort(new Port(op,"pivot x",OP_PORT_TYPE_VALUE,{display:'dropdown',values:["center","left","right"]} ));
var pivotY=op.addInPort(new Port(op,"pivot y",OP_PORT_TYPE_VALUE,{display:'dropdown',values:["center","top","bottom"]} ));

var trigger=op.addOutPort(new Port(op,"Trigger",OP_PORT_TYPE_FUNCTION));
var geomOut=op.addOutPort(new Port(op,"Geometry",OP_PORT_TYPE_OBJECT));

var drawTop=op.inValueBool("Draw Top",true);
var drawBottom=op.inValueBool("Draw Bottom",true);
var drawLeft=op.inValueBool("Draw Left",true);
var drawRight=op.inValueBool("Draw Right",true);


var cgl=op.patch.cgl;
var mesh=null;
var geom=new CGL.Geometry();

width.set(1);
height.set(1);
thickness.set(-0.1);
pivotX.set('center');
pivotY.set('center');

geomOut.ignoreValueSerialize=true;

width.onChange=create;
pivotX.onChange=create;
pivotY.onChange=create;
height.onChange=create;
thickness.onChange=create;

drawTop.onChange=drawBottom.onChange=drawLeft.onChange=drawRight.onChange=create;

create();

render.onTriggered=function()
{
    mesh.render(cgl.getShader());
    trigger.trigger();
};


function create()
{
    var w=width.get();
    var h=height.get();
    var x=-w/2;
    var y=-h/2;
    var th=thickness.get();//*Math.min(height.get(),width.get())*-0.5;
 
    if(pivotX.get()=='right') x=-w;
    if(pivotX.get()=='left') x=0;

    if(pivotY.get()=='top') y=-w;
    if(pivotY.get()=='bottom') y=0;

    if(geom.vertices.length!=8*3) geom.vertices.length=8*3;

    var c=0;
    geom.vertices[c++]=x;
    geom.vertices[c++]=y;
    geom.vertices[c++]=0;

    geom.vertices[c++]=x+w;
    geom.vertices[c++]=y;
    geom.vertices[c++]=0;

    geom.vertices[c++]=x+w;
    geom.vertices[c++]=y+h;
    geom.vertices[c++]=0;

    geom.vertices[c++]=x;
    geom.vertices[c++]=y+h;
    geom.vertices[c++]=0;

    geom.vertices[c++]=x-th; 
    geom.vertices[c++]=y-th;
    geom.vertices[c++]=0;

    geom.vertices[c++]=x+w+th;
    geom.vertices[c++]=y-th;
    geom.vertices[c++]=0;

    geom.vertices[c++]=x+w+th;
    geom.vertices[c++]=y+h+th;
    geom.vertices[c++]=0;

    geom.vertices[c++]=x-th;
    geom.vertices[c++]=y+h+th;
    geom.vertices[c++]=0;

    if(geom.vertexNormals.length===0)
        geom.vertexNormals = [
             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,  0.0,  1.0,
             0.0,  0.0,  1.0,
        ];
        
    geom.verticesIndices = [];

    if(drawBottom.get()) geom.verticesIndices.push( 0, 1, 4 ,  1, 5, 4);
    if(drawRight.get())geom.verticesIndices.push( 1, 2, 5,  5, 2, 6);
    if(drawTop.get())geom.verticesIndices.push( 7, 6, 3,  6, 2, 3);
    if(drawLeft.get())geom.verticesIndices.push( 0, 4, 3,  4, 7, 3);


    if(geom.texCoords.length===0)
    {
        geom.texCoords=new Float32Array();
        for(var i=0;i<geom.vertices.length;i+=3)
        {
            geom.texCoords[i/3*2]=geom.vertices[i+0]/width.get()-0.5;
            geom.texCoords[i/3*2]=geom.vertices[i+1]/height.get()-0.5;
        }
    }

    if(!mesh) mesh=new CGL.Mesh(cgl,geom);
        else mesh.setGeom(geom);

    geomOut.set(null);
    geomOut.set(geom);
}



};

Ops.Gl.Meshes.RectangleFrame.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.RandomNumbers
// 
// **************************************************************

Ops.Math.RandomNumbers = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var index=op.addInPort(new Port(op, "index",OP_PORT_TYPE_VALUE));
var seed=op.addInPort(new Port(op,"random seed"));
var min=op.addInPort(new Port(op,"Min"));
var max=op.addInPort(new Port(op,"Max"));

var outX=op.outValue("X");
var outY=op.outValue("Y");
var outZ=op.outValue("Z");

var numValues=100;
min.set(-1);
max.set(1);
seed.set(Math.round(Math.random()*99999));

max.onValueChanged=init;
min.onValueChanged=init;
seed.onValueChanged=init;

var arr=[];
init();

index.onChange=function()
{
    var idx=Math.floor(index.get())||0;
    if(idx*3>=arr.length)
    {
        numValues=idx+100;
        init();
    }
    
    idx*=3;
    
    outX.set(arr[idx+0]);
    outY.set(arr[idx+1]);
    outZ.set(arr[idx+2]);
};

function init()
{
    Math.randomSeed=seed.get();
    
    arr.length=Math.floor(numValues*3) || 300;
    for(var i=0;i<arr.length;i+=3)
    {
        arr[i+0]=Math.seededRandom()* ( max.get() - min.get() ) + min.get() ;
        arr[i+1]=Math.seededRandom()* ( max.get() - min.get() ) + min.get() ;
        arr[i+2]=Math.seededRandom()* ( max.get() - min.get() ) + min.get() ;
    }
}


};

Ops.Math.RandomNumbers.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.LFO
// 
// **************************************************************

Ops.Anim.LFO = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
const time=op.inValue("Time");
const type=op.inValueSelect("Type",["sine","triangle","ramp up","ramp down","block"],"sine");
const phase=op.inValue("Phase",0);
const amplitude=op.inValue("Amplitude",1);
const result=op.outValue("Result");


let v=0;
type.onChange=updateType;

updateType();

const PI2=Math.PI/2;


function updateType()
{
    if(type.get()=='sine') time.onChange=sine;
    if(type.get()=='ramp up') time.onChange=rampUp;
    if(type.get()=='ramp down') time.onChange=rampDown;
    if(type.get()=='block') time.onChange=block;
    if(type.get()=='triangle') time.onChange=triangle;
}

function updateTime()
{
    return time.get()+phase.get();
}

function block()
{
    var t=updateTime()+0.5;
    v=t%2.0;
    if(v<=1.0)v=-1;
    else v=1;
    v*=amplitude.get();
    result.set(v);
}

function rampUp()
{
    var t=(updateTime()+1);
    t*=0.5;
    v=t%1.0;
    v-=0.5;
    v*=2.0;
    v*=amplitude.get();
    result.set(v);
}

function rampDown()
{
    var t=updateTime();
    v=t%1.0;
    v-=0.5;
    v*=-2.0;
    v*=amplitude.get();
    result.set(v);
}

function triangle()
{
    var t=updateTime();
    v=t%2.0;
    if(v>1) v= 2.0-v ;
    v-=0.5;
    v*=2.0;
    v*=amplitude.get();
    result.set(v);
}


function sine()
{
    var t=updateTime()*Math.PI-(PI2);
    v=Math.sin( (t) );
    v*=amplitude.get();
    result.set(v);
}

};

Ops.Anim.LFO.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Textures.Graph
// 
// **************************************************************

Ops.Gl.Textures.Graph = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var trigger=this.addInPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));
var value=this.addInPort(new Port(this,"value",OP_PORT_TYPE_VALUE));
var index=this.addInPort(new Port(this,"index",OP_PORT_TYPE_VALUE));
var inReset=this.addInPort(new Port(this,"reset",OP_PORT_TYPE_FUNCTION,{display:'button'}));

var texOut=op.outTexture("Texture");

var cgl=op.patch.cgl;

var canvas = document.createElement('canvas');
canvas.id     = "graph_"+Math.random();
canvas.width  = 512;
canvas.height = 512;
canvas.style.display   = "none";
var body = document.getElementsByTagName("body")[0];
body.appendChild(canvas);

var canvImage = document.getElementById(canvas.id);
var ctx = canvImage.getContext('2d');


var buff=[];

var maxValue=-Number.MAX_VALUE;
var minValue=Number.MAX_VALUE;
var colors=[];
var lastTime=Date.now();

value.onLinkChanged=reset;
index.onLinkChanged=reset;
inReset.onTriggered=reset;

value.onValueChanged=function()
{
    addValue(value.get(),Math.round(index.get()));
};

trigger.onTriggered=function()
{
    for(var i=0;i<buff.length;i++)
    {
        if(buff[i]) addValue(buff[i][ buff[i].length-1 ],i);
    }
    updateGraph();
};

function reset()
{
    buff.length=0;
    maxValue=-999999;
    minValue=999999;
}

function addValue(val,currentIndex)
{
    maxValue=Math.max(maxValue,parseFloat(val));
    minValue=Math.min(minValue,parseFloat(val));
    
    
    if(!buff[currentIndex])
    {
        buff[currentIndex]=[];
        Math.randomSeed=5711+2*currentIndex;
        colors[currentIndex] = 'rgba('+Math.round(Math.seededRandom()*255)+','+Math.round(Math.seededRandom()*255)+','+Math.round(Math.seededRandom()*255)+',1)';
    }
    
    var buf=buff[currentIndex];
    buf.push(val);
    
    if(!trigger.isLinked())if(Date.now()-lastTime>30)updateGraph();
}

function updateGraph()
{
    function getPos(v)
    {
        return canvas.height-( (v/h*canvas.height/2*0.9)+canvas.height/2 );
    }

    ctx.fillStyle="#000";
    ctx.fillRect(0,0,canvas.width,canvas.height);

    ctx.fillStyle="#444";
    ctx.fillRect(0,getPos(0),canvas.width,1);

    for(var b=0;b<buff.length;b++)
    {
        var buf=buff[b];
        if(!buf)continue;

        ctx.lineWidth = 2;
    
        var h=Math.max(Math.abs(maxValue),Math.abs(minValue));
        var heightmul=canvas.height/h;
        var start=Math.max(0,buf.length-canvas.width);

        ctx.beginPath();    
        ctx.strokeStyle=colors[b];

        ctx.moveTo(0,getPos(buf[start]));
        
        for(var i=start;i<buf.length;i++)
        {
            ctx.lineTo(
                1+i-start,
                getPos(buf[i]));
    
        }
        ctx.stroke();
    }

    ctx.font = "22px monospace";

    ctx.fillStyle="#fff";
    ctx.fillText('max:'+(Math.round(maxValue*100)/100), 10, canvas.height-10);
    ctx.fillText('min:'+(Math.round(minValue*100)/100), 10, canvas.height-30);


    if(texOut.get()) texOut.get().initTexture(canvImage);
        else texOut.set( new CGL.Texture.createFromImage(cgl,canvImage,
        {
            "filter":CGL.Texture.FILTER_MIPMAP
            
        }) );

    lastTime=Date.now();
}



};

Ops.Gl.Textures.Graph.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.Rectangle
// 
// **************************************************************

Ops.Gl.Meshes.Rectangle = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.inFunction("render");
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var width=op.inValue("width",1);
var height=op.inValue("height",1);

var pivotX=op.addInPort(new Port(op,"pivot x",OP_PORT_TYPE_VALUE,{display:'dropdown',values:["center","left","right"]} ));
var pivotY=op.addInPort(new Port(op,"pivot y",OP_PORT_TYPE_VALUE,{display:'dropdown',values:["center","top","bottom"]} ));

var nColumns=op.inValueInt("num columns",1);
var nRows=op.inValueInt("num rows",1);
var axis=op.addInPort(new Port(op,"axis",OP_PORT_TYPE_VALUE,{display:'dropdown',values:["xy","xz"]} ));

var active=op.inValueBool('Active',true);

var geomOut=op.addOutPort(new Port(op,"geometry",OP_PORT_TYPE_OBJECT));
geomOut.ignoreValueSerialize=true;

var cgl=op.patch.cgl;
axis.set('xy');
pivotX.set('center');
pivotY.set('center');

op.setPortGroup([pivotX,pivotY]);
op.setPortGroup([width,height]);
op.setPortGroup([nColumns,nRows]);



var geom=new CGL.Geometry('rectangle');
var mesh=null;

axis.onChange=rebuild;
pivotX.onChange=rebuild;
pivotY.onChange=rebuild;
width.onChange=rebuild;
height.onChange=rebuild;
nRows.onChange=rebuild;
nColumns.onChange=rebuild;
rebuild();

render.onTriggered=function()
{
    if(active.get() && mesh) mesh.render(cgl.getShader());
    trigger.trigger();
};

function rebuild()
{
    var w=width.get();
    var h=height.get();
    var x=0;
    var y=0;
    
    if(typeof w=='string')w=parseFloat(w);
    if(typeof h=='string')h=parseFloat(h);
    
    if(pivotX.get()=='center') x=0;
    if(pivotX.get()=='right') x=-w/2;
    if(pivotX.get()=='left') x=+w/2;

    if(pivotY.get()=='center') y=0;
    if(pivotY.get()=='top') y=-h/2;
    if(pivotY.get()=='bottom') y=+h/2;

    var verts=[];
    var tc=[];
    var norms=[];
    var indices=[];

    var numRows=Math.round(nRows.get());
    var numColumns=Math.round(nColumns.get());

    var stepColumn=w/numColumns;
    var stepRow=h/numRows;

    var c,r;

    for(r=0;r<=numRows;r++)
    {
        for(c=0;c<=numColumns;c++)
        {
            verts.push( c*stepColumn - width.get()/2+x );
            if(axis.get()=='xz') verts.push( 0.0 );
            verts.push( r*stepRow - height.get()/2+y );
            if(axis.get()=='xy') verts.push( 0.0 );

            tc.push( c/numColumns );
            tc.push( 1.0-r/numRows );

            if(axis.get()=='xz')
            {
                norms.push(0);
                norms.push(1);
                norms.push(0);
            }

            if(axis.get()=='xy')
            {
                norms.push(0);
                norms.push(0);
                norms.push(-1);
            }
        }
    }
    
    for(c=0;c<numColumns;c++)
    {
        for(r=0;r<numRows;r++)
        {
            var ind = c+(numColumns+1)*r;
            var v1=ind;
            var v2=ind+1;
            var v3=ind+numColumns+1;
            var v4=ind+1+numColumns+1;

            indices.push(v1);
            indices.push(v3);
            indices.push(v2);

            indices.push(v2);
            indices.push(v3);
            indices.push(v4);
        }
    }

    geom.clear();
    geom.vertices=verts;
    geom.texCoords=tc;
    geom.verticesIndices=indices;
    geom.vertexNormals=norms;
    geom.calculateNormals();

    if(!mesh) mesh=new CGL.Mesh(cgl,geom);
        else mesh.setGeom(geom);

    geomOut.set(null);
    geomOut.set(geom);

}


};

Ops.Gl.Meshes.Rectangle.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.ArrayBuffer3x
// 
// **************************************************************

Ops.Array.ArrayBuffer3x = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="ArrayBuffer3x";

var exec=op.inFunction("exec");
var maxLength=op.inValue("Max Num Elements",100);

var valX=op.inValue("Value X");
var valY=op.inValue("Value Y");
var valZ=op.inValue("Value Z");

var inReset=op.inFunctionButton("Reset");

var arr=[];

var arrOut=op.outArray("Result");

arrOut.set(arr);

maxLength.onChange=reset;
inReset.onTriggered=reset;
reset();

var wasReset=true;

function reset()
{
    arr.length=Math.abs(Math.floor(maxLength.get()*3))||0;
    for(var i=0;i<arr.length;i++) arr[i]=0;
    wasReset=true;
    arrOut.set(null);
    arrOut.set(arr);
}

exec.onTriggered=function()
{
    // if(op.instanced(exec))return;
    if(wasReset)
    {
        for (var i = 0, len = arr.length; i < len; i+=3)
        {
            arr[i+0]=valX.get();    
            arr[i+1]=valY.get();    
            arr[i+2]=valZ.get();    
        }

        wasReset=false;
    }

    for (var i = 0, len = arr.length; i < len; i++)
        arr[i-3]=arr[i];


    // for(var i=3;i<arr.length;i++)

    arr[arr.length-3]=valX.get();
    arr[arr.length-2]=valY.get();
    arr[arr.length-1]=valZ.get();
    arrOut.set(null);
    arrOut.set(arr);

};

};

Ops.Array.ArrayBuffer3x.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.SimpleSpline
// 
// **************************************************************

Ops.Gl.Meshes.SimpleSpline = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.inFunction("Render");

var inPoints=op.inArray("Points");
var strip=op.inValueBool("Line Strip",true);
var numPoints=op.inValue("Num Points");

var next=op.outFunction("Next");

var cgl=op.patch.cgl;

var geom=new CGL.Geometry("simplespline");
geom.vertices=[0,0,0,0,0,0,0,0,0];
var mesh=new CGL.Mesh(cgl,geom);
var buff=new Float32Array();

render.onTriggered=function()
{
    var points=inPoints.get();

    if(!points)return;
    if(points.length===0)return;
    if(op.instanced(render))return;

    if(!(points instanceof Float32Array))
    {
        if(points.length!=buff.length)
        {
            buff=new Float32Array(points.length);
            buff.set(points);
        }
        else
        {
            buff.set(points);
        }
    }
    else
    {
        buff=points;
    }
    
    var shader=cgl.getShader();
    if(!shader)return;

    var oldPrim=shader.glPrimitive;
    if(strip.get())shader.glPrimitive=cgl.gl.LINE_STRIP;
        else shader.glPrimitive=cgl.gl.LINES;
    var attr=mesh.setAttribute(CGL.SHADERVAR_VERTEX_POSITION,buff,3);

    
    var numTc=(points.length/3)*2;
    if(mesh.getAttribute(CGL.SHADERVAR_VERTEX_TEXCOORD).numItems!=numTc/2)
    {
        var bufTexCoord=new Float32Array(numTc);
        var attrTc=mesh.setAttribute(CGL.SHADERVAR_VERTEX_TEXCOORD,bufTexCoord,2);
    }
    
    
    if(numPoints.get()<=0)attr.numItems=buff.length/3;
        else attr.numItems=Math.min(numPoints.get(),buff.length/3);





    mesh.render(shader);
    
    // mesh.printDebug();
    
    shader.glPrimitive=oldPrim;
    
    
    next.trigger();
    
};

};

Ops.Gl.Meshes.SimpleSpline.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.NumberArray
// 
// **************************************************************

Ops.Array.NumberArray = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var numValues=op.inValueInt("numValues");

var values=op.addOutPort(new Port(op, "values",OP_PORT_TYPE_ARRAY));
values.ignoreValueSerialize=true;

numValues.set(100);

numValues.onValueChanged=init;

var arr=[];
init();

function init()
{
    arr.length=Math.abs(Math.floor(numValues.get())) || 100;
    for(var i=0;i<arr.length;i++)arr[i]=0;

    values.set(null);
    values.set(arr);
}


};

Ops.Array.NumberArray.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.ArraySetValue3x
// 
// **************************************************************

Ops.Array.ArraySetValue3x = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.inFunctionButton("exe");

var array=op.addInPort(new Port(op, "array",OP_PORT_TYPE_ARRAY));
var index=op.addInPort(new Port(op, "index",OP_PORT_TYPE_VALUE,{type:'int'}));
var value1=op.addInPort(new Port(op, "Value 1",OP_PORT_TYPE_VALUE));
var value2=op.addInPort(new Port(op, "Value 2",OP_PORT_TYPE_VALUE));
var value3=op.addInPort(new Port(op, "Value 3",OP_PORT_TYPE_VALUE));
var values=op.addOutPort(new Port(op, "values",OP_PORT_TYPE_ARRAY));

function updateIndex()
{
    if(exe.isLinked())return;    
    update();
}
function update()
{
    if(!array.get())return;
    array.get()[index.get()*3+0]=value1.get();
    array.get()[index.get()*3+1]=value2.get();
    array.get()[index.get()*3+2]=value3.get();

    values.set(null);
    values.set(array.get());
}

// index.onChange=updateIndex;
// array.onChange=updateIndex;
// value.onChange=update;
exe.onTriggered=update;


};

Ops.Array.ArraySetValue3x.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.SplineMesh
// 
// **************************************************************

Ops.Gl.Meshes.SplineMesh = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.addInPort(new Port(op,"Render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"Next",OP_PORT_TYPE_FUNCTION));
var thick=op.inValue("Thickness");
var inStart=op.inValueSlider("Start");
var inLength=op.inValueSlider("Length",1);
var calcNormals=op.inValueBool("Calculate Normals",false);
var inStrip=op.inValueBool("Line Strip",true);
var inPoints=op.inArray('points');
var inNumPoints=op.inValue("Num Points",0);

var geomOut=op.addOutPort(new Port(op,"geometry",OP_PORT_TYPE_OBJECT));

geomOut.ignoreValueSerialize=true;

var geom=new CGL.Geometry("splinemesh");
var cgl=op.patch.cgl;

var draw=false;
var mesh=null;
var geom=null;
var needsBuild=true;

inPoints.onChange=rebuild;
thick.onChange=rebuild;
inNumPoints.onChange=rebuild;
inStrip.onChange=rebuild;
calcNormals.onChange=rebuild;
var numItems=0;

render.onTriggered=function()
{
    if(needsBuild)doRebuild();
    if(inLength.get()===0 || inStart.get()==1.0)return;

    // console.log('draw',draw);

    if(mesh && draw)
    {


        mesh._bufVertexAttrib.startItem=Math.floor(
            inStart.get()*(numItems/3))*3;
        mesh._bufVertexAttrib.numItems=
            Math.floor(
                Math.min(1,inLength.get()+inStart.get()) * (numItems)
            );

        mesh.render(cgl.getShader());

    }
    trigger.trigger();
};

function rebuild()
{
    needsBuild=true;
}

var vecRot=vec3.create();
var vecA=vec3.create();
var vecB=vec3.create();
var vecC=vec3.create();
var vecD=vec3.create();
var vStart=vec3.create();
var vEnd=vec3.create();
var q=quat.create();
var vecRotation=vec3.create();
vec3.set(vecRotation, 1,0,0);
var vecX=[1,0,0];
var vv=vec3.create();

var index=0;

function linesToGeom(points,options)
{
    if(!geom)
        geom=new CGL.Geometry();

    var i=0;

    points=points||[];

    if(points.length===0)
    {
        for(i=0;i<8;i++)
        {
            points.push(Math.random()*2-1);
            points.push(Math.random()*2-1);
            points.push(0);
        }
    }

    var numPoints=points.length;
    if(inNumPoints.get()!=0 &&
        inNumPoints.get()*3<points.length)numPoints=(inNumPoints.get()-1)*3;

    if(numPoints<2)
    {
        draw=false;
        return;
    }

    // console.log(numPoints);

    var count=0;
    var lastPA=null;
    var lastPB=null;

    if((numPoints/3)*18 > geom.vertices.length )
    {
        geom.vertices=new Float32Array( (numPoints/3*18 ) );
        geom.texCoords=new Float32Array( (numPoints/3*12) );
        // console.log('resize');
    }

    index=0;

    var indexTc=0;
    var lastC=null;
    var lastD=null;

    var m=(thick.get()||0.1)/2;
    var ppl=p/numPoints;

    var pi2=Math.PI/4;

    var strip=inStrip.get();

    var it=3;
    if(!strip)it=6;
    var vv=vec3.create();

    for(var p=0;p<numPoints;p+=it)
    {
        vec3.set(vStart,
            points[p+0],
            points[p+1],
            points[p+2]);

        vec3.set(vEnd,
            points[p+3],
            points[p+4],
            points[p+5]);

        vv[0]=vStart[0]-vEnd[0];
        vv[1]=vStart[1]-vEnd[1];
        vv[2]=vStart[2]-vEnd[2];

        vec3.normalize(vv,vv);
        quat.rotationTo(q,vecX,vv);
        quat.rotateZ(q, q, pi2);
        vec3.transformQuat(vecRot,vecRotation,q);

        if(strip)
        {
            if(lastC)
            {
                vec3.copy(vecA,lastC);
                vec3.copy(vecB,lastD);
            }
            else
            {
                vec3.set(vecA,
                    points[p+0]+vecRot[0]*m,
                    points[p+1]+vecRot[1]*m,
                    points[p+2]+vecRot[2]*m);

                vec3.set(vecB,
                    points[p+0]+vecRot[0]*-m,
                    points[p+1]+vecRot[1]*-m,
                    points[p+2]+vecRot[2]*-m);
            }
        }
        else
        {
            vec3.set(vecA,
                points[p+0]+vecRot[0]*m,
                points[p+1]+vecRot[1]*m,
                points[p+2]+vecRot[2]*m);

            vec3.set(vecB,
                points[p+0]+vecRot[0]*-m,
                points[p+1]+vecRot[1]*-m,
                points[p+2]+vecRot[2]*-m);
        }

        vec3.set(vecC,
            points[p+3]+vecRot[0]*m,
            points[p+4]+vecRot[1]*m,
            points[p+5]+vecRot[2]*m);

        vec3.set(vecD,
            points[p+3]+vecRot[0]*-m,
            points[p+4]+vecRot[1]*-m,
            points[p+5]+vecRot[2]*-m);


//    A-----C
//    |     |
//    B-----D
//
// var xd = vecC[0]-vecA[0];
// var yd = vecC[1]-vecA[1];
// var zd = vecC[2]-vecA[2];
// var dist = 3*Math.sqrt(xd*xd + yd*yd + zd*zd);

var repx0=0;
var repy0=0;
var repx=1;
var repy=1;

repx0=p/(numPoints);
repx=repx0+1/(numPoints/3);



        // a
        geom.vertices[index++]=vecA[0];
        geom.vertices[index++]=vecA[1];
        geom.vertices[index++]=vecA[2];

        geom.texCoords[indexTc++]=repx;
        geom.texCoords[indexTc++]=repy0;

        // b
        geom.vertices[index++]=vecB[0];
        geom.vertices[index++]=vecB[1];
        geom.vertices[index++]=vecB[2];

        geom.texCoords[indexTc++]=repx;
        geom.texCoords[indexTc++]=repy;

        // c
        geom.vertices[index++]=vecC[0];
        geom.vertices[index++]=vecC[1];
        geom.vertices[index++]=vecC[2];

        geom.texCoords[indexTc++]=repx0;
        geom.texCoords[indexTc++]=repy0;

        // d
        geom.vertices[index++]=vecD[0];
        geom.vertices[index++]=vecD[1];
        geom.vertices[index++]=vecD[2];

        geom.texCoords[indexTc++]=repx0;
        geom.texCoords[indexTc++]=repy;

        // c
        geom.vertices[index++]=vecC[0];
        geom.vertices[index++]=vecC[1];
        geom.vertices[index++]=vecC[2];

        geom.texCoords[indexTc++]=repx0;
        geom.texCoords[indexTc++]=repy0;

        // b
        geom.vertices[index++]=vecB[0];
        geom.vertices[index++]=vecB[1];
        geom.vertices[index++]=vecB[2];

        geom.texCoords[indexTc++]=repx;
        geom.texCoords[indexTc++]=repy;

        if(!lastC)
        {
            lastC=vec3.create();
            lastD=vec3.create();
        }

        if(strip)
        {
            lastC[0]=vecC[0];
            lastC[1]=vecC[1];
            lastC[2]=vecC[2];

            lastD[0]=vecD[0];
            lastD[1]=vecD[1];
            lastD[2]=vecD[2];
        }
    }
}

function doRebuild()
{
    draw=true;
    var points=inPoints.get()||[];
    if(!points.length)return;

    linesToGeom(points);

    if(!mesh)
        mesh=new CGL.Mesh(cgl,geom);

    geomOut.set(null);
    geomOut.set(geom);

    if(!draw)
        return;

    // mesh.addVertexNumbers=true;

    numItems=index/3;

    var attr=mesh.setAttribute(CGL.SHADERVAR_VERTEX_POSITION,geom.vertices,3);
    attr.numItems=numItems;

    var attr2=mesh.setAttribute(CGL.SHADERVAR_VERTEX_TEXCOORD,geom.texCoords,2);
    attr2.numItems=numItems;

    // console.log(numItems);

    // mesh._setVertexNumbers();

    if(calcNormals.get())geom.calculateNormals({forceZUp:true});

    needsBuild=false;
}


};

Ops.Gl.Meshes.SplineMesh.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ShaderEffects.VertexDisplacementMap
// 
// **************************************************************

Ops.Gl.ShaderEffects.VertexDisplacementMap = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var cgl=op.patch.cgl;
var id='mod'+Math.floor(Math.random()*10000);

op.render=op.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
op.trigger=op.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));

var texture=this.addInPort(new Port(this,"texture",OP_PORT_TYPE_TEXTURE));

var extrude=op.inValue("extrude",0.5);//addInPort(new Port(this,"extrude",OP_PORT_TYPE_VALUE));

var flip=op.inValueBool("flip",true);

var removeZero=op.addInPort(new Port(this,"Ignore Zero Values",OP_PORT_TYPE_VALUE,{display:'bool'}));

var invert=op.addInPort(new Port(this,"invert",OP_PORT_TYPE_VALUE,{display:'bool'}));
invert.set(false);
var offsetX=op.addInPort(new Port(this,"offset X",OP_PORT_TYPE_VALUE));
var offsetY=op.addInPort(new Port(this,"offset Y",OP_PORT_TYPE_VALUE));

var colorize=op.addInPort(new Port(this,"colorize",OP_PORT_TYPE_VALUE,{display:'bool'}));
var colorizeAdd=op.addInPort(new Port(this,"colorize add",OP_PORT_TYPE_VALUE,{display:'range'}));
colorize.set(false);

function updateColorize()
{
    if(shader)
        if(colorize.get()) shader.define(id+'HEIGHTMAP_COLORIZE');
            else shader.removeDefine(id+'HEIGHTMAP_COLORIZE');
}

function updateInvert()
{
    if(shader)
        if(invert.get()) shader.define(id+'HEIGHTMAP_INVERT');
            else shader.removeDefine(id+'HEIGHTMAP_INVERT');
}

colorize.onValueChanged=updateColorize;
invert.onValueChanged=updateInvert;

var meth=op.addInPort(new Port(this,"mode",OP_PORT_TYPE_VALUE,{display:'dropdown',
    values:['normal','mul xyz','add z','add y','mul y','sub z']}));


removeZero.onValueChanged=updateRemoveZero;

function updateRemoveZero()
{
    if(shader)
        if(removeZero.get()) shader.define(id+'DISPLACE_REMOVE_ZERO');
            else shader.removeDefine(id+'DISPLACE_REMOVE_ZERO');
}

var updateMethod=function()
{
    if(shader)
    {
        if(flip.get()) shader.define(id+'FLIPY');
            else shader.removeDefine(id+'FLIPY');

        shader.removeDefine(id+'DISPLACE_METH_MULXYZ');
        shader.removeDefine(id+'DISPLACE_METH_ADDZ');
        shader.removeDefine(id+'DISPLACE_METH_ADDY');
        shader.removeDefine(id+'DISPLACE_METH_NORMAL');

        if(meth.get()=='mul xyz') shader.define(id+'DISPLACE_METH_MULXYZ');
        if(meth.get()=='add z') shader.define(id+'DISPLACE_METH_ADDZ');
        if(meth.get()=='add y') shader.define(id+'DISPLACE_METH_ADDY');
        if(meth.get()=='mul y') shader.define(id+'DISPLACE_METH_MULY');
        if(meth.get()=='normal') shader.define(id+'DISPLACE_METH_NORMAL');

        updateRemoveZero();
    }
};

flip.onValueChanged=updateMethod;
meth.onValueChanged=updateMethod;
meth.set('normal');

var shader=null;
var uniExtrude,uniTexture;


var srcHeadVert=''
    .endl()+'UNI float {{mod}}_extrude;'
    .endl()+'UNI sampler2D {{mod}}_texture;'
    .endl()+'UNI float {{mod}}_offsetX;'
    .endl()+'UNI float {{mod}}_offsetY;'

    .endl()+'OUT float '+id+'displHeightMapColor;'
    .endl();

var srcBodyVert=''


    .endl()+'vec2 {{mod}}tc=texCoord;'
    // .endl()+'vec2 {{mod}}tc=vec2(pos.x,pos.y);'

    .endl()+'#ifdef '+id+'FLIPY'
    .endl()+'    {{mod}}tc.y=1.0-{{mod}}tc.y;'
    .endl()+'#endif'


    .endl()+'float {{mod}}_texVal=texture2D( {{mod}}_texture, vec2({{mod}}tc.x+{{mod}}_offsetX,{{mod}}tc.y+{{mod}}_offsetY) ).b;'

    .endl()+'#ifdef '+id+'HEIGHTMAP_INVERT'
    .endl()+'{{mod}}_texVal=1.0-{{mod}}_texVal;'
    .endl()+'#endif'

    .endl()+'#ifdef '+id+'DISPLACE_METH_MULXYZ'
    .endl()+'   {{mod}}_texVal+=1.0;'
    .endl()+'   pos.xyz *= {{mod}}_texVal * {{mod}}_extrude;'
    // .endl()+'   norm=normalize(norm+normalize(pos.xyz+vec3({{mod}}_texVal)* {{mod}}_extrude));'
    .endl()+'#endif'

    .endl()+'#ifdef '+id+'DISPLACE_METH_ADDZ'
    .endl()+'   pos.z+=({{mod}}_texVal * {{mod}}_extrude);'
    .endl()+'#endif'

    .endl()+'#ifdef '+id+'DISPLACE_METH_ADDY'
    .endl()+'   pos.y+=({{mod}}_texVal * {{mod}}_extrude);'
    // .endl()+'norm=normalize(norm+normalize(vec3(0.0,0.0+({{mod}}_texVal ),0.0)));'
    .endl()+'#endif'


    .endl()+'#ifdef '+id+'DISPLACE_METH_MULY'
    .endl()+'   pos.y+=(({{mod}}_texVal-0.5) * {{mod}}_extrude);'
    .endl()+'#endif'

    .endl()+'#ifdef '+id+'DISPLACE_METH_NORMAL'
    .endl()+'   pos.xyz+=norm*{{mod}}_texVal*{{mod}}_extrude;'
    .endl()+'#endif'


    .endl()+''+id+'displHeightMapColor={{mod}}_texVal;'

    .endl();

var srcHeadFrag=''
    .endl()+'UNI float {{mod}}_colorizeAdd;'
    .endl()+'IN float '+id+'displHeightMapColor;'
    .endl()+'UNI sampler2D {{mod}}_texture;'
    // .endl()+'IN vec3 vViewPosition;'

    .endl();

var srcBodyFrag=''

    .endl()+'#ifdef '+id+'HEIGHTMAP_COLORIZE'
    .endl()+'   col.rgb*='+id+'displHeightMapColor*(1.0-{{mod}}_colorizeAdd);'
    .endl()+'   col+={{mod}}_colorizeAdd;'
    .endl()+'#endif'

    .endl()+'#ifdef '+id+'DISPLACE_REMOVE_ZERO'
    .endl()+'if('+id+'displHeightMapColor==0.0)discard;'
    .endl()+'#endif'
    .endl();


var moduleFrag=null;
var moduleVert=null;

function removeModule()
{
    if(shader && moduleFrag) shader.removeModule(moduleFrag);
    if(shader && moduleVert) shader.removeModule(moduleVert);
    shader=null;
}


var uniTexture=null;
var uniTextureFrag=null;
var uniExtrude=null;
var uniOffsetX=null;
var uniOffsetY=null;
var uniColorizeAdd=null;

op.render.onLinkChanged=removeModule;

op.render.onTriggered=function()
{
    if(!cgl.getShader())
    {
         op.trigger.trigger();
         return;
    }


    if(cgl.getShader()!=shader)
    {
        if(shader) removeModule();


        // console.log('re init shader module vertexdisplacement');

        shader=cgl.getShader();


        moduleVert=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_VERTEX_POSITION',
                srcHeadVert:srcHeadVert,
                srcBodyVert:srcBodyVert
            });

        uniTexture=new CGL.Uniform(shader,'t',moduleVert.prefix+'_texture',0);
        uniExtrude=new CGL.Uniform(shader,'f',moduleVert.prefix+'_extrude',extrude);
        uniOffsetX=new CGL.Uniform(shader,'f',moduleVert.prefix+'_offsetX',offsetX);
        uniOffsetY=new CGL.Uniform(shader,'f',moduleVert.prefix+'_offsetY',offsetY);

        moduleFrag=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_COLOR',
                srcHeadFrag:srcHeadFrag,
                srcBodyFrag:srcBodyFrag
            });
        uniTextureFrag=new CGL.Uniform(shader,'t',moduleFrag.prefix+'_texture',0);
        uniColorizeAdd=new CGL.Uniform(shader,'f',moduleFrag.prefix+'_colorizeAdd',colorizeAdd);

        updateMethod();
        updateInvert();
        updateColorize();
    }


    if(!shader)return;
    var texSlot=moduleVert.num+5;

    if(texture.get())
    {
        uniTexture.setValue(texSlot);
        uniTextureFrag.setValue(texSlot);

        /* --- */cgl.setTexture(0+texSlot,texture.get().tex);
        // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, texture.get().tex);
    }

    op.trigger.trigger();
};


};

Ops.Gl.ShaderEffects.VertexDisplacementMap.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Noise.PerlinNoise
// 
// **************************************************************

Ops.Gl.TextureEffects.Noise.PerlinNoise = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["perlinnoise3d_frag"]="precision highp float;\n\nUNI float z;\nUNI float x;\nUNI float y;\nUNI float scale;\nIN vec2 texCoord;\nUNI sampler2D tex;\n\nUNI float amount;\n\n{{BLENDCODE}}\n\n\nfloat Interpolation_C2( float x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }   //  6x^5-15x^4+10x^3\t( Quintic Curve.  As used by Perlin in Improved Noise.  http://mrl.nyu.edu/~perlin/paper445.pdf )\nvec2 Interpolation_C2( vec2 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec3 Interpolation_C2( vec3 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec4 Interpolation_C2( vec4 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec4 Interpolation_C2_InterpAndDeriv( vec2 x ) { return x.xyxy * x.xyxy * ( x.xyxy * ( x.xyxy * ( x.xyxy * vec2( 6.0, 0.0 ).xxyy + vec2( -15.0, 30.0 ).xxyy ) + vec2( 10.0, -60.0 ).xxyy ) + vec2( 0.0, 30.0 ).xxyy ); }\nvec3 Interpolation_C2_Deriv( vec3 x ) { return x * x * (x * (x * 30.0 - 60.0) + 30.0); }\n\n\nvoid FAST32_hash_3D( \tvec3 gridcell,\n                        out vec4 lowz_hash_0,\n                        out vec4 lowz_hash_1,\n                        out vec4 lowz_hash_2,\n                        out vec4 highz_hash_0,\n                        out vec4 highz_hash_1,\n                        out vec4 highz_hash_2\t)\t\t//\tgenerates 3 random numbers for each of the 8 cell corners\n{\n    //    gridcell is assumed to be an integer coordinate\n\n    //\tTODO: \tthese constants need tweaked to find the best possible noise.\n    //\t\t\tprobably requires some kind of brute force computational searching or something....\n    const vec2 OFFSET = vec2( 50.0, 161.0 );\n    const float DOMAIN = 69.0;\n    const vec3 SOMELARGEFLOATS = vec3( 635.298681, 682.357502, 668.926525 );\n    const vec3 ZINC = vec3( 48.500388, 65.294118, 63.934599 );\n\n    //\ttruncate the domain\n    gridcell.xyz = gridcell.xyz - floor(gridcell.xyz * ( 1.0 / DOMAIN )) * DOMAIN;\n    vec3 gridcell_inc1 = step( gridcell, vec3( DOMAIN - 1.5 ) ) * ( gridcell + 1.0 );\n\n    //\tcalculate the noise\n    vec4 P = vec4( gridcell.xy, gridcell_inc1.xy ) + OFFSET.xyxy;\n    P *= P;\n    P = P.xzxz * P.yyww;\n    vec3 lowz_mod = vec3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell.zzz * ZINC.xyz ) );\n    vec3 highz_mod = vec3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell_inc1.zzz * ZINC.xyz ) );\n    lowz_hash_0 = fract( P * lowz_mod.xxxx );\n    highz_hash_0 = fract( P * highz_mod.xxxx );\n    lowz_hash_1 = fract( P * lowz_mod.yyyy );\n    highz_hash_1 = fract( P * highz_mod.yyyy );\n    lowz_hash_2 = fract( P * lowz_mod.zzzz );\n    highz_hash_2 = fract( P * highz_mod.zzzz );\n}\n\n//\n//\tPerlin Noise 3D  ( gradient noise )\n//\tReturn value range of -1.0->1.0\n//\thttp://briansharpe.files.wordpress.com/2011/11/perlinsample.jpg\n//\nfloat Perlin3D( vec3 P )\n{\n    //\testablish our grid cell and unit position\n    vec3 Pi = floor(P);\n    vec3 Pf = P - Pi;\n    vec3 Pf_min1 = Pf - 1.0;\n\n#if 1\n    //\n    //\tclassic noise.\n    //\trequires 3 random values per point.  with an efficent hash function will run faster than improved noise\n    //\n\n    //\tcalculate the hash.\n    //\t( various hashing methods listed in order of speed )\n    vec4 hashx0, hashy0, hashz0, hashx1, hashy1, hashz1;\n    FAST32_hash_3D( Pi, hashx0, hashy0, hashz0, hashx1, hashy1, hashz1 );\n    //SGPP_hash_3D( Pi, hashx0, hashy0, hashz0, hashx1, hashy1, hashz1 );\n\n    //\tcalculate the gradients\n    vec4 grad_x0 = hashx0 - 0.49999;\n    vec4 grad_y0 = hashy0 - 0.49999;\n    vec4 grad_z0 = hashz0 - 0.49999;\n    vec4 grad_x1 = hashx1 - 0.49999;\n    vec4 grad_y1 = hashy1 - 0.49999;\n    vec4 grad_z1 = hashz1 - 0.49999;\n    vec4 grad_results_0 = inversesqrt( grad_x0 * grad_x0 + grad_y0 * grad_y0 + grad_z0 * grad_z0 ) * ( vec2( Pf.x, Pf_min1.x ).xyxy * grad_x0 + vec2( Pf.y, Pf_min1.y ).xxyy * grad_y0 + Pf.zzzz * grad_z0 );\n    vec4 grad_results_1 = inversesqrt( grad_x1 * grad_x1 + grad_y1 * grad_y1 + grad_z1 * grad_z1 ) * ( vec2( Pf.x, Pf_min1.x ).xyxy * grad_x1 + vec2( Pf.y, Pf_min1.y ).xxyy * grad_y1 + Pf_min1.zzzz * grad_z1 );\n\n#if 1\n    //\tClassic Perlin Interpolation\n    vec3 blend = Interpolation_C2( Pf );\n    vec4 res0 = mix( grad_results_0, grad_results_1, blend.z );\n    vec4 blend2 = vec4( blend.xy, vec2( 1.0 - blend.xy ) );\n    float final = dot( res0, blend2.zxzx * blend2.wwyy );\n    final *= 1.1547005383792515290182975610039;\t\t//\t(optionally) scale things to a strict -1.0->1.0 range    *= 1.0/sqrt(0.75)\n    return final;\n#else\n    //\tClassic Perlin Surflet\n    //\thttp://briansharpe.wordpress.com/2012/03/09/modifications-to-classic-perlin-noise/\n    Pf *= Pf;\n    Pf_min1 *= Pf_min1;\n    vec4 vecs_len_sq = vec4( Pf.x, Pf_min1.x, Pf.x, Pf_min1.x ) + vec4( Pf.yy, Pf_min1.yy );\n    float final = dot( Falloff_Xsq_C2( min( vec4( 1.0 ), vecs_len_sq + Pf.zzzz ) ), grad_results_0 ) + dot( Falloff_Xsq_C2( min( vec4( 1.0 ), vecs_len_sq + Pf_min1.zzzz ) ), grad_results_1 );\n    final *= 2.3703703703703703703703703703704;\t\t//\t(optionally) scale things to a strict -1.0->1.0 range    *= 1.0/cube(0.75)\n    return final;\n#endif\n\n#else\n    //\n    //\timproved noise.\n    //\trequires 1 random value per point.  Will run faster than classic noise if a slow hashing function is used\n    //\n\n    //\tcalculate the hash.\n    //\t( various hashing methods listed in order of speed )\n    vec4 hash_lowz, hash_highz;\n    FAST32_hash_3D( Pi, hash_lowz, hash_highz );\n    //BBS_hash_3D( Pi, hash_lowz, hash_highz );\n    //SGPP_hash_3D( Pi, hash_lowz, hash_highz );\n\n    //\n    //\t\"improved\" noise using 8 corner gradients.  Faster than the 12 mid-edge point method.\n    //\tKen mentions using diagonals like this can cause \"clumping\", but we'll live with that.\n    //\t[1,1,1]  [-1,1,1]  [1,-1,1]  [-1,-1,1]\n    //\t[1,1,-1] [-1,1,-1] [1,-1,-1] [-1,-1,-1]\n    //\n    hash_lowz -= 0.5;\n    vec4 grad_results_0_0 = vec2( Pf.x, Pf_min1.x ).xyxy * sign( hash_lowz );\n    hash_lowz = abs( hash_lowz ) - 0.25;\n    vec4 grad_results_0_1 = vec2( Pf.y, Pf_min1.y ).xxyy * sign( hash_lowz );\n    vec4 grad_results_0_2 = Pf.zzzz * sign( abs( hash_lowz ) - 0.125 );\n    vec4 grad_results_0 = grad_results_0_0 + grad_results_0_1 + grad_results_0_2;\n\n    hash_highz -= 0.5;\n    vec4 grad_results_1_0 = vec2( Pf.x, Pf_min1.x ).xyxy * sign( hash_highz );\n    hash_highz = abs( hash_highz ) - 0.25;\n    vec4 grad_results_1_1 = vec2( Pf.y, Pf_min1.y ).xxyy * sign( hash_highz );\n    vec4 grad_results_1_2 = Pf_min1.zzzz * sign( abs( hash_highz ) - 0.125 );\n    vec4 grad_results_1 = grad_results_1_0 + grad_results_1_1 + grad_results_1_2;\n\n    //\tblend the gradients and return\n    vec3 blend = Interpolation_C2( Pf );\n    vec4 res0 = mix( grad_results_0, grad_results_1, blend.z );\n    vec4 blend2 = vec4( blend.xy, vec2( 1.0 - blend.xy ) );\n    return dot( res0, blend2.zxzx * blend2.wwyy ) * (2.0 / 3.0);\t//\t(optionally) mult by (2.0/3.0) to scale to a strict -1.0->1.0 range\n#endif\n}\n\nvoid main()\n{\n    vec4 base=texture2D(tex,texCoord);\n    vec2 p=vec2(texCoord.x-0.5,texCoord.y-0.5);\n    \n    p=p*scale;\n    p=vec2(p.x+0.5-x,p.y+0.5-y);\n\n    float aa=texture2D(tex,texCoord).r;\n    float v=Perlin3D(vec3(p.x,p.y,z))*0.5+0.5;\n\n   \n    vec4 col=vec4(v,v,v,1.0);\n    \n    col=vec4( _blend(base.rgb,col.rgb) ,1.0);\n    col=vec4( mix( col.rgb, base.rgb ,1.0-base.a*amount),1.0);\n    \n    gl_FragColor = col;\n}\n";
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));

var blendMode=CGL.TextureEffect.AddBlendSelect(op,"Blend Mode","normal");
var amount=op.inValueSlider("Amount",1);

var x=op.inValue("X",0);
var y=op.inValue("Y",0);
var z=op.inValue("Z",0);
var scale=op.inValue("Scale",22);
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);

var srcFrag=attachments.perlinnoise3d_frag.replace('{{BLENDCODE}}',CGL.TextureEffect.getBlendCode());

shader.setSource(shader.getDefaultVertexShader(),srcFrag );
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

var uniZ=new CGL.Uniform(shader,'f','z',z);
var uniX=new CGL.Uniform(shader,'f','x',x);
var uniY=new CGL.Uniform(shader,'f','y',y);
var uniScale=new CGL.Uniform(shader,'f','scale',scale);
var amountUniform=new CGL.Uniform(shader,'f','amount',amount);

blendMode.onChange=function()
{
    CGL.TextureEffect.onChangeBlendSelect(shader,blendMode.get());
};

render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Noise.PerlinNoise.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Geometry.TransformGeometry
// 
// **************************************************************

Ops.Gl.Geometry.TransformGeometry = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var geometry=op.addInPort(new Port(op,"Geometry",OP_PORT_TYPE_OBJECT));


var transX=op.inValue("Translate X");
var transY=op.inValue("Translate Y");
var transZ=op.inValue("Translate Z");

var scaleX=op.inValueSlider("Scale X",1);
var scaleY=op.inValueSlider("Scale Y",1);
var scaleZ=op.inValueSlider("Scale Z",1);

var rotX=op.inValue("Rotation X");
var rotY=op.inValue("Rotation Y");
var rotZ=op.inValue("Rotation Z");

var outGeom=op.outObject("Result");


transX.onChange=
transY.onChange=
transZ.onChange=
scaleX.onChange=
scaleY.onChange=
scaleZ.onChange=
rotX.onChange=
rotY.onChange=
rotZ.onChange=
geometry.onChange=update;


function update()
{
    var oldGeom=geometry.get();

    if(oldGeom)
    {
        var geom=oldGeom.copy();
        var rotVec=vec3.create();
        var emptyVec=vec3.create();
        var transVec=vec3.create();
        var centerVec=vec3.create();


        
        for(var i=0;i<geom.vertices.length;i+=3)
        {
            geom.vertices[i+0]*=scaleX.get();
            geom.vertices[i+1]*=scaleY.get();
            geom.vertices[i+2]*=scaleZ.get();

            geom.vertices[i+0]+=transX.get();
            geom.vertices[i+1]+=transY.get();
            geom.vertices[i+2]+=transZ.get();
        }

        // var bounds=geom.getBounds();
    
        // vec3.set(centerVec,
        //         bounds.minX+(bounds.maxX-bounds.minX)/2,
        //         bounds.minY+(bounds.maxY-bounds.minY)/2,
        //         bounds.minZ+(bounds.maxZ-bounds.minZ)/2
        //     );

        for(var i=0;i<geom.vertices.length;i+=3)
        {

            vec3.set(rotVec,
                geom.vertices[i+0],
                geom.vertices[i+1],
                geom.vertices[i+2]);

            vec3.rotateX(rotVec,rotVec,transVec,rotX.get()*CGL.DEG2RAD);
            vec3.rotateY(rotVec,rotVec,transVec,rotY.get()*CGL.DEG2RAD);
            vec3.rotateZ(rotVec,rotVec,transVec,rotZ.get()*CGL.DEG2RAD);

            geom.vertices[i+0]=rotVec[0];
            geom.vertices[i+1]=rotVec[1];
            geom.vertices[i+2]=rotVec[2];


        }
        
        outGeom.set(geom);
    }
    else
    {
        outGeom.set(null);
    }
    
    
    
}

};

Ops.Gl.Geometry.TransformGeometry.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Compare.Smaller
// 
// **************************************************************

Ops.Math.Compare.Smaller = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='Smaller';

var number1 = op.addInPort(new Port(this, "number1"));
var number2 = op.addInPort(new Port(this,"number2"));
var result = op.addOutPort(new Port(this, "result"));

var exec= function() {
    result.set( number1.get() < number2.get() );
};

number1.onValueChanged = exec;
number2.onValueChanged = exec;
exec();


};

Ops.Math.Compare.Smaller.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Json3d.Json3dScene2
// 
// **************************************************************

Ops.Json3d.Json3dScene2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));
var filename=op.addInPort(new Port(op,"file",OP_PORT_TYPE_VALUE,{ display:'file',type:'string',filter:'3d json' } ));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var doCreate=op.inFunctionButton("Create Nodes");
var createNonMesh=op.inValueBool("Create Non Mesh Nodes");
var createMaterials=op.inValueBool("Create Materials",false);
var detectClones=op.inValueBool("Detect Clones",true);
var inReplaceMaterials=op.inObject("Mesh Materials");
var outLoading=op.outValueBool("Loading",false);

var cgl=op.patch.cgl;

var scene=new CABLES.Variable();

cgl.frameStore.currentScene=null;

doCreate.onTriggered=createNodes;

var defaultEasing=CABLES.TL.EASING_LINEAR;
var skipFrames=1;
var frameNum=0;
var cloneTransformStore=[];
var data=null;
var prevOp=null;
filename.onChange=reload;
op.exe.onTriggered=render;

var subPatchOpStart=op;
var subPatchId=op.uiAttribs.subPatch;
var subPatchOp=null;

function render()
{
    var oldScene=cgl.frameStore.currentScene;
    cgl.frameStore.currentScene=scene;
    if(cgl.frameStore.currentScene.materials)cgl.frameStore.currentScene.materials.length=0;
    cgl.frameStore.currentScene.replaceMaterials=inReplaceMaterials.get();

    cgl.frameStore.cloneTransforms=cloneTransformStore;

    cgl.pushModelMatrix();
    trigger.trigger();
    cgl.popModelMatrix();

    cgl.frameStore.currentScene=oldScene;
}

var setPortAnimated=function(p, doLerp)
{
    p.setAnimated(true);
    if(doLerp)p.anim.defaultEasing=defaultEasing;
};



function loadMaterials(data,root)
{
    if(data.materials)
    {
        var lastSetMatop=null;
        for(var i in data.materials)
        {
            var jsonMat=data.materials[i];

            var matName='';
            for(var j in jsonMat.properties)
            {
                if(jsonMat.properties[j].key=='?mat.name')
                {
                    matName=jsonMat.properties[j].value;
                }
            }

            for(var j in jsonMat.properties)
            {

                if(createMaterials.get() && jsonMat.properties[j].key && jsonMat.properties[j].value && jsonMat.properties[j].key=='$clr.diffuse')
                {
                    const setMatOp=op.patch.addOp('Ops.Json3d.SetMaterial',{"subPatch":subPatchId});

                    setMatOp.getPort('name').set(matName);
                    setMatOp.name='Set Material '+matName;

                    var matOp=op.patch.addOp('Ops.Gl.Phong.PhongMaterial',{"subPatch":subPatchId});
                    matOp.getPort('diffuse r').set( jsonMat.properties[j].value[0] );
                    matOp.getPort('diffuse g').set( jsonMat.properties[j].value[1] );
                    matOp.getPort('diffuse b').set( jsonMat.properties[j].value[2] );
                    matOp.uiAttribs.title=matOp.name=''+matName;

                    op.patch.link(setMatOp,'material',matOp,'shader');
                    op.patch.link(setMatOp,'exe',matOp,'trigger');

                    if(lastSetMatop) op.patch.link(lastSetMatop,'trigger',matOp,'render');
                        else  op.patch.link(root,'trigger 0',matOp,'render');

                    lastSetMatop=setMatOp;
                    prevOp=matOp;
                }
            }
        }
    }
}



var loadCameras=function(data,seq)
{
    var i=0;
    var camOp=null;

    function getCamera(root,_cam)
    {
        var cam={"cam":_cam};
        for(i in root.children)
        {
            if(root.children[i].name==_cam.name)
            {
                cam.eye=root.children[i];
                cam.transformation=root.children[i].transformation;
                mat4.transpose(cam.transformation,cam.transformation);

                // guess camera target (...)
                for(var j=0;j<root.children.length;j++)
                {
                    if(root.children[j].name == root.children[i].name+'_Target')
                    {
                        console.log("Found cameratarget!");
                        cam.target=root.children[i];
                        root.children.splice(j,1);
                        root.children.splice(i,1);
                        return cam;
                    }
                }
            }
        }
        return cam;
    }


    var camSeq=null;

    if(data.hasOwnProperty('cameras'))
    {
        camSeq=op.patch.addOp('Ops.Trigger.TimedSequence',{"subPatch":subPatchId,"translate":{x:op.uiAttribs.translate.x,y:op.uiAttribs.translate.y+50}});
        op.patch.link(camSeq,'exe',op,'trigger');

        console.log("camera....");

        var camCount=0;
        for(i in data.cameras)
        {
            var cam=getCamera(data.rootnode,data.cameras[i]);

            if(cam)
            {
                if(!cam.target) continue;

                var camOp=op.patch.addOp('Ops.Gl.Matrix.QuaternionCamera',{"subPatch":subPatchId,"translate":{x:op.uiAttribs.translate.x+camCount*200,y:op.uiAttribs.translate.y+100}});
                camOp.uiAttribs.title=camOp.name='cam '+cam.cam.name;

                var an=dataGetAnimation(data,cam.cam.name);
                op.patch.link(camSeq,'trigger '+camCount,camOp,'render');
                op.patch.link(camOp,'trigger',seq,'exe '+camCount);
                camCount++;

                camOp.getPort('fov').set(cam.cam.horizontalfov);
                camOp.getPort('clip near').set(cam.cam.clipplanenear);
                camOp.getPort('clip far' ).set(cam.cam.clipplanefar);

                camOp.getPort('centerX').set(cam.cam.lookat[0]);
                camOp.getPort('centerY').set(cam.cam.lookat[1]);
                camOp.getPort('centerZ').set(cam.cam.lookat[2]);

                camOp.getPort('matrix').set(cam.transformation);

                if(an)
                {
                    if(an.positionkeys)
                    {
                        setPortAnimated(camOp.getPort('EyeX'),false);
                        setPortAnimated(camOp.getPort('EyeY'),false);
                        setPortAnimated(camOp.getPort('EyeZ'),false);

                        frameNum=skipFrames;
                        for(var k in an.positionkeys)
                        {
                            if(frameNum%skipFrames===0)
                            {
                                camOp.getPort('EyeX').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][0] );
                                camOp.getPort('EyeY').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][1] );
                                camOp.getPort('EyeZ').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][2] );
                            }
                            frameNum++;
                        }
                    }

                    if(an.rotationkeys)
                    {
                        setPortAnimated(camOp.getPort('quat x'),false);
                        setPortAnimated(camOp.getPort('quat y'),false);
                        setPortAnimated(camOp.getPort('quat z'),false);
                        setPortAnimated(camOp.getPort('quat w'),false);

                        frameNum=skipFrames;
                        for(var k in an.rotationkeys)
                        {
                            if(frameNum%skipFrames==0)
                            {
                                camOp.getPort('quat x').anim.setValue( an.rotationkeys[k][0], an.rotationkeys[k][1][0] );
                                camOp.getPort('quat y').anim.setValue( an.rotationkeys[k][0], an.rotationkeys[k][1][1] );
                                camOp.getPort('quat z').anim.setValue( an.rotationkeys[k][0], an.rotationkeys[k][1][2] );
                                camOp.getPort('quat w').anim.setValue( an.rotationkeys[k][0], an.rotationkeys[k][1][3] );
                            }
                            frameNum++;
                        }
                    }

                }
                else
                {
                    var camOp=op.patch.addOp('Ops.Gl.Matrix.LookatCamera',{"subPatch":subPatchId,"translate":{x:op.uiAttribs.translate.x+camCount*150,y:op.uiAttribs.translate.y+100}});
                    camOp.uiAttribs.title=camOp.name='cam '+cam.cam.name;
                    // op.patch.link(camOp,'render',self,'trigger');

                    op.patch.link(camSeq,'trigger '+camCount,camOp,'render');
                    op.patch.link(camOp,'trigger',seq,'exe '+camCount);
                    camCount++;

                    camOp.getPort('eyeX').set(900);
                    camOp.getPort('eyeY').set(900);
                    camOp.getPort('eyeZ').set(-240);

                    var an=dataGetAnimation(data,cam.cam.name);
                    if(an)
                    {
                        setPortAnimated(camOp.getPort('eyeX'),false);
                        setPortAnimated(camOp.getPort('eyeY'),false);
                        setPortAnimated(camOp.getPort('eyeZ'),false);

                        frameNum=skipFrames;
                        for(var k in an.positionkeys)
                        {
                            if(frameNum%skipFrames==0)
                            {
                                camOp.getPort('eyeX').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][0] );
                                camOp.getPort('eyeY').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][1] );
                                camOp.getPort('eyeZ').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][2] );
                            }
                            frameNum++;
                        }
                    }

                    var an=dataGetAnimation(data,cam.cam.name+'_Target');
                    if(an)
                    {
                        setPortAnimated(camOp.getPort('centerX'),false);
                        setPortAnimated(camOp.getPort('centerY'),false);
                        setPortAnimated(camOp.getPort('centerZ'),false);

                        frameNum=skipFrames;
                        for(var k in an.positionkeys)
                        {
                            if(frameNum%skipFrames==0)
                            {
                                camOp.getPort('centerX').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][0] );
                                camOp.getPort('centerY').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][1] );
                                camOp.getPort('centerZ').anim.setValue( an.positionkeys[k][0], an.positionkeys[k][1][2] );
                            }
                            frameNum++;
                        }
                    }
                    else
                    {
                        camOp.getPort('centerX').set(cam.target.transformation[12]);
                        camOp.getPort('centerY').set(cam.target.transformation[13]);
                        camOp.getPort('centerZ').set(cam.target.transformation[14]);
    
                        op.log("target not animated",cam.target.transformation[3]);
                    }
                }
            }
        }
    }

    return null;
};

function dataGetAnimation(data,name)
{
    if(!data.hasOwnProperty('animations')) return false;

    for(var iAnims in data.animations)
    {
        for(var iChannels in data.animations[iAnims].channels)
        {
            if(data.animations[iAnims].channels[iChannels].name==name)
            {
                return data.animations[iAnims].channels[iChannels];
            }
        }
    }
    return false;
}

var maxx=-3;
var row=0;

function hasMeshChildNode(n)
{
    if(createNonMesh.get())return true;
    if(n.meshes && n.meshes.length>0)return true;
    if(n.hasOwnProperty('children'))
    {
        for(var i=0;i<n.children.length;i++)
        {
            if(n.children[i].meshes && n.children[i].meshes.length>0)return true;
            
            var childMeshes=hasMeshChildNode(n.children[i]);
            if(childMeshes)return true;
        }
    }
    
    console.log('has no childs',n);
    
    return false;
}

function addChild(data,x,y,parentOp,parentPort,ch)
{
    if(ch.hasOwnProperty('transformation'))
    {
        maxx=Math.max(x,maxx)+1;
        var prevOp=null;

        if(data.hasOwnProperty('animations'))
        {
            var an=dataGetAnimation(data,ch.name);
            if(an)
            {
                if(an.positionkeys && an.positionkeys.length>0)
                {
                    var anTransOp=op.patch.addOp('Ops.Json3d.TranslateChannel',{"subPatch":subPatchId});
                    anTransOp.uiAttribs.title=anTransOp.name=ch.name+' trans anim';
                    anTransOp.getPort('channel').set( ch.name );
                    op.patch.link(prevOp,'trigger',anTransOp,'render');

                    if(!prevOp)op.patch.link(parentOp,parentPort,anTransOp,'render');
                    prevOp=anTransOp;
                }

                if(an.scalingkeys && an.scalingkeys.length>0)
                {
                    var anScaleOp=op.patch.addOp('Ops.Json3d.ScaleChannel',{"subPatch":subPatchId});
                    anScaleOp.uiAttribs.title=anScaleOp.name=ch.name+' scale anim';
                    anScaleOp.getPort('channel').set( ch.name );
                    op.patch.link(prevOp,'trigger',anScaleOp,'render');

                    if(!prevOp)op.patch.link(parentOp,parentPort,anScaleOp,'render');
                    prevOp=anScaleOp;
                }

                if(an.rotationkeys && an.rotationkeys.length>0)
                {
                    var anRotOp=op.patch.addOp('Ops.Json3d.QuaternionChannel',{"subPatch":subPatchId});
                    anRotOp.uiAttribs.title=anRotOp.name=ch.name+' quat rot anim';
                    anRotOp.getPort('channel').set( ch.name );
                    op.patch.link(prevOp,'trigger',anRotOp,'render');

                    if(!prevOp)op.patch.link(parentOp,parentPort,anRotOp,'render');
                    prevOp=anRotOp;
                }
            }
        }

        var sameMesh=false;
        
        if(detectClones.get())
        {
            sameMesh=true;
            
            if(ch.hasOwnProperty('children'))
            {
                // test if children are all same mesh...
    
                var cloneTransforms=[];
                if(ch.children.length>1 && ch.children[0].meshes && ch.children[0].meshes.length>0)
                {
                    for(i=0;i<ch.children.length;i++)
                    {
                        if(i>0 && ch.children[i].meshes)
                        {
                            if(ch.children[0].meshes && ch.children[i].meshes && ch.children[i].meshes.length==ch.children[0].meshes.length)
                            {
                                if(ch.children[i].meshes[0]==ch.children[0].meshes[0])
                                {
    
                                } else { sameMesh=false; }
                            } else { sameMesh=false; }
                        }
    
                        if(sameMesh)
                        {
                            if(!ch.children[i].transposed)
                            {
                                mat4.transpose(ch.children[i].transformation,ch.children[i].transformation);
                                ch.children[i].transposed=true;
                            }
                            cloneTransforms.push(ch.children[i].transformation);
                        }
                    }
                } else { sameMesh=false; }
            } else { sameMesh=false; }
        }

        if(!prevOp )
        {
            
            // console.log(ch.transformation);
            var eq=mat4.exactEquals(ch.transformation,[1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);
            
            if(!eq)
            {
                var transOp=op.patch.addOp('Ops.Gl.Matrix.MatrixMul',{"subPatch":subPatchId});
    
                if(!ch.transposed)
                {
                    ch.transposed=true;
                    mat4.transpose(ch.transformation,ch.transformation);
                }
    
                transOp.getPort('matrix').set(ch.transformation);
                prevOp=transOp;
    
                op.patch.link(parentOp,parentPort,prevOp,'render');
                if(ch.name) transOp.uiAttribs.title=transOp.name=ch.name;
                
            }
            else 
                prevOp=parentOp;
            
        }


        var i=0;
        if(ch.hasOwnProperty('meshes') || sameMesh )
        {
            var useChildrenMeshes=false;
            var len=0;
            if(ch.meshes)
            {
                len=ch.meshes.length;
            }
            else
            {
                if(ch.children[0].meshes)
                {
                    len=ch.children[0].meshes.length;
                    useChildrenMeshes=true;
                }
            }

            // console.log('useChildrenMeshes ',useChildrenMeshes);

            for(i=0;i<len;i++)
            {
                var index=-1;

                if(!useChildrenMeshes) index=ch.meshes[i];
                    else index=ch.children[0].meshes[0];

                // material
                if(data.meshes[index].hasOwnProperty('materialindex') && data.hasOwnProperty('materials'))
                {
                    var matIndex=data.meshes[index].materialindex;
                    var jsonMat=data.materials[matIndex];

                    if(inReplaceMaterials.get() && inReplaceMaterials.get()[ch.name])
                    {
                        var matOp=op.patch.addOp('Ops.Json3d.SetMaterialShader',{"subPatch":subPatchId});
                        matOp.getPort("Key").set(ch.name);

                        var l=op.patch.link(prevOp,'trigger',matOp,'exe');

                        if(!l)
                        {
                            l=op.patch.link(parentOp,'trigger 15',matOp,'exe');
                        }

                        prevOp=matOp;
                    }
                    else
                    if(createMaterials.get())
                    {
                        var matOp=op.patch.addOp('Ops.Json3d.Material',{"subPatch":subPatchId});
                        op.patch.link(prevOp,'trigger',matOp,'exe');
                        prevOp=matOp;
    
                        for(var j in jsonMat.properties)
                            if(jsonMat.properties[j].key && jsonMat.properties[j].value && jsonMat.properties[j].key=='?mat.name')
                                matOp.getPort('name').set( jsonMat.properties[j].value );
                    }
                }

                if(!sameMesh)
                {
                    // mesh
                    var meshOp=op.patch.addOp('Ops.Json3d.Mesh',{"subPatch":subPatchId});
                    meshOp.index.val=index;
                    meshOp.uiAttribs.title=meshOp.name=ch.name+'';
                    var l=op.patch.link(prevOp,'trigger',meshOp,'render');

                    if(!l)
                    {
                        var l=op.patch.link(parentOp,'trigger 15',meshOp,'render');
                        console.log('prevOp link',parentOp);
                    }
                    
                }
            }
        }


        if(ch.hasOwnProperty('children'))
        {
            console.log(ch.name+' children are clones: ',sameMesh);

            if(sameMesh)
            {
                var clonedOp=op.patch.addOp('Ops.Json3d.ClonedMesh',{"subPatch":subPatchId});

                clonedOp.getPort('transformations').set(cloneTransforms);

                cloneTransformStore.push(cloneTransforms);
                // console.log(cloneTransformStore.length+' cloneTransformStore !!!');

                op.patch.link(prevOp,'trigger',clonedOp,'render');

                var meshOp=op.patch.addOp('Ops.Json3d.Mesh',{"subPatch":subPatchId});
                meshOp.index.val=ch.children[0].meshes[0];
                meshOp.uiAttribs.title=meshOp.name='clone '+ch.name+' Mesh';
                meshOp.getPort('draw').set(false);

                op.patch.link(prevOp,'trigger',meshOp,'render');
                op.patch.link(clonedOp,'geom',meshOp,'geometry');
            }

            if(!sameMesh)
            {
                y++;
                for(i=0;i<ch.children.length;i++)
                {
                    // console.log('   child...'+i+'/'+ch.children.length);
                    var xx=maxx;
                    if(ch.children.length>1)xx++;
                    
                    if( hasMeshChildNode(ch.children[i]) )
                        addChild(data,xx,y,prevOp,'trigger',ch.children[i]);
                }
            }

        }
    }
}

function reload()
{
    doCreate.setUiAttribs({greyout:true});
    if(!filename.get())return;

    function doLoad()
    {
        CABLES.ajax(
            op.patch.getFilePath(filename.get()),
            function(err,_data,xhr)
            {

                if(err)
                {
                    if(CABLES.UI)op.uiAttr({'error':'could not load file...'});

                    console.error('ajax error:',err);
                    op.patch.loading.finished(loadingId);
                    return;
                }
                else
                {
                    if(CABLES.UI)op.uiAttr({'error':null});
                }

                try
                {
                    data=JSON.parse(_data);
                    // console.log("parsed data...");
                    // console.log(data);
                }
                catch(ex)
                {
                    outLoading.set(false);
                    op.patch.loading.finished(loadingId);
                    if(CABLES.UI)op.uiAttr({'error':'could not load file...'});
                    return;
                }
                
                
                scene.setValue(data);
                doCreate.setUiAttribs({greyout:false});
                op.patch.loading.finished(loadingId);
                
                outLoading.set(false);
                if(CABLES.UI) gui.jobs().finish('loading3d'+loadingId);
                doCreate.setUiAttribs({greyout:false});
            });
    }

    
    outLoading.set(true);
    var loadingId=op.patch.loading.start('json3dScene',filename.get());
    if(CABLES.UI) gui.jobs().start({id:'loading3d'+loadingId,title:'loading 3d data'},doLoad);
        else doLoad();

};

function createNodes()
{

    if(!trigger.isLinked())
    {
        var subPatchOpStartPort='trigger';
        if(!subPatchOp)
        {
            subPatchId=op.uiAttribs.subPatch;
            subPatchOp=op.patch.addOp(CABLES.UI.OPNAME_SUBPATCH,{"subPatch":subPatchId});
            subPatchOp.setTitle("3d scene");
            subPatchId=subPatchOp.getPort("patchId").get();
            op.patch.link(op,'trigger',subPatchOp,'create port');

            var inputs=op.patch.getOpsByObjName("Ops.Ui.PatchInput");
            
            for(var i=0;i<inputs.length;i++)
            {
                if(inputs[i].uiAttribs.subPatch==subPatchId)
                {
                    subPatchOpStart=inputs[i];
                    subPatchOpStartPort=subPatchOpStart.portsOut[0].name;
                }
            }
        }
        

        var rootMatrixOp=op.patch.addOp('Ops.Gl.Matrix.MatrixMul',{"subPatch":subPatchId,"translate":{x:op.uiAttribs.translate.x,y:op.uiAttribs.translate.y+75}});
        rootMatrixOp.uiAttribs.title='rootMatrix';

        mat4.transpose(data.rootnode.transformation,data.rootnode.transformation);
        rootMatrixOp.getPort('matrix').set(data.rootnode.transformation);

        // op.patch.link(op,'trigger',rootMatrixOp,'render');
        op.patch.link(subPatchOpStart,subPatchOpStartPort,rootMatrixOp,'render');

        var root=op.patch.addOp('Ops.Sequence',{"subPatch":subPatchId,"translate":{x:op.uiAttribs.translate.x,y:op.uiAttribs.translate.y+150}});
        var camOp=loadCameras(data,root);

        if(camOp) op.patch.link(camOp,'trigger',root,'exe');
            else op.patch.link(rootMatrixOp,'trigger',root,'exe');

        loadMaterials(data,root);

        for(var i=0;i<data.rootnode.children.length;i++)
        {
            if(data.rootnode.children[i])
            {
                var ntrigger=i+2;
                if(ntrigger>9)ntrigger=9;
                
                if( hasMeshChildNode( data.rootnode.children[i] ))
                    addChild( data,maxx-2,3,root,'trigger '+ntrigger,data.rootnode.children[i] );
            }
        }
        
        if(CABLES.UI)
        {
            setTimeout(function()
            {
                // gui.patch().setSelectedOpById(op.id);
                // CABLES.CMD.PATCH.tidyChildOps();

                gui.patch().setSelectedOpById(subPatchOpStart.id);
                CABLES.CMD.PATCH.tidyChildOps();
                
                gui.patch().updateSubPatches();

            },100);
            gui.patch().updateSubPatches();
        }
    }
    else
    {
        if(CABLES.UI)
        {
            CABLES.UI.notifyError("remove child nodes first");
            
        }
    }
}






};

Ops.Json3d.Json3dScene2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Json3d.Mesh
// 
// **************************************************************

Ops.Json3d.Mesh = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION ));
// op.index=op.addInPort(new Port(op,"mesh index",OP_PORT_TYPE_VALUE,{type:'string'} ));
op.index=op.inValueInt("mesh index");
var centerPivot=op.addInPort(new Port(op,"center pivot",OP_PORT_TYPE_VALUE,{display:'bool'} ));
var next=op.addOutPort(new Port(op,"next",OP_PORT_TYPE_FUNCTION));

var geometryOut=op.addOutPort(new Port(op,"geometry",OP_PORT_TYPE_OBJECT ));
var draw=op.addInPort(new Port(op,"draw",OP_PORT_TYPE_VALUE,{display:'bool'}));

op.index.set(0);
geometryOut.ignoreValueSerialize=true;
centerPivot.set(false);
draw.set(true);

const cgl=op.patch.cgl;
var mesh=null;
var currentIndex=0;

op.index.onValueChanged=reload;
render.onTriggered=doRender;

function doRender()
{
    // if(!mesh && cgl.frameStore.currentScene && cgl.frameStore.currentScene.getValue() || currentIndex!=op.index.get()) reload();
    if(!mesh || currentIndex!=op.index.get()) reload();
    if(draw.get())
    {
        if(mesh) mesh.render(cgl.getShader());
        next.trigger();
    }
}

function reload()
{
    if(!cgl.frameStore.currentScene || !cgl.frameStore.currentScene.getValue())return;
    var meshes=cgl.frameStore.currentScene.getValue().meshes;

    if(cgl.frameStore.currentScene && cgl.frameStore.currentScene.getValue() && op.index.get()>=0)
    {
        op.uiAttr({warning:''});
        op.uiAttr({info:''});

        var jsonMesh=null;

        currentIndex=op.index.get();

        if(isNumeric(op.index.get()))
        {
            if(op.index.get()<0 || op.index.get()>=cgl.frameStore.currentScene.getValue().meshes.length)
            {
                op.uiAttr({warning:'mesh not found - index out of range '});
                return;
            }

            jsonMesh=cgl.frameStore.currentScene.getValue().meshes[parseInt(op.index.get(),10) ];
        }

        if(!jsonMesh)
        {
            mesh=null;
            op.uiAttr({warning:'mesh not found'});
            return;
        }
        op.uiAttribs.warning='';

        var i=0;
        var verts=JSON.parse(JSON.stringify(jsonMesh.vertices));

        var geom=new CGL.Geometry();
        geom.vertices=verts;
        geom.vertexNormals=jsonMesh.normals||[];
        geom.tangents=jsonMesh.tangents||[];
        geom.biTangents=jsonMesh.bitangents||[];
        
        if(centerPivot.get())geom.center();

        if(jsonMesh.texturecoords) geom.texCoords = jsonMesh.texturecoords[0];
        geom.verticesIndices=[];
        geom.verticesIndices=[].concat.apply([], jsonMesh.faces);

        var nfo='';
        nfo += (geom.verticesIndices.length/3)+' faces <br/>';
        nfo += (geom.vertices.length/3)+' vertices <br/>';
        nfo += geom.texCoords.length+' texturecoords <br/>';
        nfo += geom.tangents.length+' tangents <br/>';
        nfo += geom.biTangents.length+' biTangents <br/>';
        if(geom.vertexNormals) nfo += geom.vertexNormals.length+' normals <br/>';
        
        op.uiAttr({"info":nfo});

        geometryOut.set(null);
        geometryOut.set(geom);
        mesh=new CGL.Mesh(cgl,geom);
    }
}

centerPivot.onValueChanged=function()
{
    mesh=null;
};


};

Ops.Json3d.Mesh.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Json3d.Bones.BoneSystem
// 
// **************************************************************

Ops.Json3d.Bones.BoneSystem = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
// https://www.khronos.org/opengl/wiki/Skeletal_Animation
// http://ogldev.atspace.co.uk/www/tutorial38/tutorial38.html

var render=op.inFunction("Render");
var inMeshIndex=op.inValueInt("Mesh Index");

var inTime=op.inValue("Time");

var inFade=op.inValueSlider("Fade Times");
var inTime2=op.inValue("Time2");


var next=op.outFunction("Next");
var outNumBounes=op.outValue("Num Bones");
var outSpline=op.outArray("Spline");
var outJoint=op.outFunction("Joint Trigger");

var points=[];
var tempMat=mat4.create();
var tempVec=vec3.create();
var emptyVec=vec3.create();
var transVec=vec3.create();

var alwaysEmptyVec=vec3.create();
var q=quat.create();
var q2=quat.create();
var qMat=mat4.create();
var boneMatrix=mat4.create();

var cgl=op.patch.cgl;
var scene=null;
var meshIndex=0;
var bones=0;
var oldScene=null;
var boneList=[];
var fillBoneList=true;
var pointCounter=0;

inMeshIndex.onChange=function()
{
    meshIndex=inMeshIndex.get();
};

function findBoneChilds(n,parent,foundBone)
{
    function isBone(name)
    {
        if(scene.meshes[meshIndex].bones)
            for(var i=0;i<scene.meshes[meshIndex].bones.length;i++)
                if(scene.meshes[meshIndex].bones[i].name==name)
                    return scene.meshes[meshIndex].bones[i];
        return false;
    }

    function findAnimation(name)
    {
        var an=0;
        for(var an=0;an<scene.animations.length;an++)
        
            for(var i=0;i<scene.animations[an].channels.length;i++)
                if(scene.animations[an].channels[i].name==name)
                    return scene.animations[an].channels[i];

        return null;
    }

// if(parent)console.log(parent.name+'  -  '+n.name);

    var time=op.patch.timer.getTime();
    if(inTime.isLinked() || inTime.get()!==0)time=inTime.get();
    var time2=inTime2.get();

    

    cgl.pushModelMatrix();

    var bone=isBone(n.name);

    if( (bone||foundBone) && n!=scene.rootnode)
    {
        foundBone=true;
        
        if(!n.anim)
        {
            // create anim objects for translation/rotation
            var anim=findAnimation(n.name);
            if(anim)
            {
                n.anim=anim;
        
                if(anim && !n.quatAnimX && anim.rotationkeys)
                {
                    n.quatAnimX=new CABLES.TL.Anim();
                    n.quatAnimY=new CABLES.TL.Anim();
                    n.quatAnimZ=new CABLES.TL.Anim();
                    n.quatAnimW=new CABLES.TL.Anim();
            
                    for(var k in anim.rotationkeys)
                    {
                        n.quatAnimX.setValue( anim.rotationkeys[k][0],anim.rotationkeys[k][1][1] );
                        n.quatAnimY.setValue( anim.rotationkeys[k][0],anim.rotationkeys[k][1][2] );
                        n.quatAnimZ.setValue( anim.rotationkeys[k][0],anim.rotationkeys[k][1][3] );
                        n.quatAnimW.setValue( anim.rotationkeys[k][0],anim.rotationkeys[k][1][0] );
                    }
                }
                if(anim && !n.posAnimX && anim.positionkeys)
                {
                    n.posAnimX=new CABLES.TL.Anim();
                    n.posAnimY=new CABLES.TL.Anim();
                    n.posAnimZ=new CABLES.TL.Anim();
            
                    for(var k in anim.positionkeys)
                    {
                        n.posAnimX.setValue( anim.positionkeys[k][0],anim.positionkeys[k][1][0] );
                        n.posAnimY.setValue( anim.positionkeys[k][0],anim.positionkeys[k][1][1] );
                        n.posAnimZ.setValue( anim.positionkeys[k][0],anim.positionkeys[k][1][2] );
                    }
                }
            }
        }

        if(n.posAnimX)
        {
            transVec[0]=n.posAnimX.getValue(time);
            transVec[1]=n.posAnimY.getValue(time);
            transVec[2]=n.posAnimZ.getValue(time);

            if(inFade.get()!=0)
            {
                transVec[0]=(transVec[0]*(1.0-inFade.get())) + (n.posAnimX.getValue(time2)*inFade.get());
                transVec[1]=(transVec[1]*(1.0-inFade.get())) + (n.posAnimY.getValue(time2)*inFade.get());
                transVec[2]=(transVec[2]*(1.0-inFade.get())) + (n.posAnimZ.getValue(time2)*inFade.get());

                mat4.translate(cgl.mvMatrix,cgl.mvMatrix,transVec);
            }
            else
            {
                mat4.translate(cgl.mvMatrix,cgl.mvMatrix,transVec);
            }
        }

        if(n.quatAnimX)
        {
            CABLES.TL.Anim.slerpQuaternion(time,q,
                n.quatAnimX,
                n.quatAnimY,
                n.quatAnimZ,
                n.quatAnimW);

            if(inFade.get()!=0)
            {
                CABLES.TL.Anim.slerpQuaternion(time2,q2,
                    n.quatAnimX,
                    n.quatAnimY,
                    n.quatAnimZ,
                    n.quatAnimW);
                quat.slerp(q,q,q2,inFade.get());
            }

            mat4.fromQuat(qMat, q);
            mat4.multiply(cgl.mvMatrix,cgl.mvMatrix, qMat);
        }

        // get position
        vec3.transformMat4( tempVec, alwaysEmptyVec, cgl.mvMatrix );
        if(!n.boneMatrix)
        {
            n.boneMatrix=mat4.create();
            n.transformed=vec3.create();
        }
        vec3.copy(n.transformed,tempVec);
        
        mat4.copy(n.boneMatrix,cgl.mvMatrix);

        // store absolute bone matrix
        if(bone)
        {
            if(!bone.matrix)bone.matrix=mat4.create();
            mat4.copy(bone.matrix,cgl.mvMatrix);
            
            if(!bone.transposedOffsetMatrix)
            {
                mat4.transpose( bone.offsetmatrix, bone.offsetmatrix );
                bone.transposedOffsetMatrix=true;
            }
            mat4.mul(bone.matrix,bone.matrix,bone.offsetmatrix);
        }

        if(parent && parent.transformed)
        {
            points[pointCounter++]=parent.transformed[0];
            points[pointCounter++]=parent.transformed[1];
            points[pointCounter++]=parent.transformed[2];

            points[pointCounter++]=tempVec[0];
            points[pointCounter++]=tempVec[1];
            points[pointCounter++]=tempVec[2];
        }

        if(fillBoneList) boneList.push(n);
        cgl.frameStore.bone=n;
    }

    if(n.children)
    {
        for(var i=0;i<n.children.length;i++)
        {
            if(isBone(n.children[i].name)) bones++;
            findBoneChilds(n.children[i],n,foundBone);
        }
    }
    
    cgl.popModelMatrix();

    return bones;
}

render.onTriggered=function()
{
    pointCounter=0;
    bones=0;
    scene=cgl.frameStore.currentScene.getValue();
    cgl.frameStore.bones=boneList;

    if(!scene)return;
    if(scene!=oldScene)
    {
        fillBoneList=true;
        boneList.length=0;
        oldScene=scene;
    }

    cgl.pushModelMatrix();
    mat4.identity(cgl.mvMatrix);
    findBoneChilds(scene.rootnode,null,false);
    cgl.popModelMatrix();

outSpline.set(null);
    outSpline.set(points);
    outNumBounes.set(bones);
    fillBoneList=false;

    next.trigger();
    cgl.frameStore.bones=null;
};




};

Ops.Json3d.Bones.BoneSystem.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Json3d.Bones.BoneSkin
// 
// **************************************************************

Ops.Json3d.Bones.BoneSkin = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["skin_vert"]="\n\nif(skinIndex.x!=-1.0)\n{\n    int index=int(skinIndex.x);\n    vec4 newPos = (bone[index] * pos) * skinWeight.x;\n    vec3 newNorm = (vec4((bone[index] * vec4(norm.xyz, 0.0)) * skinWeight.x).xyz);\n    \n    if(skinIndex.y!=-1.0)\n    {\n        index=int(skinIndex.y);\n        newPos = (bone[index] * pos) * skinWeight.y + newPos;\n        newNorm = (vec4((bone[index] * vec4(norm.xyz, 0.0)) * skinWeight.y).xyz)+newNorm;\n    }\n    \n    if(skinIndex.z!=-1.0)\n    {\n        index=int(skinIndex.z);\n        newPos = (bone[index] * pos) * skinWeight.z + newPos;\n        newNorm = (vec4((bone[index] * vec4(norm.xyz, 0.0)) * skinWeight.z).xyz)+newNorm;\n    }\n\n    if(skinIndex.w!=-1.0)\n    {\n        index=int(skinIndex.w);\n        newPos = (bone[index] * pos) * skinWeight.w + newPos;\n        newNorm = (vec4((bone[index] * vec4(norm.xyz, 0.0)) * skinWeight.w).xyz)+newNorm;\n    }\n    \n    pos=newPos;\n    norm=normalize(newNorm.xyz);\n}\n";
attachments["skin_head_vert"]="\n\nIN vec4 skinIndex;\nIN vec4 skinWeight;\n\nUNI mat4 bone[SKIN_NUM_BONES];";

// https://www.khronos.org/opengl/wiki/Skeletal_Animation

var render=op.inFunction("Render");
var inMeshIndex=op.inValueInt("MeshIndex");
var inGeom=op.inObject("Geometry");
var draw=op.inValueBool("draw",true);
var next=op.outFunction("Next");

var geom=null;
var mesh=null;
var shader=null;

var cgl=op.patch.cgl;
var meshIndex=0;

var boneMatrices=[];
var boneMatricesUniform=null;
var vertWeights=null;
var vertIndex=null;
var attribWeightsScene=-1;
var moduleVert=null;

render.onLinkChanged=removeModule;
op.onDelete=removeModule;
inMeshIndex.onChange=reset;
inGeom.onChange=setGeom;

function removeModule()
{
    if(shader && moduleVert) shader.removeModule(moduleVert);
    shader=null;
    reset();
}

function reset()
{
    meshIndex=inMeshIndex.get();
    attribWeightsScene=null;
    if(shader)removeModule();
    mesh=null;
    vertWeights=null;
}

function setGeom()
{
    vertWeights=null;
    geom=inGeom.get();

    if(geom)
    {
        mesh=new CGL.Mesh(cgl,geom);
        op.error('geom',null);
    }
    else
    {
        op.error('geom','no/invalid geometry');
    }
}

function setupIndexWeights(jsonMesh)
{
    if(!mesh)
    {
        return;
    }

    // if(!vertWeights) console.log('no vertWeights');
    //     else if(vertWeights.length!=geom.vertices.length/3) console.log('wrong length');

    if(!vertWeights || vertWeights.length!=geom.vertices.length/3)
    {
        vertWeights=[];
        vertIndex=[];
        vertWeights.length=geom.vertices.length/3;
        vertIndex.length=geom.vertices.length/3;

        for(var i=0;i<vertWeights.length;i++)
        {
            vertWeights[i]=[-1,-1,-1,-1];
            vertIndex[i]=[-1,-1,-1,-1];
        }
    }

    var maxBone=-1;
    var maxindex=-1;
    var bones=jsonMesh.bones;
    for(var i=0;i<bones.length;i++)
    {
        var bone=bones[i];
        maxBone=Math.max(maxBone,i);

        for(var w=0;w<bone.weights.length;w++)
        {
            var index=bone.weights[w][0];
            var weight=bone.weights[w][1];
            maxindex=Math.max(maxindex,index);

            if(vertWeights[index][0]==-1)
            {
                vertWeights[index][0]=weight;
                vertIndex[index][0]=i;
            }
            else if(vertWeights[index][1]==-1)
            {
                vertWeights[index][1]=weight;
                vertIndex[index][1]=i;
            }
            else if(vertWeights[index][2]==-1)
            {
                vertWeights[index][2]=weight;
                vertIndex[index][2]=i;
            }
            else if(vertWeights[index][3]==-1)
            {
                vertWeights[index][3]=weight;
                vertIndex[index][3]=i;
            }
            else console.log("too many weights for vertex!!!!!!!");
        }
    }
    
    shader.define("SKIN_NUM_BONES",bones.length);
    
    var vi=[].concat.apply([], vertIndex);
    var vw=[].concat.apply([], vertWeights);

    mesh.setAttribute("skinIndex", vi,4);
    mesh.setAttribute("skinWeight",vw ,4);
}

render.onTriggered=function()
{
    if(!cgl.getShader()) return;
    var scene=cgl.frameStore.currentScene.getValue();

    if(cgl.getShader()!=shader)
    {
        // console.log("NEW SHADER!");
    }

    if( (mesh && scene && scene.meshes && scene.meshes.length>meshIndex) || cgl.getShader()!=shader)
    {
        if(cgl.getShader()!=shader)
        {
            
            // console.log('bonesys RECOMPILE');


            var startInit=CABLES.now();
            // console.log("starting bone skin shader init...");

            if(shader)removeModule();
            shader=cgl.getShader();

            moduleVert=shader.addModule(
                {
                    title:op.objName,
                    priority:-1,
                    name:'MODULE_VERTEX_POSITION',
                    srcHeadVert:attachments.skin_head_vert||'',
                    srcBodyVert:attachments.skin_vert||''
                });
            shader.define("SKIN_NUM_BONES",1);
            boneMatricesUniform=new CGL.Uniform(shader,'m4','bone',[]);
            attribWeightsScene=null;
            // console.log("finished bone skin shader init...",(CABLES.now()-startInit));
        }

        if(attribWeightsScene!=scene)
        {
            var startInit=CABLES.now();
            // console.log("starting bone skin weights init...");
            vertWeights=null;
            setGeom();
            attribWeightsScene=scene;
            setupIndexWeights( scene.meshes[meshIndex] );
            // console.log("finished bone skin  weights init...",(CABLES.now()-startInit));
        }

        var bones=scene.meshes[meshIndex].bones;

        for(var i=0;i<bones.length;i++)
        {
            if(bones[i].matrix)
            {
                if(boneMatrices.length!=bones.length*16)
                    boneMatrices.length=bones.length*16;

                // console.log('.');

                for(var mi=0;mi<16;mi++)
                    boneMatrices[i*16+mi]=bones[i].matrix[mi];
            }
            else
            {
                // console.log('no bone matrix',i);
            }
        }
        // console.log(boneMatrices);
        
        boneMatricesUniform.setValue(boneMatrices);
        
    }

    if(draw.get() && mesh)
    {
        if(mesh) mesh.render(cgl.getShader());
        next.trigger();
    }
    
};

};

Ops.Json3d.Bones.BoneSkin.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.BoolAnim
// 
// **************************************************************

Ops.Anim.BoolAnim = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var exe=op.addInPort(new Port(op,"exe",OP_PORT_TYPE_FUNCTION));
var bool=op.addInPort(new Port(op,"bool",OP_PORT_TYPE_VALUE,{display:'bool'}));
var valueFalse=op.addInPort(new Port(op,"value false",OP_PORT_TYPE_VALUE));
var valueTrue=op.addInPort(new Port(op,"value true",OP_PORT_TYPE_VALUE));
var duration=op.addInPort(new Port(op,"duration",OP_PORT_TYPE_VALUE));


var next=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var value=op.addOutPort(new Port(op,"value",OP_PORT_TYPE_VALUE));
var finished=op.addOutPort(new Port(op,"finished",OP_PORT_TYPE_VALUE));
var finishedTrigger=op.outFunction("Finished Trigger");

valueFalse.set(0);
valueTrue.set(1);
duration.set(0.3);

var anim=new CABLES.TL.Anim();
anim.createPort(op,"easing");

var startTime=CABLES.now();

function setAnim()
{
    finished.set(false);
    var now=(CABLES.now()-startTime)/1000;
    var oldValue=anim.getValue(now);
    anim.clear();

    anim.setValue(now,oldValue);

    if(!bool.get()) anim.setValue(now+duration.get(),valueFalse.get());
        else anim.setValue(now+duration.get(),valueTrue.get());
}

bool.onValueChanged=setAnim;
valueFalse.onValueChanged=setAnim;
valueTrue.onValueChanged=setAnim;
duration.onValueChanged=setAnim;


exe.onTriggered=function()
{
    var t=(CABLES.now()-startTime)/1000;
    value.set(anim.getValue(t));
    
    if(anim.hasEnded(t))
    {
        if(!finished.get()) finishedTrigger.trigger();
        finished.set(true);
    }

    next.trigger();
};

setAnim();




};

Ops.Anim.BoolAnim.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ShaderEffects.DeformArea
// 
// **************************************************************

Ops.Gl.ShaderEffects.DeformArea = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["deformarea_vert"]="\nUNI bool MOD_smooth;\nUNI float MOD_x,MOD_y,MOD_z;\nUNI float MOD_strength;\nUNI float MOD_size;\n\nvec4 MOD_deform(vec4 pos)\n{\n    // vec3 MOD_pos=vec3();\n    vec4 modelPos=pos;\n    vec3 forcePos=vec3(MOD_x,MOD_y,MOD_z);\n    vec3 vecToOrigin=modelPos.xyz-forcePos;\n    float dist=abs(length(vecToOrigin));\n    float distAlpha = (MOD_size - dist) / MOD_size;\n\n    if(MOD_size > dist)\n    {\n        vec3 vecNormal=normalize(vecToOrigin);\n\n        if(MOD_smooth) distAlpha=smoothstep(0.0,MOD_size,distAlpha);\n\n        vec3 velocity = (vecNormal * distAlpha * MOD_strength );\n\n        pos.xyz+=velocity*0.1;\n    }    \n    // else pos.xyz*=0.01;\n    \n    return pos;\n\n}\n";

var cgl=op.patch.cgl;

op.render=op.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
op.trigger=op.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));

var inSize=op.inValue("Size",1);
var inStrength=op.inValue("Strength",0.5);
var inSmooth=op.inValueBool("Smooth",true);

{
    // position

    var x=op.inValue("x");
    var y=op.inValue("y");
    var z=op.inValue("z");
}


var shader=null;

var srcHeadVert=attachments.deformarea_vert;

var srcBodyVert=''
    .endl()+'pos=MOD_deform(pos);'
    .endl();
    
var moduleVert=null;

function removeModule()
{
    if(shader && moduleVert) shader.removeModule(moduleVert);
    shader=null;
}


op.render.onLinkChanged=removeModule;

op.render.onTriggered=function()
{
    
    if(CABLES.UI && gui.patch().isCurrentOp(op)) 
        gui.setTransformGizmo(
            {
                posX:x,
                posY:y,
                posZ:z
            });


    if(!cgl.getShader())
    {
         op.trigger.trigger();
         return;
    }

    if(cgl.getShader()!=shader)
    {
        if(shader) removeModule();
        shader=cgl.getShader();

        moduleVert=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_VERTEX_POSITION',
                srcHeadVert:srcHeadVert,
                srcBodyVert:srcBodyVert
            });

        inSize.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'size',inSize);
        inStrength.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'strength',inStrength);
        inSmooth.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'smooth',inSmooth);

        x.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'x',x);
        y.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'y',y);
        z.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'z',z);
    }
    
    
    if(!shader)return;

    op.trigger.trigger();
};















};

Ops.Gl.ShaderEffects.DeformArea.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Value.TriggerOnChange
// 
// **************************************************************

Ops.Value.TriggerOnChange = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var inval=op.inValue("Value");

var next=op.outFunction("Next");

inval.onChange=function()
{
    next.trigger();
};

};

Ops.Value.TriggerOnChange.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.Camera
// 
// **************************************************************

Ops.Gl.Matrix.Camera = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

/* Inputs */
// projection | prespective & ortogonal
var projectionMode=op.addInPort(new Port(op,"projection mode",OP_PORT_TYPE_VALUE,{display:'dropdown',values:['prespective','ortogonal']}));
var zNear=op.addInPort(new Port(op,"frustum near",OP_PORT_TYPE_VALUE ));
var zFar=op.addInPort(new Port(op,"frustum far",OP_PORT_TYPE_VALUE ));

var fov=op.addInPort(new Port(op,"fov",OP_PORT_TYPE_VALUE ));

var autoAspect=op.inValueBool("Auto Aspect Ratio",true);
var aspect=op.inValue("Aspect Ratio");

// look at camera
var eyeX=op.addInPort(new Port(op,"eye X"));
var eyeY=op.addInPort(new Port(op,"eye Y"));
var eyeZ=op.addInPort(new Port(op,"eye Z"));

var centerX=op.addInPort(new Port(op,"center X"));
var centerY=op.addInPort(new Port(op,"center Y"));
var centerZ=op.addInPort(new Port(op,"center Z"));

// camera transform and movements
var posX=op.addInPort(new Port(op,"truck"),0);
var posY=op.addInPort(new Port(op,"boom"),0);
var posZ=op.addInPort(new Port(op,"dolly"),0);

var rotX=op.addInPort(new Port(op,"tilt"),0);
var rotY=op.addInPort(new Port(op,"pan"),0);
var rotZ=op.addInPort(new Port(op,"roll"),0);


/* Outputs */
var outAsp=op.addOutPort(new Port(op,"Aspect",OP_PORT_TYPE_VALUE));
var outArr=op.outArray("Look At Array");


/* logic */
var cgl=op.patch.cgl;

// prespective
projectionMode.set('prespective');
zNear.set(0.01);
zFar.set(500.0);
fov.set(45);
aspect.set(1);

var asp=0;

// look at camera
centerX.set(0);
centerY.set(0);
centerZ.set(0);

eyeX.set(0);
eyeY.set(0);
eyeZ.set(5);

var vUp=vec3.create();
var vEye=vec3.create();
var vCenter=vec3.create();
var transMatrix=mat4.create();
mat4.identity(transMatrix);

var arr=[];

// Transform and move
var vPos=vec3.create();
var transMatrixMove=mat4.create();
mat4.identity(transMatrixMove);

var updateCameraMovementMatrix=true;

render.onTriggered=function() {
    // Aspect ration
    if(!autoAspect.get()) asp=aspect.get();
    else asp=cgl.getViewPort()[2]/cgl.getViewPort()[3];
    outAsp.set(asp);
    
    // translation (truck, boom, dolly)
    cgl.pushViewMatrix();
    
    if (updateCameraMovementMatrix) {
        mat4.identity(transMatrixMove);
        
        vec3.set(vPos, posX.get(),posY.get(),posZ.get());
        if(posX.get()!==0.0 || posY.get()!==0.0 || posZ.get()!==0.0)
            mat4.translate(transMatrixMove,transMatrixMove, vPos);
        
        if(rotX.get()!==0)
            mat4.rotateX(transMatrixMove,transMatrixMove, rotX.get()*CGL.DEG2RAD);
        if(rotY.get()!==0)
            mat4.rotateY(transMatrixMove,transMatrixMove, rotY.get()*CGL.DEG2RAD);
        if(rotZ.get()!==0)
            mat4.rotateZ(transMatrixMove,transMatrixMove, rotZ.get()*CGL.DEG2RAD);
        
        updateCameraMovementMatrix = false;
    }
    
    mat4.multiply(cgl.vMatrix,cgl.vMatrix,transMatrixMove);
    
    // projection (prespective / ortogonal)
    cgl.pushPMatrix();
    
    // look at
    cgl.pushViewMatrix();
 
    if (projectionMode.get()=='prespective') {
        mat4.perspective(
            cgl.pMatrix,
            fov.get()*0.0174533,
            asp, 
            zNear.get(), 
            zFar.get()
        );
    } else if (projectionMode.get()=='ortogonal') {
        mat4.ortho(
            cgl.pMatrix,
            -1 * (fov.get() / 14),
             1 * (fov.get() / 14),
            -1 * (fov.get() / 14) / asp,
             1 * (fov.get() / 14) / asp,
            zNear.get(), 
            zFar.get()
        );
    }
    
    
	arr[0]=eyeX.get();
	arr[1]=eyeY.get();
	arr[2]=eyeZ.get();

	arr[3]=centerX.get();
	arr[4]=centerY.get();
	arr[5]=centerZ.get();

	arr[6]=0;
	arr[7]=1;
	arr[8]=0;

	outArr.set(arr);

	vec3.set(vUp, 0, 1, 0);
	vec3.set(vEye, eyeX.get(),eyeY.get(),eyeZ.get());
	vec3.set(vCenter, centerX.get(),centerY.get(),centerZ.get());

	mat4.lookAt(transMatrix, vEye, vCenter, vUp);

	mat4.multiply(cgl.vMatrix,cgl.vMatrix,transMatrix);

	trigger.trigger();

	cgl.popViewMatrix();
	cgl.popPMatrix();

	cgl.popViewMatrix();
    
    
	// GUI for dolly, boom and truck
	if(CABLES.UI && gui.patch().isCurrentOp(op)) 
		gui.setTransformGizmo({
			posX:posX,
			posY:posY,
			posZ:posZ
		});
};

var updateUI=function() {
	if(!autoAspect.get()) {
		aspect.setUiAttribs({hidePort:false,greyout:false});
	} else {
		aspect.setUiAttribs({hidePort:true,greyout:true});
	}
};

var cameraMovementChanged=function() {
	updateCameraMovementMatrix = true;
};

// listeners
posX.onChange=cameraMovementChanged;
posY.onChange=cameraMovementChanged;
posZ.onChange=cameraMovementChanged;

rotX.onChange=cameraMovementChanged;
rotY.onChange=cameraMovementChanged;
rotZ.onChange=cameraMovementChanged;

autoAspect.onChange=updateUI;
updateUI();




};

Ops.Gl.Matrix.Camera.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.SwitchTrigger
// 
// **************************************************************

Ops.Trigger.SwitchTrigger = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
// constants
var NUM_PORTS = 10;

// inputs
var exePort = op.inFunctionButton('Execute');
var switchPort = op.inValue('Switch Value');

// outputs
var nextTriggerPort = op.outFunction('Next Trigger');
var valueOutPort = op.outValue('Switched Value');
var triggerPorts = [];
for(var j=0; j<NUM_PORTS; j++) {
    triggerPorts[j] = op.outFunction('Trigger ' + j);
}
var defaultTriggerPort = op.outFunction('Default Trigger');

// functions

/**
 * Performs the switch case
 */
function update() {
    var index = Math.round(switchPort.get());
    if(index >= 0 && index < NUM_PORTS) {
        valueOutPort.set(index);    
        triggerPorts[index].trigger();
    } else {
        valueOutPort.set(-1);    
        defaultTriggerPort.trigger();   
    }
    nextTriggerPort.trigger();
}

// change listeners / trigger events
exePort.onTriggered = update;

};

Ops.Trigger.SwitchTrigger.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Boolean.And
// 
// **************************************************************

Ops.Boolean.And = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='And';

var bool0=op.addInPort(new Port(op,"bool 1",OP_PORT_TYPE_VALUE));
var bool1=op.addInPort(new Port(op,"bool 2",OP_PORT_TYPE_VALUE));

var result=op.addOutPort(new Port(op,"result",OP_PORT_TYPE_VALUE));

function exec()
{
    result.set( bool1.get() && bool0.get() );
}

bool0.onValueChanged=exec;
bool1.onValueChanged=exec;



};

Ops.Boolean.And.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Sine
// 
// **************************************************************

Ops.Math.Sine = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
// input
var value = op.inValue('value');

var phase = op.inValue('phase', 0.0);
var mul = op.inValue('frequency', 1.0);
var amplitude = op.inValue('amplitude', 1.0);
var invert = op.inValueBool("asine", false);

// output
var result = op.outValue('result');

var calculate = Math.sin;

phase.onChange = 
value.onChange = function()
{
    result.set(
        amplitude.get() * calculate( ( value.get()*mul.get() ) + phase.get() )
    );
};

invert.onChange = function()
{
    if(invert.get()) calculate = Math.asin;
    else calculate = Math.sin;
}


};

Ops.Math.Sine.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Array.ArrayChunk
// 
// **************************************************************

Ops.Array.ArrayChunk = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="ArrayChunk";

// inputs
var inArrayPort = op.inArray("Input Array");
var beginPort = op.inValue("Begin Index", 0);
var sizePort = op.inValue("Chunk Size", 1);
var circularPort = op.inValueBool("Circular", false);

// functions
function setOutarray() {
    var inArr = inArrayPort.get();
    var begin = beginPort.get();
    var size = sizePort.get();
    var circular = circularPort.get();
    
    if(begin < 0) {
        begin = 0;
    }
    if(circular && begin >= inArr.length) {
        begin %= inArr.length;
    }
    
    if(!inArr || size < 1) {
        outArrayPort.set([]);
        return;
    }
    var end = size + begin;
    var chunk = inArr.slice(begin, end);
    // circular mode - if chunk does not contain enough elements, take more from the beginning
    if(circular && chunk.length < size) {
        var remainingArrSize = size - chunk.length;
        var beginArr = inArr.slice(0, remainingArrSize);
        chunk.push.apply(chunk, beginArr);
    }
    outArrayPort.set(chunk);
}

// change listeners
inArrayPort.onChange = setOutarray;
beginPort.onChange = setOutarray;
sizePort.onChange = setOutarray;
circularPort.onChange = setOutarray;

// outputs
var outArrayPort = op.outArray("Output Array");

};

Ops.Array.ArrayChunk.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.TriggerLimiter
// 
// **************************************************************

Ops.TriggerLimiter = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var inTriggerPort = op.inFunction("In Trigger");
var timePort = op.inValue("Milliseconds", 300);

var outTriggerPort = op.outFunction("Out Trigger");
var progress=op.outValue("Progress");

var lastTriggerTime = 0;

inTriggerPort.onTriggered = function()
{
    var now = CABLES.now();
    var prog=(now-lastTriggerTime )/timePort.get();

    if(prog>1.0)prog=1.0;
    if(prog<0.0)prog=0.0;

    progress.set(prog);

    if(now >=lastTriggerTime + timePort.get())
    {
        lastTriggerTime = now;
        outTriggerPort.trigger();
    }
};



};

Ops.TriggerLimiter.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.BulgePinch
// 
// **************************************************************

Ops.Gl.TextureEffects.BulgePinch = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var cgl=op.patch.cgl;

op.name='BulgePinch';

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var shader=new CGL.Shader(cgl);
//op.onLoaded=shader.compile;

var radius=op.addInPort(new Port(op,"Radius",OP_PORT_TYPE_VALUE,{  }));
radius.set(0.5);
var uniRadius=new CGL.Uniform(shader,'f','radius',radius.get());
radius.onValueChanged=function() { uniRadius.setValue(radius.get()); };

var strength=op.addInPort(new Port(op,"Strength",OP_PORT_TYPE_VALUE,{  }));
strength.set(1);
var uniStrength=new CGL.Uniform(shader,'f','strength',strength.get());
strength.onValueChanged=function() { uniStrength.setValue(strength.get()); };

var centerX=op.addInPort(new Port(op,"Center X",OP_PORT_TYPE_VALUE,{  }));
centerX.set(0.5);
var uniCenterX=new CGL.Uniform(shader,'f','centerX',centerX.get());
centerX.onValueChanged=function() { uniCenterX.setValue(centerX.get()); };

var centerY=op.addInPort(new Port(op,"Center Y",OP_PORT_TYPE_VALUE,{  }));
centerY.set(0.5);
var uniCenterY=new CGL.Uniform(shader,'f','centerY',centerY.get());
centerY.onValueChanged=function() { uniCenterY.setValue(centerY.get()); };

var srcFrag=''
    .endl()+'precision highp float;'
    .endl()+'IN vec2 texCoord;'
    .endl()+'UNI sampler2D tex;'

    .endl()+'UNI float radius;'
    .endl()+'UNI float strength;'
    .endl()+'UNI float centerX;'
    .endl()+'UNI float centerY;'


    .endl()+'void main()'
    .endl()+'{'
    .endl()+'   vec2 center=vec2(centerX,centerY);'
    .endl()+'   vec2 coord=texCoord;'
    .endl()+'   coord -= center;'
    .endl()+'   float distance = length(coord);'
    .endl()+'   float percent = distance / radius;'
    .endl()+'   if (strength > 0.0) coord *= mix(1.0, smoothstep(0.0, radius / distance, percent), strength * 0.75);'
    .endl()+'   else coord *= mix(1.0, pow(percent, 1.0 + strength * 0.75) * radius / distance, 1.0 - percent);'
    .endl()+'   coord += center;'
    .endl()+'   vec4 col=texture2D(tex,coord);'
    .endl()+'   gl_FragColor = col;'
    .endl()+'}';

shader.setSource(shader.getDefaultVertexShader(),srcFrag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);


render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.BulgePinch.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Meshes.TextMesh
// 
// **************************************************************

Ops.Gl.Meshes.TextMesh = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["textmesh_frag"]="UNI sampler2D tex;\nIN vec2 texCoord;\nUNI float r;\nUNI float g;\nUNI float b;\nUNI float a;\n\n\nvoid main()\n{\n   vec4 col=texture2D(tex,texCoord);\n   col.a=col.r;\n   col.r*=r;\n   col.g*=g;\n   col.b*=b;\n   col*=a;\n\n   gl_FragColor=col;\n}";
attachments["textmesh_vert"]="UNI sampler2D tex;\nUNI mat4 projMatrix;\nUNI mat4 modelMatrix;\nUNI mat4 viewMatrix;\nUNI float scale;\nIN vec3 vPosition;\nIN vec2 attrTexCoord;\nIN mat4 instMat;\nIN vec2 attrTexOffsets;\nIN vec2 attrTexSize;\n\nOUT vec2 texCoord;\n\nvoid main()\n{\n   texCoord=(attrTexCoord*(attrTexSize)) + attrTexOffsets;\n   mat4 instModelMat=instMat;\n   instModelMat[3][0]*=scale;\n\n   vec4 vert=vec4( vPosition.x*(attrTexSize.x/attrTexSize.y)*scale,vPosition.y*scale,vPosition.z*scale, 1. );\n\n   mat4 mvMatrix=viewMatrix * modelMatrix * instModelMat;\n\n   #ifndef BILLBOARD\n       gl_Position = projMatrix * mvMatrix * vert;\n   #endif\n}\n";
var render=op.inFunction("Render");
var next=op.outFunction("Next");
var textureOut=op.outTexture("texture");
var str=op.inValueString("Text","cables");
var scale=op.inValue("Scale",1);
var inFont=op.inValueString("Font","Arial");
var align=op.inValueSelect("align",['left','center','right'],'center');
var valign=op.inValueSelect("vertical align",['Top','Middle','Bottom'],'Middle');
var lineHeight=op.inValue("Line Height",1);
var letterSpace=op.addInPort(new Port(op,"Letter Spacing"));

var loaded=op.outValue("Font Available",0);

var cgl=op.patch.cgl;

var textureSize=2048;
var fontLoaded=false;

align.onChange=generateMesh;
str.onChange=generateMesh;

lineHeight.onChange=generateMesh;
var cgl=op.patch.cgl;
var geom=null;
var mesh=null;

var createMesh=true;
var createTexture=true;

textureOut.set(null);
inFont.onChange=function()
    {
        createTexture=true;
        createMesh=true;
        checkFont();
    };

function checkFont()
{
    var oldFontLoaded=fontLoaded;
    try
    {
    fontLoaded=document.fonts.check('20px '+inFont.get());
    }
    catch(ex)
    {
        console.log(ex);
    }

    if(!oldFontLoaded && fontLoaded)
    {
        loaded.set(true);
        createTexture=true;
        createMesh=true;
    }

    if(!fontLoaded) setTimeout(checkFont,250);
}

var canvasid=null;


CABLES.OpTextureMeshCanvas={};

var valignMode=0;

valign.onChange=function()
{
    if(valign.get()=='Middle')valignMode=0;
    if(valign.get()=='Top')valignMode=1;
    if(valign.get()=='Bottom')valignMode=2;
};

function getFont()
{
    canvasid=''+inFont.get();
    if(CABLES.OpTextureMeshCanvas.hasOwnProperty(canvasid))
    {
        return CABLES.OpTextureMeshCanvas[canvasid];
    }

    var fontImage = document.createElement('canvas');
    fontImage.dataset.font=inFont.get();
    fontImage.id = "texturetext_"+CABLES.generateUUID();
    fontImage.style.display = "none";
    var body = document.getElementsByTagName("body")[0];
    body.appendChild(fontImage);
    var _ctx= fontImage.getContext('2d');
    CABLES.OpTextureMeshCanvas[canvasid]=
        {
            ctx:_ctx,
            canvas:fontImage,
            chars:{},
            characters:'?',
            fontSize:320
        };
    return CABLES.OpTextureMeshCanvas[canvasid];
}

op.onDelete=function()
{
    // fontImage.remove();
    if(canvasid && CABLES.OpTextureMeshCanvas[canvasid])
        CABLES.OpTextureMeshCanvas[canvasid].canvas.remove();
};

var shader=new CGL.Shader(cgl,'TextMesh');
shader.setSource(attachments.textmesh_vert,attachments.textmesh_frag);
var uniTex=new CGL.Uniform(shader,'t','tex',0);
var uniScale=new CGL.Uniform(shader,'f','scale',scale);

var r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range',colorPick:'true' }));
r.set(1.0);
r.uniform=new CGL.Uniform(shader,'f','r',r);

var g=op.addInPort(new Port(op,"g",OP_PORT_TYPE_VALUE,{ display:'range'}));
g.set(1.0);
g.uniform=new CGL.Uniform(shader,'f','g',g);

var b=op.addInPort(new Port(op,"b",OP_PORT_TYPE_VALUE,{ display:'range' }));
b.set(1.0);
r.uniform=new CGL.Uniform(shader,'f','b',b);

var a=op.addInPort(new Port(op,"a",OP_PORT_TYPE_VALUE,{ display:'range'}));
a.uniform=new CGL.Uniform(shader,'f','a',a);
a.set(1.0);

var height=0;

var vec=vec3.create();
var lastTextureChange=-1;
var disabled=false;

render.onTriggered=function()
{
    if(op.instanced(render))return;

    var font=getFont();
    if(font.lastChange!=lastTextureChange)
    {
        createMesh=true;
        lastTextureChange=font.lastChange;
    }

    if(createTexture) generateTexture();
    if(createMesh)generateMesh();
    
    if(mesh && mesh.numInstances>0)
    {
        cgl.pushBlendMode(CGL.BLEND_NORMAL,true);
        cgl.setShader(shader);
    
        cgl.setTexture(0,textureOut.get().tex);

        if(valignMode==2) vec3.set(vec, 0,height,0);
        if(valignMode==1) vec3.set(vec, 0,0,0);
        if(valignMode==0) vec3.set(vec, 0,height/2,0);
        vec[1]-=lineHeight.get();
        cgl.pushModelMatrix();
        mat4.translate(cgl.mvMatrix,cgl.mvMatrix, vec);
        if(!disabled)mesh.render(cgl.getShader());
    
        cgl.popModelMatrix();
    
        cgl.setTexture(0,null);
        cgl.setPreviousShader();
        cgl.popBlendMode();
    }

    next.trigger();
};

letterSpace.onChange=function()
{
    createMesh=true;
};


function generateMesh()
{
    var theString=String(str.get()+'');
    if(!textureOut.get())return;

    var font=getFont();
    if(!font.geom)
    {
        font.geom=new CGL.Geometry("textmesh");

        font.geom.vertices = [
            1.0, 1.0, 0.0,
            0.0, 1.0, 0.0,
            1.0, 0.0, 0.0,
            0.0, 0.0, 0.0
        ];

        font.geom.texCoords = new Float32Array([
            1.0, 1.0,
            0.0, 1.0,
            1.0, 0.0,
            0.0, 0.0
        ]);

        font.geom.verticesIndices = [
            0, 1, 2,
            3, 1, 2
        ];
    }

    if(!mesh)mesh=new CGL.Mesh(cgl,font.geom);

    var strings=(theString).split('\n');

    var transformations=[];
    var tcOffsets=[];//new Float32Array(str.get().length*2);
    var tcSize=[];//new Float32Array(str.get().length*2);
    var charCounter=0;
    createTexture=false;
    var m=mat4.create();


    for(var s=0;s<strings.length;s++)
    {
        var txt=strings[s];
        var numChars=txt.length;

        var pos=0;
        var offX=0;
        var width=0;

        for(var i=0;i<numChars;i++)
        {
            var chStr=txt.substring(i,i+1);
            var char=font.chars[String(chStr)];
            if(char) width+=(char.texCoordWidth/char.texCoordHeight);
        }

        height=0;

        if(align.get()=='left') offX=0;
        else if(align.get()=='right') offX=width;
        else if(align.get()=='center') offX=width/2;

        height=(s+1)*lineHeight.get();

        for(var i=0;i<numChars;i++)
        {
            var chStr=txt.substring(i,i+1);
            var char=font.chars[String(chStr)];


            if(!char)
            {
                createTexture=true;
                return;
            }
            else
            {
                tcOffsets.push(char.texCoordX,1-char.texCoordY-char.texCoordHeight);
                tcSize.push(char.texCoordWidth,char.texCoordHeight);

                mat4.identity(m);
                mat4.translate(m,m,[pos-offX,0-s*lineHeight.get(),0]);

                pos+=(char.texCoordWidth/char.texCoordHeight)+letterSpace.get();
                transformations.push(Array.prototype.slice.call(m));

                charCounter++;
            }
        }
    }

    var transMats = [].concat.apply([], transformations);

    disabled=false;
    if(transMats.length==0)disabled=true;

    mesh.numInstances=transMats.length/16;

    if(mesh.numInstances==0)
    {
        disabled=true;
        return;
    }

    mesh.setAttribute('instMat',new Float32Array(transMats),16,{"instanced":true});
    mesh.setAttribute('attrTexOffsets',new Float32Array(tcOffsets),2,{"instanced":true});
    mesh.setAttribute('attrTexSize',new Float32Array(tcSize),2,{"instanced":true});

    createMesh=false;

    if(createTexture) generateTexture();
}

function printChars(fontSize,simulate)
{
    var font=getFont();
    if(!simulate) font.chars={};

    var ctx=font.ctx;

    ctx.font = fontSize+'px '+inFont.get();
    ctx.textAlign = "left";

    var posy=0,i=0;
    var posx=0;
    var lineHeight=fontSize*1.4;
    var result=
        {
            "fits":true
        };

    for(var i=0;i<font.characters.length;i++)
    {
        var chStr=String(font.characters.substring(i,i+1));
        var chWidth=(ctx.measureText(chStr).width);

        if(posx+chWidth>=textureSize)
        {
            posy+=lineHeight+2;
            posx=0;
        }

        if(!simulate)
        {
            font.chars[chStr]=
                {
                    str:chStr,
                    texCoordX:posx/textureSize,
                    texCoordY:posy/textureSize,
                    texCoordWidth:chWidth/textureSize,
                    texCoordHeight:lineHeight/textureSize,
                };

            ctx.fillText(chStr, posx, posy+fontSize);
        }

        posx+=chWidth+12;
    }

    if(posy>textureSize-lineHeight)
    {
        result.fits=false;
    }

    result.spaceLeft=textureSize-posy;

    return result;
}

function generateTexture()
{
    var font=getFont();
    var string=String(str.get());
    if(string==null || string==undefined)string='';
    for(var i=0;i<string.length;i++)
    {
        var ch=string.substring(i,i+1);
        if(font.characters.indexOf(ch)==-1)
        {
            font.characters+=ch;
            createTexture=true;
        }
    }

    var ctx=font.ctx;
    font.canvas.width=font.canvas.height=textureSize;

    if(!font.texture)
        font.texture=CGL.Texture.createFromImage(cgl,font.canvas,
            {
                filter:CGL.Texture.FILTER_MIPMAP
            });

    font.texture.setSize(textureSize,textureSize);

    ctx.fillStyle = 'transparent';
    ctx.clearRect(0,0,textureSize,textureSize);
    ctx.fillStyle = 'rgba(255,255,255,255)';

    var fontSize=font.fontSize+40;

    var simu=printChars(fontSize,true);
    while(!simu.fits)
    {
        fontSize-=5;
        simu=printChars(fontSize,true);
    }

    printChars(fontSize,false);

    ctx.restore();

    font.texture.initTexture(font.canvas,CGL.Texture.FILTER_MIPMAP);
    font.texture.unpackAlpha=true;
    textureOut.set(font.texture);

    font.lastChange=CABLES.now();

    createMesh=true;
    createTexture=false;
}


};

Ops.Gl.Meshes.TextMesh.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Boolean.ToggleBool
// 
// **************************************************************

Ops.Boolean.ToggleBool = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='ToggleBool';

var trigger=op.inFunctionButton("trigger");
var reset=op.inFunctionButton("reset");
var outBool=op.addOutPort(new Port(op,"result",OP_PORT_TYPE_VALUE));
var theBool=false;
outBool.set(theBool);
outBool.ignoreValueSerialize=true;

trigger.onTriggered=function()
{
    theBool=!theBool;
    outBool.set(theBool);
};

reset.onTriggered=function()
{
    theBool=false;
    outBool.set(theBool);
};



};

Ops.Boolean.ToggleBool.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Json.AjaxRequest
// 
// **************************************************************

Ops.Json.AjaxRequest = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="AjaxRequest";

var filename=op.addInPort(new Port(op,"file",OP_PORT_TYPE_VALUE,{ display:'file',type:'string',filter:'json' } ));
var outData=op.addOutPort(new Port(op,"data",OP_PORT_TYPE_OBJECT));
var isLoading=op.outValue("Is Loading",false);

var jsonp=op.inValueBool("JsonP",false);

outData.ignoreValueSerialize=true;

filename.onChange=delayedReload;
jsonp.onChange=delayedReload;

var loadingId=0;
var reloadTimeout=0;

function delayedReload()
{
    clearTimeout(reloadTimeout);
    reloadTimeout=setTimeout(reload,100);
}

function reload()
{
    if(!filename.get())return;
    
    op.patch.loading.finished(loadingId);
    
    loadingId=op.patch.loading.start('jsonFile',''+filename.get());
    isLoading.set(true);

    var f=CABLES.ajax;
    if(jsonp.get())f=CABLES.jsonp;

    f(
        op.patch.getFilePath(filename.get()),
        function(err,_data,xhr)
        {
            try
            {
                var data=_data;
                if(typeof data === 'string') data=JSON.parse(_data);

                if(outData.get())outData.set(null);
                outData.set(data);
                op.uiAttr({'error':''});
                op.patch.loading.finished(loadingId);
                isLoading.set(false);
            }
            catch(e)
            {
                console.log('exc... ',filename.get(),jsonp.get());
                op.uiAttr({'error':'error loading json'});
                op.patch.loading.finished(loadingId);
                isLoading.set(false);
            }
        });
    
}



};

Ops.Json.AjaxRequest.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Noise.ValueNoise
// 
// **************************************************************

Ops.Gl.TextureEffects.Noise.ValueNoise = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["valuenoise3d_frag"]="UNI float z;\nUNI float x;\nUNI float y;\nUNI float scale;\nUNI float amount;\nIN vec2 texCoord;\nUNI sampler2D tex;\n\n{{BLENDCODE}}\n\n\n//\n//\tValue Noise 3D\n//\tReturn value range of 0.0->1.0\n//\thttp://briansharpe.files.wordpress.com/2011/11/valuesample1.jpg\n//\n\nfloat Interpolation_C2( float x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }   //  6x^5-15x^4+10x^3\t( Quintic Curve.  As used by Perlin in Improved Noise.  http://mrl.nyu.edu/~perlin/paper445.pdf )\nvec2 Interpolation_C2( vec2 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec3 Interpolation_C2( vec3 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec4 Interpolation_C2( vec4 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec4 Interpolation_C2_InterpAndDeriv( vec2 x ) { return x.xyxy * x.xyxy * ( x.xyxy * ( x.xyxy * ( x.xyxy * vec2( 6.0, 0.0 ).xxyy + vec2( -15.0, 30.0 ).xxyy ) + vec2( 10.0, -60.0 ).xxyy ) + vec2( 0.0, 30.0 ).xxyy ); }\nvec3 Interpolation_C2_Deriv( vec3 x ) { return x * x * (x * (x * 30.0 - 60.0) + 30.0); }\n\n\nvoid FAST32_hash_3D( vec3 gridcell, out vec4 lowz_hash, out vec4 highz_hash )\t//\tgenerates a random number for each of the 8 cell corners\n{\n    //    gridcell is assumed to be an integer coordinate\n\n    //\tTODO: \tthese constants need tweaked to find the best possible noise.\n    //\t\t\tprobably requires some kind of brute force computational searching or something....\n    const vec2 OFFSET = vec2( 50.0, 161.0 );\n    const float DOMAIN = 69.0;\n    const float SOMELARGEFLOAT = 635.298681;\n    const float ZINC = 48.500388;\n\n    //\ttruncate the domain\n    gridcell.xyz = gridcell.xyz - floor(gridcell.xyz * ( 1.0 / DOMAIN )) * DOMAIN;\n    vec3 gridcell_inc1 = step( gridcell, vec3( DOMAIN - 1.5 ) ) * ( gridcell + 1.0 );\n\n    //\tcalculate the noise\n    vec4 P = vec4( gridcell.xy, gridcell_inc1.xy ) + OFFSET.xyxy;\n    P *= P;\n    P = P.xzxz * P.yyww;\n    highz_hash.xy = vec2( 1.0 / ( SOMELARGEFLOAT + vec2( gridcell.z, gridcell_inc1.z ) * ZINC ) );\n    lowz_hash = fract( P * highz_hash.xxxx );\n    highz_hash = fract( P * highz_hash.yyyy );\n}\n\n\nfloat Value3D( vec3 P )\n{\n    //\testablish our grid cell and unit position\n    vec3 Pi = floor(P);\n    vec3 Pf = P - Pi;\n\n    //\tcalculate the hash.\n    //\t( various hashing methods listed in order of speed )\n    vec4 hash_lowz, hash_highz;\n    FAST32_hash_3D( Pi, hash_lowz, hash_highz );\n    //FAST32_2_hash_3D( Pi, hash_lowz, hash_highz );\n    //BBS_hash_3D( Pi, hash_lowz, hash_highz );\n    //SGPP_hash_3D( Pi, hash_lowz, hash_highz );\n\n    //\tblend the results and return\n    vec3 blend = Interpolation_C2( Pf );\n    vec4 res0 = mix( hash_lowz, hash_highz, blend.z );\n    vec4 blend2 = vec4( blend.xy, vec2( 1.0 - blend.xy ) );\n    return dot( res0, blend2.zxzx * blend2.wwyy );\n}\n\nvoid main()\n{\n    vec4 base=texture2D(tex,texCoord);\n\n   vec2 p=vec2(texCoord.x-0.5,texCoord.y-0.5);\n   p=p*scale;\n\n   p=vec2(p.x+0.5-x,p.y+0.5-y);\n\n   float v=Value3D(vec3(p.x,p.y,z));\n   vec4 col=vec4(v,v,v,1.0);\n   \n   col=vec4( _blend(base.rgb,col.rgb) ,1.0);\n   col=vec4( mix( col.rgb, base.rgb ,1.0-base.a*amount),1.0);\n\n   gl_FragColor = col;\n}\n";
op.name="ValueNoise";

var cgl=op.patch.cgl;

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));

var blendMode=CGL.TextureEffect.AddBlendSelect(op,"Blend Mode","normal");
var amount=op.inValueSlider("Amount",1);



var x=op.inValue("X",0);
var y=op.inValue("Y",0);
var z=op.inValue("Z",0);
var scale=op.inValue("Scale",4);
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var shader=new CGL.Shader(cgl);

var srcFrag=attachments.valuenoise3d_frag.replace('{{BLENDCODE}}',CGL.TextureEffect.getBlendCode());

shader.setSource(shader.getDefaultVertexShader(),srcFrag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

var uniZ=new CGL.Uniform(shader,'f','z',z);
var uniX=new CGL.Uniform(shader,'f','x',x);
var uniY=new CGL.Uniform(shader,'f','y',y);
var uniScale=new CGL.Uniform(shader,'f','scale',scale);

var amountUniform=new CGL.Uniform(shader,'f','amount',amount);

blendMode.onChange=function()
{
    CGL.TextureEffect.onChangeBlendSelect(shader,blendMode.get());
};


render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Noise.ValueNoise.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Random
// 
// **************************************************************

Ops.Math.Random = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.inFunctionButton('exe');
var minusPlusOne=op.addInPort(new Port(op,"0 to x / -x to x ",OP_PORT_TYPE_VALUE,{display:'bool'}));
var max=op.inValue("max",1);
var seed=op.inValue("random seed",0);
var result=op.outValue("result");

exe.onTriggered=calcRandom;
seed.onChange=calcRandom;
max.onChange=calcRandom;

calcRandom();

var oldSeed=0;
function calcRandom()
{
    oldSeed=Math.randomSeed;
    Math.randomSeed=seed.get();
    if(minusPlusOne.get()) result.set((Math.seededRandom()*max.get())*2-max.get() );
        else result.set(Math.seededRandom()*max.get());
        
    Math.randomSeed=oldSeed;
}



};

Ops.Math.Random.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ShaderEffects.PerlinAreaDeform2
// 
// **************************************************************

Ops.Gl.ShaderEffects.PerlinAreaDeform2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["perlindeform_vert"]="\nUNI bool MOD_smooth;\nUNI float MOD_x,MOD_y,MOD_z;\nUNI float MOD_strength;\nUNI float MOD_size;\nUNI float MOD_scale;\nUNI float MOD_scrollx;\nUNI float MOD_scrolly;\nUNI float MOD_scrollz;\n\n    \n\nfloat Interpolation_C2( float x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }   //  6x^5-15x^4+10x^3\t( Quintic Curve.  As used by Perlin in Improved Noise.  http://mrl.nyu.edu/~perlin/paper445.pdf )\nvec2 Interpolation_C2( vec2 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec3 Interpolation_C2( vec3 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec4 Interpolation_C2( vec4 x ) { return x * x * x * (x * (x * 6.0 - 15.0) + 10.0); }\nvec4 Interpolation_C2_InterpAndDeriv( vec2 x ) { return x.xyxy * x.xyxy * ( x.xyxy * ( x.xyxy * ( x.xyxy * vec2( 6.0, 0.0 ).xxyy + vec2( -15.0, 30.0 ).xxyy ) + vec2( 10.0, -60.0 ).xxyy ) + vec2( 0.0, 30.0 ).xxyy ); }\nvec3 Interpolation_C2_Deriv( vec3 x ) { return x * x * (x * (x * 30.0 - 60.0) + 30.0); }\n\n\nvoid FAST32_hash_3D( \tvec3 gridcell,\n                        out vec4 lowz_hash_0,\n                        out vec4 lowz_hash_1,\n                        out vec4 lowz_hash_2,\n                        out vec4 highz_hash_0,\n                        out vec4 highz_hash_1,\n                        out vec4 highz_hash_2\t)\t\t//\tgenerates 3 random numbers for each of the 8 cell corners\n{\n    //    gridcell is assumed to be an integer coordinate\n\n    //\tTODO: \tthese constants need tweaked to find the best possible noise.\n    //\t\t\tprobably requires some kind of brute force computational searching or something....\n    const vec2 OFFSET = vec2( 50.0, 161.0 );\n    const float DOMAIN = 69.0;\n    const vec3 SOMELARGEFLOATS = vec3( 635.298681, 682.357502, 668.926525 );\n    const vec3 ZINC = vec3( 48.500388, 65.294118, 63.934599 );\n\n    //\ttruncate the domain\n    gridcell.xyz = gridcell.xyz - floor(gridcell.xyz * ( 1.0 / DOMAIN )) * DOMAIN;\n    vec3 gridcell_inc1 = step( gridcell, vec3( DOMAIN - 1.5 ) ) * ( gridcell + 1.0 );\n\n    //\tcalculate the noise\n    vec4 P = vec4( gridcell.xy, gridcell_inc1.xy ) + OFFSET.xyxy;\n    P *= P;\n    P = P.xzxz * P.yyww;\n    vec3 lowz_mod = vec3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell.zzz * ZINC.xyz ) );\n    vec3 highz_mod = vec3( 1.0 / ( SOMELARGEFLOATS.xyz + gridcell_inc1.zzz * ZINC.xyz ) );\n    lowz_hash_0 = fract( P * lowz_mod.xxxx );\n    highz_hash_0 = fract( P * highz_mod.xxxx );\n    lowz_hash_1 = fract( P * lowz_mod.yyyy );\n    highz_hash_1 = fract( P * highz_mod.yyyy );\n    lowz_hash_2 = fract( P * lowz_mod.zzzz );\n    highz_hash_2 = fract( P * highz_mod.zzzz );\n}\n\n//\n//\tPerlin Noise 3D  ( gradient noise )\n//\tReturn value range of -1.0->1.0\n//\thttp://briansharpe.files.wordpress.com/2011/11/perlinsample.jpg\n//\nfloat Perlin3D( vec3 P )\n{\n    //\testablish our grid cell and unit position\n    vec3 Pi = floor(P);\n    vec3 Pf = P - Pi;\n    vec3 Pf_min1 = Pf - 1.0;\n\n#if 1\n    //\n    //\tclassic noise.\n    //\trequires 3 random values per point.  with an efficent hash function will run faster than improved noise\n    //\n\n    //\tcalculate the hash.\n    //\t( various hashing methods listed in order of speed )\n    vec4 hashx0, hashy0, hashz0, hashx1, hashy1, hashz1;\n    FAST32_hash_3D( Pi, hashx0, hashy0, hashz0, hashx1, hashy1, hashz1 );\n    //SGPP_hash_3D( Pi, hashx0, hashy0, hashz0, hashx1, hashy1, hashz1 );\n\n    //\tcalculate the gradients\n    vec4 grad_x0 = hashx0 - 0.49999;\n    vec4 grad_y0 = hashy0 - 0.49999;\n    vec4 grad_z0 = hashz0 - 0.49999;\n    vec4 grad_x1 = hashx1 - 0.49999;\n    vec4 grad_y1 = hashy1 - 0.49999;\n    vec4 grad_z1 = hashz1 - 0.49999;\n    vec4 grad_results_0 = inversesqrt( grad_x0 * grad_x0 + grad_y0 * grad_y0 + grad_z0 * grad_z0 ) * ( vec2( Pf.x, Pf_min1.x ).xyxy * grad_x0 + vec2( Pf.y, Pf_min1.y ).xxyy * grad_y0 + Pf.zzzz * grad_z0 );\n    vec4 grad_results_1 = inversesqrt( grad_x1 * grad_x1 + grad_y1 * grad_y1 + grad_z1 * grad_z1 ) * ( vec2( Pf.x, Pf_min1.x ).xyxy * grad_x1 + vec2( Pf.y, Pf_min1.y ).xxyy * grad_y1 + Pf_min1.zzzz * grad_z1 );\n\n#if 1\n    //\tClassic Perlin Interpolation\n    vec3 blend = Interpolation_C2( Pf );\n    vec4 res0 = mix( grad_results_0, grad_results_1, blend.z );\n    vec4 blend2 = vec4( blend.xy, vec2( 1.0 - blend.xy ) );\n    float final = dot( res0, blend2.zxzx * blend2.wwyy );\n    final *= 1.1547005383792515290182975610039;\t\t//\t(optionally) scale things to a strict -1.0->1.0 range    *= 1.0/sqrt(0.75)\n    return final;\n#else\n    //\tClassic Perlin Surflet\n    //\thttp://briansharpe.wordpress.com/2012/03/09/modifications-to-classic-perlin-noise/\n    Pf *= Pf;\n    Pf_min1 *= Pf_min1;\n    vec4 vecs_len_sq = vec4( Pf.x, Pf_min1.x, Pf.x, Pf_min1.x ) + vec4( Pf.yy, Pf_min1.yy );\n    float final = dot( Falloff_Xsq_C2( min( vec4( 1.0 ), vecs_len_sq + Pf.zzzz ) ), grad_results_0 ) + dot( Falloff_Xsq_C2( min( vec4( 1.0 ), vecs_len_sq + Pf_min1.zzzz ) ), grad_results_1 );\n    final *= 2.3703703703703703703703703703704;\t\t//\t(optionally) scale things to a strict -1.0->1.0 range    *= 1.0/cube(0.75)\n    return final;\n#endif\n\n#else\n    //\n    //\timproved noise.\n    //\trequires 1 random value per point.  Will run faster than classic noise if a slow hashing function is used\n    //\n\n    //\tcalculate the hash.\n    //\t( various hashing methods listed in order of speed )\n    vec4 hash_lowz, hash_highz;\n    FAST32_hash_3D( Pi, hash_lowz, hash_highz );\n    //BBS_hash_3D( Pi, hash_lowz, hash_highz );\n    //SGPP_hash_3D( Pi, hash_lowz, hash_highz );\n\n    //\n    //\t\"improved\" noise using 8 corner gradients.  Faster than the 12 mid-edge point method.\n    //\tKen mentions using diagonals like this can cause \"clumping\", but we'll live with that.\n    //\t[1,1,1]  [-1,1,1]  [1,-1,1]  [-1,-1,1]\n    //\t[1,1,-1] [-1,1,-1] [1,-1,-1] [-1,-1,-1]\n    //\n    hash_lowz -= 0.5;\n    vec4 grad_results_0_0 = vec2( Pf.x, Pf_min1.x ).xyxy * sign( hash_lowz );\n    hash_lowz = abs( hash_lowz ) - 0.25;\n    vec4 grad_results_0_1 = vec2( Pf.y, Pf_min1.y ).xxyy * sign( hash_lowz );\n    vec4 grad_results_0_2 = Pf.zzzz * sign( abs( hash_lowz ) - 0.125 );\n    vec4 grad_results_0 = grad_results_0_0 + grad_results_0_1 + grad_results_0_2;\n\n    hash_highz -= 0.5;\n    vec4 grad_results_1_0 = vec2( Pf.x, Pf_min1.x ).xyxy * sign( hash_highz );\n    hash_highz = abs( hash_highz ) - 0.25;\n    vec4 grad_results_1_1 = vec2( Pf.y, Pf_min1.y ).xxyy * sign( hash_highz );\n    vec4 grad_results_1_2 = Pf_min1.zzzz * sign( abs( hash_highz ) - 0.125 );\n    vec4 grad_results_1 = grad_results_1_0 + grad_results_1_1 + grad_results_1_2;\n\n    //\tblend the gradients and return\n    vec3 blend = Interpolation_C2( Pf );\n    vec4 res0 = mix( grad_results_0, grad_results_1, blend.z );\n    vec4 blend2 = vec4( blend.xy, vec2( 1.0 - blend.xy ) );\n    return dot( res0, blend2.zxzx * blend2.wwyy ) * (2.0 / 3.0);\t//\t(optionally) mult by (2.0/3.0) to scale to a strict -1.0->1.0 range\n#endif\n}\n\nvec3 MOD_deform(vec3 pos)\n{\n    // vec3 MOD_pos=vec3();\n    vec3 modelPos=pos;\n    vec3 forcePos=vec3(MOD_x,MOD_y,MOD_z);\n    \n\n    vec3 vecToOrigin=modelPos-forcePos;\n    float dist=abs(length(vecToOrigin));\n    float distAlpha = (MOD_size - dist) / MOD_size;\n\n    if(MOD_smooth) distAlpha=smoothstep(0.0,MOD_size,distAlpha);\n\n    \n    vec3 ppos=vec3(pos*MOD_scale);\n    ppos.x+=MOD_scrollx;\n    ppos.y+=MOD_scrolly;\n    ppos.z+=MOD_scrollz;\n    \n    float p=Perlin3D(ppos)*MOD_strength*distAlpha;\n    \n    vec3 pnorm=normalize(pos.xyz);\n    \n    #ifdef MOD_METH_ADD_XYZ\n        pos.x+=p*pnorm.x;\n        pos.y+=p*pnorm.y;\n        pos.z+=p*pnorm.z;\n    #endif\n\n    #ifdef MOD_METH_ADD_Z\n        pos.z+=p;\n    #endif\n\n    return pos;\n}\n\n\nvec3 MOD_calcNormal(vec3 pos)\n{\n    float theta = .001; \n    vec3 vecTangent = normalize(cross(pos, vec3(1.0, 0.0, 0.0)) + cross(pos, vec3(0.0, 1.0, 0.0)));\n    vec3 vecBitangent = normalize(cross(vecTangent, pos));\n    vec3 ptTangentSample = MOD_deform(normalize(pos + theta * normalize(vecTangent)));\n    vec3 ptBitangentSample = MOD_deform(normalize(pos + theta * normalize(vecBitangent)));\n\n    return normalize(cross(ptTangentSample - pos, ptBitangentSample - pos));\n}\n";
attachments["perlindeform_body_vert"]="\n#ifndef MOD_WORLDSPACE\n   pos.xyz=MOD_deform(pos.xyz);\n   norm=MOD_calcNormal(pos.xyz);\n#endif\n\n#ifdef MOD_WORLDSPACE\n   pos.xyz=MOD_deform( (mMatrix*pos).xyz );\n   norm=MOD_calcNormal( (mMatrix*pos).xyz);\n#endif\n";

var cgl=op.patch.cgl;

op.render=op.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
op.trigger=op.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));

var inScale=op.inValue("Scale",1);
var inSize=op.inValue("Size",1);
var inStrength=op.inValue("Strength",1);
var inSmooth=op.inValueBool("Smooth",true);

var output=op.inValueSelect("Output",['Add XYZ','Add Z'],'Add XYZ');

var x=op.inValue("x");
var y=op.inValue("y");
var z=op.inValue("z");

var scrollx=op.inValue("Scroll X");
var scrolly=op.inValue("Scroll Y");
var scrollz=op.inValue("Scroll Z");

var shader=null;

var inWorldSpace=op.inValueBool("WorldSpace");

var moduleVert=null;

function removeModule()
{
    if(shader && moduleVert) shader.removeModule(moduleVert);
    shader=null;
}

output.onChange=updateOutput;
op.render.onLinkChanged=removeModule;


inWorldSpace.onChange=updateWorldspace;

function updateOutput()
{
    if(!shader)return;
    if(output.get()=='Add XYZ') shader.define(moduleVert.prefix+"METH_ADD_XYZ");
        else shader.removeDefine(moduleVert.prefix+"METH_ADD_XYZ");

    if(output.get()=='Add Z') shader.define(moduleVert.prefix+"METH_ADD_Z");
        else shader.removeDefine(moduleVert.prefix+"METH_ADD_Z");
    
}

function updateWorldspace()
{
    if(!shader)return;
    if(inWorldSpace.get()) shader.define(moduleVert.prefix+"WORLDSPACE");
        else shader.removeDefine(moduleVert.prefix+"WORLDSPACE");
}


op.render.onTriggered=function()
{
    if(!cgl.getShader())
    {
        op.trigger.trigger();
        return;
    }
    
    if(CABLES.UI)
    {
        cgl.pushModelMatrix();
        mat4.identity(cgl.mvMatrix);

        if(CABLES.UI.renderHelper)
        {
            cgl.pushModelMatrix();
            mat4.translate(cgl.mvMatrix,cgl.mvMatrix,[x.get(),y.get(),z.get()]);
            CABLES.GL_MARKER.drawSphere(op,inSize.get());
            cgl.popModelMatrix();
        }
    
    
        if(gui.patch().isCurrentOp(op)) 
            gui.setTransformGizmo(
                {
                    posX:x,
                    posY:y,
                    posZ:z
                });


        cgl.popModelMatrix();
    }



    if(cgl.getShader()!=shader)
    {
        if(shader) removeModule();
        shader=cgl.getShader();

        moduleVert=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_VERTEX_POSITION',
                srcHeadVert:attachments.perlindeform_vert,
                srcBodyVert:attachments.perlindeform_body_vert

            });
        
        inSize.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'size',inSize);
        inStrength.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'strength',inStrength);
        inSmooth.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'smooth',inSmooth);
        inScale.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'scale',inScale);

        scrollx.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'scrollx',scrollx);
        scrolly.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'scrolly',scrolly);
        scrollz.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'scrollz',scrollz);

        x.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'x',x);
        y.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'y',y);
        z.uniform=new CGL.Uniform(shader,'f',moduleVert.prefix+'z',z);
        
        updateOutput();
        updateWorldspace();
    }
    
    
    if(!shader)return;

    op.trigger.trigger();
};















};

Ops.Gl.ShaderEffects.PerlinAreaDeform2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.ShaderEffects.VertexNumberLimit
// 
// **************************************************************

Ops.Gl.ShaderEffects.VertexNumberLimit = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var cgl=op.patch.cgl;
var shader=null;
var uniTime;

var render=this.addInPort(new Port(this,"render",OP_PORT_TYPE_FUNCTION));
var limitMax=op.inValue("Max",1000);

var trigger=this.addOutPort(new Port(this,"trigger",OP_PORT_TYPE_FUNCTION));

var srcHeadVert=''
    .endl()+'UNI float {{mod}}_max;'
    .endl()+'OUT float vertNumberLimitDiscarded;'
    // .endl()+'IN float attrVertIndex;'
    .endl();

var srcBodyVert=''
    .endl()+'if(attrVertIndex > {{mod}}_max) vertNumberLimitDiscarded=1.0; else vertNumberLimitDiscarded=0.0;'
    .endl();



var srcHeadFrag=''
    .endl()+'IN float vertNumberLimitDiscarded;'
    .endl();

var srcBodyFrag=''
    .endl()+'if(vertNumberLimitDiscarded>0.0)discard;'
    .endl();


var module=null;
var moduleFrag=null;


var startTime=Date.now()/1000.0;

function removeModule()
{
    if(shader && module)
    {
        shader.removeModule(module);
        shader.removeModule(moduleFrag);

        shader=null;
    }
}

render.onLinkChanged=removeModule;
render.onTriggered=function()
{
    if(cgl.getShader()!=shader)
    {
        if(shader) removeModule();
        shader=cgl.getShader();
        module=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_VERTEX_POSITION',
                srcHeadVert:srcHeadVert,
                srcBodyVert:srcBodyVert
            });

        moduleFrag=shader.addModule(
            {
                title:op.objName,
                name:'MODULE_COLOR',
                srcHeadFrag:srcHeadFrag,
                srcBodyFrag:srcBodyFrag
            });

        limitMax.uniform=new CGL.Uniform(shader,'f',module.prefix+'_max',limitMax);
    }

    trigger.trigger();
};


};

Ops.Gl.ShaderEffects.VertexNumberLimit.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Ease
// 
// **************************************************************

Ops.Math.Ease = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var inVal=op.inValue("Value");

var inMin=op.inValue("Min",0);
var inMax=op.inValue("Max",1);

var result=op.outValue("Result");

var anim=new CABLES.TL.Anim();

anim.createPort(op,"Easing",updateAnimEasing);

anim.setValue(0,0);
anim.setValue(1,1);

inMin.onChange=inMax.onChange=updateMinMax;

function updateMinMax()
{
    anim.keys[0].time=anim.keys[0].value=Math.min(inMin.get(),inMax.get());
    anim.keys[1].time=anim.keys[1].value=Math.max(inMin.get(),inMax.get());
}

function updateAnimEasing()
{
    anim.keys[0].setEasing(anim.defaultEasing);    
}


inVal.onChange=function()
{
    var v=inVal.get();
    var r=anim.getValue(v);
    result.set(r);

};

};

Ops.Math.Ease.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.RandomizeTriangles
// 
// **************************************************************

Ops.Gl.RandomizeTriangles = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var inGeom=op.inObject("Geometry");
var outGeom=op.outObject("Result");
const inSeed=op.inValue("Seed",0);

inGeom.ignoreValueSerialize=true;
outGeom.ignoreValueSerialize=true;

function shuffleArray(array)
{
    Math.randomSeed=inSeed.get();
    for (var i = array.length - 1; i > 0; i--) {
        var j = Math.floor(Math.seededRandom() * (i + 1));
        var temp = array[i];
        array[i] = array[j];
        array[j] = temp;
        
    }
    return array;
}


inGeom.onChange=function()
{
    var geom=inGeom.get();
    if(!geom)return;
    if(geom.verticesIndices && geom.verticesIndices.length>0)
    {
        console.log("cannot randomize indexed geom ");
        return;
    }

    var newGeom=geom.copy();
    var order=[];
    var i=0;
    order.length=geom.vertices.length/9;
    for(i=0;i<order.length;i++)order[i]=i;
    order=shuffleArray(order);

    var verts=[];
    verts.length=geom.vertices.length;

    for(i=0;i<order.length;i++)
    {
        var ind=order[i];
        for(var j=0;j<9;j++)
            verts[i*9+j]=geom.vertices[ind*9+j];
    }

    newGeom.setVertices(verts);

    outGeom.set(null);
    outGeom.set(newGeom);
};

};

Ops.Gl.RandomizeTriangles.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Desaturate
// 
// **************************************************************

Ops.Gl.TextureEffects.Desaturate = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["desaturate_frag"]="\n#ifdef HAS_TEXTURES\n  IN vec2 texCoord;\n  UNI sampler2D tex;\n#endif\nuniform float amount;\n\nvec3 desaturate(vec3 color, float amount)\n{\n   vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), color));\n   return vec3(mix(color, gray, amount));\n}\n\nvoid main()\n{\n   vec4 col=vec4(1.0,0.0,0.0,1.0);\n   #ifdef HAS_TEXTURES\n       col=texture2D(tex,texCoord);\n       col.rgb=desaturate(col.rgb,amount);\n   #endif\n   gl_FragColor = col;\n}";
op.name='Desaturate';

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var amount=op.inValueSlider("amount",1);

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);

shader.setSource(shader.getDefaultVertexShader(),attachments.desaturate_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var amountUniform=new CGL.Uniform(shader,'f','amount',amount);

render.onTriggered=function()
{
    if(!cgl.currentTextureEffect)return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Desaturate.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Geometry.SortGeometryAxis
// 
// **************************************************************

Ops.Gl.Geometry.SortGeometryAxis = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var SORT_RANDOM="Random";
var SORT_X="X Axis";
var SORT_Y="Y Axis";
var SORT_Z="Z Axis";
var SORT_NONE="None";

var geometry=op.addInPort(new Port(op,"Geometry",OP_PORT_TYPE_OBJECT));
var sorting=op.inValueSelect("Sort",[SORT_RANDOM,SORT_X,SORT_Y,SORT_Z,SORT_NONE],SORT_X);
var reverse=op.inValueBool("Reverse",false);

var outGeom=op.outObject("Result");

reverse.onChange=update;
geometry.onChange=update;
sorting.onChange=update;

function shuffleArray(a)
{
    var arr=[];
    for (var i=0;i<a.length;i++) {
        j = Math.floor(Math.random() * i);
        x = a[i - 1];
        arr[i - 1] = arr[j];
        arr[j] = x;
    }
    return arr;
}

function update()
{
    if(geometry.get())
    {
        var geom=geometry.get();
        var faces=[];
        faces.length=geom.verticesIndices.length/3;

        for(var i=0;i<geom.verticesIndices.length;i+=3)
        {
            var face=[0,0,0];
            face[0]=geom.verticesIndices[i+0];
            face[1]=geom.verticesIndices[i+1];
            face[2]=geom.verticesIndices[i+2];
            faces[i/3]=face;
        }

        if(sorting.get()==SORT_RANDOM)
        {
            faces=shuffleArray(faces);    
        }
        else
        if(sorting.get()==SORT_Y)
        {
            faces.sort(function(a,b)
            {
                var avgA=0;
                avgA+=geom.vertices[a[0]*3+1];
                avgA+=geom.vertices[a[1]*3+1];
                avgA+=geom.vertices[a[2]*3+1];
                avgA/=3;

                var avgB=0;
                avgB+=geom.vertices[b[0]*3+1];
                avgB+=geom.vertices[b[1]*3+1];
                avgB+=geom.vertices[b[2]*3+1];
                avgB/=3;

                return avgA-avgB;
            });
        }
        else
        if(sorting.get()==SORT_X )
        {
            faces.sort(function(a,b)
            {
                var avgA=0;
                avgA+=geom.vertices[a[0]*3+0];
                avgA+=geom.vertices[a[1]*3+0];
                avgA+=geom.vertices[a[2]*3+0];
                avgA/=3;

                var avgB=0;
                avgB+=geom.vertices[b[0]*3+0];
                avgB+=geom.vertices[b[1]*3+0];
                avgB+=geom.vertices[b[2]*3+0];
                avgB/=3;

                return avgA-avgB;
            });
        }
        else
        if(sorting.get()==SORT_Z)
        {
            faces.sort(function(a,b)
            {
                var avgA=0;
                avgA+=geom.vertices[a[0]*3+2];
                avgA+=geom.vertices[a[1]*3+2];
                avgA+=geom.vertices[a[2]*3+2];
                avgA/=3;

                var avgB=0;
                avgB+=geom.vertices[b[0]*3+2];
                avgB+=geom.vertices[b[1]*3+2];
                avgB+=geom.vertices[b[2]*3+2];
                avgB/=3;

                return avgA-avgB;
            });
        }
        else
        {
            if(sorting.get()!=SORT_NONE) console.error("No sorting found",sorting.get());
        }

        var newGeom=new CGL.Geometry();
        var newVerts=[];
        var newFaces=[];
        var newNormals=[];
        var newTexCoords=[];

        if(reverse.get())
        {
            faces=faces.reverse(); 
        }

        faces=[].concat.apply([], faces);

        for(var i=0;i<faces.length;i+=3)
        {
            newFaces.push( newVerts.length/3 );
            newVerts.push( geom.vertices[ faces[i+0]*3+0] );
            newVerts.push( geom.vertices[ faces[i+0]*3+1] );
            newVerts.push( geom.vertices[ faces[i+0]*3+2] );
            newNormals.push( geom.vertexNormals[ faces[i+0]*3+0] );
            newNormals.push( geom.vertexNormals[ faces[i+0]*3+1] );
            newNormals.push( geom.vertexNormals[ faces[i+0]*3+2] );
            newTexCoords.push( geom.texCoords[ faces[i+0]*2+0] );
            newTexCoords.push( geom.texCoords[ faces[i+0]*2+1] );

            newFaces.push( newVerts.length/3 );
            newVerts.push( geom.vertices[ faces[i+1]*3+0] );
            newVerts.push( geom.vertices[ faces[i+1]*3+1] );
            newVerts.push( geom.vertices[ faces[i+1]*3+2] );
            newNormals.push( geom.vertexNormals[ faces[i+1]*3+0] );
            newNormals.push( geom.vertexNormals[ faces[i+1]*3+1] );
            newNormals.push( geom.vertexNormals[ faces[i+1]*3+2] );
            newTexCoords.push( geom.texCoords[ faces[i+1]*2+0] );
            newTexCoords.push( geom.texCoords[ faces[i+1]*2+1] );

            newFaces.push( newVerts.length/3 );
            newVerts.push( geom.vertices[ faces[i+2]*3+0] );
            newVerts.push( geom.vertices[ faces[i+2]*3+1] );
            newVerts.push( geom.vertices[ faces[i+2]*3+2] );
            newNormals.push( geom.vertexNormals[ faces[i+2]*3+0] );
            newNormals.push( geom.vertexNormals[ faces[i+2]*3+1] );
            newNormals.push( geom.vertexNormals[ faces[i+2]*3+2] );
            newTexCoords.push( geom.texCoords[ faces[i+2]*2+0] );
            newTexCoords.push( geom.texCoords[ faces[i+2]*2+1] );
        }

        newGeom.vertices=newVerts;
        newGeom.vertexNormals=newNormals;
        newGeom.verticesIndices=newFaces;
        newGeom.texCoords=newTexCoords;

        outGeom.set(newGeom);
    }
}




};

Ops.Gl.Geometry.SortGeometryAxis.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Trigger.Interval
// 
// **************************************************************

Ops.Trigger.Interval = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var interval=op.inValue('interval');
var trigger=op.outFunction('trigger');
var active=op.inValueBool("Active",true);

active.onChange=function()
{
    if(!active.get())
    {
        clearTimeout(timeOutId);
        timeOutId=-1;
    }
    else exec();
};

interval.set(1000);
var timeOutId=-1;

function exec()
{
    if(!active.get())return;
    if(timeOutId!=-1)return;

    timeOutId=setTimeout(function()
    {
        timeOutId=-1;
        trigger.trigger();
        exec();
    },
    interval.get() );
}

interval.onValueChanged=exec;

exec();

};

Ops.Trigger.Interval.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Anim.RandomAnim
// 
// **************************************************************

Ops.Anim.RandomAnim = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var exe=op.inFunction("exe");
var min=op.inValue("min",0);
var max=op.inValue("max",1);

var pause=op.inValue("pause between",0);
var seed=op.inValue("random seed",0);

var duration=op.inValue("duration",0.5);

var result=op.outValue("result");

var anim=new CABLES.TL.Anim();
anim.createPort(op,"easing",reinit);

reinit();

min.onChange=reinit;
max.onChange=reinit;
pause.onChange=reinit;
seed.onChange=reinit;
duration.onChange=reinit;

var counter=0;

function getRandom()
{
    var minVal = parseFloat( min.get() );
    var maxVal = parseFloat( max.get() );
    return Math.seededRandom() * ( maxVal - minVal ) + minVal;
}

function reinit()
{
    Math.randomSeed=seed.get()+counter*100;
    init(getRandom());
}

function init(v)
{
    anim.clear();
    
    anim.setValue(op.patch.freeTimer.get(), v);
    if(pause.get()!=0.0)anim.setValue(op.patch.freeTimer.get()+pause.get(), v);
    
    anim.setValue(parseFloat(duration.get())+op.patch.freeTimer.get()+pause.get(), getRandom());
}


exe.onTriggered=function()
{
    var t=op.patch.freeTimer.get();
    var v=anim.getValue(t);
    if(anim.hasEnded(t))
    {
        counter++;
        anim.clear();
        init(v);
    }
    result.set(v);
};



};

Ops.Anim.RandomAnim.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Boolean.Or
// 
// **************************************************************

Ops.Boolean.Or = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};

var bool0=op.addInPort(new Port(op,"bool 1",OP_PORT_TYPE_VALUE,{display:'bool'}));
var bool1=op.addInPort(new Port(op,"bool 2",OP_PORT_TYPE_VALUE,{display:'bool'}));
var bool2=op.addInPort(new Port(op,"bool 3",OP_PORT_TYPE_VALUE,{display:'bool'}));
var bool3=op.addInPort(new Port(op,"bool 4",OP_PORT_TYPE_VALUE,{display:'bool'}));
var bool4=op.addInPort(new Port(op,"bool 5",OP_PORT_TYPE_VALUE,{display:'bool'}));
var bool5=op.addInPort(new Port(op,"bool 6",OP_PORT_TYPE_VALUE,{display:'bool'}));
var bool6=op.addInPort(new Port(op,"bool 7",OP_PORT_TYPE_VALUE,{display:'bool'}));
var bool7=op.addInPort(new Port(op,"bool 8",OP_PORT_TYPE_VALUE,{display:'bool'}));

var result=op.addOutPort(new Port(op,"result",OP_PORT_TYPE_VALUE));

function exec()
{
    result.set( bool0.get() || bool1.get()  || bool2.get() || bool3.get() || bool4.get() || bool5.get() || bool6.get() || bool7.get() );
}

bool0.onValueChange(exec);
bool1.onValueChange(exec);
bool2.onValueChange(exec);
bool3.onValueChange(exec);
bool4.onValueChange(exec);
bool5.onValueChange(exec);
bool6.onValueChange(exec);
bool7.onValueChange(exec);



};

Ops.Boolean.Or.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Value.ValueSwitcherNew
// 
// **************************************************************

Ops.Value.ValueSwitcherNew = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var idx=op.inValueInt("Index");
var valuePorts=[];
var result=op.outValue("Result");

idx.onChange=update;

for(var i=0;i<10;i++)
{
    var p=op.inValue("Value "+i);
    valuePorts.push( p );
    p.onChange=update;
}

function update()
{
    if(idx.get()>=0 && valuePorts[idx.get()])
    {
        result.set( valuePorts[idx.get()].get() );
    }
}

};

Ops.Value.ValueSwitcherNew.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Rectangle2
// 
// **************************************************************

Ops.Gl.TextureEffects.Rectangle2 = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["rectangle_frag"]="IN vec2 texCoord;\nUNI sampler2D tex;\n\nUNI float width;\nUNI float height;\nUNI float x;\nUNI float y;\n\nUNI float r;\nUNI float g;\nUNI float b;\nUNI float a;\n\nUNI float rotate;\nUNI float roundness;\n\n#define DEG2RAD 0.785398163397\n\nmat2 rot(float angle)\n{\n    float s=sin(angle);\n    float c=cos(angle);\n    \n    return mat2(c,-s,s,c);\n}\n\n// polynomial smooth min (k = 0.1);\nfloat smin( float a, float b, float k )\n{\n    float h = clamp( 0.5+0.5*(b-a)/k, 0.0, 1.0 );\n    return mix( b, a, h ) - k*h*(1.0-h);\n}\n\nvoid main()\n{\n    vec4 col=texture2D(tex,texCoord);\n    vec4 newcol;\n    vec2 p=texCoord*2.0-1.0;\n    float d=1.0;\n\n    vec2 pp=vec2(p.x-x,p.y-y);\n\n    pp=pp*rot(rotate*DEG2RAD/45.0);\n    \n    float roundn=roundness*min(width,height);\n\n    vec2 size=max(vec2(width,height)-roundn,0.0);\n    vec2 absPos=abs(pp)-size;\n\n    d=max(absPos.x,absPos.y);\n    d=min(d,length(max(absPos,0.0))-roundn);\n    d=step(0.0,d);\n\n    // d+=absPos.x+1.0;\n    // d=max(d,0.0);\n    // d=max(d,0.0);\n\n\n\n\n    outColor = vec4( (1.0-d)*vec3(r,g,b),1.0);\n}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));

var inWidth=op.inValue("Width",0.25);
var inHeight=op.inValue("Height",0.25);
var inPosX=op.inValue("X",0.5);
var inPosY=op.inValue("Y",0.5);

var inRot=op.inValue("Rotate",0);
var inRoundness=op.inValueSlider("roundness",0);


var r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true'}));
var g=op.addInPort(new Port(op,"g",OP_PORT_TYPE_VALUE,{ display:'range' }));
var b=op.addInPort(new Port(op,"b",OP_PORT_TYPE_VALUE,{ display:'range' }));
var a=op.addInPort(new Port(op,"a",OP_PORT_TYPE_VALUE,{ display:'range' }));

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl,'textureeffect rectangle');
shader.setSource(shader.getDefaultVertexShader(),attachments.rectangle_frag||'');
var textureUniform=new CGL.Uniform(shader,'t','tex',0);

var uniHeight=new CGL.Uniform(shader,'f','height',inHeight);
var unWidth=new CGL.Uniform(shader,'f','width',inWidth);
var uniX=new CGL.Uniform(shader,'f','x',inPosX);
var uniY=new CGL.Uniform(shader,'f','y',inPosY);
var uniRot=new CGL.Uniform(shader,'f','rotate',inRot);
var uniRoundness=new CGL.Uniform(shader,'f','roundness',inRoundness);

r.set(1.0);
g.set(1.0);
b.set(1.0);
a.set(1.0);

var uniformR=new CGL.Uniform(shader,'f','r',r);
var uniformG=new CGL.Uniform(shader,'f','g',g);
var uniformB=new CGL.Uniform(shader,'f','b',b);
var uniformA=new CGL.Uniform(shader,'f','a',a);

render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    cgl.setTexture(0, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};



};

Ops.Gl.TextureEffects.Rectangle2.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Color
// 
// **************************************************************

Ops.Gl.TextureEffects.Color = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["color_frag"]="IN vec2 texCoord;\nUNI sampler2D tex;\nUNI float r;\nUNI float g;\nUNI float b;\nUNI float amount;\n\n{{BLENDCODE}}\n\nvoid main()\n{\n   vec4 col=vec4(r,g,b,1.0);\n   vec4 base=texture2D(tex,texCoord);\n\n   col=vec4( _blend(base.rgb,col.rgb) ,1.0);\n   col=vec4( mix( col.rgb, base.rgb ,1.0-base.a*amount),1.0);\n   \n   gl_FragColor = col;\n}\n";
const render=op.inFunction("render");
const r=op.addInPort(new Port(op,"r",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true'}));
const g=op.addInPort(new Port(op,"g",OP_PORT_TYPE_VALUE,{ display:'range' }));
const b=op.addInPort(new Port(op,"b",OP_PORT_TYPE_VALUE,{ display:'range' }));
const blendMode=CGL.TextureEffect.AddBlendSelect(op,"Blend Mode","normal");
const amount=op.inValueSlider("Amount",1);
const trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));

r.set(1.0);
g.set(1.0);
b.set(1.0);

const TEX_SLOT=0;

const cgl=op.patch.cgl;
const shader=new CGL.Shader(cgl,'textureeffect color');

var srcFrag=attachments.color_frag||'';
srcFrag=srcFrag.replace("{{BLENDCODE}}",CGL.TextureEffect.getBlendCode());

shader.setSource(shader.getDefaultVertexShader(),srcFrag);

var textureUniform=new CGL.Uniform(shader,'t','tex',TEX_SLOT);

var uniformR=new CGL.Uniform(shader,'f','r',r);
var uniformG=new CGL.Uniform(shader,'f','g',g);
var uniformB=new CGL.Uniform(shader,'f','b',b);
var uniformAmount=new CGL.Uniform(shader,'f','amount',amount);

blendMode.onChange=function()
{
    CGL.TextureEffect.onChangeBlendSelect(shader,blendMode.get());
};

render.onTriggered=function()
{
    // if(!CGL.TextureEffect.checkOpInEffect(op)) return;

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    cgl.setTexture(TEX_SLOT, cgl.currentTextureEffect.getCurrentSourceTexture().tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Color.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.Matrix.CircleTransform
// 
// **************************************************************

Ops.Gl.Matrix.CircleTransform = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));
var segments=op.addInPort(new Port(op,"segments"));
var radius=op.addInPort(new Port(op,"radius"));
var percent=op.addInPort(new Port(op,"percent",OP_PORT_TYPE_VALUE,{display:'range'}));
var inRotate=op.inValueBool("Rotate");
var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var index=op.addOutPort(new Port(op,"index"));

var cgl=op.patch.cgl;

segments.set(40);
radius.set(1);
percent.set(1);

var pos=[];
segments.onChange=calcLater;
radius.onChange=calcLater;
percent.onChange=calcLater;
var needsCalc=true;

render.onTriggered=doRender;

function doRender()
{
    if(needsCalc)calc();
    var doRot=inRotate.get();
    for(var i=0;i<pos.length;i++)
    {
        cgl.pushModelMatrix();

        mat4.translate(cgl.mMatrix,cgl.mMatrix, pos[i] );
        if(doRot)mat4.rotateZ(cgl.mMatrix,cgl.mMatrix, i/pos.length*CGL.DEG2RAD*-360);

        index.set(i);
        trigger.trigger();

        cgl.popModelMatrix();
    }
}

function calcLater()
{
    needsCalc=true;
}

function calc()
{
    pos.length=0;

    var i=0,degInRad=0;
    var segs=segments.get();
    if(segs<1)segs=1;
    
    for (i=0; i < Math.round(segs*percent.get()); i++)
    {
        degInRad = (360/Math.round(segs))*i*CGL.DEG2RAD;
        pos.push(
            [
            Math.sin(degInRad)*radius.get(),
            Math.cos(degInRad)*radius.get(),
            0
            ]
            );
    }

    needsCalc=false;
}



};

Ops.Gl.Matrix.CircleTransform.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.WebAudio.Analyser
// 
// **************************************************************

Ops.WebAudio.Analyser = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name='Audio Analyser';

CABLES.WEBAUDIO.createAudioContext(op);

// vars
var analyser = audioContext.createAnalyser();
analyser.smoothingTimeConstant = 0.3;
analyser.fftSize = 256;
var fftBufferLength = analyser.frequencyBinCount;
var fftDataArray = new Uint8Array(fftBufferLength);
var getFreq=true;
var array=null;

// input ports
var refresh=op.addInPort(new Port(op,"refresh",OP_PORT_TYPE_FUNCTION));
var audioIn = CABLES.WEBAUDIO.createAudioInPort(op, "Audio In", analyser);
var anData=op.inValueSelect("Data",["Frequency","Time Domain"],"Frequency");

// output ports
var next=op.outFunction("Next");
var audioOutPort = CABLES.WEBAUDIO.createAudioOutPort(op, "Audio Out", analyser);
var avgVolume=op.addOutPort(new Port(op, "average volume",OP_PORT_TYPE_VALUE));
var fftOut=op.addOutPort(new Port(op, "fft",OP_PORT_TYPE_ARRAY));

// change listeners
anData.onChange=function() {
    if(anData.get()=="Frequency")getFreq=true;
    if(anData.get()=="Time Domain")getFreq=false;
};



refresh.onTriggered = function() {
    if(!array || array.length != analyser.frequencyBinCount)
        array = new Uint8Array(analyser.frequencyBinCount);
    
    if(!array)return;
    
    //analyser.getByteFrequencyData(array);
    analyser.minDecibels = -90;
    analyser.maxDecibels = 0;

    if(!fftDataArray) {
        op.log("fftDataArray is null, returning.");
        return;
    }
    var values = 0;

    for (var i = 0; i < array.length; i++)
        values += array[i];

    var average = values / array.length;
    
    
    avgVolume.set(average/128);
    try{
        if(getFreq)
        {
            analyser.getByteFrequencyData(fftDataArray);
        }
        else
        {
            analyser.getByteTimeDomainData(fftDataArray);    
        }
    } catch(e) { op.log(e); }
    
    
    fftOut.set(null);
    fftOut.set(fftDataArray);

    next.trigger();
};



};

Ops.WebAudio.Analyser.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Gl.TextureEffects.Border
// 
// **************************************************************

Ops.Gl.TextureEffects.Border = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
attachments["border_frag"]="\n\nIN vec2 texCoord;\nUNI float width;\nUNI sampler2D tex;\nUNI float r;\nUNI float g;\nUNI float b;\nUNI float aspect;\n\nUNI bool smoothed;\n\nvoid main()\n{\n   vec4 col=texture2D(tex,texCoord);\n\n    if(!smoothed)\n    {\n       if( texCoord.x>1.0-width/3.0 || texCoord.y>1.0-width/aspect/3.0 || texCoord.y<width/aspect/3.0 || texCoord.x<width/3.0 ) col = vec4(r,g,b, 1.0);\n       gl_FragColor = col;\n    }\n    else\n    {\n       float f=smoothstep(0.0,width,texCoord.x)-smoothstep(1.0-width,1.0,texCoord.x);\n       f*=smoothstep(0.0,width/aspect,texCoord.y);\n       f*=smoothstep(1.0,1.0-width/aspect,texCoord.y);\n       gl_FragColor = mix(col,vec4(r,g,b, 1.0),1.0-f);\n    }\n}";
op.name='border';

var render=op.addInPort(new Port(op,"render",OP_PORT_TYPE_FUNCTION));

var trigger=op.addOutPort(new Port(op,"trigger",OP_PORT_TYPE_FUNCTION));
var smooth=op.inValueBool("Smooth",false);

var cgl=op.patch.cgl;
var shader=new CGL.Shader(cgl);


shader.setSource(shader.getDefaultVertexShader(),attachments.border_frag);
var textureUniform=new CGL.Uniform(shader,'t','tex',0);
var aspectUniform=new CGL.Uniform(shader,'f','aspect',0);
var uniSmooth=new CGL.Uniform(shader,'b','smoothed',smooth);

{
    var width=op.addInPort(new Port(op,"width",OP_PORT_TYPE_VALUE,{display:'range'}));
    width.set(0.1);

    var uniWidth=new CGL.Uniform(shader,'f','width',width.get());

    width.onValueChange(function()
    {
        uniWidth.setValue(width.get()/2 );
    });
}

{
    // diffuse color

    var r=op.addInPort(new Port(op,"diffuse r",OP_PORT_TYPE_VALUE,{ display:'range', colorPick:'true' }));
    r.onValueChanged=function()
    {
        if(!r.uniform) r.uniform=new CGL.Uniform(shader,'f','r',r.get());
            else r.uniform.setValue(r.get());
    };

    var g=op.addInPort(new Port(op,"diffuse g",OP_PORT_TYPE_VALUE,{ display:'range' }));
    g.onValueChanged=function()
    {
        if(!g.uniform) g.uniform=new CGL.Uniform(shader,'f','g',g.get());
            else g.uniform.setValue(g.get());
    };

    var b=op.addInPort(new Port(op,"diffuse b",OP_PORT_TYPE_VALUE,{ display:'range' }));
    b.onValueChanged=function()
    {
        if(!b.uniform) b.uniform=new CGL.Uniform(shader,'f','b',b.get());
            else b.uniform.setValue(b.get());
    };


    r.set(Math.random());
    g.set(Math.random());
    b.set(Math.random());

}



render.onTriggered=function()
{
    if(!CGL.TextureEffect.checkOpInEffect(op)) return;

var texture=cgl.currentTextureEffect.getCurrentSourceTexture();
aspectUniform.set(texture.height/texture.width);

    cgl.setShader(shader);
    cgl.currentTextureEffect.bind();

    /* --- */cgl.setTexture(0, texture.tex );
    // cgl.gl.bindTexture(cgl.gl.TEXTURE_2D, texture.tex );

    cgl.currentTextureEffect.finish();
    cgl.setPreviousShader();

    trigger.trigger();
};


};

Ops.Gl.TextureEffects.Border.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Math.Min
// 
// **************************************************************

Ops.Math.Min = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
op.name="Min";

var result=op.addOutPort(new Port(op,"result"));
var value=op.addInPort(new Port(op,"value"));
var min=op.addInPort(new Port(op,"Minimum"));

function exec()
{
    var v=Math.min(value.get(),min.get());
    if(v==v) result.set( v );
}

min.onValueChanged=exec;
value.onValueChanged=exec;

value.set(1);
min.set(1);


};

Ops.Math.Min.prototype = new CABLES.Op();

//----------------



// **************************************************************
// 
// Ops.Html.Cursor
// 
// **************************************************************

Ops.Html.Cursor = function()
{
Op.apply(this, arguments);
var op=this;
var attachments={};
var cursorPort = op.addInPort(new Port(op,"cursor",OP_PORT_TYPE_VALUE,{display:'dropdown',values:["auto","crosshair","pointer","hand","move","n-resize","ne-resize","e-resize","se-resize","s-resize","sw-resize","w-resize","nw-resize","text","wait","help", "none"]} ));
var trigger=op.inFunctionButton("Set Cursor");

var cgl = op.patch.cgl;

function update()
{
    var cursor = cursorPort.get();
    cgl.canvas.style.cursor = cursor;
}

cursorPort.onChange = update;
trigger.onTriggered=update;

};

Ops.Html.Cursor.prototype = new CABLES.Op();

//----------------

