Source: nodes/vvvv.nodes.webgl.js

// VVVV.js -- Visual Web Client Programming
// (c) 2011 Matthias Zauner
// VVVV.js is freely distributable under the MIT license.
// Additional authors of sub components are mentioned at the specific code locations.

/** A hash table of {@VVVV.Types.ShaderCodeResource} objects, indexed with the name/path of the shader code resource */
VVVV.ShaderCodeResources = {};

/**
 * Stores and caches shader code, which comes from loaded .vvvvjs.fx files or a DefineEffect node 
 * @class
 * @constructor
 */
VVVV.Types.ShaderCodeResource = function() {
  var sourceCode = '';
  /** An array of all nodes which utilize this shader code */
  this.relatedNodes = [];
  /** the DefineNode node which defines this shader code; undefined if the shader code comes from a .vvvvjs.fx file */
  this.definingNode = undefined;
  
  /**
   * Sets the source code
   * @param {String} str the shader code as string
   */
  this.setSourceCode = function(src) {
    sourceCode = src;
    for (var i=0; i<this.relatedNodes.length; i++) {
      this.relatedNodes[i].shaderSourceUpdated(sourceCode);
    }
  }
  
  /**
   * registers a shader node with this shader code resource
   * @param {VVVV.Core.Node} the shader node
   */
  this.addRelatedNode = function(node) {
    this.relatedNodes.push(node);
    if (sourceCode!='')
      node.shaderSourceUpdated(sourceCode);
  }
}


var identity = mat4.identity(mat4.create());

/**
 * A data structure which contains all render state attributes that can be set in VVVV.js
 * This is the data object which flows between WebGlRenderState pins
 * @class
 * @constructor
 */
VVVV.Types.WebGlRenderState = function() {
  /** @member */
  this.alphaBlending = true;
  /** @member */
  this.srcBlendMode = "SRC_ALPHA";
  /** @member */
  this.destBlendMode = "ONE_MINUS_SRC_ALPHA";
  
  /** @member */
  this.enableZWrite = true;
  /** @member */
  this.depthFunc = "LEQUAL";
  /** @member */
  this.depthOffset = 0.0;
  
  /** @member */
  this.polygonDrawMode = "TRIANGLES";
  
  /**
   * Used to create a copy of a WebGlRenderState object. Heavily used by the (EX9.RenderState) nodes to create altered versions
   * of the incoming render state
   * @param {VVVV.Types.WebGlRenderState} other the source render state
   */
  this.copy_attributes = function(other) {
    this.alphaBlending = other.alphaBlending;
    this.alphaFunc = other.alphaFunc;
    this.srcBlendMode = other.srcBlendMode;
    this.destBlendMode = other.destBlendMode;
    this.enableZwrite = other.enableZWrite;
    this.depthFunc = other.depthFunc;
    this.depthOffset = other.depthOffset;
    this.polygonDrawMode = other.polygonDrawMode;
  }
  
  /**
   * makes the WebGL calls to establish the render state
   * @param {WebGlContext} gl the WebGL context
   */
  this.apply = function(gl) {
    if (this.alphaBlending)
      gl.enable(gl.BLEND);
    else
      gl.disable(gl.BLEND);
    gl.blendFunc(gl[this.srcBlendMode], gl[this.destBlendMode]);
    
    gl.depthMask(this.enableZWrite);
    //gl.depthFunc(gl[this.depthFunc]);
  }
}

/**
 * The VertexBuffer class holds vertex data and provides methods to create vertex buffer objects in a given WebGL context;
 * VVVV.Types.VertexBuffer objects mainly are used in (EX9.Geometry) nodes, and
 * ultimately are parts of a {@link VVVV.Types.Mesh} object
 * @class
 * @constructor
 * @param {WebGlContext} gl the WebGL context
 * @param {Array} p an array of vertex positions
 */
VVVV.Types.VertexBuffer = function(gl, p) {
  
  /** the WebGL Vertex Buffer Object */
  this.vbo = undefined;
  /** @member */
  this.subBuffers = {};
  /** total buffer length */
  this.length = 0;
  
  /**
   * sets sub buffer data
   * @param {String} u the buffer usage (e.g. POSITION, NORMAL, TEXCOORD0, TEXCOORD1, ...)
   * @param {Integer} s the sub buffer size
   * @param {Array} d the sub buffer data
   */
  this.setSubBuffer = function(u, s, d) {
    this.subBuffers[u] = {
      usage: u,
      data: new Float32Array(d),
      size: s,
      offset: this.length
    };
    this.length += this.subBuffers[u].data.byteLength;
  }
  this.setSubBuffer('POSITION', 3, p);
  
  /**
   * Creates the VBO in the WebGL context and stores the vertex data
   */
  this.create = function() {
    this.vbo = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, this.vbo);
    gl.bufferData(gl.ARRAY_BUFFER, this.length, gl.STATIC_DRAW);
    
    _(this.subBuffers).each(function(b) {
      gl.bufferSubData(gl.ARRAY_BUFFER, b.offset, b.data);
    });
  }
  
}

/**
 * A Mesh consists of a {@link VVVV.Types.VertexBuffer} object and a list of indices. It creates a new index buffer
 * in the given WebGL context. Mesh objects are usually created by (EX9.Geometry) nodes and flow into a shader node's
 * Mesh input pin
 * @class
 * @constructor
 * @param {WebGlContext} gl the WebGL context
 * @param {VVVV.Core.VertexBuffer} the vertex data
 * @param {Array} indices the list of indices
 */
VVVV.Types.Mesh = function(gl, vertexBuffer, indices) {
  /** @member */
  this.vertexBuffer = vertexBuffer;
  /** @member */
  this.indexBuffer = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
  /** @member */
  this.numIndices = indices.length;
}

/** 
 * A Layer is the sum of a mesh, textures, render state, shaders and it parameters. Usually, a Layer object is the output
 * of a shader node and flows into a Renderer (EX9) or Group (EX9) node.
 * @class
 * @constructor
 */
VVVV.Types.Layer = function() {
  /** @member */
  this.mesh = null;
  /** An array of WebGlTexture objects */
  this.textures = [];
  /** @type VVVV.Types.ShaderProgram */
  this.shader = null;
  /** @member */
  this.uniforms = {};
  /** @member */
  this.uniformNames = []; // to help iterate through this.uniforms
  /** @type VVVV.Types.WebGlRenderState */
  this.renderState = defaultWebGlRenderState;
  
  /** returns "Layer" */
  this.toString = function() {
    return "Layer";
  }
  
}

/**
 * The WebGlResource Pin Type. Its connectionChangedHandler finds a downstream Renderer (EX9) node, gets its WebGL context, and
 * sets it to all upstream WebGL nodes' renderContext members.
 * @mixin
 * @property {String} typeName "WebGlResource"
 * @property {Boolean} reset_on_disconnect true
 * @property {Object} connectionChangedHandlers "webglresource" => function
 */
VVVV.PinTypes.WebGlResource = {
  typeName: "WebGlResource",
  reset_on_disconnect: true,
  connectionChangedHandlers: {
    "webglresource": function() {
      if (this.direction==PinDirection.Input)
        return;
      var that = this.node
      var renderers = that.findDownstreamNodes('Renderer (EX9)');
      if (!that.renderContexts)
        that.renderContexts = []; // this 'public property' should actually go to the top, right above this.setAsWebGlResourcePin. However, that doesnt work, values got overwritte by nodes of the same type.
      if (that.contextChanged==undefined)
        that.contextChanged = false;
      for (var i=0; i<renderers.length; i++) {
        that.contextChanged |= (!that.renderContexts[i] || that.renderContexts[i].canvas.id!=renderers[i].ctxt.id)
        that.renderContexts[i] = renderers[i].ctxt;
        that.dirty = true;
      }
      if (that.renderContexts.length!=renderers.length) {
        that.renderContexts.length = renderers.length;
        that.contextChanged = true;
        that.dirty = true;
      }
      
      function isWebGlPinType(typeName) {
        return typeName=="WebGlResource" || typeName=="WebGlTexture";
      }
      
      if (!that.isSubpatch) {
        _(that.inputPins).each(function(p) {
          var fromPin;
          p.markPinAsChanged();
          if (that.nodename!="Renderer (EX9)") {
            if (p.isConnected()) {
              if (p.links.length>0)
                fromPin = p.links[0].fromPin
              else if (p.masterPin.links[0].fromPin.links.length>0)
                fromPin = p.masterPin.links[0].fromPin;
              if (fromPin && isWebGlPinType(fromPin.typeName))
                fromPin.connectionChanged();
            }
          }
        });
      }
      
      if (this.masterPin && isWebGlPinType(this.masterPin.typeName))
        this.masterPin.connectionChanged();
    }
  },
  defaultValue: function() {
    return new VVVV.Types.Layer();
  }
}

/**
 * The WebGLTexture Pin Type, has the same connectionChangedHandler as {@link VVVV.PinTypes.WebGlResource}.
 * @mixin
 * @property {String} typeName "WebGlTexture"
 * @property {Boolean} reset_on_disconnect true
 * @property {Object} connectionChangedHandlers "webglresource" => function
 * @property {Function} defaultValue a function returning {@link VVVV.DefaultTexture}
 */
VVVV.PinTypes.WebGlTexture = {
  typeName: "WebGlTexture",
  reset_on_disconnect: true,
  connectionChangedHandlers: {
    "webglresource": VVVV.PinTypes.WebGlResource.connectionChangedHandlers["webglresource"]
  },
  defaultValue: function() {
    return VVVV.DefaultTexture;
  }
}

var defaultWebGlRenderState = new VVVV.Types.WebGlRenderState();
/**
 * The WebGlRenderState Pin Type
 * @mixin
 * @property {String} typeName "WebGlRenderState"
 * @property {Boolean} reset_on_disconnect true
 * @property {Function} defaultValue a function returning the default {@link VVVV.Types.WebGlRenderState} object
 */
VVVV.PinTypes.WebGlRenderState = {
  typeName: "WebGlRenderState",
  reset_on_disconnect: true,
  defaultValue: function() {
    return defaultWebGlRenderState;
  }
}

/**
 * Constant representing a WebGl context's default texture
 * @const
 */
VVVV.DefaultTexture = "Empty Texture";

/**
 * The ShaderProgram class holds vertex shader and fragment shader code and provides methods to extract uniform/attribute positions
 * and to create the shader program in the WebGl context
 */
VVVV.Types.ShaderProgram = function() {

  this.uniformSpecs = {};
  this.attributeSpecs = {};
  
  this.attribSemanticMap = {};
  this.uniformSemanticMap = {};
  
  var vertexShaderCode = '';
  var fragmentShaderCode = '';
  
  var vertexShader;
  var fragmentShader;
  
  this.isSetup = false;
  
  this.shaderProgram = undefined;
  this.log = '';
  
  var thatShader = this;
  
  this.extractSemantics = function(code) {
    thatShader.attributeSpecs = {};
    thatShader.attribSemanticMap = {};
    thatShader.uniformSpecs = {};
    thatShader.uniformSemanticMap = {};
    var pattern = /(uniform|attribute) ([a-zA-Z]+)([0-9xD]*) ([a-zA-Z0-9_]+)( : ([A-Z0-9]+))?( = \{?([^;\}]+)\}?)?;/g;
    var match;
    while ((match = pattern.exec(code))) {
      if (match[1]=='attribute' && !(thatShader.attributeSpecs[match[4]])) {
        thatShader.attributeSpecs[match[4]] = {
          varname: match[4],
          semantic: match[6],
          position: 0
        };
        if (match[6]!=undefined)
          thatShader.attribSemanticMap[match[6]] = match[4];
      }
      else if (match[1]=='uniform' && !thatShader.uniformSpecs[match[4]]) {
        var dimension = match[3]=='' ? 1 : match[3];
        var uniformSpec = {
          varname: match[4],
          semantic: match[6],
          position: 0,
          type: match[2],
          defaultValue: match[8],
          dimension: dimension
        }
        thatShader.uniformSpecs[match[4]] = uniformSpec;
        if (match[6]!=undefined)
          thatShader.uniformSemanticMap[match[6]] = match[4];
      }
    }
  }
  
  this.setVertexShader = function(code) {
    vertexShaderCode = code;
    //extractSemantics(code);
  }
  
  this.setFragmentShader =function(code) {
    fragmentShaderCode = code;
    //extractSemantics(code);
  }
  
  this.setup = function(gl) {
    this.log = '';
    vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexShaderCode.replace(/((uniform|attribute) [a-zA-Z0-9]+ [a-zA-Z0-9_]+)[^;]*/g, '$1'));
    gl.compileShader(vertexShader);
    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
      this.log = gl.getShaderInfoLog(vertexShader);
      console.log(this.log);
    }
    
    fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentShaderCode.replace(/((uniform|attribute) [a-zA-Z0-9]+ [a-zA-Z0-9_]+)[^;]*/g, '$1'));
    gl.compileShader(fragmentShader);
    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
      this.log = gl.getShaderInfoLog(fragmentShader);
      console.log(this.log);
    }
    
    this.shaderProgram = gl.createProgram();
    gl.attachShader(this.shaderProgram, vertexShader);
    gl.attachShader(this.shaderProgram, fragmentShader);
    gl.linkProgram(this.shaderProgram);

    if (!gl.getProgramParameter(this.shaderProgram, gl.LINK_STATUS)) {
      console.log("Could not initialise shaders");
    }
    
    _(this.attributeSpecs).each(function(aSpec) {
      aSpec.position = gl.getAttribLocation(thatShader.shaderProgram, aSpec.varname);
    }); 
    
    _(this.uniformSpecs).each(function(uSpec) {
      uSpec.position = gl.getUniformLocation(thatShader.shaderProgram, uSpec.varname);
    }); 
    
    this.isSetup = true;
    
    return this.log=='';
  }

}

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: FileTexture (EX9.Texture)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.FileTexture = function(id, graph) {
  this.constructor(id, "FileTexture (EX9.Texture)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: ['Always loads in background', 'No reload pin', 'No preload pin (preloading handled by browser)', 'No up and running pin', 'No texture info outputs']
  };
  
  this.auto_evaluate = false;

  var filenamePin = this.addInputPin("Filename", [""], VVVV.PinTypes.String);
  var outputPin = this.addOutputPin("Texture Out", [], VVVV.PinTypes.WebGlTexture);
  
  var typeIn = this.addInvisiblePin("Type", ["Texture"], VVVV.PinTypes.Enum);
  typeIn.enumOptions = ["Texture", "Cube Texture"];
  
  var textures = [];
  
  this.evaluate = function() {

    if (!this.renderContexts) return;
    var gl = this.renderContexts[0];
    
    if (!gl)
      return;
      
    if (this.contextChanged) {
      for (var i=0; i<textures.length; i++) {
        textures[i].context.deleteTexture(textures[i]);
      }
      textures = [];
    }
  
    if (filenamePin.pinIsChanged() || typeIn.pinIsChanged() || this.contextChanged) {
      var type = typeIn.getValue(0);
      var maxSize = this.getMaxInputSliceCount();
      for (var i=0; i<maxSize; i++) {
        var filename = VVVV.Helpers.prepareFilePath(filenamePin.getValue(i), this.parentPatch);
        if (filename.indexOf('http://')===0 && VVVV.ImageProxyPrefix!==undefined)
          filename = VVVV.ImageProxyPrefix+encodeURI(filename);
        textures[i] = gl.createTexture();
        textures[i].context = gl;
        if (type=="Texture") {
          textures[i].image = new Image();
          textures[i].image.onload = (function(j) {
            return function() {  // this is to create a new scope within the loop. see "javascript closure in for loops" http://www.mennovanslooten.nl/blog/post/62
              gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
              gl.bindTexture(gl.TEXTURE_2D, textures[j]);
              //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
              gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textures[j].image);
              gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
              gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
              gl.bindTexture(gl.TEXTURE_2D, null);
              outputPin.setValue(j, textures[j]);
            }
          })(i);
          textures[i].image.src = filename;
        }
        else if (type=="Cube Texture") {
          textures[i].image = new Image();
          textures[i].image.onload = (function(j) {
            return function() {
              var faces = [
                {face: gl.TEXTURE_CUBE_MAP_POSITIVE_X, offset: [2, 1]},
                {face: gl.TEXTURE_CUBE_MAP_NEGATIVE_X, offset: [0, 1]},
                {face: gl.TEXTURE_CUBE_MAP_POSITIVE_Y, offset: [1, 0]},
                {face: gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, offset: [1, 2]},
                {face: gl.TEXTURE_CUBE_MAP_POSITIVE_Z, offset: [1, 1]},
                {face: gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, offset: [3, 1]}];
              gl.bindTexture(gl.TEXTURE_CUBE_MAP, textures[j]);
              
              var $texcanvas = $('<canvas style="display:none" width="'+(this.width/4)+'" height="'+(this.height/3)+'"></canvas>');
              $('body').append($texcanvas);
              var ctx = $texcanvas.get(0).getContext("2d");
              
              for (var k=0; k<6; k++) {
                ctx.save();
                ctx.translate(-this.width/4 * faces[k].offset[0], -this.height/3 * faces[k].offset[1]);
                ctx.drawImage(this, 0, 0);
                gl.texImage2D(faces[k].face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, $texcanvas.get(0));
                gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
                gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                ctx.restore();
              }
              gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
              $texcanvas.remove();
              outputPin.setValue(j, textures[j]);
            }
          })(i);
          textures[i].image.src = filename;
        }
      
        outputPin.setValue(i, VVVV.defaultTexture);
      }
      outputPin.setSliceCount(maxSize);
    }
    this.contextChanged = false;
  
  }
  
  this.destroy = function() {
    for (var i=0; i<textures.length; i++) {
      textures[i].context.deleteTexture(textures[i]);
    }
  }

}
VVVV.Nodes.FileTexture.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: DX9Texture (EX9.Texture)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.DX9Texture = function(id, graph) {
  this.constructor(id, "DX9Texture (EX9.Texture)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: ['Using WebGL renderer as source doesnt work correctly in Chrome.']
  };

  var sourceIn = this.addInputPin("Source", [], VVVV.PinTypes.WebGlResource);
  var outputOut = this.addOutputPin("Texture Out", [], VVVV.PinTypes.WebGlTexture);
  
  var texture;
  
  this.evaluate = function() {
    if (!this.renderContexts) return;
    var gl = this.renderContexts[0];
    if (!gl)
      return;
      
    if (this.contextChanged && texture) {
      texture.context.deleteTexture(texture);
      texture = undefined;
    }

    if (sourceIn.isConnected()) {
      var source = sourceIn.getValue(0);
      if (!source)
        return;
      if ( (source.width & (source.width-1)) != 0 || (source.height & (source.height-1)) != 0)
        console.log("Warning: Source renderer's width/height is not a power of 2. DX9Texture will most likely not work.");
      if (source instanceof WebGLTexture) {
        outputOut.setValue(0, source);
      }
      else {
        if (texture==undefined) {
          texture = gl.createTexture();
          texture.context = gl;
        }
        gl.bindTexture(gl.TEXTURE_2D, texture);
        //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
        gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.bindTexture(gl.TEXTURE_2D, null);
      
        outputOut.setValue(0, texture);
      }
    }
    else {
      delete texture;
      gl.deleteTexture(texture);
      outputOut.setValue(0, undefined);
    }
    
    this.contextChanged = false;
  
  }
  
  this.destroy = function() {
    if (texture)
      texture.context.deleteTexture(texture);
  }

}
VVVV.Nodes.DX9Texture.prototype = new VVVV.Core.Node();

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: VideoTexture (EX9.Texture VMR9)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.VideoTexture = function(id, graph) {
  this.constructor(id, "VideoTexture (EX9.Texture VMR9)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: ['Only supports power-of-2 sized videos', 'Has no output pins for meta data']
  };

  var sourceIn = this.addInputPin("Video", [], this);
  var outputOut = this.addOutputPin("Texture Out", [], VVVV.PinTypes.WebGlTexture);
  
  var texture;
  
  this.evaluate = function() {
    if (!this.renderContexts) return;
    var gl = this.renderContexts[0];
    if (!gl)
      return;

    if (sourceIn.isConnected()) {
      var source = sourceIn.getValue(0);
      if ( (source.videoWidth & (source.videoWidth-1)) != 0 || (source.videoHeight & (source.videoHeight-1)) != 0)
        console.log("Warning: Video width/height is not a power of 2. VideoTexture will most likely not work.");
      if (texture==undefined || this.contextChanged)
        texture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, texture);
      //gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
      gl.bindTexture(gl.TEXTURE_2D, null);
    
      outputOut.setValue(0, texture);
      this.contextChanged = false;
    }
    else {
      delete texture;
      gl.deleteTexture(texture);
      outputOut.setValue(0, undefined);
    }
  
  }

}
VVVV.Nodes.VideoTexture.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: VertexBuffer(EX9.Geometry Join)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.VertexBufferJoin = function(id, graph) {
  this.constructor(id, "VertexBuffer (EX9.Geometry Join)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  var posIn = this.addInputPin("Position XYZ", [0.0, 0.0, 0.0], VVVV.PinTypes.Value);
  var normalIn = this.addInputPin("Normal XYZ", [0.0, 0.0, 0.0], VVVV.PinTypes.Value);
  var texCoord0In = this.addInputPin("Texture Coordinate 0 XY", [0.0, 0.0], VVVV.PinTypes.Value);
  var applyIn = this.addInputPin("Apply", [1], VVVV.PinTypes.Value);
  
  var vbOut = this.addOutputPin("Vertex Buffer", [], VVVV.PinTypes.WebGlResource);
  
  var vertexBuffer = null;
  
  this.evaluate = function() {
  
    var gl = this.renderContexts[0];
    if (!gl)
      return;
    
    if (applyIn.getValue(0)>=.5) {
      var positions = [];
      var texCoords0 = [];
      var normals = [];
      for (var i=0; i<this.getMaxInputSliceCount(); i++) { // this is most likely wrong, because texcoord only has 2 elements, which might cause some shift glitch
        positions[i] = parseFloat(posIn.getValue(i));
        texCoords0[i] = parseFloat(texCoord0In.getValue(i));
        normals[i] = parseFloat(normalIn.getValue(i));
      }
      vertexBuffer = new VVVV.Types.VertexBuffer(gl, positions);
      vertexBuffer.setSubBuffer('TEXCOORD0', 2, texCoords0);
      vertexBuffer.setSubBuffer('NORMAL', 3, normals);
      vertexBuffer.create();
      
      vbOut.setValue(0, vertexBuffer);
    }
    
  }

}
VVVV.Nodes.VertexBufferJoin.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Mesh (EX9.Geometry Join)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.MeshJoin = function(id, graph) {
  this.constructor(id, "Mesh (EX9.Geometry Join)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  var vbIn = this.addInputPin("Vertex Buffer", [], VVVV.PinTypes.WebGlResource);
  var indicesIn = this.addInputPin("Indices", [0], VVVV.PinTypes.Value);
  var applyIn = this.addInputPin("Apply", [1], VVVV.PinTypes.Value);
  
  var meshOut = this.addOutputPin("Mesh", [], VVVV.PinTypes.WebGlResource);
  
  var mesh = null;
  
  this.evaluate = function() {
  
    var gl = this.renderContexts[0];
    if (!gl)
      return;
    
    if (applyIn.getValue(0)>=.5) {
      if (vbIn.isConnected()) {
        mesh = new VVVV.Types.Mesh(gl, vbIn.getValue(0), indicesIn.values);
        meshOut.setValue(0, mesh);
      }
      else {
        meshOut.setValue(0, undefined);
        delete mesh;
      }
    }
    
  }

}
VVVV.Nodes.MeshJoin.prototype = new VVVV.Core.Node();

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Grid (EX9.Geometry)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Grid = function(id, graph) {
  this.constructor(id, "Grid (EX9.Geometry)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  var xIn = this.addInputPin("Resolution X", [2], VVVV.PinTypes.Value);
  var yIn = this.addInputPin("Resolution Y", [2], VVVV.PinTypes.Value);
  
  var meshOut = this.addOutputPin("Mesh", [], VVVV.PinTypes.WebGlResource);
  
  var mesh = null;
  
  this.evaluate = function() {

    if (!this.renderContexts) return;
    var gl = this.renderContexts[0];
    if (!gl)
      return;
  
    var xRes = parseInt(xIn.getValue(0));
    var yRes = parseInt(yIn.getValue(0));
      
    var vertices = [];
    var normals = [];
    var texCoords = [];
    var index = 0;
    for (var y=0; y<yRes; y++) {
      for (var x=0; x<xRes; x++) {
        vertices.push(parseFloat(x)/(xRes-1)-0.5);
        vertices.push(0.5-parseFloat(y)/(yRes-1));
        vertices.push(0.0);
        index++;
        
        normals.push(0);
        normals.push(0);
        normals.push(1);
        
        texCoords.push(parseFloat(x)/(xRes-1));
        texCoords.push(parseFloat(y)/(yRes-1));
      }
    }
    
    var vertexBuffer = new VVVV.Types.VertexBuffer(gl, vertices);
    vertexBuffer.setSubBuffer('TEXCOORD0', 2, texCoords);
    vertexBuffer.setSubBuffer('NORMAL', 3, normals);
    vertexBuffer.create();
    
    var indices = [];
    for (var y=0; y<yRes-1; y++) {
      for (var x=0; x<xRes-1; x++) {
        var refP = x+xRes*y;
        indices.push(refP);
        indices.push(refP+1);
        indices.push(refP+xRes+1);
        
        indices.push(refP+xRes+1);
        indices.push(refP+xRes);
        indices.push(refP);
      }
    }
    mesh = new VVVV.Types.Mesh(gl, vertexBuffer, indices);
      
    meshOut.setValue(0, mesh);
    
  }

}
VVVV.Nodes.Grid.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Sphere (EX9.Geometry)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Sphere = function(id, graph) {
  this.constructor(id, "Sphere (EX9.Geometry)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  var rIn = this.addInputPin("Radius", [0.5], VVVV.PinTypes.Value);
  var xIn = this.addInputPin("Resolution X", [15], VVVV.PinTypes.Value);
  var yIn = this.addInputPin("Resolution Y", [15], VVVV.PinTypes.Value);
  
  var meshOut = this.addOutputPin("Mesh", [], VVVV.PinTypes.WebGlResource);
  
  var mesh = null;
  
  this.evaluate = function() {
  
    if (!this.renderContexts) return;
    var gl = this.renderContexts[0];
    if (!gl)
      return;
  
    var xRes = parseInt(xIn.getValue(0));
    var yRes = parseInt(yIn.getValue(0));
    var radius = parseFloat(rIn.getValue(0));
      
    var vertices = [];
    var normals = [];
    var texCoords = [];
    for (var y=0; y<yRes+1; y++) {
      var yPos = Math.cos(-parseFloat(y)/yRes*Math.PI);
      for (var x=0; x<xRes; x++) {
        var xPos = Math.cos(parseFloat(x)/xRes*2*Math.PI)*Math.cos(Math.asin(yPos));
        var zPos = Math.sin(parseFloat(x)/xRes*2*Math.PI)*Math.cos(Math.asin(yPos));
        vertices.push(xPos*radius);
        vertices.push(yPos*radius);
        vertices.push(zPos*radius);
        
        normals.push(xPos);
        normals.push(yPos);
        normals.push(zPos);
        
        texCoords.push(parseFloat(x)/(xRes));
        texCoords.push(parseFloat(y)/(yRes));
      }
    }
    
    var vertexBuffer = new VVVV.Types.VertexBuffer(gl, vertices);
    vertexBuffer.setSubBuffer('TEXCOORD0', 2, texCoords);
    vertexBuffer.setSubBuffer('NORMAL', 3, normals);
    vertexBuffer.create();
    
    var indices = [];
    for (var y=0; y<yRes; y++) {
      for (var x=0; x<xRes; x++) {
        var yOff = xRes*y;
        var refP = x+yOff;
        indices.push(refP);
        indices.push((refP+1)%xRes+yOff);
        indices.push((refP+1)%xRes+xRes+yOff);
        
        indices.push((refP+1)%xRes+xRes+yOff);
        indices.push(refP+xRes);
        indices.push(refP);
      }
    }
    mesh = new VVVV.Types.Mesh(gl, vertexBuffer, indices);
      
    meshOut.setValue(0, mesh);
    
  }

}
VVVV.Nodes.Sphere.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Cylinder (EX9.Geometry)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Cylinder = function(id, graph) {
  this.constructor(id, "Cylinder (EX9.Geometry)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  var r1In = this.addInputPin("Radius 1", [0.5], VVVV.PinTypes.Value);
  var r2In = this.addInputPin("Radius 2", [0.5], VVVV.PinTypes.Value);
  var lIn = this.addInputPin("Length", [1.0], VVVV.PinTypes.Value);
  var cyclesIn = this.addInputPin("Cycles", [1.0], VVVV.PinTypes.Value);
  var capsIn = this.addInputPin("Caps", [1], VVVV.PinTypes.Value);
  var xIn = this.addInputPin("Resolution X", [15], VVVV.PinTypes.Value);
  var yIn = this.addInputPin("Resolution Y", [1], VVVV.PinTypes.Value);
  
  var meshOut = this.addOutputPin("Mesh", [], VVVV.PinTypes.WebGlResource);
  
  var mesh = null;
  
  this.evaluate = function() {
  
    if (!this.renderContexts) return;
    var gl = this.renderContexts[0];
    if (!gl)
      return;
  
    var xRes = parseInt(xIn.getValue(0));
    var yRes = parseInt(yIn.getValue(0));
    var radius1 = parseFloat(r1In.getValue(0));
    var radius2 = parseFloat(r2In.getValue(0));
    var length = parseFloat(lIn.getValue(0));
    var cycles = parseFloat(cyclesIn.getValue(0));
      
    var vertices = [];
    var normals = [];
    var texCoords = [];
    
    // cap vertices ...
    vertices.push(0.0);
    vertices.push(length/2);
    vertices.push(0.0);
    
    normals.push(0.0);
    normals.push(1.0);
    normals.push(0.0);
    
    texCoords.push(0.0);
    texCoords.push(0.0);
    
    vertices.push(0.0);
    vertices.push(-length/2);
    vertices.push(0.0);
    
    normals.push(0.0);
    normals.push(-1.0);
    normals.push(0.0);
    
    texCoords.push(0.0);
    texCoords.push(0.0);
    
    // other vertices ...
    for (var y=0; y<yRes+1; y++) {
      var n = parseFloat(y)/yRes
      var yPos = (n - 0.5) * -length;
      for (var x=0; x<xRes+1; x++) {
        var xPos = Math.cos((parseFloat(x)/xRes*2*Math.PI * cycles  - Math.PI*cycles -Math.PI/2));
        var zPos = Math.sin((parseFloat(x)/xRes*2*Math.PI * cycles  - Math.PI*cycles -Math.PI/2));
        var r = n*radius2 + (1-n)*radius1;
        vertices.push(xPos*r);
        vertices.push(yPos);
        vertices.push(zPos*r);
        
        normals.push(xPos);
        normals.push(0.0);
        normals.push(zPos);
        
        texCoords.push(parseFloat(x)/(xRes));
        texCoords.push(parseFloat(y)/(yRes));
      }
    }
    
    var vertexBuffer = new VVVV.Types.VertexBuffer(gl, vertices);
    vertexBuffer.setSubBuffer('TEXCOORD0', 2, texCoords);
    vertexBuffer.setSubBuffer('NORMAL', 3, normals);
    vertexBuffer.create();
    
    var indices = [];
    
    // caps indices ...
    if (capsIn.getValue(0)>.5) {
      for (var n=0; n<2; n++) {
        for (var x=0; x<xRes; x++) {
          indices.push(n);
          indices.push(2+x+n+(n*yRes*(xRes+1)));
          indices.push(2+x+(1-n)+(n*yRes*(xRes+1)));
        }
      }
    }
    
    // other indices ...
    for (var y=0; y<yRes; y++) {
      for (var x=0; x<xRes; x++) {
        var refP = x+(xRes+1)*y + 2;
        indices.push(refP);
        indices.push(refP+1);
        indices.push(refP+xRes+2);
        
        indices.push(refP+xRes+2);
        indices.push(refP+xRes+1);
        indices.push(refP);
      }
    }
    
    mesh = new VVVV.Types.Mesh(gl, vertexBuffer, indices);
      
    meshOut.setValue(0, mesh);
    
  }

}
VVVV.Nodes.Cylinder.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Blend (EX9.RenderState Advanced)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.BlendWebGLAdvanced = function(id, graph) {
  this.constructor(id, "Blend (EX9.RenderState Advanced)", graph);
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  var renderStateIn = this.addInputPin("Render State In", [], VVVV.PinTypes.WebGlRenderState);
  var alphaBlendingIn = this.addInputPin("Alpha Blending", [1], VVVV.PinTypes.Value);
  var srcModeIn = this.addInputPin("Source Blend Mode", ['SrcAlpha'], VVVV.PinTypes.Enum); 
  var destModeIn = this.addInputPin("Destination Blend Mode", ['SrcAlpha'], VVVV.PinTypes.Enum);
  srcModeIn.enumOptions = destModeIn.enumOptions = ['One', 'Zero', 'SrcAlpha', 'InvSrcAlpha', 'DestAlpha', 'InvDestAlpha', 'SrcColor', 'InvSrcColor', 'DestColor', 'InvDestColor'];
  
  var renderStateOut = this.addOutputPin("Render State Out", [], VVVV.PinTypes.WebGlRenderState);
  
  var renderStates = [];
  
  function convertToWebGLBlendFactor(VVVVFactor) {
    switch (VVVVFactor) {
      case 'One': return "ONE";
      case 'Zero': return "ZERO";
      case 'SrcAlpha': return "SRC_ALPHA";
      case 'InvSrcAlpha': return "ONE_MINUS_SRC_ALPHA";
      case 'DestAlpha': return "DST_ALPHA";
      case 'InvDestAlpha': return "ONE_MINUS_DST_ALPHA";
      case 'SrcColor': return "SRC_COLOR";
      case 'InvSrcColor': return "ONE_MINUS_SRC_COLOR";
      case 'DestColor': return "DST_COLOR";
      case 'InvDestColor': return "ONE_MINUS_DST_COLOR";
    }
    return null;
  }
  
  this.evaluate = function() {
    var maxSpreadSize = this.getMaxInputSliceCount();
  
    for (var i=0; i<maxSpreadSize; i++) {
      if (renderStates[i]==undefined) {
        renderStates[i] = new VVVV.Types.WebGlRenderState();
      }
      renderStates[i].copy_attributes(renderStateIn.getValue(i));
      renderStates[i].alphaBlending = parseFloat(alphaBlendingIn.getValue(i))>.5;
      renderStates[i].srcBlendMode = convertToWebGLBlendFactor(srcModeIn.getValue(i));
      renderStates[i].destBlendMode = convertToWebGLBlendFactor(destModeIn.getValue(i));
      renderStateOut.setValue(i, renderStates[i]);
    }
    renderStateOut.setSliceCount(maxSpreadSize);
    
  }

}
VVVV.Nodes.BlendWebGLAdvanced.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Blend (EX9.RenderState)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.BlendWebGL = function(id, graph) {
  this.constructor(id, "Blend (EX9.RenderState)", graph);
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: ['results differ from VVVV', 'Multiply mode not supported']
  };
  
  var renderStateIn = this.addInputPin("Render State In", [], VVVV.PinTypes.WebGlRenderState);
  var drawModeIn = this.addInputPin("Draw Mode", ["Blend"], VVVV.PinTypes.Enum);
  drawModeIn.enumOptions = ['Add', 'Multiply', 'Blend', 'ColorAsAlphaAdd', 'ColorAsAlphaBlend'];
  
  var renderStateOut = this.addOutputPin("Render State Out", [], VVVV.PinTypes.WebGlRenderState);
  
  var renderStates = [];
  
  this.evaluate = function() {
    var maxSpreadSize = this.getMaxInputSliceCount();
  
    for (var i=0; i<maxSpreadSize; i++) {
      if (renderStates[i]==undefined) {
        renderStates[i] = new VVVV.Types.WebGlRenderState();
      }
      renderStates[i].copy_attributes(renderStateIn.getValue(i));
      switch (drawModeIn.getValue(i)) {
        case "Add":
          renderStates[i].srcBlendMode = "SRC_ALPHA";
          renderStates[i].destBlendMode = "ONE";
          break;
        case "Multiply":
          console.log("Multiply Blend Mode not supported (or we just missed it)");
        case "Blend":
          renderStates[i].srcBlendMode = "SRC_ALPHA";
          renderStates[i].destBlendMode = "ONE_MINUS_SRC_ALPHA";
          break;
        case "ColorAsAlphaAdd":
          renderStates[i].srcBlendMode = "SRC_COLOR";
          renderStates[i].destBlendMode = "ONE";
          break;
        case "ColorAsAlphaBlend":
          renderStates[i].srcBlendMode = "SRC_COLOR";
          renderStates[i].destBlendMode = "ONE_MINUS_SRC_COLOR";
          break;
      }
      renderStateOut.setValue(i, renderStates[i]);
    }
    renderStateOut.setSliceCount(maxSpreadSize);
    
  }

}
VVVV.Nodes.BlendWebGL.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Fill (EX9.RenderState)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.FillWebGL = function(id, graph) {
  this.constructor(id, "Fill (EX9.RenderState)", graph);
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: ['does not actually draw wireframe, because this is not supported in WebGL, but makes renderer use gl.LINE instead of gl.TRIANGLES when drawing']
  };
  
  var renderStateIn = this.addInputPin("Render State In", [], VVVV.PinTypes.WebGlRenderState);
  var fillModeIn = this.addInputPin("Fill Mode", ["Solid"], VVVV.PinTypes.Enum);
  fillModeIn.enumOptions = ['Point', 'Solid', 'WireFrame'];
  
  var renderStateOut = this.addOutputPin("Render State Out", [], VVVV.PinTypes.WebGlRenderState);
  
  var renderStates = [];
  
  this.evaluate = function() {
    var maxSpreadSize = this.getMaxInputSliceCount();
  
    for (var i=0; i<maxSpreadSize; i++) {
      if (renderStates[i]==undefined) {
        renderStates[i] = new VVVV.Types.WebGlRenderState();
      }
      renderStates[i].copy_attributes(renderStateIn.getValue(i));
      switch (fillModeIn.getValue(i)) {
        case 'Point':
          renderStates[i].polygonDrawMode = "POINTS";
          break;
        case 'Solid':
          renderStates[i].polygonDrawMode = "TRIANGLES";
          break;
        case 'WireFrame':
          renderStates[i].polygonDrawMode = "LINES";
      }
      renderStateOut.setValue(i, renderStates[i]);
    }
    renderStateOut.setSliceCount(maxSpreadSize);
    
  }

}
VVVV.Nodes.FillWebGL.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: ZWriteEnable (EX9.RenderState)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.ZWriteEnableWebGL = function(id, graph) {
  this.constructor(id, "ZWriteEnable (EX9.RenderState)", graph);
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  var renderStateIn = this.addInputPin("Render State In", [], VVVV.PinTypes.WebGlRenderState);
  var enableZWriteIn = this.addInputPin("ZWrite Enable", [1], VVVV.PinTypes.Value);
  var depthFuncIn = this.addInputPin("Compare Function", ['Always'], VVVV.PinTypes.Enum); 
  depthFuncIn.enumOptions = ['Never', 'Less', 'LessEqual', 'Equal', 'NotEqual', 'Greater', 'GreaterEqual', 'Always'];
  var biasIn = this.addInputPin("Depth Bias", [0.0], VVVV.PinTypes.Value); 
  
  var renderStateOut = this.addOutputPin("Render State Out", [], VVVV.PinTypes.WebGlRenderState);
  
  var renderStates = [];
  
  function convertToWebGLDepthFunc(VVVVFunc) {
    switch (VVVVFunc) {
      case 'Never': return "NEVER";
      case 'Less': return "LESS";
      case 'LessEqual': return "LEQUAL";
      case 'Equal': return "EQUAL";
      case 'NotEqual': return "NOTEQUAL";
      case 'Greater': return "GREATER";
      case 'GreaterEqual': return "GEQUAL";
      case 'Always': return "ALWAYS";
    }
    return null;
  }
  
  this.evaluate = function() {
    var maxSpreadSize = this.getMaxInputSliceCount();
  
    for (var i=0; i<maxSpreadSize; i++) {
      if (renderStates[i]==undefined) {
        renderStates[i] = new VVVV.Types.WebGlRenderState();
      }
      renderStates[i].copy_attributes(renderStateIn.getValue(i));
      renderStates[i].enableZWrite = parseFloat(enableZWriteIn.getValue(i))>.5;
      renderStates[i].depthFunc = convertToWebGLDepthFunc(depthFuncIn.getValue(i));
      renderStates[i].depthOffset = parseFloat(biasIn.getValue(0));
      renderStateOut.setValue(i, renderStates[i]);
    }
    renderStateOut.setSliceCount(maxSpreadSize);
    
  }

}
VVVV.Nodes.ZWriteEnableWebGL.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: GenericShader (EX9.Effect)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.GenericShader = function(id, graph) {
  this.constructor(id, "GenericShader (EX9.Effect)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  this.shaderFile = '';
  
  var renderStateIn = this.addInputPin("Render State", [], VVVV.PinTypes.WebGlRenderState);
  var meshIn = this.addInputPin("Mesh", [], VVVV.PinTypes.WebGlResource);
  var transformIn = this.addInputPin("Transform", [], VVVV.PinTypes.Transform);
  var techniqueIn = this.addInputPin("Technique", [''], VVVV.PinTypes.Enum);
  techniqueIn.enumOptions = [''];
  
  var layerOut = this.addOutputPin("Layer", [], VVVV.PinTypes.WebGlResource);
  
  var layers = [];
  var mesh = null;
  var shader = null;
  var shaderCode;
  var shaderCodeChanged = false;
  
  var shaderPins = [];
  
  var initialized = false;
  
  var thatNode = this;
  
  this.initialize = function() {
    
    // add the pins which have already been added (by the patch XML) to the shaderPins array
    var defaultPins = ["Render State", "Mesh", "Transform", "Technique"];
    _(thatNode.inputPins).each(function(p) {
      if (defaultPins.indexOf(p.pinname)<0) {
        p.unvalidated = true;
        shaderPins.push(p);
      }
    })
    
    if (VVVV.ShaderCodeResources[thatNode.shaderFile]==undefined) {
      VVVV.ShaderCodeResources[thatNode.shaderFile] = new VVVV.Types.ShaderCodeResource();
      VVVV.ShaderCodeResources[thatNode.shaderFile].addRelatedNode(thatNode);
      $.ajax({
        url: VVVV.Helpers.prepareFilePath(thatNode.shaderFile, thatNode.parentPatch),
        async: false,
        dataType: 'text',
        success: function(response) {
          VVVV.ShaderCodeResources[thatNode.shaderFile].setSourceCode(response);
        },
        error: function() {
          console.log('ERROR: Could not load shader file '+thatNode.shaderFile.replace('%VVVV%', VVVV.Root));
          VVVV.onNotImplemented('Could not load shader file '+thatNode.shaderFile.replace('%VVVV%', VVVV.Root));
        }
      });
    }
    else {
      VVVV.ShaderCodeResources[thatNode.shaderFile].addRelatedNode(thatNode);
    }
    
 
  }
  
  this.shaderSourceUpdated = function(sc) {
    shaderCode = sc;
    if (!shader)
      shader = new VVVV.Types.ShaderProgram();
    thatNode.addUniformPins();
    thatNode.setupShader();
    _(thatNode.inputPins).each(function(p) {
      p.markPinAsChanged();
    })
    shaderCodeChanged = true;
    this.parentPatch.afterUpdate();
  }
  
  this.addUniformPins = function() {
    shader.extractSemantics(shaderCode);
    
    // delete pins which have been removed from shader code or where the type changed
    var deletables = [];
    for (var i=0; i<shaderPins.length; i++) {
      if (!shader.uniformSpecs[shaderPins[i].pinname.replace(/ /g, '_')]) {
        thatNode.removeInputPin(shaderPins[i].pinname);
        deletables.push(i);
      }
    }
    for (var i=0; i<deletables.length; i++) {
      shaderPins.splice(deletables[i], 1);
    }
    
    // add pins
    _(shader.uniformSpecs).each(function(u) {
      if (u.semantic=="VIEW" || u.semantic=="PROJECTION" || u.semantic=="WORLD")
        return;
      var pinType = VVVV.PinTypes.Value;
      var defaultValue = [];
      switch (u.type) {
        case 'mat':
          pinType = VVVV.PinTypes.Transform;
          break;
        case 'samplerCube':
        case 'sampler':
          pinType = VVVV.PinTypes.WebGlTexture;
          break;
        default:
          if (u.semantic == 'COLOR') {
            pinType = VVVV.PinTypes.Color;
            defaultValue = ['1.0, 1.0, 1.0, 1.0'];
          }
          else
            defaultValue = [0.0];
          if (u.defaultValue) {
            if (u.semantic != 'COLOR')
              defaultValue = _(u.defaultValue.split(',')).map(function(e) { return parseFloat(e); });
            else
              defaultValue = [u.defaultValue];
          }
          
      }
      for (var i=0; i<shaderPins.length; i++) {
        if (shaderPins[i].pinname==u.varname.replace(/_/g,' ')) {
          shaderPins[i].dimensions = u.dimension;
          if (shaderPins[i].unvalidated && !shaderPins[i].isConnected())
            shaderPins[i].values = thatNode.defaultPinValues[shaderPins[i].pinname] ? thatNode.defaultPinValues[shaderPins[i].pinname].slice() : defaultValue;
          if (shaderPins[i].typeName!=pinType.typeName) {
            var values = shaderPins[i].values.slice();
            shaderPins[i].setType(pinType);
            if (shaderPins[i].unvalidated && (pinType.primitive || shaderPins[i].isConnected()))
              shaderPins[i].values = values;
            if (shaderPins[i].isConnected() && !shaderPins[i].unvalidated) {
              shaderPins[i].connectionChanged();
              shaderPins[i].links[0].destroy();
            }
          }
          shaderPins[i].unvalidated = false;
          return;
        }
      }
        
      var pin = thatNode.addInputPin(u.varname.replace(/_/g,' '), defaultValue, pinType);
      pin.dimensions = u.dimension;
      shaderPins.push(pin);
    });
  }
  
  this.setupShader = function() {
    var technique = techniqueIn.getValue(0);
    technique = technique.replace(/^\s*/, '').replace(/\s*$/, '');
    var rx = new RegExp(/(vertex_shader|fragment_shader)\{([^\}]+)\}/g);
    techniqueIn.enumOptions = [];
    var match;
    while ((match = rx.exec(shaderCode))!=null) {
      techniqueIn.enumOptions = techniqueIn.enumOptions.concat(match[2].replace(/\s/g, '').split(','));
    }
    techniqueIn.enumOptions = techniqueIn.enumOptions.filter(function(e, index, self) { return self.indexOf(e)===index })
    if (techniqueIn.enumOptions.length==0)
      techniqueIn.enumOptions.push('');
    if (technique=="" || techniqueIn.enumOptions.indexOf(technique)<0) {
      technique = techniqueIn.enumOptions[0];
      techniqueIn.setValue(0, technique);
    }
    var vsRegEx = new RegExp('vertex_shader(\\{([a-zA-Z0-9]+,\\s*)*'+technique+'(,\\s*[a-zA-Z0-9]+)*\\})?:([\\s\\S]*?)(vertex_shader|fragment_shader)');
    var psRegEx = new RegExp('fragment_shader(\\{([a-zA-Z0-9]+,\\s*)*'+technique+'(,\\s*[a-zA-Z0-9]+)*\\})?:([\\s\\S]*?)(vertex_shader|fragment_shader)');
    
    var match;
    
    match = /STARTOFSTRING((\r?\n|.)*?)(vertex_shader|fragment_shader)/.exec('STARTOFSTRING'+shaderCode);
    var varDefs = match[1];
    
    if ((match = vsRegEx.exec(shaderCode+'\nfragment_shader'))==undefined) {
      console.log('ERROR: No vertex shader code for technique '+technique+' found');
      return;
    }
    var vertexShaderCode = match[4];
    
    if ((match = psRegEx.exec(shaderCode+'\nfragment_shader'))==undefined) {
      console.log('ERROR: No fragment shader code for technique '+technique+' found');
      return;
    }
    var fragmentShaderCode = match[4];
    
    shader.setFragmentShader(varDefs+fragmentShaderCode);
    shader.setVertexShader(varDefs+vertexShaderCode);
    
  }
  
  this.evaluate = function() {
    if (!this.renderContexts) return;
    var gl = this.renderContexts[0];
    if (!gl || !shader)
      return;
    if (!shader.isSetup || this.contextChanged || techniqueIn.pinIsChanged()) {
      this.setupShader();
      if (shader.setup(gl)) {
        if (VVVV.ShaderCodeResources[thatNode.shaderFile].definingNode)
          VVVV.ShaderCodeResources[thatNode.shaderFile].definingNode.showStatus('success', "Successfully compiled");
      }
      else {
        if (VVVV.ShaderCodeResources[thatNode.shaderFile].definingNode)
          VVVV.ShaderCodeResources[thatNode.shaderFile].definingNode.showStatus('error', shader.log);
      }
    }

    // find out input slice count with respect to the input pin dimension, defined by the shader code  
    var maxSize = 0;
    _(this.inputPins).each(function(p) {
      var sliceCount = p.getSliceCount();
      var pinname = p.pinname.replace(/ /g,'_')
      if (shader.uniformSpecs[pinname] && shader.uniformSpecs[pinname].type=='vec' && shader.uniformSpecs[pinname].semantic!='COLOR') {
        sliceCount = parseInt(sliceCount/shader.uniformSpecs[pinname].dimension);
      }
      if (sliceCount > maxSize)
        maxSize = sliceCount;
    });
    if (!meshIn.isConnected() || meshIn.getValue(0)==undefined)
      maxSize = 0;
    
    var currentLayerCount = layers.length;
    if (this.contextChanged || shaderCodeChanged)
      currentLayerCount = 0;
    // shorten layers array, if input slice count decreases
    if (maxSize<currentLayerCount) {
      layers.splice(maxSize, currentLayerCount-maxSize);
    }
    for (var j=currentLayerCount; j<maxSize; j++) {
      layers[j] = new VVVV.Types.Layer();
      layers[j].mesh = meshIn.getValue(0);
      layers[j].shader = shader;
      _(shader.uniformSpecs).each(function(u) {
        layers[j].uniformNames.push(u.varname);
        layers[j].uniforms[u.varname] = { uniformSpec: u, value: undefined };
      });
    }
    if (meshIn.pinIsChanged()) {
      for (var j=0; j<maxSize; j++) {
      	layers[j].mesh = meshIn.getValue(0);
      }
    }
    for (var j=0; j<maxSize; j++) {
      layers[j].shader = shader;
    }
    
    for (var i=0; i<shaderPins.length; i++) {
      var pinname = shaderPins[i].pinname.replace(/ /g, '_');
      if (shaderPins[i].pinIsChanged() || currentLayerCount<maxSize) {
        for (var j=0; j<maxSize; j++) {
          if (shader.uniformSpecs[pinname].type=='vec') {
            if (shader.uniformSpecs[pinname].semantic=='COLOR') {
              var rgba = _(shaderPins[i].getValue(j).split(',')).map(function(x) { return parseFloat(x) });
              layers[j].uniforms[pinname].value = new Float32Array(rgba);
            }
            else {
              var arr = shaderPins[i].getValue(j, shaderPins[i].dimensions);
              layers[j].uniforms[pinname].value = new Float32Array(arr);
            }
          }
          else {
            var v = shaderPins[i].getValue(j);
            layers[j].uniforms[pinname].value = v;
          }
        }
      }
    }
    
    if (renderStateIn.pinIsChanged() || currentLayerCount<maxSize) {
      for (var i=0; i<maxSize; i++) {
        if (renderStateIn.isConnected())
          layers[i].renderState = renderStateIn.getValue(i);
        else
          layers[i].renderState = VVVV.DefaultRenderState;
      }
    }
    
    if (transformIn.pinIsChanged() || currentLayerCount<maxSize) {
      for (var i=0; i<maxSize; i++) {
        var transform = this.inputPins["Transform"].getValue(i);
        layers[i].uniforms[layers[i].shader.uniformSemanticMap['WORLD']].value = transform;
      }
    }
    
    this.outputPins["Layer"].setSliceCount(maxSize);
    for (var i=0; i<maxSize; i++) {
      this.outputPins["Layer"].setValue(i, layers[i]);
    }
    
    this.contextChanged = false;
    shaderCodeChanged = false;
        
  }
  
  this.openUIWindow = function() {
    if (VVVV.ShaderCodeResources[thatNode.shaderFile].definingNode)
      VVVV.ShaderCodeResources[thatNode.shaderFile].definingNode.openUIWindow();
  }
    

}
VVVV.Nodes.GenericShader.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Quad (DX9)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Quad = function(id, graph) {
  this.constructor(id, "Quad (DX9)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: ['No Sampler States', 'No texture coord mapping', 'No enable pin', 'Transprent pixels are discarded by default']
  };
  
  this.auto_evaluate = false;
  
  var renderStateIn = this.addInputPin("Render State", [], VVVV.PinTypes.WebGlRenderState);
  this.addInputPin("Transform", [], VVVV.PinTypes.Transform);
  this.addInputPin("Texture", [], VVVV.PinTypes.WebGlTexture);
  this.addInputPin("Texture Transform", [], VVVV.PinTypes.Transform);
  this.addInputPin("Color", ["1.0, 1.0, 1.0, 1.0"], VVVV.PinTypes.Color);
  
  var layerOut = this.addOutputPin("Layer", [], VVVV.PinTypes.WebGlResource);
  
  var initialized = false;
  var layers = [];
  var mesh = null;
  var shader = null;
  
  this.evaluate = function() {
  	
    if (!this.renderContexts) return;
    var gl = this.renderContexts[0];
  	
    if (!gl)
      return;
  
    if (this.contextChanged) {
      var vertices = [
         0.5,  0.5,  0.0,
        -0.5,  0.5,  0.0,
         0.5, -0.5,  0.0,
        -0.5, -0.5,  0.0
      ];
      
      var texCoords = [
        1.0, 0.0,
        0.0, 0.0,
        1.0, 1.0,
        0.0, 1.0
      ];
      
      var vertexBuffer = new VVVV.Types.VertexBuffer(gl, vertices);
      vertexBuffer.setSubBuffer('TEXCOORD0', 2, texCoords);
      vertexBuffer.create();
      mesh = new VVVV.Types.Mesh(gl, vertexBuffer, [ 0, 1, 2, 1, 3, 2 ]);
      
      // shaders
  
      var fragmentShaderCode = "#ifdef GL_ES\n";
      fragmentShaderCode += "precision highp float;\n";
      fragmentShaderCode += "#endif\n";
      fragmentShaderCode += "uniform vec4 col : COLOR = {1.0, 1.0, 1.0, 1.0}; varying vec2 vs2psTexCd; uniform sampler2D Samp0; void main(void) { gl_FragColor = col*texture2D(Samp0, vs2psTexCd); if (gl_FragColor.a==0.0) discard;  }";
      var vertexShaderCode = "attribute vec3 PosO : POSITION; attribute vec2 TexCd : TEXCOORD0; uniform mat4 tW : WORLD; uniform mat4 tV : VIEW; uniform mat4 tP : PROJECTION; uniform mat4 tTex; varying vec2 vs2psTexCd; void main(void) { gl_Position = tP * tV * tW * vec4(PosO, 1.0); vs2psTexCd = (tTex * vec4(TexCd.xy-.5, 0.0, 1.0)).xy+.5; }";
      
      shader = new VVVV.Types.ShaderProgram();
      shader.extractSemantics(fragmentShaderCode + vertexShaderCode);
      shader.setFragmentShader(fragmentShaderCode);
      shader.setVertexShader(vertexShaderCode);
      shader.setup(gl);
          
    }
    
    var maxSize = this.getMaxInputSliceCount();
    var currentLayerCount = layers.length;
    if (this.contextChanged)
      currentLayerCount = 0;
    // shorten layers array, if input slice count decreases
    if (maxSize<currentLayerCount) {
      layers.splice(maxSize, currentLayerCount-maxSize);
    }
    for (var j=currentLayerCount; j<maxSize; j++) {
      layers[j] = new VVVV.Types.Layer();
      layers[j].mesh = mesh;
      layers[j].shader = shader;
      
      _(shader.uniformSpecs).each(function(u) {
        layers[j].uniformNames.push(u.varname);
        layers[j].uniforms[u.varname] = { uniformSpec: u, value: undefined };
      });
    }
    
    var colorChanged = this.inputPins["Color"].pinIsChanged();
    var transformChanged = this.inputPins["Transform"].pinIsChanged();
    var textureChanged = this.inputPins["Texture"].pinIsChanged();
    var textureTransformChanged = this.inputPins["Texture Transform"].pinIsChanged();
    
    if (colorChanged || currentLayerCount<maxSize) {
      for (var i=0; i<maxSize; i++) {
        var color = this.inputPins["Color"].getValue(i);
        var rgba = _(color.split(',')).map(function(x) { return parseFloat(x) });
        layers[i].uniforms['col'].value = new Float32Array(rgba);
      }
    }
    
    if (renderStateIn.pinIsChanged() || currentLayerCount<maxSize) {
      for (var i=0; i<maxSize; i++) {
        if (renderStateIn.isConnected())
          layers[i].renderState = renderStateIn.getValue(i);
        else
          layers[i].renderState = VVVV.DefaultRenderState;
      }
    }
    
    if (transformChanged || currentLayerCount<maxSize) {
      for (var i=0; i<maxSize; i++) {
        var transform = this.inputPins["Transform"].getValue(i);
        layers[i].uniforms[layers[i].shader.uniformSemanticMap['WORLD']].value = transform;
      }
    }
    
    if (textureChanged || currentLayerCount<maxSize) {
      for (var i=0; i<maxSize; i++) {
        layers[i].uniforms["Samp0"].value = this.inputPins["Texture"].getValue(i);
      }
    }
    
    if (textureTransformChanged || currentLayerCount<maxSize) {
      for (var i=0; i<maxSize; i++) {
        var transform = this.inputPins["Texture Transform"].getValue(i);
        layers[i].uniforms["tTex"].value = transform;
      }
    }
    
    this.outputPins["Layer"].setSliceCount(maxSize);
    for (var i=0; i<maxSize; i++) {
      this.outputPins["Layer"].setValue(i, layers[i]);
    }
    
    this.contextChanged = false;
    
  }

}
VVVV.Nodes.Quad.prototype = new VVVV.Core.Node();

/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Group (EX9)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.Group = function(id, graph) {
  this.constructor(id, "Group (EX9)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: []
  };
  
  var layerIns = [];
  var enableIn = this.addInputPin("Enabled", [1], VVVV.PinTypes.Value);
  var layerCountIn = this.addInvisiblePin("Layer Template Count", [2], VVVV.PinTypes.Value);
  
  var layerOut = this.addOutputPin("Layer", [], VVVV.PinTypes.WebGlResource);
  
  this.initialize = function() {
  	var layerCount = Math.max(2, layerCountIn.getValue(0));
  	VVVV.Helpers.dynamicPins(this, layerIns, layerCount, function(i) {
      return this.addInputPin("Layer "+(i+1), [], VVVV.PinTypes.WebGlResource);
    });
  }
  
  this.evaluate = function() {
  	if (layerCountIn.pinIsChanged()) {
      this.initialize();
  	}
  	
  	var outSliceIdx = 0;
    if(enableIn.getValue(0) > .5) {
      for(var i = 0; i < layerIns.length; i++) {
        for(var j = 0; j < layerIns[i].getSliceCount(); j++) {
          layerOut.setValue(outSliceIdx++, layerIns[i].getValue(j));
        }
      }
    }
    layerOut.setSliceCount(outSliceIdx);
  }

}
VVVV.Nodes.Group.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: Renderer (EX9)
 Author(s): Matthias Zauner
 Original Node Author(s): VVVV Group
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.RendererWebGL = function(id, graph) {
  this.constructor(id, "Renderer (EX9)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['VVVV Group'],
    credits: [],
    compatibility_issues: ['Disabling Clear doesn\'t work in Chrome', 'No Fullscreen', 'No Enable Pin', 'No Aspect Ration and Viewport transform', 'No mouse output']
  };
  
  this.addInputPin("Layers", [], VVVV.PinTypes.WebGlResource);
  var clearIn = this.addInputPin("Clear", [1], VVVV.PinTypes.Value);
  var bgColIn = this.addInputPin("Background Color", ['0.0, 0.0, 0.0, 1.0'], VVVV.PinTypes.Color);
  var bufferWidthIn = this.addInputPin("Backbuffer Width", [0], VVVV.PinTypes.Value);
  var bufferHeightIn = this.addInputPin("Backbuffer Height", [0], VVVV.PinTypes.Value);
  var viewIn = this.addInputPin("View", [], VVVV.PinTypes.Transform);
  var projIn = this.addInputPin("Projection", [], VVVV.PinTypes.Transform);
  
  var enableDepthBufIn = this.addInvisiblePin("Windowed Depthbuffer Format", ['NONE'], VVVV.PinTypes.Enum);
  enableDepthBufIn.enumOptions = ['NONE', 'DX16'];
  
  var bufferWidthOut = this.addOutputPin("Actual Backbuffer Width", [0.0], VVVV.PinTypes.Value);
  var bufferHeightOut = this.addOutputPin("Actual Backbuffer Height", [0.0], VVVV.PinTypes.Value);
  var ex9Out = this.addOutputPin("EX9 Out", [], VVVV.PinTypes.WebGlResource);
  
  var width = 0.0;
  var height = 0.0;
  
  var pMatrix;
  var vMatrix;
  
  var canvas;
  this.ctxt = undefined;              // the renderer's active context. might be the canvas context, or the context of a connected downstream renderer
  var canvasCtxt = undefined;         // the context of the canvas which is connected to the renderer
  var gl;                             // just a convenience variable for keeping the lines short 
  
  var bbufFramebuffer;
  var bbufTexture;
  
  function attachMouseEvents() {
    $(canvas).detach('mousemove');
    $(canvas).detach('mousedown');
    $(canvas).detach('mouseup');
    VVVV.MousePositions[canvas.id] = {'x': 0.0, 'y': 0.0, 'wheel': 0.0, 'lb': 0.0, 'mb': 0.0, 'rb': 0.0};
    $(canvas).mousemove(function(e) {
      var x = (e.pageX - $(this).offset().left) * 2 / $(this).width() - 1;
      var y = -((e.pageY - $(this).offset().top) * 2 / $(this).height() - 1);
      VVVV.MousePositions['_all'].x = x;
      VVVV.MousePositions['_all'].y = y;
      VVVV.MousePositions[canvas.id].x = x;
      VVVV.MousePositions[canvas.id].y = y;
    });
    $(canvas).bind('mousewheel', function(e) {
      var delta = e.originalEvent.wheelDelta/120;
      VVVV.MousePositions[canvas.id].wheel += delta;
      VVVV.MousePositions['_all'].wheel += delta;
    });
    $(canvas).bind('DOMMouseScroll', function(e) {
      var delta = -e.originalEvent.detail/3;
      VVVV.MousePositions[canvas.id].wheel += delta;
      VVVV.MousePositions['_all'].wheel += delta;
    })
    $(canvas).mousedown(function(e) {
      switch (e.which) {
        case 1: VVVV.MousePositions['_all'].lb = 1; VVVV.MousePositions[canvas.id].lb = 1; break;
        case 2: VVVV.MousePositions['_all'].mb = 1; VVVV.MousePositions[canvas.id].mb = 1; break;
        case 3: VVVV.MousePositions['_all'].rb = 1; VVVV.MousePositions[canvas.id].rb = 1; break;
      }
    });
    $(canvas).mouseup(function(e) {
      switch (e.which) {
        case 1: VVVV.MousePositions['_all'].lb = 0; VVVV.MousePositions[canvas.id].lb = 0; break;
        case 2: VVVV.MousePositions['_all'].mb = 0; VVVV.MousePositions[canvas.id].mb = 0; break;
        case 3: VVVV.MousePositions['_all'].rb = 0; VVVV.MousePositions[canvas.id].rb = 0; break;
      }
    });
  }
  
  this.getContexts = function() {
    if (!this.invisiblePins["Descriptive Name"])
      return;
    var selector = this.invisiblePins["Descriptive Name"].getValue(0);
    var targetElement = $(selector).get(0);
    if (!targetElement || targetElement.nodeName!='CANVAS') {
      var w = parseInt(bufferWidthIn.getValue(0));
      var h = parseInt(bufferHeightIn.getValue(0));
      w = w > 0 ? w : 512;
      h = h > 0 ? h : 512;
      canvas = $('<canvas width="'+w+'" height="'+h+'" id="vvvv-js-generated-renderer-'+(new Date().getTime())+'" class="vvvv-js-generated-renderer"></canvas>');
      if (!targetElement) targetElement = 'body';
      $(targetElement).append(canvas);
    }
    else
      canvas = $(targetElement);
    
    if (!canvas)
      return;
      
    attachMouseEvents();

    try {
      canvasCtxt = canvas.get(0).getContext("experimental-webgl", {preserveDrawingBuffer: true});
      canvasCtxt.viewportWidth = parseInt(canvas.get(0).width);
      canvasCtxt.viewportHeight = parseInt(canvas.get(0).height);
    } catch (e) {
      console.log(e);
    }
    this.ctxt = canvasCtxt;

    if (ex9Out.isConnected() && this.renderContexts && this.renderContexts[0]) {
      this.ctxt = this.renderContexts[0];
      
      gl = this.ctxt;
      
      bbufFramebuffer = gl.createFramebuffer();
      gl.bindFramebuffer(gl.FRAMEBUFFER, bbufFramebuffer);
      bbufFramebuffer.width = canvas.get(0).width;
      bbufFramebuffer.height = canvas.get(0).height;

      bbufTexture = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, bbufTexture);
      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);

      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, bbufFramebuffer.width, bbufFramebuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
      gl.generateMipmap(gl.TEXTURE_2D);

      var renderbuffer = gl.createRenderbuffer();
      gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
      gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, bbufFramebuffer.width, bbufFramebuffer.height);

      gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, bbufTexture, 0);
      gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, renderbuffer);

      gl.bindTexture(gl.TEXTURE_2D, null);
      gl.bindRenderbuffer(gl.RENDERBUFFER, null);
      gl.bindFramebuffer(gl.FRAMEBUFFER, null);

    }
    else {
      if (this.renderContexts && this.renderContexts[0]) {
        this.renderContexts[0].deleteTexture(bbufTexture);
        bbufTexture = undefined;
        // TODO: destroy framebuffer resources ...
      }
    }
    
    if (!this.ctxt)
      return;
      
    // doing this afterwards, so we can use these values in the patch for checking, if webgl context was set up correctly
    width = parseInt(canvas.get(0).width);
    height = parseInt(canvas.get(0).height);
    
    // create default white texture
    
    gl = this.ctxt;
 
    var pixels = new Uint8Array([255, 255, 255]);
    gl.DefaultTexture = {};
    gl.DefaultTexture['2D'] = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_2D, gl.DefaultTexture['2D']);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, pixels);
    gl.bindTexture(gl.TEXTURE_2D, null);
    
    gl.DefaultTexture['CUBE'] = gl.createTexture();
    gl.bindTexture(gl.TEXTURE_CUBE_MAP, gl.DefaultTexture['CUBE']);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
    gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_X, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, pixels);
    gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_X, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, pixels);
    gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Y, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, pixels);
    gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, pixels);
    gl.texImage2D(gl.TEXTURE_CUBE_MAP_POSITIVE_Z, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, pixels);
    gl.texImage2D(gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, gl.RGB, 1, 1, 0, gl.RGB, gl.UNSIGNED_BYTE, pixels);
    gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
    
    // this is to ensure that all the input pins get evaluated, if the gl context has been set after the node creation
    this.inputPins["Layers"].markPinAsChanged();
    clearIn.markPinAsChanged();
    bgColIn.markPinAsChanged();
    viewIn.markPinAsChanged();
    projIn.markPinAsChanged();
    
  }
  
  this.destroy = function() {
    $(canvas).remove();
  }
  
  var initialized = false;

  this.evaluate = function() {
    gl = this.ctxt;
    
    if (this.invisiblePins["Descriptive Name"].pinIsChanged() || this.contextChanged) {
      if (canvasCtxt && $(canvasCtxt.canvas).hasClass('vvvv-js-generated-renderer'))
        $(canvasCtxt.canvas).remove();
      this.getContexts();
      if (this.inputPins["Layers"].isConnected())
        this.inputPins["Layers"].links[0].fromPin.connectionChanged();
    }
    
    if (!initialized) {
      bufferWidthOut.setValue(0, width);
      bufferHeightOut.setValue(0, height);
      initialized = true;
    }
    
    if (gl==undefined)
      return;
    
    if (bufferWidthIn.pinIsChanged() && !(this.renderContexts && this.renderContexts[0])) {
      var w = parseInt(bufferWidthIn.getValue(0));
      if (w>0) {
        width = w;
        $(canvasCtxt.canvas).attr('width', width);
        bufferWidthOut.setValue(0, width);
      }
    }
    if (bufferHeightIn.pinIsChanged() && !(this.renderContexts && this.renderContexts[0])) {
      var h = parseInt(bufferHeightIn.getValue(0));
      if (h>0) {
        height = h;
        $(canvasCtxt.canvas).attr('height', height);
        bufferHeightOut.setValue(0, height);
      }
    }
    
    if (this.renderContexts && this.renderContexts[0] && gl==this.renderContexts[0]) {
      gl.bindFramebuffer(gl.FRAMEBUFFER, bbufFramebuffer);
    }
    else {
      gl.bindFramebuffer(gl.FRAMEBUFFER, null);
    }
    
    if (true) {//bgColIn.pinIsChanged()) {
      var col = _(bgColIn.getValue(0).split(',')).map(function(e) {
        return parseFloat(e);
      });
      gl.clearColor(col[0], col[1], col[2], col[3]);
      gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    }
    
    if (true) {//enableDepthBufIn.pinIsChanged()) {
      if (enableDepthBufIn.getValue(0)=='NONE')
        gl.disable(gl.DEPTH_TEST);
      else
        gl.enable(gl.DEPTH_TEST);
    }
  
    if (projIn.pinIsChanged()) {
      if (projIn.isConnected()) {
        pMatrix = mat4.create();
        mat4.set(projIn.getValue(0), pMatrix);
        mat4.scale(pMatrix, [1, 1, -1]);
      }
      else {
        pMatrix = mat4.create();
        mat4.ortho(-1, 1, -1, 1, -100, 100, pMatrix);
        mat4.scale(pMatrix, [1, 1, -1]);
      }
      if (this.renderContexts && this.renderContexts[0]) // flip the output texture, if connected to downstream renderer
        mat4.scale(pMatrix, [1, -1, 1]);
    }
    if (viewIn.pinIsChanged()) {
      vMatrix = viewIn.getValue(0);
    }
    
    if (this.contextChanged) { // don't render anything, if the context changed in this frame. will only give warnings...
      this.contextChanged = false;
      return
    }
    
    gl.viewport(0, 0, width, height);
      
    var currentShaderProgram = null;
    var currentRenderState = null;
    var currentMesh = null;
    
    if (this.inputPins["Layers"].isConnected()) {
      var layers = this.inputPins["Layers"].values;
      for (var i=0; i<layers.length; i++) {
        layer = layers[i];
        
        if (layer.shader==undefined) // if it's an empty layer (e.g. created by IOBox (Node))
          continue;
        
        if (currentShaderProgram!=layer.shader.shaderProgram) {
          gl.useProgram(layer.shader.shaderProgram);
          gl.uniformMatrix4fv(layer.shader.uniformSpecs[layer.shader.uniformSemanticMap["PROJECTION"]].position, false, pMatrix);
          gl.uniformMatrix4fv(layer.shader.uniformSpecs[layer.shader.uniformSemanticMap["VIEW"]].position, false, vMatrix);
          
        }
        
        var renderState = layer.renderState;
        if (!renderState)
          renderState = defaultWebGlRenderState;
        if (renderState!=currentRenderState)
          renderState.apply(gl);
        
        if (layer.mesh != currentMesh || layer.shader.shaderProgram != currentShaderProgram) {
          gl.bindBuffer(gl.ARRAY_BUFFER, layer.mesh.vertexBuffer.vbo);
          _(layer.mesh.vertexBuffer.subBuffers).each(function(b) {
            if (!layer.shader.attributeSpecs[layer.shader.attribSemanticMap[b.usage]] || layer.shader.attributeSpecs[layer.shader.attribSemanticMap[b.usage]].position==-1)
              return;
            gl.enableVertexAttribArray(layer.shader.attributeSpecs[layer.shader.attribSemanticMap[b.usage]].position);
            gl.vertexAttribPointer(layer.shader.attributeSpecs[layer.shader.attribSemanticMap[b.usage]].position, b.size, gl.FLOAT, false, 0, b.offset);
          });
          
          gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, layer.mesh.indexBuffer);
        }
        
        var uniformCount = layer.uniformNames.length;
        var textureIdx = 0;
        for (var j=0; j<uniformCount; j++) {
          var u = layer.uniforms[layer.uniformNames[j]];

          if (u.value==undefined)
            continue;
          if (i>0 && layer.shader.shaderProgram==currentShaderProgram && layers[i-1].uniforms[layer.uniformNames[j]] && u.value==layers[i-1].uniforms[layer.uniformNames[j]].value)
            continue;
          start = new Date().getTime(); 
          switch (u.uniformSpec.type) {
            case "mat": gl['uniformMatrix'+u.uniformSpec.dimension+'fv'](u.uniformSpec.position, false, u.value); break;
            case "vec": gl['uniform'+u.uniformSpec.dimension+'fv'](u.uniformSpec.position, u.value); break;
            case "int": gl['uniform'+u.uniformSpec.dimension+'i'](u.uniformSpec.position, u.value); break;
            case "float": gl['uniform'+u.uniformSpec.dimension+'f'](u.uniformSpec.position, u.value); break;
            case "sampler":
              var tex = u.value;
              if (tex==VVVV.DefaultTexture)
                tex = gl.DefaultTexture['2D'];
              gl.activeTexture(gl['TEXTURE'+textureIdx]);
              gl.bindTexture(gl['TEXTURE_'+u.uniformSpec.dimension], tex);
              gl.uniform1i(u.uniformSpec.position, textureIdx);
              textureIdx++;
              break;
            case "samplerCube":
              var tex = u.value;
              if (tex==VVVV.DefaultTexture)
                tex = gl.DefaultTexture['CUBE'];
              gl.activeTexture(gl['TEXTURE'+textureIdx]);
              gl.bindTexture(gl.TEXTURE_CUBE_MAP, tex);
              gl.uniform1i(u.uniformSpec.position, textureIdx);
              textureIdx++;
              break;
          }
          loopstart = new Date().getTime();
        }
        
        gl.drawElements(gl[renderState.polygonDrawMode], layer.mesh.numIndices, gl.UNSIGNED_SHORT, 0);
        
        // save current states
        currentShaderProgram = layer.shader.shaderProgram;
        currentRenderState = renderState;
        currentMesh = layer.mesh;
      }
      
      gl.bindTexture(gl.TEXTURE_2D, null);
      gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
      
    }
    
    if (this.renderContexts && this.renderContexts[0]) {
      gl.bindTexture(gl.TEXTURE_2D, bbufTexture);
      gl.generateMipmap(gl.TEXTURE_2D);
      gl.bindTexture(this.renderContexts[0].TEXTURE_2D, null);
    }
    
    ex9Out.setValue(0, bbufTexture);
  }

}
VVVV.Nodes.RendererWebGL.prototype = new VVVV.Core.Node();


/*
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 NODE: DefineEffect (DX9)
 Author(s): Matthias Zauner
 Original Node Author(s): Matthias Zauner
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

VVVV.Nodes.DefineEffect = function(id, graph) {
  this.constructor(id, "DefineEffect (DX9)", graph);
  
  this.auto_nil = false;
  
  this.meta = {
    authors: ['Matthias Zauner'],
    original_authors: ['Matthias Zauner'],
    credits: [],
    compatibility_issues: ['Not available in classic VVVV']
  };
  
  this.auto_evaluate = false;
  
  var nameIn = this.addInputPin("Effect Descriptor", [''], VVVV.PinTypes.String);
  var sourceCodeIn = this.addInvisiblePin("Source Code", ['#ifdef GL_ES\nprecision highp float;\n#endif\n\nuniform mat4 tW : WORLD;\nuniform mat4 tV : VIEW;\nuniform mat4 tP : PROJECTION;\n\nuniform vec4 Color : COLOR = {1.0, 1.0, 1.0, 1.0};\nuniform sampler2D Texture;\nuniform mat4 Texture_Transform;\nuniform float Alpha = 1.0;\n\nvarying vec2 vs2psTexCd;\n\nvertex_shader:\n\nattribute vec3 PosO : POSITION;\nattribute vec2 TexCd : TEXCOORD0;\n\nvoid main(void) {\n  gl_Position = tP * tV * tW * vec4(PosO, 1.0);\n  vs2psTexCd = (Texture_Transform * vec4(TexCd, 0, 1)).xy;\n}\n\n\nfragment_shader:\n\nvoid main(void) {\n  gl_FragColor = Color * texture2D(Texture, vs2psTexCd) * vec4(1.0, 1.0, 1.0, Alpha);\n}'], VVVV.PinTypes.String);
  
  var currentName = '';
  var w; // the UI window
  
  this.evaluate = function() {
    if (nameIn.getValue(0)!='') {
      if (nameIn.pinIsChanged()) {
        var descriptor = nameIn.getValue(0);
        if (descriptor=='')
          return;
        if (currentName=='') { // if is set for the first time
          if (VVVV.ShaderCodeResources["./"+descriptor+'.vvvvjs.fx']==undefined)
            VVVV.ShaderCodeResources["./"+descriptor+'.vvvvjs.fx'] = new VVVV.Types.ShaderCodeResource();
        }
        else
          VVVV.ShaderCodeResources["./"+descriptor+'.vvvvjs.fx'] = VVVV.ShaderCodeResources[currentName];
        currentName = "./"+descriptor+'.vvvvjs.fx';
        VVVV.ShaderCodeResources[currentName].definingNode = this;
        VVVV.ShaderCodeResources[currentName].setSourceCode(sourceCodeIn.getValue(0));
        if (w)
          $('#path', w.document).text((this.parentPatch.nodename || 'root')+' / '+(currentName!='' ? currentName : 'Untitled'));
      }
      
      if (sourceCodeIn.pinIsChanged()) {
        if (VVVV.ShaderCodeResources[currentName])
          VVVV.ShaderCodeResources[currentName].setSourceCode(sourceCodeIn.getValue(0));
      }
    }
  }
  
  this.openUIWindow = function() {
    w = window.open("code_editor.html", currentName+" / VVVV.js Effect Editor", "location=no, width=800, height=800, toolbar=no");
    var thatNode = this;
    window.setTimeout(function() {
      w.document.title = currentName+" / VVVV.js Effect Editor";
      var definingNodeName = thatNode.parentPatch.nodename || 'root';
      var shaderName = currentName!='' ? currentName : 'Untitled';
      $('#path', w.document).text(definingNodeName+' / '+shaderName);
      $('textarea', w.document).text(sourceCodeIn.getValue(0));
      $('#compile_button', w.document).click(function() {
        if (currentName=='') {
          thatNode.showStatus('error', 'Please provide a name for this shader first');
          return;
        }
        sourceCodeIn.setValue(0, $('textarea', w.document).val());
        if (VVVV.ShaderCodeResources[currentName].relatedNodes.length>0)
          thatNode.showStatus('notice', 'Compiling ...');
        else
          thatNode.showStatus('notice', 'No instance of this shader found. Create a node (./'+currentName+') and connect it to a Renderer (EX9) to compile.');
      });
      w.focus();
    }, 500);
  }
  
  this.showStatus = function(type, message) {
    if (w) {
      $('#status', w.document).text(message);
      $('#status', w.document).attr('class', type);
    }
  }

}
VVVV.Nodes.DefineEffect.prototype = new VVVV.Core.Node();
VVVV.js Copyright © 2012-2013 The contributors to the VVVV.js project.
Documentation generated by JSDoc 3.3.0-alpha4 on Thu May 08 2014 13:12:34 GMT+0200 (Mitteleuropäische Sommerzeit) using the DocStrap template.