/** X3DOM Runtime, http://www.x3dom.org/ 1.7.2 - 61a235203deb34329fe615cbbf21314db6ebf49f - Mon Dec 19 19:17:05 2016 +0100 *//*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

// Add some JS1.6 Array functions:
// (This only includes the non-prototype versions, because otherwise it messes up 'for in' loops)

if (!Array.forEach) {
	/*
	 *	Function: Array.forEach
	 *
	 *	Javascript array forEach() method calls a function for each element in the array.
	 * 
	 *  Parameters:
	 *
	 *   	array - The array
	 *		fun   - Function to test each element of the array
	 *      thisp - Object to use as __this__ when executing callback
	 *
	 *	Returns:
	 *
	 * 		The created array
	 */
	Array.forEach = function (array, fun, thisp) {
        var len = array.length;
        for (var i = 0; i < len; i++) {
            if (i in array) {
                fun.call(thisp, array[i], i, array);
            }
        }
    };
}

if (!Array.map) {
    Array.map = function(array, fun, thisp) {
        var len = array.length;
        var res = [];
        for (var i = 0; i < len; i++) {
            if (i in array) {
                res[i] = fun.call(thisp, array[i], i, array);
            }
        }
        return res;
    };
}

if (!Array.filter) {
    Array.filter = function(array, fun, thisp) {
        var len = array.length;
        var res = [];
        for (var i = 0; i < len; i++) {
            if (i in array) {
                var val = array[i];
                if (fun.call(thisp, val, i, array)) {
                    res.push(val);
                }
            }
        }
        return res;
    };
}

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * The Namespace container for x3dom objects.
 * @namespace x3dom
 * */
var x3dom = {
    canvases : [],

    x3dNS    : 'http://www.web3d.org/specifications/x3d-namespace',
    x3dextNS : 'http://philip.html5.org/x3d/ext',
    xsltNS   : 'http://www.w3.org/1999/XSL/x3dom.Transform',
    xhtmlNS  : 'http://www.w3.org/1999/xhtml'
};

/**
 * The x3dom.nodeTypes namespace.
 * @namespace x3dom.nodeTypes
 * */
x3dom.nodeTypes = {};

/**
 * The x3dom.nodeTypesLC namespace. Stores nodetypes in lowercase
 * @namespace x3dom.nodeTypesLC
 * */
x3dom.nodeTypesLC = {};

/**
 * The x3dom.components namespace.
 * @namespace x3dom.components
 * */
x3dom.components = {};

/** Cache for primitive nodes (Box, Sphere, etc.) */
x3dom.geoCache = [];

/** Stores information about Browser and hardware capabilities */
x3dom.caps = { PLATFORM: navigator.platform, AGENT: navigator.userAgent, RENDERMODE: "HARDWARE" };

/** Registers the node defined by @p nodeDef.

    The node is registered with the given @p nodeTypeName and @p componentName.

    @param nodeTypeName the name of the node type (e.g. Material, Shape, ...)
    @param componentName the name of the component the node type belongs to
    @param nodeDef the definition of the node type
 */
x3dom.registerNodeType = function(nodeTypeName, componentName, nodeDef) {
    //console.log("Registering nodetype [" + nodeTypeName + "] in component [" + componentName + "]");
    if (x3dom.components[componentName] === undefined) {
        x3dom.components[componentName] = {};
    }
    nodeDef._typeName = nodeTypeName;
    nodeDef._compName = componentName;
    x3dom.components[componentName][nodeTypeName] = nodeDef;
    x3dom.nodeTypes[nodeTypeName] = nodeDef;
    x3dom.nodeTypesLC[nodeTypeName.toLowerCase()] = nodeDef;
};

/** Test if node is registered X3D element */
x3dom.isX3DElement = function(node) {
    // x3dom.debug.logInfo("node=" + node + "node.nodeType=" + node.nodeType + ", node.localName=" + node.localName + ", ");
    var name = (node.nodeType === Node.ELEMENT_NODE && node.localName) ? node.localName.toLowerCase() : null;
    return (name && (x3dom.nodeTypes[node.localName] || x3dom.nodeTypesLC[name] ||
            name == "x3d" || name == "websg" || name == "route"));
};

/*
 *	Function: x3dom.extend
 *
 *	Returns a prototype object suitable for extending the given class
 *	_f_. Rather than constructing a new instance of _f_ to serve as
 *	the prototype (which unnecessarily runs the constructor on the created
 *	prototype object, potentially polluting it), an anonymous function is
 *	generated internally that shares the same prototype:
 *
 *	Parameters:
 *   	f - Method f a constructor
 *
 *	Returns:
 * 		A suitable prototype object
 *
 *	See Also:
 *		Douglas Crockford's essay on <prototypical inheritance at http://javascript.crockford.com/prototypal.html>.
 */
// TODO; unify with defineClass, which does basically the same
x3dom.extend = function(f) {
  function G() {}
  G.prototype = f.prototype || f;
  return new G();
};

/**
 * Function x3dom.getStyle
 * 
 * Computes the value of the specified CSS property <tt>p</tt> on the
 * specified element <tt>e</tt>.
 *
 * Parameters:
 *     oElm       - The element on which to compute the CSS property
 *     strCssRule - The name of the CSS property
 *
 *	Returns:
 *
 * 		The computed value of the CSS property
 */
x3dom.getStyle = function(oElm, strCssRule) {
    var strValue = "";
    var style = document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(oElm, null) : null;
    if (style) {
        strValue = style.getPropertyValue(strCssRule);
    }
    else if(oElm.currentStyle){
        strCssRule = strCssRule.replace(/\-(\w)/g, function (strMatch, p1){ return p1.toUpperCase(); });
        strValue = oElm.currentStyle[strCssRule];
    }
    return strValue;
};


/** Utility function for defining a new class.

    @param parent the parent class of the new class
    @param ctor the constructor of the new class
    @param methods an object literal containing the methods of the new class
    @return the constructor function of the new class
  */
function defineClass(parent, ctor, methods) {
    if (parent) {
        function Inheritance() {}
        Inheritance.prototype = parent.prototype;

        ctor.prototype = new Inheritance();
        ctor.prototype.constructor = ctor;
        ctor.superClass = parent;
    }
    if (methods) {
        for (var m in methods) {
            ctor.prototype[m] = methods[m];
        }
    }
    return ctor;
}

/** Utility function for testing a node type.

    @param object the object to test
    @param clazz the type of the class
    @return true or false
  */
x3dom.isa = function(object, clazz) {
    /*
	if (!object || !object.constructor || object.constructor.superClass === undefined) {
		return false;
	}
    if (object.constructor === clazz) {
        return true;
    }

    function f(c) {
        if (c === clazz) {
            return true;
        }
        if (c.prototype && c.prototype.constructor && c.prototype.constructor.superClass) {
            return f(c.prototype.constructor.superClass);
        }
        return false;
    }
    return f(object.constructor.superClass);
    */
    return (object instanceof clazz);
};


/// helper
x3dom.getGlobal = function () {
    return (function () {
        return this;
    }).call(null);
};


/**
 * Load javascript file either by performing an synchronous jax request
 * an eval'ing the response or by dynamically creating a <script> tag.
 *
 * CAUTION: This function is a possible source for Cross-Site
 *          Scripting Attacks.
 *
 * @param  src  The location of the source file relative to
 *              path_prefix. If path_prefix is omitted, the
 *              current directory (relative to the HTML document)
 *              is used instead.
 * @param  path_prefix A prefix URI to add to the resource to be loaded.
 *                     The URI must be given in normalized path form ending in a
 *                     path separator (i.e. src/nodes/). It can be in absolute
 *                     URI form (http://somedomain.tld/src/nodes/)
 * @param  blocking    By default the lookup is done via blocking jax request.
 *                     set to false to use the script i
 */
x3dom.loadJS = function(src, path_prefix, blocking) {
    blocking = (blocking === false) ? blocking : true;   // default to true

    if (blocking) {
        var url = (path_prefix) ? path_prefix.trim() + src : src;
        var req = new XMLHttpRequest();

        if (req) {
            // third parameter false = synchronous/blocking call
            // need this to load the JS before onload completes
            req.open("GET", url, false);
            req.send(null); // blocking

            // maybe consider global eval
            // http://perfectionkills.com/global-eval-what-are-the-options/#indirect_eval_call_examples
            eval(req.responseText);
        }
    } else {
        var head = document.getElementsByTagName('HEAD').item(0);
        var script = document.createElement("script");
        var loadpath = (path_prefix) ? path_prefix.trim() + src : src;
        if (head) {
            x3dom.debug.logError("Trying to load external JS file: " + loadpath);
            //alert("Trying to load external JS file: " + loadpath);
            script.type = "text/javascript";
            script.src = loadpath;
            head.appendChild(script);
        } else {
            alert("No document object found. Can't load components!");
            //x3dom.debug.logError("No document object found. Can't load components");
        }
    }
};

// helper
function array_to_object(a) {
  var o = {};
  for(var i=0;i<a.length;i++) {
    o[a[i]]='';
  }
  return o;
}

/**
 * Provides requestAnimationFrame in a cross browser way.
 * https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/sdk/demos/common/webgl-utils.js
 */
window.requestAnimFrame = (function() {
	return window.requestAnimationFrame ||
    	   window.webkitRequestAnimationFrame ||
           window.mozRequestAnimationFrame ||
           window.oRequestAnimationFrame ||
           window.msRequestAnimationFrame ||
           function(/* function FrameRequestCallback */ callback, /* DOMElement Element */ element) {
             window.setTimeout(callback, 16);
           };
})();

/**
 * Toggle full-screen mode
 */
x3dom.toggleFullScreen = function() {
    if (document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen) {
        if (document.cancelFullScreen) {
            document.cancelFullScreen();
        }
        else if (document.mozCancelFullScreen) {
            document.mozCancelFullScreen();
        }
        else if (document.webkitCancelFullScreen) {
            document.webkitCancelFullScreen();
        }
    }
    else {
        var docElem = document.documentElement;
        if (docElem.requestFullScreen) {
            docElem.requestFullScreen();
        }
        else if (docElem.mozRequestFullScreen) {
            docElem.mozRequestFullScreen();
        }
        else if (docElem.webkitRequestFullScreen) {
            docElem.webkitRequestFullScreen();
        }
    }
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

x3dom.debug = {

    INFO:       "INFO",
    WARNING:    "WARNING",
    ERROR:      "ERROR",
    EXCEPTION:  "EXCEPTION",
    
	// determines whether debugging/logging is active. If set to "false"
	// no debugging messages will be logged.
	isActive: false,

    // stores if firebug is available
    isFirebugAvailable: false,
    
    // stores if the x3dom.debug object is initialized already
    isSetup: false,
	
	// stores if x3dom.debug object is append already (Need for IE integration)
	isAppend: false,

    // stores the number of lines logged
    numLinesLogged: 0,
    
    // the maximum number of lines to log in order to prevent
    // the browser to slow down
    maxLinesToLog: 10000,

	// the container div for the logging messages
	logContainer: null,
    
    /** @brief Setup the x3dom.debug object.

        Checks for firebug and creates the container div for the logging 
		messages.
      */
    setup: function() {
		// If debugging is already setup simply return
        if (x3dom.debug.isSetup) { return; }

		// Check for firebug console
        try {
            if (window.console.firebug !== undefined) {
                x3dom.debug.isFirebugAvailable = true;           
            }
        }
        catch (err) {
            x3dom.debug.isFirebugAvailable = false;
        }
        
		// 
		x3dom.debug.setupLogContainer();

        // setup should be setup only once, thus store if we done that already
        x3dom.debug.isSetup = true;
    },
	
	/** @brief Activates the log
      */
	activate: function(visible) {
		x3dom.debug.isActive = true;
		
        //var aDiv = document.createElement("div");
        //aDiv.style.clear = "both";
        //aDiv.appendChild(document.createTextNode("\r\n"));
        //aDiv.style.display = (visible) ? "block" : "none";
        x3dom.debug.logContainer.style.display = (visible) ? "block" : "none";
		
		//Need this HACK for IE/Flash integration. IE don't have a document.body at this time when starting Flash-Backend
		if(!x3dom.debug.isAppend) {
			if(navigator.appName == "Microsoft Internet Explorer") {
				//document.documentElement.appendChild(aDiv);
				x3dom.debug.logContainer.style.marginLeft = "8px";
				document.documentElement.appendChild(x3dom.debug.logContainer);
			}else{
				//document.body.appendChild(aDiv);
				document.body.appendChild(x3dom.debug.logContainer);
			}
			x3dom.debug.isAppend = true;
		}
	},

	/** @brief Inserts a container div for the logging messages into the HTML page
      */
	setupLogContainer: function() {
		x3dom.debug.logContainer = document.createElement("div");
		x3dom.debug.logContainer.id = "x3dom_logdiv";
		x3dom.debug.logContainer.setAttribute("class", "x3dom-logContainer");
		x3dom.debug.logContainer.style.clear = "both";
		//document.body.appendChild(x3dom.debug.logContainer);
	},

	/** @brief Generic logging function which does all the work.

		@param msg the log message
		@param logType the type of the log message. One of INFO, WARNING, ERROR 
					   or EXCEPTION.
      */
    doLog: function(msg, logType) {

		// If logging is deactivated do nothing and simply return
		if (!x3dom.debug.isActive) { return; }

		// If we have reached the maximum number of logged lines output
		// a warning message
		if (x3dom.debug.numLinesLogged === x3dom.debug.maxLinesToLog) {
			msg = "Maximum number of log lines (=" + x3dom.debug.maxLinesToLog + 
				  ") reached. Deactivating logging...";
		}

		// If the maximum number of log lines is exceeded do not log anything
		// but simply return 
		if (x3dom.debug.numLinesLogged > x3dom.debug.maxLinesToLog) { return; }

		// Output a log line to the HTML page
		var node = document.createElement("p");
		node.style.margin = 0;
        switch (logType) {
            case x3dom.debug.INFO:
                node.style.color = "#00ff00";
                break;
            case x3dom.debug.WARNING:
                node.style.color = "#cd853f";
                break;
            case x3dom.debug.ERROR:
                node.style.color = "#ff4500";
                break;
            case x3dom.debug.EXCEPTION:
                node.style.color = "#ffff00";
                break;
            default: 
                node.style.color = "#00ff00";
                break;
        }
		
		// not sure if try/catch solves problem http://sourceforge.net/apps/trac/x3dom/ticket/52
		// but due to no avail of ATI gfxcard can't test
        try {
			node.innerHTML = logType + ": " + msg;
			x3dom.debug.logContainer.insertBefore(node, x3dom.debug.logContainer.firstChild);
        } catch (err) {
			if (window.console.firebug !== undefined) {
				window.console.warn(msg);
			}
        }
        
		// Use firebug's console if available
        if (x3dom.debug.isFirebugAvailable) {
            switch (logType) {
                case x3dom.debug.INFO:
                    window.console.info(msg);
                    break;
                case x3dom.debug.WARNING:
                    window.console.warn(msg);
                    break;
                case x3dom.debug.ERROR:
                    window.console.error(msg);
                    break;
                case x3dom.debug.EXCEPTION:
                    window.console.debug(msg);
                    break;
                default: 
                    break;
            }
        }
        
		x3dom.debug.numLinesLogged++;
    },
    
    /** Log an info message. */
    logInfo: function(msg) {
        x3dom.debug.doLog(msg, x3dom.debug.INFO);
    },
    
    /** Log a warning message. */
    logWarning: function(msg) {
        x3dom.debug.doLog(msg, x3dom.debug.WARNING);
    },
    
    /** Log an error message. */
    logError: function(msg) {
        x3dom.debug.doLog(msg, x3dom.debug.ERROR);
    },
    
    /** Log an exception message. */
    logException: function(msg) {
        x3dom.debug.doLog(msg, x3dom.debug.EXCEPTION);
    },

    /** Log an assertion. */
	assert: function(c, msg) {
		if (!c) {
			x3dom.debug.doLog("Assertion failed in " + 
                    x3dom.debug.assert.caller.name + ': ' + 
                    msg, x3dom.debug.ERROR);
		}
	},
	
	/**
	 Checks the type of a given object.
	 
	 @param obj the object to check.
	 @returns one of; "boolean", "number", "string", "object",
	  "function", or "null".
	*/
	typeOf: function (obj) {
		var type = typeof obj;
		return type === "object" && !obj ? "null" : type;
	},

	/**
	 Checks if a property of a specified object has the given type.
	 
	 @param obj the object to check.
	 @param name the property name.
	 @param type the property type (optional, default is "function").
	 @returns true if the property exists and has the specified type,
	  otherwise false.
	*/
	exists: function (obj, name, type) {
		type = type || "function";
		return (obj ? this.typeOf(obj[name]) : "null") === type;
	},
	
	/**
	 Dumps all members of the given object.
	*/
	dumpFields: function (node) {
		var str = "";
		for (var fName in node) {
			str += (fName + ", ");
		}
		str += '\n';
		x3dom.debug.logInfo(str);
		return str;
	}
};

// Call the setup function to... umm, well, setup x3dom.debug
x3dom.debug.setup();

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

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

x3dom.arc = {};
x3dom.arc.instance = null;

x3dom.arc.Limits = function(min, max, initial)
{
    this._min = min;
    this._max = max;

    this.getValue = function(value)
    {
        value = this._min + (this._max - this._min) * value;
        return this._max >= value ? (this._min <= value ? value : this._min ) : this._max;
    };
};

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

x3dom.arc.ARF = function(name, min, max, dirFac, factorGetterFunc, factorSetterFunc, getterFunc, setterFunc)
{
    this._name = name;
    //start with average
    this._stateValue = [ 0.5, 0.5 ];

    this._limits = new x3dom.arc.Limits(min, max);
    this._factorGetterFunc = factorGetterFunc;
    this._factorSetterFunc = factorSetterFunc;
    this._setterFunc = setterFunc;
    this._getterFunc = getterFunc;
    this._dirFac = dirFac;

    this.getFactor = function()
    {
        return this._factorGetterFunc();
    };

    this.update = function(state, step)
    {
        var stateVal = this._stateValue[state] + step * this._dirFac;
        this._stateValue[state] =  0 <= stateVal ? ( 1 >= stateVal ? stateVal : 1 ) : 0;
        this._setterFunc(this._limits.getValue(this._stateValue[state]));

        //console.log(this.name +" "+this._factorGetterFunc() +" *  " + step +" "+ this._stateValue[state] +" "+ state);
    };

    this.reset = function()
    {
        this._stateValue[0] = 0.5;
        this._stateValue[1] = 0.5;
    };
};

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

x3dom.arc.AdaptiveRenderControl = defineClass(
    null,
    function(scene)
    {
        x3dom.arc.instance = this;

        this._scene = scene;
        this._targetFrameRate = [];
        this._targetFrameRate[0] = this._scene._vf.minFrameRate;
        this._targetFrameRate[1] = this._scene._vf.maxFrameRate;

        this._currentState = 0;

        var that = this;
        var environment = that._scene.getEnvironment();

        this._arfs = [];

        this._arfs.push(
            new x3dom.arc.ARF("smallFeatureCulling",
                0, 10, -1,
                function()
                {
                    return environment._vf.smallFeatureFactor;
                },
                function(value)
                {
                    environment._vf.smallFeatureFactor = value;
                },
                function()
                {
                    return  environment._vf.smallFeatureThreshold;
                },
                function(value)
                {
                    environment._vf.smallFeatureThreshold = value;
                }
            )
        );

        this._arfs.push(
            new x3dom.arc.ARF("lowPriorityCulling",
                0,100,1,
                function()
                {
                    return environment._vf.lowPriorityFactor;
                },
                function(value)
                {
                    environment._vf.lowPriorityFactor = value;
                },
                function()
                {
                    return environment._vf.lowPriorityThreshold * 100;
                },
                function(value)
                {
                    environment._vf.lowPriorityThreshold = value / 100;
                }
            )
        );

        this._arfs.push(
            new x3dom.arc.ARF("tessellationDetailCulling",
                1,12,-1,
                function()
                {
                    return environment._vf.tessellationErrorFactor;
                },
                function(value)
                {
                    environment._vf.tessellationErrorFactor = value;
                },
                //@todo: this factor is a static member of PopGeo... should it belong to scene instead?
                function()
                {
                    return environment.tessellationErrorThreshold;
                },
                function(value)
                {
                    environment.tessellationErrorThreshold = value;
                }
            )
        );

        this._stepWidth = 0.1;
    },
    {
        update : function(state, fps) // state: 0 = static, 1 : moving
        {
            this._currentState = state;
            var delta =  fps - this._targetFrameRate[state];

            //to prevent flickering
            this._stepWidth = Math.abs(delta) > 10 ? 0.1 : 0.01;

            /*if( (delta > 0 && state == 1) || (delta < 0 && state == 0))
                return;
            */

            var factorSum = 0;
            var normFactors = [];

            //normalize factors
            var i, n = this._arfs.length;

            for(i = 0; i < n; ++i)
            {
                normFactors[i] = this._arfs[i].getFactor();
                if(normFactors[i] > 0)
                    factorSum += normFactors[i];
            }

            var dirFac = delta < 0 ? -1 : 1;
            for(i = 0; i < n; ++i)
            {
                if(normFactors[i] > 0)
                {
                    normFactors[i] /= factorSum;
                    this._arfs[i].update(state, this._stepWidth * normFactors[i] * dirFac);
                }
            }
        },

        reset: function()
        {
            for( var i = 0, n = this._arfs.length; i < n; ++i)
            {
                this._arfs[i].reset();
            }
        }
    }
);

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


/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */
 
 
 /**
 * Class: x3dom.DownloadManager
 *
 * Simple priority-based download manager.
 * Before objects of priority n+1 are available,
 * all objects of priority n must have been already delivered.
 * The highest priority key is 0.
 * 
 */

/// a small Request class
x3dom.Request = function(url, onloadCallback, priority){
	this.url 	  		     = url;	
	this.priority 		   = priority;
	this.xhr 	  		     = new XMLHttpRequest();
	this.onloadCallbacks = [onloadCallback];
	
	var self = this;

	this.xhr.onload = function() {		
		if (x3dom.DownloadManager.debugOutput) {
			x3dom.debug.logInfo('Download manager received data for URL \'' + self.url + '\'.');
		}
		
		--x3dom.DownloadManager.activeDownloads;
	
    if ((x3dom.DownloadManager.stallToKeepOrder === false ) || (x3dom.DownloadManager.resultGetsStalled(self.priority) === false)) {
      var i;
      for (i = 0; i < self.onloadCallbacks.length; ++i) {			
        self.onloadCallbacks[i](self.xhr.response);
      }
      
      x3dom.DownloadManager.removeDownload(self);
      
      x3dom.DownloadManager.updateStalledResults();
    }
    else if (x3dom.DownloadManager.debugOutput) {
			x3dom.debug.logInfo('Download manager stalled downloaded result for URL \'' + self.url + '\'.');
		}
    
		x3dom.DownloadManager.tryNextDownload();
	};
};


x3dom.Request.prototype.send = function() {
	this.xhr.open('GET', encodeURI(this.url), true); //asynchronous	
	
	//at the moment, ArrayBuffer is the only possible return type
	this.xhr.responseType = 'arraybuffer';
	
	this.xhr.send(null);
	
	if (x3dom.DownloadManager.debugOutput) {
		x3dom.debug.logInfo('Download manager posted XHR for URL \'' + this.url + '\'.');
	}
};


x3dom.DownloadManager = {

    requests 		     : [], //map priority->[requests]

    maxDownloads 	   : 6,  //number of max. concurrent downloads

    activeDownloads  : 0,  //number of active downloads

    debugOutput		   : false,

    stallToKeepOrder : false,


    toggleDebugOutput : function(flag) {
        this.debugOutput = flag;	
    },


    toggleStrictReturnOrder : function(flag) {
      //@todo: this is not working properly yet!
      this.stallToKeepOrder = false;
      //this.stallToKeepOrder = flag;
    },


    removeDownload : function(req) {
        var i, j;
        var done = false;

        for (i = 0; i < this.requests.length && !done; ++i) {	
            if (this.requests[i]){			
                for (j = 0; j < this.requests[i].length; ++j) {
                    if (this.requests[i][j] === req) {
                        this.requests[i].splice(j, 1);
                        done = true;
                        break;
                    }
                }
            }
        }
    },


    tryNextDownload : function() {
        var firstRequest;
        var i, j;
            
        //if there are less then maxDownloads running, start a new one,
        //otherwise do nothing
        if (this.activeDownloads < this.maxDownloads) {	
            //remove first queue element, if any
            for (i = 0; i < this.requests.length && !firstRequest; ++i) {
                //find the request queue with the highest priority
                if (this.requests[i]) {
                    //remove first unsent request from the queue, if any
                    for (j = 0; j < this.requests[i].length; ++j) {					
                        if (this.requests[i][j].xhr.readyState === XMLHttpRequest.UNSENT) {
                            firstRequest = this.requests[i][j];
                            break;						
                        }
                    }
                }
            }
            
            if (firstRequest) {		
                firstRequest.send();			
                
                ++this.activeDownloads;
            }
        }
    },


    resultGetsStalled : function(priority) {
      var i;
      
      for (i = 0; i < priority; ++i) {
        if (this.requests[i] && this.requests[i].length) {
          return true;
        }
      }
      
      return false;
    },


    updateStalledResults : function() {
      if (x3dom.DownloadManager.stallToKeepOrder) {  
        var i, j, k;
        var req, pendingRequestFound = false;
        
        for (i = 0; i < this.requests.length && !pendingRequestFound; ++i) {
        
          if (this.requests[i]) {
            for (j = 0; j < this.requests[i].length; ++j) {
              //check if there is a stalled result and relase it, if so
              req = this.requests[i][j];
              
              if (req.xhr.readyState === XMLHttpRequest.DONE) {
              
                if (x3dom.DownloadManager.debugOutput) {
                  x3dom.debug.logInfo('Download manager releases stalled result for URL \'' + req.url + '\'.');
                }
                
                for (k = 0; k < req.onloadCallbacks.length; ++k) {
                  req.onloadCallbacks[k](req.xhr.response);
                }
                
                //remove request from the list
                this.requests[i].splice(j, 1);          
              }
              //if there is an unfinished result, stop releasing results of lower priorities
              else {
                pendingRequestFound = true;	
              }
            }
          }
          
        }
      }
    },


    /**
     * Requests a download from the given URL, with the given onloadCallback and priority.
     * The callback function will be invoked with a JSON object as parameter, where the
     * 'arrayBuffer' member contains a reference to the requested data and the 'url' member
     * contains the original user-given URL of the object.
     * 
     * If there is no data from the given url available, but there is already a registered request
     * for it, the new callback is just appended to the old registered request object. Note that,
     * in this special case, the priority of the old request is not changed, i.e. the priority
     * of the new request to the same url is ignored.
     */
    get : function(urls, onloadCallbacks, priorities) {
      var i, j, k, r;
      var found = false;
      var url, onloadCallback, priority;
      
      if (urls.length !== onloadCallbacks.length || urls.length !== priorities.length)
      {
        x3dom.debug.logError('DownloadManager: The number of given urls, onload callbacks and priorities is not equal. Ignoring requests.');
        return;
      }
      
      //insert requests
      for (k = 0; k < urls.length; ++k) {
        if (!onloadCallbacks[k] === undefined || !priorities[k] === undefined) {
          x3dom.debug.logError('DownloadManager: No onload callback and / or priority specified. Ignoring request for \"' + url + '\"');
          continue;
        }
        else {
          url            = urls[k];
          onloadCallback = onloadCallbacks[k];
          priority       = priorities[k];
          
          //enqueue request priority-based or append callback to a matching active request		
          
          //check if there is already an enqueued or sent request for the given url
          for (i = 0; i < this.requests.length && !found; ++i) {
            if (this.requests[i]) {			
              for (j = 0; j < this.requests[i].length; ++j) {
                if (this.requests[i][j].url === url) {
                  this.requests[i][j].onloadCallbacks.push(onloadCallback);
                  
                  if (x3dom.DownloadManager.debugOutput) {
                    x3dom.debug.logInfo('Download manager appended onload callback for URL \'' + url + '\' to a registered request using the same URL.');
                  }
                  
                  found = true;
                  break;
                }
              }
            }
          }
        
          if (!found) {
            r = new x3dom.Request(url, onloadCallback, priority);
            
            if (this.requests[priority] != undefined) {
              this.requests[priority].push(r);
            }
            else {
              this.requests[priority] = [r];
            }
          }
        }
      }
      
      //try to download data
      for (i = 0; i < urls.length && this.activeDownloads < this.maxDownloads; ++i) {
        this.tryNextDownload();    
      }
    },

    abortAllDownloads : function()
    {
        var request;
        
        for ( var i = 0; i < this.requests.length; i++ )
        {
            if ( this.requests[ i ] != undefined )
            {
                for ( var j = 0; j < this.requests[ i ].length; j++ )
                {
                    //Get Request
                    request = this.requests[i][j];
                    
                    //Abort XHR
                    request.xhr.abort();
                    
                    //Remove Request
                    this.removeDownload( request );
                }
            }
        }
    }
	
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

 /**
 * Class: x3dom.RequestManager
 */

x3dom.RequestManager = {};

/**
 *
 * @type {number}
 */
x3dom.RequestManager.requests = [];

/**
 *
 * @type {number}
 */
x3dom.RequestManager.maxParallelRequests = 40;

/**
 *
 * @type {number}
 */
x3dom.RequestManager.failedRequests = 0;

/**
 *
 * @type {number}
 */
x3dom.RequestManager.loadedRequests = 0;

/**
 *
 * @type {number}
 */
x3dom.RequestManager.totalRequests = 0;


/**
 *
 * @type {number}
 */
x3dom.RequestManager.activeRequests = [];

/**
 *
 * @type {number}
 */
x3dom.RequestManager.requestHeaders = [];

/**
 *
 * @type {number}
 */
x3dom.RequestManager.withCredentials = false;

/**
 *
 * @param header
 * @param value
 */
x3dom.RequestManager.addRequestHeader = function( header, value )
{
    this.requestHeaders.push( { header: header, value : value } );
};

/**
 *
 * @private
 */
x3dom.RequestManager._sendRequest = function()
{
    //Check if we have reached the maximum parallel request limit
    if ( this.activeRequests.length > this.maxParallelRequests )
    {
        return;
    }

    //Get next available request
    var request = this.requests.pop();

    //Check if the request is valid
    if ( request )
    {
        this.activeRequests.push( request );

        //Send request
        request.send( null );

        //Trigger next request sending
        this._sendRequest();
    }
};

/**
 *
 * @param request
 */
x3dom.RequestManager.addRequest = function( request )
{
    //Return if request is not a valid XMLHttpRequest
    if ( !( request instanceof XMLHttpRequest ) )
    {
        return;
    }

    //Increment total request counter
    this.totalRequests++;

    //Set withCredentials property
    request.withCredentials = this.withCredentials;

    //Set available request headers
    for ( var i = 0; i < this.requestHeaders.length; i++ )
    {
        var header = this.requestHeaders[ i ].header;
        var value = this.requestHeaders[ i ].value;

        request.setRequestHeader( header, value );
    }

    //Listen for onLoad
    request.addEventListener( "load", this._onLoadHandler.bind( this ) );

    //Listen for onError
    request.addEventListener( "error", this._onErrorHandler.bind( this ) );

    //Push it to the list
    this.requests.push( request );

    //Send next available request
    this._sendRequest();
};

/**
 *
 */
x3dom.RequestManager.abortAllRequests = function()
{
    for ( var i = 0; i < this.activeRequests.length; i++ )
    {
        this.activeRequests[ i ].abort();
    }

    this.requests = this.activeRequests = [];
};

/**
 *
 */
x3dom.RequestManager._removeActiveRequest = function( request )
{
    var idx = this.activeRequests.indexOf( request );

    return this.activeRequests.splice( idx, 1 );
};

/**
 *
 * @param e
 * @private
 */
x3dom.RequestManager._onLoadHandler = function( e )
{
    //Decrement active request counter
    this._removeActiveRequest( e.target );

    //Increment loaded request counter
    this.loadedRequests++;

    //Send next available request
    this._sendRequest();
};

/**
 *
 * @param e
 * @private
 */
x3dom.RequestManager._onErrorHandler = function( e )
{
    //Decrement active request counter
    this._removeActiveRequest( e.target );

    //Increment loaded request counter
    this.failedRequests++;

    //Send next available request
    this._sendRequest();
};

/**
 * Created by tsturm on 30.10.2014.
 */
/**
 *  Parts Object is return
 */
x3dom.MultiMaterial = function( params )
{
    this._origAmbientIntensity      = params.ambientIntensity;
    this._origDiffuseColor          = params.diffuseColor;
    this._origEmissiveColor         = params.emissiveColor;
    this._origShininess             = params.shininess;
    this._origSpeclarColor          = params.specularColor;
    this._origTransparency          = params.transparency;

    this._origBackAmbientIntensity  = params.backAmbientIntensity;
    this._origBackDiffuseColor      = params.backDiffuseColor;
    this._origBackEmissiveColor     = params.backEmissiveColor;
    this._origBackShininess         = params.backShininess;
    this._origBackSpecularColor     = params.backSpecularColor;
    this._origBackTransparency      = params.backTransparency;

    this._ambientIntensity          = params.ambientIntensity;
    this._diffuseColor              = params.diffuseColor;
    this._emissiveColor             = params.emissiveColor;
    this._shininess                 = params.shininess;
    this._specularColor             = params.specularColor;
    this._transparency              = params.transparency;

    this._backAmbientIntensity      = params.backAmbientIntensity;
    this._backDiffuseColor          = params.backDiffuseColor;
    this._backEmissiveColor         = params.backEmissiveColor;
    this._backShininess             = params.backShininess;
    this._backSpecularColor         = params.backSpecularColor;
    this._backTransparency          = params.backTransparency;

    this._highlighted               = false;

    this.reset = function () {
        this._ambientIntensity      = this._origAmbientIntensity;
        this._diffuseColor          = this._origDiffuseColor;
        this._emissiveColor         = this._origEmissiveColor;
        this._shininess             = this._origShininess;
        this._specularColor         = this._origSpeclarColor;
        this._transparency          = this._origTransparency;
        this._backAmbientIntensity  = this._origBackAmbientIntensity;
        this._backDiffuseColor      = this._origBackDiffuseColor;
        this._backEmissiveColor     = this._origBackEmissiveColor;
        this._backShininess         = this._origBackShininess;
        this._backSpecularColor     = this._origBackSpecularColor;
        this._backTransparency      = this._origBackTransparency;
    };

};
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


/**
 *  Parts Object is return
 */
x3dom.Parts = function(multiPart, ids, colorMap, emissiveMap, specularMap, visibilityMap)
{
    var parts = this;
    this.multiPart = multiPart;
    this.ids = ids;
    this.colorMap = colorMap;
    this.emissiveMap = emissiveMap;
    this.specularMap = specularMap;
    this.visibilityMap = visibilityMap;
    this.width = parts.colorMap.getWidth();
    this.widthTwo =  this.width * this.width;

    /**
     *
     * @param color
     * @param frontSide
     */
    this.setDiffuseColor = function(color, side)
    {
        var i, partID, pixelIDFront, pixelIDBack;

        if(side == undefined && side != "front" && side != "back" && side != "both") {
            side = "both";
        }

        color = x3dom.fields.SFColor.parse( color );

        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var pixels = parts.colorMap.getPixels();

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack = partID + this.widthTwo;

                //Check for front/back
                if (side == "front")
                {
                    this.multiPart._materials[partID]._diffuseColor = color;
                }
                else if(side == "back")
                {
                    this.multiPart._materials[partID]._backDiffuseColor = color;
                }
                else if(side == "both")
                {
                    this.multiPart._materials[partID]._diffuseColor = color;
                    this.multiPart._materials[partID]._backDiffuseColor = color;
                }

                //If part is not highlighted update the pixel
                if ( !this.multiPart._materials[partID]._highlighted )
                {
                    if (side == "front") {
                        pixels[pixelIDFront].r = color.r;
                        pixels[pixelIDFront].g = color.g;
                        pixels[pixelIDFront].b = color.b;
                    } else if(side == "back") {
                        pixels[pixelIDBack].r = color.r;
                        pixels[pixelIDBack].g = color.g;
                        pixels[pixelIDBack].b = color.b;
                    } else if(side == "both") {
                        pixels[pixelIDFront].r = color.r;
                        pixels[pixelIDFront].g = color.g;
                        pixels[pixelIDFront].b = color.b;
                        pixels[pixelIDBack].r = color.r;
                        pixels[pixelIDBack].g = color.g;
                        pixels[pixelIDBack].b = color.b;
                    }
                }
            }

            parts.colorMap.setPixels(pixels);
        }
        else
        {
            var xFront, yFront, xBack, yBack, pixelFront, pixelBack;

            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack = partID + this.widthTwo;

            //Check for front/back
            if (side == "front")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                pixelFront = parts.colorMap.getPixel(xFront, yFront);
                this.multiPart._materials[partID]._diffuseColor = color;
            }
            else if(side == "back")
            {
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelBack = parts.colorMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._backDiffuseColor = color;
            }
            else if(side == "both")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelFront = parts.colorMap.getPixel(xFront, yFront);
                pixelBack = parts.colorMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._diffuseColor = color;
                this.multiPart._materials[partID]._backDiffuseColor = color;
            }

            //If part is not highlighted update the pixel
            if ( !this.multiPart._materials[partID]._highlighted )
            {
                if (side == "front")
                {
                    pixelFront.r = color.r;
                    pixelFront.g = color.g;
                    pixelFront.b = color.b;

                    parts.colorMap.setPixel(xFront, yFront, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.colorMap.setPixel(xBack, yBack, pixelBack);
                }
                else if(side == "both")
                {
                    pixelFront.r = color.r;
                    pixelFront.g = color.g;
                    pixelFront.b = color.b;
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.colorMap.setPixel(xFront, yFront, pixelFront);
                    parts.colorMap.setPixel(xBack, yBack, pixelBack);
                }
            }
        }
    };

    /**
     *
     * @param frontSide
     * @returns {*}
     */
    this.getDiffuseColor = function(side)
    {
        var i, partID;

        if(side == undefined && side != "front" && side != "back") {
            side = "front";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            var diffuseColors = [];

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];

                if(side == "front")
                {
                    diffuseColors.push(this.multiPart._materials[partID]._diffuseColor);
                }
                else if(side == "back")
                {
                    diffuseColors.push(this.multiPart._materials[partID]._backDiffuseColor);
                }
            }
            return diffuseColors;
        }
        else
        {
            partID = parts.ids[0];

            if(side == "front")
            {
                return this.multiPart._materials[partID]._diffuseColor;
            }
            else if(side == "back")
            {
                return this.multiPart._materials[partID]._backDiffuseColor;
            }
        }
    };

    /**
     *
     * @param color
     * @param side
     */
    this.setEmissiveColor = function(color, side)
    {
        var i, partID, pixelIDFront, pixelIDBack;

        if(side == undefined && side != "front" && side != "back" && side != "both") {
            side = "both";
        }

        color = x3dom.fields.SFColor.parse( color );

        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var pixels = parts.emissiveMap.getPixels();

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack = partID + this.widthTwo;

                //Check for front/back
                if (side == "front")
                {
                    this.multiPart._materials[partID]._emissiveColor = color;
                }
                else if(side == "back")
                {
                    this.multiPart._materials[partID]._backEmissiveColor = color;
                }
                else if(side == "both")
                {
                    this.multiPart._materials[partID]._emissiveColor = color;
                    this.multiPart._materials[partID]._backEmissiveColor = color;
                }

                //If part is not highlighted update the pixel
                if ( !this.multiPart._materials[partID]._highlighted )
                {
                    if (side == "front") {
                        pixels[pixelIDFront].r = color.r;
                        pixels[pixelIDFront].g = color.g;
                        pixels[pixelIDFront].b = color.b;
                    } else if(side == "back") {
                        pixels[pixelIDBack].r = color.r;
                        pixels[pixelIDBack].g = color.g;
                        pixels[pixelIDBack].b = color.b;
                    } else if(side == "both") {
                        pixels[pixelIDFront].r = color.r;
                        pixels[pixelIDFront].g = color.g;
                        pixels[pixelIDFront].b = color.b;
                        pixels[pixelIDBack].r = color.r;
                        pixels[pixelIDBack].g = color.g;
                        pixels[pixelIDBack].b = color.b;
                    }
                }
            }

            parts.emissiveMap.setPixels(pixels);
        }
        else
        {
            var xFront, yFront, xBack, yBack, pixelFront, pixelBack;

            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack = partID + this.widthTwo;

            //Check for front/back
            if (side == "front")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                pixelFront = parts.emissiveMap.getPixel(xFront, yFront);
                this.multiPart._materials[partID]._emissiveColor = color;
            }
            else if(side == "back")
            {
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelBack = parts.emissiveMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._backEmissiveColor = color;
            }
            else if(side == "both")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelFront = parts.emissiveMap.getPixel(xFront, yFront);
                pixelBack = parts.emissiveMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._emissiveColor = color;
                this.multiPart._materials[partID]._backEmissiveColor = color;
            }

            //If part is not highlighted update the pixel
            if ( !this.multiPart._materials[partID]._highlighted )
            {
                if (side == "front")
                {
                    pixelFront.r = color.r;
                    pixelFront.g = color.g;
                    pixelFront.b = color.b;

                    parts.emissiveMap.setPixel(xFront, yFront, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.emissiveMap.setPixel(xBack, yBack, pixelBack);
                }
                else if(side == "both")
                {
                    pixelFront.r = color.r;
                    pixelFront.g = color.g;
                    pixelFront.b = color.b;
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.emissiveMap.setPixel(xFront, yFront, pixelFront);
                    parts.emissiveMap.setPixel(xBack, yBack, pixelBack);
                }
            }
        }
    };

    /**
     *
     * @param side
     * @returns {*}
     */
    this.getEmissiveColor = function(side)
    {
        var i, partID;

        if(side == undefined && side != "front" && side != "back") {
            side = "front";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            var emissiveColors = [];

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];

                if(side == "front")
                {
                    emissiveColors.push(this.multiPart._materials[partID]._emissiveColor);
                }
                else if(side == "back")
                {
                    emissiveColors.push(this.multiPart._materials[partID]._backEmissiveColor);
                }
            }
            return emissiveColors;
        }
        else
        {
            partID = parts.ids[0];

            if(side == "front")
            {
                return this.multiPart._materials[partID]._emissiveColor;
            }
            else if(side == "back")
            {
                return this.multiPart._materials[partID]._backEmissiveColor;
            }
        }
    };

    /**
     *
     * @param color
     * @param side
     */
    this.setSpecularColor = function(color, side)
    {
        var i, partID, pixelIDFront, pixelIDBack;

        if(side == undefined && side != "front" && side != "back" && side != "both") {
            side = "both";
        }

        color = x3dom.fields.SFColor.parse( color );

        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var pixels = parts.specularMap.getPixels();

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack = partID + this.widthTwo;

                //Check for front/back
                if (side == "front")
                {
                    this.multiPart._materials[partID]._specularColor = color;
                }
                else if(side == "back")
                {
                    this.multiPart._materials[partID]._backSpecularColor = color;
                }
                else if(side == "both")
                {
                    this.multiPart._materials[partID]._specularColor = color;
                    this.multiPart._materials[partID]._backSpecularColor = color;
                }

                //If part is not highlighted update the pixel
                if ( !this.multiPart._materials[partID]._highlighted )
                {
                    if (side == "front") {
                        pixels[pixelIDFront].r = color.r;
                        pixels[pixelIDFront].g = color.g;
                        pixels[pixelIDFront].b = color.b;
                    } else if(side == "back") {
                        pixels[pixelIDBack].r = color.r;
                        pixels[pixelIDBack].g = color.g;
                        pixels[pixelIDBack].b = color.b;
                    } else if(side == "both") {
                        pixels[pixelIDFront].r = color.r;
                        pixels[pixelIDFront].g = color.g;
                        pixels[pixelIDFront].b = color.b;
                        pixels[pixelIDBack].r = color.r;
                        pixels[pixelIDBack].g = color.g;
                        pixels[pixelIDBack].b = color.b;
                    }
                }
            }

            parts.specularMap.setPixels(pixels);
        }
        else
        {
            var xFront, yFront, xBack, yBack, pixelFront, pixelBack;

            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack = partID + this.widthTwo;

            //Check for front/back
            if (side == "front")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                pixelFront = parts.specularMap.getPixel(xFront, yFront);
                this.multiPart._materials[partID]._specularColor = color;
            }
            else if(side == "back")
            {
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelBack = parts.specularMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._backSpecularColor = color;
            }
            else if(side == "both")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelFront = parts.specularMap.getPixel(xFront, yFront);
                pixelBack = parts.specularMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._specularColor = color;
                this.multiPart._materials[partID]._backSpecularColor = color;
            }

            //If part is not highlighted update the pixel
            if ( !this.multiPart._materials[partID]._highlighted )
            {
                if (side == "front")
                {
                    pixelFront.r = color.r;
                    pixelFront.g = color.g;
                    pixelFront.b = color.b;

                    parts.specularMap.setPixel(xFront, yFront, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.specularMap.setPixel(xBack, yBack, pixelBack);
                }
                else if(side == "both")
                {
                    pixelFront.r = color.r;
                    pixelFront.g = color.g;
                    pixelFront.b = color.b;
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.specularMap.setPixel(xFront, yFront, pixelFront);
                    parts.specularMap.setPixel(xBack, yBack, pixelBack);
                }
            }
        }
    };


    /**
     *
     * @param side
     * @returns {*}
     */
    this.getSpecularColor = function(side)
    {
        var i, partID;

        if(side == undefined && side != "front" && side != "back") {
            side = "front";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            var specularColors = [];

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];

                if(side == "front")
                {
                    specularColors.push(this.multiPart._materials[partID]._specularColor);
                }
                else if(side == "back")
                {
                    specularColors.push(this.multiPart._materials[partID]._backSpecularColor);
                }
            }
            return specularColors;
        }
        else
        {
            partID = parts.ids[0];

            if(side == "front")
            {
                return this.multiPart._materials[partID]._specularColor;
            }
            else if(side == "back")
            {
                return this.multiPart._materials[partID]._backSpecularColor;
            }
        }
    };

    /**
     *
     * @param transparency
     * @param side
     */
    this.setTransparency = function(transparency, side)
    {
        var i, partID, pixelIDFront, pixelIDBack;

        if(side == undefined && side != "front" && side != "back" && side != "both") {
            side = "both";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var pixels = parts.colorMap.getPixels();

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack = partID + this.widthTwo;

                //Check for front/back
                if (side == "front")
                {
                    this.multiPart._materials[partID]._transparency = transparency;
                }
                else if(side == "back")
                {
                    this.multiPart._materials[partID]._backTransparency = transparency;
                }
                else if(side == "both")
                {
                    this.multiPart._materials[partID]._transparency = transparency;
                    this.multiPart._materials[partID]._backTransparency = transparency;
                }

                //If part is not highlighted update the pixel
                if ( !this.multiPart._materials[partID]._highlighted )
                {
                    if (side == "front") {
                        pixels[pixelIDFront].a = 1.0 - transparency;
                    } else if(side == "back") {
                        pixels[pixelIDBack].a = 1.0 - transparency;
                    } else if(side == "both") {
                        pixels[pixelIDFront].a = 1.0 - transparency;
                        pixels[pixelIDBack].a = 1.0 - transparency;
                    }
                }
            }

            parts.colorMap.setPixels(pixels);
        }
        else
        {
            var xFront, yFront, xBack, yBack, pixelFront, pixelBack;

            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack = partID + this.widthTwo;

            //Check for front/back
            if (side == "front")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                pixelFront = parts.colorMap.getPixel(xFront, yFront);
                this.multiPart._materials[partID]._transparency = transparency;
            }
            else if(side == "back")
            {
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelBack = parts.colorMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._backTransparency = transparency;
            }
            else if(side == "both")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelFront = parts.colorMap.getPixel(xFront, yFront);
                pixelBack = parts.colorMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._transparency = transparency;
                this.multiPart._materials[partID]._backTransparency = transparency;
            }

            //If part is not highlighted update the pixel
            if ( !this.multiPart._materials[partID]._highlighted )
            {
                if (side == "front")
                {
                    pixelFront.a = 1.0 - transparency;

                    parts.colorMap.setPixel(xFront, yFront, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.a = 1.0 - transparency;

                    parts.colorMap.setPixel(xBack, yBack, pixelBack);
                }
                else if(side == "both")
                {
                    pixelFront.a = 1.0 - transparency;
                    pixelBack.a = 1.0 - transparency;

                    parts.colorMap.setPixel(xFront, yFront, pixelFront);
                    parts.colorMap.setPixel(xBack, yBack, pixelBack);
                }
            }
        }
    };


    /**
     *
     * @param side
     * @returns {*}
     */
    this.getTransparency = function(side)
    {
        var i, partID;

        if(side == undefined && side != "front" && side != "back") {
            side = "front";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            var transparencies = [];

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];

                if(side == "front")
                {
                    transparencies.push(this.multiPart._materials[partID]._transparency);
                }
                else if(side == "back")
                {
                    transparencies.push(this.multiPart._materials[partID]._backTransparency);
                }
            }
            return transparencies;
        }
        else
        {
            partID = parts.ids[0];

            if(side == "front")
            {
                return this.multiPart._materials[partID]._transparency;
            }
            else if(side == "back")
            {
                return this.multiPart._materials[partID]._backTransparency;
            }
        }
    };

    /**
     *
     * @param shininess
     * @param frontSide
     */
    this.setShininess = function(shininess, side)
    {
        var i, partID, pixelIDFront, pixelIDBack;

        if(side == undefined && side != "front" && side != "back" && side != "both") {
            side = "both";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var pixels = parts.specularMap.getPixels();

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack = partID + this.widthTwo;

                //Check for front/back
                if (side == "front")
                {
                    this.multiPart._materials[partID]._shininess = shininess;
                }
                else if(side == "back")
                {
                    this.multiPart._materials[partID]._backShininess = shininess;
                }
                else if(side == "both")
                {
                    this.multiPart._materials[partID]._shininess = shininess;
                    this.multiPart._materials[partID]._backShininess = shininess;
                }

                //If part is not highlighted update the pixel
                if ( !this.multiPart._materials[partID]._highlighted )
                {
                    if (side == "front") {
                        pixels[pixelIDFront].a = shininess;
                    } else if(side == "back") {
                        pixels[pixelIDBack].a = shininess;
                    } else if(side == "both") {
                        pixels[pixelIDFront].a = shininess;
                        pixels[pixelIDBack].a = shininess;
                    }
                }
            }

            parts.specularMap.setPixels(pixels);
        }
        else
        {
            var xFront, yFront, xBack, yBack, pixelFront, pixelBack;

            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack = partID + this.widthTwo;

            //Check for front/back
            if (side == "front")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                pixelFront = parts.specularMap.getPixel(xFront, yFront);
                this.multiPart._materials[partID]._shininess = shininess;
            }
            else if(side == "back")
            {
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelBack = parts.specularMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._backShininess = shininess;
            }
            else if(side == "both")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelFront = parts.specularMap.getPixel(xFront, yFront);
                pixelBack = parts.specularMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._shininess = shininess;
                this.multiPart._materials[partID]._backShininess = shininess;
            }

            //If part is not highlighted update the pixel
            if ( !this.multiPart._materials[partID]._highlighted )
            {
                if (side == "front")
                {
                    pixelFront.a = shininess;

                    parts.specularMap.setPixel(xFront, yFront, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.a = shininess;

                    parts.specularMap.setPixel(xBack, yBack, pixelBack);
                }
                else if(side == "both")
                {
                    pixelFront.a = shininess;
                    pixelBack.a = shininess;

                    parts.specularMap.setPixel(xFront, yFront, pixelFront);
                    parts.specularMap.setPixel(xBack, yBack, pixelBack);
                }
            }
        }
    };


    /**
     *
     * @param side
     * @returns {*}
     */
    this.getShininess = function(side)
    {
        var i, partID;

        if(side == undefined && side != "front" && side != "back") {
            side = "front";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            var shininesses = [];

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];

                if(side == "front")
                {
                    shininesses.push(this.multiPart._materials[partID]._shininess);
                }
                else if(side == "back")
                {
                    shininesses.push(this.multiPart._materials[partID]._backShininess);
                }
            }
            return shininesses;
        }
        else
        {
            partID = parts.ids[0];

            if(side == "front")
            {
                return this.multiPart._materials[partID]._shininess;
            }
            else if(side == "back")
            {
                return this.multiPart._materials[partID]._backShininess;
            }
        }
    };

    /**
     *
     * @param ambientIntensity
     * @param side
     */
    this.setAmbientIntensity = function(ambientIntensity, side)
    {
        var i, partID, pixelIDFront, pixelIDBack;

        if(side == undefined && side != "front" && side != "back" && side != "both") {
            side = "both";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var pixels = parts.emissiveMap.getPixels();

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack = partID + this.widthTwo;

                //Check for front/back
                if (side == "front")
                {
                    this.multiPart._materials[partID]._ambientIntensity = ambientIntensity;
                }
                else if(side == "back")
                {
                    this.multiPart._materials[partID]._backAmbientIntensity = ambientIntensity;
                }
                else if(side == "both")
                {
                    this.multiPart._materials[partID]._ambientIntensity = ambientIntensity;
                    this.multiPart._materials[partID]._backAmbientIntensity = ambientIntensity;
                }

                //If part is not highlighted update the pixel
                if ( !this.multiPart._materials[partID]._highlighted )
                {
                    if (side == "front") {
                        pixels[pixelIDFront].a = ambientIntensity;
                    } else if(side == "back") {
                        pixels[pixelIDBack].a = ambientIntensity;
                    } else if(side == "both") {
                        pixels[pixelIDFront].a = ambientIntensity;
                        pixels[pixelIDBack].a = ambientIntensity;
                    }
                }
            }

            parts.emissiveMap.setPixels(pixels);
        }
        else
        {
            var xFront, yFront, xBack, yBack, pixelFront, pixelBack;

            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack = partID + this.widthTwo;

            //Check for front/back
            if (side == "front")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                pixelFront = parts.emissiveMap.getPixel(xFront, yFront);
                this.multiPart._materials[partID]._ambientIntensity = ambientIntensity;
            }
            else if(side == "back")
            {
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelBack = parts.emissiveMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._backAmbientIntensity = ambientIntensity;
            }
            else if(side == "both")
            {
                xFront = pixelIDFront % this.width;
                yFront = Math.floor(pixelIDFront / this.width);
                xBack = pixelIDBack % this.width;
                yBack = Math.floor(pixelIDBack / this.width);
                pixelFront = parts.emissiveMap.getPixel(xFront, yFront);
                pixelBack = parts.emissiveMap.getPixel(xBack, yBack);
                this.multiPart._materials[partID]._ambientIntensity = ambientIntensity;
                this.multiPart._materials[partID]._backAmbientIntensity = ambientIntensity;
            }

            //If part is not highlighted update the pixel
            if ( !this.multiPart._materials[partID]._highlighted )
            {
                if (side == "front")
                {
                    pixelFront.a = ambientIntensity;

                    parts.emissiveMap.setPixel(xFront, yFront, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.a = ambientIntensity;

                    parts.emissiveMap.setPixel(xBack, yBack, pixelBack);
                }
                else if(side == "both")
                {
                    pixelFront.a = ambientIntensity;
                    pixelBack.a = ambientIntensity;

                    parts.emissiveMap.setPixel(xFront, yFront, pixelFront);
                    parts.emissiveMap.setPixel(xBack, yBack, pixelBack);
                }
            }
        }
    };


    /**
     *
     * @param side
     * @returns {*}
     */
    this.getAmbientIntensity = function(side)
    {
        var i, partID;

        if(side == undefined && side != "front" && side != "back") {
            side = "front";
        }

        if (ids.length && ids.length > 1) //Multi select
        {
            var ambientIntensities = [];

            for ( i=0; i < parts.ids.length; i++ )
            {
                partID = parts.ids[i];

                if(side == "front")
                {
                    ambientIntensities.push(this.multiPart._materials[partID]._ambientIntensity);
                }
                else if(side == "back")
                {
                    ambientIntensities.push(this.multiPart._materials[partID]._backAmbientIntensity);
                }
            }
            return ambientIntensities;
        }
        else
        {
            partID = parts.ids[0];

            if(side == "front")
            {
                return this.multiPart._materials[partID]._ambientIntensity;
            }
            else if(side == "back")
            {
                return this.multiPart._materials[partID]._backAmbientIntensity;
            }
        }
    };

    /**
     *
     * @param color
     */
    this.highlight = function (color)
    {
        var i, partID, pixelIDFront, pixelIDBack, dtColor, eaColor, ssColor;

        color = x3dom.fields.SFColor.parse( color );

        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var dtPixels = parts.colorMap.getPixels();
            var eaPixels = parts.emissiveMap.getPixels();
            var ssPixels = parts.specularMap.getPixels();

            dtColor = new x3dom.fields.SFColorRGBA(0, 0, 0, 1.0);
            eaColor = new x3dom.fields.SFColorRGBA(color.r, color.g, color.b, 0);
            ssColor = new x3dom.fields.SFColorRGBA(0, 0, 0, 0);

            for ( i=0; i < parts.ids.length; i++ ) {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack  = (parseInt(partID) + parseInt(this.widthTwo)).toString();

                if( !this.multiPart._materials[partID]._highlighted )
                {
                    this.multiPart._materials[partID]._highlighted = true;

                    dtPixels[pixelIDFront] = dtColor;
                    eaPixels[pixelIDFront] = eaColor;
                    ssPixels[pixelIDFront] = ssColor;

                    dtPixels[pixelIDBack] = dtColor;
                    eaPixels[pixelIDBack] = eaColor;
                    ssPixels[pixelIDBack] = ssColor;
                }
            }

            this.colorMap.setPixels(dtPixels, false);
            this.emissiveMap.setPixels(eaPixels, false);
            this.specularMap.setPixels(ssPixels, true);
        }
        else
        {
            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack  = partID + this.widthTwo;

            var xFront = pixelIDFront % this.width;
            var yFront = Math.floor(pixelIDFront / this.width);

            var xBack = pixelIDBack % this.width;
            var yBack = Math.floor(pixelIDBack / this.width);

            //If part is not highlighted update the pixel
            if ( !this.multiPart._materials[partID]._highlighted )
            {
                this.multiPart._materials[partID]._highlighted = true;

                dtColor = new x3dom.fields.SFColorRGBA(0, 0, 0, 1);
                eaColor = new x3dom.fields.SFColorRGBA(color.r, color.g, color.b, 0);
                ssColor = new x3dom.fields.SFColorRGBA(0, 0, 0, 0);

                this.colorMap.setPixel(xFront, yFront, dtColor, false);
                this.emissiveMap.setPixel(xFront, yFront, eaColor, false);
                this.specularMap.setPixel(xFront, yFront, ssColor, false);

                this.colorMap.setPixel(xBack, yBack, dtColor, false);
                this.emissiveMap.setPixel(xBack, yBack, eaColor, false);
                this.specularMap.setPixel(xBack, yBack, ssColor, true);
            }
        }
    };

    this.unhighlight = function() {
        var i, partID, pixelIDFront, pixelIDBack, material;
        var dtColorFront, eaColorFront, ssColorFront;
        var dtColorBack, eaColorBack, ssColorBack;

        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var dtPixels = parts.colorMap.getPixels();
            var eaPixels = parts.emissiveMap.getPixels();
            var ssPixels = parts.specularMap.getPixels();

            for ( i=0; i < parts.ids.length; i++ ) {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack  = partID + this.widthTwo;

                material = this.multiPart._materials[partID];

                if( material._highlighted )
                {
                    material._highlighted = false;

                    dtPixels[pixelIDFront] = new x3dom.fields.SFColorRGBA(material._diffuseColor.r, material._diffuseColor.g,
                                                                          material._diffuseColor.b, 1.0 - material._transparency);
                    eaPixels[pixelIDFront] = new x3dom.fields.SFColorRGBA(material._emissiveColor.r, material._emissiveColor.g,
                                                                          material._emissiveColor.b, material._ambientIntensity);
                    ssPixels[pixelIDFront] = new x3dom.fields.SFColorRGBA(material._specularColor.r, material._specularColor.g,
                                                                          material._specularColor.b, material._shininess);

                    dtPixels[pixelIDBack] = new x3dom.fields.SFColorRGBA(material._backDiffuseColor.r, material._backDiffuseColor.g,
                                                                         material._backDiffuseColor.b, 1.0 - material._backTransparency);
                    eaPixels[pixelIDBack] = new x3dom.fields.SFColorRGBA(material._backEmissiveColor.r, material._backEmissiveColor.g,
                                                                         material._backEmissiveColor.b, material._backAmbientIntensity);
                    ssPixels[pixelIDBack] = new x3dom.fields.SFColorRGBA(material._backSpecularColor.r, material._backSpecularColor.g,
                                                                         material._backSpecularColor.b, material._backShininess);
                }
            }

            this.colorMap.setPixels(dtPixels, false);
            this.emissiveMap.setPixels(eaPixels, false);
            this.specularMap.setPixels(ssPixels, true);
        }
        else
        {
            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack  = partID + this.widthTwo;

            var xFront = pixelIDFront % this.width;
            var yFront = Math.floor(pixelIDFront / this.width);

            var xBack = pixelIDBack % this.width;
            var yBack = Math.floor(pixelIDBack / this.width);

            material = this.multiPart._materials[partID];

            //If part is not highlighted update the pixel
            if ( material._highlighted )
            {
                material._highlighted = false;

                dtColorFront = new x3dom.fields.SFColorRGBA(material._diffuseColor.r, material._diffuseColor.g,
                                                            material._diffuseColor.b, 1.0 - material._transparency);
                eaColorFront = new x3dom.fields.SFColorRGBA(material._emissiveColor.r, material._emissiveColor.g,
                                                            material._emissiveColor.b, material._ambientIntensity);
                ssColorFront = new x3dom.fields.SFColorRGBA(material._specularColor.r, material._specularColor.g,
                                                            material._specularColor.b, material._shininess);

                dtColorBack = new x3dom.fields.SFColorRGBA(material._backDiffuseColor.r, material._backDiffuseColor.g,
                                                           material._backDiffuseColor.b, 1.0 - material._backTransparency);
                eaColorBack = new x3dom.fields.SFColorRGBA(material._backEmissiveColor.r, material._backEmissiveColor.g,
                                                           material._backEmissiveColor.b, material._backAmbientIntensity);
                ssColorBack = new x3dom.fields.SFColorRGBA(material._backSpecularColor.r, material._backSpecularColor.g,
                                                           material._backSpecularColor.b, material._backShininess);

                this.colorMap.setPixel(xFront, yFront, dtColorFront, false);
                this.emissiveMap.setPixel(xFront, yFront, eaColorFront, false);
                this.specularMap.setPixel(xFront, yFront, ssColorFront, false);

                this.colorMap.setPixel(xBack, yBack, dtColorBack, false);
                this.emissiveMap.setPixel(xBack, yBack, eaColorBack, false);
                this.specularMap.setPixel(xBack, yBack, ssColorBack, true);
            }
        }
    };


    /**
     *
     * @param color
     */
    this.toggleHighlight = function ( color ) {
        for ( var i=0; i < parts.ids.length; i++ ) {
            if ( this.multiPart._materials[parts.ids[i]]._highlighted ) {
                this.unhighlight();
            } else {
                this.highlight(color);
            }
        }
    };


    /**
     *
     * @param color
     * @param side
     */
    this.setColor = function(color, side) {
        this.setDiffuseColor(color, side);
    };


    /**
     * Returns the RGB string representation of a color
     * @returns {String}
     */
    this.getColorRGB = function() {
        var str = this.getColorRGBA();

        var values = str.split(" ");

        return values[0] + " " + values[1] + " " + values[2];
    };

    /**
     * Returns the RGBA string representation of a color
     * @returns {String}
     */
    this.getColorRGBA = function() {
        var x, y;

        //in case of multi select, this function returns the color of the first object
        var colorRGBA = this.multiPart._originalColor[parts.ids[0]];

        if (this.multiPart._highlightedParts[parts.ids[0]]){
            colorRGBA = this.multiPart._highlightedParts[parts.ids[0]];
        } else {
            x = parts.ids[0] % parts.colorMap.getWidth();
            y = Math.floor(parts.ids[0] / parts.colorMap.getWidth());
            colorRGBA = parts.colorMap.getPixel(x, y);
        }

        return colorRGBA.toString();
    };

	/**
     *
     */
    this.resetColor = function() {

        var i, partID, pixelIDFront, pixelIDBack, material;
        var dtColorFront, eaColorFront, ssColorFront;
        var dtColorBack, eaColorBack, ssColorBack;
		
        if (ids.length && ids.length > 1) //Multi select
        {
            //Get original pixels
            var dtPixels = parts.colorMap.getPixels();
            var eaPixels = parts.emissiveMap.getPixels();
            var ssPixels = parts.specularMap.getPixels();

            for ( i=0; i < parts.ids.length; i++ ) {
                partID = parts.ids[i];
                pixelIDFront = partID;
                pixelIDBack  = partID + this.widthTwo;

                material = this.multiPart._materials[partID];

                material.reset();

                if( !material._highlighted )
                {
                    dtPixels[pixelIDFront] = new x3dom.fields.SFColorRGBA(material._diffuseColor.r, material._diffuseColor.g,
                                                                          material._diffuseColor.b, 1.0 - material._transparency);
                    eaPixels[pixelIDFront] = new x3dom.fields.SFColorRGBA(material._emissiveColor.r, material._emissiveColor.g,
                                                                          material._emissiveColor.b, material._ambientIntensity);
                    ssPixels[pixelIDFront] = new x3dom.fields.SFColorRGBA(material._specularColor.r, material._specularColor.g,
                                                                          material._specularColor.b, material._shininess);

                    dtPixels[pixelIDBack] = new x3dom.fields.SFColorRGBA(material._backDiffuseColor.r, material._backDiffuseColor.g,
                                                                         material._backDiffuseColor.b, 1.0 - material._backTransparency);
                    eaPixels[pixelIDBack] = new x3dom.fields.SFColorRGBA(material._backEmissiveColor.r, material._backEmissiveColor.g,
                                                                         material._backEmissiveColor.b, material._backAmbientIntensity);
                    ssPixels[pixelIDBack] = new x3dom.fields.SFColorRGBA(material._backSpecularColor.r, material._backSpecularColor.g,
                                                                         material._backSpecularColor.b, material._backShininess);
                }
            }

            this.colorMap.setPixels(dtPixels, false);
            this.emissiveMap.setPixels(eaPixels, false);
            this.specularMap.setPixels(ssPixels, true);
        }
        else //Single select
        {
            partID = parts.ids[0];
            pixelIDFront = partID;
            pixelIDBack  = partID + this.widthTwo;

            var xFront = pixelIDFront % this.width;
            var yFront = Math.floor(pixelIDFront / this.width);

            var xBack = pixelIDBack % this.width;
            var yBack = Math.floor(pixelIDBack / this.width);

            material = this.multiPart._materials[partID];

            material.reset();

            //If part is not highlighted update the pixel
            if ( !material._highlighted )
            {
                dtColorFront = new x3dom.fields.SFColorRGBA(material._diffuseColor.r, material._diffuseColor.g,
                                                            material._diffuseColor.b, 1.0 - material._transparency);
                eaColorFront = new x3dom.fields.SFColorRGBA(material._emissiveColor.r, material._emissiveColor.g,
                                                            material._emissiveColor.b, material._ambientIntensity);
                ssColorFront = new x3dom.fields.SFColorRGBA(material._specularColor.r, material._specularColor.g,
                                                            material._specularColor.b, material._shininess);

                dtColorBack = new x3dom.fields.SFColorRGBA(material._backDiffuseColor.r, material._backDiffuseColor.g,
                                                           material._backDiffuseColor.b, 1.0 - material._backTransparency);
                eaColorBack = new x3dom.fields.SFColorRGBA(material._backEmissiveColor.r, material._backEmissiveColor.g,
                                                           material._backEmissiveColor.b, material._backAmbientIntensity);
                ssColorBack = new x3dom.fields.SFColorRGBA(material._backSpecularColor.r, material._backSpecularColor.g,
                                                           material._backSpecularColor.b, material._backShininess);

                this.colorMap.setPixel(xFront, yFront, dtColorFront, false);
                this.emissiveMap.setPixel(xFront, yFront, eaColorFront, false);
                this.specularMap.setPixel(xFront, yFront, ssColorFront, false);

                this.colorMap.setPixel(xBack, yBack, dtColorBack, false);
                this.emissiveMap.setPixel(xBack, yBack, eaColorBack, false);
                this.specularMap.setPixel(xBack, yBack, ssColorBack, true);
            }
        }
    };

    /**
     *
     * @param visibility
     */
    this.setVisibility = function(visibility) {

        var i, j, x, y, usage, visibleCount, visibilityAsInt;

        if (!(ids.length && ids.length > 1)) {
            x = parts.ids[0] % parts.colorMap.getWidth();
            y = Math.floor(parts.ids[0] / parts.colorMap.getWidth());

            var pixel = parts.visibilityMap.getPixel(x, y);

            visibilityAsInt = (visibility) ? 1 : 0;

            if (pixel.r != visibilityAsInt) {
                pixel.r = visibilityAsInt;

                this.multiPart._partVisibility[parts.ids[0]] = visibility;
                
                //get used shapes
                usage = this.multiPart._idMap.mapping[parts.ids[0]].usage;

                //Change the shapes render flag
                for (j = 0; j < usage.length; j++) {
                    visibleCount = this.multiPart._visiblePartsPerShape[usage[j]];
                    if (visibility && visibleCount.val < visibleCount.max) {
                        visibleCount.val++;
                    } else if (!visibility && visibleCount.val > 0) {
                        visibleCount.val--;
                    }

                    if (visibleCount.val) {
                        this.multiPart._inlineNamespace.defMap[usage[j]]._vf.render = true;
                    } else {
                        this.multiPart._inlineNamespace.defMap[usage[j]]._vf.render = false;
                    }
                }
            }

            parts.visibilityMap.setPixel(x, y, pixel);
            this.multiPart.invalidateVolume();
        }
        else
        {
            var pixels = parts.visibilityMap.getPixels();

            for (i = 0; i < parts.ids.length; i++) {

                visibilityAsInt = (visibility) ? 1 : 0;

                if (pixels[parts.ids[i]].r != visibilityAsInt) {
                    pixels[parts.ids[i]].r = visibilityAsInt;

                    this.multiPart._partVisibility[parts.ids[i]] = visibility;

                    //get used shapes
                    usage = this.multiPart._idMap.mapping[parts.ids[i]].usage;

                    //Change the shapes render flag
                    for (j = 0; j < usage.length; j++) {
                        visibleCount = this.multiPart._visiblePartsPerShape[usage[j]];
                        if (visibility && visibleCount.val < visibleCount.max) {
                            visibleCount.val++;
                        } else if (!visibility && visibleCount.val > 0) {
                            visibleCount.val--;
                        }

                        if (visibleCount.val) {
                            this.multiPart._inlineNamespace.defMap[usage[j]]._vf.render = true;
                        } else {
                            this.multiPart._inlineNamespace.defMap[usage[j]]._vf.render = false;
                        }
                    }
                }
            }

            parts.visibilityMap.setPixels(pixels);
            this.multiPart.invalidateVolume();
        }
    };


    /**
     * get bounding volume
     *
     */
    this.getVolume = function() {

        var volume;
        var transmat = this.multiPart.getCurrentTransform();

        if (ids.length && ids.length > 1) //Multi select
        {
            volume = new x3dom.fields.BoxVolume();

            for(var i=0; i<parts.ids.length; i++) {
                volume.extendBounds(this.multiPart._partVolume[parts.ids[i]].min, this.multiPart._partVolume[parts.ids[i]].max);
            }

            volume.transform(transmat);

            return volume;
        }
        else
        {
            volume = x3dom.fields.BoxVolume.copy(this.multiPart._partVolume[parts.ids[0]]);
            volume.transform(transmat);
            return volume;
        }
    };

    /**
     * Fit the selected Parts to the screen
     * @param updateCenterOfRotation
     */
    this.fit = function (updateCenterOfRotation) {

        var volume = this.getVolume();

        this.multiPart._nameSpace.doc._viewarea.fit(volume.min, volume.max, updateCenterOfRotation);
    };
};
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


x3dom.Properties = function() {
    this.properties = {};
};

x3dom.Properties.prototype.setProperty = function(name, value) {
    x3dom.debug.logInfo("Properties: Setting property '"+ name + "' to value '" + value + "'");
    this.properties[name] = value;
};

x3dom.Properties.prototype.getProperty = function(name, def) {
    if (this.properties[name]) {
        return this.properties[name]
    } else {
        return def;
    }
};

x3dom.Properties.prototype.merge = function(other) {
    for (var attrname in other.properties) {
        this.properties[attrname] = other.properties[attrname];
    }
};

x3dom.Properties.prototype.toString = function() {
    var str = "";
    for (var name in this.properties) {
        str += "Name: " + name + " Value: " + this.properties[name] + "\n";
    }
    return str;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

x3dom.DoublyLinkedList = function() {
	this.length = 0;
	this.first = null;
	this.last = null;
};

x3dom.DoublyLinkedList.ListNode = function(point, point_index, normals, colors, texCoords) {
	this.point = point;
	this.point_index = point_index;
	this.normals = normals;
	this.colors = colors;
	this.texCoords = texCoords;
	this.next = null;
	this.prev = null;
};

x3dom.DoublyLinkedList.prototype.appendNode = function(node) {
  	if (this.first === null) {
    	node.prev = node;
    	node.next = node;
    	this.first = node;
    	this.last = node;
  	} else {
   	 	node.prev = this.last;
   	 	node.next = this.first;
   	 	this.first.prev = node;
    	this.last.next = node;
    	this.last = node;
  	}
  	this.length++;
};

x3dom.DoublyLinkedList.prototype.insertAfterNode = function(node, newNode) {
  	newNode.prev = node;
 	newNode.next = node.next;
  	node.next.prev = newNode;
  	node.next = newNode;
  	if (newNode.prev == this.last) { 
		this.last = newNode;
	}
  	this.length++;
};

x3dom.DoublyLinkedList.prototype.deleteNode = function(node) {
 	if (this.length > 1) {
		node.prev.next = node.next;
		node.next.prev = node.prev;
		if (node == this.first) {
			this.first = node.next;
		}
		if (node == this.last) {
			this.last = node.prev;
		}
	} else {
		this.first = null;
		this.last = null;
	}
	node.prev = null;
	node.next = null;
	this.length--;
};

x3dom.DoublyLinkedList.prototype.getNode = function(index) {
	var node = null;
	if(index > this.length) {
		return node;
	}
	for(var i = 0; i < this.length; i++) {
		if(i == 0) {
			node = this.first;
		} else {
			node = node.next;
		}
		if(i == index) {
			return node;
		}
	}
    return null;
};

x3dom.DoublyLinkedList.prototype.invert = function() {
	var tmp = null;
	var node = this.first;
	
	for(var i = 0; i < this.length; i++) {
		tmp = node.prev;
		node.prev =	node.next;
		node.next = tmp;
		node = node.prev;
	}
	tmp = this.first;
	this.first = this.last;
	this.last = tmp;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

x3dom.EarClipping = {
	
	getIndexes: function (linklist) {
		
		var node = linklist.first.next;
		var plane = this.identifyPlane(node.prev.point, node.point, node.next.point);
		var i, points, x, y;
		points = [];
		point_indexes = [];
		
		for (i = 0; i < linklist.length; i++) {
			node = linklist.getNode(i);
			switch (plane) {
				case "XY": { x = node.point.x; y = node.point.y; break; }
				case "XZ": { x = node.point.z; y = node.point.x; break; }
				default: { x = node.point.y; y = node.point.z; }
			}
			points.push(y);
			points.push(x);
			point_indexes.push(node.point_index);
		}
		var triangles = x3dom.EarCut.triangulate(points, null, 2);
		triangles = triangles.map(function(m) {return point_indexes[m];}) ;
		return triangles;
	},

	getMultiIndexes: function (linklist) {
		var node = linklist.first.next;
		var plane = this.identifyPlane(node.prev.point, node.point, node.next.point);
		var data = {};
		data.indices = [];
		data.point = [];
		data.normals = [];
		data.colors = [];
		data.texCoords = [];
		var mapped = {};
		mapped.indices = [];
		mapped.point = [];
		mapped.normals = [];
		mapped.colors = [];
		mapped.texCoords = [];
		
		points = [];
		
		for (i = 0; i < linklist.length; i++) {
			node = linklist.getNode(i);
			switch (plane) {
				case "XY": { x = node.point.x; y = node.point.y; break; }
				case "XZ": { x = node.point.z; y = node.point.x; break; }
				default: { x = node.point.y; y = node.point.z; }
			}
			points.push(y);
			points.push(x);
			mapped.indices.push(node.point_index);
			mapped.point.push(node.point);
			if (node.normals) mapped.normals.push(node.normals);
			if (node.colors) mapped.colors.push(node.colors);
			if (node.texCoords) mapped.texCoords.push(node.texCoords);
		}
		
		var triangles = x3dom.EarCut.triangulate(points, null, 2);
		data.indices = triangles.map(function(m) {return mapped.indices[m];}) ;
		data.point = triangles.map(function(m) {return mapped.point[m];}) ;
		if (node.normals) data.normals = triangles.map(function(m) {return mapped.normals[m];}) ;
		if (node.colors) data.colors = triangles.map(function(m) {return mapped.colors[m];}) ;
		if (node.texCoords) data.texCoords = triangles.map(function(m) {return mapped.texCoords[m];}) ;
		return data;
	}, 
	
	identifyPlane: function(p1, p2, p3) {
		var v1x, v1y, v1z;
		var v2x, v2y, v2z;
		var v3x, v3y, v3z;
	
		v1x = p2.x - p1.x; v1y = p2.y - p1.y; v1z = p2.z - p1.z;
		v2x = p3.x - p1.x; v2y = p3.y - p1.y; v2z = p3.z - p1.z;
		
		v3x = Math.abs(v1y*v2z - v1z*v2y);
		v3y = Math.abs(v1z*v2x - v1x*v2z);
		v3z = Math.abs(v1x*v2y - v1y*v2x);
		
		var angle = Math.max(v3x, v3y, v3z);
		
		if(angle == v3x) {
			return 'YZ';
		} else if(angle == v3y) {
			return 'XZ';
		} else if(angle == v3z) {
			return 'XY';
		} else {
			return 'XZ';    // error
		}
	}
};

//TODO: adjust to directly use x3dom linked list
//      move to separate file
x3dom.EarCut = {

	triangulate: function mapEarcut (data, holes, dim) {
		return earcut(data, holes, dim);

/*
The following code is
Copyright (c) 2015, Mapbox

Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
*/

function earcut(data, holeIndices, dim) {

    dim = dim || 2;

    var hasHoles = holeIndices && holeIndices.length,
        outerLen = hasHoles ? holeIndices[0] * dim : data.length,
        //outerNode = filterPoints(linkedList(data, 0, outerLen, dim, true, clockwise)),
        //AP: remember winding order
        clockwise = windingOrder(data, 0, outerLen, dim),
        outerNode = linkedList(data, 0, outerLen, dim, true, clockwise),
        triangles = [];
        
	if (!outerNode) return triangles;

    var minX, minY, maxX, maxY, x, y, size;

    if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);

    // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
    if (data.length > 80 * dim) {
        minX = maxX = data[0];
        minY = maxY = data[1];

        for (var i = dim; i < outerLen; i += dim) {
            x = data[i];
            y = data[i + 1];
            if (x < minX) minX = x;
            if (y < minY) minY = y;
            if (x > maxX) maxX = x;
            if (y > maxY) maxY = y;
        }

        // minX, minY and size are later used to transform coords into integers for z-order calculation
        size = Math.max(maxX - minX, maxY - minY);
    }
	earcutLinked(outerNode, triangles, dim, minX, minY, size);
	//AP: preserve winding order
	if (clockwise === false) {triangles.reverse();}
	return triangles;
}

// calculate original winding order of a polygon ring
// AP: separated to get original winding order
function windingOrder(data, start, end, dim) {
	var sum = 0;
    for (i = start, j = end - dim; i < end; i += dim) {
        sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
        j = i;
    }
	//true for clockwise
	return sum > 0;
}

// create a circular doubly linked list from polygon points in the specified winding order
// AP: oclockwise = original winding order
function linkedList(data, start, end, dim, clockwise, oclockwise) {
    var i, j, last;

    // link points into circular doubly-linked list in the specified winding order
    if (clockwise === oclockwise) {
        for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
    } else {
        for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
    }

    return last;
}
// eliminate colinear or duplicate points
function filterPoints(start, end) {
    if (!start) return start;
    if (!end) end = start;

    var p = start,
        again;
    do {
        again = false;

        if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
            removeNode(p);
            p = end = p.prev;
            if (p === p.next) return null;
            again = true;

        } else {
            p = p.next;
        }
    } while (again || p !== end);

    return end;
}

// main ear slicing loop which triangulates a polygon (given as a linked list)
function earcutLinked(ear, triangles, dim, minX, minY, size, pass) {
    if (!ear) return;

    // interlink polygon nodes in z-order
    if (!pass && size) indexCurve(ear, minX, minY, size);

    var stop = ear,
        prev, next;

    // iterate through ears, slicing them one by one
    while (ear.prev !== ear.next) {
        prev = ear.prev;
        next = ear.next;

        if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) {
            // cut off the triangle
            triangles.push(prev.i / dim);
            triangles.push(ear.i / dim);
            triangles.push(next.i / dim);

            removeNode(ear);

            // skipping the next vertice leads to less sliver triangles
            ear = next.next;
            stop = next.next;

            continue;
        }

        ear = next;

        // if we looped through the whole remaining polygon and can't find any more ears
        if (ear === stop) {
            // try filtering points and slicing again
            if (!pass) {
                earcutLinked(filterPoints(ear), triangles, dim, minX, minY, size, 1);

            // if this didn't work, try curing all small self-intersections locally
            } else if (pass === 1) {
                ear = cureLocalIntersections(ear, triangles, dim);
                earcutLinked(ear, triangles, dim, minX, minY, size, 2);

            // as a last resort, try splitting the remaining polygon into two
            } else if (pass === 2) {
                splitEarcut(ear, triangles, dim, minX, minY, size);
            }

            break;
        }
    }
}

// check whether a polygon node forms a valid ear with adjacent nodes
function isEar(ear) {
    var a = ear.prev,
        b = ear,
        c = ear.next;

    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear

    // now make sure we don't have other points inside the potential ear
    var p = ear.next.next;

    while (p !== ear.prev) {
        if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
            area(p.prev, p, p.next) >= 0) return false;
        p = p.next;
    }

    return true;
}

function isEarHashed(ear, minX, minY, size) {
    var a = ear.prev,
        b = ear,
        c = ear.next;

    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear

    // triangle bbox; min & max are calculated like this for speed
    var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
        minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
        maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
        maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);

    // z-order range for the current triangle bbox;
    var minZ = zOrder(minTX, minTY, minX, minY, size),
        maxZ = zOrder(maxTX, maxTY, minX, minY, size);

    // first look for points inside the triangle in increasing z-order
    var p = ear.nextZ;

    while (p && p.z <= maxZ) {
        if (p !== ear.prev && p !== ear.next &&
            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
            area(p.prev, p, p.next) >= 0) return false;
        p = p.nextZ;
    }

    // then look for points in decreasing z-order
    p = ear.prevZ;

    while (p && p.z >= minZ) {
        if (p !== ear.prev && p !== ear.next &&
            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
            area(p.prev, p, p.next) >= 0) return false;
        p = p.prevZ;
    }

    return true;
}

// go through all polygon nodes and cure small local self-intersections
function cureLocalIntersections(start, triangles, dim) {
    var p = start;
    do {
        var a = p.prev,
            b = p.next.next;

        // a self-intersection where edge (v[i-1],v[i]) intersects (v[i+1],v[i+2])
        if (intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {

            triangles.push(a.i / dim);
            triangles.push(p.i / dim);
            triangles.push(b.i / dim);

            // remove two nodes involved
            removeNode(p);
            removeNode(p.next);

            p = start = b;
        }
        p = p.next;
    } while (p !== start);

    return p;
}

// try splitting polygon into two and triangulate them independently
function splitEarcut(start, triangles, dim, minX, minY, size) {
    // look for a valid diagonal that divides the polygon into two
    var a = start;
    do {
        var b = a.next.next;
        while (b !== a.prev) {
            if (a.i !== b.i && isValidDiagonal(a, b)) {
                // split the polygon in two by the diagonal
                var c = splitPolygon(a, b);

                // filter colinear points around the cuts
                a = filterPoints(a, a.next);
                c = filterPoints(c, c.next);

                // run earcut on each half
                earcutLinked(a, triangles, dim, minX, minY, size);
                earcutLinked(c, triangles, dim, minX, minY, size);
                return;
            }
            b = b.next;
        }
        a = a.next;
    } while (a !== start);
}

// link every hole into the outer loop, producing a single-ring polygon without holes
function eliminateHoles(data, holeIndices, outerNode, dim) {
    var queue = [],
        i, len, start, end, list;

    for (i = 0, len = holeIndices.length; i < len; i++) {
        start = holeIndices[i] * dim;
        end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
        list = linkedList(data, start, end, dim, false);
        if (list === list.next) list.steiner = true;
        //list = filterPoints(list);
        //if (list)
        queue.push(getLeftmost(list));
    }

    queue.sort(compareX);

    // process holes from left to right
    for (i = 0; i < queue.length; i++) {
        eliminateHole(queue[i], outerNode);
        outerNode = filterPoints(outerNode, outerNode.next);
    }

    return outerNode;
}

function compareX(a, b) {
    return a.x - b.x;
}

// find a bridge between vertices that connects hole with an outer ring and and link it
function eliminateHole(hole, outerNode) {
    outerNode = findHoleBridge(hole, outerNode);
    if (outerNode) {
        var b = splitPolygon(outerNode, hole);
        filterPoints(b, b.next);
    }
}

// David Eberly's algorithm for finding a bridge between hole and outer polygon
function findHoleBridge(hole, outerNode) {
    var p = outerNode,
        hx = hole.x,
        hy = hole.y,
        qx = -Infinity,
        m;

    // find a segment intersected by a ray from the hole's leftmost point to the left;
    // segment's endpoint with lesser x will be potential connection point
    do {
        if (hy <= p.y && hy >= p.next.y) {
            var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
            if (x <= hx && x > qx) {
                qx = x;
                m = p.x < p.next.x ? p : p.next;
            }
        }
        p = p.next;
    } while (p !== outerNode);

    if (!m) return null;

    // look for points inside the triangle of hole point, segment intersection and endpoint;
    // if there are no points found, we have a valid connection;
    // otherwise choose the point of the minimum angle with the ray as connection point

    var stop = m,
        tanMin = Infinity,
        tan;

    p = m.next;

    while (p !== stop) {
        if (hx >= p.x && p.x >= m.x &&
                pointInTriangle(hy < m.y ? hx : qx, hy, m.x, m.y, hy < m.y ? qx : hx, hy, p.x, p.y)) {

            tan = Math.abs(hy - p.y) / (hx - p.x); // tangential

            if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) {
                m = p;
                tanMin = tan;
            }
        }

        p = p.next;
    }

    return m;
}

// interlink polygon nodes in z-order
function indexCurve(start, minX, minY, size) {
    var p = start;
    do {
        if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size);
        p.prevZ = p.prev;
        p.nextZ = p.next;
        p = p.next;
    } while (p !== start);

    p.prevZ.nextZ = null;
    p.prevZ = null;

    sortLinked(p);
}

// Simon Tatham's linked list merge sort algorithm
// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
function sortLinked(list) {
    var i, p, q, e, tail, numMerges, pSize, qSize,
        inSize = 1;

    do {
        p = list;
        list = null;
        tail = null;
        numMerges = 0;

        while (p) {
            numMerges++;
            q = p;
            pSize = 0;
            for (i = 0; i < inSize; i++) {
                pSize++;
                q = q.nextZ;
                if (!q) break;
            }

            qSize = inSize;

            while (pSize > 0 || (qSize > 0 && q)) {

                if (pSize === 0) {
                    e = q;
                    q = q.nextZ;
                    qSize--;
                } else if (qSize === 0 || !q) {
                    e = p;
                    p = p.nextZ;
                    pSize--;
                } else if (p.z <= q.z) {
                    e = p;
                    p = p.nextZ;
                    pSize--;
                } else {
                    e = q;
                    q = q.nextZ;
                    qSize--;
                }

                if (tail) tail.nextZ = e;
                else list = e;

                e.prevZ = tail;
                tail = e;
            }

            p = q;
        }

        tail.nextZ = null;
        inSize *= 2;

    } while (numMerges > 1);

    return list;
}

// z-order of a point given coords and size of the data bounding box
function zOrder(x, y, minX, minY, size) {
    // coords are transformed into non-negative 15-bit integer range
    x = 32767 * (x - minX) / size;
    y = 32767 * (y - minY) / size;

    x = (x | (x << 8)) & 0x00FF00FF;
    x = (x | (x << 4)) & 0x0F0F0F0F;
    x = (x | (x << 2)) & 0x33333333;
    x = (x | (x << 1)) & 0x55555555;

    y = (y | (y << 8)) & 0x00FF00FF;
    y = (y | (y << 4)) & 0x0F0F0F0F;
    y = (y | (y << 2)) & 0x33333333;
    y = (y | (y << 1)) & 0x55555555;

    return x | (y << 1);
}

// find the leftmost node of a polygon ring
function getLeftmost(start) {
    var p = start,
        leftmost = start;
    do {
        if (p.x < leftmost.x) leftmost = p;
        p = p.next;
    } while (p !== start);

    return leftmost;
}

// check if a point lies within a convex triangle
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
    return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
           (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
           (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
}

// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
function isValidDiagonal(a, b) {
    return equals(a, b) || a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
           locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
}

// signed area of a triangle
function area(p, q, r) {
    return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
}

// check if two points are equal
function equals(p1, p2) {
    return p1.x === p2.x && p1.y === p2.y;
}

// check if two segments intersect
function intersects(p1, q1, p2, q2) {
    return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 &&
           area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;
}

// check if a polygon diagonal intersects any polygon segments
function intersectsPolygon(a, b) {
    var p = a;
    do {
        if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
                intersects(p, p.next, a, b)) return true;
        p = p.next;
    } while (p !== a);

    return false;
}

// check if a polygon diagonal is locally inside the polygon
function locallyInside(a, b) {
    return area(a.prev, a, a.next) < 0 ?
        area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
        area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
}

// check if the middle point of a polygon diagonal is inside the polygon
function middleInside(a, b) {
    var p = a,
        inside = false,
        px = (a.x + b.x) / 2,
        py = (a.y + b.y) / 2;
    do {
        if (((p.y > py) !== (p.next.y > py)) && (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
            inside = !inside;
        p = p.next;
    } while (p !== a);

    return inside;
}

// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
// if one belongs to the outer ring and another to a hole, it merges it into a single ring
function splitPolygon(a, b) {
    var a2 = new Node(a.i, a.x, a.y),
        b2 = new Node(b.i, b.x, b.y),
        an = a.next,
        bp = b.prev;

    a.next = b;
    b.prev = a;

    a2.next = an;
    an.prev = a2;

    b2.next = a2;
    a2.prev = b2;

    bp.next = b2;
    b2.prev = bp;

    return b2;
}

// create a node and optionally link it with previous one (in a circular doubly linked list)
function insertNode(i, x, y, last) {
    var p = new Node(i, x, y);

    if (!last) {
        p.prev = p;
        p.next = p;

    } else {
        p.next = last.next;
        p.prev = last;
        last.next.prev = p;
        last.next = p;
    }
    return p;
}

function removeNode(p) {
    p.next.prev = p.prev;
    p.prev.next = p.next;

    if (p.prevZ) p.prevZ.nextZ = p.nextZ;
    if (p.nextZ) p.nextZ.prevZ = p.prevZ;
}

function Node(i, x, y) {
    // vertice index in coordinates array
    this.i = i;

    // vertex coordinates
    this.x = x;
    this.y = y;

    // previous and next vertice nodes in a polygon ring
    this.prev = null;
    this.next = null;

    // z-order curve value
    this.z = null;

    // previous and next nodes in z-order
    this.prevZ = null;
    this.nextZ = null;

    // indicates whether this is a steiner point
    this.steiner = false;
}
}
}

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

x3dom.FieldInterpolator = function( beginTime, endTime, beginValue, endValue )
{
	this.beginTime = beginTime || 0;
	this.endTime   = endTime || 1;
	this.beginValue = beginValue || 0;
	this.endValue   = endValue || 0;
	this.isInterpolating = false;
};

x3dom.FieldInterpolator.prototype.isActive = function()
{
    return ( this.beginTime > 0 );
};

x3dom.FieldInterpolator.prototype.calcFraction = function(time)
{
    var fraction = ( time - this.beginTime ) / ( this.endTime - this.beginTime );
    return ( Math.sin( ( fraction * Math.PI ) - ( Math.PI / 2 ) ) + 1 ) / 2.0;
};

x3dom.FieldInterpolator.prototype.reset = function()
{
	this.isInterpolating = false;
    this.beginTime = 0;
    this.endTime = 1;
	this.beginValue = 0;
	this.endValue   = 0;
};

x3dom.FieldInterpolator.prototype.interpolate = function( time )
{
	if ( time < this.beginTime )
	{
		return this.beginValue;
	}
	else if ( time >= this.endTime )
	{
		var endValue = this.endValue;
		
		this.reset();
		
		return endValue;
	}
	else
	{
		this.isInterpolating = true;
		return this.beginValue + ( this.endValue - this.beginValue ) * this.calcFraction( time );
	}
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/*****************************************************************************
* Utils class holds utility functions for renderer
*****************************************************************************/
x3dom.Utils = {};

x3dom.Utils.maxIndexableCoords = 65535;
x3dom.Utils.needLineWidth = false;  // lineWidth not impl. in IE11
x3dom.Utils.measurements = [];


// http://gent.ilcore.com/2012/06/better-timer-for-javascript.html
window.performance = window.performance || {};
performance.now = (function () {
    return performance.now ||
           performance.mozNow ||
           performance.msNow ||
           performance.oNow ||
           performance.webkitNow ||
           function () {
               return new Date().getTime();
           };
})();

x3dom.Utils.startMeasure = function (name) {
    var uname = name.toUpperCase();
    if (!x3dom.Utils.measurements[uname]) {
        if (performance && performance.now) {
            x3dom.Utils.measurements[uname] = performance.now();
        } else {
            x3dom.Utils.measurements[uname] = new Date().getTime();
        }
    }
};

x3dom.Utils.stopMeasure = function (name) {
    var uname = name.toUpperCase();
    if (x3dom.Utils.measurements[uname]) {
        var startTime = x3dom.Utils.measurements[uname];
        delete x3dom.Utils.measurements[uname];
        if (performance && performance.now) {
            return performance.now() - startTime;
        } else {
            return new Date().getTime() - startTime;
        }
    }
    return 0;
};

/*****************************************************************************
 *
 *****************************************************************************/
x3dom.Utils.isNumber = function(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
};

/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.createTexture2D = function(gl, doc, src, bgnd, crossOrigin, scale, genMipMaps)
{
	var texture = gl.createTexture();

    //Create a black 4 pixel texture to prevent 'texture not complete' warning
    var data = new Uint8Array([0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255]);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
    if (genMipMaps) {
        gl.generateMipmap(gl.TEXTURE_2D);
    }
    gl.bindTexture(gl.TEXTURE_2D, null);

    texture.ready = false;

	if (src == null || src == '')
	    return texture;

	var image = new Image();

    switch(crossOrigin.toLowerCase()) {
        case 'anonymous': {
            image.crossOrigin = 'anonymous';
        } break;
        case 'use-credentials': {
            image.crossOrigin = 'use-credentials'
        } break;
        case 'none': {
            //this is needed to omit the default case, if default is none, erase this and the default case
        } break;
        default: {
            if(x3dom.Utils.forbiddenBySOP(src)) {
                image.crossOrigin = 'anonymous';
            }
        }
    }

	image.src = src;

	doc.downloadCount++;

	image.onload = function() {
		
		texture.originalWidth  = image.width;
		texture.originalHeight = image.height;
		
        if (scale)
		    image = x3dom.Utils.scaleImage( image );

		if(bgnd == true) {
			gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
		}
		gl.bindTexture(gl.TEXTURE_2D, texture);
		//gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        if (genMipMaps) {
            gl.generateMipmap(gl.TEXTURE_2D);
        }
		gl.bindTexture(gl.TEXTURE_2D, null);
		if(bgnd == true) {
			gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
		}

		//Save image size
		texture.width  = image.width;
		texture.height = image.height;
		texture.ready = true;

		doc.downloadCount--;
		doc.needRender = true;
	};

	image.onerror = function(error) {
    // Try loading the image as a compressed texture, if the extension is provided
    // by the platform.
    // Copyrigth (C) 2014 TOSHIBA
    // Dual licensed under the MIT and GPL licenses.
    // Based on code originally provided by　http://www.x3dom.org

   if(x3dom.caps.EXTENSIONS.indexOf('WEBGL_compressed_texture_s3tc') !== -1){
  		x3dom.Utils.tryCompressedTexture2D(texture, gl, doc, src, bgnd,
  		    crossOrigin, genMipMaps, function(success){
  		  if(success){
	      }else{
          x3dom.debug.logError("[Utils|createTexture2D] Can't load Image: " + src);
		    }
        doc.downloadCount--;
		  });
	  }else{
      x3dom.debug.logError("[Utils|createTexture2D] Can't load Image: " + src);
	    doc.downloadCount--;
    }
	};

	return texture;
};

/*****************************************************************************
*  Creating textures from S3TC compressed files.
*  Copyrigth (C) 2014 TOSHIBA
*  Dual licensed under the MIT and GPL licenses.
*  Based on code originally provided by
*  http://www.x3dom.org
*
*  S3TC file reading code originaly provided by Brandon Jones
*  (http://media.tojicode.com/)
*****************************************************************************/

x3dom.Utils.createCompressedTexture2D = function(gl, doc, src, bgnd, crossOrigin, genMipMaps)
{
  var texture = gl.createTexture();

    //Create a black 4 pixel texture to prevent 'texture not complete' warning
    var data = new Uint8Array([0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255]);
    gl.bindTexture(gl.TEXTURE_2D, texture);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
    if (genMipMaps) {
        gl.generateMipmap(gl.TEXTURE_2D);
    }
    gl.bindTexture(gl.TEXTURE_2D, null);

    texture.ready = false;

  if (src == null || src == '')
      return texture;

  //start loading

  ddsXhr = new XMLHttpRequest();

  var ext = gl.getExtension('WEBGL_compressed_texture_s3tc');

  ddsXhr.open('GET', src, true);
  ddsXhr.responseType = "arraybuffer";
  ddsXhr.onload = function() {
      gl.bindTexture(gl.TEXTURE_2D, texture);
      var mipmaps = uploadDDSLevels(gl, ext, this.response);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mipmaps > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);

      texture.ready = true;

      doc.downloadCount--;
      doc.needRender = true;
  };

  doc.downloadCount++;
  //ddsXhr.send(null);
  x3dom.RequestManager.addRequest(ddsXhr);

  return texture;
};

x3dom.Utils.tryCompressedTexture2D = function(texture, gl, doc, src, bgnd, crossOrigin, genMipMaps, cb)
{
  //start loading

  ddsXhr = new XMLHttpRequest();

  var ext = gl.getExtension('WEBGL_compressed_texture_s3tc');

  ddsXhr.open('GET', src, true);
  ddsXhr.responseType = "arraybuffer";
  ddsXhr.onload = function() {
      gl.bindTexture(gl.TEXTURE_2D, texture);
      var mipmaps = uploadDDSLevels(gl, ext, this.response);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mipmaps > 1 ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);

      texture.ready = true;

      doc.needRender = true;

      cb(true);
  };

  ddsXhr.onerror = function() {
      cb(false);
  };

  //ddsXhr.send(null);
  x3dom.RequestManager.addRequest(ddsXhr);
};


/*****************************************************************************
*  Original code by  Brandon Jones
* http://media.tojicode.com/
*****************************************************************************/
function uploadDDSLevels(gl, ext, arrayBuffer, loadMipmaps) {
    var DDS_MAGIC = 0x20534444;

    var DDSD_CAPS = 0x1,
        DDSD_HEIGHT = 0x2,
        DDSD_WIDTH = 0x4,
        DDSD_PITCH = 0x8,
        DDSD_PIXELFORMAT = 0x1000,
        DDSD_MIPMAPCOUNT = 0x20000,
        DDSD_LINEARSIZE = 0x80000,
        DDSD_DEPTH = 0x800000;

    var DDSCAPS_COMPLEX = 0x8,
        DDSCAPS_MIPMAP = 0x400000,
        DDSCAPS_TEXTURE = 0x1000;

    var DDSCAPS2_CUBEMAP = 0x200,
        DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
        DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
        DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
        DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
        DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
        DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
        DDSCAPS2_VOLUME = 0x200000;

    var DDPF_ALPHAPIXELS = 0x1,
        DDPF_ALPHA = 0x2,
        DDPF_FOURCC = 0x4,
        DDPF_RGB = 0x40,
        DDPF_YUV = 0x200,
        DDPF_LUMINANCE = 0x20000;

    function FourCCToInt32(value) {
        return value.charCodeAt(0) +
            (value.charCodeAt(1) << 8) +
            (value.charCodeAt(2) << 16) +
            (value.charCodeAt(3) << 24);
    }

    function Int32ToFourCC(value) {
        return String.fromCharCode(
            value & 0xff,
            (value >> 8) & 0xff,
            (value >> 16) & 0xff,
            (value >> 24) & 0xff
        );
    }

    var FOURCC_DXT1 = FourCCToInt32("DXT1");
    var FOURCC_DXT5 = FourCCToInt32("DXT5");

    var headerLengthInt = 31; // The header length in 32 bit ints

    // Offsets into the header array
    var off_magic = 0;

    var off_size = 1;
    var off_flags = 2;
    var off_height = 3;
    var off_width = 4;

    var off_mipmapCount = 7;

    var off_pfFlags = 20;
    var off_pfFourCC = 21;

    var header = new Int32Array(arrayBuffer, 0, headerLengthInt),
        fourCC, blockBytes, internalFormat,
        width, height, dataLength, dataOffset,
        byteArray, mipmapCount, i;

    if(header[off_magic] != DDS_MAGIC) {
        console.error("Invalid magic number in DDS header");
        return 0;
    }

    if(!header[off_pfFlags] & DDPF_FOURCC) {
        console.error("Unsupported format, must contain a FourCC code");
        return 0;
    }

    fourCC = header[off_pfFourCC];
    switch(fourCC) {
        case FOURCC_DXT1:
            blockBytes = 8;
            internalFormat = ext.COMPRESSED_RGBA_S3TC_DXT1_EXT;
            break;

        case FOURCC_DXT5:
            blockBytes = 16;
            internalFormat = ext.COMPRESSED_RGBA_S3TC_DXT5_EXT;
            break;

        default:
            console.error("Unsupported FourCC code:", Int32ToFourCC(fourCC));
            return null;
    }

    mipmapCount = 1;
    if(header[off_flags] & DDSD_MIPMAPCOUNT && loadMipmaps !== false) {
        mipmapCount = Math.max(1, header[off_mipmapCount]);
    }

    width = header[off_width];
    height = header[off_height];
    dataOffset = header[off_size] + 4;

    for(i = 0; i < mipmapCount; ++i) {
        dataLength = Math.max( 4, width )/4 * Math.max( 4, height )/4 * blockBytes;
        byteArray = new Uint8Array(arrayBuffer, dataOffset, dataLength);
        gl.compressedTexImage2D(gl.TEXTURE_2D, i, internalFormat, width, height, 0, byteArray);
        dataOffset += dataLength;
        width *= 0.5;
        height *= 0.5;
    }

    return mipmapCount;
};


/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.createTextureCube = function(gl, doc, src, bgnd, crossOrigin, scale, genMipMaps)
{
	var texture = gl.createTexture();

	var faces;
	if (bgnd) {
		faces = [gl.TEXTURE_CUBE_MAP_POSITIVE_Z, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z,
				 gl.TEXTURE_CUBE_MAP_POSITIVE_Y, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
				 gl.TEXTURE_CUBE_MAP_POSITIVE_X, gl.TEXTURE_CUBE_MAP_NEGATIVE_X];
	}
	else
	{
		//       back, front, bottom, top, left, right
		faces = [gl.TEXTURE_CUBE_MAP_NEGATIVE_Z, gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
				 gl.TEXTURE_CUBE_MAP_NEGATIVE_Y, gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
				 gl.TEXTURE_CUBE_MAP_NEGATIVE_X, gl.TEXTURE_CUBE_MAP_POSITIVE_X];
	}

    texture.ready = false;
    texture.pendingTextureLoads = -1;
    texture.textureCubeReady = false;

    var width = 0, height = 0;

	for (var i=0; i<faces.length; i++) {
		var face = faces[i];

		var image = new Image();

        switch(crossOrigin.toLowerCase()) {
            case 'anonymous': {
                image.crossOrigin = 'anonymous';
            } break;
            case 'use-credentials': {
                image.crossOrigin = 'use-credentials'
            } break;
            case 'none': {
                //this is needed to omit the default case, if default is none, erase this and the default case
            } break;
            default: {
                if(x3dom.Utils.forbiddenBySOP(src[i])) {
                    image.crossOrigin = 'anonymous';
                }
            }
        }

		texture.pendingTextureLoads++;
		doc.downloadCount++;

		image.onload = (function(texture, face, image, swap) {
			return function() {
				if (width == 0 && height == 0) {
					width = image.width;
					height = image.height;
				}
				else if (scale && (width != image.width || height != image.height)) {
					x3dom.debug.logWarning("[Utils|createTextureCube] Rescaling CubeMap images, which are of different size!");
					image = x3dom.Utils.rescaleImage(image, width, height);
				}

				gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, swap);

				gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
				gl.texImage2D(face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
				gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
				gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

				texture.pendingTextureLoads--;
				doc.downloadCount--;

				if (texture.pendingTextureLoads < 0) {
                    //Save image size also for cube tex
                    texture.width  = width;
                    texture.height = height;
					texture.textureCubeReady = true;

                    if (genMipMaps) {
                        gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
                        gl.generateMipmap(gl.TEXTURE_CUBE_MAP);
                        gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
                    }

					x3dom.debug.logInfo("[Utils|createTextureCube] Loading CubeMap finished...");
					doc.needRender = true;
				}
			};
		})( texture, face, image, bgnd );

		image.onerror = function()
		{
			doc.downloadCount--;

			x3dom.debug.logError("[Utils|createTextureCube] Can't load CubeMap!");
		};

		// backUrl, frontUrl, bottomUrl, topUrl, leftUrl, rightUrl (for bgnd)
		image.src = src[i];
	}

	return texture;
};

/*****************************************************************************
 * Initialize framebuffer object and associated texture(s)
 *****************************************************************************/
x3dom.Utils.initFBO = function(gl, w, h, type, mipMap, needDepthBuf, numMrt) {
    
	var tex = gl.createTexture();
    tex.width  = w;
    tex.height = h;

    gl.bindTexture(gl.TEXTURE_2D, tex);
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, type, null);
    if (mipMap)
        gl.generateMipmap(gl.TEXTURE_2D);
    gl.bindTexture(gl.TEXTURE_2D, null);

    var i, mrts = null;

    if (x3dom.caps.DRAW_BUFFERS && numMrt !== undefined) {
        mrts = [ tex ];

        for (i=1; i<numMrt; i++) {
            mrts[i] = gl.createTexture();
            mrts[i].width  = w;
            mrts[i].height = h;

            gl.bindTexture(gl.TEXTURE_2D, mrts[i]);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, type, null);
            if (mipMap)
                gl.generateMipmap(gl.TEXTURE_2D);
            gl.bindTexture(gl.TEXTURE_2D, null);
        }
    }

    var fbo = gl.createFramebuffer();
    var dtex = null;
    var rb = null;

    if (needDepthBuf) {
        if(x3dom.caps.DEPTH_TEXTURE !== null) {
            dtex = gl.createTexture();
            gl.bindTexture(gl.TEXTURE_2D, dtex);
            gl.texImage2D(gl.TEXTURE_2D, 0, gl.DEPTH_COMPONENT, w, h, 0, gl.DEPTH_COMPONENT, gl.UNSIGNED_SHORT, null);
            if(mipMap)
                gl.generateMipmap(gl.TEXTURE_2D);
            gl.bindTexture(gl.TEXTURE_2D, null);
            dtex.width = w;
            dtex.height = h;
        }
        else {
            rb = gl.createRenderbuffer();
            
            gl.bindRenderbuffer(gl.RENDERBUFFER, rb);
            gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, w, h);
            gl.bindRenderbuffer(gl.RENDERBUFFER, null);
        }
    }

    gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);

    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
    if (x3dom.caps.DRAW_BUFFERS && numMrt !== undefined) {
        for (i=1; i<numMrt; i++) {
            gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, mrts[i], 0);
        }
    }
    
    if(needDepthBuf && x3dom.caps.DEPTH_TEXTURE !== null) {
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.TEXTURE_2D, dtex, 0);
    }
    else {
        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, rb);
    }

    var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);
	
    if (status != gl.FRAMEBUFFER_COMPLETE) {
        x3dom.debug.logWarning("[Utils|InitFBO] FBO-Status: " + status);
    }

    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
	
    return {
        fbo: fbo, dtex: dtex, rbo: rb,
        tex: tex, texTargets: mrts,
        width: w, height: h,
        type: type, mipMap: mipMap
    };
};

/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.getFileName = function(url)
{
	var filename;

	if( url.lastIndexOf("/") > -1 ) {
		filename = url.substr( url.lastIndexOf("/") + 1 );
	}
	else if( url.lastIndexOf("\\") > -1 ) {
		filename = url.substr( url.lastIndexOf("\\") + 1 );
	}
	else {
		filename = url;
	}

	return filename;
};

/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.isWebGL2Enabled = function()
{
	var canvas = document.createElement("canvas");
	
	var webgl2 = canvas.getContext("webgl2") || canvas.getContext("experimental-webgl2");
	
	return ( webgl2 ) ? true : false;
};

/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.findTextureByName = function(texture, name)
{
	for ( var i=0; i<texture.length; ++i )
	{
		if ( name == texture[i].samplerName )
			return texture[i];
	}
	return false;
};

/*****************************************************************************
* Rescale image to given size
*****************************************************************************/
x3dom.Utils.rescaleImage = function(image, width, height)
{
	var canvas = document.createElement("canvas");
	canvas.width = width; canvas.height = height;
	canvas.getContext("2d").drawImage(image,
				0, 0, image.width, image.height,
				0, 0, canvas.width, canvas.height);
	return canvas;
};

/*****************************************************************************
* Scale image to next best power of two
*****************************************************************************/
x3dom.Utils.scaleImage = function(image)
{
	if (!x3dom.Utils.isPowerOfTwo(image.width) || !x3dom.Utils.isPowerOfTwo(image.height)) {
		var canvas = document.createElement("canvas");
		canvas.width = x3dom.Utils.nextHighestPowerOfTwo(image.width);
		canvas.height = x3dom.Utils.nextHighestPowerOfTwo(image.height);
		var ctx = canvas.getContext("2d");
		ctx.drawImage(image,
					  0, 0, image.width, image.height,
					  0, 0, canvas.width, canvas.height);
		image = canvas;
	}
	return image;
};


/*****************************************************************************
* Check if value is power of two
*****************************************************************************/
x3dom.Utils.isPowerOfTwo = function(x)
{
	return ((x & (x - 1)) === 0);
};


/*****************************************************************************
* Return next highest power of two
*****************************************************************************/
x3dom.Utils.nextHighestPowerOfTwo = function(x)
{
	--x;
	for (var i = 1; i < 32; i <<= 1) {
		x = x | x >> i;
	}
	return (x + 1);
};


/*****************************************************************************
* Return next best power of two
*****************************************************************************/
x3dom.Utils.nextBestPowerOfTwo = function(x)
{
    // use precomputed log(2.0) = 0.693147180559945
	var log2x = Math.log(x) / 0.693147180559945;
	return Math.pow(2, Math.round(log2x));
};

/*****************************************************************************
* Return data type size in byte
*****************************************************************************/
x3dom.Utils.getDataTypeSize = function(type)
{
	switch(type)
	{
		case "Int8":
		case "Uint8":
			return 1;
		case "Int16":
		case "Uint16":
			return 2;
		case "Int32":
		case "Uint32":
		case "Float32":
			return 4;
		case "Float64":
		default:
			return 8;
	}
};

/*****************************************************************************
 * Return offset multiplier (Uint32 is twice as big as Uint16)
 *****************************************************************************/
x3dom.Utils.getOffsetMultiplier = function(indexType, gl)
{
    switch(indexType)
    {
        case gl.UNSIGNED_SHORT:
            return 1;
        case gl.UNSIGNED_INT:
            return 2;
        case gl.UNSIGNED_BYTE:
            return 0.5;
        default:
            return 1;
    }
};

/*****************************************************************************
 * Return byte aware offset
 *****************************************************************************/
x3dom.Utils.getByteAwareOffset = function(offset, indexType, gl)
{
    switch(indexType)
    {
        case gl.UNSIGNED_SHORT:
            return 2 * offset;
        case gl.UNSIGNED_INT:
            return 4 * offset;
        case gl.UNSIGNED_BYTE:
            return offset;
        default:
            return 2 * offset;
    }
};

/*****************************************************************************
* Return this.gl-Type
*****************************************************************************/
x3dom.Utils.getVertexAttribType = function(type, gl)
{
	var dataType = gl.NONE;

	switch(type)
	{
		case "Int8":
			dataType = gl.BYTE;
			break;
		case "Uint8":
			dataType = gl.UNSIGNED_BYTE;
			break;
		case "Int16":
			dataType = gl.SHORT;
			break;
		case "Uint16":
			dataType = gl.UNSIGNED_SHORT;
			break;
		case "Int32":
			dataType = gl.INT;
			break;
		case "Uint32":
			dataType = gl.UNSIGNED_INT;
			break;
		case "Float32":
			dataType = gl.FLOAT;
			break;
		case "Float64":
		default:
			x3dom.debug.logError("Can't find this.gl data type for " + type + ", getting FLOAT...");
			dataType = gl.FLOAT;
			break;
	}

	return dataType;
};

/*****************************************************************************
* Return TypedArray View
*****************************************************************************/
x3dom.Utils.getArrayBufferView = function(type, buffer)
{
	var array = null;

	switch(type)
	{
		case "Int8":
			array = new Int8Array(buffer);
			break;
		case "Uint8":
			array = new Uint8Array(buffer);
			break;
		case "Int16":
			array = new Int16Array(buffer);
			break;
		case "Uint16":
			array = new Uint16Array(buffer);
			break;
		case "Int32":
			array = new Int32Array(buffer);
			break;
		case "Uint32":
			array = new Uint32Array(buffer);
			break;
		case "Float32":
			array = new Float32Array(buffer);
			break;
		case "Float64":
			array = new Float64Array(buffer);
			break;
		default:
			x3dom.debug.logError("Can't create typed array view of type " + type + ", trying Float32...");
			array = new Float32Array(buffer);
			break;
	}

	return array;
};

/*****************************************************************************
* Checks whether a TypedArray View Type with the given name string is unsigned
*****************************************************************************/
x3dom.Utils.isUnsignedType = function (str)
{
  return (str == "Uint8" || str == "Uint16" || str == "Uint16" || str == "Uint32");
};


/*****************************************************************************
* Checks for lighting
*****************************************************************************/
x3dom.Utils.checkDirtyLighting = function(viewarea)
{
	return (viewarea.getLights().length + viewarea._scene.getNavigationInfo()._vf.headlight);
};

/*****************************************************************************
 * Checks for environment
 *****************************************************************************/
x3dom.Utils.checkDirtyEnvironment = function(viewarea, shaderProperties)
{
    var environment = viewarea._scene.getEnvironment();

    return (shaderProperties.GAMMACORRECTION != environment._vf.gammaCorrectionDefault);
};

/*****************************************************************************
* Get GL min filter
*****************************************************************************/
x3dom.Utils.minFilterDic = function(gl, minFilter)
{
	switch(minFilter.toUpperCase())
	{
		case "NEAREST":                      return gl.NEAREST;
		case "LINEAR":                       return gl.LINEAR;
		case "NEAREST_MIPMAP_NEAREST":       return gl.NEAREST_MIPMAP_NEAREST;
		case "NEAREST_MIPMAP_LINEAR":        return gl.NEAREST_MIPMAP_LINEAR;
		case "LINEAR_MIPMAP_NEAREST":        return gl.LINEAR_MIPMAP_NEAREST;
		case "LINEAR_MIPMAP_LINEAR":         return gl.LINEAR_MIPMAP_LINEAR;
		case "AVG_PIXEL":                    return gl.LINEAR;
		case "AVG_PIXEL_AVG_MIPMAP":         return gl.LINEAR_MIPMAP_LINEAR;
		case "AVG_PIXEL_NEAREST_MIPMAP":     return gl.LINEAR_MIPMAP_NEAREST;
		case "DEFAULT":                      return gl.LINEAR_MIPMAP_LINEAR;
		case "FASTEST":                      return gl.NEAREST;
		case "NEAREST_PIXEL":                return gl.NEAREST;
		case "NEAREST_PIXEL_AVG_MIPMAP":     return gl.NEAREST_MIPMAP_LINEAR;
		case "NEAREST_PIXEL_NEAREST_MIPMAP": return gl.NEAREST_MIPMAP_NEAREST;
		case "NICEST":                       return gl.LINEAR_MIPMAP_LINEAR;
		default:							 return gl.LINEAR;
	}
};

/*****************************************************************************
* Get GL mag filter
*****************************************************************************/
x3dom.Utils.magFilterDic = function(gl, magFilter)
{
	switch(magFilter.toUpperCase())
	{
		case "NEAREST": 		return gl.NEAREST;
		case "LINEAR":			return gl.LINEAR;
		case "AVG_PIXEL":		return gl.LINEAR;
		case "DEFAULT":			return gl.LINEAR;
		case "FASTEST":			return gl.NEAREST;
		case "NEAREST_PIXEL":	return gl.NEAREST;
		case "NICEST":			return gl.LINEAR;
		default:				return gl.LINEAR;
	}
};

/*****************************************************************************
* Get GL boundary mode
*****************************************************************************/
x3dom.Utils.boundaryModesDic = function(gl, mode)
{
	switch(mode.toUpperCase())
	{
		case "CLAMP":             return gl.CLAMP_TO_EDGE;
		case "CLAMP_TO_EDGE":     return gl.CLAMP_TO_EDGE;
		case "CLAMP_TO_BOUNDARY": return gl.CLAMP_TO_EDGE;
		case "MIRRORED_REPEAT":   return gl.MIRRORED_REPEAT;
		case "REPEAT":            return gl.REPEAT;
		default:				  return gl.REPEAT;
	}
};

/*****************************************************************************
 * Get GL primitive type
 *****************************************************************************/
x3dom.Utils.primTypeDic = function(gl, type)
{
    switch(type.toUpperCase())
    {
        case "POINTS":        return gl.POINTS;
        case "LINES":         return gl.LINES;
        case "LINELOOP":      return gl.LINE_LOOP;
        case "LINESTRIP":     return gl.LINE_STRIP;
        case "TRIANGLES":     return gl.TRIANGLES;
        case "TRIANGLESTRIP": return gl.TRIANGLE_STRIP;
        case "TRIANGLEFAN":   return gl.TRIANGLE_FAN;
        default:              return gl.TRIANGLES;
    }
};

/*****************************************************************************
* Get GL depth function
*****************************************************************************/
x3dom.Utils.depthFunc = function(gl, func)
{
	switch(func.toUpperCase())
	{
		case "NEVER":             return gl.NEVER;
		case "ALWAYS":            return gl.ALWAYS;
		case "LESS":              return gl.LESS;
		case "EQUAL":             return gl.EQUAL;
		case "LEQUAL":            return gl.LEQUAL;
        case "GREATER":           return gl.GREATER;
        case "GEQUAL":            return gl.GEQUAL;
        case "NOTEQUAL":          return gl.NOTEQUAL;
		default:				  return gl.LEQUAL;
	}
};

/*****************************************************************************
 * Get GL blend function
 *****************************************************************************/
x3dom.Utils.blendFunc = function(gl, func)
{
    switch(func.toLowerCase())
    {
        case "zero":                        return gl.ZERO;
        case "one":                         return gl.ONE;
        case "dst_color":                   return gl.DST_COLOR;
        case "dst_alpha":                   return gl.DST_ALPHA;
        case "src_color":                   return gl.SRC_COLOR;
        case "src_alpha":                   return gl.SRC_ALPHA;
        case "one_minus_dst_color":         return gl.ONE_MINUS_DST_COLOR;
        case "one_minus_dst_alpha":         return gl.ONE_MINUS_DST_ALPHA;
        case "one_minus_src_color":         return gl.ONE_MINUS_SRC_COLOR;
        case "one_minus_src_alpha":         return gl.ONE_MINUS_SRC_ALPHA;
        case "src_alpha_saturate":          return gl.SRC_ALPHA_SATURATE;
        case "constant_color":              return gl.CONSTANT_COLOR;
        case "constant_alpha":              return gl.CONSTANT_ALPHA;
        case "one_minus_constant_color":    return gl.ONE_MINUS_CONSTANT_COLOR;
        case "one_minus_constant_alpha":    return gl.ONE_MINUS_CONSTANT_ALPHA;
        default:				            return 0;
    }
};

/*****************************************************************************
 * Get GL blend equations
 *****************************************************************************/
x3dom.Utils.blendEquation = function(gl, func)
{
    switch(func.toLowerCase())
    {
        case "func_add":                return gl.FUNC_ADD;
        case "func_subtract":           return gl.FUNC_SUBTRACT;
        case "func_reverse_subtract":   return gl.FUNC_REVERSE_SUBTRACT;
        case "min":                     return 0;  //Not supported yet
        case "max":                     return 0;  //Not supported yet
        case "logic_op":                return 0;  //Not supported yet
        default:				        return 0;
    }
};

/*****************************************************************************
 * Try to gunzip arraybuffer, otherwise return unmodified arraybuffer
 *****************************************************************************/
x3dom.Utils.gunzip = function (arraybuffer)
{
    var byteArray = new Uint8Array(arraybuffer);

    try {
        arraybuffer = new Zlib.Gunzip(byteArray).decompress().buffer;
    } catch (e) {
        //Decompression failed, file is not compressed.
    }

    return arraybuffer;
};

/*****************************************************************************
*
*****************************************************************************/
x3dom.Utils.generateProperties = function (viewarea, shape)
{
	var property = {};

	var geometry 	= shape._cf.geometry.node;
	var appearance 	= shape._cf.appearance.node;
	var texture 	= appearance ? appearance._cf.texture.node : null;
	var material    = appearance ? appearance._cf.material.node : null;
    var environment = viewarea._scene.getEnvironment();

	//Check if it's a composed shader
	if (appearance && appearance._shader &&
        x3dom.isa(appearance._shader, x3dom.nodeTypes.ComposedShader)) {

		property.CSHADER          = appearance._shader._id; //shape._objectID;
	}
    else if (geometry) {

        property.CSHADER          = -1;
        property.SOLID            = (shape.isSolid()) ? 1 : 0;
        property.TEXT             = (x3dom.isa(geometry, x3dom.nodeTypes.Text)) ? 1 : 0;
        property.POPGEOMETRY      = (x3dom.isa(geometry, x3dom.nodeTypes.PopGeometry)) ? 1 : 0;
        property.IMAGEGEOMETRY    = (x3dom.isa(geometry, x3dom.nodeTypes.ImageGeometry))  ? 1 : 0;
        property.BINARYGEOMETRY   = (x3dom.isa(geometry, x3dom.nodeTypes.BinaryGeometry))  ? 1 : 0;
		property.EXTERNALGEOMETRY = (x3dom.isa(geometry, x3dom.nodeTypes.ExternalGeometry))  ? 1 : 0;
        property.IG_PRECISION     = (property.IMAGEGEOMETRY) ? geometry.numCoordinateTextures() : 0;
        property.IG_INDEXED       = (property.IMAGEGEOMETRY && geometry.getIndexTexture() != null) ? 1 : 0;
        property.POINTLINE2D      = !geometry.needLighting() ? 1 : 0;
        property.VERTEXID         = ((property.BINARYGEOMETRY || property.EXTERNALGEOMETRY) && geometry._vf.idsPerVertex) ? 1 : 0;
        property.IS_PARTICLE      = (x3dom.isa(geometry, x3dom.nodeTypes.ParticleSet)) ? 1 : 0;


        property.TWOSIDEDMAT      = ( property.APPMAT && x3dom.isa(material, x3dom.nodeTypes.TwoSidedMaterial)) ? 1 : 0;
        property.SEPARATEBACKMAT  = ( property.TWOSIDEDMAT && material._vf.separateBackColor) ? 1 : 0;
        property.SHADOW           = (viewarea.getLightsShadow()) ? 1 : 0;
        property.FOG              = (viewarea._scene.getFog()._vf.visibilityRange > 0) ? 1 : 0;
        property.CSSHADER         = (appearance && appearance._shader &&
                                     x3dom.isa(appearance._shader, x3dom.nodeTypes.CommonSurfaceShader)) ? 1 : 0;
        property.APPMAT           = (appearance && (material || property.CSSHADER) ) ? 1 : 0;
        property.LIGHTS           = (!property.POINTLINE2D && appearance && shape.isLit() && (material || property.CSSHADER)) ?
                                     viewarea.getLights().length + (viewarea._scene.getNavigationInfo()._vf.headlight) : 0;
        property.TEXTURED         = (texture || property.TEXT || ( property.CSSHADER && appearance._shader.needTexcoords() ) ) ? 1 : 0;
        property.CUBEMAP          = (texture && x3dom.isa(texture, x3dom.nodeTypes.X3DEnvironmentTextureNode)) ||
                                    (property.CSSHADER && appearance._shader.getEnvironmentMap()) ? 1 : 0;
        property.PIXELTEX         = (texture && x3dom.isa(texture, x3dom.nodeTypes.PixelTexture)) ? 1 : 0;
        property.TEXTRAFO         = (appearance && appearance._cf.textureTransform.node) ? 1 : 0;
        property.DIFFUSEMAP       = (texture && !x3dom.isa(texture, x3dom.nodeTypes.X3DEnvironmentTextureNode) ) || (property.CSSHADER && appearance._shader.getDiffuseMap()) ? 1 : 0;
        property.NORMALMAP        = (property.CSSHADER && appearance._shader.getNormalMap()) ? 1 : 0;
		property.NORMALSPACE      = (property.NORMALMAP) ? appearance._shader._vf.normalSpace.toUpperCase() : "";
        property.SPECMAP          = (property.CSSHADER && appearance._shader.getSpecularMap()) ? 1 : 0;
        property.SHINMAP          = (property.CSSHADER && appearance._shader.getShininessMap()) ? 1 : 0;
        property.DISPLACEMENTMAP  = (property.CSSHADER && appearance._shader.getDisplacementMap()) ? 1 : 0;
        property.DIFFPLACEMENTMAP = (property.CSSHADER && appearance._shader.getDiffuseDisplacementMap()) ? 1 : 0;
        property.MULTIDIFFALPMAP  = (property.VERTEXID && property.CSSHADER && appearance._shader.getMultiDiffuseAlphaMap()) ? 1 : 0;
        property.MULTIEMIAMBMAP   = (property.VERTEXID && property.CSSHADER && appearance._shader.getMultiEmissiveAmbientMap()) ? 1 : 0;
        property.MULTISPECSHINMAP = (property.VERTEXID && property.CSSHADER && appearance._shader.getMultiSpecularShininessMap()) ? 1 : 0;
        property.MULTIVISMAP      = (property.VERTEXID && property.CSSHADER && appearance._shader.getMultiVisibilityMap()) ? 1 : 0;

        property.BLENDING         = (property.TEXT || property.CUBEMAP || property.CSSHADER || (texture && texture._blending)) ? 1 : 0;
        property.REQUIREBBOX      = (geometry._vf.coordType !== undefined && geometry._vf.coordType != "Float32") ? 1 : 0;
        property.REQUIREBBOXNOR   = (geometry._vf.normalType !== undefined && geometry._vf.normalType != "Float32") ? 1 : 0;
        property.REQUIREBBOXCOL   = (geometry._vf.colorType !== undefined && geometry._vf.colorType != "Float32") ? 1 : 0;
        property.REQUIREBBOXTEX   = (geometry._vf.texCoordType !== undefined && geometry._vf.texCoordType != "Float32") ? 1 : 0;
        property.COLCOMPONENTS    = geometry._mesh._numColComponents;
        property.NORCOMPONENTS    = geometry._mesh._numNormComponents;
        property.POSCOMPONENTS    = geometry._mesh._numPosComponents;
        property.SPHEREMAPPING    = (geometry._cf.texCoord !== undefined && geometry._cf.texCoord.node !== null &&
                                     geometry._cf.texCoord.node._vf.mode &&
                                     geometry._cf.texCoord.node._vf.mode.toLowerCase() == "sphere") ? 1 : 0;
        property.VERTEXCOLOR      = (geometry._mesh._colors[0].length > 0 ||
                                     (property.IMAGEGEOMETRY && geometry.getColorTexture()) ||
                                     (property.POPGEOMETRY    && geometry.hasColor()) ||
                                     (geometry._vf.color !== undefined && geometry._vf.color.length > 0)) ? 1 : 0;
        property.CLIPPLANES       = shape._clipPlanes.length;
		property.ALPHATHRESHOLD	  = (appearance) ? appearance._vf.alphaClipThreshold.toFixed(2) : 0.1;

        property.GAMMACORRECTION  = environment._vf.gammaCorrectionDefault;

        property.KHR_MATERIAL_COMMONS = 0;
        //console.log(property);
	}

	property.toIdentifier = function() {
        delete this.id;
		var id = "";
		for(var p in this) {
			if(this[p] != this.toIdentifier && this[p] != this.toString) {
				id += this[p];
			}
		}
        this.id = id;
		return id;
	};

	property.toString = function() {
		var str = "";
		for(var p in this) {
			if(this[p] != this.toIdentifier && this[p] != this.toString) {
				str += p + ": " + this[p] + ", ";
			}
		}
		return str;
	};

    property.toIdentifier();

	return property;
};


/*****************************************************************************
* Returns "shader" such that "shader.foo = [1,2,3]" magically sets the
* appropriate uniform
*****************************************************************************/
x3dom.Utils.wrapProgram = function (gl, program, shaderID)
{
	var shader = {
        shaderID: shaderID,
        program: program
    };

	shader.bind = function () {
		gl.useProgram(program);
	};

	var loc = null;
	var obj = null;
	var i, glErr;

    // get uniforms
	var numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);

	for (i=0; i < numUniforms; ++i) {
		try {
			obj = gl.getActiveUniform(program, i);
		}
		catch (eu) {
            if (!obj) continue;
        }

        glErr = gl.getError();
        if (glErr) {
            x3dom.debug.logError("GL-Error (on searching uniforms): " + glErr);
        }

		loc = gl.getUniformLocation(program, obj.name);

		switch (obj.type) {
			case gl.SAMPLER_2D:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniform1i(loc, val); }; })(loc));
				break;
			case gl.SAMPLER_CUBE:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniform1i(loc, val); }; })(loc));
				break;
			case gl.BOOL:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniform1i(loc, val); }; })(loc));
				break;
			case gl.FLOAT:
                /*
                 * Passing a MFFloat type into uniform.
                 * by Sofiane Benchaa, 2012.
                 *
                 * Based on OpenGL specification.
                 * url: http://www.opengl.org/sdk/docs/man/xhtml/glGetUniformLocation.xml
                 *
                 * excerpt : Except if the last part of name indicates a uniform variable array,
                 * the location of the first element of an array can be retrieved by using the name of the array,
                 * or by using the name appended by "[0]".
                 *
                 * Detecting the float array and extracting its uniform name without the brackets.
                 */
				if (obj.name.indexOf("[0]") != -1)
					shader.__defineSetter__(obj.name.substring(0, obj.name.length-3),
						(function (loc) { return function (val) { gl.uniform1fv(loc, new Float32Array(val)); }; })(loc));
				else
					shader.__defineSetter__(obj.name,
						(function (loc) { return function (val) { gl.uniform1f(loc, val); }; })(loc));
                break;
			case gl.FLOAT_VEC2:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniform2f(loc, val[0], val[1]); }; })(loc));
				break;
			case gl.FLOAT_VEC3:
				/* Passing arrays of vec3. see above.*/
				if (obj.name.indexOf("[0]") != -1)
					shader.__defineSetter__(obj.name.substring(0, obj.name.length-3),
						(function (loc) { return function (val) { gl.uniform3fv(loc, new Float32Array(val)); }; })(loc));
				else
					shader.__defineSetter__(obj.name,
						(function (loc) { return function (val) { gl.uniform3f(loc, val[0], val[1], val[2]); }; })(loc));
				break;
			case gl.FLOAT_VEC4:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniform4f(loc, val[0], val[1], val[2], val[3]); }; })(loc));
				break;
			case gl.FLOAT_MAT2:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniformMatrix2fv(loc, false, new Float32Array(val)); }; })(loc));
				break;
			case gl.FLOAT_MAT3:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniformMatrix3fv(loc, false, new Float32Array(val)); }; })(loc));
				break;
			case gl.FLOAT_MAT4:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniformMatrix4fv(loc, false, new Float32Array(val)); }; })(loc));
				break;
			case gl.INT:
				shader.__defineSetter__(obj.name,
					(function (loc) { return function (val) { gl.uniform1i(loc, val); }; }) (loc));
				break;
			default:
				x3dom.debug.logWarning('GLSL program variable '+obj.name+' has unknown type '+obj.type);
		}
	}

    // get attributes
	var numAttribs = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);

	for (i=0; i < numAttribs; ++i) {
		try {
			obj = gl.getActiveAttrib(program, i);
		}
		catch (ea) {
            if (!obj) continue;
        }

        glErr = gl.getError();
        if (glErr) {
            x3dom.debug.logError("GL-Error (on searching attributes): " + glErr);
        }

		loc = gl.getAttribLocation(program, obj.name);
		shader[obj.name] = loc;
	}

	return shader;
};


/**
 * Matches a given URI with document.location. If domain, port and protocol are the same SOP won't forbid access to the resource.
 * @param {String} uri_string
 * @returns {boolean}
 */
x3dom.Utils.forbiddenBySOP = function (uri_string) {

    uri_string = uri_string.toLowerCase();
    // scheme ":" hier-part [ "?" query ] [ "#" fragment ]
    var Scheme_AuthorityPQF = uri_string.split('//'); //Scheme and AuthorityPathQueryFragment
    var Scheme;
    var AuthorityPQF;
    var Authority;
    var UserInfo_HostPort;
    var HostPort;
    var Host_Port;
    var Port;
    var Host;
    var originPort = document.location.port === "" ? "80" : document.location.port;

    if (Scheme_AuthorityPQF.length === 2) { // if there is '//' authority is given;
        Scheme = Scheme_AuthorityPQF[0];
        AuthorityPQF = Scheme_AuthorityPQF[1];

        /*
         * The authority component is preceded by a double slash ("//") and is
         * terminated by the next slash ("/"), question mark ("?"), or number
         * sign ("#") character, or by the end of the URI.
         */
        Authority = AuthorityPQF.split('/')[0].split('?')[0].split('#')[0];

        //authority   = [ userinfo "@" ] host [ ":" port ]
        UserInfo_HostPort = Authority.split('@');
        if (UserInfo_HostPort.length === 1) { //No Userinfo given
            HostPort = UserInfo_HostPort[0];
        } else {
            HostPort = UserInfo_HostPort[1];
        }

        Host_Port = HostPort.split(':');
        Host = Host_Port[0];
        Port = Host_Port[1];
    } // else will return false for an invalid URL or URL without authority

    Port = Port || "80";
    Host = Host || document.location.host;
    Scheme = Scheme || document.location.protocol;
    return !(Port === originPort && Host === document.location.host && Scheme === document.location.protocol);
};
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * States namespace
 */
x3dom.States = function (x3dElem) {
    var that = this;
    this.active = false;

    this.viewer = document.createElement('div');
    this.viewer.id = 'x3dom-state-viewer';

    var title = document.createElement('div');
    title.className = 'x3dom-states-head';
    title.appendChild(document.createTextNode('x3dom'));

    var subTitle = document.createElement('span');
    subTitle.className = 'x3dom-states-head2';
    subTitle.appendChild(document.createTextNode('stats'));
    title.appendChild(subTitle);

    this.renderMode = document.createElement('div');
    this.renderMode.className = 'x3dom-states-rendermode-hardware';

    this.measureList = document.createElement('ul');
    this.measureList.className = 'x3dom-states-list';

    this.infoList = document.createElement('ul');
    this.infoList.className = 'x3dom-states-list';

    this.requestList = document.createElement('ul');
    this.requestList.className = 'x3dom-states-list';

    //this.viewer.appendChild(title);
    this.viewer.appendChild(this.renderMode);
    this.viewer.appendChild(this.measureList);
    this.viewer.appendChild(this.infoList);
    this.viewer.appendChild(this.requestList);

    /**
     * Disable the context menu
     */
    this.disableContextMenu = function (e) {
        e.preventDefault();
        e.stopPropagation();
        e.returnValue = false;
        return false;
    };

    /**
     * Add a seperator for thousands to the string
     */
    this.thousandSeperator = function (value) {
        return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
    };

    /**
     * Return numerical value to fixed length
     */
    this.toFixed = function (value) {
        var fixed = (value < 1) ? 2 : (value < 10) ? 2 : 2;
        return value.toFixed(fixed);
    };

    /**
     *
     */
    this.addItem = function ( list, key, value ) {
        var item = document.createElement('li');
        item.className = 'x3dom-states-item';

        var keyDiv = document.createElement('div');
        keyDiv.className = 'x3dom-states-item-title';
        keyDiv.appendChild(document.createTextNode(key));

        var valueDiv = document.createElement('div');
        valueDiv.className = 'x3dom-states-item-value';
        valueDiv.appendChild(document.createTextNode(value));

        item.appendChild(keyDiv);
        item.appendChild(valueDiv);

        list.appendChild(item);
    };

    /**
     * Update the states.
     */
    this.update = function () {
        if (!x3dElem.runtime && this.updateMethodID !== undefined) {
            clearInterval(this.updateMethodID);
            return;
        }

        var infos = x3dElem.runtime.states.infos;
        var measurements = x3dElem.runtime.states.measurements;

        var renderMode = x3dom.caps.RENDERMODE;

        if ( renderMode == "HARDWARE" ) {
            this.renderMode.innerHTML = "Hardware-Rendering";
            this.renderMode.className = 'x3dom-states-rendermode-hardware';
        } else if ( renderMode == "SOFTWARE" ) {
            this.renderMode.innerHTML = "Software-Rendering";
            this.renderMode.className = 'x3dom-states-rendermode-software';
        }


        //Clear measure list
        this.measureList.innerHTML = "";

        //Create list items
        for (var m in measurements)
        {
			if( measurements.hasOwnProperty( m ) )
			{
			    this.addItem(this.measureList, m, this.toFixed(measurements[m]) );
			}
        }

        //Clear info list
        this.infoList.innerHTML = "";

        //Create list items
        for (var i in infos)
        {
			if( infos.hasOwnProperty( i ) )
			{
                this.addItem(this.infoList, i, this.thousandSeperator(infos[i]) );
			}
        }

        //Clear request list
        this.requestList.innerHTML = "";

        this.addItem(this.requestList, "#ACTIVE", x3dom.RequestManager.activeRequests.length );
        this.addItem(this.requestList, "#TOTAL",  x3dom.RequestManager.totalRequests  );
        this.addItem(this.requestList, "#LOADED", x3dom.RequestManager.loadedRequests );
        this.addItem(this.requestList, "#FAILED", x3dom.RequestManager.failedRequests );
    };

    this.updateMethodID = window.setInterval(function () {
        that.update();
    }, 1000);

    this.viewer.addEventListener("contextmenu", that.disableContextMenu);
};

/**
 * Display the states
 */
x3dom.States.prototype.display = function (value) {
    this.active = (value !== undefined) ? value : !this.active;
    this.viewer.style.display = (this.active) ? "block" : "none";
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Manage all the GL-States and try to reduce the state changes
 */
x3dom.StateManager = function (ctx3d)
{
    //Our GL-Context
    this.gl = ctx3d;

    //Hold all the active states
    this.states = [];

    //Initialize States
    this.initStates();
};

/*
 * Initialize States
 */
x3dom.StateManager.prototype.initStates = function ()
{
    //Initialize Shader states
    this.states['shaderID'] = null;

    //Initialize Framebuffer-Operation states
    this.states['colorMask'] = {red: null, green: null, blue: null, alpha: null};
    this.states['depthMask'] = null;
    this.states['stencilMask'] = null;

    //Initialize Rasterization states
    this.states['cullFace'] = null;
    this.states['frontFace'] = null;
    this.states['lineWidth'] = null;

    //Initialize Per-Fragment-Operation states
    this.states['blendColor'] = {red: null, green: null, blue: null, alpha: null};
    this.states['blendEquation'] = null;
    this.states['blendEquationSeparate'] = {modeRGB: null, modeAlpha: null};
    this.states['blendFunc'] = {sfactor: null, dfactor: null};
    this.states['blendFuncSeparate'] = {srcRGB: null, dstRGB: null, srcAlpha: null, dstAlpha: null};
    this.states['depthFunc'] = null;

    //Initialize View and Clip states
    this.states['viewport'] = {x: null, y: null, width: null, height: null};
    this.states['depthRange'] = {zNear: null, zFar: null};

    //TODO more states (e.g. stencil, texture, ...)
};

/*
 * Only bind program if different (returns true if changed)
 */
x3dom.StateManager.prototype.useProgram = function (shader)
{
    if (this.states['shaderID'] != shader.shaderID)
    {
        this.gl.useProgram(shader.program);
        this.states['shaderID'] = shader.shaderID;
        return true;
    }
    return false;
};

/*
 * Unset active program for clean init state
 */
x3dom.StateManager.prototype.unsetProgram = function ()
{
    this.states['shaderID'] = null;
};

/*
 * Enable GL capabilities
 */
x3dom.StateManager.prototype.enable = function (cap)
{
    if (this.states[cap] !== true)
    {
        this.gl.enable(cap);
        this.states[cap] = true;
    }
};

/*
 * Disable GL capabilities
 */
x3dom.StateManager.prototype.disable = function (cap)
{
    if (this.states[cap] !== false)
    {
        this.gl.disable(cap);
        this.states[cap] = false;
    }
};

/*
 * Enable and disable writing of frame buffer color components
 */
x3dom.StateManager.prototype.colorMask = function (red, green, blue, alpha)
{
    if (this.states['colorMask'].red != red ||
        this.states['colorMask'].green != green ||
        this.states['colorMask'].blue != blue ||
        this.states['colorMask'].alpha != alpha)
    {
        this.gl.colorMask(red, green, blue, alpha);
        this.states['colorMask'].red = red;
        this.states['colorMask'].green = green;
        this.states['colorMask'].blue = blue;
        this.states['colorMask'].alpha = alpha;
    }
};

/*
 * Sets whether or not you can write to the depth buffer.
 */
x3dom.StateManager.prototype.depthMask = function (flag)
{
    if (this.states['depthMask'] != flag)
    {
        this.gl.depthMask(flag);
        this.states['depthMask'] = flag;
    }
};

/*
 * Control the front and back writing of individual bits in the stencil planes
 */
x3dom.StateManager.prototype.stencilMask = function (mask)
{
    if (this.states['stencilMask'] != mask)
    {
        this.gl.stencilMask(mask);
        this.states['stencilMask'] = mask;
    }
};

/*
 * Specify whether front- or back-facing facets can be culled
 */
x3dom.StateManager.prototype.cullFace = function (mode)
{
    if (this.states['cullFace'] != mode)
    {
        this.gl.cullFace(mode);
        this.states['cullFace'] = mode;
    }
};

/*
 * Define front- and back-facing polygons
 */
x3dom.StateManager.prototype.frontFace = function (mode)
{
    if (this.states['frontFace'] != mode)
    {
        this.gl.frontFace(mode);
        this.states['frontFace'] = mode;
    }
};

/*
 * Specify the width of rasterized lines
 */
x3dom.StateManager.prototype.lineWidth = function (width)
{
    width = (width <= 1) ? 1 : width;

    if (this.states['lineWidth'] != width)
    {
        this.gl.lineWidth(width);
        this.states['lineWidth'] = width;
    }
};

/*
 * Set the blend color
 */
x3dom.StateManager.prototype.blendColor = function (red, green, blue, alpha)
{
    if (this.states['blendColor'].red != red ||
        this.states['blendColor'].green != green ||
        this.states['blendColor'].blue != blue ||
        this.states['blendColor'].alpha != alpha)
    {
        this.gl.blendColor(red, green, blue, alpha);
        this.states['blendColor'].red = red;
        this.states['blendColor'].green = green;
        this.states['blendColor'].blue = blue;
        this.states['blendColor'].alpha = alpha;
    }
};

/*
 * Specify the equation used for both the RGB blend equation and the Alpha blend equation
 */
x3dom.StateManager.prototype.blendEquation = function (mode)
{
    if (mode && this.states['blendEquation'] != mode)
    {
        this.gl.blendEquation(mode);
        this.states['blendEquation'] = mode;
    }
};

/*
 * set the RGB blend equation and the alpha blend equation separately
 */
x3dom.StateManager.prototype.blendEquationSeparate = function (modeRGB, modeAlpha)
{
    if (this.states['blendEquationSeparate'].modeRGB != modeRGB ||
        this.states['blendEquationSeparate'].modeAlpha != modeAlpha)
    {
        this.gl.blendEquationSeparate(modeRGB, modeAlpha);
        this.states['blendEquationSeparate'].modeRGB = modeRGB;
        this.states['blendEquationSeparate'].modeAlpha = modeAlpha;
    }
};

/*
 * Specify pixel arithmetic
 */
x3dom.StateManager.prototype.blendFunc = function (sfactor, dfactor)
{
    if (this.states['blendFunc'].sfactor != sfactor ||
        this.states['blendFunc'].dfactor != dfactor)
    {
        this.gl.blendFunc(sfactor, dfactor);
        this.states['blendFunc'].sfactor = sfactor;
        this.states['blendFunc'].dfactor = dfactor;
    }
};

/*
 * Specify pixel arithmetic for RGB and alpha components separately
 */
x3dom.StateManager.prototype.blendFuncSeparate = function (srcRGB, dstRGB, srcAlpha, dstAlpha)
{
    if (this.states['blendFuncSeparate'].srcRGB != srcRGB ||
        this.states['blendFuncSeparate'].dstRGB != dstRGB ||
        this.states['blendFuncSeparate'].srcAlpha != srcAlpha ||
        this.states['blendFuncSeparate'].dstAlpha != dstAlpha)
    {
        this.gl.blendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
        this.states['blendFuncSeparate'].srcRGB = srcRGB;
        this.states['blendFuncSeparate'].dstRGB = dstRGB;
        this.states['blendFuncSeparate'].srcAlpha = srcAlpha;
        this.states['blendFuncSeparate'].dstAlpha = dstAlpha;
    }
};

/*
 * Specify the value used for depth buffer comparisons
 */
x3dom.StateManager.prototype.depthFunc = function (func)
{
    if (this.states['depthFunc'] != func)
    {
        this.gl.depthFunc(func);
        this.states['depthFunc'] = func;
    }
};

/*
 * Specify the value used for depth buffer comparisons
 */
x3dom.StateManager.prototype.depthRange = function (zNear, zFar)
{
    if (zNear < 0 || zFar < 0 || zNear > zFar)
    {
        return;   // do noting and leave default values
    }

    zNear = (zNear > 1) ? 1 : zNear;
    zFar  = (zFar  > 1) ? 1 : zFar;

    if (this.states['depthRange'].zNear != zNear || this.states['depthRange'].zFar != zFar)
    {
        this.gl.depthRange(zNear, zFar);
        this.states['depthRange'].zNear = zNear;
        this.states['depthRange'].zFar = zFar;
    }
};

/*
 * Set the viewport
 */
x3dom.StateManager.prototype.viewport = function (x, y, width, height)
{
    if (this.states['viewport'].x != x ||
        this.states['viewport'].y != y ||
        this.states['viewport'].width != width ||
        this.states['viewport'].height != height)
    {
        this.gl.viewport(x, y, width, height);
        this.states['viewport'].x = x;
        this.states['viewport'].y = y;
        this.states['viewport'].width = width;
        this.states['viewport'].height = height;
    }
};

/*
 * Bind a framebuffer to a framebuffer target
 */
x3dom.StateManager.prototype.bindFramebuffer = function (target, framebuffer)
{
    this.gl.bindFramebuffer(target, framebuffer);
    this.initStates();
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


/** used from within gfx_webgl.js */
x3dom.BinaryContainerLoader = {
    outOfMemory: false,     // try to prevent browser crashes

    checkError: function(gl) {
        var glErr = gl.getError();
        if (glErr) {
            if (glErr == gl.OUT_OF_MEMORY) {
                this.outOfMemory = true;
                x3dom.debug.logError("GL-Error " + glErr + " on loading binary container (out of memory).");
                console.error("WebGL: OUT_OF_MEMORY");
            }
            else {
                x3dom.debug.logError("GL-Error " + glErr + " on loading binary container.");
            }
        }
    }
};


/** setup/download binary geometry */
x3dom.BinaryContainerLoader.setupBinGeo = function(shape, sp, gl, viewarea, currContext)
{
    if (this.outOfMemory) {
        return;
    }

    var t00 = new Date().getTime();
    var that = this;

    var binGeo = shape._cf.geometry.node;

    // 0 := no BG, 1 := indexed BG, -1 := non-indexed BG
    shape._webgl.binaryGeometry = -1;

    shape._webgl.internalDownloadCount = ((binGeo._vf.index.length > 0) ? 1 : 0) +
        ((binGeo._hasStrideOffset && binGeo._vf.coord.length > 0) ? 1 : 0) +
        ((!binGeo._hasStrideOffset && binGeo._vf.coord.length > 0) ? 1 : 0) +
        ((!binGeo._hasStrideOffset && binGeo._vf.normal.length > 0) ? 1 : 0) +
        ((!binGeo._hasStrideOffset && binGeo._vf.texCoord.length > 0) ? 1 : 0) +
        ((!binGeo._hasStrideOffset && binGeo._vf.color.length > 0) ? 1 : 0);

    var createTriangleSoup = (binGeo._vf.normalPerVertex == false) ||
                              ((binGeo._vf.index.length > 0) && (binGeo._vf.indexType == "Int32" ||
                                (binGeo._vf.indexType == "Uint32" && !x3dom.caps.INDEX_UINT)));

    shape._webgl.makeSeparateTris = {
        index: null,
        coord: null,
        normal: null,
        texCoord: null,
        color: null,

        pushBuffer: function(name, buf) {
            this[name] = buf;

            if (--shape._webgl.internalDownloadCount == 0) {
                if (this.coord)
                    this.createMesh();
                shape._nameSpace.doc.needRender = true;
            }
            if (--shape._nameSpace.doc.downloadCount == 0)
                shape._nameSpace.doc.needRender = true;
        },

        createMesh: function() {
            var geoNode = binGeo;

            if (geoNode._hasStrideOffset) {
                x3dom.debug.logError(geoNode._vf.indexType +
                    " index type and per-face normals not supported for interleaved arrays.");
                return;
            }

            for (var k=0; k<shape._webgl.primType.length; k++) {
                if (shape._webgl.primType[k] == gl.TRIANGLE_STRIP) {
                    x3dom.debug.logError("makeSeparateTris: triangle strips not yet supported for per-face normals.");
                    return;
                }
            }

            var attribTypeStr = geoNode._vf.coordType;
            shape._webgl.coordType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

            // remap vertex data
            var bgCenter, bgSize, bgPrecisionMax;

            if (shape._webgl.coordType != gl.FLOAT)
            {
                if (geoNode._mesh._numPosComponents == 4 &&
                    x3dom.Utils.isUnsignedType(geoNode._vf.coordType))
                    bgCenter = x3dom.fields.SFVec3f.copy(geoNode.getMin());
                else
                    bgCenter = x3dom.fields.SFVec3f.copy(geoNode._vf.position);

                bgSize = x3dom.fields.SFVec3f.copy(geoNode._vf.size);
                bgPrecisionMax = geoNode.getPrecisionMax('coordType');
            }
            else
            {
                bgCenter = new x3dom.fields.SFVec3f(0, 0, 0);
                bgSize = new x3dom.fields.SFVec3f(1, 1, 1);
                bgPrecisionMax = 1.0;
            }

            // check types
            var dataLen = shape._coordStrideOffset[0] / x3dom.Utils.getDataTypeSize(geoNode._vf.coordType);
            dataLen = (dataLen == 0) ? 3 : dataLen;

            x3dom.debug.logWarning("makeSeparateTris.createMesh called with coord length " + dataLen);

            if (this.color && dataLen != shape._colorStrideOffset[0] / x3dom.Utils.getDataTypeSize(geoNode._vf.colorType))
            {
                this.color = null;
                x3dom.debug.logWarning("Color format not supported.");
            }

            var texDataLen = this.texCoord ? (shape._texCoordStrideOffset[0] /
                                              x3dom.Utils.getDataTypeSize(geoNode._vf.texCoordType)) : 0;

            // set data types
            //geoNode._vf.coordType = "Float32";
            geoNode._vf.normalType = "Float32";

            //shape._webgl.coordType = gl.FLOAT;
            shape._webgl.normalType = gl.FLOAT;

            //geoNode._mesh._numPosComponents = 3;
            geoNode._mesh._numNormComponents = 3;

            //shape._coordStrideOffset = [0, 0];
            shape._normalStrideOffset = [0, 0];

            // create non-indexed mesh
            var posBuf = [], normBuf = [], texcBuf = [], colBuf = [];
            var i, j, l, n = this.index ? (this.index.length - 2) : (this.coord.length / 3 - 2);

            for (i=0; i<n; i+=3)
            {
                j = dataLen * (this.index ? this.index[i] : i);
                var p0 = new x3dom.fields.SFVec3f(bgSize.x * this.coord[j  ] / bgPrecisionMax,
                                                  bgSize.y * this.coord[j+1] / bgPrecisionMax,
                                                  bgSize.z * this.coord[j+2] / bgPrecisionMax);
                // offset irrelevant for normal calculation
                //p0 = bgCenter.add(p0);

                posBuf.push(this.coord[j  ]);
                posBuf.push(this.coord[j+1]);
                posBuf.push(this.coord[j+2]);
                if (dataLen > 3) posBuf.push(this.coord[j+3]);

                if (this.color) {
                    colBuf.push(this.color[j  ]);
                    colBuf.push(this.color[j+1]);
                    colBuf.push(this.color[j+2]);
                    if (dataLen > 3) colBuf.push(this.color[j+3]);
                }

                if (this.texCoord) {
                    l = texDataLen * (this.index ? this.index[i] : i);

                    texcBuf.push(this.texCoord[l  ]);
                    texcBuf.push(this.texCoord[l+1]);
                    if (texDataLen > 3) {
                        texcBuf.push(this.texCoord[l+2]);
                        texcBuf.push(this.texCoord[l+3]);
                    }
                }

                j = dataLen * (this.index ? this.index[i+1] : i+1);
                var p1 = new x3dom.fields.SFVec3f(bgSize.x * this.coord[j  ] / bgPrecisionMax,
                                                  bgSize.y * this.coord[j+1] / bgPrecisionMax,
                                                  bgSize.z * this.coord[j+2] / bgPrecisionMax);
                //p1 = bgCenter.add(p1);

                posBuf.push(this.coord[j  ]);
                posBuf.push(this.coord[j+1]);
                posBuf.push(this.coord[j+2]);
                if (dataLen > 3) posBuf.push(this.coord[j+3]);

                if (this.color) {
                    colBuf.push(this.color[j  ]);
                    colBuf.push(this.color[j+1]);
                    colBuf.push(this.color[j+2]);
                    if (dataLen > 3) colBuf.push(this.color[j+3]);
                }

                if (this.texCoord) {
                    l = texDataLen * (this.index ? this.index[i+1] : i+1);

                    texcBuf.push(this.texCoord[l  ]);
                    texcBuf.push(this.texCoord[l+1]);
                    if (texDataLen > 3) {
                        texcBuf.push(this.texCoord[l+2]);
                        texcBuf.push(this.texCoord[l+3]);
                    }
                }

                j = dataLen * (this.index ? this.index[i+2] : i+2);
                var p2 = new x3dom.fields.SFVec3f(bgSize.x * this.coord[j  ] / bgPrecisionMax,
                                                  bgSize.y * this.coord[j+1] / bgPrecisionMax,
                                                  bgSize.z * this.coord[j+2] / bgPrecisionMax);
                //p2 = bgCenter.add(p2);

                posBuf.push(this.coord[j  ]);
                posBuf.push(this.coord[j+1]);
                posBuf.push(this.coord[j+2]);
                if (dataLen > 3) posBuf.push(this.coord[j+3]);

                if (this.color) {
                    colBuf.push(this.color[j  ]);
                    colBuf.push(this.color[j+1]);
                    colBuf.push(this.color[j+2]);
                    if (dataLen > 3) colBuf.push(this.color[j+3]);
                }

                if (this.texCoord) {
                    l = texDataLen * (this.index ? this.index[i+2] : i+2);

                    texcBuf.push(this.texCoord[l  ]);
                    texcBuf.push(this.texCoord[l+1]);
                    if (texDataLen > 3) {
                        texcBuf.push(this.texCoord[l+2]);
                        texcBuf.push(this.texCoord[l+3]);
                    }
                }

                var a = p0.subtract(p1);
                var b = p1.subtract(p2);
                var norm = a.cross(b).normalize();

                for (j=0; j<3; j++) {
                    normBuf.push(norm.x);
                    normBuf.push(norm.y);
                    normBuf.push(norm.z);
                }
            }

            // coordinates
            var buffer = gl.createBuffer();
            shape._webgl.buffers[1] = buffer;

            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
            gl.bufferData(gl.ARRAY_BUFFER,
                x3dom.Utils.getArrayBufferView(geoNode._vf.coordType, posBuf), gl.STATIC_DRAW);

            gl.vertexAttribPointer(sp.position, geoNode._mesh._numPosComponents,
                shape._webgl.coordType, false,
                shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
            gl.enableVertexAttribArray(sp.position);

            // normals
            buffer = gl.createBuffer();
            shape._webgl.buffers[2] = buffer;

            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normBuf), gl.STATIC_DRAW);

            gl.vertexAttribPointer(sp.normal, geoNode._mesh._numNormComponents,
                shape._webgl.normalType, false,
                shape._normalStrideOffset[0], shape._normalStrideOffset[1]);
            gl.enableVertexAttribArray(sp.normal);

            // tex coords
            if (this.texCoord)
            {
                buffer = gl.createBuffer();
                shape._webgl.buffers[3] = buffer;

                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                gl.bufferData(gl.ARRAY_BUFFER,
                    x3dom.Utils.getArrayBufferView(geoNode._vf.texCoordType, texcBuf),
                    gl.STATIC_DRAW);

                gl.vertexAttribPointer(sp.texcoord, geoNode._mesh._numTexComponents,
                    shape._webgl.texCoordType, false,
                    shape._texCoordStrideOffset[0], shape._texCoordStrideOffset[1]);
                gl.enableVertexAttribArray(sp.texcoord);
            }

            // colors
            if (this.color)
            {
                buffer = gl.createBuffer();
                shape._webgl.buffers[4] = buffer;

                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                gl.bufferData(gl.ARRAY_BUFFER,
                    x3dom.Utils.getArrayBufferView(geoNode._vf.colorType, colBuf),
                    gl.STATIC_DRAW);

                gl.vertexAttribPointer(sp.color, geoNode._mesh._numColComponents,
                    shape._webgl.colorType, false,
                    shape._colorStrideOffset[0], shape._colorStrideOffset[1]);
                gl.enableVertexAttribArray(sp.color);
            }

            // adjust sizes
            geoNode._vf.vertexCount = [];
            geoNode._vf.vertexCount[0] = posBuf.length / dataLen;

            geoNode._mesh._numCoords = geoNode._vf.vertexCount[0];
            geoNode._mesh._numFaces = geoNode._vf.vertexCount[0] / 3;

            shape._webgl.primType = [];
            shape._webgl.primType[0] = gl.TRIANGLES;

            // cleanup
            posBuf = null;
            normBuf = null;
            texcBuf = null;
            colBuf = null;

            this.index = null;
            this.coord = null;
            this.normal = null;
            this.texCoord = null;
            this.color = null;

            that.checkError(gl);

            // recreate shader
            delete shape._webgl.shader;
            shape._webgl.shader = currContext.cache.getDynamicShader(gl, viewarea, shape);
        }
    };

    // index
    if (binGeo._vf.index.length > 0)
    {
        shape._webgl.binaryGeometry = 1;    // indexed BG

        var xmlhttp0 = new XMLHttpRequest();
        xmlhttp0.open("GET", shape._nameSpace.getURL(binGeo._vf.index), true);
        xmlhttp0.responseType = "arraybuffer";

        shape._nameSpace.doc.downloadCount += 1;

        //xmlhttp0.send(null);
        x3dom.RequestManager.addRequest( xmlhttp0 );

        xmlhttp0.onload = function()
        {
            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;

            if (xmlhttp0.status != 200) {
                x3dom.debug.logError( "XHR1/ index load failed with status: " + xmlhttp0.status );
                return;
            }

            if (!shape._webgl)
                return;

            var XHR_buffer = binGeo._vf.compressed == true ? x3dom.Utils.gunzip(xmlhttp0.response) : xmlhttp0.response;

            var geoNode = binGeo;
            var attribTypeStr = geoNode._vf.indexType;  //"Uint16"

            var indexArray = x3dom.Utils.getArrayBufferView(attribTypeStr, XHR_buffer);

            if (createTriangleSoup) {
                shape._webgl.makeSeparateTris.pushBuffer("index", indexArray);
                return;
            }

            var indicesBuffer = gl.createBuffer();


            if (x3dom.caps.INDEX_UINT && attribTypeStr == "Uint32") {
                //indexArray is Uint32Array
                shape._webgl.indexType = gl.UNSIGNED_INT;
            }
            else {
                //indexArray is Uint16Array
                shape._webgl.indexType = gl.UNSIGNED_SHORT;
            }

            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW);
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);

            // Test reading Data
            //x3dom.debug.logWarning("arraybuffer[0]="+indexArray[0]+"; n="+indexArray.length);

            if (geoNode._vf.vertexCount[0] == 0)
                geoNode._vf.vertexCount[0] = indexArray.length;

            geoNode._mesh._numFaces = 0;

            for (var i=0; i<geoNode._vf.vertexCount.length; i++) {
                if (shape._webgl.primType[i] == gl.TRIANGLE_STRIP)
                    geoNode._mesh._numFaces += geoNode._vf.vertexCount[i] - 2;
                else
                    geoNode._mesh._numFaces += geoNode._vf.vertexCount[i] / 3;
            }

            indexArray = null;

            if (shape._webgl.internalDownloadCount == 0)
            {
                shape._nameSpace.doc.needRender = true;
            }

            that.checkError(gl);

            var t11 = new Date().getTime() - t00;
            x3dom.debug.logInfo("XHR0/ index load time: " + t11 + " ms");

            shape._webgl.buffers[0] = indicesBuffer;
        };
    }

    // interleaved array -- assume all attributes are given in one single array buffer
    if (binGeo._hasStrideOffset && binGeo._vf.coord.length > 0)
    {
        var xmlhttp = new XMLHttpRequest();
        xmlhttp.open("GET", shape._nameSpace.getURL(binGeo._vf.coord), true);
        xmlhttp.responseType = "arraybuffer";

        shape._nameSpace.doc.downloadCount += 1;

        //xmlhttp.send(null);
        x3dom.RequestManager.addRequest( xmlhttp );

        xmlhttp.onload = function()
        {
            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;

            if (xmlhttp.status != 200) {
                x3dom.debug.logError( "XHR1/ interleaved array load failed with status: " + xmlhttp.status );
                return;
            }

            if (!shape._webgl)
                return;

            var XHR_buffer = binGeo._vf.compressed == true ? x3dom.Utils.gunzip(xmlhttp.response) : xmlhttp.response;

            var geoNode = binGeo;
            var attribTypeStr = geoNode._vf.coordType;

            // assume same data type for all attributes (but might be wrong)
            shape._webgl.coordType    = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);
            shape._webgl.normalType   = shape._webgl.coordType;
            shape._webgl.texCoordType = shape._webgl.coordType;
            shape._webgl.colorType    = shape._webgl.coordType;

            var attributes = x3dom.Utils.getArrayBufferView(attribTypeStr, XHR_buffer);

            // calculate number of single data packages by including stride and type size
            var dataLen = shape._coordStrideOffset[0] / x3dom.Utils.getDataTypeSize(attribTypeStr);
            if (dataLen)
                geoNode._mesh._numCoords = attributes.length / dataLen;

            if (geoNode._vf.index.length == 0) {
                for (var i=0; i<geoNode._vf.vertexCount.length; i++) {
                    if (shape._webgl.primType[i] == gl.TRIANGLE_STRIP)
                        geoNode._mesh._numFaces += geoNode._vf.vertexCount[i] - 2;
                    else
                        geoNode._mesh._numFaces += geoNode._vf.vertexCount[i] / 3;
                }
            }

            var buffer = gl.createBuffer();

            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
            gl.bufferData(gl.ARRAY_BUFFER, attributes, gl.STATIC_DRAW);

            gl.vertexAttribPointer(sp.position, geoNode._mesh._numPosComponents,
                shape._webgl.coordType, false,
                shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
            gl.enableVertexAttribArray(sp.position);

            if (geoNode._vf.normal.length > 0)
            {
                shape._webgl.buffers[2] = buffer;

                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                gl.bufferData(gl.ARRAY_BUFFER, attributes, gl.STATIC_DRAW);

                gl.vertexAttribPointer(sp.normal, geoNode._mesh._numNormComponents,
                    shape._webgl.normalType, false,
                    shape._normalStrideOffset[0], shape._normalStrideOffset[1]);
                gl.enableVertexAttribArray(sp.normal);
            }

            if (geoNode._vf.texCoord.length > 0)
            {
                shape._webgl.buffers[3] = buffer;

                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                gl.bufferData(gl.ARRAY_BUFFER, attributes, gl.STATIC_DRAW);

                gl.vertexAttribPointer(sp.texcoord, geoNode._mesh._numTexComponents,
                    shape._webgl.texCoordType, false,
                    shape._texCoordStrideOffset[0], shape._texCoordStrideOffset[1]);
                gl.enableVertexAttribArray(sp.texcoord);
            }

            if (geoNode._vf.color.length > 0)
            {
                shape._webgl.buffers[4] = buffer;

                gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
                gl.bufferData(gl.ARRAY_BUFFER, attributes, gl.STATIC_DRAW);

                gl.vertexAttribPointer(sp.color, geoNode._mesh._numColComponents,
                    shape._webgl.colorType, false,
                    shape._colorStrideOffset[0], shape._colorStrideOffset[1]);
                gl.enableVertexAttribArray(sp.color);
            }

            attributes = null;  // delete data block in CPU memory

            if (shape._webgl.internalDownloadCount == 0)
            {
                shape._nameSpace.doc.needRender = true;
            }

            that.checkError(gl);

            var t11 = new Date().getTime() - t00;
            x3dom.debug.logInfo("XHR/ interleaved array load time: " + t11 + " ms");

            shape._webgl.buffers[1] = buffer;

        };
    }

    // coord
    if (!binGeo._hasStrideOffset && binGeo._vf.coord.length > 0)
    {
        var xmlhttp1 = new XMLHttpRequest();
        xmlhttp1.open("GET", shape._nameSpace.getURL(binGeo._vf.coord), true);
        xmlhttp1.responseType = "arraybuffer";

        shape._nameSpace.doc.downloadCount += 1;

        //xmlhttp1.send(null);
        x3dom.RequestManager.addRequest( xmlhttp1 );

        xmlhttp1.onload = function()
        {
            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;

            if (xmlhttp1.status != 200) {
                x3dom.debug.logError( "XHR1/ coord load failed with status: " + xmlhttp1.status );
                return;
            }

            if (!shape._webgl)
                return;

            var XHR_buffer = binGeo._vf.compressed == true ? x3dom.Utils.gunzip(xmlhttp1.response) : xmlhttp1.response;

            var geoNode = binGeo;
            var i = 0;

            var attribTypeStr = geoNode._vf.coordType;
            shape._webgl.coordType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

            var vertices = x3dom.Utils.getArrayBufferView(attribTypeStr, XHR_buffer);

            if (createTriangleSoup) {
                shape._webgl.makeSeparateTris.pushBuffer("coord", vertices);
                return;
            }

            gl.bindAttribLocation(sp.program, 0, "position");

            var positionBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
            gl.bindBuffer(gl.ARRAY_BUFFER, null);

            geoNode._mesh._numCoords = vertices.length / geoNode._mesh._numPosComponents;

            if (geoNode._vf.index.length == 0) {
                for (i=0; i<geoNode._vf.vertexCount.length; i++) {
                    if (shape._webgl.primType[i] == gl.TRIANGLE_STRIP)
                        geoNode._mesh._numFaces += geoNode._vf.vertexCount[i] - 2;
                    else
                        geoNode._mesh._numFaces += geoNode._vf.vertexCount[i] / 3;
                }
            }

            // Test reading Data
            //x3dom.debug.logWarning("arraybuffer[0].vx="+vertices[0]);

            if ((attribTypeStr == "Float32") &&
                (shape._vf.bboxSize.x < 0 || shape._vf.bboxSize.y < 0 || shape._vf.bboxSize.z < 0))
            {
                var min = new x3dom.fields.SFVec3f(vertices[0],vertices[1],vertices[2]);
                var max = new x3dom.fields.SFVec3f(vertices[0],vertices[1],vertices[2]);

                for (i=3; i<vertices.length; i+=3)
                {
                    if (min.x > vertices[i+0]) { min.x = vertices[i+0]; }
                    if (min.y > vertices[i+1]) { min.y = vertices[i+1]; }
                    if (min.z > vertices[i+2]) { min.z = vertices[i+2]; }

                    if (max.x < vertices[i+0]) { max.x = vertices[i+0]; }
                    if (max.y < vertices[i+1]) { max.y = vertices[i+1]; }
                    if (max.z < vertices[i+2]) { max.z = vertices[i+2]; }
                }

                // TODO; move to mesh for all cases?
                shape._vf.bboxCenter.setValues(min.add(max).multiply(0.5));
                shape._vf.bboxSize.setValues(max.subtract(min));
            }

            vertices = null;

            if (shape._webgl.internalDownloadCount == 0)
            {
                shape._nameSpace.doc.needRender = true;
            }

            that.checkError(gl);

            var t11 = new Date().getTime() - t00;
            x3dom.debug.logInfo("XHR1/ coord load time: " + t11 + " ms");

            shape._webgl.buffers[1] = positionBuffer;
        };
    }

    // normal
    if (!binGeo._hasStrideOffset && binGeo._vf.normal.length > 0)
    {
        var xmlhttp2 = new XMLHttpRequest();
        xmlhttp2.open("GET", shape._nameSpace.getURL(binGeo._vf.normal), true);
        xmlhttp2.responseType = "arraybuffer";

        shape._nameSpace.doc.downloadCount += 1;

        //xmlhttp2.send(null);
        x3dom.RequestManager.addRequest( xmlhttp2 );

        xmlhttp2.onload = function()
        {
            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;

            if (xmlhttp2.status != 200) {
                x3dom.debug.logError( "XHR2/ normal load failed with status: " + xmlhttp2.status );
                return;
            }

            if (!shape._webgl)
                return;

            var XHR_buffer = binGeo._vf.compressed == true ? x3dom.Utils.gunzip(xmlhttp2.response) : xmlhttp2.response;

            var attribTypeStr = binGeo._vf.normalType;
            shape._webgl.normalType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

            var normals = x3dom.Utils.getArrayBufferView(attribTypeStr, XHR_buffer);

            if (createTriangleSoup) {
                shape._webgl.makeSeparateTris.pushBuffer("normal", normals);
                return;
            }

            var normalBuffer = gl.createBuffer();

            gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);
            gl.bindBuffer(gl.ARRAY_BUFFER, null);

            // Test reading Data
            //x3dom.debug.logWarning("arraybuffer[0].nx="+normals[0]);

            normals = null;

            if (shape._webgl.internalDownloadCount == 0)
            {
                shape._nameSpace.doc.needRender = true;
            }

            that.checkError(gl);

            var t11 = new Date().getTime() - t00;
            x3dom.debug.logInfo("XHR2/ normal load time: " + t11 + " ms");

            shape._webgl.buffers[2] = normalBuffer;
        };
    }

    // texCoord
    if (!binGeo._hasStrideOffset && binGeo._vf.texCoord.length > 0)
    {
        var xmlhttp3 = new XMLHttpRequest();
        xmlhttp3.open("GET", shape._nameSpace.getURL(binGeo._vf.texCoord), true);
        xmlhttp3.responseType = "arraybuffer";

        shape._nameSpace.doc.downloadCount += 1;

        //xmlhttp3.send(null);
        x3dom.RequestManager.addRequest( xmlhttp3 );

        xmlhttp3.onload = function()
        {
            var i, j;
            var tmp;

            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;

            if (xmlhttp3.status != 200) {
                x3dom.debug.logError( "XHR3/ texcoord load failed with status: " + xmlhttp3.status );
                return;
            }

            if (!shape._webgl)
                return;

            var XHR_buffer = binGeo._vf.compressed == true ? x3dom.Utils.gunzip(xmlhttp3.response) : xmlhttp3.response;

            var attribTypeStr = binGeo._vf.texCoordType;
            shape._webgl.texCoordType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

            var texCoords = x3dom.Utils.getArrayBufferView(attribTypeStr, XHR_buffer);

            if (createTriangleSoup) {
                shape._webgl.makeSeparateTris.pushBuffer("texCoord", texCoords);
                return;
            }


            //if IDs are given in texture coordinates, interpret texcoords as ID buffer
            if (binGeo._vf["idsPerVertex"])
            {
                var idBuffer = gl.createBuffer();

                gl.bindBuffer(gl.ARRAY_BUFFER, idBuffer);

                //Create a buffer for the ids with half size of the texccoord buffer
                var ids = x3dom.Utils.getArrayBufferView("Float32", texCoords.length/2);

                //swap x and y, in order to interpret tex coords as FLOAT later on
                for (i = 0, j= 0; i < texCoords.length; i+=2, j++)
                {
                    ids[j] = texCoords[i+1] * 65536 + texCoords[i];
                }

                gl.bufferData(gl.ARRAY_BUFFER, ids, gl.STATIC_DRAW);
                gl.bindBuffer(gl.ARRAY_BUFFER, null);

                shape._webgl.buffers[5] = idBuffer;
            }
            else
            {
                var texcBuffer = gl.createBuffer();

                gl.bindBuffer(gl.ARRAY_BUFFER, texcBuffer);
                gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
                gl.bindBuffer(gl.ARRAY_BUFFER, null);

                shape._webgl.buffers[3] = texcBuffer;
            }
            // Test reading Data
            //x3dom.debug.logWarning("arraybuffer[0].tx="+texCoords[0]);

            texCoords = null;

            if (shape._webgl.internalDownloadCount == 0)
            {
                shape._nameSpace.doc.needRender = true;
            }

            that.checkError(gl);

            var t11 = new Date().getTime() - t00;
            x3dom.debug.logInfo("XHR3/ texCoord load time: " + t11 + " ms");
        };
    }

    // color
    if (!binGeo._hasStrideOffset && binGeo._vf.color.length > 0)
    {
        var xmlhttp4 = new XMLHttpRequest();
        xmlhttp4.open("GET", shape._nameSpace.getURL(binGeo._vf.color), true);
        xmlhttp4.responseType = "arraybuffer";

        shape._nameSpace.doc.downloadCount += 1;

        //xmlhttp4.send(null);
        x3dom.RequestManager.addRequest( xmlhttp4 );

        xmlhttp4.onload = function()
        {
            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;

            if (xmlhttp4.status != 200) {
                x3dom.debug.logError( "XHR4/ color load failed with status: " + xmlhttp4.status );
                return;
            }

            if (!shape._webgl)
                return;

            var XHR_buffer = binGeo._vf.compressed == true ? x3dom.Utils.gunzip(xmlhttp4.response) : xmlhttp4.response;

            var attribTypeStr = binGeo._vf.colorType;
            shape._webgl.colorType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

            var colors = x3dom.Utils.getArrayBufferView(attribTypeStr, XHR_buffer);

            if (createTriangleSoup) {
                shape._webgl.makeSeparateTris.pushBuffer("color", colors);
                return;
            }

            var colorBuffer = gl.createBuffer();

            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);
            gl.bindBuffer(gl.ARRAY_BUFFER, null);

            // Test reading Data
            //x3dom.debug.logWarning("arraybuffer[0].cx="+colors[0]);

            colors = null;

            if (shape._webgl.internalDownloadCount == 0)
            {
                shape._nameSpace.doc.needRender = true;
            }

            that.checkError(gl);

            var t11 = new Date().getTime() - t00;
            x3dom.debug.logInfo("XHR4/ color load time: " + t11 + " ms");

            shape._webgl.buffers[4] = colorBuffer;
        };
    }
    // TODO: tangent AND binormal
};

/** setup/download pop geometry */
x3dom.BinaryContainerLoader.setupPopGeo = function(shape, sp, gl, viewarea, currContext)
{
    if (this.outOfMemory) {
        return;
    }

    var popGeo = shape._cf.geometry.node;

    //reserve space for vertex buffer (and index buffer if any) on the gpu
    if (popGeo.hasIndex()) {
        shape._webgl.popGeometry = 1;

        shape._webgl.buffers[0] = gl.createBuffer();

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, shape._webgl.buffers[0]);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, popGeo.getTotalNumberOfIndices()*2, gl.STATIC_DRAW);

        //this is a workaround to mimic gl_VertexID
        shape._webgl.buffers[5] = gl.createBuffer();

        var idBuffer = new Float32Array(popGeo._vf.vertexBufferSize);

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

        gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[5]);
        gl.bufferData(gl.ARRAY_BUFFER, idBuffer, gl.STATIC_DRAW);
    }
    else {
        shape._webgl.popGeometry = -1;
    }

    shape._webgl.buffers[1] = gl.createBuffer();

    gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[1]);
    gl.bufferData(gl.ARRAY_BUFFER, (popGeo._vf.attributeStride * popGeo._vf.vertexBufferSize), gl.STATIC_DRAW);


    //setup general render settings
    var attribTypeStr      = popGeo._vf.coordType;
    shape._webgl.coordType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

    shape._coordStrideOffset[0] = popGeo.getAttributeStride();
    shape._coordStrideOffset[1] = popGeo.getPositionOffset();

    gl.vertexAttribPointer(sp.position, shape._cf.geometry.node._mesh._numPosComponents, shape._webgl.coordType,
                           false, shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
    gl.enableVertexAttribArray(sp.position);

    if (popGeo.hasNormal()) {
        attribTypeStr           = popGeo._vf.normalType;
        shape._webgl.normalType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

        shape._normalStrideOffset[0] = popGeo.getAttributeStride();
        shape._normalStrideOffset[1] = popGeo.getNormalOffset();

        shape._webgl.buffers[2] = shape._webgl.buffers[1]; //use interleaved vertex data buffer

        gl.vertexAttribPointer(sp.normal, shape._cf.geometry.node._mesh._numNormComponents, shape._webgl.normalType,
                               false, shape._normalStrideOffset[0], shape._normalStrideOffset[1]);
        gl.enableVertexAttribArray(sp.normal);
    }
    if (popGeo.hasTexCoord()) {
        attribTypeStr             = popGeo._vf.texCoordType;
        shape._webgl.texCoordType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

        shape._webgl.buffers[3] = shape._webgl.buffers[1]; //use interleaved vertex data buffer

        shape._texCoordStrideOffset[0] = popGeo.getAttributeStride();
        shape._texCoordStrideOffset[1] = popGeo.getTexCoordOffset();

        gl.vertexAttribPointer(sp.texcoord, shape._cf.geometry.node._mesh._numTexComponents, shape._webgl.texCoordType,
                               false, shape._texCoordStrideOffset[0], shape._texCoordStrideOffset[1]);
        gl.enableVertexAttribArray(sp.texcoord);
    }
    if (popGeo.hasColor()) {
        attribTypeStr          = popGeo._vf.colorType;
        shape._webgl.colorType = x3dom.Utils.getVertexAttribType(attribTypeStr, gl);

        shape._webgl.buffers[4] = shape._webgl.buffers[1]; //use interleaved vertex data buffer

        shape._colorStrideOffset[0] = popGeo.getAttributeStride();
        shape._colorStrideOffset[1] = popGeo.getColorOffset();

        gl.vertexAttribPointer(sp.color, shape._cf.geometry.node._mesh._numColComponents, shape._webgl.colorType,
                               false, shape._colorStrideOffset[0], shape._colorStrideOffset[1]);
        gl.enableVertexAttribArray(sp.color);
    }

    shape._webgl.currentNumIndices  = 0;
    shape._webgl.currentNumVertices = 0;
    shape._webgl.numVerticesAtLevel = [];
    shape._webgl.levelsAvailable    = 0;

    this.checkError(gl);

    shape._webgl.levelLoaded = [];
    (function() {
        for (var i = 0; i < popGeo.getNumLevels(); ++i)
            shape._webgl.levelLoaded.push(false);
    })();

    //download callback, used to simply upload received vertex data to the GPU
    var uploadDataToGPU = function(data, lvl) {
        //x3dom.debug.logInfo("PopGeometry: Received data for level " + lvl + " !\n");

        shape._webgl.levelLoaded[lvl] = true;
        shape._webgl.numVerticesAtLevel[lvl] = 0;

        if (data) {
            //perform gpu data upload
            var indexDataLengthInBytes = 0;
            var redrawNeeded = false;

            if (popGeo.hasIndex()) {
                indexDataLengthInBytes = popGeo.getNumIndicesByLevel(lvl)*2;

                if (indexDataLengthInBytes > 0) {
                    redrawNeeded = true;

                    var indexDataView = new Uint8Array(data, 0, indexDataLengthInBytes);

                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, shape._webgl.buffers[0]);
                    //index data is always placed where it belongs, as we have to keep the order of rendering
                    (function() {
                        var indexDataOffset = 0;

                        for (var i = 0; i < lvl; ++i) { indexDataOffset += popGeo.getNumIndicesByLevel(i); }

                        gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, indexDataOffset*2, indexDataView);
                    })();
                }
            }

            var vertexDataLengthInBytes = data.byteLength - indexDataLengthInBytes;

            if (vertexDataLengthInBytes > 0) {
                redrawNeeded = true;

                var attributeDataView = new Uint8Array(data, indexDataLengthInBytes, vertexDataLengthInBytes);

                gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[1]);
                if (!popGeo.hasIndex()) {
                    //on non-indexed rendering, vertex data is just appended, the order of vertex data packages doesn't matter
                    gl.bufferSubData(gl.ARRAY_BUFFER, shape._webgl.currentNumVertices       * popGeo.getAttributeStride(),
                                     attributeDataView);
                }
                else {
                    //on indexed rendering, vertex data is always placed where it belongs, as we have to keep the indexed order
                    gl.bufferSubData(gl.ARRAY_BUFFER,popGeo.getVertexDataBufferOffset(lvl) * popGeo.getAttributeStride(),
                                     attributeDataView);
                }

                //adjust render settings: vertex data
                shape._webgl.numVerticesAtLevel[lvl] = vertexDataLengthInBytes / popGeo.getAttributeStride();
                shape._webgl.currentNumVertices += shape._webgl.numVerticesAtLevel[lvl];
            }

            //compute number of valid indices
            (function() {
                var numValidIndices = 0;

                for (var i = shape._webgl.levelsAvailable; i < popGeo.getNumLevels(); ++i) {
                    if (shape._webgl.levelLoaded[i] === false) {
                        break;
                    }
                    else {
                        numValidIndices += popGeo.getNumIndicesByLevel(i);
                        ++shape._webgl.levelsAvailable;
                    }
                }

                //adjust render settings: index data
                shape._webgl.currentNumIndices = numValidIndices;
            })();

            //here, we tell X3DOM how many faces / vertices get displayed in the stats
            popGeo._mesh._numCoords = shape._webgl.currentNumVertices;
            //@todo: this assumes pure TRIANGLES data
            popGeo._mesh._numFaces  = (popGeo.hasIndex() ? shape._webgl.currentNumIndices : shape._webgl.currentNumVertices) / 3;

            //here, we tell X3DOM how many vertices get rendered
            //@todo: this assumes pure TRIANGLES data
            popGeo.adaptVertexCount(popGeo.hasIndex() ? popGeo._mesh._numFaces * 3 : popGeo._mesh._numCoords);
            //x3dom.debug.logInfo("PopGeometry: Loaded level " + lvl + " data to gpu, model has now " +
            //    popGeo._mesh._numCoords + " vertices and " + popGeo._mesh._numFaces + " triangles, " +
            //    (new Date().getTime() - shape._webgl.downloadStartTimer) + " ms after posting download requests");

            //request redraw, if necessary
            if (redrawNeeded) {
                shape._nameSpace.doc.needRender = true;
            }
        }
    };

    //post XHRs
    var dataURLs = popGeo.getDataURLs();

    var downloadCallbacks = [];
    var priorities        = [];

    shape._webgl.downloadStartTimer = new Date().getTime();

    //CODE WITH DL MANAGER
    //use the DownloadManager to prioritize loading

    for (var i = 0; i < dataURLs.length; ++i) {
        shape._nameSpace.doc.downloadCount += 1;

        (function(idx) {
            downloadCallbacks.push(function(data) {
                shape._nameSpace.doc.downloadCount -= 1;
                return uploadDataToGPU(data, idx);
            });
        })(i);

        priorities.push(i);
    }

    x3dom.DownloadManager.get(dataURLs, downloadCallbacks, priorities);
    //END CODE WITH DL MANAGER
};

/** setup/download image geometry */
x3dom.BinaryContainerLoader.setupImgGeo = function(shape, sp, gl, viewarea, currContext)
{
    if (this.outOfMemory) {
        return;
    }

    var imageGeometry = shape._cf.geometry.node;

    if ( imageGeometry.getIndexTexture() ) {
        shape._webgl.imageGeometry = 1;
    } else {
        shape._webgl.imageGeometry = -1;
    }

    imageGeometry.unsetGeoDirty();

    if (currContext.IG_PositionBuffer == null) {
        currContext.IG_PositionBuffer = gl.createBuffer();
    }

    shape._webgl.buffers[1] = currContext.IG_PositionBuffer;
    gl.bindBuffer(gl.ARRAY_BUFFER, currContext.IG_PositionBuffer);

    var vertices = new Float32Array(shape._webgl.positions[0]);

    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
    gl.bindBuffer(gl.ARRAY_BUFFER, currContext.IG_PositionBuffer);

    gl.vertexAttribPointer(sp.position, imageGeometry._mesh._numPosComponents,
        shape._webgl.coordType, false,
        shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
    gl.enableVertexAttribArray(sp.position);

    vertices = null;

    this.checkError(gl);
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


/**
 * c'tor
 */
x3dom.DrawableCollection = function (drawableCollectionConfig) {
    this.collection = [];

    this.viewMatrix = drawableCollectionConfig.viewMatrix;
    this.projMatrix = drawableCollectionConfig.projMatrix;
    this.sceneMatrix = drawableCollectionConfig.sceneMatrix;

    this.viewarea = drawableCollectionConfig.viewArea;

    var scene = this.viewarea._scene;
    var env = scene.getEnvironment();
    var viewpoint = scene.getViewpoint();

    this.near = viewpoint.getNear();
    this.pixelHeightAtDistOne = viewpoint.getImgPlaneHeightAtDistOne() / this.viewarea._height;

    this.context = drawableCollectionConfig.context;
    this.gl = drawableCollectionConfig.gl;

    this.viewFrustum = this.viewarea.getViewfrustum(this.sceneMatrix);
    this.worldVol = new x3dom.fields.BoxVolume();     // helper

    this.frustumCulling = drawableCollectionConfig.frustumCulling && (this.viewFrustum != null);
    this.smallFeatureThreshold = drawableCollectionConfig.smallFeatureThreshold;

    // if (lowPriorityThreshold < 1) sort all potentially visible objects according to priority
    this.sortOpaque = (this.smallFeatureThreshold > 0 && env._lowPriorityThreshold < 1);
    this.sortTrans = drawableCollectionConfig.sortTrans;

    this.prioLevels = 10;
    this.maxTreshold = 100;

    this.sortBySortKey = false;
    this.sortByPriority = false;

    this.numberOfNodes = 0;

    this.length = 0;
};

/**
 * Returns the result of the culling process, including view frustum culling and small feature culling.
 * A value > 0 identifies a plane mask, indicating that the object is inside the frustum with respect to
 * the respective planes (and large enough), -1 means the object was culled
 * "0" is a rare case, indicating that the object intersects with all planes of the frustum
 *  graphState = {
 *     boundedNode:  backref to bounded node object
 *     localMatrix:  mostly identity
 *     globalMatrix: current transform
 *     volume:       local bbox
 *     worldVolume:  global bbox
 *     center:       center in eye coords
 *     coverage:     currently approx. number of pixels on screen
 *  };
 */
x3dom.DrawableCollection.prototype.cull = function (transform, graphState, singlePath, planeMask) {
    var node = graphState.boundedNode;  // get ref to SG node

    if (!node || !node._vf.render) {
        return -1;
    }

    var volume = node.getVolume();      // create on request
    var MASK_SET = 63;  // 2^6-1, i.e. all sides of the volume

    if (this.frustumCulling && graphState.needCulling) {
        var wvol;

        if (singlePath && !graphState.worldVolume.isValid()) {
            graphState.worldVolume.transformFrom(transform, volume);
            wvol = graphState.worldVolume;  // use opportunity to update if necessary
        }
        else if (planeMask < MASK_SET) {
            this.worldVol.transformFrom(transform, volume);
            wvol = this.worldVol;
        }

        if (planeMask < MASK_SET)
            planeMask = this.viewFrustum.intersect(wvol, planeMask);

        //-1 indicates that the object has been culled
        if (planeMask == -1)
        {
            return -1;
        }
    }
    else {
        planeMask = MASK_SET;
    }

    graphState.coverage = -1;    // if -1 then ignore value later on

    // TODO: save the coverage only for drawables, which are unique (shapes can be shared!)
    if (this.smallFeatureThreshold > 0 || node.forceUpdateCoverage()) {
        var modelViewMat = this.viewMatrix.mult(transform);

        graphState.center = modelViewMat.multMatrixPnt(volume.getCenter());

        var rVec = modelViewMat.multMatrixVec(volume.getRadialVec());
        var r    = rVec.length();

        var dist = Math.max(-graphState.center.z - r, this.near);
        var projPixelLength = dist * this.pixelHeightAtDistOne;

        graphState.coverage = (r * 2.0) / projPixelLength;

        if (this.smallFeatureThreshold > 0 && graphState.coverage < this.smallFeatureThreshold && 
            graphState.needCulling) {
            return -1;
        }
    }

    // not culled, incr node cnt
    this.numberOfNodes++;
    
    return planeMask;   // >= 0 means inside or overlap
};

/**
 * A drawable is basically a unique pair of a shape node and a global transformation.
 */
x3dom.DrawableCollection.prototype.addShape = function (shape, transform, graphState) {
    //Create a new drawable object
    var drawable = {};

    //Set the shape
    drawable.shape = shape;

    //Set the transform
    drawable.transform = transform;

    drawable.localTransform = graphState.localMatrix;

    //Set the local bounding box (reference, can be shared amongst shapes)
    drawable.localVolume = graphState.volume;

    //Set the global bbox (needs to be cloned since shape can be shared)
    drawable.worldVolume = x3dom.fields.BoxVolume.copy(graphState.worldVolume);

    //Calculate the magical object priority (though currently not very magic)
    drawable.priority = Math.max(0, graphState.coverage);
    //drawable.priority = this.calculatePriority(graphState);

    //Get shaderID from shape
    drawable.shaderID = shape.getShaderProperties(this.viewarea).id;

    var appearance = shape._cf.appearance.node;

    drawable.sortType = appearance ? appearance._vf.sortType.toLowerCase() : "opaque";
    drawable.sortKey = appearance ? appearance._vf.sortKey : 0;

    if (drawable.sortType == 'transparent') {
        if (this.smallFeatureThreshold > 0) {
            // TODO: center was previously set in cull, which is called first, but this
            // might be problematic if scene is traversed in parallel and node is shared
            // (though currently traversal is sequential, so everything is fine)
            drawable.zPos = graphState.center.z;
        }
        else {
            //Calculate the z-Pos for transparent object sorting
            //if the center of the box is not available
            var center = transform.multMatrixPnt(shape.getCenter());
            center = this.viewMatrix.multMatrixPnt(center);
            drawable.zPos = center.z;
        }
    }

    //Look for sorting by sortKey
    if (!this.sortBySortKey && drawable.sortKey != 0) {
        this.sortBySortKey = true;
    }

    //Generate separate array for sortType if not exists
    if (this.collection[drawable.sortType] === undefined) {
        this.collection[drawable.sortType] = [];
    }

    //Push drawable to the collection
    this.collection[drawable.sortType].push(drawable);
    //this.collection[drawable.sortType][drawable.sortKey][drawable.priority][drawable.shaderID].push(drawable);

    //Increment collection length
    this.length++;

    //Finally setup shape directly here to avoid another loop of O(n)
    if (this.context && this.gl) {
        this.context.setupShape(this.gl, drawable, this.viewarea);
    }
    //TODO: what about Flash? Shall we also setup structures here?
};

/**
 * A drawable is basically a unique pair of a shape node and a global transformation.
 */
x3dom.DrawableCollection.prototype.addDrawable = function (drawable) {
    //Calculate the magical object priority (though currently not very magic)
    //drawable.priority = this.calculatePriority(graphState);

    //Get shaderID from shape
    drawable.shaderID = drawable.shape.getShaderProperties(this.viewarea).id;

    var appearance = drawable.shape._cf.appearance.node;

    drawable.sortType = appearance ? appearance._vf.sortType.toLowerCase() : "opaque";
    drawable.sortKey = appearance ? appearance._vf.sortKey : 0;

    if (drawable.sortType == 'transparent') {
        //TODO set zPos for drawable for z-sorting
        //Calculate the z-Pos for transparent object sorting
        //if the center of the box is not available
        var center = drawable.transform.multMatrixPnt(drawable.shape.getCenter());
        center = this.viewMatrix.multMatrixPnt(center);
        drawable.zPos = center.z;
    }

    //Look for sorting by sortKey
    if (!this.sortBySortKey && drawable.sortKey != 0) {
        this.sortBySortKey = true;
    }

    //Generate separate array for sortType if not exists
    if (this.collection[drawable.sortType] === undefined) {
        this.collection[drawable.sortType] = [];
    }

    //Push drawable to the collection
    this.collection[drawable.sortType].push(drawable);
    //this.collection[drawable.sortType][drawable.sortKey][drawable.priority][drawable.shaderID].push(drawable);

    //Increment collection length
    this.length++;

    //Finally setup shape directly here to avoid another loop of O(n)
    if (this.context && this.gl) {
        this.context.setupShape(this.gl, drawable, this.viewarea);
    }
};


/**
 * Calculate the magical object priority (though currently not very magic).
 */
x3dom.DrawableCollection.prototype.calculatePriority = function (graphState) {
    //Use coverage as priority
    var priority = Math.max(0, graphState.coverage);

    //Classify the priority level
    var pl = this.prioLevels - 1;   // Can this be <= 0? Then FIXME!
    priority = Math.min( Math.round(priority / (this.maxTreshold / pl)), pl );

    return priority;
};

/**
 *  Concatenate opaque and transparent drawables
 */
x3dom.DrawableCollection.prototype.concat = function () {
    var opaque = (this.collection['opaque'] !== undefined) ? this.collection['opaque'] : [];
    var transparent = (this.collection['transparent'] !== undefined) ? this.collection['transparent'] : [];

    //Merge opaque and transparent drawables to a single array
    this.collection = opaque.concat(transparent);
};

/**
 *  Get drawable for id
 */
x3dom.DrawableCollection.prototype.get = function (idx) {
    return this.collection[idx];
};

/**
 * Sort the DrawableCollection
 */
x3dom.DrawableCollection.prototype.sort = function () {
    var opaque = [];
    var transparent = [];
    var that = this;

    //Sort opaque drawables
    if (this.collection['opaque'] !== undefined) {
        // never call this for very big scenes, getting very slow; try binning approach
        if (this.sortOpaque) {
            this.collection['opaque'].sort(function (a, b) {
                if (a.sortKey == b.sortKey || !that.sortBySortKey) {
                    //Second sort criteria (priority)
                    return b.priority - a.priority;
                }
                //First sort criteria (sortKey)
                return a.sortKey - b.sortKey;
            });
        }
        opaque = this.collection['opaque'];
    }

    //Sort transparent drawables
    if (this.collection['transparent'] !== undefined) {
        if (this.sortTrans) {
            this.collection['transparent'].sort(function (a, b) {
                if (a.sortKey == b.sortKey || !that.sortBySortKey) {
                    if (a.priority == b.priority || !that.sortByPriority) {
                        //Third sort criteria (zPos)
                        return a.zPos - b.zPos;
                    }
                    //Second sort criteria (priority)
                    return b.priority - a.priority;
                }
                //First sort criteria (sortKey)
                return a.sortKey - b.sortKey;
            });
        }
        transparent = this.collection['transparent'];
    }

    //Merge opaque and transparent drawables to a single array (slow operation)
    this.collection = opaque.concat(transparent);
};

x3dom.DrawableCollection.prototype.forEach = function (fnc, maxPriority) {
    //Set maximal priority
    maxPriority = (maxPriority !== undefined) ? Math.min(maxPriority, this.prioLevels) : this.prioLevels;

    //Define run variables
    var sortKey, priority, shaderID, drawable;

    //First traverse Opaque drawables
    // TODO; FIXME; this is wrong, sortKey can also be negative!
    for (sortKey=0; sortKey<this.collection['opaque'].length; ++sortKey)
    {
        if (this.collection['opaque'][sortKey] !== undefined)
        {
            for (priority=this.collection['opaque'][sortKey].length; priority>0; --priority)
            {
                if (this.collection['opaque'][sortKey][priority] !== undefined)
                {
                    for (shaderID in this.collection['opaque'][sortKey][priority])
                    {
                        for (drawable=0; drawable<this.collection['opaque'][sortKey][priority][shaderID].length; ++drawable)
                        {
                            fnc( this.collection['opaque'][sortKey][priority][shaderID][drawable] );
                        }
                    }
                }
            }
        }
    }

    //Next traverse transparent drawables
    // TODO; FIXME; this is wrong, sortKey can also be negative!
    for (sortKey=0; sortKey<this.collection['transparent'].length; ++sortKey)
    {
        if (this.collection['transparent'][sortKey] !== undefined)
        {
            for (priority=this.collection['transparent'][sortKey].length; priority>0; --priority)
            {
                if (this.collection['transparent'][sortKey][priority] !== undefined)
                {
                    for (var shaderId in this.collection['transparent'][sortKey][priority])
                    {
                        //Sort transparent drawables by z-Pos
                        this.collection['transparent'][sortKey][priority][shaderId].sort(function(a, b) {
                            return a.zPos - b.zPos
                        });

                        for (drawable=0; drawable<this.collection['transparent'][sortKey][priority][shaderId].length; ++drawable)
                        {
                            fnc( this.collection['transparent'][sortKey][priority][shaderId][drawable] );
                        }
                    }
                }
            }
        }
    }
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


/**
 *  Moveable interface, wraps x3d bounded node with SpaceSensor-like movement functionality,
 *  therefore attaches event handlers, thus to be called earliest in document.onload method.
 *
 *  Cleanup backrefs and listeners on delete by explicitly calling detachHandlers()
 */
x3dom.Moveable = function(x3domElem, boundedObj, callback, gridSize, mode) {
    this._x3domRoot = x3domElem;
    this._runtime = x3domElem.runtime;

    // callback function for notifying changes
    this._callback = callback;

    // snap to grid of given size (0, no grid, if undefined)
    this._gridSize = gridSize ? gridSize : 0;

    this._moveable = boundedObj;
    this._drag = false;

    this._w = 0;
    this._h = 0;

    this._uPlane = null;
    this._vPlane = null;
    this._pPlane = null;

    this._isect = null;

    this._translationOffset = null;
    this._rotationOffset = null;
    this._scaleOffset = null;

    this._lastX = 0;
    this._lastY = 0;
    this._buttonState = 0;

    this._mode = (mode && mode.length) ? mode.toLowerCase() : "translation"; //"all";

    this._firstRay = null;
    this._matrixTrafo = null;

    this._navType = "examine";

    this.attachHandlers();
};

// grid size setter, for snapping
x3dom.Moveable.prototype.setGridSize = function(gridSize) {
    this._gridSize = gridSize;
};

// interaction mode setter, for translation and/or rotation
x3dom.Moveable.prototype.setMode = function(mode) {
    this._mode = mode.toLowerCase();
};

x3dom.Moveable.prototype.attachHandlers = function() {
    // add backref to movable object (for member access and wrapping)
    this._moveable._iMove = this;

    // add backref to <x3d> element
    if (!this._x3domRoot._iMove)
        this._x3domRoot._iMove = [];
    this._x3domRoot._iMove.push(this);

    // mouse events
    this._moveable.addEventListener('mousedown', this.start, false);
    this._moveable.addEventListener('mouseover', this.over, false);
    this._moveable.addEventListener('mouseout', this.out, false);

    if (this._x3domRoot._iMove.length == 1) {
        // more mouse events
        this._x3domRoot.addEventListener('mouseup', this.stop, false);
        this._x3domRoot.addEventListener('mouseout', this.stop, false);
        this._x3domRoot.addEventListener('mousemove', this.move, true);

        if (!this._runtime.canvas.disableTouch) {
            // mozilla touch events
            this._x3domRoot.addEventListener('MozTouchDown', this.touchStartHandlerMoz, false);
            this._x3domRoot.addEventListener('MozTouchMove', this.touchMoveHandlerMoz, true);
            this._x3domRoot.addEventListener('MozTouchUp', this.touchEndHandlerMoz, false);
            // w3c / apple touch events
            this._x3domRoot.addEventListener('touchstart', this.touchStartHandler, false);
            this._x3domRoot.addEventListener('touchmove', this.touchMoveHandler, true);
            this._x3domRoot.addEventListener('touchend', this.touchEndHandler, false);
        }
    }
};

x3dom.Moveable.prototype.detachHandlers = function() {
    // remove backref to <x3d> element
    var iMove = this._x3domRoot._iMove;
    if (iMove) {
        for (var i=0, n=iMove.length; i<n; i++) {
            if (iMove[i] == this) {
                iMove.splice(i, 1);
                break;
            }
        }
    }

    // mouse events
    this._moveable.removeEventListener('mousedown', this.start, false);
    this._moveable.removeEventListener('mouseover', this.over, false);
    this._moveable.removeEventListener('mouseout', this.out, false);

    if (iMove.length == 0) {
        // more mouse events
        this._x3domRoot.removeEventListener('mouseup', this.stop, false);
        this._x3domRoot.removeEventListener('mouseout', this.stop, false);
        this._x3domRoot.removeEventListener('mousemove', this.move, true);

        if (!this._runtime.canvas.disableTouch) {
            // touch events
            this._x3domRoot.removeEventListener('MozTouchDown', this.touchStartHandlerMoz, false);
            this._x3domRoot.removeEventListener('MozTouchMove', this.touchMoveHandlerMoz, true);
            this._x3domRoot.removeEventListener('MozTouchUp', this.touchEndHandlerMoz, false);
            // mozilla version
            this._x3domRoot.removeEventListener('touchstart', this.touchStartHandler, false);
            this._x3domRoot.removeEventListener('touchmove', this.touchMoveHandler, true);
            this._x3domRoot.removeEventListener('touchend', this.touchEndHandler, false);
        }
    }

    // finally remove backref to movable object
    if (this._moveable._iMove)
        delete this._moveable._iMove;
};

// calculate viewing plane
x3dom.Moveable.prototype.calcViewPlane = function(origin) {
    // init width and height
    this._w = this._runtime.getWidth();
    this._h = this._runtime.getHeight();

    //bottom left of viewarea
    var ray = this._runtime.getViewingRay(0, this._h - 1);
    var r = ray.pos.add(ray.dir);

    //bottom right of viewarea
    ray = this._runtime.getViewingRay(this._w - 1, this._h - 1);
    var s = ray.pos.add(ray.dir);

    //top left of viewarea
    ray = this._runtime.getViewingRay(0, 0);
    var t = ray.pos.add(ray.dir);

    this._uPlane = s.subtract(r).normalize();
    this._vPlane = t.subtract(r).normalize();

    if (arguments.length === 0)
        this._pPlane = r;
    else
        this._pPlane = x3dom.fields.SFVec3f.copy(origin);
};

// helper method to obtain determinant
x3dom.Moveable.prototype.det = function(mat) {
    return mat[0][0] * mat[1][1] * mat[2][2] + mat[0][1] * mat[1][2] * mat[2][0] +
           mat[0][2] * mat[2][1] * mat[1][0] - mat[2][0] * mat[1][1] * mat[0][2] -
           mat[0][0] * mat[2][1] * mat[1][2] - mat[1][0] * mat[0][1] * mat[2][2];
};

// Translation along plane parallel to viewing plane E:x=p+t*u+s*v
x3dom.Moveable.prototype.translateXY = function(l) {
    var track = null;
    var z = [], n = [];

    for (var i = 0; i < 3; i++) {
        z[i] = [];
        n[i] = [];

        z[i][0] = this._uPlane.at(i);
        n[i][0] = z[i][0];

        z[i][1] = this._vPlane.at(i);
        n[i][1] = z[i][1];

        z[i][2] = (l.pos.subtract(this._pPlane)).at(i);
        n[i][2] = -l.dir.at(i);
    }

    // get intersection line-plane with Cramer's rule
    var s = this.det(n);

    if (s !== 0) {
        var t = this.det(z) / s;
        track = l.pos.addScaled(l.dir, t);
    }

    if (track) {
        if (this._isect) {
            // calc offset from first click position
            track = track.subtract(this._isect);
        }
        track = track.add(this._translationOffset);
    }

    return track;
};

// Translation along picking ray
x3dom.Moveable.prototype.translateZ = function(l, currY) {
    var vol = this._runtime.getSceneBBox();

    var sign = (currY < this._lastY) ? 1 : -1;
    var fact = sign * (vol.max.subtract(vol.min)).length() / 100;

    this._translationOffset = this._translationOffset.addScaled(l.dir, fact);

    return this._translationOffset;
};

x3dom.Moveable.prototype.rotate = function(posX, posY) {
    var twoPi = 2 * Math.PI;
    var alpha = ((posY - this._lastY) * twoPi) / this._w;
    var beta  = ((posX - this._lastX) * twoPi) / this._h;

    var q = x3dom.fields.Quaternion.axisAngle(this._uPlane, alpha);
    var h = q.toMatrix();
    this._rotationOffset = h.mult(this._rotationOffset);

    q = x3dom.fields.Quaternion.axisAngle(this._vPlane, beta);
    h = q.toMatrix();
    this._rotationOffset = h.mult(this._rotationOffset);

    var mat = this._rotationOffset.mult(x3dom.fields.SFMatrix4f.scale(this._scaleOffset));
    var rot = new x3dom.fields.Quaternion(0, 0, 1, 0);
    rot.setValue(mat);

    return rot;
};

x3dom.Moveable.prototype.over = function(event) {
    var that = this._iMove;

    that._runtime.getCanvas().style.cursor = "crosshair";
};

x3dom.Moveable.prototype.out = function(event) {
    var that = this._iMove;

    if (!that._drag)
        that._runtime.getCanvas().style.cursor = "pointer";
};

// start object movement, switch from navigation to interaction
x3dom.Moveable.prototype.start = function(event) {
    var that = this._iMove;

    // use mouse button to distinguish between parallel or orthogonal movement or rotation
    switch (that._mode) {
        case "translation":
            that._buttonState = (event.button == 4) ? 1 : (event.button & 3);
            break;
        case "rotation":
            that._buttonState = 4;
            break;
        case "all":
        default:
            that._buttonState = event.button;
            break;
    }

    if (!that._drag && that._buttonState) {
        that._lastX = event.layerX;
        that._lastY = event.layerY;

        that._drag = true;

        // temporarily disable navigation
        that._navType = that._runtime.navigationType();
        that._runtime.noNav();

        // calc view-aligned plane through original pick position
        that._isect = new x3dom.fields.SFVec3f(event.worldX, event.worldY, event.worldZ);
        that.calcViewPlane(that._isect);

        that._firstRay = that._runtime.getViewingRay(event.layerX, event.layerY);

        var mTrans = that._moveable.getAttribute("translation");
        that._matrixTrafo = null;

        if (mTrans) {
            that._translationOffset = x3dom.fields.SFVec3f.parse(mTrans);

            var mRot = that._moveable.getAttribute("rotation");
            mRot = mRot ? x3dom.fields.Quaternion.parseAxisAngle(mRot) : new x3dom.fields.Quaternion(0,0,1,0);
            that._rotationOffset = mRot.toMatrix();

            var mScal = that._moveable.getAttribute("scale");
            that._scaleOffset = mScal ? x3dom.fields.SFVec3f.parse(mScal) : new x3dom.fields.SFVec3f(1, 1, 1);
        }
        else {
            mTrans = that._moveable.getAttribute("matrix");

            if (mTrans) {
                that._matrixTrafo = x3dom.fields.SFMatrix4f.parse(mTrans).transpose();

                var translation = new x3dom.fields.SFVec3f(0,0,0),
                    scaleFactor = new x3dom.fields.SFVec3f(1,1,1);
                var rotation = new x3dom.fields.Quaternion(0,0,1,0),
                    scaleOrientation = new x3dom.fields.Quaternion(0,0,1,0);

                that._matrixTrafo.getTransform(translation, rotation, scaleFactor, scaleOrientation);

                //that._translationOffset = that._matrixTrafo.e3();
                that._translationOffset = translation;
                that._rotationOffset = rotation.toMatrix();
                that._scaleOffset = scaleFactor;
            }
            else {
                that._translationOffset = new x3dom.fields.SFVec3f(0, 0, 0);
                that._rotationOffset = new x3dom.fields.SFMatrix4f();
                that._scaleOffset = new x3dom.fields.SFVec3f(1, 1, 1);
            }
        }

        that._runtime.getCanvas().style.cursor = "crosshair";
    }
};

x3dom.Moveable.prototype.move = function(event) {
    for (var i=0, n=this._iMove.length; i<n; i++) {
        var that = this._iMove[i];

        if (that._drag) {
            var pos = that._runtime.mousePosition(event);
            var ray = that._runtime.getViewingRay(pos[0], pos[1]);

            var track = null;

            // zoom with right mouse button (2), pan with left (1)
            if (that._buttonState == 2)
                track = that.translateZ(that._firstRay, pos[1]);
            else if (that._buttonState == 1)
                track = that.translateXY(ray);
            else  // middle button: 4
                track = that.rotate(pos[0], pos[1]);

            if (track) {
                if (that._gridSize > 0 && that._buttonState != 4) {
                    var x = that._gridSize * Math.round(track.x / that._gridSize);
                    var y = that._gridSize * Math.round(track.y / that._gridSize);
                    var z = that._gridSize * Math.round(track.z / that._gridSize);
                    track = new x3dom.fields.SFVec3f(x, y, z);
                }

                if (!that._matrixTrafo) {
                    if (that._buttonState == 4) {
                        that._moveable.setAttribute("rotation", track.toAxisAngle().toString());
                    }
                    else {
                        that._moveable.setAttribute("translation", track.toString());
                    }
                }
                else {
                    if (that._buttonState == 4) {
                        that._matrixTrafo.setRotate(track);
                    }
                    else {
                        that._matrixTrafo.setTranslate(track);
                    }
                    that._moveable.setAttribute("matrix", that._matrixTrafo.toGL().toString());
                }

                if (that._callback) {
                    that._callback(that._moveable, track);
                }
            }

            that._lastX = pos[0];
            that._lastY = pos[1];
        }
    }
};

// stop object movement, switch from interaction to navigation
x3dom.Moveable.prototype.stop = function(event) {
    for (var i=0, n=this._iMove.length; i<n; i++) {
        var that = this._iMove[i];

        if (that._drag) {
            that._lastX = event.layerX;
            that._lastY = event.layerY;

            that._isect = null;
            that._drag = false;

            // we're done, re-enable navigation
            var navi = that._runtime.canvas.doc._scene.getNavigationInfo();
            navi.setType(that._navType);

            that._runtime.getCanvas().style.cursor = "pointer";
        }
    }
};

// TODO: impl. special (multi-)touch event stuff
// === Touch Start (W3C) ===
x3dom.Moveable.prototype.touchStartHandler = function (evt) {
    evt.preventDefault();
};

// === Touch Start Moz (Firefox has other touch interface) ===
x3dom.Moveable.prototype.touchStartHandlerMoz = function (evt) {
    evt.preventDefault();
};

// === Touch Move ===
x3dom.Moveable.prototype.touchMoveHandler = function (evt) {
    evt.preventDefault();
};

// === Touch Move Moz ===
x3dom.Moveable.prototype.touchMoveHandlerMoz = function (evt) {
    evt.preventDefault();
};

// === Touch End ===
x3dom.Moveable.prototype.touchEndHandler = function (evt) {
    if (this._iMove.length) {
        var that = this._iMove[0];
        // mouse start code is called, but not stop
        that.stop.apply(that._x3domRoot, [evt]);
    }
    evt.preventDefault();
};

// === Touch End Moz ===
x3dom.Moveable.prototype.touchEndHandlerMoz = function (evt) {
    if (this._iMove.length) {
        var that = this._iMove[0];
        that.stop.apply(that._x3domRoot, [evt]);
    }
    evt.preventDefault();
};

/** @license zlib.js 2012 - imaya [ https://github.com/imaya/zlib.js ] The MIT License */(function() {'use strict';function q(b){throw b;}var t=void 0,u=!0,aa=this;function A(b,a){var c=b.split("."),d=aa;!(c[0]in d)&&d.execScript&&d.execScript("var "+c[0]);for(var e;c.length&&(e=c.shift());)!c.length&&a!==t?d[e]=a:d=d[e]?d[e]:d[e]={}};var B="undefined"!==typeof Uint8Array&&"undefined"!==typeof Uint16Array&&"undefined"!==typeof Uint32Array&&"undefined"!==typeof DataView;function F(b,a){this.index="number"===typeof a?a:0;this.m=0;this.buffer=b instanceof(B?Uint8Array:Array)?b:new (B?Uint8Array:Array)(32768);2*this.buffer.length<=this.index&&q(Error("invalid index"));this.buffer.length<=this.index&&this.f()}F.prototype.f=function(){var b=this.buffer,a,c=b.length,d=new (B?Uint8Array:Array)(c<<1);if(B)d.set(b);else for(a=0;a<c;++a)d[a]=b[a];return this.buffer=d};
F.prototype.d=function(b,a,c){var d=this.buffer,e=this.index,f=this.m,g=d[e],k;c&&1<a&&(b=8<a?(H[b&255]<<24|H[b>>>8&255]<<16|H[b>>>16&255]<<8|H[b>>>24&255])>>32-a:H[b]>>8-a);if(8>a+f)g=g<<a|b,f+=a;else for(k=0;k<a;++k)g=g<<1|b>>a-k-1&1,8===++f&&(f=0,d[e++]=H[g],g=0,e===d.length&&(d=this.f()));d[e]=g;this.buffer=d;this.m=f;this.index=e};F.prototype.finish=function(){var b=this.buffer,a=this.index,c;0<this.m&&(b[a]<<=8-this.m,b[a]=H[b[a]],a++);B?c=b.subarray(0,a):(b.length=a,c=b);return c};
var ba=new (B?Uint8Array:Array)(256),ca;for(ca=0;256>ca;++ca){for(var K=ca,da=K,ea=7,K=K>>>1;K;K>>>=1)da<<=1,da|=K&1,--ea;ba[ca]=(da<<ea&255)>>>0}var H=ba;function ja(b,a,c){var d,e="number"===typeof a?a:a=0,f="number"===typeof c?c:b.length;d=-1;for(e=f&7;e--;++a)d=d>>>8^O[(d^b[a])&255];for(e=f>>3;e--;a+=8)d=d>>>8^O[(d^b[a])&255],d=d>>>8^O[(d^b[a+1])&255],d=d>>>8^O[(d^b[a+2])&255],d=d>>>8^O[(d^b[a+3])&255],d=d>>>8^O[(d^b[a+4])&255],d=d>>>8^O[(d^b[a+5])&255],d=d>>>8^O[(d^b[a+6])&255],d=d>>>8^O[(d^b[a+7])&255];return(d^4294967295)>>>0}
var ka=[0,1996959894,3993919788,2567524794,124634137,1886057615,3915621685,2657392035,249268274,2044508324,3772115230,2547177864,162941995,2125561021,3887607047,2428444049,498536548,1789927666,4089016648,2227061214,450548861,1843258603,4107580753,2211677639,325883990,1684777152,4251122042,2321926636,335633487,1661365465,4195302755,2366115317,997073096,1281953886,3579855332,2724688242,1006888145,1258607687,3524101629,2768942443,901097722,1119000684,3686517206,2898065728,853044451,1172266101,3705015759,
2882616665,651767980,1373503546,3369554304,3218104598,565507253,1454621731,3485111705,3099436303,671266974,1594198024,3322730930,2970347812,795835527,1483230225,3244367275,3060149565,1994146192,31158534,2563907772,4023717930,1907459465,112637215,2680153253,3904427059,2013776290,251722036,2517215374,3775830040,2137656763,141376813,2439277719,3865271297,1802195444,476864866,2238001368,4066508878,1812370925,453092731,2181625025,4111451223,1706088902,314042704,2344532202,4240017532,1658658271,366619977,
2362670323,4224994405,1303535960,984961486,2747007092,3569037538,1256170817,1037604311,2765210733,3554079995,1131014506,879679996,2909243462,3663771856,1141124467,855842277,2852801631,3708648649,1342533948,654459306,3188396048,3373015174,1466479909,544179635,3110523913,3462522015,1591671054,702138776,2966460450,3352799412,1504918807,783551873,3082640443,3233442989,3988292384,2596254646,62317068,1957810842,3939845945,2647816111,81470997,1943803523,3814918930,2489596804,225274430,2053790376,3826175755,
2466906013,167816743,2097651377,4027552580,2265490386,503444072,1762050814,4150417245,2154129355,426522225,1852507879,4275313526,2312317920,282753626,1742555852,4189708143,2394877945,397917763,1622183637,3604390888,2714866558,953729732,1340076626,3518719985,2797360999,1068828381,1219638859,3624741850,2936675148,906185462,1090812512,3747672003,2825379669,829329135,1181335161,3412177804,3160834842,628085408,1382605366,3423369109,3138078467,570562233,1426400815,3317316542,2998733608,733239954,1555261956,
3268935591,3050360625,752459403,1541320221,2607071920,3965973030,1969922972,40735498,2617837225,3943577151,1913087877,83908371,2512341634,3803740692,2075208622,213261112,2463272603,3855990285,2094854071,198958881,2262029012,4057260610,1759359992,534414190,2176718541,4139329115,1873836001,414664567,2282248934,4279200368,1711684554,285281116,2405801727,4167216745,1634467795,376229701,2685067896,3608007406,1308918612,956543938,2808555105,3495958263,1231636301,1047427035,2932959818,3654703836,1088359270,
936918E3,2847714899,3736837829,1202900863,817233897,3183342108,3401237130,1404277552,615818150,3134207493,3453421203,1423857449,601450431,3009837614,3294710456,1567103746,711928724,3020668471,3272380065,1510334235,755167117],O=B?new Uint32Array(ka):ka;function P(){}P.prototype.getName=function(){return this.name};P.prototype.getData=function(){return this.data};P.prototype.Y=function(){return this.Z};A("Zlib.GunzipMember",P);A("Zlib.GunzipMember.prototype.getName",P.prototype.getName);A("Zlib.GunzipMember.prototype.getData",P.prototype.getData);A("Zlib.GunzipMember.prototype.getMtime",P.prototype.Y);function la(b){this.buffer=new (B?Uint16Array:Array)(2*b);this.length=0}la.prototype.getParent=function(b){return 2*((b-2)/4|0)};la.prototype.push=function(b,a){var c,d,e=this.buffer,f;c=this.length;e[this.length++]=a;for(e[this.length++]=b;0<c;)if(d=this.getParent(c),e[c]>e[d])f=e[c],e[c]=e[d],e[d]=f,f=e[c+1],e[c+1]=e[d+1],e[d+1]=f,c=d;else break;return this.length};
la.prototype.pop=function(){var b,a,c=this.buffer,d,e,f;a=c[0];b=c[1];this.length-=2;c[0]=c[this.length];c[1]=c[this.length+1];for(f=0;;){e=2*f+2;if(e>=this.length)break;e+2<this.length&&c[e+2]>c[e]&&(e+=2);if(c[e]>c[f])d=c[f],c[f]=c[e],c[e]=d,d=c[f+1],c[f+1]=c[e+1],c[e+1]=d;else break;f=e}return{index:b,value:a,length:this.length}};function ma(b){var a=b.length,c=0,d=Number.POSITIVE_INFINITY,e,f,g,k,h,l,s,p,m,n;for(p=0;p<a;++p)b[p]>c&&(c=b[p]),b[p]<d&&(d=b[p]);e=1<<c;f=new (B?Uint32Array:Array)(e);g=1;k=0;for(h=2;g<=c;){for(p=0;p<a;++p)if(b[p]===g){l=0;s=k;for(m=0;m<g;++m)l=l<<1|s&1,s>>=1;n=g<<16|p;for(m=l;m<e;m+=h)f[m]=n;++k}++g;k<<=1;h<<=1}return[f,c,d]};function na(b,a){this.k=qa;this.I=0;this.input=B&&b instanceof Array?new Uint8Array(b):b;this.b=0;a&&(a.lazy&&(this.I=a.lazy),"number"===typeof a.compressionType&&(this.k=a.compressionType),a.outputBuffer&&(this.a=B&&a.outputBuffer instanceof Array?new Uint8Array(a.outputBuffer):a.outputBuffer),"number"===typeof a.outputIndex&&(this.b=a.outputIndex));this.a||(this.a=new (B?Uint8Array:Array)(32768))}var qa=2,ra={NONE:0,v:1,o:qa,ba:3},sa=[],S;
for(S=0;288>S;S++)switch(u){case 143>=S:sa.push([S+48,8]);break;case 255>=S:sa.push([S-144+400,9]);break;case 279>=S:sa.push([S-256+0,7]);break;case 287>=S:sa.push([S-280+192,8]);break;default:q("invalid literal: "+S)}
na.prototype.g=function(){var b,a,c,d,e=this.input;switch(this.k){case 0:c=0;for(d=e.length;c<d;){a=B?e.subarray(c,c+65535):e.slice(c,c+65535);c+=a.length;var f=a,g=c===d,k=t,h=t,l=t,s=t,p=t,m=this.a,n=this.b;if(B){for(m=new Uint8Array(this.a.buffer);m.length<=n+f.length+5;)m=new Uint8Array(m.length<<1);m.set(this.a)}k=g?1:0;m[n++]=k|0;h=f.length;l=~h+65536&65535;m[n++]=h&255;m[n++]=h>>>8&255;m[n++]=l&255;m[n++]=l>>>8&255;if(B)m.set(f,n),n+=f.length,m=m.subarray(0,n);else{s=0;for(p=f.length;s<p;++s)m[n++]=
f[s];m.length=n}this.b=n;this.a=m}break;case 1:var r=new F(B?new Uint8Array(this.a.buffer):this.a,this.b);r.d(1,1,u);r.d(1,2,u);var v=ta(this,e),x,Q,y;x=0;for(Q=v.length;x<Q;x++)if(y=v[x],F.prototype.d.apply(r,sa[y]),256<y)r.d(v[++x],v[++x],u),r.d(v[++x],5),r.d(v[++x],v[++x],u);else if(256===y)break;this.a=r.finish();this.b=this.a.length;break;case qa:var E=new F(B?new Uint8Array(this.a.buffer):this.a,this.b),Ka,R,X,Y,Z,pb=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],fa,La,ga,Ma,oa,wa=Array(19),
Na,$,pa,C,Oa;Ka=qa;E.d(1,1,u);E.d(Ka,2,u);R=ta(this,e);fa=ua(this.W,15);La=va(fa);ga=ua(this.V,7);Ma=va(ga);for(X=286;257<X&&0===fa[X-1];X--);for(Y=30;1<Y&&0===ga[Y-1];Y--);var Pa=X,Qa=Y,J=new (B?Uint32Array:Array)(Pa+Qa),w,L,z,ha,I=new (B?Uint32Array:Array)(316),G,D,M=new (B?Uint8Array:Array)(19);for(w=L=0;w<Pa;w++)J[L++]=fa[w];for(w=0;w<Qa;w++)J[L++]=ga[w];if(!B){w=0;for(ha=M.length;w<ha;++w)M[w]=0}w=G=0;for(ha=J.length;w<ha;w+=L){for(L=1;w+L<ha&&J[w+L]===J[w];++L);z=L;if(0===J[w])if(3>z)for(;0<
z--;)I[G++]=0,M[0]++;else for(;0<z;)D=138>z?z:138,D>z-3&&D<z&&(D=z-3),10>=D?(I[G++]=17,I[G++]=D-3,M[17]++):(I[G++]=18,I[G++]=D-11,M[18]++),z-=D;else if(I[G++]=J[w],M[J[w]]++,z--,3>z)for(;0<z--;)I[G++]=J[w],M[J[w]]++;else for(;0<z;)D=6>z?z:6,D>z-3&&D<z&&(D=z-3),I[G++]=16,I[G++]=D-3,M[16]++,z-=D}b=B?I.subarray(0,G):I.slice(0,G);oa=ua(M,7);for(C=0;19>C;C++)wa[C]=oa[pb[C]];for(Z=19;4<Z&&0===wa[Z-1];Z--);Na=va(oa);E.d(X-257,5,u);E.d(Y-1,5,u);E.d(Z-4,4,u);for(C=0;C<Z;C++)E.d(wa[C],3,u);C=0;for(Oa=b.length;C<
Oa;C++)if($=b[C],E.d(Na[$],oa[$],u),16<=$){C++;switch($){case 16:pa=2;break;case 17:pa=3;break;case 18:pa=7;break;default:q("invalid code: "+$)}E.d(b[C],pa,u)}var Ra=[La,fa],Sa=[Ma,ga],N,Ta,ia,za,Ua,Va,Wa,Xa;Ua=Ra[0];Va=Ra[1];Wa=Sa[0];Xa=Sa[1];N=0;for(Ta=R.length;N<Ta;++N)if(ia=R[N],E.d(Ua[ia],Va[ia],u),256<ia)E.d(R[++N],R[++N],u),za=R[++N],E.d(Wa[za],Xa[za],u),E.d(R[++N],R[++N],u);else if(256===ia)break;this.a=E.finish();this.b=this.a.length;break;default:q("invalid compression type")}return this.a};
function xa(b,a){this.length=b;this.Q=a}
var ya=function(){function b(a){switch(u){case 3===a:return[257,a-3,0];case 4===a:return[258,a-4,0];case 5===a:return[259,a-5,0];case 6===a:return[260,a-6,0];case 7===a:return[261,a-7,0];case 8===a:return[262,a-8,0];case 9===a:return[263,a-9,0];case 10===a:return[264,a-10,0];case 12>=a:return[265,a-11,1];case 14>=a:return[266,a-13,1];case 16>=a:return[267,a-15,1];case 18>=a:return[268,a-17,1];case 22>=a:return[269,a-19,2];case 26>=a:return[270,a-23,2];case 30>=a:return[271,a-27,2];case 34>=a:return[272,
a-31,2];case 42>=a:return[273,a-35,3];case 50>=a:return[274,a-43,3];case 58>=a:return[275,a-51,3];case 66>=a:return[276,a-59,3];case 82>=a:return[277,a-67,4];case 98>=a:return[278,a-83,4];case 114>=a:return[279,a-99,4];case 130>=a:return[280,a-115,4];case 162>=a:return[281,a-131,5];case 194>=a:return[282,a-163,5];case 226>=a:return[283,a-195,5];case 257>=a:return[284,a-227,5];case 258===a:return[285,a-258,0];default:q("invalid length: "+a)}}var a=[],c,d;for(c=3;258>=c;c++)d=b(c),a[c]=d[2]<<24|d[1]<<
16|d[0];return a}(),Aa=B?new Uint32Array(ya):ya;
function ta(b,a){function c(a,c){var b=a.Q,d=[],e=0,f;f=Aa[a.length];d[e++]=f&65535;d[e++]=f>>16&255;d[e++]=f>>24;var g;switch(u){case 1===b:g=[0,b-1,0];break;case 2===b:g=[1,b-2,0];break;case 3===b:g=[2,b-3,0];break;case 4===b:g=[3,b-4,0];break;case 6>=b:g=[4,b-5,1];break;case 8>=b:g=[5,b-7,1];break;case 12>=b:g=[6,b-9,2];break;case 16>=b:g=[7,b-13,2];break;case 24>=b:g=[8,b-17,3];break;case 32>=b:g=[9,b-25,3];break;case 48>=b:g=[10,b-33,4];break;case 64>=b:g=[11,b-49,4];break;case 96>=b:g=[12,b-
65,5];break;case 128>=b:g=[13,b-97,5];break;case 192>=b:g=[14,b-129,6];break;case 256>=b:g=[15,b-193,6];break;case 384>=b:g=[16,b-257,7];break;case 512>=b:g=[17,b-385,7];break;case 768>=b:g=[18,b-513,8];break;case 1024>=b:g=[19,b-769,8];break;case 1536>=b:g=[20,b-1025,9];break;case 2048>=b:g=[21,b-1537,9];break;case 3072>=b:g=[22,b-2049,10];break;case 4096>=b:g=[23,b-3073,10];break;case 6144>=b:g=[24,b-4097,11];break;case 8192>=b:g=[25,b-6145,11];break;case 12288>=b:g=[26,b-8193,12];break;case 16384>=
b:g=[27,b-12289,12];break;case 24576>=b:g=[28,b-16385,13];break;case 32768>=b:g=[29,b-24577,13];break;default:q("invalid distance")}f=g;d[e++]=f[0];d[e++]=f[1];d[e++]=f[2];var h,k;h=0;for(k=d.length;h<k;++h)m[n++]=d[h];v[d[0]]++;x[d[3]]++;r=a.length+c-1;p=null}var d,e,f,g,k,h={},l,s,p,m=B?new Uint16Array(2*a.length):[],n=0,r=0,v=new (B?Uint32Array:Array)(286),x=new (B?Uint32Array:Array)(30),Q=b.I,y;if(!B){for(f=0;285>=f;)v[f++]=0;for(f=0;29>=f;)x[f++]=0}v[256]=1;d=0;for(e=a.length;d<e;++d){f=k=0;
for(g=3;f<g&&d+f!==e;++f)k=k<<8|a[d+f];h[k]===t&&(h[k]=[]);l=h[k];if(!(0<r--)){for(;0<l.length&&32768<d-l[0];)l.shift();if(d+3>=e){p&&c(p,-1);f=0;for(g=e-d;f<g;++f)y=a[d+f],m[n++]=y,++v[y];break}0<l.length?(s=Ba(a,d,l),p?p.length<s.length?(y=a[d-1],m[n++]=y,++v[y],c(s,0)):c(p,-1):s.length<Q?p=s:c(s,0)):p?c(p,-1):(y=a[d],m[n++]=y,++v[y])}l.push(d)}m[n++]=256;v[256]++;b.W=v;b.V=x;return B?m.subarray(0,n):m}
function Ba(b,a,c){var d,e,f=0,g,k,h,l,s=b.length;k=0;l=c.length;a:for(;k<l;k++){d=c[l-k-1];g=3;if(3<f){for(h=f;3<h;h--)if(b[d+h-1]!==b[a+h-1])continue a;g=f}for(;258>g&&a+g<s&&b[d+g]===b[a+g];)++g;g>f&&(e=d,f=g);if(258===g)break}return new xa(f,a-e)}
function ua(b,a){var c=b.length,d=new la(572),e=new (B?Uint8Array:Array)(c),f,g,k,h,l;if(!B)for(h=0;h<c;h++)e[h]=0;for(h=0;h<c;++h)0<b[h]&&d.push(h,b[h]);f=Array(d.length/2);g=new (B?Uint32Array:Array)(d.length/2);if(1===f.length)return e[d.pop().index]=1,e;h=0;for(l=d.length/2;h<l;++h)f[h]=d.pop(),g[h]=f[h].value;k=Ca(g,g.length,a);h=0;for(l=f.length;h<l;++h)e[f[h].index]=k[h];return e}
function Ca(b,a,c){function d(b){var c=h[b][l[b]];c===a?(d(b+1),d(b+1)):--g[c];++l[b]}var e=new (B?Uint16Array:Array)(c),f=new (B?Uint8Array:Array)(c),g=new (B?Uint8Array:Array)(a),k=Array(c),h=Array(c),l=Array(c),s=(1<<c)-a,p=1<<c-1,m,n,r,v,x;e[c-1]=a;for(n=0;n<c;++n)s<p?f[n]=0:(f[n]=1,s-=p),s<<=1,e[c-2-n]=(e[c-1-n]/2|0)+a;e[0]=f[0];k[0]=Array(e[0]);h[0]=Array(e[0]);for(n=1;n<c;++n)e[n]>2*e[n-1]+f[n]&&(e[n]=2*e[n-1]+f[n]),k[n]=Array(e[n]),h[n]=Array(e[n]);for(m=0;m<a;++m)g[m]=c;for(r=0;r<e[c-1];++r)k[c-
1][r]=b[r],h[c-1][r]=r;for(m=0;m<c;++m)l[m]=0;1===f[c-1]&&(--g[0],++l[c-1]);for(n=c-2;0<=n;--n){v=m=0;x=l[n+1];for(r=0;r<e[n];r++)v=k[n+1][x]+k[n+1][x+1],v>b[m]?(k[n][r]=v,h[n][r]=a,x+=2):(k[n][r]=b[m],h[n][r]=m,++m);l[n]=0;1===f[n]&&d(n)}return g}
function va(b){var a=new (B?Uint16Array:Array)(b.length),c=[],d=[],e=0,f,g,k,h;f=0;for(g=b.length;f<g;f++)c[b[f]]=(c[b[f]]|0)+1;f=1;for(g=16;f<=g;f++)d[f]=e,e+=c[f]|0,e<<=1;f=0;for(g=b.length;f<g;f++){e=d[b[f]];d[b[f]]+=1;k=a[f]=0;for(h=b[f];k<h;k++)a[f]=a[f]<<1|e&1,e>>>=1}return a};function Da(b,a){this.input=b;this.b=this.c=0;this.i={};a&&(a.flags&&(this.i=a.flags),"string"===typeof a.filename&&(this.filename=a.filename),"string"===typeof a.comment&&(this.A=a.comment),a.deflateOptions&&(this.l=a.deflateOptions));this.l||(this.l={})}
Da.prototype.g=function(){var b,a,c,d,e,f,g,k,h=new (B?Uint8Array:Array)(32768),l=0,s=this.input,p=this.c,m=this.filename,n=this.A;h[l++]=31;h[l++]=139;h[l++]=8;b=0;this.i.fname&&(b|=Ea);this.i.fcomment&&(b|=Fa);this.i.fhcrc&&(b|=Ga);h[l++]=b;a=(Date.now?Date.now():+new Date)/1E3|0;h[l++]=a&255;h[l++]=a>>>8&255;h[l++]=a>>>16&255;h[l++]=a>>>24&255;h[l++]=0;h[l++]=Ha;if(this.i.fname!==t){g=0;for(k=m.length;g<k;++g)f=m.charCodeAt(g),255<f&&(h[l++]=f>>>8&255),h[l++]=f&255;h[l++]=0}if(this.i.comment){g=
0;for(k=n.length;g<k;++g)f=n.charCodeAt(g),255<f&&(h[l++]=f>>>8&255),h[l++]=f&255;h[l++]=0}this.i.fhcrc&&(c=ja(h,0,l)&65535,h[l++]=c&255,h[l++]=c>>>8&255);this.l.outputBuffer=h;this.l.outputIndex=l;e=new na(s,this.l);h=e.g();l=e.b;B&&(l+8>h.buffer.byteLength?(this.a=new Uint8Array(l+8),this.a.set(new Uint8Array(h.buffer)),h=this.a):h=new Uint8Array(h.buffer));d=ja(s,t,t);h[l++]=d&255;h[l++]=d>>>8&255;h[l++]=d>>>16&255;h[l++]=d>>>24&255;k=s.length;h[l++]=k&255;h[l++]=k>>>8&255;h[l++]=k>>>16&255;h[l++]=
k>>>24&255;this.c=p;B&&l<h.length&&(this.a=h=h.subarray(0,l));return h};var Ha=255,Ga=2,Ea=8,Fa=16;A("Zlib.Gzip",Da);A("Zlib.Gzip.prototype.compress",Da.prototype.g);function T(b,a){this.p=[];this.q=32768;this.e=this.j=this.c=this.u=0;this.input=B?new Uint8Array(b):b;this.w=!1;this.r=Ia;this.M=!1;if(a||!(a={}))a.index&&(this.c=a.index),a.bufferSize&&(this.q=a.bufferSize),a.bufferType&&(this.r=a.bufferType),a.resize&&(this.M=a.resize);switch(this.r){case Ja:this.b=32768;this.a=new (B?Uint8Array:Array)(32768+this.q+258);break;case Ia:this.b=0;this.a=new (B?Uint8Array:Array)(this.q);this.f=this.U;this.B=this.R;this.s=this.T;break;default:q(Error("invalid inflate mode"))}}
var Ja=0,Ia=1,Ya={O:Ja,N:Ia};
T.prototype.h=function(){for(;!this.w;){var b=U(this,3);b&1&&(this.w=u);b>>>=1;switch(b){case 0:var a=this.input,c=this.c,d=this.a,e=this.b,f=a.length,g=t,k=t,h=d.length,l=t;this.e=this.j=0;c+1>=f&&q(Error("invalid uncompressed block header: LEN"));g=a[c++]|a[c++]<<8;c+1>=f&&q(Error("invalid uncompressed block header: NLEN"));k=a[c++]|a[c++]<<8;g===~k&&q(Error("invalid uncompressed block header: length verify"));c+g>a.length&&q(Error("input buffer is broken"));switch(this.r){case Ja:for(;e+g>d.length;){l=
h-e;g-=l;if(B)d.set(a.subarray(c,c+l),e),e+=l,c+=l;else for(;l--;)d[e++]=a[c++];this.b=e;d=this.f();e=this.b}break;case Ia:for(;e+g>d.length;)d=this.f({F:2});break;default:q(Error("invalid inflate mode"))}if(B)d.set(a.subarray(c,c+g),e),e+=g,c+=g;else for(;g--;)d[e++]=a[c++];this.c=c;this.b=e;this.a=d;break;case 1:this.s(Za,$a);break;case 2:ab(this);break;default:q(Error("unknown BTYPE: "+b))}}return this.B()};
var bb=[16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15],cb=B?new Uint16Array(bb):bb,db=[3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258,258,258],eb=B?new Uint16Array(db):db,fb=[0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0],gb=B?new Uint8Array(fb):fb,hb=[1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577],ib=B?new Uint16Array(hb):hb,jb=[0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,
10,11,11,12,12,13,13],kb=B?new Uint8Array(jb):jb,lb=new (B?Uint8Array:Array)(288),V,mb;V=0;for(mb=lb.length;V<mb;++V)lb[V]=143>=V?8:255>=V?9:279>=V?7:8;var Za=ma(lb),nb=new (B?Uint8Array:Array)(30),ob,qb;ob=0;for(qb=nb.length;ob<qb;++ob)nb[ob]=5;var $a=ma(nb);function U(b,a){for(var c=b.j,d=b.e,e=b.input,f=b.c,g=e.length,k;d<a;)f>=g&&q(Error("input buffer is broken")),c|=e[f++]<<d,d+=8;k=c&(1<<a)-1;b.j=c>>>a;b.e=d-a;b.c=f;return k}
function rb(b,a){for(var c=b.j,d=b.e,e=b.input,f=b.c,g=e.length,k=a[0],h=a[1],l,s;d<h&&!(f>=g);)c|=e[f++]<<d,d+=8;l=k[c&(1<<h)-1];s=l>>>16;b.j=c>>s;b.e=d-s;b.c=f;return l&65535}
function ab(b){function a(a,b,c){var d,e=this.J,f,g;for(g=0;g<a;)switch(d=rb(this,b),d){case 16:for(f=3+U(this,2);f--;)c[g++]=e;break;case 17:for(f=3+U(this,3);f--;)c[g++]=0;e=0;break;case 18:for(f=11+U(this,7);f--;)c[g++]=0;e=0;break;default:e=c[g++]=d}this.J=e;return c}var c=U(b,5)+257,d=U(b,5)+1,e=U(b,4)+4,f=new (B?Uint8Array:Array)(cb.length),g,k,h,l;for(l=0;l<e;++l)f[cb[l]]=U(b,3);if(!B){l=e;for(e=f.length;l<e;++l)f[cb[l]]=0}g=ma(f);k=new (B?Uint8Array:Array)(c);h=new (B?Uint8Array:Array)(d);
b.J=0;b.s(ma(a.call(b,c,g,k)),ma(a.call(b,d,g,h)))}T.prototype.s=function(b,a){var c=this.a,d=this.b;this.C=b;for(var e=c.length-258,f,g,k,h;256!==(f=rb(this,b));)if(256>f)d>=e&&(this.b=d,c=this.f(),d=this.b),c[d++]=f;else{g=f-257;h=eb[g];0<gb[g]&&(h+=U(this,gb[g]));f=rb(this,a);k=ib[f];0<kb[f]&&(k+=U(this,kb[f]));d>=e&&(this.b=d,c=this.f(),d=this.b);for(;h--;)c[d]=c[d++-k]}for(;8<=this.e;)this.e-=8,this.c--;this.b=d};
T.prototype.T=function(b,a){var c=this.a,d=this.b;this.C=b;for(var e=c.length,f,g,k,h;256!==(f=rb(this,b));)if(256>f)d>=e&&(c=this.f(),e=c.length),c[d++]=f;else{g=f-257;h=eb[g];0<gb[g]&&(h+=U(this,gb[g]));f=rb(this,a);k=ib[f];0<kb[f]&&(k+=U(this,kb[f]));d+h>e&&(c=this.f(),e=c.length);for(;h--;)c[d]=c[d++-k]}for(;8<=this.e;)this.e-=8,this.c--;this.b=d};
T.prototype.f=function(){var b=new (B?Uint8Array:Array)(this.b-32768),a=this.b-32768,c,d,e=this.a;if(B)b.set(e.subarray(32768,b.length));else{c=0;for(d=b.length;c<d;++c)b[c]=e[c+32768]}this.p.push(b);this.u+=b.length;if(B)e.set(e.subarray(a,a+32768));else for(c=0;32768>c;++c)e[c]=e[a+c];this.b=32768;return e};
T.prototype.U=function(b){var a,c=this.input.length/this.c+1|0,d,e,f,g=this.input,k=this.a;b&&("number"===typeof b.F&&(c=b.F),"number"===typeof b.P&&(c+=b.P));2>c?(d=(g.length-this.c)/this.C[2],f=258*(d/2)|0,e=f<k.length?k.length+f:k.length<<1):e=k.length*c;B?(a=new Uint8Array(e),a.set(k)):a=k;return this.a=a};
T.prototype.B=function(){var b=0,a=this.a,c=this.p,d,e=new (B?Uint8Array:Array)(this.u+(this.b-32768)),f,g,k,h;if(0===c.length)return B?this.a.subarray(32768,this.b):this.a.slice(32768,this.b);f=0;for(g=c.length;f<g;++f){d=c[f];k=0;for(h=d.length;k<h;++k)e[b++]=d[k]}f=32768;for(g=this.b;f<g;++f)e[b++]=a[f];this.p=[];return this.buffer=e};
T.prototype.R=function(){var b,a=this.b;B?this.M?(b=new Uint8Array(a),b.set(this.a.subarray(0,a))):b=this.a.subarray(0,a):(this.a.length>a&&(this.a.length=a),b=this.a);return this.buffer=b};function sb(b){this.input=b;this.c=0;this.t=[];this.D=!1}sb.prototype.X=function(){this.D||this.h();return this.t.slice()};
sb.prototype.h=function(){for(var b=this.input.length;this.c<b;){var a=new P,c=t,d=t,e=t,f=t,g=t,k=t,h=t,l=t,s=t,p=this.input,m=this.c;a.G=p[m++];a.H=p[m++];(31!==a.G||139!==a.H)&&q(Error("invalid file signature:"+a.G+","+a.H));a.z=p[m++];switch(a.z){case 8:break;default:q(Error("unknown compression method: "+a.z))}a.n=p[m++];l=p[m++]|p[m++]<<8|p[m++]<<16|p[m++]<<24;a.Z=new Date(1E3*l);a.fa=p[m++];a.ea=p[m++];0<(a.n&4)&&(a.aa=p[m++]|p[m++]<<8,m+=a.aa);if(0<(a.n&Ea)){h=[];for(k=0;0<(g=p[m++]);)h[k++]=
String.fromCharCode(g);a.name=h.join("")}if(0<(a.n&Fa)){h=[];for(k=0;0<(g=p[m++]);)h[k++]=String.fromCharCode(g);a.A=h.join("")}0<(a.n&Ga)&&(a.S=ja(p,0,m)&65535,a.S!==(p[m++]|p[m++]<<8)&&q(Error("invalid header crc16")));c=p[p.length-4]|p[p.length-3]<<8|p[p.length-2]<<16|p[p.length-1]<<24;p.length-m-4-4<512*c&&(f=c);d=new T(p,{index:m,bufferSize:f});a.data=e=d.h();m=d.c;a.ca=s=(p[m++]|p[m++]<<8|p[m++]<<16|p[m++]<<24)>>>0;ja(e,t,t)!==s&&q(Error("invalid CRC-32 checksum: 0x"+ja(e,t,t).toString(16)+
" / 0x"+s.toString(16)));a.da=c=(p[m++]|p[m++]<<8|p[m++]<<16|p[m++]<<24)>>>0;(e.length&4294967295)!==c&&q(Error("invalid input size: "+(e.length&4294967295)+" / "+c));this.t.push(a);this.c=m}this.D=u;var n=this.t,r,v,x=0,Q=0,y;r=0;for(v=n.length;r<v;++r)Q+=n[r].data.length;if(B){y=new Uint8Array(Q);for(r=0;r<v;++r)y.set(n[r].data,x),x+=n[r].data.length}else{y=[];for(r=0;r<v;++r)y[r]=n[r].data;y=Array.prototype.concat.apply([],y)}return y};A("Zlib.Gunzip",sb);A("Zlib.Gunzip.prototype.decompress",sb.prototype.h);A("Zlib.Gunzip.prototype.getMembers",sb.prototype.X);function tb(b){if("string"===typeof b){var a=b.split(""),c,d;c=0;for(d=a.length;c<d;c++)a[c]=(a[c].charCodeAt(0)&255)>>>0;b=a}for(var e=1,f=0,g=b.length,k,h=0;0<g;){k=1024<g?1024:g;g-=k;do e+=b[h++],f+=e;while(--k);e%=65521;f%=65521}return(f<<16|e)>>>0};function ub(b,a){var c,d;this.input=b;this.c=0;if(a||!(a={}))a.index&&(this.c=a.index),a.verify&&(this.$=a.verify);c=b[this.c++];d=b[this.c++];switch(c&15){case vb:this.method=vb;break;default:q(Error("unsupported compression method"))}0!==((c<<8)+d)%31&&q(Error("invalid fcheck flag:"+((c<<8)+d)%31));d&32&&q(Error("fdict flag is not supported"));this.L=new T(b,{index:this.c,bufferSize:a.bufferSize,bufferType:a.bufferType,resize:a.resize})}
ub.prototype.h=function(){var b=this.input,a,c;a=this.L.h();this.c=this.L.c;this.$&&(c=(b[this.c++]<<24|b[this.c++]<<16|b[this.c++]<<8|b[this.c++])>>>0,c!==tb(a)&&q(Error("invalid adler-32 checksum")));return a};var vb=8;function wb(b,a){this.input=b;this.a=new (B?Uint8Array:Array)(32768);this.k=W.o;var c={},d;if((a||!(a={}))&&"number"===typeof a.compressionType)this.k=a.compressionType;for(d in a)c[d]=a[d];c.outputBuffer=this.a;this.K=new na(this.input,c)}var W=ra;
wb.prototype.g=function(){var b,a,c,d,e,f,g,k=0;g=this.a;b=vb;switch(b){case vb:a=Math.LOG2E*Math.log(32768)-8;break;default:q(Error("invalid compression method"))}c=a<<4|b;g[k++]=c;switch(b){case vb:switch(this.k){case W.NONE:e=0;break;case W.v:e=1;break;case W.o:e=2;break;default:q(Error("unsupported compression type"))}break;default:q(Error("invalid compression method"))}d=e<<6|0;g[k++]=d|31-(256*c+d)%31;f=tb(this.input);this.K.b=k;g=this.K.g();k=g.length;B&&(g=new Uint8Array(g.buffer),g.length<=
k+4&&(this.a=new Uint8Array(g.length+4),this.a.set(g),g=this.a),g=g.subarray(0,k+4));g[k++]=f>>24&255;g[k++]=f>>16&255;g[k++]=f>>8&255;g[k++]=f&255;return g};function xb(b,a){var c,d,e,f;if(Object.keys)c=Object.keys(a);else for(d in c=[],e=0,a)c[e++]=d;e=0;for(f=c.length;e<f;++e)d=c[e],A(b+"."+d,a[d])};A("Zlib.Inflate",ub);A("Zlib.Inflate.prototype.decompress",ub.prototype.h);xb("Zlib.Inflate.BufferType",{ADAPTIVE:Ya.N,BLOCK:Ya.O});A("Zlib.Deflate",wb);A("Zlib.Deflate.compress",function(b,a){return(new wb(b,a)).g()});A("Zlib.Deflate.prototype.compress",wb.prototype.g);xb("Zlib.Deflate.CompressionType",{NONE:W.NONE,FIXED:W.v,DYNAMIC:W.o});}).call(this);

/**
 * Created by Sven Kluge on 27.06.2016.
 */

if(x3dom.glTF == null)
    x3dom.glTF = {};

x3dom.glTF.glTFLoader = function(response, meshOnly)
{
    this.meshOnly = meshOnly;

    this.header = this.readHeader(response);

    if (this.header.sceneLength > 0) {
        this.scene = this.readScene(response, this.header);
        this.body = this.readBody(response, this.header);
    }

    this._mesh = {};
};

x3dom.glTF.glTFLoader.prototype.getScene = function(shape,shaderProgram, gl, sceneName)
{
    this.reset(shape,gl);

    if(sceneName == null)
    {
        sceneName = this.scene["scene"];

    }
    var scene = this.scene.scenes[sceneName];

    this.updateScene(shape, shaderProgram, gl, scene);
};

x3dom.glTF.glTFLoader.prototype.getMesh = function(shape,shaderProgram, gl, meshName)
{
    this.reset(shape,gl);

    var mesh;
    if(meshName == null)
    {
        mesh = Object.keys(this.scene.meshes)[0];
    }else
    {
        for(var key in this.scene.meshes){
            if(this.scene.meshes.hasOwnProperty(key)
                && key == meshName)
            {
                mesh = this.scene.meshes[key];
                break;
            }
        }
    }
    this.updateMesh(shape, shaderProgram, gl, mesh);
};

x3dom.glTF.glTFLoader.prototype.reset = function(shape, gl)
{
    this._mesh._numCoords = 0;
    this._mesh._numFaces = 0;

    shape._webgl.externalGeometry = -1;

    if(this.loaded.bufferViews==null)
        this.loaded.bufferViews = this.loadBufferViews(shape, gl);
};

x3dom.glTF.glTFLoader.prototype.updateScene = function(shape, shaderProgram, gl, scene)
{
    var nodes = scene["nodes"];

    for(var i = 0; i<nodes.length;++i)
    {
        var nodeID = nodes[i];
        this.traverseNode(shape, shaderProgram, gl, this.scene.nodes[nodeID]);
    }
};

x3dom.glTF.glTFLoader.prototype.traverseNode = function(shape, shaderProgram, gl, node)
{
    var children = node["children"];
    if(children!=null)
        for(var i = 0; i<children.length;++i)
        {
            var childID = children[i];
            this.traverseNode(shape, shaderProgram, gl, this.scene.nodes[childID]);
        }

    var meshes = node["meshes"];
    if(meshes != null && meshes.length > 0)
        for (var i = 0; i < meshes.length; ++i) {
            var meshID = meshes[i];
            if (this.loaded.meshes[meshID] == null) {
                this.updateMesh(shape, shaderProgram, gl, this.scene.meshes[meshID]);
                this.loaded.meshes[meshID] = 1;
            }
        }
};

x3dom.glTF.glTFLoader.prototype.updateMesh = function(shape, shaderProgram, gl, mesh)
{
    var primitives = mesh["primitives"];
    for(var i = 0; i<primitives.length; ++i){
        this.loadglTFMesh(shape, shaderProgram, gl, primitives[i]);
    }
};

x3dom.glTF.glTFLoader.prototype.loadPrimitive =  function(shape, shaderProgram, gl, primitive)
{
    var INDEX_BUFFER_IDX    = 0;
    var POSITION_BUFFER_IDX = 1;
    var NORMAL_BUFFER_IDX   = 2;
    var TEXCOORD_BUFFER_IDX = 3;
    var COLOR_BUFFER_IDX    = 4;

    var x3domTypeID, x3domShortTypeID;

    var meshIdx = this.loaded.meshCount;
    var bufferOffset = meshIdx * 6;
    shape._webgl.primType[meshIdx] = primitive["mode"];

    var indexed = (primitive.indices != null && primitive.indices != "");

    if(indexed == true){
        var indicesAccessor = this.scene.accessors[primitive.indices];

        shape._webgl.indexOffset[meshIdx] = indicesAccessor["byteOffset"];
        shape._webgl.drawCount[meshIdx]   = indicesAccessor["count"];

        shape._webgl.buffers[INDEX_BUFFER_IDX + bufferOffset] =
            this.loaded.bufferViews[indicesAccessor["bufferView"]];

        //TODO: add support for LINES and POINTS
        this._mesh._numFaces += indicesAccessor["count"] / 3;
    }

    var attributes = primitive["attributes"];

    for (var attributeID in attributes)
    {
        var accessorName = attributes[attributeID];
        var accessor = this.scene.accessors[accessorName];

        //the current renderer does not support generic vertex attributes, so simply look for useable cases
        switch (attributeID)
        {
            case "POSITION":
                x3domTypeID      = "coord";
                x3domShortTypeID = "Pos";
                shape._webgl.buffers[POSITION_BUFFER_IDX + bufferOffset] =
                    this.loaded.bufferViews[accessor["bufferView"]];
                //for non-indexed rendering, we assume that all attributes have the same count
                if (indexed == false)
                {
                    shape._webgl.drawCount[meshIdx] = accessor["count"];
                    //TODO: add support for LINES and POINTS
                    this._mesh._numFaces += accessor["count"] / 3;
                }
                this._mesh._numCoords += accessor["count"];
                break;

            case "NORMAL":
                x3domTypeID      = "normal";
                x3domShortTypeID = "Norm";
                shape._webgl.buffers[NORMAL_BUFFER_IDX + bufferOffset] =
                    this.loaded.bufferViews[accessor["bufferView"]];
                break;

            case "TEXCOORD_0":
                x3domTypeID      = "texCoord";
                x3domShortTypeID = "Tex";
                shape._webgl.buffers[TEXCOORD_BUFFER_IDX + bufferOffset] =
                    this.loaded.bufferViews[accessor["bufferView"]];
                break;

            case "COLOR":
                x3domTypeID      = "color";
                x3domShortTypeID = "Col";
                shape._webgl.buffers[COLOR_BUFFER_IDX + bufferOffset] =
                    this.loaded.bufferViews[accessor["bufferView"]];
                break;
        }

        if(x3domTypeID != null){
            shape["_" + x3domTypeID + "StrideOffset"][meshIdx] = [];

            shape["_" + x3domTypeID + "StrideOffset"][meshIdx][0] = accessor["byteStride"];
            shape["_" + x3domTypeID + "StrideOffset"][meshIdx][1] = accessor["byteOffset"];
            shape._webgl[x3domTypeID + "Type"]           = accessor["componentType"];

            this._mesh["_num" + x3domShortTypeID + "Components"] = this.getNumComponentsForType(accessor["type"]);
        }
    }

    this.loaded.meshCount += 1;

    shape._dirty.shader = true;
    shape._nameSpace.doc.needRender = true;
    x3dom.BinaryContainerLoader.checkError(gl);
};

x3dom.glTF.glTFLoader.prototype.loadglTFMesh =  function(shape, shaderProgram, gl, primitive)
{
    "use strict";

    var mesh = new x3dom.glTF.glTFMesh();

    mesh.primitiveType = primitive["mode"];

    var indexed = (primitive.indices != null && primitive.indices != "");

    if(indexed == true){
        var indicesAccessor = this.scene.accessors[primitive.indices];

        mesh.buffers[glTF_BUFFER_IDX.INDEX] = {};
        mesh.buffers[glTF_BUFFER_IDX.INDEX].offset = indicesAccessor["byteOffset"];
        mesh.buffers[glTF_BUFFER_IDX.INDEX].type =  indicesAccessor["componentType"];
        mesh.buffers[glTF_BUFFER_IDX.INDEX].idx = this.loaded.bufferViews[indicesAccessor["bufferView"]];

        mesh.drawCount = indicesAccessor["count"];
        this._mesh._numFaces += indicesAccessor["count"] / 3;
    }

    var attributes = primitive["attributes"];

    for (var attributeID in attributes)
    {
        var accessorName = attributes[attributeID];
        var accessor = this.scene.accessors[accessorName];

        var idx = null;

        //the current renderer does not support generic vertex attributes, so simply look for useable cases
        switch (attributeID)
        {
            case "POSITION":
                idx = glTF_BUFFER_IDX.POSITION;

                //for non-indexed rendering, we assume that all attributes have the same count
                if (indexed == false)
                {
                    mesh.drawCount = accessor["count"];
                    this._mesh._numFaces += indicesAccessor["count"] / 3;
                }
                this._mesh.numCoords += accessor["count"];
                break;

            case "NORMAL":
                idx = glTF_BUFFER_IDX.NORMAL;
                break;

            case "TEXCOORD_0":
                idx = glTF_BUFFER_IDX.TEXCOORD;
                break;

            case "COLOR":
                idx = glTF_BUFFER_IDX.COLOR;
                break;
        }

        if(idx != null){
            mesh.buffers[idx] = {};
            mesh.buffers[idx].idx = this.loaded.bufferViews[accessor["bufferView"]];
            mesh.buffers[idx].offset = accessor["byteOffset"];
            mesh.buffers[idx].stride = accessor["byteStride"];

            mesh.buffers[idx].type = accessor["componentType"];
            mesh.buffers[idx].numComponents = this.getNumComponentsForType(accessor["type"]);
        }
    }

    this.loaded.meshCount += 1;

    shape._dirty.shader = true;
    shape._nameSpace.doc.needRender = true;
    x3dom.BinaryContainerLoader.checkError(gl);

    if(primitive.material != null && !this.meshOnly)
        mesh.material = this.loadMaterial(gl, this.scene.materials[primitive.material]);

    if(shape.meshes == null)
        shape.meshes = [];
    shape.meshes.push(mesh);
};

x3dom.glTF.glTFLoader.prototype.loadBufferViews = function(shape, gl)
{
    var buffers = {};

    var bufferViews = this.scene.bufferViews;
    for(var bufferId in bufferViews)
    {
        if(!bufferViews.hasOwnProperty(bufferId)) continue;

        var bufferView = bufferViews[bufferId];

        // do not use Buffer for Skin or animation data
        if(bufferView.target == null && bufferView.target != gl.ARRAY_BUFFER && bufferView.target != gl.ELEMENT_ARRAY_BUFFER)
            continue;

        if(bufferView.target == gl.ELEMENT_ARRAY_BUFFER)
            shape._webgl.externalGeometry = 1;

        var data = new Uint8Array(this.body.buffer,
            this.header.bodyOffset + bufferView["byteOffset"],
            bufferView["byteLength"]);

        var newBuffer = gl.createBuffer();
        gl.bindBuffer(bufferView["target"], newBuffer);

        //upload all chunk data to GPU
        gl.bufferData(bufferView["target"], data, gl.STATIC_DRAW);

        buffers[bufferId] = newBuffer;
    }



    return buffers;
};

x3dom.glTF.glTFLoader.prototype.readHeader = function(response)
{
    var header = {};
    var magicBytes = new Uint8Array(response, 0, 4);
    var versionBytes = new Uint32Array(response, 4, 1);
    var lengthBytes = new Uint32Array(response, 8, 1);
    var sceneLengthBytes = new Uint32Array(response, 12, 1);
    var sceneFormatBytes = new Uint32Array(response, 16, 1);

    header.magic = new TextDecoder("ascii").decode(magicBytes);
    if(versionBytes[0] == 1)
        header.version = "Version 1";

    header.length = lengthBytes[0];
    header.sceneLength = sceneLengthBytes[0];

    if(sceneFormatBytes[0] == 0)
        header.sceneFormat = "JSON";


    header.bodyOffset = header.sceneLength + 20;

    return header;
};

x3dom.glTF.glTFLoader.prototype.readScene = function(response,header)
{
    var sceneBytes = new Uint8Array(response, 20, header.sceneLength);

    var json = JSON.parse(new TextDecoder("utf-8").decode(sceneBytes));

    return json;
};

x3dom.glTF.glTFLoader.prototype.readBody = function(response, header)
{
    var offset = header.sceneLength + 20;
    var body = new Uint8Array(response, offset, header.length-offset);

    return body;
};

x3dom.glTF.glTFLoader.prototype.getNumComponentsForType = function(type)
{
    switch (type)
    {
        case "SCALAR": return 1;
        case "VEC2":   return 2;
        case "VEC3":   return 3;
        case "VEC4":   return 4;
        default:       return 0;
    }
};

x3dom.glTF.glTFLoader.prototype.loadImage = function(imageNodeName, mimeType)
{
    if(this.loaded.images == null)
        this.loaded.images = {};

    if(this.loaded.images[imageNodeName]!=null)
        return this.loaded.images[imageNodeName];

    var imageNode = this.scene.images[imageNodeName];
    if(imageNode.extensions!=null && imageNode.extensions.KHR_binary_glTF != null)
    {
        var ext = imageNode.extensions.KHR_binary_glTF;
        var bufferView = this.scene.bufferViews[ext.bufferView];
        var uint8Array = new Uint8Array(this.body.buffer, this.header.bodyOffset + bufferView.byteOffset, bufferView.byteLength);

        var blob = new Blob([uint8Array], {
            type : ext.mimeType
        });
        var blobUrl = window.URL.createObjectURL(blob);

        var image = new Image();
        image.src = blobUrl;

        this.loaded.images[imageNodeName] = image;

        return image;
    }

    return null;
};

x3dom.glTF.glTFLoader.prototype.loadTexture = function(gl, textureNode)
{
    var format = textureNode.format;
    var internalFormat = textureNode.internalFormat;
    var sampler = {};

    var samplerNode = this.scene.samplers[textureNode.sampler];
    if(samplerNode!=null)
    {
        for(var key in samplerNode){
            if(samplerNode.hasOwnProperty(key))
                sampler[key] = samplerNode[key];
        }
    }

    var image = this.loadImage(textureNode.source);
    var target = textureNode.target;
    var type = textureNode.type;

    var glTFTexture = new x3dom.glTF.glTFTexture(gl, format, internalFormat, sampler, target, type, image);

    return glTFTexture;
};

x3dom.glTF.glTFLoader.prototype.loadMaterial = function(gl, materialNode)
{
    if(materialNode.extensions != null && materialNode.extensions.KHR_materials_common != null)
    {
        materialNode = materialNode.extensions.KHR_materials_common;

        var material = new x3dom.glTF.glTFKHRMaterialCommons();

        material.technique = glTF_KHR_MATERIAL_COMMON_TECHNIQUE[materialNode.technique];
        material.doubleSided = materialNode.doubleSided;

        for(var key in materialNode.values)
            if(materialNode.values.hasOwnProperty(key))
            {
                var value = materialNode.values[key];
                if(typeof value === 'string')
                {
                    var textureNode = this.scene.textures[value];
                    material[key+"Tex"] = this.loadTexture(gl, textureNode);
                }
                else
                {
                    material[key] = value;
                }
            }

        return material;
    }else
    {
        var technique = this.scene.techniques[materialNode.technique];
        var program = this.loadShaderProgram(gl, technique.program);

        var material = new x3dom.glTF.glTFMaterial(technique);
        material.program = program;

        for(var key in materialNode.values)
            if(materialNode.values.hasOwnProperty(key))
            {
                var value = materialNode.values[key];
                if(typeof value === 'string')
                {
                    var textureNode = this.scene.textures[value];
                    material.textures[key] = this.loadTexture(gl, textureNode);
                }
                else
                {
                    material.values[key] = value;
                }
            }

        return material;
    }

    return new x3dom.glTF.glTFKHRMaterialCommons();
};

x3dom.glTF.glTFLoader.prototype.loadShaderProgram = function(gl, shaderProgramName)
{
    if(this.loaded.programs == null)
        this.loaded.programs = {};

    if(this.loaded.programs[shaderProgramName] != null)
        return this.loaded.programs[shaderProgramName];

    var shaderProgramNode = this.scene.programs[shaderProgramName];

    var vertexShaderNode = this.scene.shaders[shaderProgramNode.vertexShader];
    var vertexShaderSrc = this._loadShaderSource(vertexShaderNode);

    var fragmentShaderNode = this.scene.shaders[shaderProgramNode.fragmentShader];
    var fragmentShaderSrc = this._loadShaderSource(fragmentShaderNode);

    var program = gl.createProgram();

    var vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, vertexShaderSrc);
    gl.compileShader(vertexShader);

    if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
        x3dom.debug.logError("[glTF binary] VertexShader " + gl.getShaderInfoLog(vertexShader));
    }

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, fragmentShaderSrc);
    gl.compileShader(fragmentShader);

    if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
        x3dom.debug.logError("[glTF binary] FragmentShader " + gl.getShaderInfoLog(fragmentShader));
    }

    gl.attachShader(program, vertexShader);
    gl.attachShader(program, fragmentShader);

    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(program, 0, "position");

    gl.linkProgram(program);

    var program = x3dom.Utils.wrapProgram(gl, program);

    this.loaded.programs[shaderProgramName] = program;

    return program;
};

x3dom.glTF.glTFLoader.prototype._loadShaderSource = function(shaderNode)
{
    var bufferView = this.scene.bufferViews[shaderNode.extensions.KHR_binary_glTF.bufferView];

    var shaderBytes = new Uint8Array(this.body.buffer, this.header.bodyOffset+bufferView.byteOffset, bufferView.byteLength);
    var src = new TextDecoder("ascii").decode(shaderBytes);
    return src;
};

/**
 * Created by skluge on 27.06.2016.
 */
if(x3dom.glTF == null)
    x3dom.glTF = {};

glTF_BUFFER_IDX =
{
    INDEX : 0,
    POSITION : 1,
    NORMAL : 2,
    TEXCOORD : 3,
    COLOR : 4
};

glTF_KHR_MATERIAL_COMMON_TECHNIQUE =
{
    BLINN : 0,
    PHONG : 1,
    LAMBERT : 2,
    CONSTANT : 3
};

x3dom.glTF.glTFMesh = function()
{
    this.indexOffset = 0;
    this.drawCount = 0;

    this.numFaces = 0;
    this.primitiveType = 0;

    this.numCoords = 0;

    this.buffers = {};

    this.material = null;
};

x3dom.glTF.glTFMesh.prototype.bindVertexAttribPointer = function(gl, shaderProgram)
{
    if(this.buffers[glTF_BUFFER_IDX.INDEX]){
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers[glTF_BUFFER_IDX.INDEX].idx);
    }

    if(this.material != null && this.material.attributeMapping != null)
    {
        var mapping = this.material.attributeMapping;
        this._bindVertexAttribPointer(gl, shaderProgram[mapping[glTF_BUFFER_IDX.POSITION]], this.buffers[glTF_BUFFER_IDX.POSITION]);
        this._bindVertexAttribPointer(gl, shaderProgram[mapping[glTF_BUFFER_IDX.NORMAL]], this.buffers[glTF_BUFFER_IDX.NORMAL]);
        this._bindVertexAttribPointer(gl, shaderProgram[mapping[glTF_BUFFER_IDX.TEXCOORD]], this.buffers[glTF_BUFFER_IDX.TEXCOORD]);
        this._bindVertexAttribPointer(gl, shaderProgram[mapping[glTF_BUFFER_IDX.COLOR]], this.buffers[glTF_BUFFER_IDX.COLOR]);
    }
    else
    {
        this._bindVertexAttribPointer(gl, shaderProgram.position, this.buffers[glTF_BUFFER_IDX.POSITION]);
        this._bindVertexAttribPointer(gl, shaderProgram.normal, this.buffers[glTF_BUFFER_IDX.NORMAL]);
        this._bindVertexAttribPointer(gl, shaderProgram.texcoord, this.buffers[glTF_BUFFER_IDX.TEXCOORD]);
        this._bindVertexAttribPointer(gl, shaderProgram.color, this.buffers[glTF_BUFFER_IDX.COLOR]);
    }
};

x3dom.glTF.glTFMesh.prototype.bindVertexAttribPointerPosition = function(gl, shaderProgram, useMaterial)
{
    if(this.buffers[glTF_BUFFER_IDX.INDEX]){
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.buffers[glTF_BUFFER_IDX.INDEX].idx);
    }

    if(useMaterial == true && this.material != null && this.material.attributeMapping != null)
    {
        var mapping = this.material.attributeMapping;
        this._bindVertexAttribPointer(gl, shaderProgram[mapping[glTF_BUFFER_IDX.POSITION]], this.buffers[glTF_BUFFER_IDX.POSITION]);
    }
    else
    {
        this._bindVertexAttribPointer(gl, shaderProgram.position, this.buffers[glTF_BUFFER_IDX.POSITION]);
    }
};

x3dom.glTF.glTFMesh.prototype._bindVertexAttribPointer = function(gl, shaderPosition, buffer)
{
    if(shaderPosition!=null && buffer != null)
    {
        gl.bindBuffer(gl.ARRAY_BUFFER, buffer.idx);
        gl.vertexAttribPointer(shaderPosition,
            buffer.numComponents, buffer.type, false,
            buffer.stride, buffer.offset);
        gl.enableVertexAttribArray(shaderPosition);
    }
};

x3dom.glTF.glTFMesh.prototype.render = function(gl, polyMode)
{
    if(this.material != null && !this.material.created())
        return;

    if(polyMode == null)
        polyMode = this.primitiveType;

    if(this.buffers[glTF_BUFFER_IDX.INDEX])
        gl.drawElements(polyMode, this.drawCount, this.buffers[glTF_BUFFER_IDX.INDEX].type, this.buffers[glTF_BUFFER_IDX.INDEX].offset);
    else
        gl.drawArrays(polyMode, 0, this.drawCount);
};

x3dom.glTF.glTFTexture = function(gl, format, internalFormat, sampler, target, type, image)
{
    this.format = format;
    this.internalFormat = internalFormat;
    this.sampler = sampler;
    this.target = target;
    this.type = type;
    this.image = image;

    this.created = false;

    this.create(gl);
};

x3dom.glTF.glTFTexture.prototype.isPowerOfTwo = function(x)
{
    var powerOfTwo = !(x == 0) && !(x & (x - 1));
    return powerOfTwo;
};

x3dom.glTF.glTFTexture.prototype.create = function(gl)
{
    if(this.image.complete == false)
        return;

    this.glTexture = gl.createTexture();

    gl.bindTexture(gl.TEXTURE_2D, this.glTexture);
    gl.texImage2D(gl.TEXTURE_2D, 0, this.internalFormat, this.format, this.type, this.image);

    if(this.sampler.magFilter != null)
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.sampler.magFilter);

    if(this.sampler.minFilter != null)
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.sampler.minFilter);

    //if(!this.isPowerOfTwo(this.image.width)||!this.isPowerOfTwo(this.image.height)){
        // gl.NEAREST is also allowed, instead of gl.LINEAR, as neither mipmap.
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        // Prevents s-coordinate wrapping (repeating).
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        // Prevents t-coordinate wrapping (repeating).
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
    //}

    //gl.generateMipmap(gl.TEXTURE_2D);
    gl.bindTexture(gl.TEXTURE_2D, null);

    this.created = true;
};

x3dom.glTF.glTFTexture.prototype.bind = function(gl, textureUnit, shaderProgram, uniformName)
{
    if(!this.created)
        this.create(gl);

    gl.activeTexture(gl.TEXTURE0+textureUnit);
    gl.bindTexture(gl.TEXTURE_2D, this.glTexture);
    gl.uniform1i(gl.getUniformLocation(shaderProgram, uniformName), textureUnit);
};


x3dom.glTF.glTFKHRMaterialCommons = function()
{
    this.diffuse = [0.3,0.1,0.1,1];
    this.diffuseTex = null;

    this.emission = [0.0,0.0,0.0,1];
    this.emissionTex = null;

    this.specular = [0.8,0.8,0.8,1];
    this.specularTex = null;

    this.ambient = [0,0,0,1];

    this.shininess = 2;
    this.transparency = 0.0;

    this.globalAmbient = [0,0,0,1];
    this.lightVector = [1,0,0,1];

    this.doubleSided = false;

    this.technique = glTF_KHR_MATERIAL_COMMON_TECHNIQUE.BLINN;
};

x3dom.glTF.glTFKHRMaterialCommons.prototype.created = function()
{
    if(this.diffuseTex != null && this.diffuseTex.created != true)
        return false;

    if(this.emissionTex != null && this.emissionTex.created != true)
        return false;

    if(this.specularTex != null && this.specularTex.created != true)
        return false;

    return true;
};

x3dom.glTF.glTFKHRMaterialCommons.prototype.setShader = function(gl, cache, shape, properties)
{

    properties.EMPTY_SHADER = 0;

    properties.KHR_MATERIAL_COMMONS = 1;

    if(this.diffuseTex != null)
        properties.USE_DIFFUSE_TEX = 1;
    else
        properties.USE_DIFFUSE_TEX = 0;

    if(this.emissionTex != null)
        properties.USE_SPECULAR_TEX = 1;
    else
        properties.USE_SPECULAR_TEX = 0;

    if(this.specularTex != null)
        properties.USE_EMISSION_TEX = 1;
    else
        properties.USE_EMISSION_TEX = 0;

    properties.toIdentifier();

    this.program = cache.getShaderByProperties(gl, shape, properties);

};

x3dom.glTF.glTFKHRMaterialCommons.prototype.bind = function(gl, shaderProgram)
{
    this.program.bind();


    // set all used Shader Parameter
    for(var key in shaderProgram){
        if(!shaderProgram.hasOwnProperty(key))
            continue;

        if(this.program.hasOwnProperty(key))
            this.program[key] = shaderProgram[key];
    }

    if(this.diffuseTex != null)
        this.diffuseTex.bind(gl, 0, this.program.program, "diffuseTex");
    else
        this.program.diffuse = this.diffuse;

    if(this.emissionTex != null)
        this.emissionTex.bind(gl, 0, this.program.program, "emissionTex");
    else
        this.program.emission = this.emission;

    if(this.specularTex != null)
        this.specularTex.bind(gl, 0, this.program.program, "specularTex");
    else
        this.program.specular = this.specular;

    this.program.shininess = this.shininess;
    this.program.transparency = this.transparency;
    this.program.globalAmbient = this.globalAmbient;
    this.program.lightVector = this.lightVector;

    this.program.technique = this.technique;
};

x3dom.glTF.glTFMaterial = function(technique)
{
    this.technique = technique;
    this.values = {};
    this.semanticMapping = {};
    this.attributeMapping = {};
    this.textures = {};

    for(var key in this.technique.uniforms)
    {
        if(this.technique.uniforms.hasOwnProperty(key))
        {
            var parameter = this.technique.parameters[this.technique.uniforms[key]];
            if(parameter.semantic != null)
                switch(parameter.semantic)
                {
                    case "MODELVIEW":
                        this.semanticMapping["modelViewMatrix"] = key;
                        break;
                    case "MODELVIEWINVERSETRANSPOSE":
                        this.semanticMapping["modelViewInverseTransposeMatrix"] = key;
                        break;
                    case "PROJECTION":
                        this.semanticMapping["projectionMatrix"] = key;
                        break;
                    case "MODEL":
                        this.semanticMapping["modelMatrix"] = key;
                        break;
                    case "MODELVIEWPROJECTION":
                        this.semanticMapping["modelViewProjectionMatrix"] = key;
                        break;
                    case "VIEW":
                        this.semanticMapping["viewMatrix"] = key;
                        break;
                    case "MODELVIEWINVERSE":
                        this.semanticMapping["modelViewInverseMatrix"] = key;
                        break;
                    default:
                        break;
                }
        }
    }

    for(var key in this.technique.attributes) {
        if (this.technique.attributes.hasOwnProperty(key)) {
            var parameter = this.technique.parameters[this.technique.attributes[key]];
            if (parameter.semantic != null)
                switch (parameter.semantic) {
                    case "POSITION":
                        this.attributeMapping[glTF_BUFFER_IDX.POSITION] = key;
                        break;
                    case "NORMAL":
                        this.attributeMapping[glTF_BUFFER_IDX.NORMAL] = key;
                        break;
                    case "TEXCOORD_0":
                        this.attributeMapping[glTF_BUFFER_IDX.TEXCOORD] = key;
                        break;
                    case "COLOR":
                        this.attributeMapping[glTF_BUFFER_IDX.COLOR] = key;
                        break;
                    default:
                        break;
                }
        }
    }
};

x3dom.glTF.glTFMaterial.prototype.created = function()
{
    for(var key in this.textures){
      if(!this.textures.hasOwnProperty(key)) continue;

      if(this.textures[key].created != true)
          return false;
    }

    return true;
};

x3dom.glTF.glTFMaterial.prototype.bind = function(gl, shaderParameter)
{
    if(this.program != null)
        this.program.bind();

    this.updateTransforms(shaderParameter);

    for(var key in this.technique.uniforms)
        if(this.technique.uniforms.hasOwnProperty(key))
        {
            var uniformName = this.technique.uniforms[key];
            if(this.textures[uniformName] != null){
                var texture = this.textures[uniformName];
                texture.bind(gl, 0, this.program.program, key);
            }
            else if(this.values[uniformName] != null)
                this.program[key] = this.values[uniformName];
        }
};

x3dom.glTF.glTFMaterial.prototype.updateTransforms = function(shaderParameter)
{
    if(this.program != null)
    {
        this.program.bind();

        if(this.semanticMapping["modelViewMatrix"] != null)
            this.program[this.semanticMapping["modelViewMatrix"]] = shaderParameter.modelViewMatrix;

        if(this.semanticMapping["viewMatrix"] != null)
            this.program[this.semanticMapping["viewMatrix"]] = shaderParameter.viewMatrix;

        if(this.semanticMapping["modelViewInverseTransposeMatrix"] != null) {
            var mat = shaderParameter.normalMatrix;

            var model_view_inv_gl =
                [mat[0], mat[1], mat[2],
                mat[4],mat[5],mat[6],
                mat[8],mat[9],mat[10]];

            this.program[this.semanticMapping["modelViewInverseTransposeMatrix"]] = model_view_inv_gl;
        }

        if(this.semanticMapping["modelViewInverseMatrix"] != null)
            this.program[this.semanticMapping["modelViewInverseMatrix"]] = shaderParameter.modelViewMatrixInverse;

        if(this.semanticMapping["modelViewProjectionMatrix"] != null)
            this.program[this.semanticMapping["modelViewProjectionMatrix"]] = shaderParameter.modelViewProjectionMatrix;

        if(this.semanticMapping["modelMatrix"] != null)
            this.program[this.semanticMapping["modelMatrix"]] = shaderParameter.model;

        if(this.semanticMapping["projectionMatrix"] != null)
            this.program[this.semanticMapping["projectionMatrix"]] = shaderParameter.projectionMatrix;
    }

};
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * The canvas object wraps the HTML canvas x3dom draws
 * @constructs x3dom.X3DCanvas
 * @param {Object} [x3dElement] - x3d element rendering into the canvas
 * @param {String} [canvasIdx] - id of HTML canvas
 */
x3dom.X3DCanvas = function(x3dElem, canvasIdx)
{
    var that = this;

    /**
     * The index of the HTML canvas
     * @member {String} _canvasIdx
     */
    this._canvasIdx = canvasIdx;

    /**
     * The X3D Element
     * @member {X3DElement} x3dElem
     */
    this.x3dElem = x3dElem;

    /**
     * The current canvas dimensions
     * @member {Array} _current_dim
     */
    this._current_dim = [0, 0];

    // for FPS measurements
    this.fps_t0 = new Date().getTime();
    this.lastTimeFPSWasTaken = 0;
    this.framesSinceLastTime = 0;

    this._totalTime = 0;
    this._elapsedTime = 0;

    this.doc = null;

    this.devicePixelRatio = window.devicePixelRatio || 1;

    this.lastMousePos = { x: 0, y: 0 };
    //try to determine behavior of certain DOMNodeInsertedEvent:
    //IE11 dispatches one event for each node in an inserted subtree, other browsers use a single event per subtree
    x3dom.caps.DOMNodeInsertedEvent_perSubtree = !(navigator.userAgent.indexOf('MSIE')    != -1 ||
                                                   navigator.userAgent.indexOf('Trident') != -1 );

    // allow listening for (size) changes
    x3dElem.__setAttribute = x3dElem.setAttribute;

    //adds setAttribute function for width and height to the X3D element
    x3dElem.setAttribute = function(attrName, newVal)
    {
        this.__setAttribute(attrName, newVal);

        // scale resolution so device pixel are used rather then css pixels
        newVal = parseInt(newVal) * that.devicePixelRatio;

        switch(attrName) {

            case "width":
                that.canvas.setAttribute("width", newVal);
                if (that.doc && that.doc._viewarea) {
                    that.doc._viewarea._width = parseInt(that.canvas.getAttribute("width"), 0);
                    that.doc.needRender = true;
                }
                break;

            case "height":
                that.canvas.setAttribute("height", newVal);
                if (that.doc && that.doc._viewarea) {
                    that.doc._viewarea._height = parseInt(that.canvas.getAttribute("height"), 0);
                    that.doc.needRender = true;
                }
                break;

            default:
                break;
        }
    };


    x3dom.caps.MOBILE = (navigator.appVersion.indexOf("Mobile") > -1);

    this.backend = this.x3dElem.getAttribute('backend');
	
    this.backend = ( this.backend ) ? this.backend.toLowerCase() : 'none';
        
	this.canvas = this._createHTMLCanvas( x3dElem );
	
	this.canvas.parent = this;
	
	this.gl = this._initContext( this.canvas, (this.backend.search("desktop") >= 0),
											  (this.backend.search("mobile") >= 0),
											  (this.backend.search("flashie") >= 0),
											  (this.backend.search("webgl2") >= 0));
	this.backend = 'webgl';
	
	if (this.gl == null)
	{
		this.hasRuntime = false;
		this._createInitFailedDiv(x3dElem);
		return;
	}

    x3dom.caps.BACKEND = this.backend;

    var runtimeEnabled = x3dElem.getAttribute("runtimeEnabled");

    if (runtimeEnabled !== null)
	{
        this.hasRuntime = (runtimeEnabled.toLowerCase() == "true");
    }
	else
	{
        this.hasRuntime = x3dElem.hasRuntime;
    }

	/**
	 * STATS VIEWER STUFF
	 * TODO MOVE IT TO MAIN.JS
	 */
    this.showStat = x3dElem.getAttribute("showStat");
    this.stateViewer = new x3dom.States(x3dElem);
	
	if (this.showStat !== null && this.showStat == "true")
	{
		this.stateViewer.display(true);
	}

    this.x3dElem.appendChild(this.stateViewer.viewer);

	
	
    // progress bar
    this.showProgress = x3dElem.getAttribute("showProgress");
    this.progressDiv = this._createProgressDiv();
    this.progressDiv.style.display = (this.showProgress !== null && this.showProgress == "true") ? "inline" : "none";
    this.x3dElem.appendChild(this.progressDiv);

    // touch visualization
    this.showTouchpoints = x3dElem.getAttribute("showTouchpoints");
    this.showTouchpoints = this.showTouchpoints ? this.showTouchpoints : false;

    // disable touch events
    this.disableTouch = x3dElem.getAttribute("disableTouch");
    this.disableTouch = this.disableTouch ? (this.disableTouch.toLowerCase() == "true") : false;
	
	this.disableKeys = x3dElem.getAttribute("keysEnabled");
	this.disableKeys = this.disableKeys ? (this.disableKeys.toLowerCase() == "true") : false;
	
	this.disableRightDrag = x3dElem.getAttribute("disableRightDrag");
	this.disableRightDrag = this.disableRightDrag ? (this.disableRightDrag.toLowerCase() == "true") : false;
	
	this.disableLeftDrag = x3dElem.getAttribute("disableLeftDrag");
	this.disableLeftDrag = this.disableLeftDrag ? (this.disableLeftDrag.toLowerCase() == "true") : false;
	
	this.disableMiddleDrag = x3dElem.getAttribute("disableMiddleDrag");
	this.disableMiddleDrag = this.disableMiddleDrag ? (this.disableMiddleDrag.toLowerCase() == "true") : false;

    this.bindEventListeners();
};


x3dom.X3DCanvas.prototype.bindEventListeners = function() {
    var that = this;

    this.onMouseDown = function (evt) {
        if(!this.isMulti) {
            this.focus();
            this.classList.add('x3dom-canvas-mousedown');

            switch(evt.button) {
                case 0:  this.mouse_button = 1; break;  //left
                case 1:  this.mouse_button = 4; break;  //middle
                case 2:  this.mouse_button = 2; break;  //right
                default: this.mouse_button = 0; break;
            }

            if (evt.shiftKey) { this.mouse_button = 1; }
            if (evt.ctrlKey)  { this.mouse_button = 4; }
            if (evt.altKey)   { this.mouse_button = 2; }

            var pos = this.parent.mousePosition(evt);
            this.mouse_drag_x = pos.x;
            this.mouse_drag_y = pos.y;

            this.mouse_dragging = true;

            this.parent.doc.onMousePress(that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button);
            this.parent.doc.needRender = true;
        }
    }

    this.onMouseUp = function (evt) {
        if(!this.isMulti) {
            var prev_mouse_button = this.mouse_button;
            this.classList.remove('x3dom-canvas-mousedown');

            this.mouse_button = 0;
            this.mouse_dragging = false;

            this.parent.doc.onMouseRelease(that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button, prev_mouse_button);
            this.parent.doc.needRender = true;
        }
    }

    this.onMouseOver = function (evt) {
        if(!this.isMulti) {
            this.mouse_button = 0;
            this.mouse_dragging = false;

            this.parent.doc.onMouseOver(that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button);
            this.parent.doc.needRender = true;
        }
    }

    this.onMouseAlt = function (evt) {
        if(!this.isMulti) {
            this.mouse_button = 0;
            this.mouse_dragging = false;
            this.classList.remove('x3dom-canvas-mousedown');

            this.parent.doc.onMouseOut(that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button);
            this.parent.doc.needRender = true;
        }
    }

    this.onDoubleClick = function (evt) {
        if(!this.isMulti) {
            this.mouse_button = 0;

            var pos = this.parent.mousePosition(evt);
            this.mouse_drag_x = pos.x;
            this.mouse_drag_y = pos.y;

            this.mouse_dragging = false;

            this.parent.doc.onDoubleClick(that.gl, this.mouse_drag_x, this.mouse_drag_y);
            this.parent.doc.needRender = true;
        }
    }

    this.onMouseMove = function (evt) {
        if(!this.isMulti) {

            var pos = this.parent.mousePosition(evt);
            
            if ( pos.x != that.lastMousePos.x || pos.y != that.lastMousePos.y ) {
                that.lastMousePos = pos;
                if (evt.shiftKey) { this.mouse_button = 1; }
                if (evt.ctrlKey)  { this.mouse_button = 4; }
                if (evt.altKey)   { this.mouse_button = 2; }

                this.mouse_drag_x = pos.x;
                this.mouse_drag_y = pos.y;

                if (this.mouse_dragging) {
                    
                    if ( this.mouse_button == 1 && !this.parent.disableLeftDrag ||
                         this.mouse_button == 2 && !this.parent.disableRightDrag ||
                         this.mouse_button == 4 && !this.parent.disableMiddleDrag ) 
                    {
                        this.parent.doc.onDrag(that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button);
                    }
                }
                else {
                    this.parent.doc.onMove(that.gl, this.mouse_drag_x, this.mouse_drag_y, this.mouse_button);
                }

                this.parent.doc.needRender = true;

                // deliberately different for performance reasons
                evt.preventDefault();
                evt.stopPropagation();
            }
        }
    }

    this.onDOMMouseScroll = function (evt) {
        if(!this.isMulti) {
            this.focus();

            var originalY = this.parent.mousePosition(evt).y;

            this.mouse_drag_y += 2 * evt.detail;

            this.parent.doc.onWheel(that.gl, this.mouse_drag_x, this.mouse_drag_y, originalY);
            this.parent.doc.needRender = true;

            evt.preventDefault();
            evt.stopPropagation();
        }
    }

    this.onKeyPress = function (evt) {
        if (!this.parent.disableKeys) {
            this.parent.doc.onKeyPress(evt.charCode);
        }
        this.parent.doc.needRender = true;
    }

    this.onMouseWheel = function (evt) {
        if(!this.isMulti) {
            this.focus();

            var originalY = this.parent.mousePosition(evt).y;

            this.mouse_drag_y -= 0.1 * evt.wheelDelta;

            this.parent.doc.onWheel(that.gl, this.mouse_drag_x, this.mouse_drag_y, originalY);
            this.parent.doc.needRender = true;

            evt.preventDefault();
            evt.stopPropagation();
        }
    }

    this.onKeyUp = function (evt) {
        if (!this.parent.disableKeys) {
            this.parent.doc.onKeyUp(evt.keyCode);
        }
        this.parent.doc.needRender = true;
    }

    this.onKeyDown = function (evt) {
        if (!this.parent.disableKeys) {
            this.parent.doc.onKeyDown(evt.keyCode);
        }
        this.parent.doc.needRender = true;
    }

    if (this.canvas !== null && this.gl !== null && this.hasRuntime) {
        // event handler for mouse interaction
        this.canvas.mouse_dragging = false;
        this.canvas.mouse_button = 0;
        this.canvas.mouse_drag_x = 0;
        this.canvas.mouse_drag_y = 0;

        this.canvas.isMulti = false;    // don't interfere with multi-touch

        this.canvas.oncontextmenu = function(evt) {
            evt.preventDefault();
            evt.stopPropagation();
            return false;
        };

        // TODO: handle context lost events properly
        this.canvas.addEventListener("webglcontextlost", function(event) {
            x3dom.debug.logError("WebGL context lost");
            event.preventDefault();
        }, false);

        this.canvas.addEventListener("webglcontextrestored", function(event) {
            x3dom.debug.logError("recover WebGL state and resources on context lost NYI");
            event.preventDefault();
        }, false);


        // Mouse Events
        this.canvas.addEventListener('mousedown', this.onMouseDown , false);

        this.canvas.addEventListener('mouseup', this.onMouseUp, false);

        this.canvas.addEventListener('mouseover', this.onMouseOver, false);

        this.canvas.addEventListener('mouseout', this.onMouseOut, false);

        this.canvas.addEventListener('dblclick', this.onDoubleClick, false);

        this.canvas.addEventListener('mousemove', this.onMouseMove, false);

        this.canvas.addEventListener('DOMMouseScroll', this.onDOMMouseScroll, false);

        this.canvas.addEventListener('mousewheel', this.onMouseWheel, false);


        // Key Events
        this.canvas.addEventListener('keypress', this.onKeyPress, true);

        // in webkit special keys are only handled on key-up
        this.canvas.addEventListener('keyup', this.onKeyUp, true);

        this.canvas.addEventListener('keydown', this.onKeyDown, true);


        // Multitouch Events
        var touches =
        {
            numTouches : 0,

            firstTouchTime: new Date().getTime(),
            firstTouchPoint: new x3dom.fields.SFVec2f(0,0),

            lastPos : new x3dom.fields.SFVec2f(),
            lastDrag : new x3dom.fields.SFVec2f(),

            lastMiddle : new x3dom.fields.SFVec2f(),
            lastSquareDistance : 0,
            lastAngle : 0,
            lastLayer : [],

            examineNavType: 1,

            calcAngle : function(vector)
            {
                var rotation = vector.normalize().dot(new x3dom.fields.SFVec2f(1,0));
                rotation = Math.acos(rotation);

                if(vector.y < 0)
                    rotation = Math.PI + (Math.PI - rotation);

                return rotation;
            },

            disableTouch: this.disableTouch,
            // set a marker in HTML so we can track the position of the finger visually
            visMarker: this.showTouchpoints,
            visMarkerBag: [],

            visualizeTouches: function(evt)
            {
                if (!this.visMarker)
                    return;

                var touchBag = [];
                var marker = null;

                for (var i=0; i<evt.touches.length; i++) {
                    var id = evt.touches[i].identifier || evt.touches[i].streamId;
                    if (!id) id = 0;

                    var index = this.visMarkerBag.indexOf(id);

                    if (index >= 0) {
                        marker = document.getElementById("visMarker" + id);

                        marker.style.left = (evt.touches[i].pageX) + "px";
                        marker.style.top  = (evt.touches[i].pageY) + "px";
                    }
                    else {
                        marker = document.createElement("div");

                        marker.appendChild(document.createTextNode("#" + id));
                        marker.id = "visMarker" + id;
                        marker.className = "x3dom-touch-marker";
                        document.body.appendChild(marker);

                        index = this.visMarkerBag.length;
                        this.visMarkerBag[index] = id;
                    }

                    touchBag.push(id);
                }

                for (var j=this.visMarkerBag.length-1; j>=0; j--) {
                    var oldId = this.visMarkerBag[j];

                    if (touchBag.indexOf(oldId) < 0) {
                        this.visMarkerBag.splice(j, 1);
                        marker = document.getElementById("visMarker" + oldId);
                        document.body.removeChild(marker);
                    }
                }
            }
        };

        // === Touch Start ===
        var touchStartHandler = function(evt, doc)
        {
            this.isMulti = true;
            evt.preventDefault();
            touches.visualizeTouches(evt);

            this.focus();

            if (doc == null)
                doc = this.parent.doc;

            var navi = doc._scene.getNavigationInfo();

            switch(navi.getType()) {
                case "examine":
                    touches.examineNavType = 1;
                    break;
                case "turntable":
                    touches.examineNavType = 2;
                    break;
                default:
                    touches.examineNavType = 0;
                    break;
            }

            touches.lastLayer = [];

            var i, pos;
            for(i = 0; i < evt.touches.length; i++) {
                pos = this.parent.mousePosition(evt.touches[i]);
                touches.lastLayer.push([evt.touches[i].identifier, new x3dom.fields.SFVec2f(pos.x,pos.y)]);
            }

            if(touches.numTouches < 1 && evt.touches.length == 1) {

                touches.numTouches = 1;
                touches.lastDrag = new x3dom.fields.SFVec2f(evt.touches[0].screenX, evt.touches[0].screenY);
            }
            else if(touches.numTouches < 2 && evt.touches.length >= 2) {

                touches.numTouches = 2;

                var touch0 = new x3dom.fields.SFVec2f(evt.touches[0].screenX, evt.touches[0].screenY);
                var touch1 = new x3dom.fields.SFVec2f(evt.touches[1].screenX, evt.touches[1].screenY);

                var distance = touch1.subtract(touch0);
                var middle = distance.multiply(0.5).add(touch0);
                var squareDistance = distance.dot(distance);

                touches.lastMiddle = middle;
                touches.lastSquareDistance = squareDistance;
                touches.lastAngle = touches.calcAngle(distance);

                touches.lastPos = this.parent.mousePosition(evt.touches[0]);
            }

            // update scene bbox
            doc._scene.updateVolume();

            if (touches.examineNavType == 1) {
                for(i = 0; i < evt.touches.length; i++) {
                    pos = this.parent.mousePosition(evt.touches[i]);
                    doc.onPick(that.gl, pos.x, pos.y);
                    doc._viewarea.prepareEvents(pos.x, pos.y, 1, "onmousedown");
                    doc._viewarea._pickingInfo.lastClickObj = doc._viewarea._pickingInfo.pickObj;
                }
            }
            else if (evt.touches.length) {
                pos = this.parent.mousePosition(evt.touches[0]);
                doc.onMousePress(that.gl, pos.x, pos.y, 1);     // 1 means left mouse button
            }

            doc.needRender = true;

        };

        // === Touch Move ===
        var touchMoveHandler = function(evt, doc)
        {
            evt.preventDefault();
            touches.visualizeTouches(evt);

            if (doc == null)
                doc = this.parent.doc;

            var pos = null;
            var rotMatrix = null;

            var touch0, touch1, distance, middle, squareDistance, deltaMiddle, deltaZoom, deltaMove;

            if (touches.examineNavType == 1) {
                /*
                 if (doc._scene._vf.doPickPass && doc._scene._vf.pickMode.toLowerCase() !== "box") {
                 for(var i = 0; i < evt.touches.length; i++) {
                 pos = this.parent.mousePosition(evt.touches[i]);
                 doc.onPick(that.gl, pos.x, pos.y);

                 doc._viewarea.handleMoveEvt(pos.x, pos.y, 1);
                 }
                 }
                 */

                // one finger: x/y rotation
                if(evt.touches.length == 1) {
                    var currentDrag = new x3dom.fields.SFVec2f(evt.touches[0].screenX, evt.touches[0].screenY);

                    var deltaDrag = currentDrag.subtract(touches.lastDrag);
                    touches.lastDrag = currentDrag;

                    var mx = x3dom.fields.SFMatrix4f.rotationY(deltaDrag.x / 100);
                    var my = x3dom.fields.SFMatrix4f.rotationX(deltaDrag.y / 100);
                    rotMatrix = mx.mult(my);

                    doc.onMoveView(that.gl, evt, touches, null, rotMatrix);
                }
                // two fingers: scale, translation, rotation around view (z) axis
                else if(evt.touches.length >= 2) {
                    touch0 = new x3dom.fields.SFVec2f(evt.touches[0].screenX, evt.touches[0].screenY);
                    touch1 = new x3dom.fields.SFVec2f(evt.touches[1].screenX, evt.touches[1].screenY);

                    distance = touch1.subtract(touch0);
                    middle = distance.multiply(0.5).add(touch0);
                    squareDistance = distance.dot(distance);

                    deltaMiddle = middle.subtract(touches.lastMiddle);
                    deltaZoom = squareDistance - touches.lastSquareDistance;

                    deltaMove = new x3dom.fields.SFVec3f(
                                deltaMiddle.x / screen.width, -deltaMiddle.y / screen.height,
                                deltaZoom / (screen.width * screen.height * 0.2));

                    var rotation = touches.calcAngle(distance);
                    var angleDelta = touches.lastAngle - rotation;
                    touches.lastAngle = rotation;

                    rotMatrix = x3dom.fields.SFMatrix4f.rotationZ(angleDelta);

                    touches.lastMiddle = middle;
                    touches.lastSquareDistance = squareDistance;

                    doc.onMoveView(that.gl, evt, touches, deltaMove, rotMatrix);
                }
            }
            else if (evt.touches.length) {
                if (touches.examineNavType == 2 && evt.touches.length >= 2) {
                    touch0 = new x3dom.fields.SFVec2f(evt.touches[0].screenX, evt.touches[0].screenY);
                    touch1 = new x3dom.fields.SFVec2f(evt.touches[1].screenX, evt.touches[1].screenY);

                    distance = touch1.subtract(touch0);
                    squareDistance = distance.dot(distance);
                    deltaZoom = (squareDistance - touches.lastSquareDistance) / (0.1 * (screen.width + screen.height));

                    touches.lastPos.y += deltaZoom;
                    touches.lastSquareDistance = squareDistance;

                    doc.onDrag(that.gl, touches.lastPos.x, touches.lastPos.y, 2);
                }
                else {
                    pos = this.parent.mousePosition(evt.touches[0]);

                    doc.onDrag(that.gl, pos.x, pos.y, 1);
                }
            }

            doc.needRender = true;
        };

        // === Touch end ===
        var touchEndHandler = function(evt, doc)
        {
            this.isMulti = false;
            evt.preventDefault();
            touches.visualizeTouches(evt);

            if (doc == null)
                doc = this.parent.doc;

            doc._viewarea._isMoving = false;

            // reinit first finger for rotation
            if (touches.numTouches == 2 && evt.touches.length == 1)
                touches.lastDrag = new x3dom.fields.SFVec2f(evt.touches[0].screenX, evt.touches[0].screenY);

            var dblClick = false;

            if (evt.touches.length < 2) {
                if (touches.numTouches == 1)
                    dblClick = true;
                touches.numTouches = evt.touches.length;
            }

            if (touches.examineNavType == 1) {
                for(var i = 0; i < touches.lastLayer.length; i++) {
                    var pos = touches.lastLayer[i][1];

                    doc.onPick(that.gl, pos.x, pos.y);

                    if (doc._scene._vf.pickMode.toLowerCase() !== "box") {
                        doc._viewarea.prepareEvents(pos.x, pos.y, 1, "onmouseup");
                        doc._viewarea._pickingInfo.lastClickObj = doc._viewarea._pickingInfo.pickObj;

                        // click means that mousedown _and_ mouseup were detected on same element
                        if (doc._viewarea._pickingInfo.pickObj &&
                            doc._viewarea._pickingInfo.pickObj ===
                                doc._viewarea._pickingInfo.lastClickObj) {

                            doc._viewarea.prepareEvents(pos.x, pos.y, 1, "onclick");
                        }
                    }
                    else {
                        var line = doc._viewarea.calcViewRay(pos.x, pos.y);
                        var isect = doc._scene.doIntersect(line);
                        var obj = line.hitObject;

                        if (isect && obj) {
                            doc._viewarea._pick.setValues(line.hitPoint);
                            doc._viewarea.checkEvents(obj, pos.x, pos.y, 1, "onclick");

                            x3dom.debug.logInfo("Hit '" + obj._xmlNode.localName + "/ " +
                                obj._DEF + "' at pos " + doc._viewarea._pick);
                        }
                    }
                }

                if (dblClick) {
                    var now = new Date().getTime();
                    var dist = touches.firstTouchPoint.subtract(touches.lastDrag).length();

                    if (dist < 18 && now - touches.firstTouchTime < 180)
                        doc.onDoubleClick(that.gl, 0, 0);

                    touches.firstTouchTime = now;
                    touches.firstTouchPoint = touches.lastDrag;
                }
            }
            else if (touches.lastLayer.length) {
                pos = touches.lastLayer[0][1];
                doc.onMouseRelease(that.gl, pos.x, pos.y, 0, 1);
            }

            doc.needRender = true;
        };

        if (!this.disableTouch)
        {
            // w3c / apple touch events (in Chrome via chrome://flags)
            this.canvas.addEventListener('touchstart',    touchStartHandler, true);
            this.canvas.addEventListener('touchmove',     touchMoveHandler,  true);
            this.canvas.addEventListener('touchend',      touchEndHandler,   true);
        }
    }
}


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

/**
 * Creates the WebGL context and returns it
 * @returns {WebGLContext} gl
 * @param {HTMLCanvas} canvas
 * @param {Boolean} forbidMobileShaders - no mobile shaders allowed
 * @param {Boolean} forceMobileShaders - force mobile shaders
 * @param {Boolean} forceFlashForIE - force flash backend for internet explorer
 * @param {Boolean} tryWebGL2 - try to retrieve a WebGL2 context
 */
x3dom.X3DCanvas.prototype._initContext = function(canvas, forbidMobileShaders, forceMobileShaders, tryWebGL2)
{
    x3dom.debug.logInfo("Initializing X3DCanvas for [" + canvas.id + "]");
    var gl = x3dom.gfx_webgl(canvas, forbidMobileShaders, forceMobileShaders, tryWebGL2, this.x3dElem);

    if (!gl)
    {
        x3dom.debug.logError("No 3D context found...");
        this.x3dElem.removeChild(canvas);
        return null;
    }
    else
    {
        var webglVersion = parseFloat(x3dom.caps.VERSION.match(/\d+\.\d+/)[0]);
        if (webglVersion < 1.0) {
			x3dom.debug.logError("WebGL version " + x3dom.caps.VERSION +
				" lacks important WebGL/GLSL features needed for shadows, special vertex attribute types, etc.!");
        }
    }

    return gl;
};

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

/**
 * Creates a param node and adds it to the target node's children
 * @param {String} node - the target node
 * @param {String} name - the name for the parameter
 * @param {String} value - the value for the parameter
 */
x3dom.X3DCanvas.prototype.appendParam = function(node, name, value) {
    var param = document.createElement('param');
    param.setAttribute('name', name);
    param.setAttribute('value', value);
    node.appendChild( param );
};

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

/**
 * Creates a div to inform the user that the initialization failed
 * @param {String} x3dElem - the X3D element
 */
x3dom.X3DCanvas.prototype._createInitFailedDiv = function(x3dElem) {
    var div = document.createElement('div');
    div.setAttribute("id", "x3dom-create-init-failed");
    div.style.width = x3dElem.getAttribute("width");
    div.style.height = x3dElem.getAttribute("height");
    div.style.backgroundColor = "#C00";
    div.style.color = "#FFF";
    div.style.fontSize = "20px";
    div.style.fontWidth = "bold";
    div.style.padding = "10px 10px 10px 10px";
    div.style.display = "inline-block";
    div.style.fontFamily = "Helvetica";
    div.style.textAlign = "center";

    div.appendChild(document.createTextNode('Your Browser does not support X3DOM'));
    div.appendChild(document.createElement('br'));
    div.appendChild(document.createTextNode('Read more about Browser support on:'));
    div.appendChild(document.createElement('br'));

    var link = document.createElement('a');
    link.setAttribute('href', 'http://www.x3dom.org/?page_id=9');
    link.appendChild( document.createTextNode('X3DOM | Browser Support'));
    div.appendChild(link);

    // check if "altImg" is specified on x3d element and if so use it as background
    var altImg = x3dElem.getAttribute("altImg") || null;
    if (altImg) {
        var altImgObj = new Image();
        altImgObj.src = altImg;
        div.style.backgroundImage = "url("+altImg+")";
        div.style.backgroundRepeat = "no-repeat";
        div.style.backgroundPosition = "50% 50%";
    }

    x3dElem.appendChild(div);

    x3dom.debug.logError("Your Browser does not support X3DOM!");
};

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

/**
 * Creates the HTML canvas used as render target
 * @returns {HTMLCanvas} - the created canvas
 * @param {HTMLNode} x3dElem - the X3D root node
 */
x3dom.X3DCanvas.prototype._createHTMLCanvas = function(x3dElem)
{
    x3dom.debug.logInfo("Creating canvas for (X)3D element...");
    var canvas = document.createElement('canvas');
    canvas.setAttribute("class", "x3dom-canvas");

    // check if user wants to style the X3D element
    var userStyle = x3dElem.getAttribute("style");
    if (userStyle) {
        x3dom.debug.logInfo("Inline X3D styles detected");
    }

    // check if user wants to attach events to the X3D element
    var evtArr = [
        "onmousedown",
        "onmousemove",
        "onmouseout",
        "onmouseover",
        "onmouseup",
        "onclick",
        "ondblclick",
        "onkeydown",
        "onkeypress",
        "onkeyup",

        // w3c touch: http://www.w3.org/TR/2011/WD-touch-events-20110505/
        "ontouchstart",
        "ontouchmove",
        "ontouchend",
        "ontouchcancel",
        "ontouchleave",
        "ontouchenter",

        // drag and drop, requires 'draggable' source property set true (usually of an img)
        "ondragstart",
        "ondrop",
        "ondragover"
    ];

    // TODO; handle attribute event handlers dynamically during runtime
    //this step is necessary because of some weird behavior in some browsers:
    //we need a canvas element on startup to make every callback (e.g., 'onmousemove') work,
    //which was previously set for the canvas' outer elements
    for (var i=0; i < evtArr.length; i++)
    {
        var evtName = evtArr[i];
        var userEvt = x3dElem.getAttribute(evtName);
        if (userEvt) {
            x3dom.debug.logInfo(evtName +", "+ userEvt);

            canvas.setAttribute(evtName, userEvt);

            //remove the event attribute from the X3D element to prevent duplicate callback invocation
            x3dElem.removeAttribute(evtName);
        }
    }

    var userProp = x3dElem.getAttribute("draggable");
    if (userProp) {
        x3dom.debug.logInfo("draggable=" + userProp);
        canvas.setAttribute("draggable", userProp);
    }

    // workaround since one cannot find out which handlers are registered
    if (!x3dElem.__addEventListener && !x3dElem.__removeEventListener)
    {
        x3dElem.__addEventListener = x3dElem.addEventListener;
        x3dElem.__removeEventListener = x3dElem.removeEventListener;

        // helpers to propagate the element's listeners
        x3dElem.addEventListener = function(type, func, phase) {
            var j, found = false;
            for (j=0; j < evtArr.length && !found; j++) {
                if (evtArr[j] === type) {
                    found = true;
                }
            }

            if (found) {
                x3dom.debug.logInfo('addEventListener for div.on' + type);
                canvas.addEventListener(type, func, phase);
            } else {
                x3dom.debug.logInfo('addEventListener for X3D.on' + type);
                this.__addEventListener(type, func, phase);
            }
        };

        x3dElem.removeEventListener = function(type, func, phase) {
            var j, found = false;
            for (j=0; j<evtArr.length && !found; j++) {
                if (evtArr[j] === type) {
                    found = true;
                }
            }

            if (found) {
                x3dom.debug.logInfo('removeEventListener for div.on' + type);
                canvas.removeEventListener(type, func, phase);
            } else {
                x3dom.debug.logInfo('removeEventListener for X3D.on' + type);
                this.__removeEventListener(type, func, phase);
            }
        };
    }

    //add element-specific (global) events for the X3D tag
    if (x3dElem.hasAttribute("ondownloadsfinished"))
    {
        x3dElem.addEventListener("downloadsfinished",
            function()
            {
                var eventObject = {
                    target: x3dElem,
                    type: "downloadsfinished"
                };

                var funcStr = x3dElem.getAttribute("ondownloadsfinished");
                var func = new Function('event', funcStr);
                func.call(x3dElem, eventObject);
            },
            true);
    }

    x3dElem.appendChild(canvas);

    // If the X3D element has an id attribute, append "_canvas"
    // to it and and use that as the id for the canvas
    var id = x3dElem.getAttribute("id");
    if (id !== null) {
        canvas.id = "x3dom-" + id + "-canvas";
    } else {
        // If the X3D element does not have an id... do what?
        // For now check the date for creating a (hopefully) unique id
        var index = new Date().getTime();
        canvas.id = "x3dom-" + index + "-canvas";
    }

    // Apply the width and height of the X3D element to the canvas
    var w, h;

    if ((w = x3dElem.getAttribute("width")) !== null) {
        //Attention: pbuffer dim is _not_ derived from style attribs!
        if (w.indexOf("%") >= 0) {
            x3dom.debug.logWarning("The width attribute is to be specified in pixels not in percent.");
        }
        canvas.style.width = w;
        canvas.setAttribute("width", w);
    }

    if ((h = x3dElem.getAttribute("height")) !== null) {
        //Attention: pbuffer dim is _not_ derived from style attribs!
        if (h.indexOf("%") >= 0) {
            x3dom.debug.logWarning("The height attribute is to be specified in pixels not in percent.");
        }
        canvas.style.height = h;
        canvas.setAttribute("height", h);
    }

    // http://snook.ca/archives/accessibility_and_usability/elements_focusable_with_tabindex
    canvas.setAttribute("tabindex", "0");
    // canvas.focus(); ???why - it is necessary - makes touch events break???

    return canvas;
};

/**
 * Watches for a resize of the canvas and sets the current dimensions
 */
x3dom.X3DCanvas.prototype._watchForResize = function() {

    var new_dim = [
        parseInt(x3dom.getStyle(this.canvas, "width")),
        parseInt(x3dom.getStyle(this.canvas, "height"))
    ];

    if ((this._current_dim[0] != new_dim[0]) || (this._current_dim[1] != new_dim[1])) {
        this._current_dim = new_dim;
        this.x3dElem.setAttribute("width", new_dim[0]+"px");
        this.x3dElem.setAttribute("height", new_dim[1]+"px");
    }
};

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

/**
 * Creates the div for progression visualization
 */
x3dom.X3DCanvas.prototype._createProgressDiv = function() {
    var progressDiv = document.createElement('div');
    progressDiv.setAttribute("class", "x3dom-progress");

    var _text = document.createElement('strong');
    _text.appendChild(document.createTextNode('Loading...'));
    progressDiv.appendChild(_text);

    var _inner = document.createElement('span');
    _inner.setAttribute('style', "width: 25%;");
    _inner.appendChild(document.createTextNode(' '));  // this needs to be a protected whitespace
    progressDiv.appendChild(_inner);

    progressDiv.oncontextmenu = progressDiv.onmousedown = function(evt) {
        evt.preventDefault();
        evt.stopPropagation();
        return false;
    };
    return progressDiv;
};

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

/** Helper that converts a point from node coordinates to page coordinates
 FIXME: does NOT work when x3dom.css is not included so that x3d element is not floating
 */
x3dom.X3DCanvas.prototype.mousePosition = function( evt )
{
    var rect = evt.target.getBoundingClientRect();

    var offsetX = Math.round( evt.clientX - rect.left ) * this.devicePixelRatio;
    var offsetY = Math.round( evt.clientY - rect.top  ) * this.devicePixelRatio;

    return new x3dom.fields.SFVec2f( offsetX, offsetY );
};

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

/** Is called in the main loop after every frame
 */
x3dom.X3DCanvas.prototype.tick = function(timestamp)
{
    var that = this;

    this._elapsedTime = (this._totalTime) ? timestamp - this._totalTime : 0;

    this._totalTime = timestamp;

    var runtime = this.x3dElem.runtime;
    var d = new Date().getTime();
    var diff = d - this.lastTimeFPSWasTaken;

    var fps = 1000.0 / (d - this.fps_t0);
    this.fps_t0 = d;

    // update routes and stuff
    this.doc.advanceTime(d / 1000.0);
    var animD = new Date().getTime() - d;

    if (this.doc.needRender) {
        // calc average frames per second
        if (diff >= 1000) {
            runtime.fps = this.framesSinceLastTime / (diff / 1000.0);
            runtime.addMeasurement('FPS', runtime.fps);

            this.framesSinceLastTime = 0;
            this.lastTimeFPSWasTaken = d;
        }
        this.framesSinceLastTime++;

        runtime.addMeasurement('ANIM', animD);

        if (runtime.isReady == false) {
            runtime.ready();
            runtime.isReady = true;
        }

        runtime.enterFrame( {"total": this._totalTime, "elapsed": this._elapsedTime} );

		// picking might require another pass
		this.doc.needRender = false;
		this.doc.render(this.gl);

		if (!this.doc._scene._vf.doPickPass)
		{
			runtime.removeMeasurement('PICKING');
		}
                

        runtime.exitFrame( {"total": this._totalTime, "elapsed": this._elapsedTime} );
    }

    if (this.progressDiv) {
        if (this.doc.downloadCount > 0) {
            runtime.addInfo("#LOADS:", this.doc.downloadCount);
        } else {
            runtime.removeInfo("#LOADS:");
        }

        if (this.doc.properties.getProperty("showProgress") !== 'false') {
            if (this.progressDiv) {
                this.progressDiv.childNodes[0].textContent = 'Loading: ' + (+this.doc.downloadCount);
                if (this.doc.downloadCount > 0) {
                    this.progressDiv.style.display = 'inline';
                } else {
                    this.progressDiv.style.display = 'none';
                }
            }
        } else {
            this.progressDiv.style.display = 'none';
        }
    }

    //fire downloadsfinished event, if necessary
    if (this.doc.downloadCount == 0 && this.doc.previousDownloadCount > 0)
    {
        var evt;
        if (document.createEvent) {
            evt = document.createEvent("Events");
            evt.initEvent("downloadsfinished", true, true);
            that.x3dElem.dispatchEvent(evt);
        } else if (document.createEventObject) {
            evt = document.createEventObject();
            // http://stackoverflow.com/questions/1874866/how-to-fire-onload-event-on-document-in-ie
            that.x3dElem.fireEvent("ondownloadsfinished", evt);
        }
    }

    this.doc.previousDownloadCount = this.doc.downloadCount;
};

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

/** Loads the given @p uri.
 * @param uri can be a uri or an X3D node
 * @param sceneElemPos
 * @param settings properties
 */
x3dom.X3DCanvas.prototype.load = function(uri, sceneElemPos, settings) {
    this.doc = new x3dom.X3DDocument(this.canvas, this.gl, settings);
    var x3dCanvas = this;

    this.doc.onload = function () {
        //x3dom.debug.logInfo("loaded '" + uri + "'");

        if (x3dCanvas.hasRuntime) {

			// requestAnimationFrame https://cvs.khronos.org/svn/repos/registry/trunk/public/webgl/sdk/demos/common/webgl-utils.js
			(function mainloop(timestamp){
                if (x3dCanvas.doc && x3dCanvas.x3dElem.runtime) {
                    x3dCanvas._watchForResize();
                    x3dCanvas.tick(timestamp);
                    window.requestAnimFrame(mainloop, x3dCanvas);
                }
		    })();

        } else {
            x3dCanvas.tick();
        }
    };

    this.x3dElem.render = function() {
        if (x3dCanvas.hasRuntime) {
            x3dCanvas.doc.needRender = true;
        } else {
            x3dCanvas.doc.render(x3dCanvas.gl);
        }
    };

    this.x3dElem.context = x3dCanvas.gl.ctx3d;

    this.doc.onerror = function () {
        alert('Failed to load X3D document');
    };

    this.doc.load(uri, sceneElemPos);
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


/**
 * Class: x3dom.runtime
 *
 * Runtime proxy object to get and set runtime parameters. This object
 * is attached to each X3D element and can be used in the following manner:
 *
 * > var e = document.getElementById('the_x3delement');
 * > e.runtime.showAll();
 * > e.runtime.resetView();
 * > ...
 */

// Global runtime
/**
 * Namespace container for Runtime module
 * @namespace x3dom.runtime
 */
x3dom.runtime = {};

/** c'tor */
x3dom.Runtime = function(doc, canvas) {
    this.doc = doc;
    this.canvas = canvas;
    
    this.config = {};
    this.isReady = false;
    
    this.fps = 0;
      
    this.states = { measurements: [], infos: [] };
};


x3dom.Runtime.prototype.addMeasurement = function (title, value) {
    this.states.measurements[title] = value;
};

x3dom.Runtime.prototype.removeMeasurement = function (title) {
    if (this.states.measurements[title]) {
        delete this.states.measurements[title];
    }
};

x3dom.Runtime.prototype.addInfo = function (title, value) {
    this.states.infos[title] = value;
};

x3dom.Runtime.prototype.removeInfo = function (title) {
    delete this.states.infos[title];
};


x3dom.Runtime.prototype.initialize = function(doc, canvas) {
    this.doc = doc;
    this.canvas = canvas;

    // place to hold configuration data, i.e. flash backend path, etc.
    // format and structure needs to be decided.
    this.config = {};
    this.isReady = false;
    
    this.fps = 0;
};


/**
 * APIFunction: noBackendFound
 *
 * This method is called once the system initialized and is not ready to 
 * render the first time because there is no backend found. By default this
 * method noop. You can however override it with your own implementation.
 *
 * > x3dom.runtime.noBackendFound = function() {
 * >    alert("Dingel Dingel Ding Dong...");
 * > }
 *
 * It is important to create this override before the document onLoad event has 
 * fired. Therefore putting it directly under the inclusion of x3dom.js is the 
 * preferred way to ensure overloading of this function.
 */
x3dom.Runtime.prototype.noBackendFound = function() {
    x3dom.debug.logInfo('No backend found. Unable to render.');
};

/**
 * APIFunction: ready
 *
 * This method is called once the system initialized and is ready to render
 * the first time. It is therefore possible to execute custom
 * action by overriding this method in your code:
 *
 * > x3dom.runtime.ready = function() {
 * >    alert("About to render something the first time");
 * > }
 *
 * It is important to create this override before the document onLoad event has fired.
 * Therefore putting it directly under the inclusion of x3dom.js is the preferred
 * way to ensure overloading of this function.
 *
 * Parameters:
 * 		element - The x3d element this handler is acting upon
 */
x3dom.Runtime.prototype.ready = function() {
    x3dom.debug.logInfo('System ready.');
};

/**
 * APIFunction: enterFrame
 *
 * This method is called just before the next frame is
 * rendered. It is therefore possible to execute custom
 * action by overriding this method in your code:
 *
 * > var element = document.getElementById('my_element');
 * > element.runtime.enterFrame = function() {
 *     alert('hello custom enter frame');
 * };
 *
 * If you have more than one X3D element in your HTML
 * During initialization, just after ready() executed and before the very first frame
 * is rendered, only the global override of this method works. If you need to execute
 * code before the first frame renders, it is therefore best to use the ready()
 * function instead.
 *
 * Parameters:
 * 		element - The x3d element this handler is acting upon
 */
x3dom.Runtime.prototype.enterFrame = function() {
    //x3dom.debug.logInfo('Render frame imminent');
    // to be overwritten by user
};

/**
 * APIFunction: exitFrame
 *
 * This method is called just after the current frame was
 * rendered. It is therefore possible to execute custom
 * action by overriding this method in your code:
 *
 * > var element = document.getElementById('my_element');
 * > element.runtime.exitFrame = function() {
 *     alert('hello custom exit frame');
 * };
 *
 * Parameters:
 * 		element - The x3d element this handler is acting upon
 */
x3dom.Runtime.prototype.exitFrame = function() {
    //x3dom.debug.logInfo('Render frame finished');
    // to be overwritten by user
};

/**
 * APIFunction: triggerRedraw
 *
 * triggers a redraw of the scene
 *
 */
x3dom.Runtime.prototype.triggerRedraw = function() {
    this.canvas.doc.needRender = true;
};

/**
 * APIFunction: getActiveBindable
 *
 * Returns the currently active bindable DOM element of the given type.
 * typeName must be a valid Bindable node (e.g. Viewpoint, Background, etc.).
 *
 * For example:
 *
 *   > var element, bindable;
 *   > element = document.getElementById('the_x3delement');
 *   > bindable = element.runtime.getActiveBindable('background');
 *   > bindable.setAttribute('bind', 'false');
 *
 * Parameters:
 * 		typeName - Bindable type name
 *
 * Returns:
 * 		The active DOM element
 */
x3dom.Runtime.prototype.getActiveBindable = function(typeName) {
    var stacks;
    var i, current, result;
    var type;

    stacks = this.canvas.doc._bindableBag._stacks;
    result = [];

    type = x3dom.nodeTypesLC[typeName.toLowerCase()];

    if (!type) {
        x3dom.debug.logError('No node of type "' + typeName + '" found.');
        return null;
    }

    for (i=0; i < stacks.length; i++) {
        current = stacks[i].getActive();
            if (current._xmlNode !== undefined && x3dom.isa(current, type) ) {
                result.push(current);
            }
    }
    return result[0] ? result[0]._xmlNode : null;
};

/**
 * APIFunction: nextView
 *
 * Navigates tho the next viewpoint
 *
 */
x3dom.Runtime.prototype.nextView = function() {
    var stack = this.canvas.doc._scene.getViewpoint()._stack;
    if (stack) {
        stack.switchTo('next');
    } else {
        x3dom.debug.logError('No valid ViewBindable stack.');
    }
};

/**
 * APIFunction: prevView
 *
 * Navigates tho the previous viewpoint
 *
 */
x3dom.Runtime.prototype.prevView = function() {
    var stack = this.canvas.doc._scene.getViewpoint()._stack;
    if (stack) {
        stack.switchTo('prev');
    } else {
        x3dom.debug.logError('No valid ViewBindable stack.');
    }
};

/**
 * Function: viewpoint
 *
 * Returns the current viewpoint.
 *
 * Returns:
 * 		The viewpoint
 */
x3dom.Runtime.prototype.viewpoint = function() {
    return this.canvas.doc._scene.getViewpoint();
};

/**
 * Function: viewMatrix
 *
 * Returns the current view matrix.
 *
 * Returns:
 * 		Matrix object
 */
x3dom.Runtime.prototype.viewMatrix = function() {
    return this.canvas.doc._viewarea.getViewMatrix();
};

/**
 * Function: projectionMatrix
 *
 * Returns the current projection matrix.
 *
 * Returns:
 * 		Matrix object
 */
x3dom.Runtime.prototype.projectionMatrix = function() {
    return this.canvas.doc._viewarea.getProjectionMatrix();
};

/**
 * Function: getWorldToCameraCoordinatesMatrix
 *
 * Returns the current world to camera coordinates matrix.
 *
 * Returns:
 * 		Matrix object
 */
x3dom.Runtime.prototype.getWorldToCameraCoordinatesMatrix = function() {
    return this.canvas.doc._viewarea.getWCtoCCMatrix();
};

/**
 * Function: getCameraToWorldCoordinatesMatrix
 *
 * Returns the current camera to world coordinates matrix.
 *
 * Returns:
 * 		Matrix object
 */
x3dom.Runtime.prototype.getCameraToWorldCoordinatesMatrix = function() {
    return this.canvas.doc._viewarea.getCCtoWCMatrix();
};

/**
 * Function: getViewingRay
 *
 * Returns the viewing ray for a given (x, y) position.
 *
 * Returns:
 * 		Ray object
 */
x3dom.Runtime.prototype.getViewingRay = function(x, y) {
    return this.canvas.doc._viewarea.calcViewRay(x, y);
};

/**
 * Function: shootRay
 *
 * Returns pickPosition, pickNormal, and pickObject for a given (x, y) position.
 *
 * Returns:
 * 		{pickPosition, pickNormal, pickObject}
 */
x3dom.Runtime.prototype.shootRay = function(x, y) {
    var doc = this.canvas.doc;
    var info = doc._viewarea._pickingInfo;

    doc.onPick(this.canvas.gl, x, y);

    return {
        pickPosition: info.pickObj ? info.pickPos  : null,
        pickNormal:   info.pickObj ? info.pickNorm : null,
        pickObject:   info.pickObj ? info.pickObj._xmlNode : null
    };
};

/**
 * Function: getWidth
 *
 * Returns the width of the canvas element.
 */
x3dom.Runtime.prototype.getWidth = function() {
    return this.canvas.doc._viewarea._width;
};

/**
 * Function: getHeight
 *
 * Returns the width of the canvas element.
 */
x3dom.Runtime.prototype.getHeight = function() {
    return this.canvas.doc._viewarea._height;
};

/**
 * Function: mousePosition
 *
 * Returns the 2d canvas layer position [x, y] for a given mouse event, i.e.,
 * the mouse cursor's x and y positions relative to the canvas (x3d) element.
 */
x3dom.Runtime.prototype.mousePosition = function(event) {
    var pos = this.canvas.mousePosition(event);
    
    return [pos.x, pos.y];
};

/**
 * Function: calcCanvasPos
 *
 * Returns the 2d screen position [cx, cy] for a given point [wx, wy, wz] in world coordinates.
 */
x3dom.Runtime.prototype.calcCanvasPos = function(wx, wy, wz) {
    var pnt = new x3dom.fields.SFVec3f(wx, wy, wz);
    
    var mat = this.canvas.doc._viewarea.getWCtoCCMatrix();
    var pos = mat.multFullMatrixPnt(pnt);
    
    var w = this.canvas.doc._viewarea._width;
    var h = this.canvas.doc._viewarea._height;
    
    var x = Math.round((pos.x + 1) * (w - 1) / 2);
    var y = Math.round((h - 1) * (1 - pos.y) / 2);
    
    return [x, y];
};

/**
 * Function: calcPagePos
 *
 * Returns the 2d page (returns the mouse coordinates relative to the document) position [cx, cy] 
 * for a given point [wx, wy, wz] in world coordinates.
 */
x3dom.Runtime.prototype.calcPagePos = function(wx, wy, wz) {
    var elem = this.canvas.canvas.offsetParent;

    if (!elem) {
        x3dom.debug.logError("Can't calc page pos without offsetParent.");
        return [0, 0];
    }
    
	var canvasPos = elem.getBoundingClientRect();
	var mousePos = this.calcCanvasPos(wx, wy, wz);
	
	var scrollLeft = window.pageXOffset || document.body.scrollLeft;
	var scrollTop = window.pageYOffset || document.body.scrollTop;

    var compStyle = document.defaultView.getComputedStyle(elem, null);
	
	var paddingLeft = parseFloat(compStyle.getPropertyValue('padding-left'));
	var borderLeftWidth = parseFloat(compStyle.getPropertyValue('border-left-width'));
		
	var paddingTop = parseFloat(compStyle.getPropertyValue('padding-top'));
	var borderTopWidth = parseFloat(compStyle.getPropertyValue('border-top-width'));
		
	var x = canvasPos.left + paddingLeft + borderLeftWidth + scrollLeft + mousePos[0];
    var y = canvasPos.top + paddingTop + borderTopWidth + scrollTop + mousePos[1];
    
    return [x, y];
};

/**
 * Function: calcClientPos
 *
 * Returns the 2d client (returns the mouse coordinates relative to the window) position [cx, cy] 
 * for a given point [wx, wy, wz] in world coordinates.
 */
x3dom.Runtime.prototype.calcClientPos = function(wx, wy, wz) {
    var elem = this.canvas.canvas.offsetParent;

    if (!elem) {
        x3dom.debug.logError("Can't calc client pos without offsetParent.");
        return [0, 0];
    }

    var canvasPos = elem.getBoundingClientRect();
    var mousePos = this.calcCanvasPos(wx, wy, wz);

    var compStyle = document.defaultView.getComputedStyle(elem, null);
	
	var paddingLeft = parseFloat(compStyle.getPropertyValue('padding-left'));
	var borderLeftWidth = parseFloat(compStyle.getPropertyValue('border-left-width'));
		
	var paddingTop = parseFloat(compStyle.getPropertyValue('padding-top'));
	var borderTopWidth = parseFloat(compStyle.getPropertyValue('border-top-width'));
	
	var x = canvasPos.left + paddingLeft + borderLeftWidth + mousePos[0];
    var y = canvasPos.top + paddingTop + borderTopWidth + mousePos[1];
    
    return [x, y];
};

/**
 * Function: getScreenshot
 *
 * Returns a Base64 encoded png image consisting of the current rendering.
 *
 * Returns:
 * 		The Base64 encoded PNG image string
 */
x3dom.Runtime.prototype.getScreenshot = function() {
	var url = "";
	var backend = this.canvas.backend;
	var canvas = this.canvas.canvas;

	if(canvas) {
		if(backend == "flash") {
			url = canvas.getScreenshot();
		}
		else {
			// first flip along y axis
			var canvas2d = document.createElement("canvas");
			canvas2d.width = canvas.width;
			canvas2d.height = canvas.height;

			var ctx = canvas2d.getContext("2d");
			ctx.drawImage(canvas, 0, 0, canvas.width, canvas.height);
			ctx.scale(1, -1);
			ctx.translate(0, -canvas.height);

			url = canvas2d.toDataURL();
		}
	}
	
	return url;
};

/**
 * Function: getCanvas
 *
 * Returns the internal canvas element (only valid for WebGL backend)
 *
 * Returns:
 * 		The internal canvas element
 */
x3dom.Runtime.prototype.getCanvas = function() {
    return this.canvas.canvas;
};

/**
 * Function: lightMatrix
 *
 * Returns the current light matrix.
 *
 * Returns:
 * 		The light matrix
 */
x3dom.Runtime.prototype.lightMatrix = function() {
    this.canvas.doc._viewarea.getLightMatrix();
};

/**
 * APIFunction: resetView
 *
 * Resets the view to initial.
 *
 */
x3dom.Runtime.prototype.resetView = function() {
    this.canvas.doc._viewarea.resetView();
};

/**
 * Function: lightView
 *
 * Navigates to the first light, if any.
 *
 * Returns:
 * 		True if navigation was possible, false otherwise.
 */
x3dom.Runtime.prototype.lightView = function() {
    if (this.canvas.doc._nodeBag.lights.length > 0) {
        this.canvas.doc._viewarea.animateTo(this.canvas.doc._viewarea.getLightMatrix()[0],
                                            this.canvas.doc._scene.getViewpoint());
        return true;
    } else {
        x3dom.debug.logInfo("No lights to navigate to.");
        return false;
    }
};

/**
 * APIFunction: uprightView
 *
 * Navigates to upright view
 *
 */
x3dom.Runtime.prototype.uprightView = function() {
    this.canvas.doc._viewarea.uprightView();
};

/**
 * APIFunction: fitAll
 *
 * Zooms so that all objects are fully visible. Without change the actual Viewpoint orientation
 *
 * Parameter:
 *     updateCenterOfRotation - a boolean value that specifies if the new center of rotation is set
 *
 */
x3dom.Runtime.prototype.fitAll = function(updateCenterOfRotation)
{
    if (updateCenterOfRotation === undefined) {
        updateCenterOfRotation = true;
    }

    var scene = this.canvas.doc._scene;
    scene.updateVolume();

    this.canvas.doc._viewarea.fit(scene._lastMin, scene._lastMax, updateCenterOfRotation);
};

/**
 * APIFunction: fitObject
 *
 * Zooms so that a given object are fully visible. Without change the actual Viewpoint orientation
 *
 * Parameter:
 *     updateCenterOfRotation - a boolean value that specifies if the new center of rotation is set
 *
 */
x3dom.Runtime.prototype.fitObject = function(obj, updateCenterOfRotation)
{
    if (obj && obj._x3domNode)
    {
        if (updateCenterOfRotation === undefined) {
            updateCenterOfRotation = true;
        }

        var min = x3dom.fields.SFVec3f.MAX();
        var max = x3dom.fields.SFVec3f.MIN();

        var vol = obj._x3domNode.getVolume();
        vol.getBounds(min, max);

        var mat = obj._x3domNode.getCurrentTransform();

        min = mat.multMatrixPnt(min);
        max = mat.multMatrixPnt(max);

        //TODO: revise separation of "getVolume" and "getCurrentTransform"
        //      for the transform nodes - currently, both "overlap" because
        //      both include the transform's own matrix
        //      but which is what you usually expect from both methods...
        if (x3dom.isa(obj._x3domNode, x3dom.nodeTypes.X3DTransformNode))
        {
            var invMat = obj._x3domNode._trafo.inverse();
            min = invMat.multMatrixPnt(min);
            max = invMat.multMatrixPnt(max);
        }

        this.canvas.doc._viewarea.fit(min, max, updateCenterOfRotation);
    }
};

/**
 * APIFunction: showAll
 *
 * Zooms so that all objects are fully visible.
 *
 * Parameter:
 *     axis - the axis as string: posX, negX, posY, negY, posZ, negZ
 *     updateCenterOfRotation - sets the center of rotation to the center of the scene volume
 *
 */
x3dom.Runtime.prototype.showAll = function(axis, updateCenterOfRotation) {
    this.canvas.doc._viewarea.showAll(axis, updateCenterOfRotation);
};

/**
 * APIFunction: showObject
 *
 * Zooms so that a given object is fully visible in the middle of the screen.
 *
 * Parameter:
 *     obj  - the scene-graph element on which to focus
 *     axis - the axis as string: posX, negX, posY, negY, posZ, negZ
 */
x3dom.Runtime.prototype.showObject = function(obj, axis)
{
    if (obj && obj._x3domNode)
    {
        var min = x3dom.fields.SFVec3f.MAX();
        var max = x3dom.fields.SFVec3f.MIN();

        var vol = obj._x3domNode.getVolume();
        vol.getBounds(min, max);

        var mat = obj._x3domNode.getCurrentTransform();

        min = mat.multMatrixPnt(min);
        max = mat.multMatrixPnt(max);

        var viewarea = this.canvas.doc._viewarea;

        // assume FOV_smaller as camera's fovMode
        var focalLen = (viewarea._width < viewarea._height) ?
                        viewarea._width : viewarea._height;

        var n0;    // facingDir

        switch( axis )
        {
            case "posX": n0 = new x3dom.fields.SFVec3f( 1,  0,  0); break;
            case "negX": n0 = new x3dom.fields.SFVec3f(-1,  0,  0); break;
            case "posY": n0 = new x3dom.fields.SFVec3f( 0,  1,  0); break;
            case "negY": n0 = new x3dom.fields.SFVec3f( 1, -1,  0); break;
            case "posZ": n0 = new x3dom.fields.SFVec3f( 0,  0,  1); break;
            case "negZ": n0 = new x3dom.fields.SFVec3f( 0,  0, -1); break;
        }

        var viewpoint = this.canvas.doc._scene.getViewpoint();
        var fov = viewpoint.getFieldOfView() / 2.0;
        var ta = Math.tan(fov);

        if (Math.abs(ta) > x3dom.fields.Eps) {
            focalLen /= ta;
        }

        var w = viewarea._width - 1;
        var h = viewarea._height - 1;

        var frame = 0.25;
        var minScreenPos = new x3dom.fields.SFVec2f(frame * w, frame * h);

        frame = 0.75;
        var maxScreenPos = new x3dom.fields.SFVec2f(frame * w, frame * h);

        var dia2 = max.subtract(min).multiply(0.5);     // half diameter
        var rw = dia2.length();                         // approx radius

        var pc = min.add(dia2);                         // center in wc
        var vc = maxScreenPos.subtract(minScreenPos).multiply(0.5);

        var rs = 1.5 * vc.length();
        vc = vc.add(minScreenPos);

        var dist = 1.0;
        if (rs > x3dom.fields.Eps) {
            dist = (rw / rs) * Math.sqrt(vc.x*vc.x + vc.y*vc.y + focalLen*focalLen);
        }

        n0 = mat.multMatrixVec(n0).normalize();
        n0 = n0.multiply(dist);
        var p0 = pc.add(n0);

        var qDir = x3dom.fields.Quaternion.rotateFromTo(new x3dom.fields.SFVec3f(0, 0, 1), n0);
        var R = qDir.toMatrix();

        var T = x3dom.fields.SFMatrix4f.translation(p0.negate());
        var M = x3dom.fields.SFMatrix4f.translation(p0);

        M = M.mult(R).mult(T).mult(M);
        var viewmat = M.inverse();

        viewarea.animateTo(viewmat, viewpoint);
    }
};

/**
 * APIMethod getCenter
 *
 * Returns the center of a X3DShapeNode or X3DGeometryNode.
 *
 * Parameters:
 *    domNode: the node for which its center shall be returned
 *
 *  Returns:
 *    Node center (or null if no Shape or Geometry)
 */
x3dom.Runtime.prototype.getCenter = function(domNode) {
    if (domNode && domNode._x3domNode &&
        (this.isA(domNode, "X3DShapeNode") || this.isA(domNode, "X3DGeometryNode")))
    {
        return domNode._x3domNode.getCenter();
    }
    
    return null;
};

/**
 * APIMethod getCurrentTransform
 *
 * Returns the current to world transformation of a node.
 *
 * Parameters:
 *    domNode: the node for which its transformation shall be returned
 *
 *  Returns:
 *    Transformation matrix (or null no valid node is given)
 */
x3dom.Runtime.prototype.getCurrentTransform = function(domNode) {
    if (domNode && domNode._x3domNode)
    {
        return domNode._x3domNode.getCurrentTransform();
    }
    
    return null;
};

/**
 * APIMethod getBBox
 *
 * Returns the bounding box of a node.
 *
 * Parameters:
 *    domNode: the node for which its volume shall be returned
 *
 *  Returns:
 *    The min and max positions of the node's bounding box.
 */
x3dom.Runtime.prototype.getBBox = function(domNode) {
    if (domNode && domNode._x3domNode && this.isA(domNode, "X3DBoundedObject"))
    {
        var vol = domNode._x3domNode.getVolume();

        return {
            min: x3dom.fields.SFVec3f.copy(vol.min),
            max: x3dom.fields.SFVec3f.copy(vol.max)
        }
    }

    return null;
};

/**
 * APIMethod getSceneBBox
 *
 * Returns the bounding box of the scene.
 *
 *  Returns:
 *    The min and max positions of the scene's bounding box.
 */
x3dom.Runtime.prototype.getSceneBBox = function() {
    var scene = this.canvas.doc._scene;
    scene.updateVolume();
    
    return {
        min: x3dom.fields.SFVec3f.copy(scene._lastMin),
        max: x3dom.fields.SFVec3f.copy(scene._lastMax)
    }
};

/**
 * APIFunction: debug
 *
 * Displays or hides the debug window. If parameter is omitted,
 * the current visibility status is returned.
 *
 * Parameter:
 *     show - true to show debug window, false to hide
 *
 * Returns:
 *     Current visibility status of debug window (true=visible, false=hidden)
 */
x3dom.Runtime.prototype.debug = function(show) {
    var doc = this.canvas.doc;
    if (doc._viewarea._visDbgBuf === undefined)
        doc._viewarea._visDbgBuf = (doc._x3dElem.getAttribute("showLog") === 'true');

    if (arguments.length > 0) {
        if (show === true) {
            doc._viewarea._visDbgBuf = true;
            x3dom.debug.logContainer.style.display = "block";
        }
        else {
            doc._viewarea._visDbgBuf = false;
            x3dom.debug.logContainer.style.display = "none";
        }
    }
    else {
        doc._viewarea._visDbgBuf = !doc._viewarea._visDbgBuf;
        x3dom.debug.logContainer.style.display = (doc._viewarea._visDbgBuf == true) ? "block" : "none";
    }
    doc.needRender = true;

    return doc._viewarea._visDbgBuf;
};

/**
 * APIFunction: navigationType
 *
 * Readout of the currently active navigation.
 *
 * Returns:
 *     A string representing the active navigation type
 */
x3dom.Runtime.prototype.navigationType = function() {
    return this.canvas.doc._scene.getNavigationInfo().getType();
};

/**
 * APIFunction: noNav
 *
 * Switches to noNav mode
 */
x3dom.Runtime.prototype.noNav = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("none");
};

/**
 * APIFunction: examine
 *
 * Switches to examine mode
 */
x3dom.Runtime.prototype.examine = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("examine");
};

/**
 * APIFunction: turnTable
 *
 * Switches to turnTable mode
 */
x3dom.Runtime.prototype.turnTable = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("turntable");
};

/**
 * APIFunction: fly
 *
 * Switches to fly mode
 */
x3dom.Runtime.prototype.fly = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("fly");
};

/**
 * APIFunction: freeFly
 *
 * Switches to freeFly mode
 */
x3dom.Runtime.prototype.freeFly = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("freefly");
};

/**
 * APIFunction: lookAt
 *
 * Switches to lookAt mode
 */
x3dom.Runtime.prototype.lookAt = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("lookat");
};
/**
 * APIFunction: lookAround
 *
 * Switches to lookAround mode
 */
x3dom.Runtime.prototype.lookAround = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("lookaround");
};

/**
 * APIFunction: walk
 *
 * Switches to walk mode
 */
x3dom.Runtime.prototype.walk = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("walk");
};

/**
 * APIFunction: game
 *
 * Switches to game mode
 */
x3dom.Runtime.prototype.game = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("game");
};

/**
 * APIFunction: helicopter
 *
 * Switches to helicopter mode
 */
x3dom.Runtime.prototype.helicopter = function() {
    this.canvas.doc._scene.getNavigationInfo().setType("helicopter");
};

/**
 * Function: resetExamin
 *
 * Resets all variables required by examine mode to init state
 */
 x3dom.Runtime.prototype.resetExamin = function() {
    var viewarea = this.canvas.doc._viewarea;
    viewarea._rotMat = x3dom.fields.SFMatrix4f.identity();
    viewarea._transMat = x3dom.fields.SFMatrix4f.identity();
    viewarea._movement = new x3dom.fields.SFVec3f(0, 0, 0);
    viewarea._needNavigationMatrixUpdate = true;
    this.canvas.doc.needRender = true;
 };
 
 /**
 * APIFunction: disableKeys
 *
 * Disable keys
 */
x3dom.Runtime.prototype.disableKeys = function() {
    this.canvas.disableKeys = true;
};

 /**
 * APIFunction: enableKeys
 *
 * Enable keys
 */
x3dom.Runtime.prototype.enableKeys = function() {
    this.canvas.disableKeys = false;
};

 /**
 * APIFunction: disableLeftDrag
 *
 * Disable left drag
 */
x3dom.Runtime.prototype.disableLeftDrag = function() {
    this.canvas.disableLeftDrag = true;
};

 /**
 * APIFunction: enableLeftDrag
 *
 * Enable left drag
 */
x3dom.Runtime.prototype.enableLeftDrag = function() {
    this.canvas.disableLeftDrag = false;
};

 /**
 * APIFunction: disableRightDrag
 *
 * Disable right drag
 */
x3dom.Runtime.prototype.disableRightDrag = function() {
    this.canvas.disableRightDrag = true;
};

 /**
 * APIFunction: enableRightDrag
 *
 * Enable right drag
 */
x3dom.Runtime.prototype.enableRightDrag = function() {
    this.canvas.disableRightDrag = false;
};

 /**
 * APIFunction: disableMiddleDrag
 *
 * Disable middle drag
 */
x3dom.Runtime.prototype.disableMiddleDrag = function() {
    this.canvas.disableMiddleDrag = true;
};

 /**
 * APIFunction: enableMiddleDrag
 *
 * Enable right drag
 */
x3dom.Runtime.prototype.enableMiddleDrag = function() {
    this.canvas.disableMiddleDrag = false;
};

/**
 * Function: togglePoints
 *
 * Toggles points attribute
 */
x3dom.Runtime.prototype.togglePoints = function(lines) {
    var doc = this.canvas.doc;
    var mod = (lines === true) ? 3 : 2;

    doc._viewarea._points = ++doc._viewarea._points % mod;
    doc.needRender = true;

    return doc._viewarea._points;
};

/**
 * Function: pickRect
 *
 * Returns an array of all shape elements that are within the picked rectangle 
 * defined by (x1, y1) and (x2, y2) in canvas coordinates
 */
x3dom.Runtime.prototype.pickRect = function(x1, y1, x2, y2) {
    return this.canvas.doc.onPickRect(this.canvas.gl, x1, y1, x2, y2);
};

/**
 * Function: pickMode
 *
 * Get the current pickMode intersect type
 *
 * Parameters:
 *		internal - true/false. If given return the internal representation.
 *                 Only use for debugging.
 *
 * Returns:
 * 		The current intersect type value suitable to use with changePickMode
 *      If parameter is, given, provide with internal representation.
 */
x3dom.Runtime.prototype.pickMode = function(options) {
    if (options && options.internal === true) {
        return this.canvas.doc._scene._vf.pickMode;
    }
    return this.canvas.doc._scene._vf.pickMode.toLowerCase();
};

/**
 * Function: changePickMode
 *
 * Alter the value of intersect type. Can be one of: box, idBuf, idBuf24, idBufId, color, texCoord.
 * Other values are ignored.
 *
 * Parameters:
 *		type - The new intersect type: box, idBuf, idBuf24, idBufId, color, texCoord
 *
 * Returns:
 * 		true if the type has been changed, false otherwise
 */
x3dom.Runtime.prototype.changePickMode = function(type) {
    // pick type one of : box, idBuf, idBuf24, idBufId, color, texCoord
    type = type.toLowerCase();

    switch(type) {
        case 'idbuf':    type = 'idBuf';    break;
        case 'idbuf24':  type = 'idBuf24';  break;
        case 'idbufid':  type = 'idBufId';  break;
        case 'texcoord': type = 'texCoord'; break;
        case 'color':    type = 'color';    break;
        case 'box':      type = 'box';      break;
        default:
            x3dom.debug.logWarning("Switch pickMode to "+ type + ' unknown intersect type');
            type = undefined;
    }

    if (type !== undefined) {
        this.canvas.doc._scene._vf.pickMode = type;
        x3dom.debug.logInfo("Switched pickMode to '" + type + "'.");
        return true;
    }

    return false;
};

/**
 * APIFunction: speed
 *
 *	Get the current speed value. If parameter is given the new speed value is set.
 *
 * Parameters:
 *		newSpeed - The new speed value (optional)
 *
 * Returns:
 * 		The current speed value
 */
x3dom.Runtime.prototype.speed = function(newSpeed) {
    var navi = this.canvas.doc._scene.getNavigationInfo();
    if (newSpeed) {
        navi._vf.speed = newSpeed;
        x3dom.debug.logInfo("Changed navigation speed to " + navi._vf.speed);
    }
    return navi._vf.speed;
};

/**
 * APIFunction: zoom
 *
 *	Modifies the zoom of current viewpoint with the specified zoom value.
 *
 * Parameters:
 *		zoomAmount - The zoom amount
 *
 */
x3dom.Runtime.prototype.zoom = function(zoomAmount) {
    this.canvas.doc._viewarea.zoom( zoomAmount );
    this.canvas.doc.needRender = true;
};

/**
 * APIFunction: statistics
 *
 * Get or set statistics info. If parameter is omitted, this method
 * only returns the the visibility status of the statistics info overlay.
 *
 * Parameters:
 *		mode - true or false. To enable or disable the statistics info
 *
 * Returns:
 * 		The current visibility of the statistics info (true = visible, false = invisible)
 */
x3dom.Runtime.prototype.statistics = function(mode) {
    var states = this.canvas.stateViewer;
    if (states) {
        this.canvas.doc.needRender = true;
        if (mode === true) {
            states.display(mode);
            return true;
        }
        else if (mode === false) {
            states.display(mode);
            return false;
        }
        else {
            states.display(!states.active);
            // if no parameter is given return current status (false = not visible, true = visible)
            return states.active;
        }
    }
    return false;
};

/**
 * Function: processIndicator
 *
 * Enable or disable the process indicator. If parameter is omitted, this method
 * only returns the the visibility status of the progress bar overlay.
 *
 * Parameters:
 *		mode - true or false. To enable or disable the progress indicator
 *
 * Returns:
 * 		The current visibility of the progress indicator info (true = visible, false = invisible)
 */
x3dom.Runtime.prototype.processIndicator = function(mode) {
    var processDiv = this.canvas.progressDiv;
    if (processDiv) {
        if (mode === true) {
            processDiv.style.display = 'inline';
            return true;
        }
        else if (mode === false) {
            processDiv.style.display = 'none';
            return false;
        }

        // if no parameter is given return current status (false = not visible, true = visible)
        return processDiv.style.display != 'none'
    }
    return false;
};

/** Get properties */
x3dom.Runtime.prototype.properties = function() {
    return this.canvas.doc.properties;
};

/** Get current backend name */
x3dom.Runtime.prototype.backendName = function() {
    return this.canvas.backend;
};

/** Get current framerate */
x3dom.Runtime.prototype.getFPS = function() {
    return this.fps;
};


/**
 * APIMethod isA
 *
 * Test a DOM node object against a node type string. This method
 * can be used to determine the "type" of a DOM node.
 *
 * Parameters:
 *    domNode: the node to test for
 *    nodeType: node name to test domNode against
 *
 *  Returns:
 *    True or false
 */
x3dom.Runtime.prototype.isA = function(domNode, nodeType) {
    var inherits = false;
    
    if (nodeType && domNode && domNode._x3domNode) {
        if (nodeType === "") {
            nodeType = "X3DNode";
        }
        inherits = x3dom.isa(domNode._x3domNode, 
                             x3dom.nodeTypesLC[nodeType.toLowerCase()]);
    }
    
    return inherits;
};

/**
 * APIMethod getPixelScale
 *
 * Returns the virtual scale of one pixel for the current orthographic viewpoint.
 * The returned vector contains scale values for the x and y direction. The z value is always null.
 *
 * Parameters:
 *
 *  Returns:
 *    x3dom.fields.SFVec3f or null if non orthographic view
 */
x3dom.Runtime.prototype.getPixelScale = function(){
    var vp = this.viewpoint();
    if(!x3dom.isa(vp, x3dom.nodeTypes.OrthoViewpoint)){
        x3dom.debug.logError("getPixelScale is only implemented for orthographic Viewpoints");
        return null;
    }

    var zoomLevel = vp.getZoom();
    
    var left   = zoomLevel[0];
    var bottom = zoomLevel[1];
    var right  = zoomLevel[2];
    var top    = zoomLevel[3];

    var x = right - left;
    var y = top - bottom;

    var pixelScaleX = x / this.getWidth();
    var pixelScaleY = y / this.getHeight();

    return new x3dom.fields.SFVec3f(pixelScaleX,pixelScaleY,0.0);
};

x3dom.Runtime.prototype.toggleProjection = function( perspViewID, orthoViewID )
{
    var dist;
    var factor = 2.2;
    var runtime = document.getElementById("x3d").runtime;
    var navInfo = runtime.canvas.doc._scene.getNavigationInfo();
    var speed = navInfo._vf.transitionTime;
    var persp = document.getElementById(perspViewID)._x3domNode;
    var ortho = document.getElementById(orthoViewID)._x3domNode;

    navInfo._vf.transitionTime = 0;

    ortho._bindAnimation = false;
    persp._bindAnimation = false;

    if (persp._vf.isActive) {
        ortho._viewMatrix = persp._viewMatrix;

        document.getElementById(orthoViewID).setAttribute("set_bind", "true");

        dist = persp._viewMatrix.e3().length() / factor;

        ortho.setZoom(dist);
    }
    else if (ortho._vf.isActive) {
        persp._viewMatrix = ortho._viewMatrix;

        document.getElementById(perspViewID).setAttribute("set_bind", "true");

        dist = ortho._fieldOfView[2] * factor;
        var translation = ortho._viewMatrix.e3().normalize().multiply(dist);

        persp._viewMatrix.setTranslate(translation);
    }

    navInfo._vf.transitionTime = speed;

    ortho._bindAnimation = true;
    persp._bindAnimation = true;

    return (persp._vf.isActive) ? 0 : 1;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

// holds the UserAgent feature
x3dom.userAgentFeature = {
    supportsDOMAttrModified: false
};


(function loadX3DOM() {
    "use strict";

    var onload = function() {
        var i,j;  // counters

        // Search all X3D elements in the page
        var x3ds_unfiltered = document.getElementsByTagName('X3D');
        var x3ds = [];

        // check if element already has been processed
        for (i=0; i < x3ds_unfiltered.length; i++) {
            if (x3ds_unfiltered[i].hasRuntime === undefined)
                x3ds.push(x3ds_unfiltered[i]);
        }

        // ~~ Components and params {{{ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        var params;
        var settings = new x3dom.Properties();  // stores the stuff in <param>
        var validParams = array_to_object([ 
            'showLog', 
            'showStat',
            'showProgress', 
            'PrimitiveQuality', 
            'components', 
            'loadpath', 
            'disableDoubleClick',
            'backend',
            'altImg',
            'flashrenderer',
            'swfpath',
            'runtimeEnabled',
            'keysEnabled',
            'showTouchpoints',
            'disableTouch',
            'maxActiveDownloads'
        ]);
        var components, prefix;
		var showLoggingConsole = false;

        // for each X3D element
        for (i=0; i < x3ds.length; i++) {

            // default parameters
            settings.setProperty("showLog", x3ds[i].getAttribute("showLog") || 'false');
            settings.setProperty("showStat", x3ds[i].getAttribute("showStat") || 'false');
            settings.setProperty("showProgress", x3ds[i].getAttribute("showProgress") || 'true');
            settings.setProperty("PrimitiveQuality", x3ds[i].getAttribute("PrimitiveQuality") || 'High');

            // for each param element inside the X3D element
            // add settings to properties object
            params = x3ds[i].getElementsByTagName('PARAM');
            for (j=0; j < params.length; j++) {
                if (params[j].getAttribute('name') in validParams) {
                    settings.setProperty(params[j].getAttribute('name'), params[j].getAttribute('value'));
                } else {
                    //x3dom.debug.logError("Unknown parameter: " + params[j].getAttribute('name'));
                }
            }

            // enable log
            if (settings.getProperty('showLog') === 'true') {
				showLoggingConsole = true;
            }

            if (typeof X3DOM_SECURITY_OFF != 'undefined' && X3DOM_SECURITY_OFF === true) {
                // load components from params or default to x3d attribute
                components = settings.getProperty('components', x3ds[i].getAttribute("components"));
                if (components) {
                    prefix = settings.getProperty('loadpath', x3ds[i].getAttribute("loadpath"));
                    components = components.trim().split(',');
                    for (j=0; j < components.length; j++) {
                        x3dom.loadJS(components[j] + ".js", prefix);
                    }
                }

                // src=foo.x3d adding inline node, not a good idea, but...
                if (x3ds[i].getAttribute("src")) {
                    var _scene = document.createElement("scene");
                    var _inl = document.createElement("Inline");
                    _inl.setAttribute("url", x3ds[i].getAttribute("src"));
                    _scene.appendChild(_inl);
                    x3ds[i].appendChild(_scene);
                }
            }
        }
		
		if (showLoggingConsole == true) {
			x3dom.debug.activate(true);
		} else {
			x3dom.debug.activate(false);
		}

        // Convert the collection into a simple array (is this necessary?)
        x3ds = Array.map(x3ds, function (n) {
            n.hasRuntime = true;
            return n;
        });

        if (x3dom.versionInfo !== undefined) {
            x3dom.debug.logInfo("X3DOM version " + x3dom.versionInfo.version + ", " +
                                "Revison <a href='https://github.com/x3dom/x3dom/tree/"+ x3dom.versionInfo.revision +"'>"
                                + x3dom.versionInfo.revision + "</a>, " +
                                "Date " + x3dom.versionInfo.date);
        }
        
        x3dom.debug.logInfo("Found " + x3ds.length + " X3D and nodes...");
        
        // Create a HTML canvas for every X3D scene and wrap it with
        // an X3D canvas and load the content
        var x3d_element;
        var x3dcanvas;
        var altDiv, altP, aLnk, altImg;
        var t0, t1;

        for (i=0; i < x3ds.length; i++)
        {
            x3d_element = x3ds[i];

            x3dcanvas = new x3dom.X3DCanvas(x3d_element, x3dom.canvases.length);

            x3dom.canvases.push(x3dcanvas);

            if (x3dcanvas.gl === null) {

                altDiv = document.createElement("div");
                altDiv.setAttribute("class", "x3dom-nox3d");
                altDiv.setAttribute("id", "x3dom-nox3d");

                altP = document.createElement("p");
                altP.appendChild(document.createTextNode("WebGL is not yet supported in your browser. "));
                aLnk = document.createElement("a");
                aLnk.setAttribute("href","http://www.x3dom.org/?page_id=9");
                aLnk.appendChild(document.createTextNode("Follow link for a list of supported browsers... "));
                
                altDiv.appendChild(altP);
                altDiv.appendChild(aLnk);
                
                x3dcanvas.x3dElem.appendChild(altDiv);

                // remove the stats div (it's not added when WebGL doesn't work)
                if (x3dcanvas.stateViewer) { 
                    x3d_element.removeChild(x3dcanvas.stateViewer.viewer);
                }

                continue;
            }
            
            t0 = new Date().getTime();

            x3ds[i].runtime = new x3dom.Runtime(x3ds[i], x3dcanvas);
            x3ds[i].runtime.initialize(x3ds[i], x3dcanvas);

            if (x3dom.runtime.ready) {
                x3ds[i].runtime.ready = x3dom.runtime.ready;
            }
            
            // no backend found method system wide call
            if (x3dcanvas.backend == '') {
                x3dom.runtime.noBackendFound();
            }
            
            x3dcanvas.load(x3ds[i], i, settings);

            // show or hide statistics based on param/x3d attribute settings
            if (settings.getProperty('showStat') === 'true') {
                x3ds[i].runtime.statistics(true);
            } else {
                x3ds[i].runtime.statistics(false);
            }

            if (settings.getProperty('showProgress') === 'true') {
                if (settings.getProperty('showProgress') === 'bar'){
                    x3dcanvas.progressDiv.setAttribute("class", "x3dom-progress bar");
                }
                x3ds[i].runtime.processIndicator(true);
            } else {
                x3ds[i].runtime.processIndicator(false);
            }

			t1 = new Date().getTime() - t0;
            x3dom.debug.logInfo("Time for setup and init of GL element no. " + i + ": " + t1 + " ms.");
        }
        
        var ready = (function(eventType) {
            var evt = null;

            if (document.createEvent) {
                evt = document.createEvent("Events");    
                evt.initEvent(eventType, true, true);     
                document.dispatchEvent(evt);              
            } else if (document.createEventObject) {
                evt = document.createEventObject();
                // http://stackoverflow.com/questions/1874866/how-to-fire-onload-event-on-document-in-ie
                document.body.fireEvent('on' + eventType, evt);   
            }
        })('load');
    };
    
    var onunload = function() {
        if (x3dom.canvases) {
            for (var i=0; i<x3dom.canvases.length; i++) {
                x3dom.canvases[i].doc.shutdown(x3dom.canvases[i].gl);
            }
            x3dom.canvases = [];
        }
    };
    
    /** Initializes an <x3d> root element that was added after document load. */
    x3dom.reload = function() {
        onload();
    };
    
    if (window.addEventListener)  {
        window.addEventListener('load', onload, false);
        window.addEventListener('unload', onunload, false);
        window.addEventListener('reload', onunload, false);
    } else if (window.attachEvent) {
        window.attachEvent('onload', onload);
        window.attachEvent('onunload', onunload);
        window.attachEvent('onreload', onunload);
    }

    // Initialize if we were loaded after 'DOMContentLoaded' already fired.
    // This can happen if the script was loaded by other means.
    if (document.readyState === "complete") {
        window.setTimeout( function() { onload(); }, 20 );
    }
})();

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

x3dom.Cache = function () {
    this.textures = [];
    this.shaders = [];
};

/**
 * Returns a Texture 2D
 */
x3dom.Cache.prototype.getTexture2D = function (gl, doc, url, bgnd, crossOrigin, scale, genMipMaps) {
    var textureIdentifier = url;

    if (this.textures[textureIdentifier] === undefined) {
        this.textures[textureIdentifier] = x3dom.Utils.createTexture2D(
                                           gl, doc, url, bgnd, crossOrigin, scale, genMipMaps);
    }

    return this.textures[textureIdentifier];
};

/**
 * Returns a Texture 2D
 */
x3dom.Cache.prototype.getTexture2DByDEF = function (gl, nameSpace, def) {
    var textureIdentifier = nameSpace.name + "_" + def;

    if (this.textures[textureIdentifier] === undefined) {
        this.textures[textureIdentifier] = gl.createTexture();
    }

    return this.textures[textureIdentifier];
};

/**
 * Returns a Cube Texture
 */
x3dom.Cache.prototype.getTextureCube = function (gl, doc, url, bgnd, crossOrigin, scale, genMipMaps) {
    var textureIdentifier = "";

    for (var i = 0; i < url.length; ++i) {
        textureIdentifier += url[i] + "|";
    }

    if (this.textures[textureIdentifier] === undefined) {
        this.textures[textureIdentifier] = x3dom.Utils.createTextureCube(
                                           gl, doc, url, bgnd, crossOrigin, scale, genMipMaps);
    }

    return this.textures[textureIdentifier];
};

/**
 * Returns one of the default shader programs
 */
x3dom.Cache.prototype.getShader = function (gl, shaderIdentifier) {
    var program = null;

    //Check if shader is in cache
    if (this.shaders[shaderIdentifier] === undefined) {
        //Choose shader based on identifier
        switch (shaderIdentifier) {
            case x3dom.shader.PICKING:
                program = new x3dom.shader.PickingShader(gl);
                break;
            case x3dom.shader.PICKING_24:
                program = new x3dom.shader.Picking24Shader(gl);
                break;
            case x3dom.shader.PICKING_ID:
                program = new x3dom.shader.PickingIdShader(gl);
                break;
            case x3dom.shader.PICKING_COLOR:
                program = new x3dom.shader.PickingColorShader(gl);
                break;
            case x3dom.shader.PICKING_TEXCOORD:
                program = new x3dom.shader.PickingTexcoordShader(gl);
                break;
            case x3dom.shader.FRONTGROUND_TEXTURE:
                program = new x3dom.shader.FrontgroundTextureShader(gl);
                break;
            case x3dom.shader.BACKGROUND_TEXTURE:
                program = new x3dom.shader.BackgroundTextureShader(gl);
                break;
            case x3dom.shader.BACKGROUND_SKYTEXTURE:
                program = new x3dom.shader.BackgroundSkyTextureShader(gl);
                break;
            case x3dom.shader.BACKGROUND_CUBETEXTURE:
                program = new x3dom.shader.BackgroundCubeTextureShader(gl);
                break;
            case x3dom.shader.SHADOW:
                program = new x3dom.shader.ShadowShader(gl);
                break;
            case x3dom.shader.BLUR:
                program = new x3dom.shader.BlurShader(gl);
                break;
            case x3dom.shader.DEPTH:
                //program = new x3dom.shader.DepthShader(gl);
                break;
            case x3dom.shader.NORMAL:
                program = new x3dom.shader.NormalShader(gl);
                break;
            case x3dom.shader.TEXTURE_REFINEMENT:
                program = new x3dom.shader.TextureRefinementShader(gl);
                break;
            default:
                break;
        }

        if (program)
            this.shaders[shaderIdentifier] = x3dom.Utils.wrapProgram(gl, program, shaderIdentifier);
        else
            x3dom.debug.logError("Couldn't create shader: " + shaderIdentifier);
    }

    return this.shaders[shaderIdentifier];
};

/**
 * Returns a dynamic generated shader program by viewarea and shape
 */
x3dom.Cache.prototype.getDynamicShader = function (gl, viewarea, shape) {
    //Generate Properties
    var properties = x3dom.Utils.generateProperties(viewarea, shape);

    var shaderID = properties.id;

    if (this.shaders[shaderID] === undefined) {
        var program = null;
        if (properties.CSHADER != -1) {
            program = new x3dom.shader.ComposedShader(gl, shape);
        } else {
            program = (x3dom.caps.MOBILE && !properties.CSSHADER) ?
                            new x3dom.shader.DynamicMobileShader(gl, properties) :
                            new x3dom.shader.DynamicShader(gl, properties);
        }
        this.shaders[shaderID] = x3dom.Utils.wrapProgram(gl, program, shaderID);
    }

    return this.shaders[shaderID];
};

/**
 * Returns a dynamic generated shader program by properties
 */
x3dom.Cache.prototype.getShaderByProperties = function (gl, shape, properties, pickMode, shadows) {

    //Get shaderID
    var shaderID = properties.id;

    if(pickMode !== undefined && pickMode !== null) {
        shaderID += pickMode;
    }

    if(shadows !== undefined && shadows !== null) {
        shaderID += "S";
    }

    if (this.shaders[shaderID] === undefined)
    {
        var program = null;
        
        if (pickMode !== undefined && pickMode !== null) {
            program = new x3dom.shader.DynamicShaderPicking(gl, properties, pickMode);
        }
        else if (shadows !== undefined && shadows !== null) {
            program = new x3dom.shader.DynamicShadowShader(gl, properties);
        }
        else if (properties.CSHADER != -1)
            program = new x3dom.shader.ComposedShader(gl, shape);
        else if(properties.KHR_MATERIAL_COMMONS != null && properties.KHR_MATERIAL_COMMONS != 0)
            program = new x3dom.shader.KHRMaterialCommonsShader(gl, properties);
        else if(properties.EMPTY_SHADER != null && properties.EMPTY_SHADER != 0)
            return {"shaderID": shaderID};
        else {
            program = (x3dom.caps.MOBILE && !properties.CSSHADER) ?
                        new x3dom.shader.DynamicMobileShader(gl, properties) :
                        new x3dom.shader.DynamicShader(gl, properties);
        }

        this.shaders[shaderID] = x3dom.Utils.wrapProgram(gl, program, shaderID);
    }

    return this.shaders[shaderID];
};

/**
 * Returns the dynamically created shadow rendering shader
 */
x3dom.Cache.prototype.getShadowRenderingShader = function (gl, shadowedLights) {
    var ID = "shadow";
    for (var i = 0; i < shadowedLights.length; i++) {
        if (x3dom.isa(shadowedLights[i], x3dom.nodeTypes.SpotLight))
            ID += "S";
        else if (x3dom.isa(shadowedLights[i], x3dom.nodeTypes.PointLight))
            ID += "P";
        else
            ID += "D";
    }

    if (this.shaders[ID] === undefined) {
        var program = new x3dom.shader.ShadowRenderingShader(gl, shadowedLights);
        this.shaders[ID] = x3dom.Utils.wrapProgram(gl, program, ID);
    }
    return this.shaders[ID];
};

/**
 * Release texture and shader resources
 */
x3dom.Cache.prototype.Release = function (gl) {
    for (var texture in this.textures) {
        gl.deleteTexture(this.textures[texture]);
    }
    this.textures = [];

    for (var shaderId in this.shaders) {
        var shader = this.shaders[shaderId];
        var glShaders = gl.getAttachedShaders(shader.program);
        for (var i=0; i<glShaders.length; ++i) {
            gl.detachShader(shader.program, glShaders[i]);
            gl.deleteShader(glShaders[i]);
        }
        gl.deleteProgram(shader.program)
    }
    this.shaders = [];
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


function startDashVideo(recurl, texturediv) {
    var vars = function () {
            var vars = {};
            var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function (m, key, value) {
                vars[key] = value;
            });
            return vars;
        },
        url = recurl,
        video,
        context,
        player;

    if (vars && vars.hasOwnProperty("url")) {
        url = vars.url;
    }

    video = document.querySelector(texturediv);
    context = new Dash.di.DashContext();
    player = new MediaPlayer(context);

    player.startup();

    player.attachView(video);
    player.setAutoPlay(false);

    player.attachSource(url);
}


/**
 * Texture
 */
x3dom.Texture = function (gl, doc, cache, node) {
    this.gl = gl;
    this.doc = doc;
    this.cache = cache;
    this.node = node;

    this.samplerName = "diffuseMap";
    this.type = gl.TEXTURE_2D;
    this.format = gl.RGBA;
    this.magFilter = gl.LINEAR;
    this.minFilter = gl.LINEAR;
    this.wrapS = gl.REPEAT;
    this.wrapT = gl.REPEAT;
    this.genMipMaps = false;
    this.texture = null;
    this.ready = false;

    this.dashtexture = false;

    var tex = this.node;
    var suffix = "mpd";

    this.node._x3domTexture = this;

    if (x3dom.isa(tex, x3dom.nodeTypes.MovieTexture)) {
        // for dash we are lazy and check only the first url
        if (tex._vf.url[0].indexOf(suffix, tex._vf.url[0].length - suffix.length) !== -1) {
            this.dashtexture = true;
            // we need to initially place the script for the dash player once in the document,
            // but insert this additional script only, if really needed and Dash is requested!
            var js = document.getElementById("AdditionalDashVideoScript");
            if (!js) {
                js = document.createElement("script");
                js.setAttribute("type", "text/javascript");
                js.setAttribute("src", x3dom.Texture.dashVideoScriptFile);
                js.setAttribute("id", "AdditionalDashVideoScript");
                js.onload = function() {
                    var texObj;
                    while ( (texObj = x3dom.Texture.loadDashVideos.pop()) ) {
                        x3dom.Texture.textNum++;
                        texObj.update();
                    }
                    js.ready = true;
                };
                document.getElementsByTagName('head')[0].appendChild(js);
            }
            if (js.ready === true) {
                // count dash players and add this number to the class name for future reference
                // (append in id too, for play, pause etc)
                x3dom.Texture.textNum++;
                // update can be directly called as script is already loaded
                this.update();
            }
            else {
                // push to stack and process later when script has loaded
                x3dom.Texture.loadDashVideos.push(this);
            }
        }
    }

    if (!this.dashtexture) {
        this.update();
    }
};

x3dom.Texture.dashVideoScriptFile = "dash.all.js";
x3dom.Texture.loadDashVideos = [];
x3dom.Texture.textNum = 0;
x3dom.Texture.clampFontSize = false;
x3dom.Texture.minFontQuality = 0.5;
x3dom.Texture.maxFontQuality = 10;

x3dom.Texture.prototype.update = function()
{
	if ( x3dom.isa(this.node, x3dom.nodeTypes.Text) )
	{
		this.updateText();
	}
	else
	{
		this.updateTexture();
	}
};

x3dom.Texture.prototype.setPixel = function(x, y, pixel, update)
{
    var gl  = this.gl;

    var pixels = new Uint8Array(pixel);

    gl.bindTexture(this.type, this.texture);

    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);

    gl.texSubImage2D(this.type, 0, x, y, 1, 1, this.format, gl.UNSIGNED_BYTE, pixels);

    gl.bindTexture(this.type, null);

    if(update) {
        this.doc.needRender = true;
    }
};

x3dom.Texture.prototype.updateTexture = function()
{
    var gl  = this.gl;
	var doc = this.doc;
	var tex = this.node;

	//Set sampler
	this.samplerName = tex._type;

	//Set texture type
	if ( x3dom.isa(tex, x3dom.nodeTypes.X3DEnvironmentTextureNode) ) {
		this.type = gl.TEXTURE_CUBE_MAP;
	} else {
		this.type = gl.TEXTURE_2D;
	}

	//Set texture format
	if (x3dom.isa(tex, x3dom.nodeTypes.PixelTexture)) {
		switch (tex._vf.image.comp)
		{
			case 1: this.format = gl.LUMINANCE; break;
			case 2: this.format = gl.LUMINANCE_ALPHA; break;
			case 3: this.format = gl.RGB; break;
			case 4: this.format = gl.RGBA; break;
		}
	} else {
		this.format = gl.RGBA;
	}

    //Set texture min, mag, wrapS and wrapT
    if (tex._cf.textureProperties.node !== null) {
		var texProp = tex._cf.textureProperties.node;

        this.wrapS = x3dom.Utils.boundaryModesDic(gl, texProp._vf.boundaryModeS);
        this.wrapT = x3dom.Utils.boundaryModesDic(gl, texProp._vf.boundaryModeT);

		this.minFilter = x3dom.Utils.minFilterDic(gl, texProp._vf.minificationFilter);
		this.magFilter = x3dom.Utils.magFilterDic(gl, texProp._vf.magnificationFilter);

		if (texProp._vf.generateMipMaps === true) {
			this.genMipMaps = true;

			if (this.minFilter == gl.NEAREST) {
				this.minFilter  = gl.NEAREST_MIPMAP_NEAREST;
			} else if (this.minFilter == gl.LINEAR) {
				this.minFilter  = gl.LINEAR_MIPMAP_LINEAR;
			}

            if (this.texture && (this.texture.ready || this.texture.textureCubeReady)) {
                gl.bindTexture(this.type, this.texture);
                gl.generateMipmap(this.type);
                gl.bindTexture(this.type, null);
            }
		} else {
			this.genMipMaps = false;

			if ( (this.minFilter == gl.LINEAR_MIPMAP_LINEAR) ||
				 (this.minFilter == gl.LINEAR_MIPMAP_NEAREST) ) {
				this.minFilter  = gl.LINEAR;
			} else if ( (this.minFilter == gl.NEAREST_MIPMAP_LINEAR) ||
					    (this.minFilter == gl.NEAREST_MIPMAP_NEAREST) ) {
				this.minFilter  = gl.NEAREST;
			}
		}
	} else {
		if (tex._vf.repeatS == false) {
			this.wrapS = gl.CLAMP_TO_EDGE;
		}
        else
        {
            this.wrapS = gl.REPEAT;
        }
		if (tex._vf.repeatT == false) {
			this.wrapT = gl.CLAMP_TO_EDGE;
		}
        else
        {
            this.wrapT = gl.REPEAT;
        }

        if (this.samplerName == "displacementMap" ||
            this.samplerName == "multiDiffuseAlphaMap" ||
            this.samplerName == "multiVisibilityMap" ||
            this.samplerName == "multiEmissiveAmbientMap" ||
            this.samplerName == "multiSpecularShininessMap")
        {
            this.wrapS = gl.CLAMP_TO_EDGE;
            this.wrapT = gl.CLAMP_TO_EDGE;
            this.minFilter = gl.NEAREST;
            this.magFilter = gl.NEAREST;
        }
	}

    //Looking for child texture
    var childTex = (tex._video && tex._needPerFrameUpdate === true);

	//Set texture
	if (tex._isCanvas && tex._canvas)
	{
		if (this.texture == null) {
			this.texture = gl.createTexture()
		}
        this.texture.width  = tex._canvas.width;
        this.texture.height = tex._canvas.height;
        this.texture.ready = true;

		gl.bindTexture(this.type, this.texture);
        gl.texImage2D(this.type, 0, this.format, this.format, gl.UNSIGNED_BYTE, tex._canvas);
        if (this.genMipMaps) {
            gl.generateMipmap(this.type);
        }
		gl.bindTexture(this.type, null);
	}

	else if (x3dom.isa(tex, x3dom.nodeTypes.RenderedTexture))
    {
        if (tex._webgl && tex._webgl.fbo) {
            if(tex._webgl.fbo.dtex && tex._vf.depthMap)
                this.texture = tex._webgl.fbo.dtex;
            else
                this.texture = tex._webgl.fbo.tex;
        }
        else {
            this.texture = null;
            x3dom.debug.logError("Try updating RenderedTexture without FBO initialized!");
        }
        if (this.texture) {
            this.texture.ready = true;
        }
	}
	else if (x3dom.isa(tex, x3dom.nodeTypes.PixelTexture))
	{
		if (this.texture == null) {
            if (this.node._DEF) {
                this.texture = this.cache.getTexture2DByDEF(gl, this.node._nameSpace, this.node._DEF);
            } else {
                this.texture = gl.createTexture();
            }
		}
        this.texture.width  = tex._vf.image.width;
        this.texture.height = tex._vf.image.height;
        this.texture.ready = true;

		var pixelArr = tex._vf.image.array;//.toGL();
		var pixelArrfont_size = tex._vf.image.width * tex._vf.image.height * tex._vf.image.comp;

        if (pixelArr.length < pixelArrfont_size)
        {
            pixelArr = tex._vf.image.toGL();

            while (pixelArr.length < pixelArrfont_size) {
                pixelArr.push(0);
            }
        }

		var pixels = new Uint8Array(pixelArr);

		gl.bindTexture(this.type, this.texture);
		gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
        gl.texImage2D(this.type, 0, this.format,
                      tex._vf.image.width, tex._vf.image.height, 0,
                      this.format, gl.UNSIGNED_BYTE, pixels);
        if (this.genMipMaps) {
            gl.generateMipmap(this.type);
        }
		gl.bindTexture(this.type, null);
	}
	else if (x3dom.isa(tex, x3dom.nodeTypes.MovieTexture) || childTex)
    {
        var that = this;
        var p = document.getElementsByTagName('body')[0];

        if (this.texture == null) {
            this.texture = gl.createTexture();
        }

        if (this.dashtexture) {
            var element_vid = document.createElement('div');
            element_vid.setAttribute('class', 'dash-video-player' + x3dom.Texture.textNum);
            tex._video = document.createElement('video');
            tex._video.setAttribute('preload', 'auto');
            tex._video.setAttribute('muted', 'muted');

            var scriptToRun = document.createElement('script');
            scriptToRun.setAttribute('type', 'text/javascript');
            scriptToRun.innerHTML = 'startDashVideo("' + tex._vf.url[0] +
                                    '",".dash-video-player' + x3dom.Texture.textNum + ' video")';
            element_vid.appendChild(scriptToRun);
            element_vid.appendChild(tex._video);
            p.appendChild(element_vid);
            tex._video.style.visibility = "hidden";
            tex._video.style.display = "none";
        }
        else {
            if (!childTex) {
                tex._video = document.createElement('video');
                tex._video.setAttribute('preload', 'auto');
                tex._video.setAttribute('muted', 'muted');
                p.appendChild(tex._video);
                tex._video.style.visibility = "hidden";
                tex._video.style.display = "none";
            }
            for (var i = 0; i < tex._vf.url.length; i++) {
                var videoUrl = tex._nameSpace.getURL(tex._vf.url[i]);
                x3dom.debug.logInfo('Adding video file: ' + videoUrl);
                var src = document.createElement('source');
                src.setAttribute('src', videoUrl);
                tex._video.appendChild(src);
            }
        }

		var updateMovie = function()
		{
			gl.bindTexture(that.type, that.texture);
			gl.texImage2D(that.type, 0, that.format, that.format, gl.UNSIGNED_BYTE, tex._video);
            if (that.genMipMaps) {
                gl.generateMipmap(that.type);
            }
			gl.bindTexture(that.type, null);
            that.texture.ready = true;
			doc.needRender = true;
		};

		var startVideo = function()
		{
			tex._video.play();
			tex._intervalID = setInterval(updateMovie, 16);
		};

		var videoDone = function()
		{
			clearInterval(tex._intervalID);
			if (tex._vf.loop === true)
			{
				tex._video.play();
				tex._intervalID = setInterval(updateMovie, 16);
			}
		};

		// Start listening for the canplaythrough event, so we do not
		// start playing the video until we can do so without stuttering
		tex._video.addEventListener("canplaythrough", startVideo, true);

		// Start listening for the ended event, so we can stop the
		// texture update when the video is finished playing
		tex._video.addEventListener("ended", videoDone, true);
	}
	else if (x3dom.isa(tex, x3dom.nodeTypes.X3DEnvironmentTextureNode))
	{
		this.texture = this.cache.getTextureCube(gl, doc, tex.getTexUrl(), false,
		                                         tex._vf.crossOrigin, tex._vf.scale, this.genMipMaps);
	}
	else
	{
		this.texture = this.cache.getTexture2D(gl, doc, tex._nameSpace.getURL(tex._vf.url[0]),
		                                       false, tex._vf.crossOrigin, tex._vf.scale, this.genMipMaps);
	}
};

x3dom.Texture.prototype.updateText = function()
{
	var gl = this.gl;

	this.wrapS			= gl.CLAMP_TO_EDGE;
	this.wrapT			= gl.CLAMP_TO_EDGE;
	this.type = gl.TEXTURE_2D;
	this.format = gl.RGBA;
	this.magFilter = gl.LINEAR;
	this.minFilter = gl.LINEAR;
    
	var fontStyleNode = this.node._cf.fontStyle.node; // should always exist?

    var font_family = 'serif'; // should be dealt with by default fontStyleNode?
    var font_style = 'normal';
    var font_justify = 'left';
    var font_size = 1.0;
    var font_spacing = 1.0;
    var font_horizontal = true;
    var font_language = "";
    var oversample = 2.0;
    var minor_alignment = 'FIRST';

    if ( fontStyleNode !== null )
	{
		var fonts = fontStyleNode._vf.family.toString();

		// clean attribute values and split in array
		fonts = fonts.trim().replace(/\'/g,'').replace(/\,/, ' ');
		fonts = fonts.split(" ");

		font_family = Array.map(fonts, function(s) {
			if (s == 'SANS') { return 'sans-serif'; }
			else if (s == 'SERIF') { return 'serif'; }
			else if (s == 'TYPEWRITER') { return 'monospace'; }
			else { return ''+s+''; }  //'Verdana'
		}).join(",");

		font_style = fontStyleNode._vf.style.toString().replace(/\'/g,'');
		switch (font_style.toUpperCase()) {
			case 'PLAIN': 		font_style = 'normal'; 		break;
			case 'BOLD': 		font_style = 'bold'; 		break;
			case 'ITALIC': 		font_style = 'italic'; 		break;
			case 'BOLDITALIC': 	font_style = 'italic bold'; break;
			default: 			font_style = 'normal';
		}

		var leftToRight = fontStyleNode._vf.leftToRight ? 'ltr' : 'rtl';
		var topToBottom = fontStyleNode._vf.topToBottom;

		font_justify = fontStyleNode._vf.justify[0].toString().replace(/\'/g,'');
		switch (font_justify.toUpperCase()) {
			case 'BEGIN': 	font_justify = 'left'; 		break;
			case 'END': 	font_justify = 'right'; 	break;
			case 'FIRST': 	font_justify = 'left'; 		break; // relevant only in justify[1], eg. minor alignment
			case 'MIDDLE': 	font_justify = 'center'; 	break;
			default: 		font_justify = 'left'; 		break;
		}
		
		if (fontStyleNode._vf.justify[1] === undefined) {
			minor_alignment = 'FIRST';
		}
		else {
			minor_alignment = fontStyleNode._vf.justify[1].toString().replace(/\'/g,'');
			switch (minor_alignment.toUpperCase()) {
				case 'BEGIN': 		minor_alignment = 'BEGIN'; 		break;
				case 'FIRST': 		minor_alignment = 'FIRST'; 		break;
				case 'MIDDLE': 		minor_alignment = 'MIDDLE'; 	break;
				case 'END': 		minor_alignment = 'END'; 		break;
				default: 			minor_alignment = 'FIRST'; 		break;
			}
		}
		
		font_size 		= fontStyleNode._vf.size;
		font_spacing 	= fontStyleNode._vf.spacing;
		font_horizontal = fontStyleNode._vf.horizontal; //TODO: vertical needs canvas support
		font_language 	= fontStyleNode._vf.language;
		oversample = fontStyleNode._vf.quality;
		oversample = Math.max(x3dom.Texture.minFontQuality, oversample);
		oversample = Math.min(x3dom.Texture.maxFontQuality, oversample);
	
        if (font_size < 0.1) font_size = 0.1;
        if (x3dom.Texture.clampFontSize && font_size > 2.3)
        {
            font_size = 2.3;
        }
	}

	var textX, textY;
	var paragraph = this.node._vf.string;
	var maxExtent = this.node._vf.maxExtent;
	var lengths = [];
	var text_canvas = document.createElement('canvas');
	text_canvas.dir = leftToRight;
	var x3dToPx = 42;
	var textHeight = font_size * x3dToPx; // pixel size relative to local coordinate system
	var textAlignment = font_justify;
	
	// needed to make webfonts work
	document.body.appendChild(text_canvas);
	var text_ctx = text_canvas.getContext('2d');
	
	text_ctx.font = font_style + " " + textHeight + "px " + font_family;

	var maxWidth = 0, pWidth, pLength;
	var i, j;

	// calculate maxWidth and length scaling; sanitize lengths
	for(i = 0; i < paragraph.length; i++) {

		pWidth = text_ctx.measureText( paragraph[i] ).width; 
		if ( pWidth > maxWidth ) { maxWidth = pWidth; }

		pLength = this.node._vf.length[i] | 0;
		if (maxExtent > 0 && (pLength > maxExtent || pLength == 0)) {
			pLength = maxExtent;			
		}
		lengths[i] = pLength <= 0 ? pWidth : pLength * x3dToPx;
	}
	
	var canvas_extra = 0.1 * textHeight; //single line, some fonts are higher than textHeight
	var txtW = maxWidth ;
	var txtH = textHeight * font_spacing * paragraph.length + canvas_extra ;

	textX = 0;
	textY = 0;
	
	var x_offset = 0, y_offset = 0, baseLine = 'top';
	
	//x_offset and starting X
	switch (font_justify) {
		case "center":	 
			x_offset = -txtW/2;
			textX = txtW/2;
			break;
		case "left":
			x_offset = leftToRight == 'ltr' ? 0 : -txtW;
			textX = 0;
			break;
		case "right":
			x_offset = leftToRight == 'ltr' ? -txtW : 0;
			textX = txtW;
			break;
	}

	//y_offset, baseline and first Y
	switch (minor_alignment) {
		case "MIDDLE":
			y_offset = txtH/2;
			break;
		case "BEGIN":
			y_offset = topToBottom ? 0 : txtH - canvas_extra;
			baseLine = topToBottom ? 'top' : 'bottom';
			textY = topToBottom ? 0 : textHeight; // adjust for baseline
			break;
		case "FIRST":
			//special case of BEGIN
			y_offset = topToBottom ? textHeight : txtH - canvas_extra ;
			baseLine = topToBottom ? 'alphabetic' : 'bottom';
			textY = topToBottom ? textHeight : textHeight;
			break;
		case "END":
			y_offset = topToBottom ? txtH - canvas_extra: 0;
			baseLine = topToBottom ? 'bottom' : 'top';
			textY = topToBottom ? textHeight : 0;
			break;
	}
	
	var pxToX3d = 1/42.0;
	var w = txtW * pxToX3d;
	var h = txtH * pxToX3d;
	
	x_offset *= pxToX3d;
	y_offset *= pxToX3d;
	
	text_canvas.width = txtW * oversample ;
	text_canvas.height = txtH * oversample ;
	text_canvas.dir = leftToRight;
	
	text_ctx.scale(oversample, oversample);
	
	// transparent background
	text_ctx.fillStyle = 'rgba(0,0,0,0)';
	text_ctx.fillRect(0, 0, text_ctx.canvas.width, text_ctx.canvas.height);

	// write white text with black border
	text_ctx.fillStyle = 'white';
	text_ctx.textBaseline = baseLine;

	text_ctx.font = font_style + " " + textHeight + "px " + font_family;
	text_ctx.textAlign = textAlignment;

	// create the multiline text always top down
	for (i = 0; i < paragraph.length; i++) {
		j = topToBottom ? i : paragraph.length - 1 - i;
		text_ctx.fillText(paragraph[j], textX,  textY, lengths[j]);
		textY += textHeight * font_spacing;
	}

	if ( this.texture === null )
	{
		this.texture = gl.createTexture();
	}

	gl.bindTexture(this.type, this.texture);
	gl.texImage2D(this.type, 0, this.format, this.format, gl.UNSIGNED_BYTE, text_canvas);
	gl.bindTexture(this.type, null);

	//remove canvas after Texture creation
	document.body.removeChild(text_canvas);

	this.node._mesh._positions[0] = [
		0 + x_offset, -h + y_offset, 0,
		w + x_offset, -h + y_offset, 0,
		w + x_offset,  0 + y_offset, 0,
		0 + x_offset,  0 + y_offset, 0];
	
	this.node.invalidateVolume();
	Array.forEach(this.node._parentNodes, function (node) {
    	node.setAllDirty();
    });
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


// ### X3DDocument ###
x3dom.X3DDocument = function(canvas, ctx, settings) {
    this.canvas = canvas;       // The <canvas> elem
    this.ctx = ctx;             // WebGL context object, AKA gl
    this.properties = settings; // showStat, showLog, etc.
    this.needRender = true;     // Trigger redraw if true
    this._x3dElem = null;       // Backref to <X3D> root element (set on parsing)
    this._scene = null;         // Scene root element
    this._viewarea = null;      // Viewport, handles rendering and interaction
    this.downloadCount = 0;     // Counter for objects to be loaded
    this.previousDownloadCount = 0;

    // bag for pro-active (or multi-core-like) elements
    this._nodeBag = {
        timer: [],                // TimeSensor (tick)
        lights: [],               // Light
        clipPlanes: [],           // ClipPlane
        followers: [],            // X3DFollowerNode
        trans: [],                // X3DTransformNode (for listening to CSS changes)
        renderTextures: [],       // RenderedTexture
        viewarea: [],             // Viewport (for updating camera navigation)
        affectedPointingSensors: [] // all X3DPointingDeviceSensor currently activated (i.e., used for interaction),
                                    // this list is maintained for efficient update / deactivation
    };

    this.onload = function () {};
    this.onerror = function () {};
};

x3dom.X3DDocument.prototype.load = function (uri, sceneElemPos) {
    // Load uri. Get sceneDoc, list of sub-URIs.
    // For each URI, get docs[uri] = whatever, extend list of sub-URIs.

    var uri_docs = {};
    var queued_uris = [uri];
    var doc = this;

    function next_step() {
        // TODO: detect circular inclusions
        // TODO: download in parallel where possible

        if (queued_uris.length === 0) {
            // All done
            doc._setup(uri_docs[uri], uri_docs, sceneElemPos);
            doc.onload();
            return;
        }
        var next_uri = queued_uris.shift();

        if ( x3dom.isX3DElement(next_uri) &&
            (next_uri.localName.toLowerCase() === 'x3d' || next_uri.localName.toLowerCase() === 'websg') )
        {
            // Special case, when passed an X3D node instead of a URI string
            uri_docs[next_uri] = next_uri;
            doc._x3dElem = next_uri;
            next_step();
        }
    }

    next_step();
};

x3dom.findScene = function(x3dElem) {
    var sceneElems = [];

    for (var i=0; i<x3dElem.childNodes.length; i++) {
        var sceneElem = x3dElem.childNodes[i];

        if (sceneElem && sceneElem.localName && sceneElem.localName.toLowerCase() === "scene") {
            sceneElems.push(sceneElem);
        }
    }

    if (sceneElems.length > 1) {
        x3dom.debug.logError("X3D element has more than one Scene child (has " +
                             x3dElem.childNodes.length + ").");
    }
    else {
        return sceneElems[0];
    }
    return null;
};


x3dom.X3DDocument.prototype._setup = function (sceneDoc, uriDocs, sceneElemPos) {
    var doc = this;

    function cleanNodeBag(bag, node) {
        for (var i=0, n=bag.length; i<n; i++) {
            if (bag[i] === node) {
                bag.splice(i, 1);
                break;
            }
        }
    }

    function removeX3DOMBackendGraph(domNode) {
        var children = domNode.childNodes;

        for (var i=0, n=children.length; i<n; i++) {
            removeX3DOMBackendGraph(children[i]);
        }

        if (domNode._x3domNode) {
            var node = domNode._x3domNode;
            var nameSpace = node._nameSpace;

            if (x3dom.isa(node, x3dom.nodeTypes.X3DShapeNode)) {
                if (node._cleanupGLObjects) {
                    node._cleanupGLObjects(true);
                    // TODO: more cleanups, e.g. texture/shader cache?
                }
                if (x3dom.nodeTypes.Shape.idMap.nodeID[node._objectID]) {
                    delete x3dom.nodeTypes.Shape.idMap.nodeID[node._objectID];
                }
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.TimeSensor)) {
                cleanNodeBag(doc._nodeBag.timer, node);
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.X3DLightNode)) {
                cleanNodeBag(doc._nodeBag.lights, node);
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.X3DFollowerNode)) {
                cleanNodeBag(doc._nodeBag.followers, node);
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.X3DTransformNode)) {
                cleanNodeBag(doc._nodeBag.trans, node);
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.RenderedTexture)) {
                cleanNodeBag(doc._nodeBag.renderTextures, node);
                if (node._cleanupGLObjects) {
                    node._cleanupGLObjects();
                }
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.X3DPointingDeviceSensorNode)) {
                cleanNodeBag(doc._nodeBag.affectedPointingSensors, node);
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.Texture)) {
                node.shutdown();    // general texture might have video
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.AudioClip)) {
                node.shutdown();
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.X3DBindableNode)) {
                var stack = node._stack;
                if (stack) {
                    node.bind(false);
                    cleanNodeBag(stack._bindBag, node);
                }
                // Background may have geometry
                if (node._cleanupGLObjects) {
                    node._cleanupGLObjects();
                }
            }
            else if (x3dom.isa(node, x3dom.nodeTypes.Scene)) {
                if (node._webgl) {
                    node._webgl = null;
                    // TODO; explicitly delete all gl objects
                }
            }

            //do not remove node from namespace if it was only "USE"d	
            if (nameSpace && !(domNode.getAttribute('use') || domNode.getAttribute('USE')))
            {
                nameSpace.removeNode(node._DEF);
            }
            node._xmlNode = null;

            delete domNode._x3domNode;
        }
    }

    // Test capturing DOM mutation events on the X3D subscene
    var domEventListener = {
        onAttrModified: function(e) {
            if ('_x3domNode' in e.target) {
                var attrToString = {
                    1: "MODIFICATION",
                    2: "ADDITION",
                    3: "REMOVAL"
                };
                //x3dom.debug.logInfo("MUTATION: " + e.attrName + ", " + e.type + ", attrChange=" + attrToString[e.attrChange]);
                e.target._x3domNode.updateField(e.attrName, e.newValue);
                doc.needRender = true;
            }
        },
        
        onNodeRemoved: function(e) {
            var domNode = e.target;
            if (!domNode)
                return;

            if ('_x3domNode' in domNode.parentNode && '_x3domNode' in domNode) {
                var parent = domNode.parentNode._x3domNode;
                var child = domNode._x3domNode;

                if (parent && child) {
                    parent.removeChild(child);
                    parent.nodeChanged();

                    removeX3DOMBackendGraph(domNode);

                    if (doc._viewarea && doc._viewarea._scene) {
                        doc._viewarea._scene.nodeChanged();
                        doc._viewarea._scene.updateVolume();
                        doc.needRender = true;
                    }
                }
            }
            else if (domNode.localName && domNode.localName.toUpperCase() == "ROUTE" && domNode._nodeNameSpace) {
                var fromNode = domNode._nodeNameSpace.defMap[domNode.getAttribute('fromNode')];
                var toNode = domNode._nodeNameSpace.defMap[domNode.getAttribute('toNode')];

                if (fromNode && toNode) {
                    fromNode.removeRoute(domNode.getAttribute('fromField'), toNode, domNode.getAttribute('toField'));
                }
            }
            else if (domNode.localName && domNode.localName.toUpperCase() == "X3D") {
                var runtime = domNode.runtime;

                if (runtime && runtime.canvas && runtime.canvas.doc && runtime.canvas.doc._scene) {
                    var sceneNode = runtime.canvas.doc._scene._xmlNode;

                    removeX3DOMBackendGraph(sceneNode);

                    // also clear corresponding X3DCanvas element
                    for (var i=0; i<x3dom.canvases.length; i++) {
                        if (x3dom.canvases[i] === runtime.canvas) {
                            x3dom.canvases[i].doc.shutdown(x3dom.canvases[i].gl);
                            x3dom.canvases.splice(i, 1);
                            break;
                        }
                    }

                    runtime.canvas.doc._scene = null;
                    runtime.canvas.doc._viewarea = null;
                    runtime.canvas.doc = null;
                    runtime.canvas = null;
                    runtime = null;

                    domNode.context = null;
                    domNode.runtime = null;
                }
            }
        },
        
        onNodeInserted: function(e) {
            var child = e.target;
            var parentNode = child.parentNode;
            
            // only act on x3dom nodes, ignore regular HTML
            if ('_x3domNode' in parentNode) {
				if (parentNode.tagName && parentNode.tagName.toLowerCase() == 'inline' ||
                    parentNode.tagName.toLowerCase() == 'multipart') {
                    // do nothing
				}
				else {
					var parent = parentNode._x3domNode;
					
					if (parent && parent._nameSpace && (child instanceof Element)) {

                        if (x3dom.caps.DOMNodeInsertedEvent_perSubtree)
                        {
                            removeX3DOMBackendGraph(child);    // not really necessary...
                        }

                        var newNode = parent._nameSpace.setupTree(child);

                        parent.addChild(newNode, child.getAttribute("containerField"));
                        parent.nodeChanged();

                        var grandParentNode = parentNode.parentNode;
                        if (grandParentNode && grandParentNode._x3domNode)
                            grandParentNode._x3domNode.nodeChanged();

                        if (doc._viewarea && doc._viewarea._scene) {
                            doc._viewarea._scene.nodeChanged();
                            doc._viewarea._scene.updateVolume();
                            doc.needRender = true;
                        }
					}
					else {
						x3dom.debug.logWarning("No _nameSpace in onNodeInserted");
					}
				}
            }
        }
    };

    //sceneDoc.addEventListener('DOMCharacterDataModified', domEventListener.onAttrModified, true);
    sceneDoc.addEventListener('DOMNodeRemoved', domEventListener.onNodeRemoved, true);
    sceneDoc.addEventListener('DOMNodeInserted', domEventListener.onNodeInserted, true);
    if ( (x3dom.userAgentFeature.supportsDOMAttrModified === true ) ) {
        sceneDoc.addEventListener('DOMAttrModified', domEventListener.onAttrModified, true);
    }

    // sceneDoc is the X3D element here...
    var sceneElem = x3dom.findScene(sceneDoc);

    // create and add BindableBag that holds all bindable stacks
    this._bindableBag = new x3dom.BindableBag(this);

    // create and add the NodeNameSpace
    var nameSpace = new x3dom.NodeNameSpace("scene", doc);
    
    var scene = nameSpace.setupTree(sceneElem);

    // link scene
    this._scene = scene;
    this._bindableBag.setRefNode(scene);

    // create view
    this._viewarea = new x3dom.Viewarea (this, scene);

    this._viewarea._width = this.canvas.width;
    this._viewarea._height = this.canvas.height;
};

x3dom.X3DDocument.prototype.advanceTime = function (t) {
    var i = 0;

    if (this._nodeBag.timer.length) {
        for (i=0; i < this._nodeBag.timer.length; i++)
            { this.needRender |= this._nodeBag.timer[i].tick(t); }
    }
    if (this._nodeBag.followers.length) {
        for (i=0; i < this._nodeBag.followers.length; i++)
            { this.needRender |= this._nodeBag.followers[i].tick(t); }
    }
    // just a temporary tricker solution to update the CSS transforms
    if (this._nodeBag.trans.length) {
        for (i=0; i < this._nodeBag.trans.length; i++)
            { this.needRender |= this._nodeBag.trans[i].tick(t); }
    }
    if (this._nodeBag.viewarea.length) {
        for (i=0; i < this._nodeBag.viewarea.length; i++)
            { this.needRender |= this._nodeBag.viewarea[i].tick(t); }
    }
};

x3dom.X3DDocument.prototype.render = function (ctx) {
    if (!ctx || !this._viewarea) {
        return;
    }

    ctx.renderScene(this._viewarea);
};

x3dom.X3DDocument.prototype.onPick = function (ctx, x, y) {
    if (!ctx || !this._viewarea) {
        return;
    }
	
    ctx.pickValue(this._viewarea, x, y, 1);
};

x3dom.X3DDocument.prototype.onPickRect = function (ctx, x1, y1, x2, y2) {
    if (!ctx || !this._viewarea) {
        return [];
    }
	
    return ctx.pickRect(this._viewarea, x1, y1, x2, y2);
};

x3dom.X3DDocument.prototype.onMove = function (ctx, x, y, buttonState) {
    if (!ctx || !this._viewarea) {
        return;
    }

    if (this._viewarea._scene._vf.doPickPass)
        ctx.pickValue(this._viewarea, x, y, buttonState);
    this._viewarea.onMove(x, y, buttonState);
};

x3dom.X3DDocument.prototype.onMoveView = function (ctx,evt, touches, translation, rotation) {
    if (!ctx || !this._viewarea) {
        return;
    }

    this._scene.getNavigationInfo()._impl.onTouchDrag(this._viewarea, evt, touches, translation, rotation);
};

x3dom.X3DDocument.prototype.onDrag = function (ctx, x, y, buttonState) {
    if (!ctx || !this._viewarea) {
        return;
    }

    if (this._viewarea._scene._vf.doPickPass)
        ctx.pickValue(this._viewarea, x, y, buttonState);
    this._viewarea.onDrag(x, y, buttonState);
};

x3dom.X3DDocument.prototype.onWheel = function (ctx, x, y, originalY) {
    if (!ctx || !this._viewarea) {
        return;
    }

    if (this._viewarea._scene._vf.doPickPass)
        ctx.pickValue(this._viewarea, x, originalY, 0);
    this._viewarea.onDrag(x, y, 2);
};

x3dom.X3DDocument.prototype.onMousePress = function (ctx, x, y, buttonState) {
    if (!ctx || !this._viewarea) {
        return;
    }

    // update volume only on click since expensive!
    this._viewarea._scene.updateVolume();

    ctx.pickValue(this._viewarea, x, y, buttonState);
    this._viewarea.onMousePress(x, y, buttonState);
};

x3dom.X3DDocument.prototype.onMouseRelease = function (ctx, x, y, buttonState, prevButton) {
    if (!ctx || !this._viewarea) {
        return;
    }

    var button = (prevButton << 8) | buttonState;   // for shadowObjectIdChanged
    ctx.pickValue(this._viewarea, x, y, button);
    this._viewarea.onMouseRelease(x, y, buttonState, prevButton);
};

x3dom.X3DDocument.prototype.onMouseOver = function (ctx, x, y, buttonState) {
    if (!ctx || !this._viewarea) {
        return;
    }

    ctx.pickValue(this._viewarea, x, y, buttonState);
    this._viewarea.onMouseOver(x, y, buttonState);
};

x3dom.X3DDocument.prototype.onMouseOut = function (ctx, x, y, buttonState) {
    if (!ctx || !this._viewarea) {
        return;
    }

    ctx.pickValue(this._viewarea, x, y, buttonState);
    this._viewarea.onMouseOut(x, y, buttonState);
};

x3dom.X3DDocument.prototype.onDoubleClick = function (ctx, x, y) {
    if (!ctx || !this._viewarea) {
        return;
    }

    this._viewarea.onDoubleClick(x, y);
};


x3dom.X3DDocument.prototype.onKeyDown = function(keyCode)
{
    //x3dom.debug.logInfo("pressed key " + keyCode);
    switch (keyCode) {
        case 37: /* left */
            this._viewarea.strafeLeft();
            break;
        case 38: /* up */
            this._viewarea.moveFwd();
            break;
        case 39: /* right */
            this._viewarea.strafeRight();
            break;
        case 40: /* down */
            this._viewarea.moveBwd();
            break;
        default:
    }
};

x3dom.X3DDocument.prototype.onKeyUp = function(keyCode)
{
    //x3dom.debug.logInfo("released key " + keyCode);
    var stack = null;

    switch (keyCode) {
        case 13: /* return */
            x3dom.toggleFullScreen();
            break;
        case 33: /* page up */
            stack = this._scene.getViewpoint()._stack;

            if (stack) {
                stack.switchTo('next');
            }
            else {
                x3dom.debug.logError ('No valid ViewBindable stack.');
            }
            break;
        case 34: /* page down */
            stack = this._scene.getViewpoint()._stack;

            if (stack) {
                stack.switchTo('prev');
            }
            else {
                x3dom.debug.logError ('No valid ViewBindable stack.');
            }
            break;
        case 37: /* left */
            break;
        case 38: /* up */
            break;
        case 39: /* right */
            break;
        case 40: /* down */
            break;
        default:
    }
};

x3dom.X3DDocument.prototype.onKeyPress = function(charCode)
{
    //x3dom.debug.logInfo("pressed key " + charCode);
    var nav = this._scene.getNavigationInfo();
    var env = this._scene.getEnvironment();

    switch (charCode)
    {
        case  32: /* space */
            var states = this.canvas.parent.stateViewer;
			if (states) {
				states.display();
			}
            x3dom.debug.logInfo("a: show all | d: show helper buffers | s: small feature culling | t: light view | " +
                                "m: toggle render mode | c: frustum culling | p: intersect type | r: reset view | \n" +
                                "e: examine mode | f: fly mode | y: freefly mode | w: walk mode | h: helicopter mode | " +
                                "l: lookAt mode | o: lookaround | g: game mode | n: turntable | u: upright position | \n" +
                                "v: print viewpoint info | pageUp: next view | pageDown: prev. view | " +
                                "+: increase speed | -: decrease speed ");
            break;
        case  43: /* + (incr. speed) */
            nav._vf.speed = 2 * nav._vf.speed;
            x3dom.debug.logInfo("Changed navigation speed to " + nav._vf.speed);
            break;
        case  45: /* - (decr. speed) */
            nav._vf.speed = 0.5 * nav._vf.speed;
            x3dom.debug.logInfo("Changed navigation speed to " + nav._vf.speed);
            break;
        case  51: /* 3 (decr pg error tol) */
            x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor += 0.5;
            x3dom.debug.logInfo("Changed POP error tolerance to " + x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor);
            break;
        case  52: /* 4 (incr pg error tol) */
            x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor -= 0.5;
            x3dom.debug.logInfo("Changed POP error tolerance to " + x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor);
            break;
        case  54: /* 6 (incr height) */
            nav._vf.typeParams[1] += 1.0;
            nav._heliUpdated = false;
            x3dom.debug.logInfo("Changed helicopter height to " + nav._vf.typeParams[1]);
            break;
        case  55: /* 7 (decr height) */
            nav._vf.typeParams[1] -= 1.0;
            nav._heliUpdated = false;
            x3dom.debug.logInfo("Changed helicopter height to " + nav._vf.typeParams[1]);
            break;
        case  56: /* 8 (decr angle) */
            nav._vf.typeParams[0] -= 0.02;
            nav._heliUpdated = false;
            x3dom.debug.logInfo("Changed helicopter angle to " + nav._vf.typeParams[0]);
            break;
        case  57: /* 9 (incr angle) */
            nav._vf.typeParams[0] += 0.02;
            nav._heliUpdated = false;
            x3dom.debug.logInfo("Changed helicopter angle to " + nav._vf.typeParams[0]);
            break;
        case  97: /* a, view all */
            this._viewarea.showAll();
            break;
        case  99: /* c, toggle frustum culling */
            env._vf.frustumCulling = !env._vf.frustumCulling;
            x3dom.debug.logInfo("Viewfrustum culling " + (env._vf.frustumCulling ? "on" : "off"));
            break;
        case  100: /* d, switch on/off buffer view for dbg */
            if (this._viewarea._visDbgBuf === undefined) {
                this._viewarea._visDbgBuf = (this._x3dElem.getAttribute("showLog") === 'true');
            }
            this._viewarea._visDbgBuf = !this._viewarea._visDbgBuf;
            x3dom.debug.logContainer.style.display = (this._viewarea._visDbgBuf == true) ? "block" : "none";
            break;
        case 101: /* e, examine mode */
            nav.setType("examine", this._viewarea);
            break;
        case 102: /* f, fly mode */
            nav.setType("fly", this._viewarea);
            break;
        case 103: /* g, game mode */
            nav.setType("game", this._viewarea);
            break;
        case 104: /* h, helicopter mode */
            nav.setType("helicopter", this._viewarea);
            break;
        case 105: /* i, fit all */
            this._viewarea.fit(this._scene._lastMin, this._scene._lastMax);
            break;
        case 108: /* l, lookAt mode */
            nav.setType("lookat", this._viewarea);
            break;
        case 109: /* m, toggle "points" attribute */
            //"0" = triangles
            //"1" = points
            //"2" = lines
	    //TODO: here, as option "2", we originally rendered triangle meshes as lines
            //	    instead, we should create a separate line buffer and render it
            this._viewarea._points = ++this._viewarea._points % 2;
            break;
        case 110: /* n, turntable */
            nav.setType("turntable", this._viewarea);
            break;
        case 111: /* o, look around like in fly, but don't move */
            nav.setType("lookaround", this._viewarea);
            break;
        case 112: /* p, switch intersect type */
            switch(this._scene._vf.pickMode.toLowerCase())
            {
                case "idbuf":
                    this._scene._vf.pickMode = "color";
                    break;
                case "color":
                    this._scene._vf.pickMode = "texCoord";
                    break;
                case "texcoord":
                    this._scene._vf.pickMode = "box";
                    break;
                default:
                    this._scene._vf.pickMode = "idBuf";
                    break;
            }
            x3dom.debug.logInfo("Switch pickMode to '" + this._scene._vf.pickMode + "'.");
            break;
        case 114: /* r, reset view */
            this._viewarea.resetView();
            break;
        case 115: /* s, toggle small feature culling */
            env._vf.smallFeatureCulling = !env._vf.smallFeatureCulling;
            x3dom.debug.logInfo("Small feature culling " + (env._vf.smallFeatureCulling ? "on" : "off"));
            break;
        case 116: /* t, light view */
            if (this._nodeBag.lights.length > 0) {
                this._viewarea.animateTo(this._viewarea.getLightMatrix()[0], this._scene.getViewpoint());
            }
            break;
        case 117: /* u, upright position */
            this._viewarea.uprightView();
            break;
        case 118: /* v, print viewpoint position/orientation */
            var that = this;
            (function() {
                var viewpoint = that._viewarea._scene.getViewpoint();
                var mat_view = that._viewarea.getViewMatrix().inverse();
    			
    			var rotation = new x3dom.fields.Quaternion(0, 0, 1, 0);
    			rotation.setValue(mat_view);
    			var rot = rotation.toAxisAngle();
    			var translation = mat_view.e3();
    			
    			x3dom.debug.logInfo('\n&lt;Viewpoint position="' + translation.x.toFixed(5) + ' '
    			                    + translation.y.toFixed(5) + ' ' + translation.z.toFixed(5) + '" ' +
    								'orientation="' + rot[0].x.toFixed(5) + ' ' + rot[0].y.toFixed(5) + ' ' 
    								+ rot[0].z.toFixed(5) + ' ' + rot[1].toFixed(5) + '" \n\t' +
                                    'zNear="' + viewpoint.getNear().toFixed(5) + '" ' +
    								'zFar="' + viewpoint.getFar().toFixed(5) + '" ' +
    								'description="' + viewpoint._vf.description + '"&gt;' +
                                    '&lt;/Viewpoint&gt;');
            })();
            break;
        case 119: /* w, walk mode */
            nav.setType("walk", this._viewarea);
            break;
        case 121: /* y, freefly mode */
            nav.setType("freefly", this._viewarea);
            break;
        default:
    }
};

x3dom.X3DDocument.prototype.shutdown = function(ctx)
{
    if (!ctx) {
        return;
    }
    ctx.shutdown(this._viewarea);
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

x3dom.MatrixMixer = function( beginTime, endTime )
{
    this.beginTime = beginTime || 0;
    this.endTime = endTime || 1;
    this.isMixing = false;

    this._beginMat = x3dom.fields.SFMatrix4f.identity();
    this._beginInvMat = x3dom.fields.SFMatrix4f.identity();
    this._beginLogMat = x3dom.fields.SFMatrix4f.identity();
    this._endMat = x3dom.fields.SFMatrix4f.identity();
    this._endLogMat = x3dom.fields.SFMatrix4f.identity();

    this._beginRot = new x3dom.fields.Quaternion();
    this._endRot = new x3dom.fields.Quaternion();

    this._beginPos = new x3dom.fields.SFVec3f();
    this._endPos = new x3dom.fields.SFVec3f();
    this._result = x3dom.fields.SFMatrix4f.identity();

    this._useQuaternion = false;
};

x3dom.MatrixMixer.prototype._calcFraction = function( time )
{
    var fraction = ( time - this.beginTime ) / ( this.endTime - this.beginTime );
	
    return ( Math.sin( ( fraction * Math.PI ) - ( Math.PI / 2 ) ) + 1 ) / 2.0;
};

x3dom.MatrixMixer.prototype._isValid = function()
{
    var angles = this._beginMat.inverse().mult( this._endMat ).getEulerAngles();
	
    return ( Math.abs( angles[ 0 ] ) != Math.PI && 
	         Math.abs( angles[ 1 ] ) != Math.PI && 
			 Math.abs( angles[ 2 ] ) != Math.PI );
};

x3dom.MatrixMixer.prototype._prepareQuaternionAnimation = function()
{
    this._beginRot.setValue( this._beginMat );
    this._endRot.setValue( this._endMat );

    this._beginPos = this._beginMat.e3();
    this._endPos = this._endMat.e3();

    this._useQuaternion = true;
};

x3dom.MatrixMixer.prototype._reset = function()
{
    this.beginTime = 0;
    this.endTime = 0;
    this._useQuaternion = false;
    this.isMixing = false;
};

x3dom.MatrixMixer.prototype.isActive = function()
{
    return ( this.beginTime > 0 );
};

x3dom.MatrixMixer.prototype.setBeginMatrix = function( mat )
{
    this._beginMat.setValues( mat );
    this._beginInvMat = mat.inverse();
    this._beginLogMat = x3dom.fields.SFMatrix4f.zeroMatrix();
};

x3dom.MatrixMixer.prototype.setEndMatrix = function( mat )
{
    this._endMat.setValues( mat );

    if ( !this._isValid() )
	{
        this._prepareQuaternionAnimation();
    }

    this._endLogMat = this._endMat.mult( this._beginInvMat ).log();
    this._logDiffMat = this._endLogMat.addScaled( this._beginLogMat, -1 );
};

x3dom.MatrixMixer.prototype._mixQuaternion = function( fraction )
{
    var rotation = this._beginRot.slerp( this._endRot, fraction );
    var translation = this._beginPos.addScaled( this._endPos.subtract( this._beginPos ), fraction );

    this._result.setRotate( rotation );
    this._result.setTranslate( translation );

    return this._result.copy();
};

x3dom.MatrixMixer.prototype._mixMatrix = function( fraction )
{
    return this._logDiffMat.multiply( fraction ).add( this._beginLogMat ).exp().mult( this._beginMat );
};


x3dom.MatrixMixer.prototype.mix = function( time )
{
	if ( time <= this.beginTime )
	{
		return this._beginMat;
	}
	else if ( time >= this.endTime )
	{
		this._reset();
		
		return this._endMat;
	}
	else
	{
		this.isMixing = true;
		
		var fraction = this._calcFraction( time );
		
		if(this._useQuaternion)
		{
			return this._mixQuaternion( fraction );
		}
		else
		{
			return this._mixMatrix( fraction );
		}
	}  
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


/**
 * Input types - X3DOM allows either navigation or interaction.
 * During each frame, only interaction of the current type is being processed, it is not possible to
 * perform interaction (for instance, selecting or dragging objects) and navigation at the same time
 */
x3dom.InputTypes = {
    NAVIGATION:  1,
    INTERACTION: 2
};


/**
* Constructor.
*
* @class represents a view area
* @param {x3dom.X3DDocument} document - the target X3DDocument
* @param {Object} scene - the scene
*/
// ### Viewarea ###
x3dom.Viewarea = function (document, scene) {
    this._doc = document; // x3ddocument
    this._scene = scene; // FIXME: updates ?!

    document._nodeBag.viewarea.push(this);

    /**
     * picking informations containing
     * pickingpos, pickNorm, pickObj, firstObj, lastObj, lastClickObj, shadowObjId
     * @var {Object} _pickingInfo
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._pickingInfo = {
        pickPos: new x3dom.fields.SFVec3f(0, 0, 0),
        pickNorm: new x3dom.fields.SFVec3f(0, 0, 1),
        pickObj: null,
        firstObj: null,
        lastObj: null,
        lastClickObj: null,
        shadowObjectId: -1
    };

    this._currentInputType = x3dom.InputTypes.NAVIGATION;

    /**
     * rotation matrix
     * @var {x3dom.fields.SFMatrix4f} _rotMat
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._rotMat = x3dom.fields.SFMatrix4f.identity();

    /**
     * translation matrix
     * @var {x3dom.fields.SFMatrix4f} _transMat
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._transMat = x3dom.fields.SFMatrix4f.identity();

    /**
     * movement vector
     * @var {x3dom.fields.SFVec3f} _movement
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._movement = new x3dom.fields.SFVec3f(0, 0, 0);

    /**
     * flag to signal a needed NavigationMatrixUpdate
     * @var {Boolean} _needNavigationMatrixUpdate
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._needNavigationMatrixUpdate = true;

    /**
     * time passed since last update
     * @var {Number} _deltaT
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._deltaT = 0;

    this._flyMat = null;

    this._pitch = 0;
    this._yaw = 0;

    /**
     * eye position of the view area
     * @var {x3dom.fields.SFVec3f} _eyePos
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._eyePos = new x3dom.fields.SFVec3f(0, 0, 0);

    /**
     * width of the view area
     * @var {Number} _width
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._width = 400;

    /**
     * height of the view area
     * @var {Number} _height
     * @memberof x3dom.Viewarea
     * @instance
     * @protected
     */
    this._height = 300;
    
    this._dx = 0;
    this._dy = 0;
    this._lastX = -1;
    this._lastY = -1;
    this._pressX = -1;
    this._pressY = -1;
    this._lastButton = 0;

    this._points = 0;   // old render mode flag (but think of better name!)
    this._numRenderedNodes = 0;
    
    this._pick = new x3dom.fields.SFVec3f(0, 0, 0);
    this._pickNorm = new x3dom.fields.SFVec3f(0, 0, 1);
    
    this._isAnimating = false;
    this._isMoving = false;
    this._lastTS = 0;
    this._mixer = new x3dom.MatrixMixer();
	this._interpolator = new x3dom.FieldInterpolator();

    this.arc = null;
};

/**
 * Method gets called every frame with the current timestamp
 * @param {Number} timeStamp - current time stamp
 * @return {Boolean} view area animation state
 */
x3dom.Viewarea.prototype.tick = function(timeStamp)
{
    var needMixAnim = false;
    var env = this._scene.getEnvironment();

    if (env._vf.enableARC && this.arc == null)
    {
        this.arc = new x3dom.arc.AdaptiveRenderControl(this._scene);
    }

    if (this._mixer.isActive() )
    {
		var mat = this._mixer.mix( timeStamp );
		this._scene.getViewpoint().setView( mat );
    }
	
	if ( this._interpolator.isActive() )
	{
		var value = this._interpolator.interpolate( timeStamp );
		this._scene.getViewpoint().setZoom( value );
	}

    var needNavAnim = this.navigateTo(timeStamp);
    var lastIsAnimating = this._isAnimating;

    this._lastTS = timeStamp;
    this._isAnimating = (this._mixer.isMixing || this._interpolator.isInterpolating || needNavAnim);

    if (this.arc != null )
    {
        this.arc.update(this.isMovingOrAnimating() ? 1 : 0, this._doc._x3dElem.runtime.getFPS());
    }

    return (this._isAnimating || lastIsAnimating);
};

/**
 * Returns moving state of view are
 * @return {Boolean} moving state of view area
 */
x3dom.Viewarea.prototype.isMoving = function()
{
    return this._isMoving;
};

/**
 * Returns animation state of view area
 * @return {Boolean} animation state of view area
 */
x3dom.Viewarea.prototype.isAnimating = function()
{
    return this._isAnimating;
};

/**
 * is view area moving or animating
 * @return {Boolean} view area moving or animating state
 */
x3dom.Viewarea.prototype.isMovingOrAnimating = function()
{
    return (this._isMoving || this._isAnimating);
};

/**
 * triggers view area to move to something by passing the timestamp
 * returning a flag if the view area needs a navigation animation
 * @return {Boolean} flag if the view area need a navigation state
 */
x3dom.Viewarea.prototype.navigateTo = function(timeStamp)
{
    var navi = this._scene.getNavigationInfo();
    return navi._impl.navigateTo(this, timeStamp);
};

x3dom.Viewarea.prototype.moveFwd = function()
{
    var navi = this._scene.getNavigationInfo();
    navi._impl.moveForward(this);
};

x3dom.Viewarea.prototype.moveBwd = function()
{
    var navi = this._scene.getNavigationInfo();
    navi._impl.moveBackwards(this);    
};

x3dom.Viewarea.prototype.strafeRight = function()
{
    var navi = this._scene.getNavigationInfo();
    navi._impl.strafeRight(this);
    
};

x3dom.Viewarea.prototype.strafeLeft = function()
{
    var navi = this._scene.getNavigationInfo();
    navi._impl.strafeLeft(this);
    
};

x3dom.Viewarea.prototype.animateTo = function(target, prev, dur)
{
    var navi = this._scene.getNavigationInfo();
    navi._impl.animateTo(this, target, prev, dur);
};

x3dom.Viewarea.prototype.orthoAnimateTo = function( target, prev, duration )
{
    var navi = this._scene.getNavigationInfo();
    navi._impl.orthoAnimateTo(this, target, prev, duration);
};

x3dom.Viewarea.prototype.zoom = function( zoomAmount )
{
    var navi = this._scene.getNavigationInfo();
    navi._impl.zoom(this, zoomAmount);
};

x3dom.Viewarea.prototype.getLights = function () {
    var enabledLights = [];
    for (var i=0; i<this._doc._nodeBag.lights.length; i++)
    {
        if (this._doc._nodeBag.lights[i]._vf.on == true)
        {
            enabledLights.push(this._doc._nodeBag.lights[i]);
        }
    }
    return enabledLights;
};

x3dom.Viewarea.prototype.getLightsShadow = function () {
	var lights = this._doc._nodeBag.lights;
	for(var l=0; l<lights.length; l++) {
		if(lights[l]._vf.shadowIntensity > 0.0){
            return true;
        }
	}
    return false;
};

x3dom.Viewarea.prototype.updateSpecialNavigation = function (viewpoint, mat_viewpoint) {
    var navi = this._scene.getNavigationInfo();
    var navType = navi.getType();
    
    // helicopter mode needs to manipulate view matrix specially
    if (navType == "helicopter" && !navi._heliUpdated)
    {
        var typeParams = navi.getTypeParams();
        var theta = typeParams[0];
        var currViewMat = viewpoint.getViewMatrix().mult(mat_viewpoint.inverse()).inverse();

        this._from = currViewMat.e3();
        this._at = this._from.subtract(currViewMat.e2());
        this._up = new x3dom.fields.SFVec3f(0, 1, 0);

        this._from.y = typeParams[1];
        this._at.y = this._from.y;

        var sv = currViewMat.e0();
        var q = x3dom.fields.Quaternion.axisAngle(sv, theta);
        var temp = q.toMatrix();

        var fin = x3dom.fields.SFMatrix4f.translation(this._from);
        fin = fin.mult(temp);

        temp = x3dom.fields.SFMatrix4f.translation(this._from.negate());
        fin = fin.mult(temp);

        this._at = fin.multMatrixPnt(this._at);

        this._flyMat = x3dom.fields.SFMatrix4f.lookAt(this._from, this._at, this._up);
        this._scene.getViewpoint().setView(this._flyMat.inverse());

        navi._heliUpdated = true;
    }
};

/**
 * Get the view areas view point matrix
 * @return {x3dom.fields.SFMatrix4f} view areas view point matrix
 */
x3dom.Viewarea.prototype.getViewpointMatrix = function ()
{
    var viewpoint = this._scene.getViewpoint();
    var mat_viewpoint = viewpoint.getCurrentTransform();
    
    this.updateSpecialNavigation(viewpoint, mat_viewpoint);
    
    return viewpoint.getViewMatrix().mult(mat_viewpoint.inverse());
};

/**
 * Get the view areas view matrix
 * @return {x3dom.fields.SFMatrix4f} view areas view matrix
 */
x3dom.Viewarea.prototype.getViewMatrix = function ()
{
    return this.getViewpointMatrix().mult(this._transMat).mult(this._rotMat);
};

x3dom.Viewarea.prototype.getLightMatrix = function ()
{
    var lights = this._doc._nodeBag.lights;
    var i, n = lights.length;

    if (n > 0)
    {
        var vol = this._scene.getVolume();

        if (vol.isValid())
        {
            var min = x3dom.fields.SFVec3f.MAX();
            var max = x3dom.fields.SFVec3f.MIN();
            vol.getBounds(min, max);

            var l_arr = [];
            var viewpoint = this._scene.getViewpoint();
            var fov = viewpoint.getFieldOfView();

            var dia = max.subtract(min);
            var dist1 = (dia.y/2.0) / Math.tan(fov/2.0) + (dia.z/2.0);
            var dist2 = (dia.x/2.0) / Math.tan(fov/2.0) + (dia.z/2.0);

            dia = min.add(dia.multiply(0.5));

            for (i=0; i<n; i++)
            {
                if (x3dom.isa(lights[i], x3dom.nodeTypes.PointLight)) {
                    var wcLoc = lights[i].getCurrentTransform().multMatrixPnt(lights[i]._vf.location);
                    dia = dia.subtract(wcLoc).normalize();
                }
                else {
                    var dir = lights[i].getCurrentTransform().multMatrixVec(lights[i]._vf.direction);
                    dir = dir.normalize().negate();
                    dia = dia.add(dir.multiply(1.2 * (dist1 > dist2 ? dist1 : dist2)));
                }

                l_arr[i] = lights[i].getViewMatrix(dia);
            }

            return l_arr;
        }
    }

    //TODO, this is only for testing
    return [ this.getViewMatrix() ];
};

x3dom.Viewarea.prototype.getWCtoLCMatrix = function(lMat)
{
    var proj = this.getProjectionMatrix();
    var view;

    if (arguments.length === 0) {
        view = this.getLightMatrix()[0];
    }
    else {
        view = lMat;
    }

    return proj.mult(view);
};

/**
 * Get six WCtoLCMatrices for point light
 * @param {x3dom.fields.SFMatrix4f} view - the view matrix
 * @param {x3dom.nodeTypes.X3DNode} lightNode - the light node
 * @param {x3dom.fields.SFMatrix4f} mat_proj - the projection matrix
 * @return {Array} six WCtoLCMatrices
 */
x3dom.Viewarea.prototype.getWCtoLCMatricesPointLight = function(view, lightNode, mat_proj)
{	 
	var zNear = lightNode._vf.zNear;
	var zFar = lightNode._vf.zFar;
	
	var proj = this.getLightProjectionMatrix(view, zNear, zFar, false, mat_proj);
	
	//set projection matrix to 90 degrees FOV (vertical and horizontal)
	proj._00 = 1;
	proj._11 = 1;
	
	var matrices = [];
	
	//create six matrices to cover all directions of point light
	matrices[0] = proj.mult(view);
		
	var rotationMatrix;
	
	//y-rotation
	for (var i=1; i<=3; i++){	
		rotationMatrix = x3dom.fields.SFMatrix4f.rotationY(i*Math.PI/2);
		matrices[i] = proj.mult(rotationMatrix.mult(view));
	}
	
	//x-rotation
	rotationMatrix = x3dom.fields.SFMatrix4f.rotationX(Math.PI/2);
	matrices[4] = proj.mult(rotationMatrix.mult(view));
	
	rotationMatrix = x3dom.fields.SFMatrix4f.rotationX(3*Math.PI/2);
	matrices[5] = proj.mult(rotationMatrix.mult(view));
	
    return matrices;
};

/*
 * Get WCToLCMatrices for cascaded light
 */
x3dom.Viewarea.prototype.getWCtoLCMatricesCascaded = function(view, lightNode, mat_proj)
{
	var numCascades = Math.max(1, Math.min(lightNode._vf.shadowCascades, 6));
	var splitFactor = Math.max(0, Math.min(lightNode._vf.shadowSplitFactor, 1));
	var splitOffset = Math.max(0, Math.min(lightNode._vf.shadowSplitOffset, 1));

	var isSpotLight = x3dom.isa(lightNode, x3dom.nodeTypes.SpotLight);
	var zNear = lightNode._vf.zNear;
	var zFar = lightNode._vf.zFar;
	
	var proj = this.getLightProjectionMatrix(view, zNear, zFar, true, mat_proj);
	
	if (isSpotLight){
		//set FOV to 90 degrees
		proj._00 = 1;
		proj._11 = 1;
	}	
	
	//get view projection matrix
	var viewProj = proj.mult(view);	
	
	var matrices = [];

	if (numCascades == 1){
		//return if only one cascade
		matrices[0] = viewProj;
		return matrices;
	}
	
	//compute split positions of view frustum
	var cascadeSplits = this.getShadowSplitDepths(numCascades, splitFactor, splitOffset, true, mat_proj);
	
	//calculate fitting matrices and multiply with view projection
	for (var i=0; i<numCascades; i++){
		var fittingMat = this.getLightFittingMatrix(viewProj, cascadeSplits[i], cascadeSplits[i+1], mat_proj);
		matrices[i] = fittingMat.mult(viewProj);
	}	
	
	return matrices;
};

x3dom.Viewarea.prototype.getLightProjectionMatrix = function(lMat, zNear, zFar, highPrecision, mat_proj)
{
    var proj = x3dom.fields.SFMatrix4f.copy(mat_proj);
	
	if (!highPrecision || zNear > 0 || zFar > 0) {
		//replace near and far plane of projection matrix
		//by values adapted to the light position
		
		var lightPos = lMat.inverse().e3();
		
		var nearScale = 0.8;
		var farScale = 1.2;
		
		var min = x3dom.fields.SFVec3f.copy(this._scene._lastMin);
		var max = x3dom.fields.SFVec3f.copy(this._scene._lastMax); 

		var dia = max.subtract(min);
		var sRad = dia.length() / 2;
		
		var sCenter = min.add(dia.multiply(0.5));
		var vDist = (lightPos.subtract(sCenter)).length();
		
		var near, far;
		
		if (sRad) {
			if (vDist > sRad)
				near = (vDist - sRad) * nearScale; 
			else
				near = 1;                           
			far = (vDist + sRad) * farScale;
		}
		if (zNear > 0) near = zNear;
		if (zFar > 0) far = zFar;

		proj._22 = -(far+near)/(far-near);
		proj._23 = -2.0*far*near / (far-near);
		
		return proj;
	}
    else {
		//should be more accurate, but also more expensive
		var cropMatrix = this.getLightCropMatrix(proj.mult(lMat));
		
		return cropMatrix.mult(proj);
	}
};

x3dom.Viewarea.prototype.getProjectionMatrix = function()
{
    var viewpoint = this._scene.getViewpoint();

    return viewpoint.getProjectionMatrix(this._width/this._height);
};

/**
 * Get the view frustum for a given clipping matrix
 * @param {x3dom.fields.SFMatrix4f} clipMat - the clipping matrix
 * @return {x3dom.fields.FrustumVolume} the resulting view frustum
 */
x3dom.Viewarea.prototype.getViewfrustum = function(clipMat)
{
    var env = this._scene.getEnvironment();

    if (env._vf.frustumCulling == true)
    {
        if (arguments.length == 0) {
            var proj = this.getProjectionMatrix();
            var view = this.getViewMatrix();
    
            return new x3dom.fields.FrustumVolume(proj.mult(view));
        }
        else {
            return new x3dom.fields.FrustumVolume(clipMat);
        }
    }

    return null;
};

/**
 * Get the world coordinates to clipping coordinates matrix by multiplying the projection and view matrices
 * @return {x3dom.fields.SFMatrix4f} world coordinates to clipping coordinates matrix
 */
x3dom.Viewarea.prototype.getWCtoCCMatrix = function()
{
    var view = this.getViewMatrix();
    var proj = this.getProjectionMatrix();

    return proj.mult(view);
};

/**
 * Get the clipping coordinates to world coordinates matrix by multiplying the projection and view matrices
 * @return {x3dom.fields.SFMatrix4f} clipping coordinates to world coordinates  matrix
 */
x3dom.Viewarea.prototype.getCCtoWCMatrix = function()
{
    var mat = this.getWCtoCCMatrix();

    return mat.inverse();
};

x3dom.Viewarea.prototype.calcViewRay = function(x, y, mat)
{
    var cctowc = mat ? mat : this.getCCtoWCMatrix();

    var rx = x / (this._width - 1.0) * 2.0 - 1.0;
    var ry = (this._height - 1.0 - y) / (this._height - 1.0) * 2.0 - 1.0;

    var from = cctowc.multFullMatrixPnt(new x3dom.fields.SFVec3f(rx, ry, -1));
    var at = cctowc.multFullMatrixPnt(new x3dom.fields.SFVec3f(rx, ry,  1));
    var dir = at.subtract(from);

    return new x3dom.fields.Ray(from, dir);
};

x3dom.Viewarea.prototype.showAll = function(axis, updateCenterOfRotation)
{
    if (axis === undefined)
        axis = "negZ";

    if (updateCenterOfRotation === undefined) {
        updateCenterOfRotation = false;
    }


    var scene = this._scene;
    scene.updateVolume();

    var min = x3dom.fields.SFVec3f.copy(scene._lastMin);
    var max = x3dom.fields.SFVec3f.copy(scene._lastMax);

    var x = "x", y = "y", z = "z";
    var sign = 1;
    var to, from = new x3dom.fields.SFVec3f(0, 0, -1);

    switch (axis) {
        case "posX":
        sign = -1;
        case "negX":
        z = "x"; x = "y"; y = "z";
        to = new x3dom.fields.SFVec3f(sign, 0, 0);
        break;
        case "posY":
        sign = -1;
        case "negY":
        z = "y"; x = "z"; y = "x";
        to = new x3dom.fields.SFVec3f(0, sign, 0);
        break;
        case "posZ":
        sign = -1;
        case "negZ":
        default:
        to = new x3dom.fields.SFVec3f(0, 0, -sign);
        break;
    }

    var viewpoint = scene.getViewpoint();
    var fov = viewpoint.getFieldOfView();

    var isOrtho = x3dom.isa(viewpoint, x3dom.nodeTypes.OrthoViewpoint);

    var dia = max.subtract(min); 
    var dia2 = dia.multiply(0.5);
    var center = min.add(dia2);

    if (updateCenterOfRotation) {
        viewpoint.setCenterOfRotation(center);
    }

    var diaz2 = dia[z] / 2.0, tanfov2 = Math.tan(fov / 2.0);

    var dist1 = (dia[y] / 2.0) / tanfov2 + diaz2;
    var dist2 = (dia[x] / 2.0) / tanfov2 + diaz2;

    dia = min.add(dia.multiply(0.5));

    if(isOrtho)
    {
        dia[z] += sign * (dist1 > dist2 ? dist1 : dist2) * 3.01;
    }
    else
    {
        dia[z] += sign * (dist1 > dist2 ? dist1 : dist2) * 1.01;
    }


    var quat = x3dom.fields.Quaternion.rotateFromTo(from, to);

    var viewmat = quat.toMatrix();

    viewmat = viewmat.mult(x3dom.fields.SFMatrix4f.translation(dia.negate()));

    if ( isOrtho )
    {
        this.orthoAnimateTo( dist1, Math.abs(viewpoint._fieldOfView[0]) );

        this.animateTo( viewmat, viewpoint );
    }
    else
    {
        this.animateTo( viewmat, viewpoint );
    }
};

x3dom.Viewarea.prototype.fit = function(min, max, updateCenterOfRotation)
{
    if (updateCenterOfRotation === undefined) {
        updateCenterOfRotation = true;
    }

    var dia2 = max.subtract(min).multiply(0.5);    // half diameter
    var center = min.add(dia2);                    // center in wc
    var bsr = dia2.length();                       // bounding sphere radius

    var viewpoint = this._scene.getViewpoint();
    var fov = viewpoint.getFieldOfView();

    var viewmat = x3dom.fields.SFMatrix4f.copy(this.getViewMatrix());

    var rightDir = new x3dom.fields.SFVec3f(viewmat._00, viewmat._01, viewmat._02);
    var upDir = new x3dom.fields.SFVec3f(viewmat._10, viewmat._11, viewmat._12);
    var viewDir = new x3dom.fields.SFVec3f(viewmat._20, viewmat._21, viewmat._22);

    var tanfov2 = Math.tan(fov / 2.0);
    var dist = bsr / tanfov2;

    var eyePos = center.add(viewDir.multiply(dist));

    viewmat._03 = -rightDir.dot(eyePos);
    viewmat._13 = -upDir.dot(eyePos);
    viewmat._23 = -viewDir.dot(eyePos);

    if (updateCenterOfRotation) {
        viewpoint.setCenterOfRotation(center);
    }

    if (x3dom.isa(viewpoint, x3dom.nodeTypes.OrthoViewpoint))
    {
        this.orthoAnimateTo( dist / 2.01, Math.abs(viewpoint._fieldOfView[0]) );
        this.animateTo( viewmat, viewpoint );
    }
    else
    {
        this.animateTo(viewmat, viewpoint);
    }
};

x3dom.Viewarea.prototype.resetView = function()
{
    var navi = this._scene.getNavigationInfo();
    navi._impl.resetView(this);    
};

x3dom.Viewarea.prototype.resetNavHelpers = function()
{
    this._rotMat = x3dom.fields.SFMatrix4f.identity();
    this._transMat = x3dom.fields.SFMatrix4f.identity();
    this._movement = new x3dom.fields.SFVec3f(0, 0, 0);
    this._needNavigationMatrixUpdate = true;
};

x3dom.Viewarea.prototype.uprightView = function()
{
    var mat = this.getViewMatrix().inverse();

    var from = mat.e3();
    var at = from.subtract(mat.e2());
    var up = new x3dom.fields.SFVec3f(0, 1, 0);

    var s = mat.e2().cross(up).normalize();
    var v = s.cross(up).normalize();
    at = from.add(v);

    mat = x3dom.fields.SFMatrix4f.lookAt(from, at, up);
    mat = mat.inverse();

    this.animateTo(mat, this._scene.getViewpoint());
};

x3dom.Viewarea.prototype.callEvtHandler = function (node, eventType, event)
{
    if (!node || !node._xmlNode)
        return null;

    try {
        var attrib = node._xmlNode[eventType];

        if (typeof(attrib) === "function") {
            attrib.call(node._xmlNode, event);
        }
        else {
            var funcStr = node._xmlNode.getAttribute(eventType);
            var func = new Function('event', funcStr);
            func.call(node._xmlNode, event);
        }

        var list = node._listeners[event.type];
        if (list) {
            for (var it=0; it<list.length; it++) {
                list[it].call(node._xmlNode, event);
            }
        }
    }
    catch(e) {
        x3dom.debug.logException(e);
    }

    return event.cancelBubble;
};

x3dom.Viewarea.prototype.checkEvents = function (obj, x, y, buttonState, eventType)
{
    var that = this;
    var needRecurse = true;
    var childNode;
    var i, n;
    var target = (obj && obj._xmlNode) ? obj._xmlNode : {};


    var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;


    var event = {
        viewarea: that,
        target: target,
        type: eventType.substr(2, eventType.length-2),
        button: buttonState,
        layerX: x,
        layerY: y,
        worldX: that._pick.x,
        worldY: that._pick.y,
        worldZ: that._pick.z,
        normalX: that._pickNorm.x,
        normalY: that._pickNorm.y,
        normalZ: that._pickNorm.z,
        hitPnt: that._pick.toGL(), // for convenience
        hitObject: target,         // deprecated, remove!
        shadowObjectId: that._pickingInfo.shadowObjectId,
        cancelBubble: false,
        stopPropagation: function() { this.cancelBubble = true; },
		preventDefault: function() { this.cancelBubble = true; }
    };

    try {
        var anObj = obj;
        
        if ( anObj && anObj._xmlNode && anObj._cf.geometry &&
             !anObj._xmlNode[eventType] &&
             !anObj._xmlNode.hasAttribute(eventType) &&
             !anObj._listeners[event.type]) {
            anObj = anObj._cf.geometry.node;
        }
        
        if (anObj && that.callEvtHandler(anObj, eventType, event) === true) {
            needRecurse = false;
        }
    }
    catch(e) {
        x3dom.debug.logException(e);
    }

    var recurse = function(obj) {
        Array.forEach(obj._parentNodes, function (node) {
            if ( node._xmlNode && (node._xmlNode[eventType] ||
                 node._xmlNode.hasAttribute(eventType) ||
                 node._listeners[event.type]) )
            {
                if (that.callEvtHandler(node, eventType, event) === true) {
                    needRecurse = false;
                }
            }

            //find the lowest pointing device sensors in the hierarchy that might be affected
            //(note that, for X3DTouchSensors, 'affected' does not necessarily mean 'activated')
            if (buttonState == 0 && affectedPointingSensorsList.length == 0 &&
                (eventType == 'onmousemove' || eventType == 'onmouseover' || eventType == 'onmouseout') )
            {
                n = node._childNodes.length;

                for (i = 0; i < n; ++i)
                {
                    childNode = node._childNodes[i];

                    if (x3dom.isa(childNode, x3dom.nodeTypes.X3DPointingDeviceSensorNode) && childNode._vf.enabled)
                    {
                        affectedPointingSensorsList.push(childNode);
                    }
                }
            }

            if (x3dom.isa(node, x3dom.nodeTypes.Anchor) && eventType === 'onclick') {
                node.handleTouch();
                needRecurse = false;
            }
            else if (needRecurse) {
                recurse(node);
            }
        });
    };

    if (needRecurse && obj) {
        recurse(obj);
    }

	return needRecurse;
};


/**
 * Notifies all pointing device sensors that are currently affected by mouse events, if any, about the given event
 * @param {DOMEvent} event - a mouse event, enriched by X3DOM-specific members
 */
x3dom.Viewarea.prototype._notifyAffectedPointingSensors = function(event)
{
    var funcDict = {
        "mousedown" : "pointerPressedOverSibling",
        "mousemove" : "pointerMoved",
        "mouseover" : "pointerMovedOver",
        "mouseout"  : "pointerMovedOut"
    };

    var func = funcDict[event.type];
    var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
    var i, n = affectedPointingSensorsList.length;

    if (n > 0 && func !== undefined)
    {
        for (i = 0; i < n; i++)
            affectedPointingSensorsList[i][func](event);
    }
};


x3dom.Viewarea.prototype.initMouseState = function()
{
    this._deltaT = 0;
    this._dx = 0;
    this._dy = 0;
    this._lastX = -1;
    this._lastY = -1;
    this._pressX = -1;
    this._pressY = -1;
    this._lastButton = 0;
    this._isMoving = false;
    this._needNavigationMatrixUpdate = true;
};

x3dom.Viewarea.prototype.onMousePress = function (x, y, buttonState)
{
    this._needNavigationMatrixUpdate = true;

    this.prepareEvents(x, y, buttonState, "onmousedown");
    this._pickingInfo.lastClickObj = this._pickingInfo.pickObj;
    this._pickingInfo.firstObj = this._pickingInfo.pickObj;

    this._dx = 0;
    this._dy = 0;
    this._lastX = x;
    this._lastY = y;
    this._pressX = x;
    this._pressY = y;
    this._lastButton = buttonState;
    this._isMoving = false;

    if (this._currentInputType == x3dom.InputTypes.NAVIGATION)
    {
        var navi = this._scene.getNavigationInfo();
        navi._impl.onMousePress(this, x, y, buttonState);
    }
};

x3dom.Viewarea.prototype.onMouseRelease = function (x, y, buttonState, prevButton)
{
    var i;
    //if the mouse is released, reset the list of currently affected pointing sensors
    var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
    for (i = 0; i < affectedPointingSensorsList.length; ++i)
    {
        affectedPointingSensorsList[i].pointerReleased();
    }
    this._doc._nodeBag.affectedPointingSensors = [];

    var tDist = 3.0;  // distance modifier for lookat, could be param
    var dir;
    var navi = this._scene.getNavigationInfo();
    var navType = navi.getType();

    if (this._scene._vf.pickMode.toLowerCase() !== "box") {
        this.prepareEvents(x, y, prevButton, "onmouseup");

        // click means that mousedown _and_ mouseup were detected on same element
        if (this._pickingInfo.pickObj &&
            this._pickingInfo.pickObj === this._pickingInfo.lastClickObj)
        {
            this.prepareEvents(x, y, prevButton, "onclick");
        }
        else if (!this._pickingInfo.pickObj && !this._pickingInfo.lastClickObj &&
                 !this._pickingInfo.firstObj)   // press and release outside object
        {
            var eventType = "backgroundClicked";
            try {
                if ( this._scene._xmlNode &&
                    (this._scene._xmlNode["on" + eventType] ||
                        this._scene._xmlNode.hasAttribute("on" + eventType) ||
                        this._scene._listeners[eventType]) ) {
                    var event = {
                        target: this._scene._xmlNode, type: eventType,
                        button: prevButton, layerX: x, layerY: y,
                        cancelBubble: false,
                        stopPropagation: function () { this.cancelBubble = true; },
                        preventDefault:  function () { this.cancelBubble = true; }
                    };
                    this._scene.callEvtHandler(("on" + eventType), event);
                }
            }
            catch (e) { x3dom.debug.logException("backgroundClicked: " + e); }
        }
    }
    else {
        var t0 = new Date().getTime();
        var line = this.calcViewRay(x, y);
        var isect = this._scene.doIntersect(line);
        var obj = line.hitObject;

        if (isect && obj)
        {
            this._pick.setValues(line.hitPoint);

            this.checkEvents(obj, x, y, buttonState, "onclick");

            x3dom.debug.logInfo("Hit '" + obj._xmlNode.localName + "/ " +
                                obj._DEF + "' at dist=" + line.dist.toFixed(4));
            x3dom.debug.logInfo("Ray hit at position " + this._pick);
        }

        var t1 = new Date().getTime() - t0;
        x3dom.debug.logInfo("Picking time (box): " + t1 + "ms");

        if (!isect) {
            dir = this.getViewMatrix().e2().negate();
            var u = dir.dot(line.pos.negate()) / dir.dot(line.dir);
            this._pick = line.pos.add(line.dir.multiply(u));
            //x3dom.debug.logInfo("No hit at position " + this._pick);
        }
    }
    this._pickingInfo.firstObj = null;

    if (this._currentInputType == x3dom.InputTypes.NAVIGATION &&
        (this._pickingInfo.pickObj || this._pickingInfo.shadowObjectId >= 0) &&
        navType === "lookat" && this._pressX === x && this._pressY === y)
    {
        var step = (this._lastButton & 2) ? -1 : 1;
        var dist = this._pickingInfo.pickPos.subtract(this._from).length() / tDist;

        var laMat = new x3dom.fields.SFMatrix4f();
        laMat.setValues(this.getViewMatrix());
        laMat = laMat.inverse();

        var from = laMat.e3();
        var at = from.subtract(laMat.e2());
        var up = laMat.e1();

        dir = this._pickingInfo.pickPos.subtract(from);
        var len = dir.length();
        dir = dir.normalize();

        //var newUp = new x3dom.fields.SFVec3f(0, 1, 0);
        var newAt = from.addScaled(dir, len);

        var s = dir.cross(up).normalize();
        dir = s.cross(up).normalize();

        if (step < 0) {
            dist = (0.5 + len + dist) * 2;
        }
        var newFrom = newAt.addScaled(dir, dist);

        laMat = x3dom.fields.SFMatrix4f.lookAt(newFrom, newAt, up);
        laMat = laMat.inverse();

        dist = newFrom.subtract(from).length();
        var dur = Math.max(0.5, Math.log((1 + dist) / navi._vf.speed));

        this.animateTo(laMat, this._scene.getViewpoint(), dur);
    }

    this._dx = 0;
    this._dy = 0;
    this._lastX = x;
    this._lastY = y;
    this._lastButton = buttonState;
    this._isMoving = false;
};

x3dom.Viewarea.prototype.onMouseOver = function (x, y, buttonState)
{
    this._dx = 0;
    this._dy = 0;
    this._lastButton = 0;
    this._isMoving = false;
    this._lastX = x;
    this._lastY = y;
    this._deltaT = 0;
};

x3dom.Viewarea.prototype.onMouseOut = function (x, y, buttonState)
{
    this._dx = 0;
    this._dy = 0;
    this._lastButton = 0;
    this._isMoving = false;
    this._lastX = x;
    this._lastY = y;
    this._deltaT = 0;

    //if the mouse is moved out of the canvas, reset the list of currently affected pointing sensors
    //(this behaves similar to a mouse release inside the canvas)
    var i;
    var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
    for (i = 0; i < affectedPointingSensorsList.length; ++i)
    {
        affectedPointingSensorsList[i].pointerReleased();
    }
    this._doc._nodeBag.affectedPointingSensors = [];
};

x3dom.Viewarea.prototype.onDoubleClick = function (x, y)
{
    if (this._doc._x3dElem.hasAttribute('disableDoubleClick') &&
        this._doc._x3dElem.getAttribute('disableDoubleClick') === 'true') {
        return;
    }
    
    var navi = this._scene.getNavigationInfo();
    navi._impl.onDoubleClick(this, x,y);
};

x3dom.Viewarea.prototype.handleMoveEvt = function (x, y, buttonState)
{
    //pointing sensors might still be in use, if the mouse has previously been pressed over sensor geometry
    //(in general, transitions between INTERACTION and NAVIGATION require that the mouse is not pressed)
    if (buttonState == 0)
    {
        this._doc._nodeBag.affectedPointingSensors = [];
    }

    this.prepareEvents(x, y, buttonState, "onmousemove");

    if (this._pickingInfo.pickObj !== this._pickingInfo.lastObj)
    {
        if (this._pickingInfo.lastObj) {
            var obj = this._pickingInfo.pickObj;
            this._pickingInfo.pickObj = this._pickingInfo.lastObj;

            // call event for lastObj
            this.prepareEvents(x, y, buttonState, "onmouseout");
            this._pickingInfo.pickObj = obj;
        }

        if (this._pickingInfo.pickObj) {
            // call event for pickObj
            this.prepareEvents(x, y, buttonState, "onmouseover");
        }

        this._pickingInfo.lastObj = this._pickingInfo.pickObj;
    }
};

x3dom.Viewarea.prototype.onMove = function (x, y, buttonState)
{
    this.handleMoveEvt(x, y, buttonState);

    if (this._lastX < 0 || this._lastY < 0) {
        this._lastX = x;
        this._lastY = y;
    }
    this._dx = x - this._lastX;
    this._dy = y - this._lastY;
    this._lastX = x;
    this._lastY = y;
};

// multi-touch version of examine mode, called from X3DCanvas.js
x3dom.Viewarea.prototype.onMoveView = function (translation, rotation)
{
    if (this._currentInputType == x3dom.InputTypes.NAVIGATION)
    {
        var navi = this._scene.getNavigationInfo();
        var viewpoint = this._scene.getViewpoint();

        if (navi.getType() === "examine")
        {
            if (translation)
            {
                var distance = (this._scene._lastMax.subtract(this._scene._lastMin)).length();
                distance = ((distance < x3dom.fields.Eps) ? 1 : distance) * navi._vf.speed;

                translation = translation.multiply(distance);
                this._movement = this._movement.add(translation);

                this._transMat = viewpoint.getViewMatrix().inverse().
                    mult(x3dom.fields.SFMatrix4f.translation(this._movement)).
                    mult(viewpoint.getViewMatrix());
            }

            if (rotation)
            {
                var center = viewpoint.getCenterOfRotation();
                var mat = this.getViewMatrix();
                mat.setTranslate(new x3dom.fields.SFVec3f(0,0,0));

                this._rotMat = this._rotMat.
                               mult(x3dom.fields.SFMatrix4f.translation(center)).
                               mult(mat.inverse()).mult(rotation).mult(mat).
                               mult(x3dom.fields.SFMatrix4f.translation(center.negate()));
            }

            this._isMoving = true;
        }
    }
};

x3dom.Viewarea.prototype.onDrag = function (x, y, buttonState)
{
    // should onmouseover/-out be handled on drag?
    this.handleMoveEvt(x, y, buttonState);

    if (this._currentInputType == x3dom.InputTypes.NAVIGATION)
    {
        this._scene.getNavigationInfo()._impl.onDrag(this,x,y,buttonState);
    }

};

x3dom.Viewarea.prototype.prepareEvents = function (x, y, buttonState, eventType)
{
    var affectedPointingSensorsList = this._doc._nodeBag.affectedPointingSensors;
    var pickMode                    = this._scene._vf.pickMode.toLowerCase();
    var avoidTraversal              = (pickMode.indexOf("idbuf") == 0 ||
                                       pickMode == "color" || pickMode == "texcoord");

    var obj = null;

    if (avoidTraversal) {
        obj = this._pickingInfo.pickObj;

        if (obj) {
            this._pick.setValues(this._pickingInfo.pickPos);
            this._pickNorm.setValues(this._pickingInfo.pickNorm);

            this.checkEvents(obj, x, y, buttonState, eventType);

            if (eventType === "onclick") {  // debug
                if (obj._xmlNode)
                    x3dom.debug.logInfo("Hit \"" + obj._xmlNode.localName + "/ " + obj._DEF + "\"");
                x3dom.debug.logInfo("Ray hit at position " + this._pick);
            }
        }
    }

    //TODO: this is pretty redundant - but from where should we obtain this event object?
    //      this also needs to work if there is no picked object, and independent from "avoidTraversal"?

    // FIXME;  avoidTraversal is only to distinguish between the ancient box and the other render-based pick modes,
    //         thus it seems the cleanest thing to just remove the old traversal-based and non-functional box mode.
    //         Concerning background: what about if we unify the onbackgroundClicked event such that there is also
    //         an onbackgroundMoved event etc?

    var event = {
        viewarea: this,
        target: {},     // should be hit xml element
        type: eventType.substr(2, eventType.length-2),
        button: buttonState,
        layerX: x,
        layerY: y,
        worldX: this._pick.x,
        worldY: this._pick.y,
        worldZ: this._pick.z,
        normalX: this._pickNorm.x,
        normalY: this._pickNorm.y,
        normalZ: this._pickNorm.z,
        hitPnt: this._pick.toGL(), // for convenience
        hitObject: (obj && obj._xmlNode) ? obj._xmlNode : null,
        shadowObjectId: this._pickingInfo.shadowObjectId,
        cancelBubble: false,
        stopPropagation: function() { this.cancelBubble = true; },
        preventDefault: function() { this.cancelBubble = true; }
    };

    //forward event to affected pointing device sensors
    this._notifyAffectedPointingSensors(event);

    //switch between navigation and interaction
    if (affectedPointingSensorsList.length > 0)
    {
        this._currentInputType = x3dom.InputTypes.INTERACTION;
    }
    else
    {
        this._currentInputType = x3dom.InputTypes.NAVIGATION;
    }
};


x3dom.Viewarea.prototype.getRenderMode = function()
{
    // this._points == 0 ? TRIANGLES or TRIANGLE_STRIP
    // this._points == 1 ? gl.POINTS
    // this._points == 2 ? gl.LINES
    // TODO: 3 :== surface with additional wireframe render mode
    return this._points;
};


x3dom.Viewarea.prototype.getShadowedLights = function()
{	
	var shadowedLights = [];
	var shadowIndex = 0;
	var slights = this.getLights();
	for (var i=0; i<slights.length; i++){
		if (slights[i]._vf.shadowIntensity > 0.0){
			shadowedLights[shadowIndex] = slights[i];
			shadowIndex++;
		}
	}
	return shadowedLights;
};


/**
 * Calculate view frustum split positions for the given number of cascades
 * @param {Number} numCascades - the number of cascades
 * @param {Number} splitFactor - the splitting factor
 * @param {Number} splitOffset - the offset for the splits
 * @param {Array} postProject - the post projection something
 * @param {x3dom.fields.SFMatrix4f} mat_proj - the projection matrix
 * @return {Array} the post projection something
 */
x3dom.Viewarea.prototype.getShadowSplitDepths = function(numCascades, splitFactor, splitOffset, postProject, mat_proj)
{
	var logSplit;
	var practSplit = [];
	
	var viewPoint = this._scene.getViewpoint();
	
	var zNear = viewPoint.getNear();
	var zFar = viewPoint.getFar();

	practSplit[0] = zNear;
	
	//pseudo near plane for bigger cascades near camera
	zNear = zNear + splitOffset*(zFar-zNear)/10;
	
	//calculate split depths according to "practical split scheme"
	for (var i=1;i<numCascades;i++){
		logSplit = zNear * Math.pow((zFar / zNear), i / numCascades);
		practSplit[i] = splitFactor * logSplit + (1 - splitFactor) * (zNear + i / (numCascades * (zNear-zFar)));
	}
	practSplit[numCascades] = zFar;
	
	//return in view coords
	if (!postProject)
        return practSplit;
	
	//return in post projective coords
	var postProj = [];
	
	for (var j=0; j<=numCascades; j++){
		postProj[j] = mat_proj.multFullMatrixPnt(new x3dom.fields.SFVec3f(0,0,-practSplit[j])).z;
	}
	
	return postProj;
};


/*
 * calculate a matrix to enhance the placement of 
 * the near and far planes of the light projection matrix
*/
x3dom.Viewarea.prototype.getLightCropMatrix = function(WCToLCMatrix)
{	
	//get corner points of scene bounds
	var sceneMin = x3dom.fields.SFVec3f.copy(this._scene._lastMin);
	var sceneMax = x3dom.fields.SFVec3f.copy(this._scene._lastMax);
	
	var sceneCorners = [];
	sceneCorners[0] = new x3dom.fields.SFVec3f(sceneMin.x, sceneMin.y, sceneMin.z);
	sceneCorners[1] = new x3dom.fields.SFVec3f(sceneMin.x, sceneMin.y, sceneMax.z);
	sceneCorners[2] = new x3dom.fields.SFVec3f(sceneMin.x, sceneMax.y, sceneMin.z);
	sceneCorners[3] = new x3dom.fields.SFVec3f(sceneMin.x, sceneMax.y, sceneMax.z);
	sceneCorners[4] = new x3dom.fields.SFVec3f(sceneMax.x, sceneMin.y, sceneMin.z);
	sceneCorners[5] = new x3dom.fields.SFVec3f(sceneMax.x, sceneMin.y, sceneMax.z);
	sceneCorners[6] = new x3dom.fields.SFVec3f(sceneMax.x, sceneMax.y, sceneMin.z);
	sceneCorners[7] = new x3dom.fields.SFVec3f(sceneMax.x, sceneMax.y, sceneMax.z);
	
	//transform scene bounds into light space
    var i;
	for (i=0; i<8; i++){
		sceneCorners[i] = WCToLCMatrix.multFullMatrixPnt(sceneCorners[i]);
	}
	
	//determine min and max values in light space
	var minScene = x3dom.fields.SFVec3f.copy(sceneCorners[0]);
	var maxScene = x3dom.fields.SFVec3f.copy(sceneCorners[0]);
	
	for (i=1; i<8; i++){
		minScene.z = Math.min(sceneCorners[i].z, minScene.z); 
		maxScene.z = Math.max(sceneCorners[i].z, maxScene.z); 
	}

	var scaleZ = 2.0 / (maxScene.z - minScene.z);
	var offsetZ = -(scaleZ * (maxScene.z + minScene.z)) / 2.0;	
		
	//var scaleZ = 1.0 / (maxScene.z - minScene.z);
	//var offsetZ = -minScene.z * scaleZ;

	var cropMatrix = x3dom.fields.SFMatrix4f.identity();
	
	cropMatrix._22 = scaleZ;
	cropMatrix._23 = offsetZ;	
	
	return cropMatrix;	
};
	

/*
 * Calculate a matrix to fit the given wctolc-matrix to the split boundaries
 */
x3dom.Viewarea.prototype.getLightFittingMatrix = function(WCToLCMatrix, zNear, zFar, mat_proj)
{
	var mat_view = this.getViewMatrix();
	var mat_view_proj = mat_proj.mult(mat_view);
	var mat_view_proj_inverse = mat_view_proj.inverse();
	
	//define view frustum corner points in post perspective view space
	var frustumCorners = [];
	frustumCorners[0] = new x3dom.fields.SFVec3f(-1, -1, zFar);
	frustumCorners[1] = new x3dom.fields.SFVec3f(-1, -1, zNear);
	frustumCorners[2] = new x3dom.fields.SFVec3f(-1,  1, zFar);
	frustumCorners[3] = new x3dom.fields.SFVec3f(-1,  1, zNear);
	frustumCorners[4] = new x3dom.fields.SFVec3f( 1, -1, zFar);
	frustumCorners[5] = new x3dom.fields.SFVec3f( 1, -1, zNear);
	frustumCorners[6] = new x3dom.fields.SFVec3f( 1,  1, zFar);
	frustumCorners[7] = new x3dom.fields.SFVec3f( 1,  1, zNear);
	

	//transform corner points into post perspective light space
    var i;
	for (i=0; i<8; i++){
		frustumCorners[i] = mat_view_proj_inverse.multFullMatrixPnt(frustumCorners[i]);
		frustumCorners[i] = WCToLCMatrix.multFullMatrixPnt(frustumCorners[i]);
	}
	
	//calculate minimum and maximum values
	var minFrustum = x3dom.fields.SFVec3f.copy(frustumCorners[0]);
	var maxFrustum = x3dom.fields.SFVec3f.copy(frustumCorners[0]);

	for (i=1; i<8; i++){
		minFrustum.x = Math.min(frustumCorners[i].x, minFrustum.x); 
		minFrustum.y = Math.min(frustumCorners[i].y, minFrustum.y);
		minFrustum.z = Math.min(frustumCorners[i].z, minFrustum.z); 
		
		maxFrustum.x = Math.max(frustumCorners[i].x, maxFrustum.x); 
		maxFrustum.y = Math.max(frustumCorners[i].y, maxFrustum.y); 
		maxFrustum.z = Math.max(frustumCorners[i].z, maxFrustum.z); 
	}
	
	
	//clip values to box (-1,-1,-1),(1,1,1)
	function clip(min,max)
    {
		var xMin = min.x;
		var yMin = min.y;
		var zMin = min.z;
		var xMax = max.x;
		var yMax = max.y;
		var zMax = max.z;
		
		if (xMin > 1.0 || xMax < -1.0) {
			xMin = -1.0;
			xMax =  1.0;
		} else {
			xMin = Math.max(xMin,-1.0);
			xMax = Math.min(xMax, 1.0);
		}
		
		if (yMin > 1.0 || yMax < -1.0) {
			yMin = -1.0;
			yMax =  1.0;
		} else {
			yMin = Math.max(yMin,-1.0);
			yMax = Math.min(yMax, 1.0);
		}
					   
		if (zMin > 1.0 || zMax < -1.0){
			zMin = -1.0;
			zMax = 1.0;
		} else {
			zMin = Math.max(zMin,-1.0);
			zMax = Math.min(zMax, 1.0);
		}
		var minValues = new x3dom.fields.SFVec3f(xMin,yMin,zMin);
		var maxValues = new x3dom.fields.SFVec3f(xMax,yMax,zMax);

		return new x3dom.fields.BoxVolume(minValues,maxValues);
	}
	
	var frustumBB = clip(minFrustum, maxFrustum);

	//define fitting matrix
	var scaleX = 2.0 / (frustumBB.max.x - frustumBB.min.x);
	var scaleY = 2.0 / (frustumBB.max.y - frustumBB.min.y);
	var offsetX = -(scaleX * (frustumBB.max.x + frustumBB.min.x)) / 2.0;
	var offsetY = -(scaleY * (frustumBB.max.y + frustumBB.min.y)) / 2.0;
	
	var fittingMatrix = x3dom.fields.SFMatrix4f.identity();
	
	fittingMatrix._00 = scaleX;
	fittingMatrix._11 = scaleY;
	fittingMatrix._03 = offsetX;
	fittingMatrix._13 = offsetY;

	return fittingMatrix;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


/** @class x3dom.Mesh
*/
x3dom.Mesh = function(parent) 
{
    this._parent = parent;

    this._vol = new x3dom.fields.BoxVolume();

    this._invalidate = true;
    this._numFaces = 0;
    this._numCoords = 0;

    // cp. x3dom.Utils.primTypeDic for type list
	this._primType = 'TRIANGLES';
    
    this._positions = [];
    this._normals   = [];
    this._texCoords = [];
    this._colors    = [];
    this._indices   = [];
    
    this._positions[0] = [];
    this._normals[0]   = [];
    this._texCoords[0] = [];
    this._colors[0]    = [];
    this._indices[0]   = [];
};

x3dom.Mesh.prototype._dynamicFields = {};   // can hold X3DVertexAttributeNodes
/*x3dom.Mesh.prototype._positions = [];
x3dom.Mesh.prototype._normals   = [];
x3dom.Mesh.prototype._texCoords = [];
x3dom.Mesh.prototype._colors    = [];
x3dom.Mesh.prototype._indices   = [];*/

x3dom.Mesh.prototype._numPosComponents = 3;
x3dom.Mesh.prototype._numTexComponents = 2;
x3dom.Mesh.prototype._numColComponents = 3;
x3dom.Mesh.prototype._numNormComponents = 3;
x3dom.Mesh.prototype._lit = true;

x3dom.Mesh.prototype._vol = null;
x3dom.Mesh.prototype._invalidate = true;
x3dom.Mesh.prototype._numFaces = 0;
x3dom.Mesh.prototype._numCoords = 0;

x3dom.Mesh.prototype.setMeshData = function(positions, normals, texCoords, colors, indices)
{
    this._positions[0] = positions;
    this._normals[0]   = normals;
    this._texCoords[0] = texCoords;
    this._colors[0]    = colors;
    this._indices[0]   = indices;
    
    this._invalidate = true;
    this._numFaces = this._indices[0].length / 3;
    this._numCoords = this._positions[0].length / 3;
};

x3dom.Mesh.prototype.getVolume = function()
{
    if (this._invalidate == true && !this._vol.isValid())
    {
        var coords = this._positions[0];
        var n = coords.length;

        if (n > 3)
        {
            var initVal = new x3dom.fields.SFVec3f(coords[0],coords[1],coords[2]);
            this._vol.setBounds(initVal, initVal);

            for (var i=3; i<n; i+=3)
            {
                if (this._vol.min.x > coords[i  ]) { this._vol.min.x = coords[i  ]; }
                if (this._vol.min.y > coords[i+1]) { this._vol.min.y = coords[i+1]; }
                if (this._vol.min.z > coords[i+2]) { this._vol.min.z = coords[i+2]; }

                if (this._vol.max.x < coords[i  ]) { this._vol.max.x = coords[i  ]; }
                if (this._vol.max.y < coords[i+1]) { this._vol.max.y = coords[i+1]; }
                if (this._vol.max.z < coords[i+2]) { this._vol.max.z = coords[i+2]; }
            }
            this._invalidate = false;
        }
    }

    return this._vol;
};

x3dom.Mesh.prototype.invalidate = function()
{
    this._invalidate = true;
    this._vol.invalidate();
};

x3dom.Mesh.prototype.isValid = function()
{
    return this._vol.isValid();
};

x3dom.Mesh.prototype.getCenter = function() 
{
    return this.getVolume().getCenter();
};

x3dom.Mesh.prototype.getDiameter = function() 
{
    return this.getVolume().getDiameter();
};

x3dom.Mesh.prototype.doIntersect = function(line)
{
    var vol = this.getVolume();
    var isect = line.intersect(vol.min, vol.max);
    
    //TODO: iterate over all faces!
    if (isect && line.enter < line.dist)
    {
        //x3dom.debug.logInfo("Hit \"" + this._parent._xmlNode.localName + "/ " + 
        //                    this._parent._DEF + "\" at dist=" + line.enter.toFixed(4));
        
        line.dist = line.enter;
        line.hitObject = this._parent;
        line.hitPoint = line.pos.add(line.dir.multiply(line.enter));
    }
    
    return isect;
};

x3dom.Mesh.prototype.calcNormals = function(creaseAngle, ccw)
{
    if (ccw === undefined)
        ccw = true;

    var multInd = this._multiIndIndices && this._multiIndIndices.length;
    var idxs = multInd ? this._multiIndIndices : this._indices[0];
    var coords = this._positions[0];

    var vertNormals = [];
    var vertFaceNormals = [];

    var i, j, m = coords.length;
    var a, b, n = null;

    var num = (this._posSize !== undefined && this._posSize > m) ? this._posSize / 3 : m / 3;
    num = 3 * ((num - Math.floor(num) > 0) ? Math.floor(num + 1) : num);
    
    for (i = 0; i < num; ++i) {
        vertFaceNormals[i] = [];
    }
    
    num = idxs.length;
        
    for (i = 0; i < num; i += 3) {
        var ind_i0, ind_i1, ind_i2;
        var t;

        if (!multInd) {
            ind_i0 = idxs[i  ] * 3;
            ind_i1 = idxs[i+1] * 3;
            ind_i2 = idxs[i+2] * 3;

            t = new x3dom.fields.SFVec3f(coords[ind_i1], coords[ind_i1+1], coords[ind_i1+2]);
            a = new x3dom.fields.SFVec3f(coords[ind_i0], coords[ind_i0+1], coords[ind_i0+2]).subtract(t);
            b = t.subtract(new x3dom.fields.SFVec3f(coords[ind_i2], coords[ind_i2+1], coords[ind_i2+2]));

            // this is needed a few lines below
            ind_i0 =  i    * 3;
            ind_i1 = (i+1) * 3;
            ind_i2 = (i+2) * 3;
        }
        else {
            ind_i0 =  i    * 3;
            ind_i1 = (i+1) * 3;
            ind_i2 = (i+2) * 3;

            t = new x3dom.fields.SFVec3f(coords[ind_i1], coords[ind_i1+1], coords[ind_i1+2]);
            a = new x3dom.fields.SFVec3f(coords[ind_i0], coords[ind_i0+1], coords[ind_i0+2]).subtract(t);
            b = t.subtract(new x3dom.fields.SFVec3f(coords[ind_i2], coords[ind_i2+1], coords[ind_i2+2]));
        }
        
        n = a.cross(b).normalize();
        if (!ccw)
            n = n.negate();

        if (creaseAngle <= x3dom.fields.Eps) {
            vertNormals[ind_i0  ] = vertNormals[ind_i1  ] = vertNormals[ind_i2  ] = n.x;
            vertNormals[ind_i0+1] = vertNormals[ind_i1+1] = vertNormals[ind_i2+1] = n.y;
            vertNormals[ind_i0+2] = vertNormals[ind_i1+2] = vertNormals[ind_i2+2] = n.z;
        }
        else {
            vertFaceNormals[idxs[i  ]].push(n);
            vertFaceNormals[idxs[i+1]].push(n);
            vertFaceNormals[idxs[i+2]].push(n);
        }
    }

    // TODO: allow generic creaseAngle
    if (creaseAngle > x3dom.fields.Eps) 
    {
        for (i = 0; i < m; i += 3) {
            var iThird = i / 3;
            var arr;

            if (!multInd) {
                arr = vertFaceNormals[iThird];
            }
            else {
                arr = vertFaceNormals[idxs[iThird]];
            }
            num = arr.length;

            n = new x3dom.fields.SFVec3f(0, 0, 0);

            for (j = 0; j < num; ++j) {
                n = n.add(arr[j]);
            }
            n = n.normalize();

            vertNormals[i  ] = n.x;
            vertNormals[i+1] = n.y;
            vertNormals[i+2] = n.z;
        }
    }
    
    this._normals[0] = vertNormals;
};

/** @param primStride Number of index entries per primitive, for example 3 for TRIANGLES
 */
x3dom.Mesh.prototype.splitMesh = function(primStride, checkMultiIndIndices)
{
    var pStride;
    var isMultiInd;

    if (typeof primStride === undefined) {
        pStride = 3;
    } else {
        pStride = primStride;
    }

    if (typeof checkMultiIndIndices === undefined) {
        checkMultiIndIndices = false;
    }

    var MAX = x3dom.Utils.maxIndexableCoords;

    //adapt MAX to match the primitive stride
    MAX = Math.floor(MAX / pStride) * pStride;

    if (this._positions[0].length / 3 <= MAX && !checkMultiIndIndices) {
        return;
    }

    if (checkMultiIndIndices) {
        isMultiInd = this._multiIndIndices && this._multiIndIndices.length;
    } else {
        isMultiInd = false;
    }
    
    var positions = this._positions[0];
    var normals = this._normals[0];
    var texCoords = this._texCoords[0];
    var colors = this._colors[0];
    var indices = isMultiInd ? this._multiIndIndices : this._indices[0];

    var i = 0;
    
    do
    {
        this._positions[i] = [];
        this._normals[i]   = [];
        this._texCoords[i] = [];
        this._colors[i]    = [];
        this._indices[i]   = [];
        
        var k = (indices.length - ((i + 1) * MAX) >= 0);
        
        if (k) {
            this._indices[i] = indices.slice(i * MAX, (i + 1) * MAX);
        } else { 
            this._indices[i] = indices.slice(i * MAX);
        }

        if(!isMultiInd) {
            if (i) {
                var m = i * MAX;
                for (var j=0, l=this._indices[i].length; j<l; j++) {
                    this._indices[i][j] -= m;
                }
            }
        } else {
            for (var j=0, l=this._indices[i].length; j<l; j++) {
                this._indices[i][j] = j;
            }
        }

        if (k) { 
            this._positions[i] = positions.slice(i * MAX * 3, 3 * (i + 1) * MAX);
        } else { 
            this._positions[i] = positions.slice(i * MAX * 3);
        }
        
        if (normals.length) {
            if (k) { 
                this._normals[i] = normals.slice(i * MAX * 3, 3 * (i + 1) * MAX);
            } else { 
                this._normals[i] = normals.slice(i * MAX * 3);
            }
        }
        if (texCoords.length) {
            if (k) { 
                this._texCoords[i] = texCoords.slice(i * MAX * this._numTexComponents, 
                                                        this._numTexComponents * (i + 1) * MAX);
            } else {
                this._texCoords[i] = texCoords.slice(i * MAX * this._numTexComponents);
            }
        }
        if (colors.length) {
            if (k) { 
                this._colors[i] = colors.slice(i * MAX * this._numColComponents, 
                                                  this._numColComponents * (i + 1) * MAX);
            } else { 
                this._colors[i] = colors.slice(i * MAX * this._numColComponents);
            }
        }
    }
    while (positions.length > ++i * MAX * 3);
};

x3dom.Mesh.prototype.calcTexCoords = function(mode)
{
    this._texCoords[0] = [];
    
    // TODO; impl. all modes that aren't handled in shader!
    // FIXME; WebKit requires valid texCoords for texturing
    if (mode.toLowerCase() === "sphere-local")
    {
        for (var i=0, j=0, n=this._normals[0].length; i<n; i+=3)
        {
            this._texCoords[0][j++] = 0.5 + this._normals[0][i  ] / 2.0;
            this._texCoords[0][j++] = 0.5 + this._normals[0][i+1] / 2.0;
        }
    }
    else    // "plane" is x3d default mapping
    {
        var min = new x3dom.fields.SFVec3f(0, 0, 0),
            max = new x3dom.fields.SFVec3f(0, 0, 0);
        var vol = this.getVolume();

        vol.getBounds(min, max);
        var dia = max.subtract(min);
        
        var S = 0, T = 1;
        
        if (dia.x >= dia.y)
        {
            if (dia.x >= dia.z)
            {
                S = 0;
                T = dia.y >= dia.z ? 1 : 2;
            }
            else // dia.x < dia.z
            {
                S = 2;
                T = 0;
            }
        }
        else // dia.x < dia.y
        {
            if (dia.y >= dia.z)
            {
                S = 1;
                T = dia.x >= dia.z ? 0 : 2;
            }
            else // dia.y < dia.z
            {
                S = 2;
                T = 1;
            }
        }
        
        var sDenom = 1, tDenom = 1;
        var sMin = 0, tMin = 0;
        
        switch(S) {
            case 0: sDenom = dia.x; sMin = min.x; break;
            case 1: sDenom = dia.y; sMin = min.y; break;
            case 2: sDenom = dia.z; sMin = min.z; break;
        }
        
        switch(T) {
            case 0: tDenom = dia.x; tMin = min.x; break;
            case 1: tDenom = dia.y; tMin = min.y; break;
            case 2: tDenom = dia.z; tMin = min.z; break;
        }
        
        for (var k=0, l=0, m=this._positions[0].length; k<m; k+=3)
        {
            this._texCoords[0][l++] = (this._positions[0][k+S] - sMin) / sDenom;
            this._texCoords[0][l++] = (this._positions[0][k+T] - tMin) / tDenom;
        }
    }
};


/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


/** If used as standalone lib, define some basics first. */
if (typeof x3dom === "undefined")
{
    /**
     * @namespace x3dom
     */
    x3dom = {
        extend: function(f) {
            function G() {}
            G.prototype = f.prototype || f;
            return new G();
        },

        debug: {
            logInfo:    function(msg) { console.log(msg); },
            logWarning: function(msg) { console.warn(msg); },
            logError:   function(msg) { console.error(msg); }
        }
    };

    if (!Array.map) {
        Array.map = function(array, fun, thisp) {
            var len = array.length;
            var res = [];
            for (var i = 0; i < len; i++) {
                if (i in array) {
                    res[i] = fun.call(thisp, array[i], i, array);
                }
            }
            return res;
        };
    }

    console.log("Using x3dom fields.js as standalone math and/or base types library.");
}


/**
 * The x3dom.fields namespace.
 * @namespace x3dom.fields
 */
x3dom.fields = {};

/** shortcut for convenience and speedup */
var VecMath = x3dom.fields;

/** Epsilon */
x3dom.fields.Eps = 0.000001;


///////////////////////////////////////////////////////////////////////////////
// Single-Field Definitions
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
/**
 * Constructor. You must either specify all argument values or no argument. In the latter case, an identity matrix will be created.
 *
 * @class Represents a 4x4 matrix in row major format.
 * @param {Number} [_00=1] - value at [0,0]
 * @param {Number} [_01=0] - value at [0,1]
 * @param {Number} [_02=0] - value at [0,2]
 * @param {Number} [_03=0] - value at [0,3]
 * @param {Number} [_10=0] - value at [1,0]
 * @param {Number} [_11=1] - value at [1,1]
 * @param {Number} [_12=0] - value at [1,2]
 * @param {Number} [_13=0] - value at [1,3]
 * @param {Number} [_20=0] - value at [2,0]
 * @param {Number} [_21=0] - value at [2,1]
 * @param {Number} [_22=1] - value at [2,2]
 * @param {Number} [_23=0] - value at [2,3]
 * @param {Number} [_30=0] - value at [3,0]
 * @param {Number} [_31=0] - value at [3,1]
 * @param {Number} [_32=0] - value at [3,2]
 * @param {Number} [_33=1] - value at [3,3]
 */
x3dom.fields.SFMatrix4f = function(	_00, _01, _02, _03, 
									_10, _11, _12, _13, 
									_20, _21, _22, _23, 
									_30, _31, _32, _33) 
{
    if (arguments.length === 0) {
        this._00 = 1; this._01 = 0; this._02 = 0; this._03 = 0;
        this._10 = 0; this._11 = 1; this._12 = 0; this._13 = 0;
        this._20 = 0; this._21 = 0; this._22 = 1; this._23 = 0;
        this._30 = 0; this._31 = 0; this._32 = 0; this._33 = 1;
    }
    else {
        this._00 = _00; this._01 = _01; this._02 = _02; this._03 = _03;
        this._10 = _10; this._11 = _11; this._12 = _12; this._13 = _13;
        this._20 = _20; this._21 = _21; this._22 = _22; this._23 = _23;
        this._30 = _30; this._31 = _31; this._32 = _32; this._33 = _33;
    }
};

/**
 * Returns the first column vector of the matrix.
 * @returns {x3dom.fields.SFVec3f} the vector
 */
x3dom.fields.SFMatrix4f.prototype.e0 = function () {
    var baseVec = new x3dom.fields.SFVec3f(this._00, this._10, this._20);
    return baseVec.normalize();
};

/**
 * Returns the second column vector of the matrix.
 * @returns {x3dom.fields.SFVec3f} the vector
 */
x3dom.fields.SFMatrix4f.prototype.e1 = function () {
    var baseVec = new x3dom.fields.SFVec3f(this._01, this._11, this._21);
    return baseVec.normalize();
};

/**
 * Returns the third column vector of the matrix.
 * @returns {x3dom.fields.SFVec3f} the vector
 */
x3dom.fields.SFMatrix4f.prototype.e2 = function () {
    var baseVec = new x3dom.fields.SFVec3f(this._02, this._12, this._22);
    return baseVec.normalize();
};

/**
 * Returns the fourth column vector of the matrix.
 * @returns {x3dom.fields.SFVec3f} the vector
 */
x3dom.fields.SFMatrix4f.prototype.e3 = function () {
    return new x3dom.fields.SFVec3f(this._03, this._13, this._23);
};

/**
 * Returns a copy of the argument matrix.
 * @param {x3dom.fields.SFMatrix4f} that - the matrix to copy
 * @returns {x3dom.fields.SFMatrix4f} the copy
 */
x3dom.fields.SFMatrix4f.copy = function(that) {
    return new x3dom.fields.SFMatrix4f(
        that._00, that._01, that._02, that._03,
        that._10, that._11, that._12, that._13,
        that._20, that._21, that._22, that._23,
        that._30, that._31, that._32, that._33
    );
};

/**
 * Returns a copy of the matrix.
 * @returns {x3dom.fields.SFMatrix4f} the copy
 */
x3dom.fields.SFMatrix4f.prototype.copy = function() {
    return x3dom.fields.SFMatrix4f.copy(this);
};

/**
 * Returns a SFMatrix4f identity matrix.
 * @returns {x3dom.fields.SFMatrix4f} the new identity matrix
 */
x3dom.fields.SFMatrix4f.identity = function () {
    return new x3dom.fields.SFMatrix4f(
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    );
};

/**
 * Returns a new null matrix.
 * @returns {x3dom.fields.SFMatrix4f} the new null matrix
 */
x3dom.fields.SFMatrix4f.zeroMatrix = function () {
    return new x3dom.fields.SFMatrix4f(
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0,
        0, 0, 0, 0
    );
};

/**
 * Returns a new translation matrix.
 * @param {x3dom.fields.SFVec3f} vec - vector that describes the desired translation
 * @returns {x3dom.fields.SFMatrix4f} the new identity matrix
 */
x3dom.fields.SFMatrix4f.translation = function (vec) {
    return new x3dom.fields.SFMatrix4f(
        1, 0, 0, vec.x,
        0, 1, 0, vec.y,
        0, 0, 1, vec.z,
        0, 0, 0, 1
    );
};

/**
 * Returns a new rotation matrix , rotating around the x axis.
 * @param {x3dom.fields.SFVec3f} a - angle in radians
 * @returns {x3dom.fields.SFMatrix4f} the new rotation matrix
 */
x3dom.fields.SFMatrix4f.rotationX = function (a) {
    var c = Math.cos(a);
    var s = Math.sin(a);
    return new x3dom.fields.SFMatrix4f(
        1, 0, 0, 0,
        0, c, -s, 0,
        0, s, c, 0,
        0, 0, 0, 1
    );
};

/**
 * Returns a new rotation matrix , rotating around the y axis.
 * @param {x3dom.fields.SFVec3f} a - angle in radians
 * @returns {x3dom.fields.SFMatrix4f} the new rotation matrix
 */
x3dom.fields.SFMatrix4f.rotationY = function (a) {
    var c = Math.cos(a);
    var s = Math.sin(a);
    return new x3dom.fields.SFMatrix4f(
        c, 0, s, 0,
        0, 1, 0, 0,
        -s, 0, c, 0,
        0, 0, 0, 1
    );
};

/**
 * Returns a new rotation matrix , rotating around the z axis.
 * @param {x3dom.fields.SFVec3f} a - angle in radians
 * @returns {x3dom.fields.SFMatrix4f} the new rotation matrix
 */
x3dom.fields.SFMatrix4f.rotationZ = function (a) {
    var c = Math.cos(a);
    var s = Math.sin(a);
    return new x3dom.fields.SFMatrix4f(
        c, -s, 0, 0,
        s, c, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    );
};

/**
 * Returns a new scale matrix.
 * @param {x3dom.fields.SFVec3f} vec - vector containing scale factors along the three main axes
 * @returns {x3dom.fields.SFMatrix4f} the new scale matrix
 */
x3dom.fields.SFMatrix4f.scale = function (vec) {
    return new x3dom.fields.SFMatrix4f(
        vec.x, 0, 0, 0,
        0, vec.y, 0, 0,
        0, 0, vec.z, 0,
        0, 0, 0, 1
    );
};

/**
 * Returns a new camera matrix, using the given "look at" parameters.
 * @param {x3dom.fields.SFVec3f} from - eye point
 * @param {x3dom.fields.SFVec3f} at - focus ("look at") point
 * @param {x3dom.fields.SFVec3f} up - up vector
 * @returns {x3dom.fields.SFMatrix4f} the new camera matrix
 */
x3dom.fields.SFMatrix4f.lookAt = function (from, at, up)
{
    var view = from.subtract(at).normalize();
    var right = up.normalize().cross(view).normalize();

    // check if zero vector, i.e. linearly dependent
    if (right.dot(right) < x3dom.fields.Eps) {
        x3dom.debug.logWarning("View matrix is linearly dependent.");
        return x3dom.fields.SFMatrix4f.translation(from);
    }

    var newUp = view.cross(right).normalize();

    var tmp = x3dom.fields.SFMatrix4f.identity();
    tmp.setValue(right, newUp, view, from);

    return tmp;
};

x3dom.fields.SFMatrix4f.perspectiveFrustum = function(left, right, bottom, top, near, far)
{
    return new x3dom.fields.SFMatrix4f(
        2*near/(right-left), 0, (right+left)/(right-left), 0,
        0, 2*near/(top-bottom), (top+bottom)/(top-bottom), 0,
        0, 0, -(far+near)/(far-near), -2*far*near/(far-near),
        0, 0, -1, 0
    );
};

/**
 * Returns a new perspective projection matrix.
 * @param {Number} fov    - field-of-view angle in radians
 * @param {Number} aspect - aspect ratio (width / height)
 * @param {Number} near   - near clipping distance
 * @param {Number} far    - far clipping distance
 * @returns {x3dom.fields.SFMatrix4f} the new projection matrix
 */
x3dom.fields.SFMatrix4f.perspective = function(fov, aspect, near, far)
{
    var f = 1 / Math.tan(fov / 2);

    return new x3dom.fields.SFMatrix4f(
        f/aspect, 0, 0, 0,
        0, f, 0, 0,
        0, 0, (near+far)/(near-far), 2*near*far/(near-far),
        0, 0, -1, 0
    );
};

/**
 * Returns a new orthogonal projection matrix.
 * @param {Number} left         - left border value of the view area
 * @param {Number} right        - right border value of the view area
 * @param {Number} bottom       - bottom border value of the view area
 * @param {Number} top          - top border value of the view area
 * @param {Number} near         - near clipping distance
 * @param {Number} far          - far clipping distance
 * @param {Number} [aspect=1.0] - desired aspect ratio (width / height) of the projected image
 * @returns {x3dom.fields.SFMatrix4f} the new projection matrix
 */
x3dom.fields.SFMatrix4f.ortho = function(left, right, bottom, top, near, far, aspect)
{
    var rl = (right - left) / 2;    // hs
    var tb = (top - bottom) / 2;    // vs
    var fn = far - near;

    if (aspect === undefined)
        aspect = 1.0;

    if (aspect < (rl / tb))
        tb = rl / aspect;
    else
        rl = tb * aspect;

    left = -rl;
    right = rl;
    bottom = -tb;
    top = tb;

    rl *= 2;
    tb *= 2;

    return new x3dom.fields.SFMatrix4f(
        2 / rl, 0, 0,  -(right+left) / rl,
        0, 2 / tb, 0,  -(top+bottom) / tb,
        0, 0, -2 / fn, -(far+near) / fn,
        0, 0, 0, 1
    );
};

/**
 * Sets the translation components of a homogenous transform matrix.
 * @param {x3dom.fields.SFVec3f} vec - the translation vector
 */
x3dom.fields.SFMatrix4f.prototype.setTranslate = function (vec) {
    this._03 = vec.x;
    this._13 = vec.y;
    this._23 = vec.z;
};

/**
 * Sets the scale components of a homogenous transform matrix.
 * @param {x3dom.fields.SFVec3f} vec - vector containing scale factors along the three main axes
 */
x3dom.fields.SFMatrix4f.prototype.setScale = function (vec) {
    this._00 = vec.x;
    this._11 = vec.y;
    this._22 = vec.z;
};

/**
 * Sets the rotation components of a homogenous transform matrix.
 * @param {x3dom.fields.Quaternion} quat - quaternion that describes the rotation
 */
x3dom.fields.SFMatrix4f.prototype.setRotate = function (quat) {
    var xx = quat.x * quat.x;
    var xy = quat.x * quat.y;
    var xz = quat.x * quat.z;
    var yy = quat.y * quat.y;
    var yz = quat.y * quat.z;
    var zz = quat.z * quat.z;
    var wx = quat.w * quat.x;
    var wy = quat.w * quat.y;
    var wz = quat.w * quat.z;

    this._00 = 1 - 2 * (yy + zz); this._01 = 2 * (xy - wz); this._02 = 2 * (xz + wy);
    this._10 = 2 * (xy + wz); this._11 = 1 - 2 * (xx + zz); this._12 = 2 * (yz - wx);
    this._20 = 2 * (xz - wy); this._21 = 2 * (yz + wx); this._22 = 1 - 2 * (xx + yy);
};

/**
 * Creates a new matrix from a column major string representation, with values separated by commas
 * @param {String} str - string to parse
 * @return {x3dom.fields.SFMatrix4f} the new matrix
 */
x3dom.fields.SFMatrix4f.parseRotation = function (str) {
    var m = /^([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)$/.exec(str);
    var x = +m[1], y = +m[2], z = +m[3], a = +m[4];
    
    var d = Math.sqrt(x*x + y*y + z*z);
    if (d === 0) {
        x = 1; y = z = 0;
    } else {
        x /= d; y /= d; z /= d;
    }
    
    var c = Math.cos(a);
    var s = Math.sin(a);
    var t = 1 - c;

    return new x3dom.fields.SFMatrix4f(
        t*x*x+c,   t*x*y+s*z, t*x*z-s*y, 0,
        t*x*y-s*z, t*y*y+c,   t*y*z+s*x, 0,
        t*x*z+s*y, t*y*z-s*x, t*z*z+c,   0,
        0,         0,         0,         1
    ).transpose();
};

/**
 * Creates a new matrix from a X3D-conformant string representation
 * @param {String} str - string to parse
 * @return {x3dom.fields.SFMatrix4f} the new rotation matrix
 */
x3dom.fields.SFMatrix4f.parse = function (str) {
    var needTranspose = false;
    var val = /matrix.*\((.+)\)/;
    if (val.exec(str)) {
        str = RegExp.$1;
        needTranspose = true;
    }
    var arr = Array.map(str.split(/[,\s]+/), function (n) { return +n; });
    if (arr.length >= 16)
    {
        if (!needTranspose) {
            return new x3dom.fields.SFMatrix4f(
                arr[0],  arr[1],  arr[2],  arr[3], 
                arr[4],  arr[5],  arr[6],  arr[7], 
                arr[8],  arr[9],  arr[10], arr[11], 
                arr[12], arr[13], arr[14], arr[15]
            );
        }
        else {
            return new x3dom.fields.SFMatrix4f(
                arr[0],  arr[4],  arr[8],  arr[12], 
                arr[1],  arr[5],  arr[9],  arr[13], 
                arr[2],  arr[6],  arr[10], arr[14], 
                arr[3],  arr[7],  arr[11], arr[15]
            );
        }
    }
    else if (arr.length === 6) {
        return new x3dom.fields.SFMatrix4f(
            arr[0],  arr[1],  0,  arr[4], 
            arr[2],  arr[3],  0,  arr[5], 
                 0,       0,  1,  0, 
                 0,       0,  0,  1
        );
    }
    else {
        x3dom.debug.logWarning("SFMatrix4f - can't parse string: " + str);
        return x3dom.fields.SFMatrix4f.identity();
    }
};

/**
 * Returns the result of multiplying this matrix with the given one, using "post-multiplication" / "right multiply".
 * @param {x3dom.fields.SFMatrix4f} that - matrix to multiply with this one
 * @return {x3dom.fields.SFMatrix4f} resulting matrix
 */
x3dom.fields.SFMatrix4f.prototype.mult = function (that)  {
    return new x3dom.fields.SFMatrix4f(
        this._00*that._00+this._01*that._10+this._02*that._20+this._03*that._30, 
        this._00*that._01+this._01*that._11+this._02*that._21+this._03*that._31, 
        this._00*that._02+this._01*that._12+this._02*that._22+this._03*that._32, 
        this._00*that._03+this._01*that._13+this._02*that._23+this._03*that._33,
        this._10*that._00+this._11*that._10+this._12*that._20+this._13*that._30, 
        this._10*that._01+this._11*that._11+this._12*that._21+this._13*that._31, 
        this._10*that._02+this._11*that._12+this._12*that._22+this._13*that._32, 
        this._10*that._03+this._11*that._13+this._12*that._23+this._13*that._33,
        this._20*that._00+this._21*that._10+this._22*that._20+this._23*that._30, 
        this._20*that._01+this._21*that._11+this._22*that._21+this._23*that._31, 
        this._20*that._02+this._21*that._12+this._22*that._22+this._23*that._32, 
        this._20*that._03+this._21*that._13+this._22*that._23+this._23*that._33,
        this._30*that._00+this._31*that._10+this._32*that._20+this._33*that._30, 
        this._30*that._01+this._31*that._11+this._32*that._21+this._33*that._31, 
        this._30*that._02+this._31*that._12+this._32*that._22+this._33*that._32, 
        this._30*that._03+this._31*that._13+this._32*that._23+this._33*that._33
    );
};

/**
 * Transforms a given 3D point, using this matrix as a homogenous transform matrix
 * (ignores projection part of matrix for speedup in standard cases).
 * @param {x3dom.fields.SFVec3f} vec - point to transform
 * @return {x3dom.fields.SFVec3f} resulting point
 */
x3dom.fields.SFMatrix4f.prototype.multMatrixPnt = function (vec) {
    return new x3dom.fields.SFVec3f(
        this._00*vec.x + this._01*vec.y + this._02*vec.z + this._03,
        this._10*vec.x + this._11*vec.y + this._12*vec.z + this._13,
        this._20*vec.x + this._21*vec.y + this._22*vec.z + this._23
    );
};

/**
 * Transforms a given 3D vector, using this matrix as a homogenous transform matrix.
 * @param {x3dom.fields.SFVec3f} vec - vector to transform
 * @return {x3dom.fields.SFVec3f} resulting vector
 */
x3dom.fields.SFMatrix4f.prototype.multMatrixVec = function (vec) {
    return new x3dom.fields.SFVec3f(
        this._00*vec.x + this._01*vec.y + this._02*vec.z,
        this._10*vec.x + this._11*vec.y + this._12*vec.z,
        this._20*vec.x + this._21*vec.y + this._22*vec.z
    );
};

/**
 * Transforms a given 3D point, using this matrix as a transform matrix
 * (also includes projection part of matrix - required for e.g. modelview-projection matrix).
 * The resulting point is normalized by a w component.
 * @param {x3dom.fields.SFVec3f} vec - point to transform
 * @return {x3dom.fields.SFVec3f} resulting point
 */
x3dom.fields.SFMatrix4f.prototype.multFullMatrixPnt = function (vec) {
    var w = this._30*vec.x + this._31*vec.y + this._32*vec.z + this._33;
    if (w) { w = 1.0 / w; }
    return new x3dom.fields.SFVec3f(
        (this._00*vec.x + this._01*vec.y + this._02*vec.z + this._03) * w,
        (this._10*vec.x + this._11*vec.y + this._12*vec.z + this._13) * w,
        (this._20*vec.x + this._21*vec.y + this._22*vec.z + this._23) * w
    );
};


/**
 * Transforms a given 3D point, using this matrix as a transform matrix
 * (also includes projection part of matrix - required for e.g. modelview-projection matrix).
 * The resulting point is normalized by a w component.
 * @param {x3dom.fields.SFVec4f} vec - plane to transform
 * @return {x3dom.fields.SFVec4f} resulting plane
 */
x3dom.fields.SFMatrix4f.prototype.multMatrixPlane = function (plane) {

    var normal = new x3dom.fields.SFVec3f(plane.x, plane.y, plane.z);

    var memberPnt = normal.multiply(-plane.w);

    memberPnt = this.multMatrixPnt(memberPnt);

    var invTranspose = this.inverse().transpose();

    normal = invTranspose.multMatrixVec(normal);

    var d = -normal.dot(memberPnt);

    return new x3dom.fields.SFVec4f(normal.x, normal.y, normal.z, d);
};

/**
 * Returns a transposed version of this matrix.
 * @return {x3dom.fields.SFMatrix4f} resulting matrix
 */
x3dom.fields.SFMatrix4f.prototype.transpose = function () {
    return new x3dom.fields.SFMatrix4f(
        this._00, this._10, this._20, this._30,
        this._01, this._11, this._21, this._31,
        this._02, this._12, this._22, this._32,
        this._03, this._13, this._23, this._33
    );
};

/**
 * Returns a negated version of this matrix.
 * @return {x3dom.fields.SFMatrix4f} resulting matrix
 */
x3dom.fields.SFMatrix4f.prototype.negate = function () {
    return new x3dom.fields.SFMatrix4f(
        -this._00, -this._01, -this._02, -this._03,
        -this._10, -this._11, -this._12, -this._13,
        -this._20, -this._21, -this._22, -this._23,
        -this._30, -this._31, -this._32, -this._33
    );
};

/**
 * Returns a scaled version of this matrix.
 * @param {Number} s - scale factor
 * @return {x3dom.fields.SFMatrix4f} resulting matrix
 */
x3dom.fields.SFMatrix4f.prototype.multiply = function (s) {
    return new x3dom.fields.SFMatrix4f(
        s*this._00, s*this._01, s*this._02, s*this._03,
        s*this._10, s*this._11, s*this._12, s*this._13,
        s*this._20, s*this._21, s*this._22, s*this._23,
        s*this._30, s*this._31, s*this._32, s*this._33
    );
};

/**
 * Returns the result of adding the given matrix to this matrix.
 * @param {x3dom.fields.SFMatrix4f} that - the other matrix
 * @return {x3dom.fields.SFMatrix4f} resulting matrix
 */
x3dom.fields.SFMatrix4f.prototype.add = function (that) {
    return new x3dom.fields.SFMatrix4f(
        this._00+that._00, this._01+that._01, this._02+that._02, this._03+that._03,
        this._10+that._10, this._11+that._11, this._12+that._12, this._13+that._13,
        this._20+that._20, this._21+that._21, this._22+that._22, this._23+that._23,
        this._30+that._30, this._31+that._31, this._32+that._32, this._33+that._33
    );
};

/**
 * Returns the result of adding the given matrix to this matrix, using an additional scale factor for the argument matrix.
 * @param {x3dom.fields.SFMatrix4f} that - the other matrix
 * @param {Number} s - the scale factor
 * @return {x3dom.fields.SFMatrix4f} resulting matrix
 */
x3dom.fields.SFMatrix4f.prototype.addScaled = function (that, s) {
    return new x3dom.fields.SFMatrix4f(
        this._00+s*that._00, this._01+s*that._01, this._02+s*that._02, this._03+s*that._03,
        this._10+s*that._10, this._11+s*that._11, this._12+s*that._12, this._13+s*that._13,
        this._20+s*that._20, this._21+s*that._21, this._22+s*that._22, this._23+s*that._23,
        this._30+s*that._30, this._31+s*that._31, this._32+s*that._32, this._33+s*that._33
    );
};

/**
 * Fills the values of this matrix with the values of the other one.
 * @param {x3dom.fields.SFMatrix4f} that - the other matrix
 */
x3dom.fields.SFMatrix4f.prototype.setValues = function (that) {
    this._00 = that._00; this._01 = that._01; this._02 = that._02; this._03 = that._03;
    this._10 = that._10; this._11 = that._11; this._12 = that._12; this._13 = that._13;
    this._20 = that._20; this._21 = that._21; this._22 = that._22; this._23 = that._23;
    this._30 = that._30; this._31 = that._31; this._32 = that._32; this._33 = that._33;
};

/**
 * Fills the upper left 3x3 or 3x4 values of this matrix, using the given (three or four) column vectors.
 * @param {x3dom.fields.SFVec3f} v1             - first column vector
 * @param {x3dom.fields.SFVec3f} v2             - second column vector
 * @param {x3dom.fields.SFVec3f} v3             - third column vector
 * @param {x3dom.fields.SFVec3f} [v4=undefined] - fourth column vector
 */
x3dom.fields.SFMatrix4f.prototype.setValue = function (v1, v2, v3, v4) {
    this._00 = v1.x; this._01 = v2.x; this._02 = v3.x;
    this._10 = v1.y; this._11 = v2.y; this._12 = v3.y;
    this._20 = v1.z; this._21 = v2.z; this._22 = v3.z;
    this._30 = 0;    this._31 = 0;    this._32 = 0;
    
    if (arguments.length > 3) {
        this._03 = v4.x;
        this._13 = v4.y;
        this._23 = v4.z;
        this._33 = 1;
    }
};

/**
 * Fills the values of this matrix, using the given array.
 * @param {Number[]} a - array, the first 16 values will be used to initialize the matrix
 */
x3dom.fields.SFMatrix4f.prototype.setFromArray = function (a) {
    this._00 = a[0]; this._01 = a[4]; this._02 = a[ 8]; this._03 = a[12];
    this._10 = a[1]; this._11 = a[5]; this._12 = a[ 9]; this._13 = a[13];
    this._20 = a[2]; this._21 = a[6]; this._22 = a[10]; this._23 = a[14];
    this._30 = a[3]; this._31 = a[7]; this._32 = a[11]; this._33 = a[15];
};

/**
 * Returns a column major version of this matrix, packed into a single array.
 * @returns {Number[]} resulting array of 16 values
 */
x3dom.fields.SFMatrix4f.prototype.toGL = function () {
    return [
        this._00, this._10, this._20, this._30,
        this._01, this._11, this._21, this._31,
        this._02, this._12, this._22, this._32,
        this._03, this._13, this._23, this._33
    ];
};

/**
 * Returns the value of this matrix at a given position.
 * @param {Number} i - row index (starting with 0)
 * @param {Number} j - column index (starting with 0)
 * @returns {Number} the value
 */
x3dom.fields.SFMatrix4f.prototype.at = function (i, j) {
	var field = "_" + i + j;
	return this[field];
};

/**
 * Computes the square root of the matrix, assuming that its determinant is greater than zero.
 * @return {SFMatrix4f} a matrix containing the result
 */
x3dom.fields.SFMatrix4f.prototype.sqrt = function () {
    var Y = x3dom.fields.SFMatrix4f.identity();
    var result = x3dom.fields.SFMatrix4f.copy(this);
    
    for (var i=0; i<6; i++)
    {
        var iX = result.inverse();
        var iY = (i == 0) ? x3dom.fields.SFMatrix4f.identity() : Y.inverse();
        
        var rd = result.det(), yd = Y.det();
        
        var g = Math.abs( Math.pow(rd * yd, -0.125) );
        var ig = 1.0 / g;
        
        result = result.multiply(g);
        result = result.addScaled(iY, ig);
        result = result.multiply(0.5);
        
        Y = Y.multiply(g);
        Y = Y.addScaled(iX, ig);
        Y = Y.multiply(0.5);
    }
    
    return result;
};

/**
 * Returns the largest absolute value of all entries in the matrix.
 * This is only a helper for calculating log and not the usual Infinity-norm for matrices.
 * @returns {Number} the largest absolute value
 */
x3dom.fields.SFMatrix4f.prototype.normInfinity = function () {
    var t = 0, m = 0;

    if ((t = Math.abs(this._00)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._01)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._02)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._03)) > m) {
        m = t;
    }
        
    if ((t = Math.abs(this._10)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._11)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._12)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._13)) > m) {
        m = t;
    }
    
    if ((t = Math.abs(this._20)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._21)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._22)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._23)) > m) {
        m = t;
    }
        
    if ((t = Math.abs(this._30)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._31)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._32)) > m) {
        m = t;
    }
    if ((t = Math.abs(this._33)) > m) {
        m = t;
    }

    return m;
};

/**
 * Returns the 1-norm of the upper left 3x3 part of this matrix.
 * The 1-norm is also known as maximum absolute column sum norm.
 * @returns {Number} the resulting number
 */
x3dom.fields.SFMatrix4f.prototype.norm1_3x3 = function() {
    var max = Math.abs(this._00) + 
              Math.abs(this._10) +
              Math.abs(this._20);
    var t = 0;
    
    if ((t = Math.abs(this._01) +
             Math.abs(this._11) +
             Math.abs(this._21)) > max) {
        max = t;
    }
    
    if ((t = Math.abs(this._02) +
             Math.abs(this._12) +
             Math.abs(this._22)) > max) {
        max = t;
    }
    
    return max;
};

/**
 * Returns the infinity-norm of the upper left 3x3 part of this matrix.
 * The infinity-norm is also known as maximum absolute row sum norm.
 * @returns {Number} the resulting number
 */
x3dom.fields.SFMatrix4f.prototype.normInf_3x3 = function() {
    var max = Math.abs(this._00) + 
              Math.abs(this._01) +
              Math.abs(this._02);
    var t = 0;
    
    if ((t = Math.abs(this._10) +
             Math.abs(this._11) +
             Math.abs(this._12)) > max) {
        max = t;
    }
    
    if ((t = Math.abs(this._20) +
             Math.abs(this._21) +
             Math.abs(this._22)) > max) {
        max = t;
    }
    
    return max;
};

/**
 * Computes the transposed adjoint of the upper left 3x3 part of this matrix,
 * and stores it in the upper left part of a new 4x4 identity matrix.
 * @returns {x3dom.fields.SFMatrix4f} the resulting matrix
 */
x3dom.fields.SFMatrix4f.prototype.adjointT_3x3 = function () {
	var result = x3dom.fields.SFMatrix4f.identity();
	
    result._00 = this._11 * this._22 - this._12 * this._21;
    result._01 = this._12 * this._20 - this._10 * this._22;
    result._02 = this._10 * this._21 - this._11 * this._20;
    
    result._10 = this._21 * this._02 - this._22 * this._01;
    result._11 = this._22 * this._00 - this._20 * this._02;
    result._12 = this._20 * this._01 - this._21 * this._00;
    
    result._20 = this._01 * this._12 - this._02 * this._11;
    result._21 = this._02 * this._10 - this._00 * this._12;
    result._22 = this._00 * this._11 - this._01 * this._10;
	
	return result;
};

/**
 * Checks whether this matrix equals another matrix.
 * @param {x3dom.fields.SFMatrix4f} that - the other matrix
 * @returns {Boolean}
 */
x3dom.fields.SFMatrix4f.prototype.equals = function (that) {
    var eps = 0.000000000001;
    return Math.abs(this._00-that._00) < eps && Math.abs(this._01-that._01) < eps && 
           Math.abs(this._02-that._02) < eps && Math.abs(this._03-that._03) < eps &&
           Math.abs(this._10-that._10) < eps && Math.abs(this._11-that._11) < eps && 
           Math.abs(this._12-that._12) < eps && Math.abs(this._13-that._13) < eps &&
           Math.abs(this._20-that._20) < eps && Math.abs(this._21-that._21) < eps && 
           Math.abs(this._22-that._22) < eps && Math.abs(this._23-that._23) < eps &&
           Math.abs(this._30-that._30) < eps && Math.abs(this._31-that._31) < eps && 
           Math.abs(this._32-that._32) < eps && Math.abs(this._33-that._33) < eps;
};

/**
 * Decomposes the matrix into a translation, rotation, scale,
 * and scale orientation. Any projection information is discarded.
 * The decomposition depends upon choice of center point for rotation and scaling,
 * which is optional as the last parameter.
 * @param {x3dom.fields.SFVec3f} translation         - 3D vector to be filled with the translation values
 * @param {x3dom.fields.Quaternion} rotation         - quaternion to be filled with the rotation values
 * @param {x3dom.fields.SFVec3f} scaleFactor         - 3D vector to be filled with the scale factors
 * @param {x3dom.fields.Quaternion} scaleOrientation - rotation (quaternion) to be applied before scaling
 * @param {x3dom.fields.SFVec3f} [center=undefined]  - center point for rotation and scaling, if not origin
 */
x3dom.fields.SFMatrix4f.prototype.getTransform = function(
				        translation, rotation, scaleFactor, scaleOrientation, center) 
{
	var m = null;
	
	if (arguments.length > 4) {
		m = x3dom.fields.SFMatrix4f.translation(center.negate());
		m = m.mult(this);
		
		var c = x3dom.fields.SFMatrix4f.translation(center);
		m = m.mult(c);
	}
	else {
	    m = x3dom.fields.SFMatrix4f.copy(this);
	}
	
	var flip = m.decompose(translation, rotation, scaleFactor, scaleOrientation);
	
	scaleFactor.setValues(scaleFactor.multiply(flip));
};

/**
 * Computes the decomposition of the given 4x4 affine matrix M as M = T F R SO S SO^t,
 * where T is a translation matrix, F is +/- I (a reflection), R is a rotation matrix,
 * SO is a rotation matrix and S is a (nonuniform) scale matrix.
 * @param {x3dom.fields.SFVec3f} t     - 3D vector to be filled with the translation values
 * @param {x3dom.fields.Quaternion} r  - quaternion to be filled with the rotation values
 * @param {x3dom.fields.SFVec3f} s     - 3D vector to be filled with the scale factors
 * @param {x3dom.fields.Quaternion} so - rotation (quaternion) to be applied before scaling
 * @returns {Number} signum of determinant of the transposed adjoint upper 3x3 matrix
 */
x3dom.fields.SFMatrix4f.prototype.decompose = function(t, r, s, so) 
{
	var A = x3dom.fields.SFMatrix4f.copy(this);
	
    var Q  = x3dom.fields.SFMatrix4f.identity(),
		S  = x3dom.fields.SFMatrix4f.identity(),
		SO = x3dom.fields.SFMatrix4f.identity();
	
	t.x = A._03;
    t.y = A._13;
    t.z = A._23;
    
    A._03 = 0.0;
    A._13 = 0.0;
    A._23 = 0.0;
    
    A._30 = 0.0;
    A._31 = 0.0;
    A._32 = 0.0;
	
	var det = A.polarDecompose(Q, S);
    var f = 1.0;

    if (det < 0.0) {
        Q = Q.negate();
        f = -1.0;
    }
    
    r.setValue(Q);
    
    S.spectralDecompose(SO, s);
    
    so.setValue(SO);
	
	return f;
};

/**
 * Performs a polar decomposition of this matrix A into two matrices Q and S, so that A = QS
 * @param {x3dom.fields.SFMatrix4f} Q - first resulting matrix
 * @param {x3dom.fields.SFMatrix4f} S - first resulting matrix
 * @returns {Number} determinant of the transposed adjoint upper 3x3 matrix
 */
x3dom.fields.SFMatrix4f.prototype.polarDecompose = function(Q, S)
{
    var TOL = 0.000000000001;
	
    var Mk = this.transpose();
    var Ek = x3dom.fields.SFMatrix4f.identity();
	
    var Mk_one = Mk.norm1_3x3();
    var Mk_inf = Mk.normInf_3x3();
    
	var MkAdjT;
    var MkAdjT_one, MkAdjT_inf;
    var Ek_one, Mk_det;
       
    do
    {
        // compute transpose of adjoint
		MkAdjT = Mk.adjointT_3x3();
        
        // Mk_det = det(Mk) -- computed from the adjoint        
        Mk_det = Mk._00 * MkAdjT._00 + 
                 Mk._01 * MkAdjT._01 +
                 Mk._02 * MkAdjT._02;

        //TODO: should this be a close to zero test ?
        if (Mk_det == 0.0)
        {
            x3dom.debug.logWarning("polarDecompose: Mk_det == 0.0");
            break;
        }
        
        MkAdjT_one = MkAdjT.norm1_3x3();
        MkAdjT_inf = MkAdjT.normInf_3x3();
        
        // compute update factors
        var gamma = Math.sqrt( Math.sqrt((MkAdjT_one * MkAdjT_inf) / 
							  (Mk_one * Mk_inf)) / Math.abs(Mk_det) );
        
        var g1 = 0.5 * gamma;
        var g2 = 0.5 / (gamma * Mk_det);
        
        Ek.setValues(Mk);
        
        Mk = Mk.multiply (g1);         // this does:
        Mk = Mk.addScaled(MkAdjT, g2); // Mk = g1 * Mk + g2 * MkAdjT
        Ek = Ek.addScaled(Mk, -1.0);   // Ek -= Mk;
        
        Ek_one = Ek.norm1_3x3();
        Mk_one = Mk.norm1_3x3();
        Mk_inf = Mk.normInf_3x3();
        
    } while (Ek_one > (Mk_one * TOL));
    
    Q.setValues(Mk.transpose());
    S.setValues(Mk.mult(this));

    for (var i = 0; i < 3; ++i)
    {
        for (var j = i; j < 3; ++j)
        {
            S['_'+j+i] = 0.5 * (S['_'+j+i] + S['_'+i+j]);
			S['_'+i+j] = 0.5 * (S['_'+j+i] + S['_'+i+j]);
        }
    }
    
    return Mk_det;
};

/**
 * Performs a spectral decomposition of this matrix.
 * @param {x3dom.fields.SFMatrix4f} SO - resulting matrix
 * @param {x3dom.fields.SFVec3f} k - resulting vector
 */
x3dom.fields.SFMatrix4f.prototype.spectralDecompose = function(SO, k)
{
    var next = [1, 2, 0];
    var maxIterations = 20;
    var diag = [this._00, this._11, this._22];
    var offDiag = [this._12, this._20, this._01];
    
    for (var iter = 0; iter < maxIterations; ++iter)
    {
        var sm = Math.abs(offDiag[0]) + Math.abs(offDiag[1]) + Math.abs(offDiag[2]);
        
        if (sm == 0) {        
            break;
        }
        
        for (var i = 2; i >= 0; --i)
        {
            var p = next[i];
            var q = next[p];
            
            var absOffDiag = Math.abs(offDiag[i]);
            var g          = 100.0 * absOffDiag; 
            
            if (absOffDiag > 0.0)
            {
                var t = 0, h = diag[q] - diag[p];
                var absh = Math.abs(h);
                
                if (absh + g == absh)
                {
                    t = offDiag[i] / h;
                }
                else
                {
                    var theta = 0.5 * h / offDiag[i];
                    t = 1.0 / (Math.abs(theta) + Math.sqrt(theta * theta + 1.0));
                    
                    t = theta < 0.0 ? -t : t;
                }
            
                var c = 1.0 / Math.sqrt(t * t + 1.0);
                var s = t * c;
                
                var tau = s / (c + 1.0);
                var ta  = t * offDiag[i];
                
                offDiag[i] = 0.0;
                
                diag[p] -= ta;
                diag[q] += ta;
                
                var offDiagq = offDiag[q];
                
                offDiag[q] -= s * (offDiag[p] + tau * offDiagq);
                offDiag[p] += s * (offDiagq - tau * offDiag[p]);
                
                for (var j = 2; j >= 0; --j)
                {
                    var a = SO['_'+j+p];
                    var b = SO['_'+j+q];
                    
                    SO['_'+j+p] -= s * (b + tau * a);
                    SO['_'+j+q] += s * (a - tau * b);
                }
            }
        }
    }
    
    k.x = diag[0];
    k.y = diag[1];
    k.z = diag[2];
};

/**
 * Computes the logarithm of this matrix, assuming that its determinant is greater than zero.
 * @returns {x3dom.fields.SFMatrix4f} log of matrix
 */
x3dom.fields.SFMatrix4f.prototype.log = function () {
    var maxiter = 12;
    var eps = 1e-12;
    
    var A = x3dom.fields.SFMatrix4f.copy(this),
        Z = x3dom.fields.SFMatrix4f.copy(this);

    // Take repeated square roots to reduce spectral radius
    Z._00 -= 1;
    Z._11 -= 1;
    Z._22 -= 1;
    Z._33 -= 1;
    
    var k = 0;

    while (Z.normInfinity() > 0.5)
    {
        A = A.sqrt();
        Z.setValues(A);

        Z._00 -= 1;
        Z._11 -= 1;
        Z._22 -= 1;
        Z._33 -= 1;

        k++;
    }

    A._00 -= 1;
    A._11 -= 1;
    A._22 -= 1;
    A._33 -= 1;

    A = A.negate();
    Z.setValues(A);
    
    var result = x3dom.fields.SFMatrix4f.copy(A);
    var i = 1;

    while (Z.normInfinity() > eps && i < maxiter)
    {
        Z = Z.mult(A);
        i++;

        result = result.addScaled(Z, 1.0 / i);
    }
    
    return result.multiply( -(1 << k) );
};

/**
 * Computes the exponential of this matrix.
 * @returns {x3dom.fields.SFMatrix4f} exp of matrix
 */
x3dom.fields.SFMatrix4f.prototype.exp = function () {
    var q = 6;
    var A = x3dom.fields.SFMatrix4f.copy(this), 
        D = x3dom.fields.SFMatrix4f.identity(), 
        N = x3dom.fields.SFMatrix4f.identity(), 
        result = x3dom.fields.SFMatrix4f.identity();
    var k = 0, c = 1.0;

    var j = 1.0 + parseInt(Math.log(A.normInfinity() / 0.693));
    //var j = 1.0 + (Math.log(A.normInfinity() / 0.693) | 0);
    
    if (j < 0) {
        j = 0;
    }

    A = A.multiply(1.0 / (1 << j));

    for (k = 1; k <= q; k++)
    {
        c *= (q - k + 1) / (k * (2 * q - k + 1));

        result = A.mult(result);

        N = N.addScaled(result, c);

        if (k % 2) {
            D = D.addScaled(result, -c);
        }
        else {
            D = D.addScaled(result, c);
        }
    }
    
    result = D.inverse().mult(N);

    for (k = 0; k < j; k++)
    {
        result = result.mult(result);
    }
    
    return result;
};

/**
 * Computes a determinant for a 3x3 matrix m, given as values in row major order.
 * @param {Number} a1 - value of m at (0,0)
 * @param {Number} a2 - value of m at (0,1)
 * @param {Number} a3 - value of m at (0,2)
 * @param {Number} b1 - value of m at (1,0)
 * @param {Number} b2 - value of m at (1,1)
 * @param {Number} b3 - value of m at (1,2)
 * @param {Number} c1 - value of m at (2,0)
 * @param {Number} c2 - value of m at (2,1)
 * @param {Number} c3 - value of m at (2,2)
 * @returns {Number} determinant
 */
x3dom.fields.SFMatrix4f.prototype.det3 = function (a1, a2, a3, b1, b2, b3, c1, c2, c3) {
    return ((a1 * b2 * c3) + (a2 * b3 * c1) + (a3 * b1 * c2) -
            (a1 * b3 * c2) - (a2 * b1 * c3) - (a3 * b2 * c1));
};

/**
 * Computes the determinant of this matrix.
 * @returns {Number} determinant
 */
x3dom.fields.SFMatrix4f.prototype.det = function () {
    var a1 = this._00;
    var b1 = this._10;
    var c1 = this._20;
    var d1 = this._30;

    var a2 = this._01;
    var b2 = this._11;
    var c2 = this._21;
    var d2 = this._31;

    var a3 = this._02;
    var b3 = this._12;
    var c3 = this._22;
    var d3 = this._32;

    var a4 = this._03;
    var b4 = this._13;
    var c4 = this._23;
    var d4 = this._33;
    
    return (a1 * this.det3(b2, b3, b4, c2, c3, c4, d2, d3, d4) - 
            b1 * this.det3(a2, a3, a4, c2, c3, c4, d2, d3, d4) + 
            c1 * this.det3(a2, a3, a4, b2, b3, b4, d2, d3, d4) - 
            d1 * this.det3(a2, a3, a4, b2, b3, b4, c2, c3, c4));
};

/**
 * Computes the inverse of this matrix, given that it is not singular.
 * @returns {x3dom.fields.SFMatrix4f}
 */
x3dom.fields.SFMatrix4f.prototype.inverse = function () {
    var a1 = this._00;
    var b1 = this._10;
    var c1 = this._20;
    var d1 = this._30;

    var a2 = this._01;
    var b2 = this._11;
    var c2 = this._21;
    var d2 = this._31;

    var a3 = this._02;
    var b3 = this._12;
    var c3 = this._22;
    var d3 = this._32;

    var a4 = this._03;
    var b4 = this._13;
    var c4 = this._23;
    var d4 = this._33;

    var rDet = this.det();

    //if (Math.abs(rDet) < 1e-30)
    if (rDet == 0)
    {
        x3dom.debug.logWarning("Invert matrix: singular matrix, no inverse!");
        return x3dom.fields.SFMatrix4f.identity();
    }

    rDet = 1.0 / rDet;

    return new x3dom.fields.SFMatrix4f(
                +this.det3(b2, b3, b4, c2, c3, c4, d2, d3, d4) * rDet,
                -this.det3(a2, a3, a4, c2, c3, c4, d2, d3, d4) * rDet,
                +this.det3(a2, a3, a4, b2, b3, b4, d2, d3, d4) * rDet,
                -this.det3(a2, a3, a4, b2, b3, b4, c2, c3, c4) * rDet,
                -this.det3(b1, b3, b4, c1, c3, c4, d1, d3, d4) * rDet,
                +this.det3(a1, a3, a4, c1, c3, c4, d1, d3, d4) * rDet,
                -this.det3(a1, a3, a4, b1, b3, b4, d1, d3, d4) * rDet,
                +this.det3(a1, a3, a4, b1, b3, b4, c1, c3, c4) * rDet,
                +this.det3(b1, b2, b4, c1, c2, c4, d1, d2, d4) * rDet,
                -this.det3(a1, a2, a4, c1, c2, c4, d1, d2, d4) * rDet,
                +this.det3(a1, a2, a4, b1, b2, b4, d1, d2, d4) * rDet,
                -this.det3(a1, a2, a4, b1, b2, b4, c1, c2, c4) * rDet,
                -this.det3(b1, b2, b3, c1, c2, c3, d1, d2, d3) * rDet,
                +this.det3(a1, a2, a3, c1, c2, c3, d1, d2, d3) * rDet,
                -this.det3(a1, a2, a3, b1, b2, b3, d1, d2, d3) * rDet,
                +this.det3(a1, a2, a3, b1, b2, b3, c1, c2, c3) * rDet
            );
};

/**
 * Returns an array of 2*3 = 6 euler angles (in radians), assuming that this is a rotation matrix.
 * The first three and the second three values are alternatives for the three euler angles,
 * where each of the two cases leads to the same resulting rotation.
 * @returns {Number[]}
 */
x3dom.fields.SFMatrix4f.prototype.getEulerAngles = function() {
    var theta_1, theta_2, theta;
    var phi_1, phi_2, phi;
    var psi_1, psi_2, psi;
    var cos_theta_1, cos_theta_2;

    if ( Math.abs((Math.abs(this._20) - 1.0)) > 0.0001) {
        theta_1 = -Math.asin(this._20);
        theta_2 = Math.PI - theta_1;

        cos_theta_1 = Math.cos(theta_1);
        cos_theta_2 = Math.cos(theta_2);

        psi_1   = Math.atan2(this._21 / cos_theta_1, this._22 / cos_theta_1);
        psi_2   = Math.atan2(this._21 / cos_theta_2, this._22 / cos_theta_2);

        phi_1   = Math.atan2(this._10 / cos_theta_1, this._00 / cos_theta_1);
        phi_2   = Math.atan2(this._10 / cos_theta_2, this._00 / cos_theta_2);

        return [psi_1, theta_1, phi_1,
                psi_2, theta_2, phi_2];
    }
    else {
        phi = 0;

        if (this._20 == -1.0) {
            theta = Math.PI / 2.0;
            psi   = phi + Math.atan2(this._01, this._02);
        }
        else {
            theta = -(Math.PI / 2.0);
            psi   = -phi + Math.atan2(-this._01, -this._02);
        }

        return [psi, theta, phi,
                psi, theta, phi];
    }
};

/**
 * Converts this matrix to a string representation, where all entries are separated by commas,
 * and where rows are additionally separated by linebreaks.
 * @returns {String}
 */
x3dom.fields.SFMatrix4f.prototype.toString = function () {
    return '\n' +
		this._00.toFixed(6)+', '+this._01.toFixed(6)+', '+
		this._02.toFixed(6)+', '+this._03.toFixed(6)+', \n'+
        this._10.toFixed(6)+', '+this._11.toFixed(6)+', '+
		this._12.toFixed(6)+', '+this._13.toFixed(6)+', \n'+
        this._20.toFixed(6)+', '+this._21.toFixed(6)+', '+
		this._22.toFixed(6)+', '+this._23.toFixed(6)+', \n'+
        this._30.toFixed(6)+', '+this._31.toFixed(6)+', '+
		this._32.toFixed(6)+', '+this._33.toFixed(6);
};

/**
 * Fills the values of this matrix from a string, where the entries are separated
 * by commas and given in column-major order.
 * @param {String} str - the string representation
 */
x3dom.fields.SFMatrix4f.prototype.setValueByStr = function(str) {
    var needTranspose = false;
    var val = /matrix.*\((.+)\)/;
    // check if matrix is set via CSS string
    if (val.exec(str)) {
        str = RegExp.$1;
        needTranspose = true;
    }
    var arr = Array.map(str.split(/[,\s]+/), function (n) { return +n; });
    if (arr.length >= 16)
    {
        if (!needTranspose) {
            this._00 = arr[0];  this._01 = arr[1];  this._02 = arr[2];  this._03 = arr[3];
            this._10 = arr[4];  this._11 = arr[5];  this._12 = arr[6];  this._13 = arr[7];
            this._20 = arr[8];  this._21 = arr[9];  this._22 = arr[10]; this._23 = arr[11];
            this._30 = arr[12]; this._31 = arr[13]; this._32 = arr[14]; this._33 = arr[15];
        }
        else {
            this._00 = arr[0];  this._01 = arr[4];  this._02 = arr[8];  this._03 = arr[12];
            this._10 = arr[1];  this._11 = arr[5];  this._12 = arr[9];  this._13 = arr[13];
            this._20 = arr[2];  this._21 = arr[6];  this._22 = arr[10]; this._23 = arr[14];
            this._30 = arr[3];  this._31 = arr[7];  this._32 = arr[11]; this._33 = arr[15];
        }
    }
    else if (arr.length === 6) {
        this._00 = arr[0]; this._01 = arr[1]; this._02 = 0; this._03 = arr[4];
        this._10 = arr[2]; this._11 = arr[3]; this._12 = 0; this._13 = arr[5];
        this._20 = 0; this._21 = 0; this._22 = 1; this._23 = 0;
        this._30 = 0; this._31 = 0; this._32 = 0; this._33 = 1;
    }
    else {
        x3dom.debug.logWarning("SFMatrix4f - can't parse string: " + str);
    }
    return this;
};


///////////////////////////////////////////////////////////////////////////////
/** SFVec2f constructor.
    @class Represents a SFVec2f
  */
x3dom.fields.SFVec2f = function(x, y) {
    if (arguments.length === 0) {
        this.x = 0;
        this.y = 0;
    }
    else {
        this.x = x;
        this.y = y;
    }
};


x3dom.fields.SFVec2f.copy = function(v) {
    return new x3dom.fields.SFVec2f(v.x, v.y);
};

x3dom.fields.SFVec2f.parse = function (str) {
    var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
    return new x3dom.fields.SFVec2f(+m[1], +m[2]);
};

x3dom.fields.SFVec2f.prototype.copy = function() {
    return x3dom.fields.SFVec2f.copy(this);
};

x3dom.fields.SFVec2f.prototype.setValues = function (that) {
    this.x = that.x;
    this.y = that.y;
};

x3dom.fields.SFVec2f.prototype.at = function (i) {
	switch(i) {
	    case 0:  return this.x;
	    case 1:  return this.y;
	    default: return this.x;
	}
};

x3dom.fields.SFVec2f.prototype.add = function (that) {
    return new x3dom.fields.SFVec2f(this.x+that.x, this.y+that.y);
};

x3dom.fields.SFVec2f.prototype.subtract = function (that) {
    return new x3dom.fields.SFVec2f(this.x-that.x, this.y-that.y);
};

x3dom.fields.SFVec2f.prototype.negate = function () {
    return new x3dom.fields.SFVec2f(-this.x, -this.y);
};

x3dom.fields.SFVec2f.prototype.dot = function (that) {
    return this.x * that.x + this.y * that.y;
};

x3dom.fields.SFVec2f.prototype.reflect = function (n) {
    var d2 = this.dot(n)*2;
    return new x3dom.fields.SFVec2f(this.x-d2*n.x, this.y-d2*n.y);
};

x3dom.fields.SFVec2f.prototype.normalize = function() {
    var n = this.length();
    if (n) { n = 1.0 / n; }
    return new x3dom.fields.SFVec2f(this.x*n, this.y*n);
};

x3dom.fields.SFVec2f.prototype.multComponents = function (that) {
    return new x3dom.fields.SFVec2f(this.x*that.x, this.y*that.y);
};

x3dom.fields.SFVec2f.prototype.multiply = function (n) {
    return new x3dom.fields.SFVec2f(this.x*n, this.y*n);
};

x3dom.fields.SFVec2f.prototype.divideComponents = function (that) {
    return new x3dom.fields.SFVec2f(this.x/that.x, this.y/that.y);
};

x3dom.fields.SFVec2f.prototype.divide = function (n) {
    var denom = n ? (1.0 / n) : 1.0;
    return new x3dom.fields.SFVec2f(this.x*denom, this.y*denom);
};

x3dom.fields.SFVec2f.prototype.equals = function (that, eps) {
    return Math.abs(this.x - that.x) < eps && 
           Math.abs(this.y - that.y) < eps;
};

x3dom.fields.SFVec2f.prototype.length = function() {
    return Math.sqrt((this.x*this.x) + (this.y*this.y));
};

x3dom.fields.SFVec2f.prototype.toGL = function () {
    return [ this.x, this.y ];
};

x3dom.fields.SFVec2f.prototype.toString = function () {
    return this.x + " " + this.y;
};

x3dom.fields.SFVec2f.prototype.setValueByStr = function(str) {
    var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
    this.x = +m[1];
    this.y = +m[2];
    return this;
};


///////////////////////////////////////////////////////////////////////////////
/** SFVec3f constructor.
    @class Represents a SFVec3f
  */
x3dom.fields.SFVec3f = function(x, y, z) {
    if (arguments.length === 0) {
        this.x = 0;
        this.y = 0;
        this.z = 0;
    }
    else {
        this.x = x;
        this.y = y;
        this.z = z;
    }
};

x3dom.fields.SFVec3f.NullVector = new x3dom.fields.SFVec3f(0, 0, 0);
x3dom.fields.SFVec3f.OneVector  = new x3dom.fields.SFVec3f(1, 1, 1);

x3dom.fields.SFVec3f.copy = function(v) {
    return new x3dom.fields.SFVec3f(v.x, v.y, v.z);
};

x3dom.fields.SFVec3f.MIN = function() {
    return new x3dom.fields.SFVec3f(-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE);
};

x3dom.fields.SFVec3f.MAX = function() {
    return new x3dom.fields.SFVec3f(Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE);
};

x3dom.fields.SFVec3f.parse = function (str) {
    try {
        var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
        return new x3dom.fields.SFVec3f(+m[1], +m[2], +m[3]);
    }
    catch (e) {
        // allow automatic type conversion as is convenient for shaders
        var c = x3dom.fields.SFColor.colorParse(str);
        return new x3dom.fields.SFVec3f(c.r, c.g, c.b);
    }
};

x3dom.fields.SFVec3f.prototype.copy = function() {
    return x3dom.fields.SFVec3f.copy(this);
};

x3dom.fields.SFVec3f.prototype.setValues = function (that) {
    this.x = that.x;
    this.y = that.y;
    this.z = that.z;   
};

x3dom.fields.SFVec3f.prototype.at = function (i) {
	switch(i) {
	    case 0:  return this.x;
	    case 1:  return this.y;
	    case 2:  return this.z;
	    default: return this.x;
	}
};

x3dom.fields.SFVec3f.prototype.add = function (that) {
    return new x3dom.fields.SFVec3f(this.x + that.x, this.y + that.y, this.z + that.z);
};

x3dom.fields.SFVec3f.prototype.addScaled = function (that, s) {
    return new x3dom.fields.SFVec3f(this.x + s*that.x, this.y + s*that.y, this.z + s*that.z);
};

x3dom.fields.SFVec3f.prototype.subtract = function (that) {
    return new x3dom.fields.SFVec3f(this.x - that.x, this.y - that.y, this.z - that.z);
};

x3dom.fields.SFVec3f.prototype.negate = function () {
    return new x3dom.fields.SFVec3f(-this.x, -this.y, -this.z);
};

x3dom.fields.SFVec3f.prototype.dot = function (that) {
    return (this.x*that.x + this.y*that.y + this.z*that.z);
};

x3dom.fields.SFVec3f.prototype.cross = function (that) {
    return new x3dom.fields.SFVec3f( this.y*that.z - this.z*that.y, 
                                     this.z*that.x - this.x*that.z,
                                     this.x*that.y - this.y*that.x );
};

x3dom.fields.SFVec3f.prototype.reflect = function (n) {
    var d2 = this.dot(n)*2;
    return new x3dom.fields.SFVec3f(this.x - d2*n.x, this.y - d2*n.y, this.z - d2*n.z);
};

x3dom.fields.SFVec3f.prototype.length = function() {
    return Math.sqrt((this.x*this.x) + (this.y*this.y) + (this.z*this.z));
};

x3dom.fields.SFVec3f.prototype.normalize = function() {
    var n = this.length();
    if (n) { n = 1.0 / n; }
    return new x3dom.fields.SFVec3f(this.x*n, this.y*n, this.z*n);
};

x3dom.fields.SFVec3f.prototype.multComponents = function (that) {
    return new x3dom.fields.SFVec3f(this.x*that.x, this.y*that.y, this.z*that.z);
};

x3dom.fields.SFVec3f.prototype.multiply = function (n) {
    return new x3dom.fields.SFVec3f(this.x*n, this.y*n, this.z*n);
};

x3dom.fields.SFVec3f.prototype.divide = function (n) {
    var denom = n ? (1.0 / n) : 1.0;
    return new x3dom.fields.SFVec3f(this.x*denom, this.y*denom, this.z*denom);
};

x3dom.fields.SFVec3f.prototype.equals = function (that, eps) {
    return Math.abs(this.x - that.x) < eps && 
           Math.abs(this.y - that.y) < eps &&
           Math.abs(this.z - that.z) < eps;
};

x3dom.fields.SFVec3f.prototype.toGL = function () {
    return [ this.x, this.y, this.z ];
};

x3dom.fields.SFVec3f.prototype.toString = function () {
    return this.x + " " + this.y + " " + this.z;
};

x3dom.fields.SFVec3f.prototype.setValueByStr = function(str) {
    try {
        var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
        this.x = +m[1];
        this.y = +m[2];
        this.z = +m[3];
    }
    catch (e) {
        // allow automatic type conversion as is convenient for shaders
        var c = x3dom.fields.SFColor.colorParse(str);
        this.x = c.r;
        this.y = c.g;
        this.z = c.b;
    }
    return this;
};


///////////////////////////////////////////////////////////////////////////////
/** SFVec4f constructor.
    @class Represents a SFVec4f
  */
x3dom.fields.SFVec4f = function(x, y, z, w) {
    if (arguments.length === 0) {
        this.x = 0;
        this.y = 0;
        this.z = 0;
        this.w = 0;
    }
    else {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }
};

x3dom.fields.SFVec4f.copy = function(v) {
    return new x3dom.fields.SFVec4f(v.x, v.y, v.z, v.w);
};


x3dom.fields.SFVec4f.prototype.copy = function() {
    return x3dom.fields.SFVec4f(this);
};

x3dom.fields.SFVec4f.parse = function (str) {
    var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
    return new x3dom.fields.SFVec4f(+m[1], +m[2], +m[3], +m[4]);
};

x3dom.fields.SFVec4f.prototype.setValueByStr = function(str) {
    var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
    this.x = +m[1];
    this.y = +m[2];
    this.z = +m[3];
    this.w = +m[4];
    return this;
};

x3dom.fields.SFVec4f.prototype.toGL = function () {
    return [ this.x, this.y, this.z, this.w ];
};

x3dom.fields.SFVec4f.prototype.toString = function () {
    return this.x + " " + this.y + " " + this.z + " " + this.w;
};


///////////////////////////////////////////////////////////////////////////////
/** Quaternion constructor.
    @class Represents a Quaternion
  */
x3dom.fields.Quaternion = function(x, y, z, w) {
    if (arguments.length === 0) {
        this.x = 0;
        this.y = 0;
        this.z = 0;
        this.w = 1;
    }
    else {
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
    }
};

x3dom.fields.Quaternion.copy = function(v) {
    return new x3dom.fields.Quaternion(v.x, v.y, v.z, v.w);
};

x3dom.fields.Quaternion.prototype.multiply = function (that) {
    return new x3dom.fields.Quaternion(
        this.w*that.x + this.x*that.w + this.y*that.z - this.z*that.y,
        this.w*that.y + this.y*that.w + this.z*that.x - this.x*that.z,
        this.w*that.z + this.z*that.w + this.x*that.y - this.y*that.x,
        this.w*that.w - this.x*that.x - this.y*that.y - this.z*that.z
    );
};

x3dom.fields.Quaternion.parseAxisAngle = function (str) {
    var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
    return x3dom.fields.Quaternion.axisAngle(new x3dom.fields.SFVec3f(+m[1], +m[2], +m[3]), +m[4]);
};

x3dom.fields.Quaternion.axisAngle = function (axis, a) {
    var t = axis.length();

    if (t > x3dom.fields.Eps)
    {
        var s = Math.sin(a/2) / t;
        var c = Math.cos(a/2);
        return new x3dom.fields.Quaternion(axis.x*s, axis.y*s, axis.z*s, c);
    }
    else
    {
        return new x3dom.fields.Quaternion(0, 0, 0, 1);
    }
};

x3dom.fields.Quaternion.prototype.copy = function() {
    return x3dom.fields.Quaternion.copy(this);
};

x3dom.fields.Quaternion.prototype.toMatrix = function () {
    var xx = this.x * this.x;
    var xy = this.x * this.y;
    var xz = this.x * this.z;
    var yy = this.y * this.y;
    var yz = this.y * this.z;
    var zz = this.z * this.z;
    var wx = this.w * this.x;
    var wy = this.w * this.y;
    var wz = this.w * this.z;

    return new x3dom.fields.SFMatrix4f(
        1 - 2 * (yy + zz), 2 * (xy - wz), 2 * (xz + wy), 0,
        2 * (xy + wz), 1 - 2 * (xx + zz), 2 * (yz - wx), 0,
        2 * (xz - wy), 2 * (yz + wx), 1 - 2 * (xx + yy), 0,
        0, 0, 0, 1
    );
};

x3dom.fields.Quaternion.prototype.toAxisAngle = function()
{
    var x = 0, y = 0, z = 0;
    var s = 0, a = 0;
    var that = this;
    
    if ( this.w > 1 )
    {
        that = x3dom.fields.Quaternion.normalize( this );
    }
    
    a = 2 * Math.acos( that.w );
    s = Math.sqrt( 1 - that.w * that.w );
    
    if ( s == 0 ) //< x3dom.fields.Eps )
    {
        x = that.x;
        y = that.y;
        z = that.z;
    }
    else
    {
        x = that.x / s;
        y = that.y / s;
        z = that.z / s;
    }
    
    return [ new x3dom.fields.SFVec3f(x,y,z), a ];
};

x3dom.fields.Quaternion.prototype.angle = function()
{
    return 2 * Math.acos(this.w);
};

x3dom.fields.Quaternion.prototype.setValue = function(matrix)
{
    var tr, s = 1;
    var qt = [0, 0, 0];

    var i = 0, j = 0, k = 0;
    var nxt = [1, 2, 0];

    tr = matrix._00 + matrix._11 + matrix._22;
	
    if (tr > 0.0)
    {
        s = Math.sqrt(tr + 1.0);

        this.w = s * 0.5;

        s = 0.5 / s;

        this.x = (matrix._21 - matrix._12) * s;
        this.y = (matrix._02 - matrix._20) * s;
        this.z = (matrix._10 - matrix._01) * s;
    }
    else
    {
        if (matrix._11 > matrix._00) {
            i = 1;
		}
        else {
            i = 0;
		}

        if (matrix._22 > matrix.at(i, i)) {
            i = 2;
		}

        j = nxt[i];
        k = nxt[j];

        s = Math.sqrt(matrix.at(i, i) - (matrix.at(j, j) + matrix.at(k, k)) + 1.0);

        qt[i] = s * 0.5;
        s     = 0.5 / s;

        this.w = (matrix.at(k, j) - matrix.at(j, k)) * s;

        qt[j] = (matrix.at(j, i) + matrix.at(i, j)) * s;
        qt[k] = (matrix.at(k, i) + matrix.at(i, k)) * s;

        this.x = qt[0];
        this.y = qt[1];
        this.z = qt[2];
    }

    if (this.w > 1.0 || this.w < -1.0)
    {
        var errThreshold = 1 + (x3dom.fields.Eps * 100);

        if (this.w > errThreshold || this.w < -errThreshold)
        {
			// When copying, then everything, incl. the famous OpenSG MatToQuat bug
            x3dom.debug.logInfo("MatToQuat: BUG: |quat[4]| (" + this.w +") >> 1.0 !");
        }

        if (this.w > 1.0) {
            this.w = 1.0;
        }
        else {
            this.w = -1.0;
        }
    }
};

x3dom.fields.Quaternion.prototype.setFromEuler = function (alpha, beta, gamma) {
    var sx = Math.sin(alpha * 0.5);
    var cx = Math.cos(alpha * 0.5);
    var sy = Math.sin(beta  * 0.5);
    var cy = Math.cos(beta  * 0.5);
    var sz = Math.sin(gamma * 0.5);
    var cz = Math.cos(gamma * 0.5);

    this.x = (sx * cy * cz) - (cx * sy * sz);
    this.y = (cx * sy * cz) + (sx * cy * sz);
    this.z = (cx * cy * sz) - (sx * sy * cz);
    this.w = (cx * cy * cz) + (sx * sy * sz);
};

x3dom.fields.Quaternion.prototype.dot = function (that) {
    return this.x*that.x + this.y*that.y + this.z*that.z + this.w*that.w;
};

x3dom.fields.Quaternion.prototype.add = function (that) {
    return new x3dom.fields.Quaternion(this.x + that.x, this.y + that.y, this.z + that.z, this.w + that.w);
};

x3dom.fields.Quaternion.prototype.subtract = function (that) {
    return new x3dom.fields.Quaternion(this.x - that.x, this.y - that.y, this.z - that.z, this.w - that.w);
};

x3dom.fields.Quaternion.prototype.setValues = function (that) { 
    this.x = that.x;
    this.y = that.y;
    this.z = that.z;
    this.w = that.w;
};

x3dom.fields.Quaternion.prototype.equals = function (that, eps) {
    return (this.dot(that) >= 1.0 - eps);
};

x3dom.fields.Quaternion.prototype.multScalar = function (s) {
    return new x3dom.fields.Quaternion(this.x*s, this.y*s, this.z*s, this.w*s);
};

x3dom.fields.Quaternion.prototype.normalize = function (that) {
    var d2 = this.dot(that);
    var id = 1.0;
    if (d2) { id = 1.0 / Math.sqrt(d2); }
    return new x3dom.fields.Quaternion(this.x*id, this.y*id, this.z*id, this.w*id);
};

x3dom.fields.Quaternion.prototype.negate = function() {
    return new x3dom.fields.Quaternion(-this.x, -this.y, -this.z, -this.w);
};

x3dom.fields.Quaternion.prototype.inverse = function () {
    return new x3dom.fields.Quaternion(-this.x, -this.y, -this.z, this.w);
};

x3dom.fields.Quaternion.prototype.slerp = function (that, t) {
    // calculate the cosine
    var cosom = this.dot(that);
    var rot1;

    // adjust signs if necessary
    if (cosom < 0.0)
    {
        cosom = -cosom;
        rot1 = that.negate();
    }
    else
    {
        rot1 = new x3dom.fields.Quaternion(that.x, that.y, that.z, that.w);
    }

    // calculate interpolating coeffs
    var scalerot0, scalerot1;
    
    if ((1.0 - cosom) > 0.00001)
    {
        // standard case
        var omega = Math.acos(cosom);
        var sinom = Math.sin(omega);
        scalerot0 = Math.sin((1.0 - t) * omega) / sinom;
        scalerot1 = Math.sin(t * omega) / sinom;
    }
    else
    {
        // rot0 and rot1 very close - just do linear interp.
        scalerot0 = 1.0 - t;
        scalerot1 = t;
    }

    // build the new quaternion
    return this.multScalar(scalerot0).add(rot1.multScalar(scalerot1));
};

x3dom.fields.Quaternion.rotateFromTo = function (fromVec, toVec) {
    var from = fromVec.normalize();
    var to   = toVec.normalize();
    var cost = from.dot(to);

    // check for degeneracies
    if (cost > 0.99999)
    {
        // vectors are parallel
        return new x3dom.fields.Quaternion(0, 0, 0, 1);
    }
    else if (cost < -0.99999)
    {
        // vectors are opposite
        // find an axis to rotate around, which should be
        // perpendicular to the original axis
        // Try cross product with (1,0,0) first, if that's one of our
        // original vectors then try  (0,1,0).
        var cAxis = new x3dom.fields.SFVec3f(1, 0, 0);

        var tmp = from.cross(cAxis);

        if (tmp.length() < 0.00001)
        {
            cAxis.x = 0;
            cAxis.y = 1;
            cAxis.z = 0;

            tmp = from.cross(cAxis);
        }
        tmp = tmp.normalize();

        return x3dom.fields.Quaternion.axisAngle(tmp, Math.PI);
    }

    var axis = fromVec.cross(toVec);
    axis = axis.normalize();

    // use half-angle formulae
    // sin^2 t = ( 1 - cos (2t) ) / 2
    var s = Math.sqrt(0.5 * (1.0 - cost));
    axis = axis.multiply(s);

    // scale the axis by the sine of half the rotation angle to get
    // the normalized quaternion
    // cos^2 t = ( 1 + cos (2t) ) / 2
    // w part is cosine of half the rotation angle
    s = Math.sqrt(0.5 * (1.0 + cost));
    
    return new x3dom.fields.Quaternion(axis.x, axis.y, axis.z, s);
};

x3dom.fields.Quaternion.prototype.toGL = function () {
    var val = this.toAxisAngle();
    return [ val[0].x, val[0].y, val[0].z, val[1] ];
};

x3dom.fields.Quaternion.prototype.toString = function () {
    return this.x + " " + this.y + " " + this.z + ", " + this.w;
};

x3dom.fields.Quaternion.prototype.setValueByStr = function(str) {
    var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
    var quat = x3dom.fields.Quaternion.axisAngle(new x3dom.fields.SFVec3f(+m[1], +m[2], +m[3]), +m[4]);
    this.x = quat.x;
    this.y = quat.y;
    this.z = quat.z;
    this.w = quat.w;
    return this;
};


///////////////////////////////////////////////////////////////////////////////
/** SFColor constructor.
    @class Represents a SFColor
  */
x3dom.fields.SFColor = function(r, g, b) {
    if (arguments.length === 0) {
        this.r = 0;
        this.g = 0;
        this.b = 0;
    }
    else {
        this.r = r;
        this.g = g;
        this.b = b;
    }
};

x3dom.fields.SFColor.parse = function(str) {
    try {
        var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
        return new x3dom.fields.SFColor( +m[1], +m[2], +m[3] );
    }
    catch (e) {
        return x3dom.fields.SFColor.colorParse(str);
    }
};

x3dom.fields.SFColor.copy = function(that) {
    return new x3dom.fields.SFColor(that.r, that.g, that.b);
};

x3dom.fields.SFColor.prototype.copy = function() {
    return x3dom.fields.SFColor.copy(this);
};

x3dom.fields.SFColor.prototype.setHSV = function (h, s, v) {
    x3dom.debug.logWarning("SFColor.setHSV() NYI");
};

x3dom.fields.SFColor.prototype.getHSV = function () {
    var h = 0, s = 0, v = 0;
    x3dom.debug.logWarning("SFColor.getHSV() NYI");
    return [ h, s, v ];
};

x3dom.fields.SFColor.prototype.setValues = function (color) {
    this.r = color.r;
    this.g = color.g;
    this.b = color.b;   
};

x3dom.fields.SFColor.prototype.equals = function (that, eps) {
    return Math.abs(this.r - that.r) < eps && 
           Math.abs(this.g - that.g) < eps &&
           Math.abs(this.b - that.b) < eps;
};

x3dom.fields.SFColor.prototype.add = function (that) {
    return new x3dom.fields.SFColor(this.r + that.r, this.g + that.g, this.b + that.b);
};

x3dom.fields.SFColor.prototype.subtract = function (that) {
    return new x3dom.fields.SFColor(this.r - that.r, this.g - that.g, this.b - that.b);
};

x3dom.fields.SFColor.prototype.multiply = function (n) {
    return new x3dom.fields.SFColor(this.r*n, this.g*n, this.b*n);
};

x3dom.fields.SFColor.prototype.toGL = function () {
    return [ this.r, this.g, this.b ];
};

x3dom.fields.SFColor.prototype.toString = function() {
    return this.r + " " + this.g + " " + this.b;
};

x3dom.fields.SFColor.prototype.setValueByStr = function(str) {
    try {
        var m = /^\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*$/.exec(str);
        this.r = +m[1];
        this.g = +m[2];
        this.b = +m[3];
    }
    catch (e) {
        var c = x3dom.fields.SFColor.colorParse(str);
        this.r = c.r;
        this.g = c.g;
        this.b = c.b;
    }
    return this;
};

x3dom.fields.SFColor.colorParse = function(color) {
    var red = 0, green = 0, blue = 0;
    
    // definition of css color names
    var color_names = {
        aliceblue: 'f0f8ff',    antiquewhite: 'faebd7', aqua: '00ffff',
        aquamarine: '7fffd4',   azure: 'f0ffff',        beige: 'f5f5dc',
        bisque: 'ffe4c4',       black: '000000',        blanchedalmond: 'ffebcd',
        blue: '0000ff',         blueviolet: '8a2be2',   brown: 'a52a2a',
        burlywood: 'deb887',    cadetblue: '5f9ea0',    chartreuse: '7fff00',
        chocolate: 'd2691e',    coral: 'ff7f50',        cornflowerblue: '6495ed',
        cornsilk: 'fff8dc',     crimson: 'dc143c',      cyan: '00ffff',
        darkblue: '00008b',     darkcyan: '008b8b',     darkgoldenrod: 'b8860b',
        darkgray: 'a9a9a9',     darkgreen: '006400',    darkkhaki: 'bdb76b',
        darkmagenta: '8b008b',  darkolivegreen: '556b2f',darkorange: 'ff8c00',
        darkorchid: '9932cc',   darkred: '8b0000',      darksalmon: 'e9967a',
        darkseagreen: '8fbc8f', darkslateblue: '483d8b',darkslategray: '2f4f4f',
        darkturquoise: '00ced1',darkviolet: '9400d3',   deeppink: 'ff1493',
        deepskyblue: '00bfff',  dimgray: '696969',      dodgerblue: '1e90ff',
        feldspar: 'd19275',     firebrick: 'b22222',    floralwhite: 'fffaf0',
        forestgreen: '228b22',  fuchsia: 'ff00ff',      gainsboro: 'dcdcdc',
        ghostwhite: 'f8f8ff',   gold: 'ffd700',         goldenrod: 'daa520',
        gray: '808080',         green: '008000',        greenyellow: 'adff2f',
        honeydew: 'f0fff0',     hotpink: 'ff69b4',      indianred : 'cd5c5c',
        indigo : '4b0082',      ivory: 'fffff0',        khaki: 'f0e68c',
        lavender: 'e6e6fa',     lavenderblush: 'fff0f5',lawngreen: '7cfc00',
        lemonchiffon: 'fffacd', lightblue: 'add8e6',    lightcoral: 'f08080',
        lightcyan: 'e0ffff',    lightgoldenrodyellow: 'fafad2', lightgrey: 'd3d3d3',
        lightgreen: '90ee90',   lightpink: 'ffb6c1',    lightsalmon: 'ffa07a',
        lightseagreen: '20b2aa',lightskyblue: '87cefa', lightslateblue: '8470ff',
        lightslategray: '778899',lightsteelblue: 'b0c4de',lightyellow: 'ffffe0',
        lime: '00ff00',         limegreen: '32cd32',    linen: 'faf0e6',
        magenta: 'ff00ff',      maroon: '800000',       mediumaquamarine: '66cdaa',
        mediumblue: '0000cd',   mediumorchid: 'ba55d3', mediumpurple: '9370d8',
        mediumseagreen: '3cb371',mediumslateblue: '7b68ee', mediumspringgreen: '00fa9a',
        mediumturquoise: '48d1cc',mediumvioletred: 'c71585',midnightblue: '191970',
        mintcream: 'f5fffa',    mistyrose: 'ffe4e1',    moccasin: 'ffe4b5',
        navajowhite: 'ffdead',  navy: '000080',         oldlace: 'fdf5e6',
        olive: '808000',        olivedrab: '6b8e23',    orange: 'ffa500',
        orangered: 'ff4500',    orchid: 'da70d6',       palegoldenrod: 'eee8aa',
        palegreen: '98fb98',    paleturquoise: 'afeeee',palevioletred: 'd87093',
        papayawhip: 'ffefd5',   peachpuff: 'ffdab9',    peru: 'cd853f',
        pink: 'ffc0cb',         plum: 'dda0dd',         powderblue: 'b0e0e6',
        purple: '800080',       red: 'ff0000',          rosybrown: 'bc8f8f',
        royalblue: '4169e1',    saddlebrown: '8b4513',  salmon: 'fa8072',
        sandybrown: 'f4a460',   seagreen: '2e8b57',     seashell: 'fff5ee',
        sienna: 'a0522d',       silver: 'c0c0c0',       skyblue: '87ceeb',
        slateblue: '6a5acd',    slategray: '708090',    snow: 'fffafa',
        springgreen: '00ff7f',  steelblue: '4682b4',    tan: 'd2b48c',
        teal: '008080',         thistle: 'd8bfd8',      tomato: 'ff6347',
        turquoise: '40e0d0',    violet: 'ee82ee',       violetred: 'd02090',
        wheat: 'f5deb3',        white: 'ffffff',        whitesmoke: 'f5f5f5',
        yellow: 'ffff00',       yellowgreen: '9acd32'
    };
    
    if (color_names[color]) {
        // first check if color is given as colorname
        color = "#" + color_names[color];
    }
    
    if (color.substr && color.substr(0,1) === "#") {
        color = color.substr(1);
        var len = color.length;
        
        if (len === 6) {
            red   = parseInt("0x"+color.substr(0,2), 16) / 255.0;
            green = parseInt("0x"+color.substr(2,2), 16) / 255.0;
            blue  = parseInt("0x"+color.substr(4,2), 16) / 255.0;
        }
        else if (len === 3) {
            red   = parseInt("0x"+color.substr(0,1), 16) / 15.0;
            green = parseInt("0x"+color.substr(1,1), 16) / 15.0;
            blue  = parseInt("0x"+color.substr(2,1), 16) / 15.0;
        }
    }
    
    return new x3dom.fields.SFColor( red, green, blue );
};


///////////////////////////////////////////////////////////////////////////////
/** SFColorRGBA constructor.
    @class Represents a SFColorRGBA
  */
x3dom.fields.SFColorRGBA = function(r, g, b, a) {
    if (arguments.length === 0) {
        this.r = 0;
        this.g = 0;
        this.b = 0;
        this.a = 1;
    }
    else {
        this.r = r;
        this.g = g;
        this.b = b;
        this.a = a;
    }    
};

x3dom.fields.SFColorRGBA.parse = function(str) {
    try {
        var m = /^([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)$/.exec(str);
        return new x3dom.fields.SFColorRGBA( +m[1], +m[2], +m[3], +m[4] );
    }
    catch (e) {
        return x3dom.fields.SFColorRGBA.colorParse(str);
    }
};

x3dom.fields.SFColorRGBA.copy = function(that) {
    return new x3dom.fields.SFColorRGBA(that.r, that.g, that.b, that.a);
};

x3dom.fields.SFColorRGBA.prototype.copy = function() {
    return x3dom.fields.SFColorRGBA.copy(this);
};

x3dom.fields.SFColorRGBA.prototype.setValues = function (color) {
    this.r = color.r;
    this.g = color.g;
    this.b = color.b;   
    this.a = color.a;   
};

x3dom.fields.SFColorRGBA.prototype.equals = function (that, eps) {
    return Math.abs(this.r - that.r) < eps && 
           Math.abs(this.g - that.g) < eps &&
           Math.abs(this.b - that.b) < eps &&
           Math.abs(this.a - that.a) < eps;
};

x3dom.fields.SFColorRGBA.prototype.toGL = function () {
    return [ this.r, this.g, this.b, this.a ];
};

x3dom.fields.SFColorRGBA.prototype.toString = function() {
    return this.r + " " + this.g + " " + this.b + " " + this.a;
};

x3dom.fields.SFColorRGBA.prototype.setValueByStr = function(str) {
    try {
        var m = /^([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)\s*,?\s*([+\-]?\d*\.*\d*[eE]?[+\-]?\d*?)$/.exec(str);
        this.r = +m[1];
        this.g = +m[2];
        this.b = +m[3];
        this.a = +m[4];
    }
    catch (e) {
        var c = x3dom.fields.SFColorRGBA.colorParse(str);
        this.r = c.r;
        this.g = c.g;
        this.b = c.b;
        this.a = c.a;
    }
    return this;
};

x3dom.fields.SFColorRGBA.prototype.toUint = function() {
    return ((Math.round(this.r * 255) << 24) |
            (Math.round(this.g * 255) << 16) |
            (Math.round(this.b * 255) << 8) |
             Math.round(this.a * 255)) >>> 0;
};

x3dom.fields.SFColorRGBA.colorParse = function(color) {
    var red = 0, green = 0, blue = 0, alpha = 0;

    // definition of css color names
    var color_names = {
        aliceblue: 'f0f8ff',    antiquewhite: 'faebd7', aqua: '00ffff',
        aquamarine: '7fffd4',   azure: 'f0ffff',        beige: 'f5f5dc',
        bisque: 'ffe4c4',       black: '000000',        blanchedalmond: 'ffebcd',
        blue: '0000ff',         blueviolet: '8a2be2',   brown: 'a52a2a',
        burlywood: 'deb887',    cadetblue: '5f9ea0',    chartreuse: '7fff00',
        chocolate: 'd2691e',    coral: 'ff7f50',        cornflowerblue: '6495ed',
        cornsilk: 'fff8dc',     crimson: 'dc143c',      cyan: '00ffff',
        darkblue: '00008b',     darkcyan: '008b8b',     darkgoldenrod: 'b8860b',
        darkgray: 'a9a9a9',     darkgreen: '006400',    darkkhaki: 'bdb76b',
        darkmagenta: '8b008b',  darkolivegreen: '556b2f',darkorange: 'ff8c00',
        darkorchid: '9932cc',   darkred: '8b0000',      darksalmon: 'e9967a',
        darkseagreen: '8fbc8f', darkslateblue: '483d8b',darkslategray: '2f4f4f',
        darkturquoise: '00ced1',darkviolet: '9400d3',   deeppink: 'ff1493',
        deepskyblue: '00bfff',  dimgray: '696969',      dodgerblue: '1e90ff',
        feldspar: 'd19275',     firebrick: 'b22222',    floralwhite: 'fffaf0',
        forestgreen: '228b22',  fuchsia: 'ff00ff',      gainsboro: 'dcdcdc',
        ghostwhite: 'f8f8ff',   gold: 'ffd700',         goldenrod: 'daa520',
        gray: '808080',         green: '008000',        greenyellow: 'adff2f',
        honeydew: 'f0fff0',     hotpink: 'ff69b4',      indianred : 'cd5c5c',
        indigo : '4b0082',      ivory: 'fffff0',        khaki: 'f0e68c',
        lavender: 'e6e6fa',     lavenderblush: 'fff0f5',lawngreen: '7cfc00',
        lemonchiffon: 'fffacd', lightblue: 'add8e6',    lightcoral: 'f08080',
        lightcyan: 'e0ffff',    lightgoldenrodyellow: 'fafad2', lightgrey: 'd3d3d3',
        lightgreen: '90ee90',   lightpink: 'ffb6c1',    lightsalmon: 'ffa07a',
        lightseagreen: '20b2aa',lightskyblue: '87cefa', lightslateblue: '8470ff',
        lightslategray: '778899',lightsteelblue: 'b0c4de',lightyellow: 'ffffe0',
        lime: '00ff00',         limegreen: '32cd32',    linen: 'faf0e6',
        magenta: 'ff00ff',      maroon: '800000',       mediumaquamarine: '66cdaa',
        mediumblue: '0000cd',   mediumorchid: 'ba55d3', mediumpurple: '9370d8',
        mediumseagreen: '3cb371',mediumslateblue: '7b68ee', mediumspringgreen: '00fa9a',
        mediumturquoise: '48d1cc',mediumvioletred: 'c71585',midnightblue: '191970',
        mintcream: 'f5fffa',    mistyrose: 'ffe4e1',    moccasin: 'ffe4b5',
        navajowhite: 'ffdead',  navy: '000080',         oldlace: 'fdf5e6',
        olive: '808000',        olivedrab: '6b8e23',    orange: 'ffa500',
        orangered: 'ff4500',    orchid: 'da70d6',       palegoldenrod: 'eee8aa',
        palegreen: '98fb98',    paleturquoise: 'afeeee',palevioletred: 'd87093',
        papayawhip: 'ffefd5',   peachpuff: 'ffdab9',    peru: 'cd853f',
        pink: 'ffc0cb',         plum: 'dda0dd',         powderblue: 'b0e0e6',
        purple: '800080',       red: 'ff0000',          rosybrown: 'bc8f8f',
        royalblue: '4169e1',    saddlebrown: '8b4513',  salmon: 'fa8072',
        sandybrown: 'f4a460',   seagreen: '2e8b57',     seashell: 'fff5ee',
        sienna: 'a0522d',       silver: 'c0c0c0',       skyblue: '87ceeb',
        slateblue: '6a5acd',    slategray: '708090',    snow: 'fffafa',
        springgreen: '00ff7f',  steelblue: '4682b4',    tan: 'd2b48c',
        teal: '008080',         thistle: 'd8bfd8',      tomato: 'ff6347',
        turquoise: '40e0d0',    violet: 'ee82ee',       violetred: 'd02090',
        wheat: 'f5deb3',        white: 'ffffff',        whitesmoke: 'f5f5f5',
        yellow: 'ffff00',       yellowgreen: '9acd32'
    };

    if (color_names[color]) {
        // first check if color is given as colorname
        color = "#" + color_names[color] + "ff";
    }

    if (color.substr && color.substr(0,1) === "#") {
        color = color.substr(1);
        var len = color.length;

        if (len === 8) {
            red   = parseInt("0x"+color.substr(0,2), 16) / 255.0;
            green = parseInt("0x"+color.substr(2,2), 16) / 255.0;
            blue  = parseInt("0x"+color.substr(4,2), 16) / 255.0;
            alpha = parseInt("0x"+color.substr(6,2), 16) / 255.0;
        }
        else if (len === 6) {
            red   = parseInt("0x"+color.substr(0,2), 16) / 255.0;
            green = parseInt("0x"+color.substr(2,2), 16) / 255.0;
            blue  = parseInt("0x"+color.substr(4,2), 16) / 255.0;
            alpha = 1.0;
        }
        else if (len === 4) {
            red   = parseInt("0x"+color.substr(0,1), 16) / 15.0;
            green = parseInt("0x"+color.substr(1,1), 16) / 15.0;
            blue  = parseInt("0x"+color.substr(2,1), 16) / 15.0;
            alpha = parseInt("0x"+color.substr(3,1), 16) / 15.0;
        }
        else if (len === 3) {
            red   = parseInt("0x"+color.substr(0,1), 16) / 15.0;
            green = parseInt("0x"+color.substr(1,1), 16) / 15.0;
            blue  = parseInt("0x"+color.substr(2,1), 16) / 15.0;
            alpha = 1.0;
        }
    }

    return new x3dom.fields.SFColorRGBA( red, green, blue, alpha );
};


///////////////////////////////////////////////////////////////////////////////
/** SFImage constructor.
    @class Represents an SFImage
  */
x3dom.fields.SFImage = function(w, h, c, arr) {
    if (arguments.length === 0 || !(arr && arr.map)) {
        this.width = 0;
        this.height = 0;
        this.comp = 0;
        this.array = [];
    }
    else {
        this.width = w;
        this.height = h;
        this.comp = c;
        var that = this.array;
        arr.map( function(v) { that.push(v); }, this.array );
    }
};

x3dom.fields.SFImage.parse = function(str) {
    var img = new x3dom.fields.SFImage();
    img.setValueByStr(str);
    return img;
};

x3dom.fields.SFImage.copy = function(that) {
    var destination = new x3dom.fields.SFImage();
    destination.width = that.width;
    destination.height = that.height;
    destination.comp = that.comp;
    //use instead slice?
    //destination.array = that.array.slice();
    destination.setPixels(that.getPixels());
    return destination;
};

x3dom.fields.SFImage.prototype.copy = function() {
    return x3dom.fields.SFImage.copy(this);
};

x3dom.fields.SFImage.prototype.setValueByStr = function(str) {
    var mc = str.match(/(\w+)/g);
    var n = mc.length;
    var c2 = 0;
    var hex = "0123456789ABCDEF";
    
    this.array = [];
    
    if (n > 2) {
        this.width = +mc[0];
        this.height = +mc[1];
        this.comp = +mc[2];
        c2 = 2 * this.comp;
    } else {
        this.width = 0;
        this.height = 0;
        this.comp = 0;
        return;
    }
    
    var len, i;
    for (i=3; i<n; i++) {
        var r, g, b, a;

        if (!mc[i].substr) {
            continue;
        }
        
        if (mc[i].substr(1,1).toLowerCase() !== "x") {
            // Maybe optimize by directly parsing value!
            var inp = parseInt(mc[i], 10);

            if (this.comp === 1) {
                r = inp;
                this.array.push( r );
            }
            else if (this.comp === 2) {
                r = inp >> 8 & 255;
                g = inp & 255;
                this.array.push( r, g );
            }
            else if (this.comp === 3) {
                r = inp >> 16 & 255;
                g = inp >> 8 & 255;
                b = inp & 255;
                this.array.push( r, g, b );
            }
            else if (this.comp === 4) {
                r = inp >> 24 & 255;
                g = inp >> 16 & 255;
                b = inp >> 8 & 255;
                a = inp & 255;
                this.array.push( r, g, b, a );
            }
        }
        else if (mc[i].substr(1,1).toLowerCase() === "x") {
            mc[i] = mc[i].substr(2);
            len = mc[i].length;
            
            if (len === c2) {
                if (this.comp === 1) {
                    r = parseInt("0x"+mc[i].substr(0,2), 16);
                    this.array.push( r );
                }
                else if (this.comp === 2) {
                    r = parseInt("0x"+mc[i].substr(0,2), 16);
                    g = parseInt("0x"+mc[i].substr(2,2), 16);
                    this.array.push( r, g );
                }
                else if (this.comp === 3) {
                    r = parseInt("0x"+mc[i].substr(0,2), 16);
                    g = parseInt("0x"+mc[i].substr(2,2), 16);
                    b = parseInt("0x"+mc[i].substr(4,2), 16);
                    this.array.push( r, g, b );
                }
                else if (this.comp === 4) {
                    r = parseInt("0x"+mc[i].substr(0,2), 16);
                    g = parseInt("0x"+mc[i].substr(2,2), 16);
                    b = parseInt("0x"+mc[i].substr(4,2), 16);
                    a = parseInt("0x"+mc[i].substr(6,2), 16);
                    this.array.push( r, g, b, a );
                }
            }
        }
    }
};

x3dom.fields.SFImage.prototype.setPixel = function(x, y, color) {
    var startIdx = (y * this.width + x) * this.comp;

    if (this.comp === 1 && startIdx < this.array.length) {
        this.array[startIdx] = color.r * 255;
    }
    else if (this.comp === 2 && (startIdx+1) < this.array.length) {
        this.array[startIdx  ] = color.r * 255;
        this.array[startIdx+1] = color.g * 255;
    }
    else if (this.comp === 3 && (startIdx+2) < this.array.length) {
        this.array[startIdx  ] = color.r * 255;
        this.array[startIdx+1] = color.g * 255;
        this.array[startIdx+2] = color.b * 255;
    }
    else if (this.comp === 4 && (startIdx+3) < this.array.length) {
        this.array[startIdx  ] = color.r * 255;
        this.array[startIdx+1] = color.g * 255;
        this.array[startIdx+2] = color.b * 255;
        this.array[startIdx+3] = color.a * 255;
    }
};

x3dom.fields.SFImage.prototype.getPixel = function(x, y) {
    var startIdx = (y * this.width + x) * this.comp;

    if (this.comp === 1 && startIdx < this.array.length) {
        return new x3dom.fields.SFColorRGBA(this.array[startIdx] / 255,
                                            0,
                                            0,
                                            1);
    }
    else if (this.comp === 2 && (startIdx+1) < this.array.length) {
        return new x3dom.fields.SFColorRGBA(this.array[startIdx] / 255,
                                            this.array[startIdx+1] / 255,
                                            0,
                                            1);
    }
    else if (this.comp === 3 && (startIdx+2) < this.array.length) {
        return new x3dom.fields.SFColorRGBA(this.array[startIdx] / 255,
                                            this.array[startIdx+1] / 255,
                                            this.array[startIdx+2] / 255,
                                            1);
    }
    else if (this.comp === 4 && (startIdx+3) < this.array.length) {
        return new x3dom.fields.SFColorRGBA(this.array[startIdx] / 255,
                                            this.array[startIdx+1] / 255,
                                            this.array[startIdx+2] / 255,
                                            this.array[startIdx+3] / 255);
    }
};

x3dom.fields.SFImage.prototype.setPixels = function(pixels) {

    var i, idx = 0;

    if (this.comp === 1) {
        for(i=0; i<pixels.length; i++) {
            this.array[idx++] = pixels[i].r * 255;
        }
    }
    else if (this.comp === 2) {
        for(i=0; i<pixels.length; i++) {
            this.array[idx++] = pixels[i].r * 255;
            this.array[idx++] = pixels[i].g * 255;
        }
    }
    else if (this.comp === 3) {
        for(i=0; i<pixels.length; i++) {
            this.array[idx++] = pixels[i].r * 255;
            this.array[idx++] = pixels[i].g * 255;
            this.array[idx++] = pixels[i].b * 255;
        }
    }
    else if (this.comp === 4) {
        for(i=0; i<pixels.length; i++) {
            this.array[idx++] = pixels[i].r * 255;
            this.array[idx++] = pixels[i].g * 255;
            this.array[idx++] = pixels[i].b * 255;
            this.array[idx++] = pixels[i].a * 255;
        }
    }
};

x3dom.fields.SFImage.prototype.getPixels = function() {
    var i;
    var pixels = [];

    if (this.comp === 1) {
        for (i=0; i<this.array.length; i+=this.comp){
            pixels.push(new x3dom.fields.SFColorRGBA(this.array[i] / 255,
                                                     0,
                                                     0,
                                                     1));
        }
     }
    else if (this.comp === 2) {
        for (i=0; i<this.array.length; i+=this.comp) {
            pixels.push(new x3dom.fields.SFColorRGBA(this.array[i    ] / 255,
                                                     this.array[i + 1] / 255,
                                                     0,
                                                     1));
        }
    }
    else if (this.comp === 3) {
        for (i=0; i<this.array.length; i+=this.comp) {
            pixels.push(new x3dom.fields.SFColorRGBA(this.array[i    ] / 255,
                                                     this.array[i + 1] / 255,
                                                     this.array[i + 2] / 255,
                                                     1));
        }
    }
    else if (this.comp === 4) {
        for (i=0; i<this.array.length; i+=this.comp) {
            pixels.push(new x3dom.fields.SFColorRGBA(this.array[i    ] / 255,
                                                     this.array[i + 1] / 255,
                                                     this.array[i + 2] / 255,
                                                     this.array[i + 3] / 255));
        }
    }

    return pixels;
};

x3dom.fields.SFImage.prototype.toGL = function() {
    var a = [];

    Array.map( this.array, function(c) {
        a.push(c);       
    });

    return a;
};



///////////////////////////////////////////////////////////////////////////////
// Multi-Field Definitions
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
/** MFColor constructor.
    @class Represents a MFColor
  */
x3dom.fields.MFColor = function(colorArray) {

    if (colorArray) {
        var that = this;
        colorArray.map( function(c) { that.push(c); }, this );
    }
};

x3dom.fields.MFColor.copy = function(colorArray) {
    var destination = new x3dom.fields.MFColor();
    colorArray.map( function(v) { destination.push(v.copy()); }, this );
    return destination;
};

x3dom.fields.MFColor.prototype = x3dom.extend([]);

x3dom.fields.MFColor.parse = function(str) {
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    var colors = [];
    for (var i=0, n=mc?mc.length:0; i<n; i+=3) {
        colors.push( new x3dom.fields.SFColor(+mc[i+0], +mc[i+1], +mc[i+2]) );
    }
    
    return new x3dom.fields.MFColor( colors );
};

x3dom.fields.MFColor.prototype.copy = function() {
    return x3dom.fields.MFColor.copy(this);
};

x3dom.fields.MFColor.prototype.setValueByStr = function(str) {
    this.length = 0;
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    for (var i=0, n=mc?mc.length:0; i<n; i+=3) {
        this.push( new x3dom.fields.SFColor(+mc[i+0], +mc[i+1], +mc[i+2]) );
    }
};

x3dom.fields.MFColor.prototype.toGL = function() {
    var a = [];

    Array.map( this, function(c) {
        a.push(c.r);
        a.push(c.g);
        a.push(c.b);        
    });

    return a;
};


///////////////////////////////////////////////////////////////////////////////
/** MFColorRGBA constructor.
    @class Represents a MFColorRGBA
  */
x3dom.fields.MFColorRGBA = function(colorArray) {
    if (colorArray) {
        var that = this;
        colorArray.map( function(c) { that.push(c); }, this );
    }
};

x3dom.fields.MFColorRGBA.copy = function(colorArray) {
    var destination = new x3dom.fields.MFColorRGBA();
    colorArray.map( function(v) { destination.push(v.copy()); }, this );
    return destination;
};

x3dom.fields.MFColorRGBA.prototype = x3dom.extend([]);

x3dom.fields.MFColorRGBA.parse = function(str) {
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    var colors = [];
    for (var i=0, n=mc?mc.length:0; i<n; i+=4) {
        colors.push( new x3dom.fields.SFColorRGBA(+mc[i+0], +mc[i+1], +mc[i+2], +mc[i+3]) );
    }
    
    return new x3dom.fields.MFColorRGBA( colors );
};

x3dom.fields.MFColorRGBA.prototype.copy = function() {
    return x3dom.fields.MFColorRGBA.copy(this);
};

x3dom.fields.MFColorRGBA.prototype.setValueByStr = function(str) {
    this.length = 0;
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    for (var i=0, n=mc?mc.length:0; i<n; i+=4) {
        this.push( new x3dom.fields.SFColorRGBA(+mc[i+0], +mc[i+1], +mc[i+2], +mc[i+3]) );
    }
};

x3dom.fields.MFColorRGBA.prototype.toGL = function() {
    var a = [];

    Array.map( this, function(c) {
        a.push(c.r);
        a.push(c.g);
        a.push(c.b);
        a.push(c.a);    
    });

    return a;
};


///////////////////////////////////////////////////////////////////////////////
/** MFRotation constructor.
    @class Represents a MFRotation
  */
x3dom.fields.MFRotation = function(rotArray) {
    if (rotArray) {
        var that = this;
        rotArray.map( function(v) { that.push(v); }, this );
    }
};

x3dom.fields.MFRotation.prototype = x3dom.extend([]);

x3dom.fields.MFRotation.copy = function(rotationArray) {
    var destination = new x3dom.fields.MFRotation();
    rotationArray.map( function(v) { destination.push(v.copy()); }, this );
    return destination;
};

x3dom.fields.MFRotation.prototype.copy = function() {
    return x3dom.fields.MFRotation.copy(this);
};

x3dom.fields.MFRotation.parse = function(str) {
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    var vecs = [];
    for (var i=0, n=mc?mc.length:0; i<n; i+=4) {
        vecs.push( x3dom.fields.Quaternion.axisAngle(new x3dom.fields.SFVec3f(+mc[i+0], +mc[i+1], +mc[i+2]), +mc[i+3]) );
    }
    
    // holds the quaternion representation as needed by interpolators etc.
    return new x3dom.fields.MFRotation( vecs );    
};

x3dom.fields.MFRotation.prototype.setValueByStr = function(str) {
    this.length = 0;
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    for (var i=0, n=mc?mc.length:0; i<n; i+=4) {
        this.push( x3dom.fields.Quaternion.axisAngle(new x3dom.fields.SFVec3f(+mc[i+0], +mc[i+1], +mc[i+2]), +mc[i+3]) );
    }
};

x3dom.fields.MFRotation.prototype.toGL = function() {
    var a = [];

    Array.map( this, function(c) {
        var val = c.toAxisAngle();
        a.push(val[0].x);
        a.push(val[0].y);
        a.push(val[0].z);
        a.push(val[1]);
    });

    return a;
};


///////////////////////////////////////////////////////////////////////////////
/** MFVec3f constructor.
    @class Represents a MFVec3f
  */
x3dom.fields.MFVec3f = function(vec3Array) {
    if (vec3Array) {
        var that = this;
        vec3Array.map( function(v) { that.push(v); }, this );
    }
};

x3dom.fields.MFVec3f.prototype = x3dom.extend(Array);

x3dom.fields.MFVec3f.copy = function(vec3Array) {
    var destination = new x3dom.fields.MFVec3f();
    vec3Array.map( function(v) { destination.push(v.copy()); }, this );
    return destination;
};

x3dom.fields.MFVec3f.parse = function(str) {
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    var vecs = [];
    for (var i=0, n=mc?mc.length:0; i<n; i+=3) {
        vecs.push( new x3dom.fields.SFVec3f(+mc[i+0], +mc[i+1], +mc[i+2]) );
    }
    
    return new x3dom.fields.MFVec3f( vecs );    
};

x3dom.fields.MFVec3f.prototype.copy = function()
{
    x3dom.fields.MFVec3f.copy(this);
};

x3dom.fields.MFVec3f.prototype.setValueByStr = function(str) {
    this.length = 0;
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    for (var i=0, n=mc?mc.length:0; i<n; i+=3) {
        this.push( new x3dom.fields.SFVec3f(+mc[i+0], +mc[i+1], +mc[i+2]) );
    }
};

x3dom.fields.MFVec3f.prototype.toGL = function() {
    var a = [];

    Array.map( this, function(c) {
        a.push(c.x);
        a.push(c.y);
        a.push(c.z);        
    });

    return a;
};


///////////////////////////////////////////////////////////////////////////////
/** MFVec2f constructor.
    @class Represents a MFVec2f
  */
x3dom.fields.MFVec2f = function(vec2Array) {
    if (vec2Array) {
        var that = this;
        vec2Array.map( function(v) { that.push(v); }, this );
    }
};

x3dom.fields.MFVec2f.prototype = x3dom.extend([]);

x3dom.fields.MFVec2f.copy = function(vec2Array) {
    var destination = new x3dom.fields.MFVec2f();
    vec2Array.map( function(v) { destination.push(v.copy()); }, this );
    return destination;
};

x3dom.fields.MFVec2f.parse = function(str) {
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    var vecs = [];
    for (var i=0, n=mc?mc.length:0; i<n; i+=2) {
        vecs.push( new x3dom.fields.SFVec2f(+mc[i+0], +mc[i+1]) );
    }

    return new x3dom.fields.MFVec2f( vecs );    
};

x3dom.fields.MFVec2f.prototype.copy = function() {
    return x3dom.fields.MFVec2f.copy(this);
};

x3dom.fields.MFVec2f.prototype.setValueByStr = function(str) {
    this.length = 0;
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    for (var i=0, n=mc?mc.length:0; i<n; i+=2) {
        this.push( new x3dom.fields.SFVec2f(+mc[i+0], +mc[i+1]) );
    }
};

x3dom.fields.MFVec2f.prototype.toGL = function() {
    var a = [];

    Array.map( this, function(v) {
        a.push(v.x);
        a.push(v.y);    
    });

    return a;
};


///////////////////////////////////////////////////////////////////////////////
/** MFInt32 constructor.
    @class Represents a MFInt32
  */
x3dom.fields.MFInt32 = function(array) {
    if (array) {
        var that = this;
        array.map( function(v) { that.push(v); }, this );
    }
};

x3dom.fields.MFInt32.prototype = x3dom.extend([]);

x3dom.fields.MFInt32.copy = function(intArray) {
    var destination = new x3dom.fields.MFInt32();
    intArray.map( function(v) { destination.push(v); }, this );
    return destination;
};

x3dom.fields.MFInt32.parse = function(str) {
    var mc = str.match(/([+\-]?\d+\s*){1},?\s*/g);
    var vals = [];
    for (var i=0, n=mc?mc.length:0; i<n; ++i) {
        vals.push( parseInt(mc[i], 10) );
    }
    
    return new x3dom.fields.MFInt32( vals );
};

x3dom.fields.MFInt32.prototype.copy = function() {
    return x3dom.fields.MFInt32.copy(this);
};

x3dom.fields.MFInt32.prototype.setValueByStr = function(str) {
    this.length = 0;
    var mc = str.match(/([+\-]?\d+\s*){1},?\s*/g);
    for (var i=0, n=mc?mc.length:0; i<n; ++i) {
        this.push( parseInt(mc[i], 10) );
    }
};

x3dom.fields.MFInt32.prototype.toGL = function() {
    var a = [];

    Array.map( this, function(v) {
        a.push(v);
    });

    return a;
};


///////////////////////////////////////////////////////////////////////////////
/** MFFloat constructor.
    @class Represents a MFFloat
  */
x3dom.fields.MFFloat = function(array) {
    if (array) {
        var that = this;
        array.map( function(v) { that.push(v); }, this );
    }
};

x3dom.fields.MFFloat.prototype = x3dom.extend([]);

x3dom.fields.MFFloat.copy = function(floatArray) {
    var destination = new x3dom.fields.MFFloat();
    floatArray.map( function(v) { destination.push(v); }, this );
    return destination;
};

x3dom.fields.MFFloat.parse = function(str) {
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    var vals = [];
    for (var i=0, n=mc?mc.length:0; i<n; i++) {
        vals.push( +mc[i] );
    }
    
    return new x3dom.fields.MFFloat( vals );    
};

x3dom.fields.MFFloat.prototype.copy = function() {
    return x3dom.fields.MFFloat.copy(this);
};

x3dom.fields.MFFloat.prototype.setValueByStr = function(str) {
    this.length = 0;
    var mc = str.match(/([+\-0-9eE\.]+)/g);
    for (var i=0, n=mc?mc.length:0; i<n; i++) {
        this.push( +mc[i] );
    }
};

x3dom.fields.MFFloat.prototype.toGL = function() {
    var a = [];

    Array.map( this, function(v) {
        a.push(v);
    });

    return a;
};


///////////////////////////////////////////////////////////////////////////////
/** MFBoolean constructor.
 @class Represents a MFBoolean
 */
x3dom.fields.MFBoolean = function(array) {
    if (array) {
        var that = this;
        array.map( function(v) { that.push(v); }, this );
    }
};

x3dom.fields.MFBoolean.prototype = x3dom.extend([]);

x3dom.fields.MFBoolean.copy = function(boolArray) {
    var destination = new x3dom.fields.MFBoolean();
    boolArray.map( function(v) { destination.push(v); }, this );
    return destination;
};

x3dom.fields.MFBoolean.parse = function(str) {
    var mc = str.match(/(true|false|1|0)/ig);
    var vals = [];
    for (var i=0, n=mc?mc.length:0; i<n; i++) {
        vals.push( (mc[i] == '1' || mc[i].toLowerCase() == 'true') );
    }

    return new x3dom.fields.MFBoolean( vals );
};

x3dom.fields.MFBoolean.prototype.copy = function() {
    return x3dom.fields.MFBoolean.copy(this);
};

x3dom.fields.MFBoolean.prototype.setValueByStr = function(str) {
    this.length = 0;
    var mc = str.match(/(true|false|1|0)/ig);
    for (var i=0, n=mc?mc.length:0; i<n; i++) {
        this.push( (mc[i] == '1' || mc[i].toLowerCase() == 'true') );
    }
};

x3dom.fields.MFBoolean.prototype.toGL = function() {
    var a = [];

    Array.map( this, function(v) {
        a.push(v ? 1 : 0);
    });

    return a;
};


///////////////////////////////////////////////////////////////////////////////
/** MFString constructor.
    @class Represents a MFString
  */
x3dom.fields.MFString = function(strArray) {
    if (strArray && strArray.map) {
        var that = this;
        strArray.map( function(v) { that.push(v); }, this );
    }
};

x3dom.fields.MFString.prototype = x3dom.extend([]);

x3dom.fields.MFString.copy = function(stringArray) {
    var destination = new x3dom.fields.MFString();
    stringArray.map( function(v) { destination.push(v); }, this );
    return destination;
};

x3dom.fields.MFString.parse = function(str) {
    var arr = [];
    // ignore leading whitespace?
    if (str.length && str[0] == '"') {
        var m, re = /"((?:[^\\"]|\\\\|\\")*)"/g;
        while ((m = re.exec(str))) {
            var s = m[1].replace(/\\([\\"])/g, "$1");
            if (s !== undefined) {
                arr.push(s);
            }
        }
    }
    else {
        arr.push(str);
    }
    return new x3dom.fields.MFString( arr );
};

x3dom.fields.MFString.prototype.copy = function() {
    return x3dom.fields.MFString.copy(this);
};

x3dom.fields.MFString.prototype.setValueByStr = function(str) {
    this.length = 0;
    // ignore leading whitespace?
    if (str.length && str[0] == '"') {
        var m, re = /"((?:[^\\"]|\\\\|\\")*)"/g;
        while ((m = re.exec(str))) {
            var s = m[1].replace(/\\([\\"])/, "$1");
            if (s !== undefined) {
                this.push(s);
            }
        }
    }
    else {
        this.push(str);
    }
    return this;
};

x3dom.fields.MFString.prototype.toString = function () {
    var str = "";
    for (var i=0, n=this.length; i<n; i++) {
		 str = str + this[i] + " ";
    }
    return str;
};



///////////////////////////////////////////////////////////////////////////////
// Single-/Multi-Field Node Definitions
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
/** SFNode constructor.
    @class Represents a SFNode
  */
x3dom.fields.SFNode = function(type) {
    this.type = type;
    this.node = null;
};

x3dom.fields.SFNode.prototype.hasLink = function(node) {
    return (node ? (this.node === node) : this.node);
};

x3dom.fields.SFNode.prototype.addLink = function(node) {
    this.node = node;
    return true;
};

x3dom.fields.SFNode.prototype.rmLink = function(node) {
    if (this.node === node) {
        this.node = null;
        return true;
    }
    else {
        return false;
    }
};


///////////////////////////////////////////////////////////////////////////////
/** MFNode constructor.
    @class Represents a MFNode
  */
x3dom.fields.MFNode = function(type) {
    this.type = type;
    this.nodes = [];
};

x3dom.fields.MFNode.prototype.hasLink = function(node) {
    if (node) {
        for (var i = 0, n = this.nodes.length; i < n; i++) {
            if (this.nodes[i] === node) {
                return true;
            }
        }
    }
    else {
        return (this.length > 0);
    }
    return false;
};

x3dom.fields.MFNode.prototype.addLink = function(node) {
    this.nodes.push (node);
    return true;
};

x3dom.fields.MFNode.prototype.rmLink = function(node) {
    for (var i = 0, n = this.nodes.length; i < n; i++) {
        if (this.nodes[i] === node) {
            this.nodes.splice(i,1);
            return true;
        }
    }
    return false;
};

x3dom.fields.MFNode.prototype.length = function() {
    return this.nodes.length;
};



///////////////////////////////////////////////////////////////////////////////
// Math Helper Class Definitions
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////

/**
 * Line constructor.
 * @param {SFVec3f} pos - anchor point of the line
 * @param {SFVec3f} dir - direction of the line, must be normalized
 * @class Represents a Line (as internal helper).
 *        A line has an origin and a vector that describes a direction, it is infinite in both directions.
 */
x3dom.fields.Line = function(pos, dir)
{
    if (arguments.length === 0)
    {
        this.pos = new x3dom.fields.SFVec3f(0, 0, 0);
        this.dir = new x3dom.fields.SFVec3f(0, 0, 1);
    }

    this.pos = x3dom.fields.SFVec3f.copy(pos);
    this.dir = x3dom.fields.SFVec3f.copy(dir);
};

/**
 * For a given point, this function returns the closest point on this line.
 * @param p {x3dom.fields.SFVec3f} - the point
 * @returns {x3dom.fields.SFVec3f} the closest point
 */
x3dom.fields.Line.prototype.closestPoint = function(p)
{
    var distVec = p.subtract(this.pos);

    //project the distance vector on the line
    var projDist = distVec.dot(this.dir);

    return this.pos.add(this.dir.multiply(projDist));
};

/**
 * For a given point, this function returns the distance to the closest point on this line.
 * @param p {x3dom.fields.SFVec3f} - the point
 * @returns {Number} the distance to the closest point
 */
x3dom.fields.Line.prototype.shortestDistance = function(p)
{
    var distVec = p.subtract(this.pos);

    //project the distance vector on the line
    var projDist = distVec.dot(this.dir);

    //subtract the projected distance vector, to obtain the part that is orthogonal to this line
    return distVec.subtract(this.dir.multiply(projDist)).length();
};

///////////////////////////////////////////////////////////////////////////////

/**
 * Ray constructor.
 * @param {SFVec3f} pos - anchor point of the ray
 * @param {SFVec3f} dir - direction of the ray, must be normalized
 * @class Represents a Ray (as internal helper).
 *        A ray is a special line that extends to only one direction from its origin.
 */
x3dom.fields.Ray = function(pos, dir)
{
    if (arguments.length === 0) 
    {
        this.pos = new x3dom.fields.SFVec3f(0, 0, 0);
        this.dir = new x3dom.fields.SFVec3f(0, 0, 1);
    } 
    else 
    {
        this.pos = new x3dom.fields.SFVec3f(pos.x, pos.y, pos.z);
        
        var n = dir.length();
        if (n) { n = 1.0 / n; }
        
        this.dir = new x3dom.fields.SFVec3f(dir.x*n, dir.y*n, dir.z*n);
    }
    
    this.enter = 0;
    this.exit  = 0;
    this.hitObject = null;
    this.hitPoint  = {};
    this.dist = Number.MAX_VALUE;
};

x3dom.fields.Ray.prototype.toString = function () {
    return 'Ray: [' + this.pos.toString() + '; ' + this.dir.toString() + ']';
};

/**
 * Intersects this ray with a plane, defined by the given anchor point and normal.
 * The result returned is the point of intersection, if any. If no point of intersection exists, null is returned.
 * Null is also returned in case there is an infinite number of solutions (, i.e., if the ray origin lies in the plane).
 *
 * @param p {x3dom.fields.SFVec3f} - anchor point
 * @param n {x3dom.fields.SFVec3f} - plane normal
 * @returns {x3dom.fields.SFVec3f} the point of intersection, can be null
 */
x3dom.fields.Ray.prototype.intersectPlane = function(p, n)
{
    var result = null;

    var alpha; //ray parameter, should be computed

    var nDotDir = n.dot(this.dir);

    //if the ray hits the plane, the plane normal and ray direction must be facing each other
    if (nDotDir < 0.0)
    {
        alpha = (p.dot(n) - this.pos.dot(n)) / nDotDir;

        result = this.pos.addScaled(this.dir, alpha);
    }

    return result;
};

/** intersect line with box volume given by low and high */
x3dom.fields.Ray.prototype.intersect = function(low, high)
{
    var isect = 0.0;
    var out = Number.MAX_VALUE;
    var r, te, tl;

    if (this.dir.x > x3dom.fields.Eps)
    {
        r = 1.0 / this.dir.x;

        te = (low.x - this.pos.x) * r;
        tl = (high.x - this.pos.x) * r;

        if (tl < out){
            out = tl;
        }

        if (te > isect){
            isect  = te;
        }
    }
    else if (this.dir.x < -x3dom.fields.Eps)
    {
        r = 1.0 / this.dir.x;

        te = (high.x - this.pos.x) * r;
        tl = (low.x - this.pos.x) * r;

        if (tl < out){
            out = tl;
        }

        if (te > isect)   {
            isect = te;
        }
    }
    else if (this.pos.x < low.x || this.pos.x > high.x)
    {
        return false;
    }

    if (this.dir.y > x3dom.fields.Eps)
    {
        r = 1.0 / this.dir.y;

        te = (low.y - this.pos.y) * r;
        tl = (high.y - this.pos.y) * r;

        if (tl < out){
            out = tl;
        }

        if (te > isect) {
            isect = te;
        }

        if (isect-out >= x3dom.fields.Eps) {
            return false;
        }
    }
    else if (this.dir.y < -x3dom.fields.Eps)
    {
        r = 1.0 / this.dir.y;

        te = (high.y - this.pos.y) * r;
        tl = (low.y - this.pos.y) * r;

        if (tl < out){
            out = tl;
        }

        if (te > isect) {
            isect = te;
        }

        if (isect-out >= x3dom.fields.Eps) {
            return false;
        }
    }
    else if (this.pos.y < low.y || this.pos.y > high.y)
    {
        return false;
    }

    if (this.dir.z > x3dom.fields.Eps)
    {
        r = 1.0 / this.dir.z;

        te = (low.z - this.pos.z) * r;
        tl = (high.z - this.pos.z) * r;

        if (tl < out) {
            out = tl;
        }

        if (te > isect) {
            isect = te;
        }
    }
    else if (this.dir.z < -x3dom.fields.Eps)
    {
        r = 1.0 / this.dir.z;

        te = (high.z - this.pos.z) * r;
        tl = (low.z - this.pos.z) * r;

        if (tl < out) {
            out = tl;
        }

        if (te > isect) {
            isect = te;
        }
    }
    else if (this.pos.z < low.z || this.pos.z > high.z)
    {
        return false;
    }

    this.enter = isect;
    this.exit  = out;

    return (isect-out < x3dom.fields.Eps);
};


///////////////////////////////////////////////////////////////////////////////
/** BoxVolume constructor.
    @class Represents a box volume (as internal helper).
  */
x3dom.fields.BoxVolume = function(min, max)
{
    if (arguments.length < 2) {
        this.min = new x3dom.fields.SFVec3f(0, 0, 0);
        this.max = new x3dom.fields.SFVec3f(0, 0, 0);
        this.valid = false;
    }
    else {
        // compiler enforced type check for min/max would be nice
        this.min = x3dom.fields.SFVec3f.copy(min);
        this.max = x3dom.fields.SFVec3f.copy(max);
        this.valid = true;
    }

    this.updateInternals();
};

x3dom.fields.BoxVolume.prototype.getScalarValue = function()
{
    var extent = this.max.subtract(this.min);

    return (extent.x*extent.y*extent.z);
};

x3dom.fields.BoxVolume.copy = function(other)
{
    var volume = new x3dom.fields.BoxVolume(other.min, other.max);
    volume.valid = other.valid;
    return volume;
};

x3dom.fields.BoxVolume.prototype.equals = function(other)
{
    return ( this.min.equals(other.min, 0.000000000001) && this.max.equals( other.max, 0.000000000001) );
};

x3dom.fields.BoxVolume.prototype.updateInternals = function()
{
    this.radialVec = this.max.subtract(this.min).multiply(0.5);
    this.center    = this.min.add(this.radialVec);
    this.diameter  = 2 * this.radialVec.length();
};

x3dom.fields.BoxVolume.prototype.setBounds = function(min, max)
{
    this.min.setValues(min);
    this.max.setValues(max);

    this.updateInternals();
    this.valid = true;
};

x3dom.fields.BoxVolume.prototype.setBoundsByCenterSize = function(center, size)
{
    var halfSize = size.multiply(0.5);
    this.min = center.subtract(halfSize);
    this.max = center.add(halfSize);

    this.updateInternals();
    this.valid = true;
};

x3dom.fields.BoxVolume.prototype.extendBounds = function(min, max)
{
    if (this.valid)
    {
        if (this.min.x > min.x) { this.min.x = min.x; }
        if (this.min.y > min.y) { this.min.y = min.y; }
        if (this.min.z > min.z) { this.min.z = min.z; }

        if (this.max.x < max.x) { this.max.x = max.x; }
        if (this.max.y < max.y) { this.max.y = max.y; }
        if (this.max.z < max.z) { this.max.z = max.z; }

        this.updateInternals();
    }
    else
    {
        this.setBounds(min, max);
    }
};

x3dom.fields.BoxVolume.prototype.getBounds = function(min, max)
{
    min.setValues(this.min);
    max.setValues(this.max);
};

x3dom.fields.BoxVolume.prototype.getRadialVec = function()
{
    return this.radialVec;
};

x3dom.fields.BoxVolume.prototype.invalidate = function()
{
    this.valid = false;
    this.min = new x3dom.fields.SFVec3f(0, 0, 0);
    this.max = new x3dom.fields.SFVec3f(0, 0, 0);
    this.updateInternals();
};

x3dom.fields.BoxVolume.prototype.isValid = function()
{
    return this.valid;
};

x3dom.fields.BoxVolume.prototype.getCenter = function()
{
    return this.center;
};

x3dom.fields.BoxVolume.prototype.getDiameter = function()
{
    return this.diameter;
};

x3dom.fields.BoxVolume.prototype.transform = function(m)
{
    var xmin, ymin, zmin;
    var xmax, ymax, zmax;

    xmin = xmax = m._03;
    ymin = ymax = m._13;
    zmin = zmax = m._23;

    // calculate xmin and xmax of new transformed BBox
    var a = this.max.x * m._00;
    var b = this.min.x * m._00;

    if (a >= b) {
        xmax += a;
        xmin += b;
    }
    else {
        xmax += b;
        xmin += a;
    }

    a = this.max.y * m._01;
    b = this.min.y * m._01;

    if (a >= b) {
        xmax += a;
        xmin += b;
    }
    else {
        xmax += b;
        xmin += a;
    }
    
    a = this.max.z * m._02;
    b = this.min.z * m._02;

    if (a >= b) {
        xmax += a;
        xmin += b;
    }
    else {
        xmax += b;
        xmin += a;
    }

    // calculate ymin and ymax of new transformed BBox
    a = this.max.x * m._10;
    b = this.min.x * m._10;

    if (a >= b) {
        ymax += a;
        ymin += b;
    }
    else {
        ymax += b;
        ymin += a;
    }

    a = this.max.y * m._11;
    b = this.min.y * m._11;

    if (a >= b) {
        ymax += a;
        ymin += b;
    }
    else {
        ymax += b;
        ymin += a;
    }

    a = this.max.z * m._12;
    b = this.min.z * m._12;

    if (a >= b) {
        ymax += a;
        ymin += b;
    }
    else {
        ymax += b;
        ymin += a;
    }

    // calculate zmin and zmax of new transformed BBox
    a = this.max.x * m._20;
    b = this.min.x * m._20;

    if (a >= b) {
        zmax += a;
        zmin += b;
    }
    else {
        zmax += b;
        zmin += a;
    }

    a = this.max.y * m._21;
    b = this.min.y * m._21;

    if (a >= b) {
        zmax += a;
        zmin += b;
    }
    else {
        zmax += b;
        zmin += a;
    }

    a = this.max.z * m._22;
    b = this.min.z * m._22;

    if (a >= b) {
        zmax += a;
        zmin += b;
    }
    else {
        zmax += b;
        zmin += a;
    }

    this.min.x = xmin;
    this.min.y = ymin;
    this.min.z = zmin;
    
    this.max.x = xmax;
    this.max.y = ymax;
    this.max.z = zmax;

    this.updateInternals();
};

x3dom.fields.BoxVolume.prototype.transformFrom = function(m, other)
{
    var xmin, ymin, zmin;
    var xmax, ymax, zmax;

    xmin = xmax = m._03;
    ymin = ymax = m._13;
    zmin = zmax = m._23;

    // calculate xmin and xmax of new transformed BBox
    var a = other.max.x * m._00;
    var b = other.min.x * m._00;

    if (a >= b) {
        xmax += a;
        xmin += b;
    }
    else {
        xmax += b;
        xmin += a;
    }

    a = other.max.y * m._01;
    b = other.min.y * m._01;

    if (a >= b) {
        xmax += a;
        xmin += b;
    }
    else {
        xmax += b;
        xmin += a;
    }

    a = other.max.z * m._02;
    b = other.min.z * m._02;

    if (a >= b) {
        xmax += a;
        xmin += b;
    }
    else {
        xmax += b;
        xmin += a;
    }

    // calculate ymin and ymax of new transformed BBox
    a = other.max.x * m._10;
    b = other.min.x * m._10;

    if (a >= b) {
        ymax += a;
        ymin += b;
    }
    else {
        ymax += b;
        ymin += a;
    }

    a = other.max.y * m._11;
    b = other.min.y * m._11;

    if (a >= b) {
        ymax += a;
        ymin += b;
    }
    else {
        ymax += b;
        ymin += a;
    }

    a = other.max.z * m._12;
    b = other.min.z * m._12;

    if (a >= b) {
        ymax += a;
        ymin += b;
    }
    else {
        ymax += b;
        ymin += a;
    }

    // calculate zmin and zmax of new transformed BBox
    a = other.max.x * m._20;
    b = other.min.x * m._20;

    if (a >= b) {
        zmax += a;
        zmin += b;
    }
    else {
        zmax += b;
        zmin += a;
    }

    a = other.max.y * m._21;
    b = other.min.y * m._21;

    if (a >= b) {
        zmax += a;
        zmin += b;
    }
    else {
        zmax += b;
        zmin += a;
    }

    a = other.max.z * m._22;
    b = other.min.z * m._22;

    if (a >= b) {
        zmax += a;
        zmin += b;
    }
    else {
        zmax += b;
        zmin += a;
    }

    this.min.x = xmin;
    this.min.y = ymin;
    this.min.z = zmin;

    this.max.x = xmax;
    this.max.y = ymax;
    this.max.z = zmax;

    this.updateInternals();
    this.valid = true;
};


///////////////////////////////////////////////////////////////////////////////
/** FrustumVolume constructor.
    @class Represents a frustum (as internal helper).
  */
x3dom.fields.FrustumVolume = function(clipMat)
{
    this.planeNormals = [];
    this.planeDistances = [];
    this.directionIndex = [];
    
    if (arguments.length === 0) {
        return;
    }
    
    var planeEquation = [];
    
    for (var i=0; i<6; i++) {
        this.planeNormals[i] = new x3dom.fields.SFVec3f(0, 0, 0);
        this.planeDistances[i] = 0;
        this.directionIndex[i] = 0;
        
        planeEquation[i] = new x3dom.fields.SFVec4f(0, 0, 0, 0);
    }
    
    planeEquation[0].x = clipMat._30 - clipMat._00;
    planeEquation[0].y = clipMat._31 - clipMat._01;
    planeEquation[0].z = clipMat._32 - clipMat._02;
    planeEquation[0].w = clipMat._33 - clipMat._03;

    planeEquation[1].x = clipMat._30 + clipMat._00;
    planeEquation[1].y = clipMat._31 + clipMat._01;
    planeEquation[1].z = clipMat._32 + clipMat._02;
    planeEquation[1].w = clipMat._33 + clipMat._03;

    planeEquation[2].x = clipMat._30 + clipMat._10;
    planeEquation[2].y = clipMat._31 + clipMat._11;
    planeEquation[2].z = clipMat._32 + clipMat._12;
    planeEquation[2].w = clipMat._33 + clipMat._13;

    planeEquation[3].x = clipMat._30 - clipMat._10;
    planeEquation[3].y = clipMat._31 - clipMat._11;
    planeEquation[3].z = clipMat._32 - clipMat._12;
    planeEquation[3].w = clipMat._33 - clipMat._13;

    planeEquation[4].x = clipMat._30 + clipMat._20;
    planeEquation[4].y = clipMat._31 + clipMat._21;
    planeEquation[4].z = clipMat._32 + clipMat._22;
    planeEquation[4].w = clipMat._33 + clipMat._23;

    planeEquation[5].x = clipMat._30 - clipMat._20;
    planeEquation[5].y = clipMat._31 - clipMat._21;
    planeEquation[5].z = clipMat._32 - clipMat._22;
    planeEquation[5].w = clipMat._33 - clipMat._23;
    
    for (i=0; i<6; i++) {
        var vectorLength = Math.sqrt(planeEquation[i].x * planeEquation[i].x +
                                     planeEquation[i].y * planeEquation[i].y +
                                     planeEquation[i].z * planeEquation[i].z);
        
        planeEquation[i].x /=  vectorLength;
        planeEquation[i].y /=  vectorLength;
        planeEquation[i].z /=  vectorLength;
        planeEquation[i].w /= -vectorLength;
    }
    
    var updateDirectionIndex = function(normalVec) {
        var ind = 0;
        if (normalVec.x > 0) ind |= 1;
        if (normalVec.y > 0) ind |= 2;
        if (normalVec.z > 0) ind |= 4;
        return ind;
    };
    
    // right
    this.planeNormals[3].setValues(planeEquation[0]);
    this.planeDistances[3] = planeEquation[0].w;
    this.directionIndex[3] = updateDirectionIndex(this.planeNormals[3]);

    // left
    this.planeNormals[2].setValues(planeEquation[1]);
    this.planeDistances[2] = planeEquation[1].w;
    this.directionIndex[2] = updateDirectionIndex(this.planeNormals[2]);

    // bottom
    this.planeNormals[5].setValues(planeEquation[2]);
    this.planeDistances[5] = planeEquation[2].w;
    this.directionIndex[5] = updateDirectionIndex(this.planeNormals[5]);

    // top
    this.planeNormals[4].setValues(planeEquation[3]);
    this.planeDistances[4] = planeEquation[3].w;
    this.directionIndex[4] = updateDirectionIndex(this.planeNormals[4]);

    // near
    this.planeNormals[0].setValues(planeEquation[4]);
    this.planeDistances[0] = planeEquation[4].w;
    this.directionIndex[0] = updateDirectionIndex(this.planeNormals[0]);

    // far
    this.planeNormals[1].setValues(planeEquation[5]);
    this.planeDistances[1] = planeEquation[5].w;
    this.directionIndex[1] = updateDirectionIndex(this.planeNormals[1]);
};

/**
*  Check the volume against the frustum.
*  Return values > 0 indicate a plane mask that was used to identify
*  the object as "inside".
*  Return value -1 means the object has been culled (i.e., is outside
*  with respect to at least one plane).
*  Return value 0 is a rare case, indicating that the object intersects
*  with all planes of the frustum.
*/
x3dom.fields.FrustumVolume.prototype.intersect = function(vol, planeMask)
{
    if (this.planeNormals.length < 6) {
        x3dom.debug.logWarning("FrustumVolume not initialized!");
        return false;
    }
    
    var that = this;
    var min = vol.min, max = vol.max;
    
    var setDirectionIndexPoint = function(index) {
        var pnt = new x3dom.fields.SFVec3f(0, 0, 0);
        if (index & 1) { pnt.x = min.x; }
        else           { pnt.x = max.x; }
        if (index & 2) { pnt.y = min.y; }
        else           { pnt.y = max.y; }
        if (index & 4) { pnt.z = min.z; }
        else           { pnt.z = max.z; }
        return pnt;
    };
    
    //Check if the point is in the halfspace
    var pntIsInHalfSpace = function(i, pnt) {
        var s = that.planeNormals[i].dot(pnt) - that.planeDistances[i];
        return (s >= 0);
    };

    //Check if the box formed by min/max is fully inside the halfspace
    var isInHalfSpace = function(i) {
        var p = setDirectionIndexPoint(that.directionIndex[i]);
        return pntIsInHalfSpace(i, p);
    };

    //Check if the box formed by min/max is fully outside the halfspace
    var isOutHalfSpace = function(i) {
        var p = setDirectionIndexPoint(that.directionIndex[i] ^ 7);
        return !pntIsInHalfSpace(i, p);
    };
    
    //Check each point of the box to the 6 planes
    var mask = 1;
    if (planeMask < 0) planeMask = 0;

    for (var i=0; i<6; i++, mask<<=1) {
        if ((planeMask & mask) != 0)
            continue;

        if (isOutHalfSpace(i))
            return -1;

        if (isInHalfSpace(i))
            planeMask |= mask;
    }

    return planeMask;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


// This module adds documentation related functionality
// to the library.

/** The x3dom.docs namespace.
 * @namespace x3dom.docs
 */
x3dom.docs = {};


x3dom.docs.specURLMap = {
    CADGeometry: "CADGeometry.html",
    Core: "core.html",
    DIS: "dis.html",
    CubeMapTexturing: "env_texture.html",
    EnvironmentalEffects: "enveffects.html",
    EnvironmentalSensor: "envsensor.html",
    Followers: "followers.html",
    Geospatial: "geodata.html",
    Geometry2D: "geometry2D.html",
    Geometry3D: "geometry3D.html",
    Grouping: "group.html",
    "H-Anim": "hanim.html",
    Interpolation: "interp.html",
    KeyDeviceSensor: "keyboard.html",
    Layering: "layering.html",
    Layout: "layout.html",
    Lighting: "lighting.html",
    Navigation: "navigation.html",
    Networking: "networking.html",
    NURBS: "nurbs.html",
    ParticleSystems: "particle_systems.html",
    Picking: "picking.html",
    PointingDeviceSensor: "pointingsensor.html",
    Rendering: "rendering.html",
    RigidBodyPhysics: "rigid_physics.html",
    Scripting: "scripting.html",
    Shaders: "shaders.html",
    Shape: "shape.html",
    Sound: "sound.html",
    Text: "text.html",
    Texturing3D: "texture3D.html",
    Texturing: "texturing.html",
    Time: "time.html",
    EventUtilities: "utils.html",
    VolumeRendering: "volume.html"
};

x3dom.docs.specBaseURL = "http://www.web3d.org/x3d/specifications/ISO-IEC-19775-1.2-X3D-AbstractSpecification/Part01/components/";


// the dump-nodetype tree functionality in a function
x3dom.docs.getNodeTreeInfo = function() {

    // Create the nodetype hierarchy
    var tn, t;
    var types = "";

    var objInArray = function(array, obj) {
        for(var i=0; i<array.length; i++) {
            if (array[i] === obj) {
                return true;
            }
        }
        return false;
    };

    var dump = function(t, indent) {
        for (var i=0; i<indent; i++) {
            types += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;";
        }

        types += "<a href='" +
                        x3dom.docs.specBaseURL + x3dom.docs.specURLMap[x3dom.nodeTypes[t]._compName] + "#" + t +
                        "' style='color:black; text-decoration:none; font-weight:bold;'>" +
                        t + "</a> &nbsp; <a href='" +
                        x3dom.docs.specBaseURL + x3dom.docs.specURLMap[x3dom.nodeTypes[t]._compName] +
                        "' style='color:black; text-decoration:none; font-style:italic;'>" +
                        x3dom.nodeTypes[t]._compName + "</a><br/>";

        for (var i in x3dom.nodeTypes[t].childTypes[t]) {
            dump(x3dom.nodeTypes[t].childTypes[t][i], indent+1);
        }
    };

    for (tn in x3dom.nodeTypes) {
     var t = x3dom.nodeTypes[tn];
         if (t.childTypes === undefined) {
             t.childTypes = {};
         }

         while (t.superClass) {
             if (t.superClass.childTypes[t.superClass._typeName] === undefined) {
                 t.superClass.childTypes[t.superClass._typeName] = [];
             }
             if (!objInArray(t.superClass.childTypes[t.superClass._typeName], t._typeName)) {
                 t.superClass.childTypes[t.superClass._typeName].push(t._typeName);
             }
             t = t.superClass;
         }
     }

    dump("X3DNode", 0);

    return "<div class='x3dom-doc-nodes-tree'>" + types + "</div>";
};


x3dom.docs.getComponentInfo = function() {
    // Dump nodetypes by component
    // but first sort alphabetically
    var components = [];
    var component;
    var result = "";
    var c, cn;

    for (c in x3dom.components) {
        components.push(c);
    }
    components.sort();

    //for (var c in x3dom.components) {
    for (cn in components) {
        c = components[cn];
        component = x3dom.components[c];
        result += "<h2><a href='" +
            x3dom.docs.specBaseURL + x3dom.docs.specURLMap[c] +
            "' style='color:black; text-decoration:none; font-style:italic;'>" +
            c + "</a></h2>";

        result += "<ul style='list-style-type:circle;'>";

        //var $ul = $("#components ul:last");
        for (var t in component) {
            result += "<li><a href='" +
                x3dom.docs.specBaseURL + x3dom.docs.specURLMap[c] + "#" + t +
                    "' style='color:black; text-decoration:none; font-weight:bold;'>" +
                    t + "</a></li>";
        }
        result += "</ul>";
    }

    return result;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

x3dom.shader = {};

x3dom.shader.PICKING = "picking";
x3dom.shader.PICKING_24 = "picking24";
x3dom.shader.PICKING_ID = "pickingId";
x3dom.shader.PICKING_COLOR = "pickingColor";
x3dom.shader.PICKING_TEXCOORD = "pickingTexCoord";
x3dom.shader.FRONTGROUND_TEXTURE = "frontgroundTexture";
x3dom.shader.BACKGROUND_TEXTURE = "backgroundTexture";
x3dom.shader.BACKGROUND_SKYTEXTURE = "backgroundSkyTexture";
x3dom.shader.BACKGROUND_CUBETEXTURE = "backgroundCubeTexture";
x3dom.shader.BLUR = "blur";
x3dom.shader.DEPTH = "depth";
x3dom.shader.NORMAL = "normal";
x3dom.shader.TEXTURE_REFINEMENT = "textureRefinement";
x3dom.shader.SSAO = "ssao";

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */
 
 
/*******************************************************************************
* Material
********************************************************************************/
 x3dom.shader.material = function() {
	var shaderPart = "uniform vec3  diffuseColor;\n" +
					 "uniform vec3  specularColor;\n" +
					 "uniform vec3  emissiveColor;\n" +
					 "uniform float shininess;\n" +
					 "uniform float transparency;\n" +
					 "uniform float ambientIntensity;\n";
					 
	return shaderPart;
};

/*******************************************************************************
 * TwoSidedMaterial
 ********************************************************************************/
x3dom.shader.twoSidedMaterial = function() {
    var shaderPart = "uniform vec3  backDiffuseColor;\n" +
                     "uniform vec3  backSpecularColor;\n" +
                     "uniform vec3  backEmissiveColor;\n" +
                     "uniform float backShininess;\n" +
                     "uniform float backTransparency;\n" +
                     "uniform float backAmbientIntensity;\n";

    return shaderPart;
};
						 
/*******************************************************************************
* Fog
********************************************************************************/						 
x3dom.shader.fog = function() {

	var shaderPart = "uniform vec3  fogColor;\n" +
					 "uniform float fogType;\n" +
					 "uniform float fogRange;\n" +
					 "varying vec3 fragEyePosition;\n" +
					 "float calcFog(in vec3 eye) {\n" +
					 "   float f0 = 0.0;\n" +      
					 "   if(fogType == 0.0) {\n" +
					 "       if(length(eye) < fogRange){\n" +
					 "           f0 = (fogRange-length(eye)) / fogRange;\n" +
					 "       }\n" +
					 "   }else{\n" +
					 "       if(length(eye) < fogRange){\n" +
					 "           f0 = exp(-length(eye) / (fogRange-length(eye) ) );\n" +
					 "       }\n" +
					 "   }\n" +
					 "   f0 = clamp(f0, 0.0, 1.0);\n" +
					 "   return f0;\n" +
					 "}\n";
					 
	return shaderPart;
};

/*******************************************************************************
 * Clipplane
 ********************************************************************************/
x3dom.shader.clipPlanes = function(numClipPlanes) {
    var shaderPart = "", c;

    for(c=0; c<numClipPlanes; c++) {
        shaderPart += 	"uniform vec4 clipPlane"+c+"_Plane;\n";
        shaderPart += 	"uniform float clipPlane"+c+"_CappingStrength;\n";
        shaderPart += 	"uniform vec3 clipPlane"+c+"_CappingColor;\n";
    }

    shaderPart += "vec3 calculateClipPlanes() {\n";

    for(c=0; c<numClipPlanes; c++) {
        shaderPart += "    vec4 clipPlane" + c + " = clipPlane" + c + "_Plane * viewMatrixInverse;\n";
        shaderPart += "    float dist" + c + " = dot(fragPosition, clipPlane" + c + ");\n";
    }

    shaderPart += "    if( ";

    for(c=0; c<numClipPlanes; c++) {
        if(c!=0) {
            shaderPart += " || ";
        }
        shaderPart += "dist" + c + " < 0.0" ;
    }

    shaderPart += " ) ";
    shaderPart += "{ discard; }\n";

    for (c = 0; c < numClipPlanes; c++) {
        shaderPart += "    if( abs(dist" + c + ") < clipPlane" + c + "_CappingStrength ) ";
        shaderPart += "{ return clipPlane" + c + "_CappingColor; }\n";
    }

    shaderPart += "    return vec3(-1.0, -1.0, -1.0);\n";

    shaderPart += "}\n";

    return shaderPart;
};

/*******************************************************************************
* Gamma correction support: initial declaration
********************************************************************************/
x3dom.shader.gammaCorrectionDecl = function(properties) {
	var shaderPart = "";
    if (properties.GAMMACORRECTION === "none") {
        // do not emit any declaration. 1.0 shall behave 'as without gamma'.
    } else if (properties.GAMMACORRECTION === "fastlinear") {
        // This is a slightly optimized gamma correction
        // which uses a gamma of 2.0 instead of 2.2. Gamma 2.0 is less costly
        // to encode in terms of cycles as sqrt() is usually optimized
        // in hardware.
        shaderPart += "vec4 gammaEncode(vec4 color){\n" +
                      "  vec4 tmp = sqrt(color);\n" +
                      "  return vec4(tmp.rgb, color.a);\n" +
                      "}\n";

        shaderPart += "vec4 gammaDecode(vec4 color){\n" +
                      "  vec4 tmp = color * color;\n" +
                      "  return vec4(tmp.rgb, color.a);\n" +
                      "}\n";

        shaderPart += "vec3 gammaEncode(vec3 color){\n" +
                      "  return sqrt(color);\n" +
                      "}\n";

        shaderPart += "vec3 gammaDecode(vec3 color){\n" +
                      "  return (color * color);\n" +
                      "}\n";
    } else {
        // The preferred implementation compensating for a gamma of 2.2, which closely
        // follows sRGB; alpha remains linear
        // minor opt: 1.0 / 2.2 = 0.4545454545454545
        shaderPart += "const vec4 gammaEncode4Vector = vec4(0.4545454545454545, 0.4545454545454545, 0.4545454545454545, 1.0);\n";
        shaderPart += "const vec4 gammaDecode4Vector = vec4(2.2, 2.2, 2.2, 1.0);\n";

        shaderPart += "vec4 gammaEncode(vec4 color){\n" +
                      "    return pow(color, gammaEncode4Vector);\n" +
                      "}\n";

        shaderPart += "vec4 gammaDecode(vec4 color){\n" +
                      "    return pow(color, gammaDecode4Vector);\n" +
                      "}\n";

        // RGB; minor opt: 1.0 / 2.2 = 0.4545454545454545
        shaderPart += "const vec3 gammaEncode3Vector = vec3(0.4545454545454545, 0.4545454545454545, 0.4545454545454545);\n";
        shaderPart += "const vec3 gammaDecode3Vector = vec3(2.2, 2.2, 2.2);\n";

        shaderPart += "vec3 gammaEncode(vec3 color){\n" +
                      "    return pow(color, gammaEncode3Vector);\n" +
                      "}\n";

        shaderPart += "vec3 gammaDecode(vec3 color){\n" +
                      "    return pow(color, gammaDecode3Vector);\n" +
                      "}\n";
    }
	return shaderPart;
};

/*******************************************************************************
* Gamma correction support: encoding and decoding of given expressions
* 
* Unlike other shader parts these javascript functions wrap the same-named gamma
* correction shader functions (if applicable). When gamma correction is  not used,
* the expression will be returned verbatim. Consequently, any terminating semicolon
* is to be issued by the caller.
********************************************************************************/
x3dom.shader.encodeGamma = function(properties, expr) {
    if (properties.GAMMACORRECTION === "none") {
        // Naive implementation: no-op, return verbatim
        return expr;
    } else {
        // The 2.0 and 2.2 cases are transparent at the call site
        return "gammaEncode (" + expr + ")";
    }
};

x3dom.shader.decodeGamma = function(properties, expr) {
    if (properties.GAMMACORRECTION === "none") {
        // Naive implementation: no-op, return verbatim
        return expr;
    } else {
        // The 2.0 and 2.2 cases are transparent at the call site
        return "gammaDecode (" + expr + ")";
    }
};

/*******************************************************************************
* Shadow
********************************************************************************/
x3dom.shader.rgbaPacking = function() {
	var shaderPart = "";
		shaderPart += 	
					"vec4 packDepth(float depth){\n" +
					"	depth = (depth + 1.0)*0.5;\n" +
					"	vec4 outVal = vec4(1.0, 255.0, 65025.0, 160581375.0) * depth;\n" +
					"	outVal = fract(outVal);\n" +
					"  	outVal -= outVal.yzww * vec4(1.0/255.0, 1.0/255.0, 1.0/255.0, 0.0);\n" +
					"  	return outVal;\n" +
					"}\n";
		
		shaderPart += 	
					"float unpackDepth(vec4 color){\n" +
					"	float depth = dot(color, vec4(1.0, 1.0/255.0, 1.0/65025.0, 1.0/160581375.0));\n" +
					"	return (2.0*depth - 1.0);\n" + 
					"}\n";
	return shaderPart;
};

x3dom.shader.shadowRendering = function(){
	//determine if and how much a given position is influenced by given light
	var shaderPart = "";
	shaderPart +=
				"float getLightInfluence(float lType, float lShadowIntensity, float lOn, vec3 lLocation, vec3 lDirection, " + 
				"float lCutOffAngle, float lBeamWidth, vec3 lAttenuation, float lRadius, vec3 eyeCoords) {\n" +
				"	if (lOn == 0.0 || lShadowIntensity == 0.0){ return 0.0;\n" +
				"	} else if (lType == 0.0) {\n" +
				"		return 1.0;\n" +
				"	} else {\n" +
				"   	float attenuation = 0.0;\n" +
				"   	vec3 lightVec = (lLocation - (eyeCoords));\n" +
				"   	float distance = length(lightVec);\n" +
				"		lightVec = normalize(lightVec);\n" +
				"		eyeCoords = normalize(-eyeCoords);\n" +
				"   	if(lRadius == 0.0 || distance <= lRadius) {\n" +
				"       	attenuation = 1.0 / max(lAttenuation.x + lAttenuation.y * distance + lAttenuation.z * (distance * distance), 1.0);\n" +
				"		}\n" +
				" 		if (lType == 1.0) return attenuation;\n" +
				"   	float spotAngle = acos(max(0.0, dot(-lightVec, normalize(lDirection))));\n" +
				"   	if(spotAngle >= lCutOffAngle) return 0.0;\n" +
				"   	else if(spotAngle <= lBeamWidth) return attenuation;\n" +
				"   	else return attenuation * (spotAngle - lCutOffAngle) / (lBeamWidth - lCutOffAngle);\n" +
				"	}\n" +
				"}\n";
	
	// get light space depth of view sample and all entries of the shadow map
	shaderPart += 	
				"void getShadowValues(inout vec4 shadowMapValues, inout float viewSampleDepth, in mat4 lightMatrix, in vec4 worldCoords, in sampler2D shadowMap){\n" +
				"	vec4 lightSpaceCoords = lightMatrix*worldCoords;\n" +
				"	vec3 lightSpaceCoordsCart = lightSpaceCoords.xyz / lightSpaceCoords.w;\n" +
				"	vec2 textureCoords = (lightSpaceCoordsCart.xy + 1.0)*0.5;\n" +
				"	viewSampleDepth = lightSpaceCoordsCart.z;\n" +	
				"	shadowMapValues = texture2D(shadowMap, textureCoords);\n";
	if (!x3dom.caps.FP_TEXTURES  || x3dom.caps.MOBILE)
		shaderPart +=	"	shadowMapValues = vec4(1.0,1.0,unpackDepth(shadowMapValues),1.0);\n";
	shaderPart +="}\n";

	
	// get light space depth of view sample and all entries of the shadow map for point lights
	shaderPart += 	
				"void getShadowValuesPointLight(inout vec4 shadowMapValues, inout float viewSampleDepth, in vec3 lLocation, in vec4 worldCoords, in mat4 lightViewMatrix," +
				"in mat4 lMatrix_0, in mat4 lMatrix_1, in mat4 lMatrix_2, in mat4 lMatrix_3, in mat4 lMatrix_4, in mat4 lMatrix_5," +
				"in sampler2D shadowMap_0, in sampler2D shadowMap_1, in sampler2D shadowMap_2, in sampler2D shadowMap_3,"+
				"in sampler2D shadowMap_4, in sampler2D shadowMap_5){\n" +
				"	vec4 transformed = lightViewMatrix * worldCoords;\n" +
				"	vec3 lightVec = normalize(transformed.xyz/transformed.w);\n"+
				"	vec3 lightVecAbs = abs(lightVec);\n" +
				"	float maximum = max(max(lightVecAbs.x, lightVecAbs.y),lightVecAbs.z);\n" +
				"	if (lightVecAbs.x == maximum) {\n" +
				"		if (lightVec.x < 0.0) getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_3,worldCoords,shadowMap_3);\n"+		//right
				"		else getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_1,worldCoords,shadowMap_1);\n" +						//left
				"	}\n" +
				"	else if (lightVecAbs.y == maximum) {\n" +
				"		if (lightVec.y < 0.0) getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_4,worldCoords,shadowMap_4);\n"+		//front
				"		else getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_5,worldCoords,shadowMap_5);\n" +						//back
				"	}\n" +
				"	else if (lightVec.z < 0.0) getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_0,worldCoords,shadowMap_0);\n"+	//bottom
				"	else getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_2,worldCoords,shadowMap_2);\n" +							//top
				"}\n";	

	// get light space depth of view sample and all entries of the shadow map
	shaderPart += 	
				"void getShadowValuesCascaded(inout vec4 shadowMapValues, inout float viewSampleDepth, in vec4 worldCoords, in float eyeDepth, in mat4 lMatrix_0, in mat4 lMatrix_1, in mat4 lMatrix_2,"+
				"in mat4 lMatrix_3, in mat4 lMatrix_4, in mat4 lMatrix_5, in sampler2D shadowMap_0, in sampler2D shadowMap_1, in sampler2D shadowMap_2,"+
				"in sampler2D shadowMap_3, in sampler2D shadowMap_4, in sampler2D shadowMap_5, in float split_0, in float split_1, in float split_2, in float split_3, in float split_4){\n" +
				"	if (eyeDepth < split_0) getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_0, worldCoords, shadowMap_0);\n" +
				"	else if (eyeDepth < split_1) getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_1, worldCoords, shadowMap_1);\n" +
				"	else if (eyeDepth < split_2) getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_2, worldCoords, shadowMap_2);\n" +
				"	else if (eyeDepth < split_3) getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_3, worldCoords, shadowMap_3);\n" +
				"	else if (eyeDepth < split_4) getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_4, worldCoords, shadowMap_4);\n" +
				"	else getShadowValues(shadowMapValues, viewSampleDepth, lMatrix_5, worldCoords, shadowMap_5);\n" +																
				"}\n";	
				
	shaderPart += 	
				"float ESM(float shadowMapDepth, float viewSampleDepth, float offset){\n";
	if (!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE)
			shaderPart += 	"	return exp(-80.0*(1.0-offset)*(viewSampleDepth - shadowMapDepth));\n";
	else 	shaderPart += 	"	return shadowMapDepth * exp(-80.0*(1.0-offset)*viewSampleDepth);\n";
	shaderPart +="}\n";	


	shaderPart += 	
				"float VSM(vec2 moments, float viewSampleDepth, float offset){\n"+
				"	viewSampleDepth = (viewSampleDepth + 1.0) * 0.5;\n" +
				"	if (viewSampleDepth <= moments.x) return 1.0;\n" +
				"	float variance = moments.y - moments.x * moments.x;\n" +
				"	variance = max(variance, 0.00002 + offset*0.01);\n" +
				"	float d = viewSampleDepth - moments.x;\n" +
				"	return variance/(variance + d*d);\n" +
				"}\n";	
			
	
	return shaderPart;
};


/*******************************************************************************
* Light
********************************************************************************/
x3dom.shader.light = function(numLights) {

	var shaderPart = "";

	for(var l=0; l<numLights; l++) {
		shaderPart += 	"uniform float light"+l+"_On;\n" +
						"uniform float light"+l+"_Type;\n" +
						"uniform vec3  light"+l+"_Location;\n" +
						"uniform vec3  light"+l+"_Direction;\n" +
						"uniform vec3  light"+l+"_Color;\n" +
						"uniform vec3  light"+l+"_Attenuation;\n" +
						"uniform float light"+l+"_Radius;\n" +
						"uniform float light"+l+"_Intensity;\n" +
						"uniform float light"+l+"_AmbientIntensity;\n" +
						"uniform float light"+l+"_BeamWidth;\n" +
						"uniform float light"+l+"_CutOffAngle;\n" +
						"uniform float light"+l+"_ShadowIntensity;\n";
	}
	
	shaderPart += 	"vec3 lighting(in float lType, in vec3 lLocation, in vec3 lDirection, in vec3 lColor, in vec3 lAttenuation, " +
					"in float lRadius, in float lIntensity, in float lAmbientIntensity, in float lBeamWidth, " +
					"in float lCutOffAngle, in vec3 N, in vec3 V, float shin, float ambIntensity)\n" +
					"{\n" +
					"   vec3 L;\n" +
					"   float spot = 1.0, attentuation = 0.0;\n" +
					"   if(lType == 0.0) {\n" +
					"       L = -normalize(lDirection);\n" +
					"		V = normalize(V);\n" +
					"		attentuation = 1.0;\n" +
					"   } else{\n" +
					"       L = (lLocation - (-V));\n" +
					"       float d = length(L);\n" +
					"		L = normalize(L);\n" +
					"		V = normalize(V);\n" +
					"       if(lRadius == 0.0 || d <= lRadius) {\n" +
					"       	attentuation = 1.0 / max(lAttenuation.x + lAttenuation.y * d + lAttenuation.z * (d * d), 1.0);\n" +
					"		}\n" +
					"       if(lType == 2.0) {\n" +
					"           float spotAngle = acos(max(0.0, dot(-L, normalize(lDirection))));\n" +
					"           if(spotAngle >= lCutOffAngle) spot = 0.0;\n" +
					"           else if(spotAngle <= lBeamWidth) spot = 1.0;\n" +
					"           else spot = (spotAngle - lCutOffAngle ) / (lBeamWidth - lCutOffAngle);\n" +
					"       }\n" +
					"   }\n" +
					
					"   vec3  H = normalize( L + V );\n" +
					"   float NdotL = clamp(dot(L, N), 0.0, 1.0);\n" +
					"   float NdotH = clamp(dot(H, N), 0.0, 1.0);\n" +
					
					"   float ambientFactor  = lAmbientIntensity * ambIntensity;\n" +
					"   float diffuseFactor  = lIntensity * NdotL;\n" +
					"   float specularFactor = lIntensity * pow(NdotH, shin*128.0);\n" +
                    "   return vec3(ambientFactor, diffuseFactor, specularFactor) * attentuation * spot;\n" +
					//"   ambient  += lColor * ambientFactor * attentuation * spot;\n" +
					//"   diffuse  += lColor * diffuseFactor * attentuation * spot;\n" +
					//"   specular += lColor * specularFactor * attentuation * spot;\n" +
                    "}\n";
						
	return shaderPart;
};

/*******************************************************************************
 * cotangent_frame
 ********************************************************************************/
x3dom.shader.TBNCalculation = function() {
    var shaderPart = "";

    shaderPart += "mat3 cotangent_frame(vec3 N, vec3 p, vec2 uv)\n" +
        "{\n" +
        "    // get edge vectors of the pixel triangle\n" +
        "    vec3 dp1 = dFdx( p );\n" +
        "    vec3 dp2 = dFdy( p );\n" +
        "    vec2 duv1 = dFdx( uv );\n" +
        "    vec2 duv2 = dFdy( uv );\n" +
        "\n" +
        "    // solve the linear system\n" +
        "    vec3 dp2perp = cross( dp2, N );\n" +
        "    vec3 dp1perp = cross( N, dp1 );\n" +
        "    vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;\n" +
        "    vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;\n" +
        "\n" +
        "    // construct a scale-invariant frame\n" +
        "    float invmax = inversesqrt( max( dot(T,T), dot(B,B) ) );\n" +
        "    return mat3( T * invmax, B * invmax, N );\n" +
        "}\n\n";

    shaderPart += "vec3 perturb_normal( vec3 N, vec3 V, vec2 texcoord )\n" +
        "{\n" +
        "    // assume N, the interpolated vertex normal and\n" +
        "    // V, the view vector (vertex to eye)\n" +
        "    vec3 map = texture2D(normalMap, texcoord ).xyz;\n" +
		"    map = 2.0 * map - 1.0;\n" +
        "    mat3 TBN = cotangent_frame(N, -V, texcoord);\n" +
        "    return normalize(TBN * map);\n" +
        "}\n\n";

    return shaderPart;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final Shader program
 */
x3dom.shader.DynamicShader = function(gl, properties)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl, properties);
	var fragmentShader 	= this.generateFragmentShader(gl, properties);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.DynamicShader.prototype.generateVertexShader = function(gl, properties)
{
	var shader = "";
	
	/*******************************************************************************
	* Generate dynamic attributes & uniforms & varyings
	********************************************************************************/
	
	//Default Matrices
	shader += "uniform mat4 modelViewMatrix;\n";
    shader += "uniform mat4 modelViewProjectionMatrix;\n";
	
	//Positions
	if(properties.POSCOMPONENTS == 3) {
		shader += "attribute vec3 position;\n";
	} else if(properties.POSCOMPONENTS == 4) {
		shader += "attribute vec4 position;\n";
	}
	
  //IG stuff
	if(properties.IMAGEGEOMETRY) {
		shader += "uniform vec3 IG_bboxMin;\n";
		shader += "uniform vec3 IG_bboxMax;\n";
		shader += "uniform float IG_coordTextureWidth;\n";
		shader += "uniform float IG_coordTextureHeight;\n";
		shader += "uniform vec2 IG_implicitMeshSize;\n";
		
		for( var i = 0; i < properties.IG_PRECISION; i++ ) {
			shader += "uniform sampler2D IG_coords" + i + "\n;";
		}
		
		if(properties.IG_INDEXED) {
			shader += "uniform sampler2D IG_index;\n";
			shader += "uniform float IG_indexTextureWidth;\n";
			shader += "uniform float IG_indexTextureHeight;\n";
		}
	}
    
    //PG stuff
    if (properties.POPGEOMETRY) {
        shader += "uniform float PG_precisionLevel;\n";
        shader += "uniform float PG_powPrecision;\n";
        shader += "uniform vec3 PG_maxBBSize;\n";
        shader += "uniform vec3 PG_bbMin;\n";
        shader += "uniform vec3 PG_bbMaxModF;\n";
        shader += "uniform vec3 PG_bboxShiftVec;\n";
        shader += "uniform float PG_numAnchorVertices;\n";
        shader += "attribute float PG_vertexID;\n";
    }
	
	//Normals
	if(properties.LIGHTS) {
		if(properties.NORMALMAP && properties.NORMALSPACE == "OBJECT") {
			//do nothing
		} else {
			shader += "varying vec3 fragNormal;\n";
			shader += "uniform mat4 normalMatrix;\n";

            if(properties.IMAGEGEOMETRY) {
                shader += "uniform sampler2D IG_normals;\n";
            } else {
                if (properties.NORCOMPONENTS == 2) {
                    if (properties.POSCOMPONENTS != 4) {
                        shader += "attribute vec2 normal;\n";
                    }
                } else if (properties.NORCOMPONENTS == 3) {
                    shader += "attribute vec3 normal;\n";
                }
            }
		}
	}
		
	//Init Colors. In the vertex shader we do not compute any color so
    //is is safe to ignore gamma here.
	if(properties.VERTEXCOLOR) {	
		if(properties.IMAGEGEOMETRY) {
			shader += "uniform sampler2D IG_colors;\n";
			if(properties.COLCOMPONENTS == 3) {
				shader += "varying vec3 fragColor;\n";
			} else if(properties.COLCOMPONENTS == 4) {
				shader += "varying vec4 fragColor;\n";
			}
		} else {
			if(properties.COLCOMPONENTS == 3) {
				shader += "attribute vec3 color;\n";
				shader += "varying vec3 fragColor;\n";
			} else if(properties.COLCOMPONENTS == 4) {
				shader += "attribute vec4 color;\n";
				shader += "varying vec4 fragColor;\n";
			}
		}
	}

	//Textures
	if(properties.TEXTURED) {
		shader += "varying vec2 fragTexcoord;\n";
		if(!properties.SPHEREMAPPING) {
			if(properties.IMAGEGEOMETRY) {
				shader += "uniform sampler2D IG_texCoords;\n";
			} else if (!properties.IS_PARTICLE) {
				shader += "attribute vec2 texcoord;\n";
			}
		}
		if(properties.TEXTRAFO){
			shader += "uniform mat4 texTrafoMatrix;\n";
		}

		if(properties.NORMALMAP && properties.NORMALSPACE == "TANGENT" && !x3dom.caps.STD_DERIVATIVES) {

            x3dom.debug.logWarning("Your System doesn't support the 'OES_STANDARD_DERIVATIVES' Extension. " +
                                   "You must set tangents and binormals manually via the FloatVertexAttribute-Node " +
                                   "to use normal maps");

			shader += "attribute vec3 tangent;\n";
			shader += "attribute vec3 binormal;\n";
			shader += "varying vec3 fragTangent;\n";
			shader += "varying vec3 fragBinormal;\n";
		}

		if(properties.CUBEMAP) {
			shader += "varying vec3 fragViewDir;\n";
			shader += "uniform mat4 viewMatrix;\n";
		}
        if (properties.DISPLACEMENTMAP) {
            shader += "uniform sampler2D displacementMap;\n";
            shader += "uniform float displacementFactor;\n";
            shader += "uniform float displacementWidth;\n";
            shader += "uniform float displacementHeight;\n";
            shader += "uniform float displacementAxis;\n";
        }
        if (properties.DIFFPLACEMENTMAP) {
			shader += "uniform sampler2D diffuseDisplacementMap;\n";
			shader += "uniform float displacementFactor;\n";
			shader += "uniform float displacementWidth;\n";
			shader += "uniform float displacementHeight;\n";
			shader += "uniform float displacementAxis;\n";
		}
	}

	if (properties.VERTEXID) {
		shader += "attribute float id;\n";
		shader += "varying float fragID;\n";
	}

    if (properties.IS_PARTICLE) {
        shader += "attribute vec3 particleSize;\n";
    }
	
	//Lights & Fog
	if(properties.LIGHTS || properties.FOG || properties.CLIPPLANES){
		shader += "uniform vec3 eyePosition;\n";
		shader += "varying vec4 fragPosition;\n";
		if(properties.FOG) {
			shader += "varying vec3 fragEyePosition;\n";
		}
	}
	
	//Bounding Boxes
	if(properties.REQUIREBBOX) {
		shader += "uniform vec3 bgCenter;\n";
		shader += "uniform vec3 bgSize;\n";
		shader += "uniform float bgPrecisionMax;\n";
	}
	if(properties.REQUIREBBOXNOR) {
		shader += "uniform float bgPrecisionNorMax;\n";
	}
	if(properties.REQUIREBBOXCOL) {
		shader += "uniform float bgPrecisionColMax;\n";
	}
	if(properties.REQUIREBBOXTEX) {
		shader += "uniform float bgPrecisionTexMax;\n";
	}

      
	/*******************************************************************************
	* Generate main function
	********************************************************************************/
	shader += "void main(void) {\n";
  
	/*******************************************************************************
	* Start of special Geometry switch
	********************************************************************************/
	if(properties.IMAGEGEOMETRY) {
		//Indices
		if(properties.IG_INDEXED) {
			shader += "vec2 halfPixel = vec2(0.5/IG_indexTextureWidth,0.5/IG_indexTextureHeight);\n";
			shader += "vec2 IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_indexTextureWidth), position.y*(IG_implicitMeshSize.y/IG_indexTextureHeight)) + halfPixel;\n";
			shader += "vec2 IG_indices = texture2D( IG_index, IG_texCoord ).rg;\n";
			shader += "halfPixel = vec2(0.5/IG_coordTextureWidth,0.5/IG_coordTextureHeight);\n";
			shader += "IG_texCoord = (IG_indices * 0.996108948) + halfPixel;\n";
		} else {
			shader += "vec2 halfPixel = vec2(0.5/IG_coordTextureWidth, 0.5/IG_coordTextureHeight);\n";
			shader += "vec2 IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_coordTextureWidth), position.y*(IG_implicitMeshSize.y/IG_coordTextureHeight)) + halfPixel;\n";
		}
		
		//Positions
		shader += "vec3 temp = vec3(0.0, 0.0, 0.0);\n";
		shader += "vec3 vertPosition = vec3(0.0, 0.0, 0.0);\n";
		
		for(var i=0; i<properties.IG_PRECISION; i++) {
			shader += "temp = 255.0 * texture2D( IG_coords" + i + ", IG_texCoord ).rgb;\n";
			shader += "vertPosition *= 256.0;\n";
			shader += "vertPosition += temp;\n";
		}
		
		shader += "vertPosition /= (pow(2.0, 8.0 * " + properties.IG_PRECISION + ".0) - 1.0);\n";
		shader += "vertPosition = vertPosition * (IG_bboxMax - IG_bboxMin) + IG_bboxMin;\n";
	
		//Normals
		if(properties.LIGHTS) {
			shader += "vec3 vertNormal = texture2D( IG_normals, IG_texCoord ).rgb;\n";
			shader += "vertNormal = vertNormal * 2.0 - 1.0;\n";
		}
		
		//Colors
		if(properties.VERTEXCOLOR) {
			if(properties.COLCOMPONENTS == 3) {
				shader += "fragColor = texture2D( IG_colors, IG_texCoord ).rgb;\n";
			} else if(properties.COLCOMPONENTS == 4) {
				shader += "fragColor = texture2D( IG_colors, IG_texCoord ).rgba;\n";
			}
		}
		
		//TexCoords
		if(properties.TEXTURED) {
			shader += "vec4 IG_doubleTexCoords = texture2D( IG_texCoords, IG_texCoord );\n";
			shader += "vec2 vertTexCoord;";
			shader += "vertTexCoord.r = (IG_doubleTexCoords.r * 0.996108948) + (IG_doubleTexCoords.b * 0.003891051);\n";
			shader += "vertTexCoord.g = (IG_doubleTexCoords.g * 0.996108948) + (IG_doubleTexCoords.a * 0.003891051);\n";
		}
	} else {
		//Positions
		shader += "vec3 vertPosition = position.xyz;\n";
		
        if (properties.POPGEOMETRY) {
          //compute offset using bounding box and test if vertPosition <= PG_bbMaxModF 
          shader += "vec3 offsetVec = step(vertPosition / bgPrecisionMax, PG_bbMaxModF) * PG_bboxShiftVec;\n";
          
          //coordinate truncation, computation of current maximum possible value
          //PG_vertexID currently mimics use of gl_VertexID
          shader += "if ((PG_precisionLevel <= 2.0) || PG_vertexID >= PG_numAnchorVertices) {\n";
          shader += "   vertPosition = floor(vertPosition / PG_powPrecision) * PG_powPrecision;\n";
          shader += "   vertPosition /= (65536.0 - PG_powPrecision);\n";
          shader += "}\n";
          shader += "else {\n";
          shader += "   vertPosition /= bgPrecisionMax;\n";
          shader += "}\n";
          
          //translate coordinates, where PG_bbMin := floor(bbMin / size) 
          shader += "vertPosition = (vertPosition + offsetVec + PG_bbMin) * PG_maxBBSize;\n";
        }
		else if(properties.REQUIREBBOX) {
          shader += "vertPosition = bgCenter + bgSize * vertPosition / bgPrecisionMax;\n";
		}

		//Normals
		if(properties.LIGHTS) {
			if(properties.NORCOMPONENTS == 2) {
				if (properties.POSCOMPONENTS == 4) {
					// (theta, phi) encoded in low/high byte of position.w
					shader += "vec3 vertNormal = vec3(position.w / 256.0); \n";
					shader += "vertNormal.x = floor(vertNormal.x) / 255.0; \n";
					shader += "vertNormal.y = fract(vertNormal.y) * 1.00392156862745; \n"; //256.0 / 255.0
				}
				else if (properties.REQUIREBBOXNOR) {
					shader += "vec3 vertNormal = vec3(normal.xy, 0.0) / bgPrecisionNorMax;\n";                    
				}

				shader += "vec2 thetaPhi = 3.14159265358979 * vec2(vertNormal.x, vertNormal.y*2.0-1.0); \n";
				shader += "vec4 sinCosThetaPhi = sin( vec4(thetaPhi, thetaPhi + 1.5707963267949) ); \n";

				shader += "vertNormal.x = sinCosThetaPhi.x * sinCosThetaPhi.w; \n";
				shader += "vertNormal.y = sinCosThetaPhi.x * sinCosThetaPhi.y; \n";
				shader += "vertNormal.z = sinCosThetaPhi.z; \n";                
			} else {
				if (properties.NORMALMAP && properties.NORMALSPACE == "OBJECT") {
					//Nothing to do
				} else {
					shader += "vec3 vertNormal = normal;\n";
					if (properties.REQUIREBBOXNOR) {
						shader += "vertNormal = vertNormal / bgPrecisionNorMax;\n";
					}
					if (properties.POPGEOMETRY) {
						shader += "vertNormal = 2.0*vertNormal - 1.0;\n";
					}
				}
			}
		}
		
		//Colors
		if(properties.VERTEXCOLOR){
			shader += "fragColor = color;\n";
            
			if(properties.REQUIREBBOXCOL) {
                shader += "fragColor = fragColor / bgPrecisionColMax;\n";
			}                              
		}
		
		//TexCoords
		if( (properties.TEXTURED) && !properties.SPHEREMAPPING) {
            if (properties.IS_PARTICLE) {
                shader += "vec2 vertTexCoord = vec2(0.0);\n";
            }
            else {
                shader += "vec2 vertTexCoord = texcoord;\n";
                if (properties.REQUIREBBOXTEX) {
                    shader += "vertTexCoord = vertTexCoord / bgPrecisionTexMax;\n";
                }
            }
		}
	}
	
	/*******************************************************************************
	* End of special Geometry switch
	********************************************************************************/
	
	
	//Normals
	if(properties.LIGHTS) {
        if ((properties.DISPLACEMENTMAP || properties.DIFFPLACEMENTMAP) && !properties.NORMALMAP) {
          //Map-Tile Size
          shader += "float dx = 1.0 / displacementWidth;\n";
          shader += "float dy = 1.0 / displacementHeight;\n";

          //Get the 4 Vertex Neighbours
          if (properties.DISPLACEMENTMAP)
          {
              shader += "float s1 = texture2D(displacementMap, vec2(vertTexCoord.x - dx, 1.0 - vertTexCoord.y)).r;\n";		 //left
              shader += "float s2 = texture2D(displacementMap, vec2(vertTexCoord.x, 1.0 - vertTexCoord.y - dy)).r;\n";		 //bottom
              shader += "float s3 = texture2D(displacementMap, vec2(vertTexCoord.x + dx, 1.0 - vertTexCoord.y)).r;\n";	   //right
              shader += "float s4 = texture2D(displacementMap, vec2(vertTexCoord.x, 1.0 - vertTexCoord.y + dy)).r;\n";		 //top
          }
          else if (properties.DIFFPLACEMENTMAP)
          {
              shader += "float s1 = texture2D(diffuseDisplacementMap, vec2(vertTexCoord.x - dx, 1.0 - vertTexCoord.y)).a;\n";		 //left
              shader += "float s2 = texture2D(diffuseDisplacementMap, vec2(vertTexCoord.x, 1.0 - vertTexCoord.y - dy)).a;\n";		 //bottom
              shader += "float s3 = texture2D(diffuseDisplacementMap, vec2(vertTexCoord.x + dx, 1.0 - vertTexCoord.y)).a;\n";	   //right
              shader += "float s4 = texture2D(diffuseDisplacementMap, vec2(vertTexCoord.x, 1.0 - vertTexCoord.y + dy)).a;\n";		 //top
          }

          //Coeffiecent for smooth/sharp Normals
          shader += "float coef = displacementFactor;\n";

          //Calculate the Normal
          shader += "vec3 calcNormal;\n";

          shader += "if (displacementAxis == 0.0) {\n"; //X
          shader += "calcNormal = vec3((s1 - s3) * coef, -5.0, (s2 - s4) * coef);\n";
          shader += "} else if(displacementAxis == 1.0) {\n"; //Y
          shader += "calcNormal = vec3((s1 - s3) * coef, -5.0, (s2 - s4) * coef);\n";
          shader += "} else {\n"; //Z
          shader += "calcNormal = vec3((s1 - s3) * coef, -(s2 - s4) * coef, 5.0);\n";
          shader += "}\n";


          //normalized Normal
          shader += "calcNormal = normalize(calcNormal);\n";
          shader += "fragNormal = (normalMatrix * vec4(calcNormal, 0.0)).xyz;\n";
        }
		else if (properties.NORMALMAP && properties.NORMALSPACE == "OBJECT") {
			//Nothing to do
		}
        else
        {
            shader += "fragNormal = (normalMatrix * vec4(vertNormal, 0.0)).xyz;\n";
        }
	}
    
	//Textures
	if(properties.TEXTURED){
		if(properties.CUBEMAP) {
			shader += "fragViewDir = (viewMatrix[3].xyz);\n";
		}
		if (properties.SPHEREMAPPING) {
			shader += " fragTexcoord = 0.5 + fragNormal.xy / 2.0;\n";
		} else if(properties.TEXTRAFO) {
			shader += " fragTexcoord = (texTrafoMatrix * vec4(vertTexCoord, 1.0, 1.0)).xy;\n";
		} else {
			shader += " fragTexcoord = vertTexCoord;\n";
			
			// LOD LUT HACK ###
			if (properties.POPGEOMETRY && x3dom.debug.usePrecisionLevelAsTexCoord === true)
			    // remap texCoords to texel middle with w = 16 and tc' := 1 / (2 * w) + tc * (w - 1) / w
                shader += "fragTexcoord = vec2(0.03125 + 0.9375 * (PG_precisionLevel / 16.0), 1.0);";
			// LOD LUT HACK ###
		}

		if(properties.NORMALMAP && properties.NORMALSPACE == "TANGENT" && !x3dom.caps.STD_DERIVATIVES) {
			shader += "fragTangent  = (normalMatrix * vec4(tangent, 0.0)).xyz;\n";
			shader += "fragBinormal = (normalMatrix * vec4(binormal, 0.0)).xyz;\n";
		}
	}
	
	//Lights & Fog
	if(properties.LIGHTS || properties.FOG || properties.CLIPPLANES){
		shader += "fragPosition = (modelViewMatrix * vec4(vertPosition, 1.0));\n";
		if (properties.FOG) {
			shader += "fragEyePosition = eyePosition - fragPosition.xyz;\n";
		}
	}

    //Vertex ID's
    if (properties.MULTIDIFFALPMAP) {
        shader += "fragID = id;\n";
    }
  
	//Displacement
    if (properties.DISPLACEMENTMAP) {
        shader += "vertPosition += normalize(vertNormal) * texture2D(displacementMap, vec2(fragTexcoord.x, 1.0-fragTexcoord.y)).r * displacementFactor;\n";
    }
    else if (properties.DIFFPLACEMENTMAP)
    {
        shader += "vertPosition += normalize(vertNormal) * texture2D(diffuseDisplacementMap, vec2(fragTexcoord.x, 1.0-fragTexcoord.y)).a * displacementFactor;\n";
    }
  
    //Positions
	shader += "gl_Position = modelViewProjectionMatrix * vec4(vertPosition, 1.0);\n";

    //Set point size
    if (properties.IS_PARTICLE) {
        shader += "float spriteDist = (gl_Position.w > 0.000001) ? gl_Position.w : 0.000001;\n";
        shader += "float pointSize = floor(length(particleSize) * 256.0 / spriteDist + 0.5);\n";
        shader += "gl_PointSize = clamp(pointSize, 2.0, 256.0);\n";
    }
    else {
        shader += "gl_PointSize = 2.0;\n";
    }
  
	//END OF SHADER
	shader += "}\n";
	
	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
        x3dom.debug.logInfo("VERTEX:\n" + shader);
		x3dom.debug.logError("VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.DynamicShader.prototype.generateFragmentShader = function(gl, properties)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += " precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";
	
	/*******************************************************************************
	* Generate dynamic uniforms & varyings
	********************************************************************************/
	
	//Default Matrices
	shader += "uniform mat4 modelMatrix;\n";
    shader += "uniform mat4 modelViewMatrix;\n";
    shader += "uniform mat4 viewMatrixInverse;\n";
	
	//Material
    shader += x3dom.shader.material();

    if (properties.TWOSIDEDMAT) {
        shader += x3dom.shader.twoSidedMaterial();
    }
	
	//Colors
	if(properties.VERTEXCOLOR){
		if(properties.COLCOMPONENTS == 3){
			shader += "varying vec3 fragColor;  \n";
		}else if(properties.COLCOMPONENTS == 4){
			shader += "varying vec4 fragColor;  \n";
		}
	}

    if(properties.CUBEMAP || properties.CLIPPLANES)
    {
        shader += "uniform mat4 modelViewMatrixInverse;\n";
    }

	//VertexID
	if (properties.VERTEXID) {
		shader += "varying float fragID;\n";

		if (properties.MULTIDIFFALPMAP) {
			shader += "uniform sampler2D multiDiffuseAlphaMap;\n";
			shader += "uniform float multiDiffuseAlphaWidth;\n";
			shader += "uniform float multiDiffuseAlphaHeight;\n";
		}
		if (properties.MULTIEMIAMBMAP) {
			shader += "uniform sampler2D multiEmissiveAmbientMap;\n";
			shader += "uniform float multiEmissiveAmbientWidth;\n";
			shader += "uniform float multiEmissiveAmbientHeight;\n";
		}
		if (properties.MULTISPECSHINMAP) {
			shader += "uniform sampler2D multiSpecularShininessMap;\n";
			shader += "uniform float multiSpecularShininessWidth;\n";
			shader += "uniform float multiSpecularShininessHeight;\n";
		}
		if (properties.MULTIVISMAP) {
			shader += "uniform sampler2D multiVisibilityMap;\n";
			shader += "uniform float multiVisibilityWidth;\n";
			shader += "uniform float multiVisibilityHeight;\n";
		}
	}
	
	//Textures
	if(properties.TEXTURED) {
		shader += "varying vec2 fragTexcoord;\n";
		if((properties.TEXTURED || properties.DIFFUSEMAP)) {
			shader += "uniform sampler2D diffuseMap;\n";
		}
		if(properties.CUBEMAP) {
			shader += "uniform samplerCube environmentMap;\n";
			shader += "varying vec3 fragViewDir;\n";
            shader += "uniform float environmentFactor;\n";

		}
		if(properties.SPECMAP){
			shader += "uniform sampler2D specularMap;\n";
		}
        if(properties.SHINMAP){
            shader += "uniform sampler2D shininessMap;\n";
        }
        if (properties.DISPLACEMENTMAP) {
          shader += "uniform sampler2D displacementMap;\n";
          shader += "uniform float displacementWidth;\n";
          shader += "uniform float displacementHeight;\n";
        }
        if (properties.DIFFPLACEMENTMAP) {
            shader += "uniform sampler2D diffuseDisplacementMap;\n";
            shader += "uniform float displacementWidth;\n";
            shader += "uniform float displacementHeight;\n";
        }
        if(properties.NORMALMAP){
            shader += "uniform sampler2D normalMap;\n";

			if(properties.NORMALSPACE == "TANGENT") {

				if (x3dom.caps.STD_DERIVATIVES) {
					shader += "#extension GL_OES_standard_derivatives:enable\n";
					shader += x3dom.shader.TBNCalculation();
				} else {
					shader += "varying vec3 fragTangent;\n";
					shader += "varying vec3 fragBinormal;\n";
				}
			} else if(properties.NORMALSPACE == "OBJECT") {

				shader += "uniform mat4 normalMatrix;\n";
			}
        }
	}
	
	//Fog
	if(properties.FOG) {
		shader += x3dom.shader.fog();
	}

    if(properties.LIGHTS || properties.CLIPPLANES)
    {
        shader += "varying vec4 fragPosition;\n";
        shader += "uniform float isOrthoView;\n";
    }

	//Lights
	if(properties.LIGHTS) {

		if(properties.NORMALMAP && properties.NORMALSPACE == "OBJECT") {
			//do nothing
		} else {
			shader += "varying vec3 fragNormal;\n";
		}

		shader += x3dom.shader.light(properties.LIGHTS);
	}

    if(properties.CLIPPLANES) {
        shader += x3dom.shader.clipPlanes(properties.CLIPPLANES);
    }

    // Declare gamma correction for color computation (see property "GAMMACORRECTION")
    shader += x3dom.shader.gammaCorrectionDecl(properties);
 
 
	/*******************************************************************************
	* Generate main function
	********************************************************************************/
	shader += "void main(void) {\n";


    if(properties.CLIPPLANES)
    {
        shader += "vec3 cappingColor = calculateClipPlanes();\n";
    }
	
	//Init color. In the fragment shader we are treating color linear by
    //gamma-adjusting actively before doing lighting computations. At the end
    //the color value is encoded again. See shader propery GAMMACORRECTION.
    shader += "vec4 color;\n";
	shader += "color.rgb = " + x3dom.shader.decodeGamma(properties, "diffuseColor") + ";\n";
	shader += "color.a = 1.0 - transparency;\n";

    shader += "vec3 _emissiveColor     = emissiveColor;\n";
    shader += "float _shininess        = shininess;\n";
    shader += "vec3 _specularColor     = specularColor;\n";
    shader += "float _ambientIntensity = ambientIntensity;\n";
	shader += "float _transparency     = transparency;\n";

    if (properties.MULTIVISMAP || properties.MULTIDIFFALPMAP || properties.MULTISPECSHINMAP || properties.MULTIEMIAMBMAP) {
        shader += "vec2 idCoord;\n";
        shader += "float roundedIDVisibility = floor(fragID+0.5);\n";
        shader += "float roundedIDMaterial = floor(fragID+0.5);\n";
        shader += "if(!gl_FrontFacing) {\n";
        shader += "    roundedIDMaterial = floor(fragID + (multiDiffuseAlphaWidth*multiDiffuseAlphaWidth) + 0.5);\n";
        shader += "}\n";
    }

    if (properties.MULTIVISMAP) {
        shader += "idCoord.x = mod(roundedIDVisibility, multiVisibilityWidth) * (1.0 / multiVisibilityWidth) + (0.5 / multiVisibilityWidth);\n";
        shader += "idCoord.y = floor(roundedIDVisibility / multiVisibilityWidth) * (1.0 / multiVisibilityHeight) + (0.5 / multiVisibilityHeight);\n";
        shader += "vec4 visibility = texture2D( multiVisibilityMap, idCoord );\n";
        shader += "if (visibility.r < 1.0) discard; \n";
    }

    if (properties.MULTIDIFFALPMAP) {
        shader += "idCoord.x = mod(roundedIDMaterial, multiDiffuseAlphaWidth) * (1.0 / multiDiffuseAlphaWidth) + (0.5 / multiDiffuseAlphaWidth);\n";
        shader += "idCoord.y = floor(roundedIDMaterial / multiDiffuseAlphaWidth) * (1.0 / multiDiffuseAlphaHeight) + (0.5 / multiDiffuseAlphaHeight);\n";
        shader += "vec4 diffAlpha = texture2D( multiDiffuseAlphaMap, idCoord );\n";
        shader += "color.rgb = " + x3dom.shader.decodeGamma(properties, "diffAlpha.rgb") + ";\n";
		shader += "_transparency = 1.0 - diffAlpha.a;\n";
        shader += "color.a = diffAlpha.a;\n";
    }

    if (properties.MULTIEMIAMBMAP) {
        shader += "idCoord.x = mod(roundedIDMaterial, multiDiffuseAlphaWidth) * (1.0 / multiDiffuseAlphaWidth) + (0.5 / multiDiffuseAlphaWidth);\n";
        shader += "idCoord.y = floor(roundedIDMaterial / multiDiffuseAlphaWidth) * (1.0 / multiDiffuseAlphaHeight) + (0.5 / multiDiffuseAlphaHeight);\n";
        shader += "vec4 emiAmb = texture2D( multiEmissiveAmbientMap, idCoord );\n";
        shader += "_emissiveColor = emiAmb.rgb;\n";
        shader += "_ambientIntensity = emiAmb.a;\n";
    }
			
	if(properties.VERTEXCOLOR) {
		if(properties.COLCOMPONENTS === 3){
			shader += "color.rgb = " + x3dom.shader.decodeGamma(properties,"fragColor") + ";\n";
		}else if(properties.COLCOMPONENTS === 4){
			shader += "color = " + x3dom.shader.decodeGamma(properties, "fragColor") + ";\n";
		}
	}
	
	if(properties.LIGHTS) {
		shader += "vec3 ambient   = vec3(0.0, 0.0, 0.0);\n";
		shader += "vec3 diffuse   = vec3(0.0, 0.0, 0.0);\n";
		shader += "vec3 specular  = vec3(0.0, 0.0, 0.0);\n";
        shader += "vec3 eye;\n";
        shader += "if ( isOrthoView > 0.0 ) {\n";
        shader += "    eye = vec3(0.0, 0.0, 1.0);\n";
        shader += "} else {\n";
        shader += "    eye = -fragPosition.xyz;\n";
        shader += "}\n";

		if(properties.NORMALMAP && properties.NORMALSPACE == "OBJECT") {
			shader += "vec3 normal  = vec3(0.0, 0.0, 0.0);\n";
		} else {
			shader += "vec3 normal 	  = normalize(fragNormal);\n";
		}

		//Normalmap
		if(properties.NORMALMAP){

			if(properties.NORMALSPACE == "TANGENT") {

				shader += "vec3 n = normal;\n";

				if (x3dom.caps.STD_DERIVATIVES) {
					shader += "normal = perturb_normal( n, fragPosition.xyz, vec2(fragTexcoord.x, 1.0-fragTexcoord.y) );\n";
				} else {
					shader += "vec3 t = normalize( fragTangent );\n";
					shader += "vec3 b = normalize( fragBinormal );\n";
					shader += "mat3 tangentToWorld = mat3(t, b, n);\n";

					shader += "normal = texture2D( normalMap, vec2(fragTexcoord.x, 1.0-fragTexcoord.y) ).rgb;\n";
					shader += "normal = 2.0 * normal - 1.0;\n";
					shader += "normal = normalize( normal * tangentToWorld );\n";

					shader += "normal.y = -normal.y;\n";
					shader += "normal.x = -normal.x;\n";
				}

			} else if(properties.NORMALSPACE == "OBJECT") {
				shader += "normal = texture2D( normalMap, vec2(fragTexcoord.x, 1.0-fragTexcoord.y) ).rgb;\n";
				shader += "normal = 2.0 * normal - 1.0;\n";
				shader += "normal = (normalMatrix * vec4(normal, 0.0)).xyz;\n";
				shader += "normal = normalize(normal);\n";
			}

		}

        if(properties.SHINMAP){
            shader += "_shininess = texture2D( shininessMap, vec2(fragTexcoord.x, 1.0-fragTexcoord.y) ).r;\n";
        }

        //Specularmap
        if(properties.SPECMAP) {
            shader += "_specularColor = " + x3dom.shader.decodeGamma(properties, "texture2D(specularMap, vec2(fragTexcoord.x, 1.0-fragTexcoord.y)).rgb") + ";\n";
        }

        if (properties.MULTISPECSHINMAP) {
            shader += "idCoord.x = mod(roundedIDMaterial, multiSpecularShininessWidth) * (1.0 / multiSpecularShininessWidth) + (0.5 / multiSpecularShininessWidth);\n";
            shader += "idCoord.y = floor(roundedIDMaterial / multiSpecularShininessWidth) * (1.0 / multiSpecularShininessHeight) + (0.5 / multiSpecularShininessHeight);\n";
            shader += "vec4 specShin = texture2D( multiSpecularShininessMap, idCoord );\n";
            shader += "_specularColor = specShin.rgb;\n";
            shader += "_shininess = specShin.a;\n";
        }
		
		//Solid
		if(!properties.SOLID || properties.TWOSIDEDMAT) {
			shader += "if (dot(normal, eye) < 0.0) {\n";
			shader += "  normal *= -1.0;\n";
            shader += "}\n";
		}

        if(properties.SEPARATEBACKMAT) {
            shader += "  if(!gl_FrontFacing) {\n";
            shader += "    color.rgb = " + x3dom.shader.decodeGamma(properties, "backDiffuseColor") + ";\n";
            shader += "    color.a = 1.0 - backTransparency;\n";
			shader += "    _transparency = 1.0 - backTransparency;\n";
            shader += "    _shininess = backShininess;\n";
            shader += "    _emissiveColor = backEmissiveColor;\n";
            shader += "    _specularColor = backSpecularColor;\n";
            shader += "    _ambientIntensity = backAmbientIntensity;\n";
            shader += "  }\n";
        }

		
		//Calculate lights
        if (properties.LIGHTS) {
            shader += "vec3 ads;\n";

            for(var l=0; l<properties.LIGHTS; l++) {
                var lightCol = "light"+l+"_Color";
                shader += "ads = lighting(light"+l+"_Type, " +
                                    "light"+l+"_Location, " +
                                    "light"+l+"_Direction, " +
                                    lightCol + ", " +
                                    "light"+l+"_Attenuation, " +
                                    "light"+l+"_Radius, " +
                                    "light"+l+"_Intensity, " +
                                    "light"+l+"_AmbientIntensity, " +
                                    "light"+l+"_BeamWidth, " +
                                    "light"+l+"_CutOffAngle, " +
                                    "normal, eye, _shininess, _ambientIntensity);\n";
                shader += "   ambient  += " + lightCol + " * ads.r;\n" +
                          "   diffuse  += " + lightCol + " * ads.g;\n" +
                          "   specular += " + lightCol + " * ads.b;\n";
            }

            shader += "ambient = max(ambient, 0.0);\n";
            shader += "diffuse = max(diffuse, 0.0);\n";
            shader += "specular = max(specular, 0.0);\n";
        }
		
		//Textures
		if(properties.TEXTURED && ( properties.DIFFUSEMAP || properties.DIFFPLACEMENTMAP || properties.TEXT || properties.CUBEMAP)){
			if(properties.CUBEMAP) {
				shader += "vec3 viewDir = normalize(fragViewDir);\n";
				shader += "vec3 reflected = reflect(-eye, normal);\n";
				shader += "reflected = (modelViewMatrixInverse * vec4(reflected, 0.0)).xyz;\n";
				shader += "vec4 envColor = " + x3dom.shader.decodeGamma(properties, "textureCube(environmentMap, reflected)") + ";\n";
				shader += "color.a *= envColor.a;\n";
			}

			if (properties.DIFFPLACEMENTMAP)
            {
                shader += "vec2 texCoord = vec2(fragTexcoord.x, 1.0-fragTexcoord.y);\n";
                shader += "vec4 texColor = texture2D(diffuseDisplacementMap, texCoord);\n";
            }
            else if(properties.DIFFUSEMAP || properties.TEXT)
            {
                if (properties.PIXELTEX) {
                    shader += "vec2 texCoord = fragTexcoord;\n";
                } else {
                    shader += "vec2 texCoord = vec2(fragTexcoord.x, 1.0-fragTexcoord.y);\n";
                }
				shader += "vec4 texColor = " + x3dom.shader.decodeGamma(properties, "texture2D(diffuseMap, texCoord)") + ";\n";
				shader += "color.a *= texColor.a;\n";
			}

			if(properties.BLENDING){
				shader += "color.rgb = (_emissiveColor + max(ambient + diffuse, 0.0) * color.rgb + specular*_specularColor);\n";

				if(properties.DIFFUSEMAP || properties.TEXT) {
					shader += "color.rgb *= texColor.rgb;\n";
				}
				if(properties.CUBEMAP) {
					shader += "color.rgb *= mix(vec3(1.0,1.0,1.0), envColor.rgb, environmentFactor);\n";
				}
			}else{
				shader += "color.rgb = (_emissiveColor + max(ambient + diffuse, 0.0) * texColor.rgb + specular*_specularColor);\n";
			}
		}else{
			shader += "color.rgb = (_emissiveColor + max(ambient + diffuse, 0.0) * color.rgb + specular*_specularColor);\n";
		}
		
	} else {
		if (properties.APPMAT && !properties.VERTEXCOLOR && !properties.TEXTURED) {
			shader += "color = vec4(0.0, 0.0, 0.0, 1.0 - _transparency);\n";
		}
		
		if(properties.TEXTURED && ( properties.DIFFUSEMAP || properties.DIFFPLACEMENTMAP || properties.TEXT )){
            if (properties.PIXELTEX) {
                shader += "vec2 texCoord = fragTexcoord;\n";
            } else {
                shader += "vec2 texCoord = vec2(fragTexcoord.x, 1.0-fragTexcoord.y);\n";
            }

            if (properties.IS_PARTICLE) {
                shader += "texCoord = clamp(gl_PointCoord, 0.01, 0.99);\n";
            }
            shader += "vec4 texColor = " + x3dom.shader.decodeGamma(properties, "texture2D(diffuseMap, texCoord)") + ";\n";
            shader += "color.a = texColor.a;\n";

			if(properties.BLENDING || properties.IS_PARTICLE){
				shader += "color.rgb += _emissiveColor.rgb;\n";
				shader += "color.rgb *= texColor.rgb;\n";
			} else {
				shader += "color = texColor;\n";
			}
		} else if(!properties.VERTEXCOLOR && !properties.POINTLINE2D){
			shader += "color.rgb += _emissiveColor;\n";
		} else if(!properties.VERTEXCOLOR && properties.POINTLINE2D){
			shader += "color.rgb = _emissiveColor;\n";
            if (properties.IS_PARTICLE) {
                shader += "float pAlpha = 1.0 - clamp(length((gl_PointCoord - 0.5) * 2.0), 0.0, 1.0);\n";
                shader += "color.rgb *= vec3(pAlpha);\n";
                shader += "color.a = pAlpha;\n";
            }
		} else if (properties.IS_PARTICLE) {
            shader += "float pAlpha = 1.0 - clamp(length((gl_PointCoord - 0.5) * 2.0), 0.0, 1.0);\n";
            shader += "color.rgb *= vec3(pAlpha);\n";
            shader += "color.a = pAlpha;\n";
        }
	}

    if(properties.CLIPPLANES)
    {
        shader += "if (cappingColor.r != -1.0) {\n";
        shader += "    color.rgb = cappingColor;\n";
        shader += "}\n";
    }

	//Kill pixel
	if(properties.TEXT) {
		shader += "if (color.a <= 0.5) discard;\n";
	} else {
		shader += "if (color.a <= " + properties.ALPHATHRESHOLD + ") discard;\n";
	}



    //Output the gamma encoded result.
    shader += "color = clamp(color, 0.0, 1.0);\n";
    shader += "color = " + x3dom.shader.encodeGamma(properties, "color") + ";\n";
	
	//Fog
	if(properties.FOG){
		shader += "float f0 = calcFog(fragEyePosition);\n";
		shader += "color.rgb = fogColor * (1.0-f0) + f0 * (color.rgb);\n";
	}

    shader += "gl_FragColor = color;\n";
	
	//End Of Shader
	shader += "}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
        x3dom.debug.logInfo("FRAGMENT:\n" + shader);
		x3dom.debug.logError("FragmentShader " + gl.getShaderInfoLog(fragmentShader));
	}

	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final Shader program
 */
x3dom.shader.DynamicMobileShader = function(gl, properties)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl, properties);
	var fragmentShader 	= this.generateFragmentShader(gl, properties);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.DynamicMobileShader.prototype.generateVertexShader = function(gl, properties)
{
	var shader = "";
	
	/*******************************************************************************
	* Generate dynamic attributes & uniforms & varyings
	********************************************************************************/
	
	//Material
	shader += x3dom.shader.material();

    if (properties.TWOSIDEDMAT ) {
        shader += x3dom.shader.twoSidedMaterial();
    }
	
	//Default Matrices
	shader += "uniform mat4 normalMatrix;\n";
	shader += "uniform mat4 modelViewMatrix;\n";
    shader += "uniform mat4 modelViewProjectionMatrix;\n";
	
	//Positions
	if(properties.POSCOMPONENTS == 3) {
		shader += "attribute vec3 position;\n";
	} else if(properties.POSCOMPONENTS == 4) {
		shader += "attribute vec4 position;\n";
	}
	
  //IG stuff
	if(properties.IMAGEGEOMETRY) {
		shader += "uniform vec3 IG_bboxMin;\n";
		shader += "uniform vec3 IG_bboxMax;\n";
		shader += "uniform float IG_coordTextureWidth;\n";
		shader += "uniform float IG_coordTextureHeight;\n";
		shader += "uniform vec2 IG_implicitMeshSize;\n";
		
		for( var i = 0; i < properties.IG_PRECISION; i++ ) {
			shader += "uniform sampler2D IG_coords" + i + "\n;";
		}
		
		if(properties.IG_INDEXED) {
			shader += "uniform sampler2D IG_index;\n";
			shader += "uniform float IG_indexTextureWidth;\n";
			shader += "uniform float IG_indexTextureHeight;\n";
		}
	}
	
    //PG stuff
    if (properties.POPGEOMETRY) {
        shader += "uniform float PG_precisionLevel;\n";
        shader += "uniform float PG_powPrecision;\n";
        shader += "uniform vec3 PG_maxBBSize;\n";
        shader += "uniform vec3 PG_bbMin;\n";
        shader += "uniform vec3 PG_bbMaxModF;\n";
        shader += "uniform vec3 PG_bboxShiftVec;\n";
        shader += "uniform float PG_numAnchorVertices;\n";
        shader += "attribute float PG_vertexID;\n";
    }
  
	//Normals
	if(!properties.POINTLINE2D) {
		if(properties.IMAGEGEOMETRY) {		
			shader += "uniform sampler2D IG_normals;\n";	
		} else {
			if(properties.NORCOMPONENTS == 2) {
				if(properties.POSCOMPONENTS != 4) {
					shader += "attribute vec2 normal;\n";
				}
			} else if(properties.NORCOMPONENTS == 3) {
				shader += "attribute vec3 normal;\n";
			}
		}
	}
	
	//Colors
	shader += "varying vec4 fragColor;\n";
	if(properties.VERTEXCOLOR){
		if(properties.IMAGEGEOMETRY) {
			shader += "uniform sampler2D IG_colors;";
		} else {
			if(properties.COLCOMPONENTS == 3){
				shader += "attribute vec3 color;";
			} else if(properties.COLCOMPONENTS == 4) {
				shader += "attribute vec4 color;";
			}
		}
	}
	
	//Textures
	if(properties.TEXTURED) {
		shader += "varying vec2 fragTexcoord;\n";
		if(properties.IMAGEGEOMETRY) {
			shader += "uniform sampler2D IG_texCoords;";
		} else {
			shader += "attribute vec2 texcoord;\n";
		}
		if(properties.TEXTRAFO){
			shader += "uniform mat4 texTrafoMatrix;\n";
		}
		if(!properties.BLENDING) {
			shader += "varying vec3 fragAmbient;\n";
			shader += "varying vec3 fragDiffuse;\n";
		}
		if(properties.CUBEMAP) {
			shader += "varying vec3 fragViewDir;\n";
			shader += "varying vec3 fragNormal;\n";
			shader += "uniform mat4 viewMatrix;\n";
		}
	}
	
	//Fog
	if(properties.FOG) {
		shader += x3dom.shader.fog();
	}
	
	//Lights
	if(properties.LIGHTS) {
		shader += x3dom.shader.light(properties.LIGHTS);
	}
	
	//Bounding Boxes
	if(properties.REQUIREBBOX) {
		shader += "uniform vec3 bgCenter;\n";
		shader += "uniform vec3 bgSize;\n";
		shader += "uniform float bgPrecisionMax;\n";
	}
	if(properties.REQUIREBBOXNOR) {
		shader += "uniform float bgPrecisionNorMax;\n";
	}
	if(properties.REQUIREBBOXCOL) {
		shader += "uniform float bgPrecisionColMax;\n";
	}
	if(properties.REQUIREBBOXTEX) {
		shader += "uniform float bgPrecisionTexMax;\n";
	}

    
	/*******************************************************************************
	* Generate main function
	********************************************************************************/
	shader += "void main(void) {\n";
	
	//Set point size
	shader += "gl_PointSize = 2.0;\n";
	
	/*******************************************************************************
	* Start of ImageGeometry switch
	********************************************************************************/
	if(properties.IMAGEGEOMETRY) {
		//Indices
		if(properties.IG_INDEXED) {
			shader += "vec2 halfPixel = vec2(0.5/IG_indexTextureWidth,0.5/IG_indexTextureHeight);\n";
			shader += "vec2 IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_indexTextureWidth), position.y*(IG_implicitMeshSize.y/IG_indexTextureHeight)) + halfPixel;\n";
			shader += "vec2 IG_indices = texture2D( IG_index, IG_texCoord ).rg;\n";
			shader += "halfPixel = vec2(0.5/IG_coordTextureWidth,0.5/IG_coordTextureHeight);\n";
			shader += "IG_texCoord = (IG_indices * 0.996108948) + halfPixel;\n";
		} else {
			shader += "vec2 halfPixel = vec2(0.5/IG_coordTextureWidth, 0.5/IG_coordTextureHeight);\n";
			shader += "vec2 IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_coordTextureWidth), position.y*(IG_implicitMeshSize.y/IG_coordTextureHeight)) + halfPixel;\n";
		}
		
		//Positions
		shader += "vec3 temp = vec3(0.0, 0.0, 0.0);\n";
		shader += "vec3 vertPosition = vec3(0.0, 0.0, 0.0);\n";
		
		for(var i=0; i<properties.IG_PRECISION; i++) {
			shader += "temp = 255.0 * texture2D( IG_coords" + i + ", IG_texCoord ).rgb;\n";
			shader += "vertPosition *= 256.0;\n";
			shader += "vertPosition += temp;\n";
		}
		
		shader += "vertPosition /= (pow(2.0, 8.0 * " + properties.IG_PRECISION + ".0) - 1.0);\n";
		shader += "vertPosition = vertPosition * (IG_bboxMax - IG_bboxMin) + IG_bboxMin;\n";
	
		//Normals
		if(!properties.POINTLINE2D) {
			shader += "vec3 vertNormal = texture2D( IG_normals, IG_texCoord ).rgb;\n";
			shader += "vertNormal = vertNormal * 2.0 - 1.0;\n";
		}
		
		//Colors
		if(properties.VERTEXCOLOR) {
			if(properties.COLCOMPONENTS == 3) {
				shader += "vec3 vertColor = texture2D( IG_colors, IG_texCoord ).rgb;";
			} else if(properties.COLCOMPONENTS  == 4) {
				shader += "vec4 vertColor = texture2D( IG_colors, IG_texCoord ).rgba;";
			}
		}
		
		//TexCoords
		if(properties.TEXTURED) {
			shader += "vec4 IG_doubleTexCoords = texture2D( IG_texCoords, IG_texCoord );\n";
			shader += "vec2 vertTexCoord;";
			shader += "vertTexCoord.r = (IG_doubleTexCoords.r * 0.996108948) + (IG_doubleTexCoords.b * 0.003891051);\n";
			shader += "vertTexCoord.g = (IG_doubleTexCoords.g * 0.996108948) + (IG_doubleTexCoords.a * 0.003891051);\n";
		}
	} else {
		//Positions
		shader += "vec3 vertPosition = position.xyz;\n";
        
        if (properties.POPGEOMETRY) {
          //compute offset using bounding box and test if vertPosition <= PG_bbMaxModF 
          shader += "vec3 offsetVec = step(vertPosition / bgPrecisionMax, PG_bbMaxModF) * PG_bboxShiftVec;\n";
          
          //coordinate truncation, computation of current maximum possible value
          //PG_vertexID currently mimics use of gl_VertexID
          shader += "if ((PG_precisionLevel <= 2.0) || PG_vertexID >= PG_numAnchorVertices) {\n";
          shader += "   vertPosition = floor(vertPosition / PG_powPrecision) * PG_powPrecision;\n";
          shader += "   vertPosition /= (65536.0 - PG_powPrecision);\n";
          shader += "}\n";
          shader += "else {\n";
          shader += "   vertPosition /= bgPrecisionMax;\n";
          shader += "}\n";
          
          //translate coordinates, where PG_bbMin := floor(bbMin / size) 
          shader += "vertPosition = (vertPosition + offsetVec + PG_bbMin) * PG_maxBBSize;\n";
        }
		else if(properties.REQUIREBBOX) {
          shader += "vertPosition = bgCenter + bgSize * vertPosition / bgPrecisionMax;\n";
		}
	
		//Normals
		if(!properties.POINTLINE2D) {
			if (properties.NORCOMPONENTS == 2) {
				if (properties.POSCOMPONENTS == 4) {
					// (theta, phi) encoded in low/high byte of position.w
					shader += "vec3 vertNormal = vec3(position.w / 256.0); \n";
					shader += "vertNormal.x = floor(vertNormal.x) / 255.0; \n";
					shader += "vertNormal.y = fract(vertNormal.y) * 1.00392156862745; \n"; //256.0 / 255.0
				} else if (properties.REQUIREBBOXNOR) {
					shader += "vec3 vertNormal = vec3(normal.xy, 0.0) / bgPrecisionNorMax;\n";
				} else {
					shader += "vec3 vertNormal = vec3(normal.xy, 0.0);\n";
				}
				
				shader += "vec2 thetaPhi = 3.14159265358979 * vec2(vertNormal.x, vertNormal.y*2.0-1.0); \n";

				// Doing approximation with Taylor series and using cos(x) = sin(x+PI/2)
				shader += "vec4 sinCosThetaPhi = vec4(thetaPhi, thetaPhi + 1.5707963267949); \n";

				shader += "vec4 thetaPhiPow2 = sinCosThetaPhi * sinCosThetaPhi; \n";
				shader += "vec4 thetaPhiPow3 =  thetaPhiPow2  * sinCosThetaPhi; \n";
				shader += "vec4 thetaPhiPow5 =  thetaPhiPow3  * thetaPhiPow2; \n";
				shader += "vec4 thetaPhiPow7 =  thetaPhiPow5  * thetaPhiPow2; \n";
				shader += "vec4 thetaPhiPow9 =  thetaPhiPow7  * thetaPhiPow2; \n";

				shader += "sinCosThetaPhi +=  -0.16666666667   * thetaPhiPow3; \n";
				shader += "sinCosThetaPhi +=   0.00833333333   * thetaPhiPow5; \n";
				shader += "sinCosThetaPhi +=  -0.000198412698  * thetaPhiPow7; \n";
				shader += "sinCosThetaPhi +=   0.0000027557319 * thetaPhiPow9; \n";

				shader += "vertNormal.x = sinCosThetaPhi.x * sinCosThetaPhi.w; \n";
				shader += "vertNormal.y = sinCosThetaPhi.x * sinCosThetaPhi.y; \n";
				shader += "vertNormal.z = sinCosThetaPhi.z; \n";
			} else {
				shader += "vec3 vertNormal = normal;\n";
				if (properties.REQUIREBBOXNOR) {
                    shader += "vertNormal = vertNormal / bgPrecisionNorMax;\n";
				}   
                if (properties.POPGEOMETRY) {
                    shader += "vertNormal = 2.0*vertNormal - 1.0;\n";                    
                }                
			}
		}
		
		//Colors
		if(properties.VERTEXCOLOR) {
			if(properties.COLCOMPONENTS == 3) {
				shader += "vec3 vertColor = color;";
			} else if(properties.COLCOMPONENTS  == 4) {
				shader += "vec4 vertColor = color;";
			}
			if(properties.REQUIREBBOXCOL) {
				shader += "vertColor = vertColor / bgPrecisionColMax;\n";
			}
		}
		
		//TexCoords
		if(properties.TEXTURED) {
			shader += "vec2 vertTexCoord = texcoord;\n";
			if(properties.REQUIREBBOXTEX) {
				shader += "vertTexCoord = vertTexCoord / bgPrecisionTexMax;\n";
			}
		}
	}
	/*******************************************************************************
	* End of ImageGeometry switch
	********************************************************************************/
	
	//positions to model-view-space
	shader += "vec3 positionMV = (modelViewMatrix * vec4(vertPosition, 1.0)).xyz;\n";
	
	//normals to model-view-space
	if(!properties.POINTLINE2D) {
		shader += "vec3 normalMV = normalize( (normalMatrix * vec4(vertNormal, 0.0)).xyz );\n";
	}
	
	shader += "vec3 eye = -positionMV;\n";
	
	//Colors
	if (properties.VERTEXCOLOR) {
		shader += "vec3 rgb = vertColor.rgb;\n";	
		if(properties.COLCOMPONENTS == 4) {
			shader += "float alpha = vertColor.a;\n";
		} else if(properties.COLCOMPONENTS == 3) {
			shader += "float alpha = 1.0 - transparency;\n";
		}
	} else {
		shader += "vec3 rgb = diffuseColor;\n";
		shader += "float alpha = 1.0 - transparency;\n";
	}
	
	//Calc TexCoords
	if(properties.TEXTURED){
		if(properties.CUBEMAP) {
			shader += "fragViewDir = viewMatrix[3].xyz;\n";
			shader += "fragNormal = normalMV;\n";
		} else if(properties.SPHEREMAPPING) {
			shader += " fragTexcoord = 0.5 + normalMV.xy / 2.0;\n";
		} else if(properties.TEXTRAFO) {
			shader += " fragTexcoord = (texTrafoMatrix * vec4(vertTexCoord, 1.0, 1.0)).xy;\n";
		} else {
			shader += " fragTexcoord = vertTexCoord;\n";
			
			// LOD LUT HACK ###
			if (properties.POPGEOMETRY && x3dom.debug.usePrecisionLevelAsTexCoord === true)
			    // remap texCoords to texel middle with w = 16 and tc' := 1 / (2 * w) + tc * (w - 1) / w
                shader += "fragTexcoord = vec2(0.03125 + 0.9375 * (PG_precisionLevel / 16.0), 1.0);";
			// LOD LUT HACK ###
		}
	}
	
	//calc lighting
	if(properties.LIGHTS) {
		shader += "vec3 ambient   = vec3(0.0, 0.0, 0.0);\n";
		shader += "vec3 diffuse   = vec3(0.0, 0.0, 0.0);\n";
		shader += "vec3 specular  = vec3(0.0, 0.0, 0.0);\n";
        shader += "float _shininess     = shininess;\n";
        shader += "vec3 _specularColor  = specularColor;\n";
        shader += "vec3 _emissiveColor  = emissiveColor;\n";
        shader += "float _ambientIntensity = ambientIntensity;\n";
		
		//Solid
		if(!properties.SOLID || properties.TWOSIDEDMAT) {
			shader += "if (dot(normalMV, eye) < 0.0) {\n";
			shader += "	 normalMV *= -1.0;\n";
            if(properties.SEPARATEBACKMAT) {
                shader += "    rgb = backDiffuseColor;\n";
                shader += "    alpha = 1.0 - backTransparency;\n";
                shader += "    _shininess = backShininess;\n";
                shader += "    _emissiveColor = backEmissiveColor;\n";
                shader += "    _specularColor = backSpecularColor;\n";
                shader += "    _ambientIntensity = backAmbientIntensity;\n";
            }
            shader += "  }\n";
		}
		
		//Calculate lighting
        if (properties.LIGHTS) {
            shader += "vec3 ads;\n";

            for(var l=0; l<properties.LIGHTS; l++) {
                var lightCol = "light"+l+"_Color";
                shader += "ads = lighting(light"+l+"_Type, " +
                          "light"+l+"_Location, " +
                          "light"+l+"_Direction, " +
                          lightCol + ", " +
                          "light"+l+"_Attenuation, " +
                          "light"+l+"_Radius, " +
                          "light"+l+"_Intensity, " +
                          "light"+l+"_AmbientIntensity, " +
                          "light"+l+"_BeamWidth, " +
                          "light"+l+"_CutOffAngle, " +
                          "normalMV, eye, _shininess, _ambientIntensity);\n";
                shader += "   ambient  += " + lightCol + " * ads.r;\n" +
                          "   diffuse  += " + lightCol + " * ads.g;\n" +
                          "   specular += " + lightCol + " * ads.b;\n";
            }

            shader += "ambient = max(ambient, 0.0);\n";
            shader += "diffuse = max(diffuse, 0.0);\n";
            shader += "specular = max(specular, 0.0);\n";
        }
		
		//Textures & blending
		if(properties.TEXTURED  && !properties.BLENDING) {
			shader += "fragAmbient = ambient;\n";
			shader += "fragDiffuse = diffuse;\n";
			shader += "fragColor.rgb = (_emissiveColor + specular*_specularColor);\n";
			shader += "fragColor.a = alpha;\n";
		} else {
			shader += "fragColor.rgb = (_emissiveColor + max(ambient + diffuse, 0.0) * rgb + specular*_specularColor);\n";
			shader += "fragColor.a = alpha;\n";
		}
	} else {
		if (properties.APPMAT && !properties.VERTEXCOLOR) {
			shader += "rgb = vec3(0.0, 0.0, 0.0);\n";
		}
		if(properties.TEXTURED && !properties.BLENDING) {
			shader += "fragAmbient = vec3(0.0);\n";
			shader += "fragDiffuse = vec3(1.0);\n";
			shader += "fragColor.rgb = vec3(0.0);\n";
			shader += "fragColor.a = alpha;\n";
		} else if(!properties.VERTEXCOLOR && properties.POINTLINE2D){
			shader += "fragColor.rgb = emissiveColor;\n";
			shader += "fragColor.a = alpha;\n";
		} else {
			shader += "fragColor.rgb = rgb + emissiveColor;\n";
			shader += "fragColor.a = alpha;\n";
		}
	}
	
	//Fog
	if(properties.FOG) {
		shader += "float f0 = calcFog(-positionMV);\n";
		shader += "fragColor.rgb = fogColor * (1.0-f0) + f0 * (fragColor.rgb);\n";
	}

	//Output
	shader += "gl_Position = modelViewProjectionMatrix * vec4(vertPosition, 1.0);\n";
	
	//End of shader
	shader += "}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[DynamicMobileShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.DynamicMobileShader.prototype.generateFragmentShader = function(gl, properties)
{
	var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += "precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";
	
	/*******************************************************************************
	* Generate dynamic uniforms & varyings
	********************************************************************************/
	//Colors
	shader += "varying vec4 fragColor;\n";
	
	//Textures
	if(properties.TEXTURED) {
		if(properties.CUBEMAP) {
			shader += "uniform samplerCube cubeMap;\n";
			shader += "varying vec3 fragViewDir;\n";
			shader += "varying vec3 fragNormal;\n";
			shader += "uniform mat4 modelViewMatrixInverse;\n";
		} else {
			shader += "uniform sampler2D diffuseMap;           \n";
			shader += "varying vec2 fragTexcoord;       \n";
		}
		if(!properties.BLENDING) {
			shader += "varying vec3 fragAmbient;\n";
			shader += "varying vec3 fragDiffuse;\n";
		}
	}
	
	/*******************************************************************************
	* Generate main function
	********************************************************************************/
	shader += "void main(void) {\n";
	
	//Colors
	shader += "vec4 color = fragColor;\n";
	
	//Textures
	if(properties.TEXTURED){
		if(properties.CUBEMAP) {
			shader += "vec3 normal = normalize(fragNormal);\n";
			shader += "vec3 viewDir = normalize(fragViewDir);\n";
			shader += "vec3 reflected = reflect(viewDir, normal);\n";
			shader += "reflected = (modelViewMatrixInverse * vec4(reflected,0.0)).xyz;\n";
			shader += "vec4 texColor = textureCube(cubeMap, reflected);\n";
		} else {
			shader += "vec4 texColor = texture2D(diffuseMap, vec2(fragTexcoord.s, 1.0-fragTexcoord.t));\n";
		}
		if(properties.BLENDING) {
			if(properties.CUBEMAP) {
				shader += "color.rgb = mix(color.rgb, texColor.rgb, vec3(0.75));\n";
				shader += "color.a = texColor.a;\n";
			} else {
				shader += "color.rgb *= texColor.rgb;\n";
				shader += "color.a *= texColor.a;\n";
			}
		} else {
			shader += "color.rgb += max(fragAmbient + fragDiffuse, 0.0) * texColor.rgb;\n";
			shader += "color.a *= texColor.a;\n";
		}
	} 
	
	//Kill pixel
	if(properties.TEXT) {
		shader += "if (color.a <= 0.5) discard;\n";
	} else {
		shader += "if (color.a <= 0.1) discard;\n";
	}
	
	//Output
	shader += "gl_FragColor = clamp(color, 0.0, 1.0);\n";
	
	//End of shader
	shader += "}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[DynamicMobileShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final Shader program
 */
x3dom.shader.DynamicShaderPicking = function(gl, properties, pickMode)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl, properties, pickMode);
	var fragmentShader 	= this.generateFragmentShader(gl, properties, pickMode);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.DynamicShaderPicking.prototype.generateVertexShader = function(gl, properties, pickMode)
{
	var shader = "";
	
	/*******************************************************************************
	* Generate dynamic attributes & uniforms & varyings
	********************************************************************************/

	//Default Matrices
    shader += "uniform mat4 modelMatrix;\n";
    shader += "uniform mat4 modelViewProjectionMatrix;\n";

    shader += "attribute vec3 position;\n";
    shader += "uniform vec3 from;\n";
    shader += "varying vec3 worldCoord;\n";

    if(pickMode == 1) { // Color Picking
        shader += "attribute vec3 color;\n";
        shader += "varying vec3 fragColor;\n";
    } else if(pickMode == 2){ //TexCoord Picking
        shader += "attribute vec2 texcoord;\n";
        shader += "varying vec3 fragColor;\n";
    }

    //Bounding stuff
    if(properties.REQUIREBBOX) {
        shader += "uniform vec3 bgCenter;\n";
        shader += "uniform vec3 bgSize;\n";
        shader += "uniform float bgPrecisionMax;\n";
    }
    if(properties.REQUIREBBOXCOL) {
        shader += "uniform float bgPrecisionColMax;\n";
    }
    if(properties.REQUIREBBOXTEX) {
        shader += "uniform float bgPrecisionTexMax;\n";
    }

    //ShadowID stuff
    if(properties.VERTEXID) {
        shader += "uniform float shadowIDs;\n";

        if (pickMode == 3) { //24bit
            shader += "varying vec3 idCoord;\n";
        } else {
            shader += "varying vec2 idCoord;\n";
        }
        shader += "varying float fragID;\n";
        shader += "attribute float id;\n";
    }
	
    //ImageGeometry stuff
	if(properties.IMAGEGEOMETRY) {
		shader += "uniform vec3 IG_bboxMin;\n";
		shader += "uniform vec3 IG_bboxMax;\n";
		shader += "uniform float IG_coordTextureWidth;\n";
		shader += "uniform float IG_coordTextureHeight;\n";
		shader += "uniform vec2 IG_implicitMeshSize;\n";
		
		for( var i = 0; i < properties.IG_PRECISION; i++ ) {
			shader += "uniform sampler2D IG_coords" + i + "\n;";
		}
		
		if(properties.IG_INDEXED) {
			shader += "uniform sampler2D IG_index;\n";
			shader += "uniform float IG_indexTextureWidth;\n";
			shader += "uniform float IG_indexTextureHeight;\n";
		}
	}
    
    //PopGeometry
    if (properties.POPGEOMETRY) {
        shader += "uniform float PG_precisionLevel;\n";
        shader += "uniform float PG_powPrecision;\n";
        shader += "uniform vec3 PG_maxBBSize;\n";
        shader += "uniform vec3 PG_bbMin;\n";
        shader += "uniform vec3 PG_bbMaxModF;\n";
        shader += "uniform vec3 PG_bboxShiftVec;\n";
        shader += "uniform float PG_numAnchorVertices;\n";
        shader += "attribute float PG_vertexID;\n";
    }

    //ClipPlane stuff
    if(properties.CLIPPLANES) {
        shader += "uniform mat4 modelViewMatrix;\n";
        shader += "varying vec4 fragPosition;\n";
    }

      
	/*******************************************************************************
	* Generate main function
	********************************************************************************/

	shader += "void main(void) {\n";

    shader += "gl_PointSize = 2.0;\n";
    shader += "vec3 pos = position;\n";


    if(properties.VERTEXID) {
       if(pickMode == 0) { //Default Picking
           shader += "idCoord = vec2((id + shadowIDs) / 256.0);\n";
           shader += "idCoord.x = floor(idCoord.x) / 255.0;\n";
           shader += "idCoord.y = fract(idCoord.y) * 1.00392156862745;\n";
           shader += "fragID = id;\n";
       } else if(pickMode == 3) { //Picking with 24bit precision
           //composed id is at least 32 (= 2*16) bit + num bits for max-orig-shape-id
           shader += "float ID = id + shadowIDs;\n";
           //however, let's ignore this and assume a maximum of 24 bits for all id's
           shader += "float h = floor(ID / 256.0);\n";
           shader += "idCoord.x = ID - (h * 256.0);\n";
           shader += "idCoord.z = floor(h / 256.0);\n";
           shader += "idCoord.y = h - (idCoord.z * 256.0);\n";
           shader += "idCoord = idCoord.zyx / 255.0;\n";
           shader += "fragID = id;\n";
       } else if(pickMode == 4) { //Picking with 32bit precision
           shader += "idCoord = vec2((id + shadowIDs) / 256.0);\n";
           shader += "idCoord.x = floor(idCoord.x) / 255.0;\n";
           shader += "idCoord.y = fract(idCoord.y) * 1.00392156862745;\n";
           shader += "fragID = id;\n";
       }
    }

    /*******************************************************************************
     * Start of special Geometry switch
     ********************************************************************************/
    if(properties.IMAGEGEOMETRY) { //ImageGeometry
        if(properties.IG_INDEXED) {
            shader += "vec2 halfPixel = vec2(0.5/IG_indexTextureWidth,0.5/IG_indexTextureHeight);\n";
            shader += "vec2 IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_indexTextureWidth), position.y*(IG_implicitMeshSize.y/IG_indexTextureHeight)) + halfPixel;\n";
            shader += "vec2 IG_indices = texture2D( IG_index, IG_texCoord ).rg;\n";
            shader += "halfPixel = vec2(0.5/IG_coordTextureWidth,0.5/IG_coordTextureHeight);\n";
            shader += "IG_texCoord = (IG_indices * 0.996108948) + halfPixel;\n";
        } else {
            shader += "vec2 halfPixel = vec2(0.5/IG_coordTextureWidth, 0.5/IG_coordTextureHeight);\n";
            shader += "vec2 IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_coordTextureWidth), position.y*(IG_implicitMeshSize.y/IG_coordTextureHeight)) + halfPixel;\n";
        }

        shader += "pos = texture2D( IG_coordinateTexture, IG_texCoord ).rgb;\n";
        shader += "pos = pos * (IG_bboxMax - IG_bboxMin) + IG_bboxMin;\n";
    } else if (properties.POPGEOMETRY) { //PopGeometry
        shader += "vec3 offsetVec = step(pos / bgPrecisionMax, PG_bbMaxModF) * PG_bboxShiftVec;\n";
            shader += "if (PG_precisionLevel <= 2.0) {\n";
            shader += "pos = floor(pos / PG_powPrecision) * PG_powPrecision;\n";
            shader += "pos /= (65536.0 - PG_powPrecision);\n";
            shader += "}\n";
            shader += "else {\n";
            shader += "pos /= bgPrecisionMax;\n";
            shader += "}\n";
            shader += "pos = (pos + offsetVec + PG_bbMin) * PG_maxBBSize;\n";
    } else {

        if(properties.REQUIREBBOX) {
            shader += "pos = bgCenter + bgSize * pos / bgPrecisionMax;\n";
        }

        if(pickMode == 1 && !properties.REQUIREBBOXCOL) { //Color Picking
            shader += "fragColor = color;\n";
        } else if(pickMode == 1 && properties.REQUIREBBOXCOL) { //Color Picking
            shader += "fragColor = color / bgPrecisionColMax;\n";
        } else  if(pickMode == 2 && !properties.REQUIREBBOXTEX) { //TexCoord Picking
            shader += "fragColor = vec3(abs(texcoord.x), abs(texcoord.y), 0.0);\n";
        } else if(pickMode == 2 && properties.REQUIREBBOXTEX) { //TexCoord Picking
            shader += "vec2 texCoord = texcoord / bgPrecisionTexMax;\n";
            shader += "fragColor = vec3(abs(texCoord.x), abs(texCoord.y), 0.0);\n";
        }
    }

    if(properties.CLIPPLANES) {
        shader += "fragPosition = (modelViewMatrix * vec4(pos, 1.0));\n";
    }

    shader += "worldCoord = (modelMatrix * vec4(pos, 1.0)).xyz - from;\n";
    shader += "gl_Position = modelViewProjectionMatrix * vec4(pos, 1.0);\n";


	//END OF SHADER
	shader += "}\n";
	
	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
        x3dom.debug.logInfo("VERTEX:\n" + shader);
		x3dom.debug.logError("VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.DynamicShaderPicking.prototype.generateFragmentShader = function(gl, properties, pickMode)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += " precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";
	
	/*******************************************************************************
	* Generate dynamic uniforms & varyings
	********************************************************************************/

    shader += "uniform float highBit;\n";
    shader += "uniform float lowBit;\n";
    shader += "uniform float sceneSize;\n";
    shader += "varying vec3 worldCoord;\n";

    if(pickMode == 1 || pickMode == 2) {
        shader += "varying vec3 fragColor;\n";
    }

    //ShadowID stuff
    if(properties.VERTEXID) {
        if (pickMode == 3) { //24bit
            shader += "varying vec3 idCoord;\n";
        } else {
            shader += "varying vec2 idCoord;\n";
        }
        shader += "varying float fragID;\n";
    }

    //ClipPlane stuff
    if(properties.CLIPPLANES) {
        shader += "uniform mat4 viewMatrixInverse;\n";
        shader += "varying vec4 fragPosition;\n";
    }

    if (properties.MULTIVISMAP) {
        shader += "uniform sampler2D multiVisibilityMap;\n";
        shader += "uniform float multiVisibilityWidth;\n";
        shader += "uniform float multiVisibilityHeight;\n";
    }

    if(properties.CLIPPLANES) {
        shader += x3dom.shader.clipPlanes(properties.CLIPPLANES);
    }

	/*******************************************************************************
	* Generate main function
	********************************************************************************/
	shader += "void main(void) {\n";

    if(properties.CLIPPLANES)
    {
        shader += "calculateClipPlanes();\n";
    }

    if(pickMode == 1 || pickMode == 2) { //Picking Color || Picking TexCoord
        shader += "vec4 color = vec4(fragColor, lowBit);\n";
    } else if(pickMode == 4) { //Picking with 32bit precision
        shader += "vec4 color = vec4(highBit, lowBit, 0.0, 0.0);\n";
    } else {
        shader += "vec4 color = vec4(0.0, 0.0, highBit, lowBit);\n";
    }

    if(properties.VERTEXID) {
        if(pickMode == 0 || pickMode == 4) { //Default Picking || Picking with 32bit precision
            shader += "color.ba = idCoord;\n";
        } else if(pickMode == 3) { //Picking with 24bit precision
            shader += "color.gba = idCoord;\n";
        }

        if (properties.MULTIVISMAP) {
            shader += "vec2 idTexCoord;\n";
            shader += "float roundedID = floor(fragID+0.5);\n";
            shader += "idTexCoord.x = (mod(roundedID, multiVisibilityWidth)) * (1.0 / multiVisibilityWidth) + (0.5 / multiVisibilityWidth);\n";
            shader += "idTexCoord.y = (floor(roundedID / multiVisibilityHeight)) * (1.0 / multiVisibilityHeight) + (0.5 / multiVisibilityHeight);\n";
            shader += "vec4 visibility = texture2D( multiVisibilityMap, idTexCoord );\n";
            shader += "if (visibility.r < 1.0) discard; \n";
        }
    }

    if(pickMode != 1 && pickMode != 2) {
        shader += "float d = length(worldCoord) / sceneSize;\n";
    }

    if(pickMode == 0) { //Default Picking
        shader += "vec2 comp = fract(d * vec2(256.0, 1.0));\n";
        shader += "color.rg = comp - (comp.rr * vec2(0.0, 1.0/256.0));\n";
    } else if(pickMode == 3) { //Picking with 24bit precision
        shader += "color.r = d;\n";
    }

    shader += "gl_FragColor = color;\n";

    //END OF SHADER
    shader += "}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
        x3dom.debug.logInfo("FRAGMENT:\n" + shader);
		x3dom.debug.logError("FragmentShader " + gl.getShaderInfoLog(fragmentShader));
	}

	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final ShadowShader program
 */
x3dom.shader.DynamicShadowShader = function(gl, properties)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl, properties);
	var fragmentShader 	= this.generateFragmentShader(gl, properties);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.DynamicShadowShader.prototype.generateVertexShader = function(gl, properties)
{
	var shader = "";

	//Shadow stuff
	shader += "attribute vec3 position;\n";
	shader += "uniform mat4 modelViewProjectionMatrix;\n";
	shader += "varying vec4 projCoords;\n";

	//ShadowID stuff
	if(properties.VERTEXID) {
		shader += "varying float fragID;\n";
		shader += "attribute float id;\n";
	}

	//Bounding stuff
	if(properties.REQUIREBBOX) {
		shader += "uniform vec3 bgCenter;\n";
		shader += "uniform vec3 bgSize;\n";
		shader += "uniform float bgPrecisionMax;\n";
	}

	//ImageGeometry stuff
	if(properties.IMAGEGEOMETRY) {
		shader += "uniform vec3 IG_bboxMin;\n";
		shader += "uniform vec3 IG_bboxMax;\n";
		shader += "uniform float IG_coordTextureWidth;\n";
		shader += "uniform float IG_coordTextureHeight;\n";
		shader += "uniform vec2 IG_implicitMeshSize;\n";

		for( var i = 0; i < properties.IG_PRECISION; i++ ) {
			shader += "uniform sampler2D IG_coords" + i + "\n;";
		}

		if(properties.IG_INDEXED) {
			shader += "uniform sampler2D IG_index;\n";
			shader += "uniform float IG_indexTextureWidth;\n";
			shader += "uniform float IG_indexTextureHeight;\n";
		}
	}

	//PopGeometry stuff
	if (properties.POPGEOMETRY) {
		shader += "uniform float PG_precisionLevel;\n";
		shader += "uniform float PG_powPrecision;\n";
		shader += "uniform vec3 PG_maxBBSize;\n";
		shader += "uniform vec3 PG_bbMin;\n";
		shader += "uniform vec3 PG_bbMaxModF;\n";
		shader += "uniform vec3 PG_bboxShiftVec;\n";
		shader += "uniform float PG_numAnchorVertices;\n";
		shader += "attribute float PG_vertexID;\n";
	}

	//ClipPlane stuff
	if(properties.CLIPPLANES) {
		shader += "uniform mat4 modelViewMatrix;\n";
		shader += "varying vec4 fragPosition;\n";
	}

	/*******************************************************************************
	 * Generate main function
	 ********************************************************************************/
	shader += "void main(void) {\n";
	shader += "    vec3 pos = position;\n";

	/*******************************************************************************
	 * Start of special Geometry switch
	 ********************************************************************************/
	if(properties.IMAGEGEOMETRY) { //ImageGeometry
		if(properties.IG_INDEXED) {
			shader += "    vec2 halfPixel = vec2(0.5/IG_indexTextureWidth,0.5/IG_indexTextureHeight);\n";
			shader += "    vec2 IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_indexTextureWidth), position.y*(IG_implicitMeshSize.y/IG_indexTextureHeight)) + halfPixel;\n";
			shader += "    vec2 IG_indices = texture2D( IG_index, IG_texCoord ).rg;\n";
			shader += "    halfPixel = vec2(0.5/IG_coordTextureWidth,0.5/IG_coordTextureHeight);\n";
			shader += "    IG_texCoord = (IG_indices * 0.996108948) + halfPixel;\n";
		} else {
			shader += "    vec2 halfPixel = vec2(0.5/IG_coordTextureWidth, 0.5/IG_coordTextureHeight);\n";
			shader += "    vec2 IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_coordTextureWidth), position.y*(IG_implicitMeshSize.y/IG_coordTextureHeight)) + halfPixel;\n";
		}

		shader += "    pos = texture2D( IG_coordinateTexture, IG_texCoord ).rgb;\n";
		shader += "    pos = pos * (IG_bboxMax - IG_bboxMin) + IG_bboxMin;\n";
	} else if (properties.POPGEOMETRY) { //PopGeometry
		shader += "    vec3 offsetVec = step(pos / bgPrecisionMax, PG_bbMaxModF) * PG_bboxShiftVec;\n";
		shader += "    if (PG_precisionLevel <= 2.0) {\n";
		shader += "        pos = floor(pos / PG_powPrecision) * PG_powPrecision;\n";
		shader += "        pos /= (65536.0 - PG_powPrecision);\n";
		shader += "    }\n";
		shader += "    else {\n";
		shader += "        pos /= bgPrecisionMax;\n";
		shader += "    }\n";
		shader += "    pos = (pos + offsetVec + PG_bbMin) * PG_maxBBSize;\n";
	} else {
		if(properties.REQUIREBBOX) {
			shader += "    pos = bgCenter + bgSize * pos / bgPrecisionMax;\n";
		}
	}

	if(properties.VERTEXID) {
		shader += "    fragID = id;\n";
	}

	if(properties.CLIPPLANES) {
		shader += "    fragPosition = (modelViewMatrix * vec4(pos, 1.0));\n";
	}

	shader += "    projCoords = modelViewProjectionMatrix * vec4(pos, 1.0);\n";
	shader += "    gl_Position = projCoords;\n";

	shader += "}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[ShadowShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.DynamicShadowShader.prototype.generateFragmentShader = function(gl, properties)
{
	var shader = "";

	shader += "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
    shader += "    precision highp float;\n";
    shader += "#else\n";
    shader += "    precision mediump float;\n";
    shader += "#endif\n\n";

	//Shadow stuff
	shader += "varying vec4 projCoords;\n";
	shader += "uniform float offset;\n";
	shader += "uniform bool cameraView;\n";

	//ShadowID stuff
	if(properties.VERTEXID) {
		shader += "varying float fragID;\n";
	}

	if (properties.MULTIVISMAP) {
		shader += "uniform sampler2D multiVisibilityMap;\n";
		shader += "uniform float multiVisibilityWidth;\n";
		shader += "uniform float multiVisibilityHeight;\n";
	}

	//ClipPlane stuff
	if(properties.CLIPPLANES) {
		shader += "uniform mat4 viewMatrixInverse;\n";
		shader += "varying vec4 fragPosition;\n";
		shader += x3dom.shader.clipPlanes(properties.CLIPPLANES);
	}

	//Import RGBA Packing stuff if FloatingPoint textures are not supported
	if(!x3dom.caps.FP_TEXTURES) {
		shader += x3dom.shader.rgbaPacking();
	}

	/*******************************************************************************
	 * Generate main function
	 ********************************************************************************/

	shader += "void main(void) {\n";

	if(properties.CLIPPLANES)
	{
		shader += "calculateClipPlanes();\n";
	}

	if (properties.MULTIVISMAP) {
		shader += "    vec2 idTexCoord;\n";
		shader += "    float roundedID = floor(fragID+0.5);\n";
		shader += "    idTexCoord.x = (mod(roundedID, multiVisibilityWidth)) * (1.0 / multiVisibilityWidth) + (0.5 / multiVisibilityWidth);\n";
		shader += "    idTexCoord.y = (floor(roundedID / multiVisibilityHeight)) * (1.0 / multiVisibilityHeight) + (0.5 / multiVisibilityHeight);\n";
		shader += "    vec4 visibility = texture2D( multiVisibilityMap, idTexCoord );\n";
		shader += "    if (visibility.r < 1.0) discard; \n";
	}

	shader += "    vec3 proj = (projCoords.xyz / projCoords.w);\n";

 	if(!x3dom.caps.FP_TEXTURES) {
		shader += "    gl_FragColor = packDepth(proj.z);\n";
	} else {
		//use variance shadow maps, when not rendering from camera view
		//shader +=	"if (!cameraView) proj.z = exp((1.0-offset)*80.0*proj.z);\n";
		shader += "	   if (!cameraView){\n";
		shader += "		   proj.z = (proj.z + 1.0)*0.5;\n";
		shader += "		   proj.y = proj.z * proj.z;\n";
		shader += "	   }\n";
		shader += "    gl_FragColor = vec4(proj, 1.0);\n";
	}

	shader += "}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[ShadowShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final Shader program
 */
x3dom.shader.ComposedShader = function(gl, shape)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl, shape);
	var fragmentShader 	= this.generateFragmentShader(gl, shape);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.ComposedShader.prototype.generateVertexShader = function(gl, shape)
{
	var shader = shape._cf.appearance.node._shader._vertex._vf.url[0];

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[ComposedShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.ComposedShader.prototype.generateFragmentShader = function(gl, shape)
{
	var shader = shape._cf.appearance.node._shader._fragment._vf.url[0];

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[ComposedShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final Shader program
 */
x3dom.shader.NormalShader = function(gl)
{
	this.program = gl.createProgram();
	
	var vertexShader   = this.generateVertexShader(gl);
	var fragmentShader = this.generateFragmentShader(gl);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.NormalShader.prototype.generateVertexShader = function(gl)
{
	var shader =    "attribute vec3 position;\n" +
                    "attribute vec3 normal;\n" +
                    "uniform vec3 bgCenter;\n" +
                    "uniform vec3 bgSize;\n" +
                    "uniform float bgPrecisionMax;\n" +
                    "uniform float bgPrecisionNorMax;\n" +
                    "uniform mat4 normalMatrix;\n" +
                    "uniform mat4 modelViewProjectionMatrix;\n" +
                    "varying vec3 fragNormal;\n" +
                    
                    "void main(void) {\n" +
                    "    vec3 pos = bgCenter + bgSize * position / bgPrecisionMax;\n" +
                    "    fragNormal = (normalMatrix * vec4(normal / bgPrecisionNorMax, 0.0)).xyz;\n" +
                    //"    fragNormal = normal;\n" +
                    "    gl_Position = modelViewProjectionMatrix * vec4(pos, 1.0);\n" +
                    "}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[NormalShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.NormalShader.prototype.generateFragmentShader = function(gl)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += "precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";

	shader += "varying vec3 fragNormal;\n" +
					
					"void main(void) {\n" +
					"    gl_FragColor = vec4(normalize(fragNormal) / 2.0 + 0.5, 1.0);\n" +
					"}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[NormalShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final ShadowShader program
 */
x3dom.shader.FrontgroundTextureShader = function(gl)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl);
	var fragmentShader 	= this.generateFragmentShader(gl);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.FrontgroundTextureShader.prototype.generateVertexShader = function(gl)
{
	var shader = 	"attribute vec3 position;\n" +
					"varying vec2 fragTexCoord;\n" +
					"\n" +
					"void main(void) {\n" +
					"    vec2 texCoord = (position.xy + 1.0) * 0.5;\n" +
					"    fragTexCoord = texCoord;\n" +
					"    gl_Position = vec4(position.xy, 0.0, 1.0);\n" +
					"}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[FrontgroundTextureShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.FrontgroundTextureShader.prototype.generateFragmentShader = function(gl)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += "precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";

	shader += "uniform sampler2D tex;\n" +
				"varying vec2 fragTexCoord;\n" +
				"\n" +
				"void main(void) {\n" +
				"    vec4 col = texture2D(tex, fragTexCoord);\n" +
				"    gl_FragColor = vec4(col.rgb, 1.0);\n" +
				"}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[FrontgroundTextureShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};


/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final ShadowShader program
 */
x3dom.shader.BackgroundTextureShader = function(gl)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl);
	var fragmentShader 	= this.generateFragmentShader(gl);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.BackgroundTextureShader.prototype.generateVertexShader = function(gl)
{
	var shader = 	"attribute vec3 position;\n" +
					"varying vec2 fragTexCoord;\n" +
					"uniform vec2 scale;\n" +
					"uniform vec2 translation;\n" +
					"\n" +
					"void main(void) {\n" +
					"    vec2 texCoord = (position.xy + 1.0) * 0.5;\n" +
					"    fragTexCoord = texCoord * scale + translation;\n" +
					"    gl_Position = vec4(position.xy, 0.0, 1.0);\n" +
					"}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[BackgroundTextureShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.BackgroundTextureShader.prototype.generateFragmentShader = function(gl)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += "precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";

	shader += "uniform sampler2D tex;\n" +
				"varying vec2 fragTexCoord;\n" +
				"\n" +
				"void main(void) {\n" +
				"    gl_FragColor = texture2D(tex, fragTexCoord);\n" +
				"}";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[BackgroundTextureShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};


/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final ShadowShader program
 */
x3dom.shader.BackgroundSkyTextureShader = function(gl)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl);
	var fragmentShader 	= this.generateFragmentShader(gl);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.BackgroundSkyTextureShader.prototype.generateVertexShader = function(gl)
{
	var shader = 	"attribute vec3 position;\n" +
					"attribute vec2 texcoord;\n" +
					"uniform mat4 modelViewProjectionMatrix;\n" +
					"varying vec2 fragTexCoord;\n" +
					"\n" +
					"void main(void) {\n" +
					"    fragTexCoord = texcoord;\n" +
					"    gl_Position = modelViewProjectionMatrix * vec4(position, 1.0);\n" +
					"}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[BackgroundSkyTextureShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.BackgroundSkyTextureShader.prototype.generateFragmentShader = function(gl)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += "precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";

	shader += "uniform sampler2D tex;\n" +
				"varying vec2 fragTexCoord;\n" +
				"\n" +
				"void main(void) {\n" +
				"    gl_FragColor = texture2D(tex, fragTexCoord);\n" +
				"}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[BackgroundSkyTextureShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final ShadowShader program
 */
x3dom.shader.BackgroundCubeTextureShader = function(gl)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl);
	var fragmentShader 	= this.generateFragmentShader(gl);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.BackgroundCubeTextureShader.prototype.generateVertexShader = function(gl)
{
	var shader = 	"attribute vec3 position;\n" +
					"uniform mat4 modelViewProjectionMatrix;\n" +
					"varying vec3 fragNormal;\n" +
					"\n" +
					"void main(void) {\n" +
					"    fragNormal = normalize(position);\n" +
					"    gl_Position = modelViewProjectionMatrix * vec4(position, 1.0);\n" +
					"}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[BackgroundCubeTextureShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.BackgroundCubeTextureShader.prototype.generateFragmentShader = function(gl)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += "precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";

	shader +=	"uniform samplerCube tex;\n" +
				"varying vec3 fragNormal;\n" +
				"\n" +
				"float magn(float val) {\n" +
				"    return ((val >= 0.0) ? val : -1.0 * val);\n" +
				"}" +
				"\n" +
				"void main(void) {\n" +
				"    vec3 normal = -reflect(normalize(fragNormal), vec3(0.0,0.0,1.0));\n" +
				"    if (magn(normal.y) >= magn(normal.x) && magn(normal.y) >= magn(normal.z))\n" +
				"        normal.xz = -normal.xz;\n" +
				"    gl_FragColor = textureCube(tex, normal);\n" +
				"}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[BackgroundCubeTextureShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final Shader program
 */
x3dom.shader.ShadowRenderingShader = function(gl,shadowedLights)
{
	this.program = gl.createProgram();
	var vertexShader 	= this.generateVertexShader(gl);
	var fragmentShader 	= this.generateFragmentShader(gl,shadowedLights);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.ShadowRenderingShader.prototype.generateVertexShader = function(gl)
{
	var shader = "";
	shader += "attribute vec2 position;\n";

	shader += "varying vec2 vPosition;\n";
	
	shader += "void main(void) {\n";
	shader += " vPosition = position;\n";
	shader += " gl_Position = vec4(position, -1.0, 1.0);\n";
	shader += "}\n";
	
	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[ShadowRendering] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.ShadowRenderingShader.prototype.generateFragmentShader = function(gl,shadowedLights)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += "precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";

	shader += "uniform mat4 inverseViewProj;\n";
	shader += "uniform mat4 inverseProj;\n";
	shader += "varying vec2 vPosition;\n";
	shader += "uniform sampler2D sceneMap;\n";  
	for (var i=0; i<5; i++)
		shader += "uniform float cascade"+i+"_Depth;\n";
	
	
	for(var l=0; l<shadowedLights.length; l++) {
	shader +=	"uniform float light"+l+"_On;\n" +
				"uniform float light"+l+"_Type;\n" +
				"uniform vec3  light"+l+"_Location;\n" +
				"uniform vec3  light"+l+"_Direction;\n" +
				"uniform vec3  light"+l+"_Attenuation;\n" +
				"uniform float light"+l+"_Radius;\n" +
				"uniform float light"+l+"_BeamWidth;\n" +
				"uniform float light"+l+"_CutOffAngle;\n" +
				"uniform float light"+l+"_ShadowIntensity;\n" +
				"uniform float light"+l+"_ShadowOffset;\n" +
				"uniform mat4 light"+l+"_ViewMatrix;\n";
		for (var j=0; j<6; j++){
			shader += "uniform mat4 light"+l+"_"+j+"_Matrix;\n";
			shader += "uniform sampler2D light"+l+"_"+j+"_ShadowMap;\n"; 
		}
		for (var j=0; j<5; j++)
			shader += "uniform float light"+l+"_"+j+"_Split;\n"; 

		
	}
	if (!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE) 
		shader += 	x3dom.shader.rgbaPacking();				
	
	shader += x3dom.shader.shadowRendering();
    
    shader += x3dom.shader.gammaCorrectionDecl({});  //TODO shader properties?
	
	shader += 	"void main(void) {\n" +
				"	float shadowValue = 1.0;\n" +
				"	vec2 texCoordsSceneMap = (vPosition + 1.0)*0.5;\n" +
				"	vec4 projCoords = texture2D(sceneMap, texCoordsSceneMap);\n" +
				"	if (projCoords != vec4(1.0,1.0,1.0,0.0)){\n";
	if (!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE){ 
		shader += 	"	projCoords.z = unpackDepth(projCoords);\n" +
					"	projCoords.w = 1.0;\n";
	}
	
	//reconstruct world and view coordinates from scene map
	shader += 	"	projCoords = projCoords / projCoords.w;\n" +
				"	projCoords.xy = vPosition;\n" +
				"	vec4 eyeCoords = inverseProj*projCoords;\n" +
				"	vec4 worldCoords = inverseViewProj*projCoords;\n" +
				"	float lightInfluence = 0.0;\n";
	
	for(var l=0; l<shadowedLights.length; l++) {
		shader += 
				"	lightInfluence = getLightInfluence(light"+l+"_Type, light"+l+"_ShadowIntensity, light"+l+"_On, light"+l+"_Location, light"+l+"_Direction, " +
						"light"+l+"_CutOffAngle, light"+l+"_BeamWidth, light"+l+"_Attenuation, light"+l+"_Radius, eyeCoords.xyz/eyeCoords.w);\n" +
				"	if (lightInfluence != 0.0){\n" +
				"		vec4 shadowMapValues;\n" +
				"		float viewSampleDepth;\n";
				

		if (!x3dom.isa(shadowedLights[l], x3dom.nodeTypes.PointLight)){
			shader += "		getShadowValuesCascaded(shadowMapValues, viewSampleDepth, worldCoords, -eyeCoords.z/eyeCoords.w,"+
								"light"+l+"_0_Matrix,light"+l+"_1_Matrix,light"+l+"_2_Matrix,light"+l+"_3_Matrix,light"+l+"_4_Matrix,light"+l+"_5_Matrix,"+
								"light"+l+"_0_ShadowMap,light"+l+"_1_ShadowMap,light"+l+"_2_ShadowMap,light"+l+"_3_ShadowMap,"+
								"light"+l+"_4_ShadowMap,light"+l+"_5_ShadowMap, light"+l+"_0_Split, light"+l+"_1_Split, light"+l+"_2_Split, light"+l+"_3_Split, \n"+
								"light"+l+"_4_Split);\n";
		} else {
			shader += "		getShadowValuesPointLight(shadowMapValues, viewSampleDepth, light"+l+"_Location, worldCoords, light"+l+"_ViewMatrix, "+
								"light"+l+"_0_Matrix,light"+l+"_1_Matrix,light"+l+"_2_Matrix,light"+l+"_3_Matrix,light"+l+"_4_Matrix,light"+l+"_5_Matrix,"+
								"light"+l+"_0_ShadowMap,light"+l+"_1_ShadowMap,light"+l+"_2_ShadowMap,light"+l+"_3_ShadowMap,"+
								"light"+l+"_4_ShadowMap,light"+l+"_5_ShadowMap);\n";
		}		
	
		if (!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE)	
			shader += 	"	shadowValue *= clamp(ESM(shadowMapValues.z, viewSampleDepth, light"+l+"_ShadowOffset), "+
						"				1.0 - light"+l+"_ShadowIntensity*lightInfluence, 1.0);\n";
		else
			shader += 	" 	shadowValue *= clamp(VSM(shadowMapValues.zy, viewSampleDepth, light"+l+"_ShadowOffset), "+
						"				1.0 - light"+l+"_ShadowIntensity*lightInfluence, 1.0);\n";				
		shader += 		"	}\n";
		
	}
					
	shader += 	"}\n" + 
                // In principle we should fix the place where this is multplied in instead
                // of overcompensating for the subsequent error from here. This way of doing
                // gamma correction explots the rule that (a*b)^x = a^x * b^x (x being the
                // gamma coefficient), i.e. the umbra is corrected for now, the penumbra
                // is incorrect and full light is zero here so unaffected as well.
				"	gl_FragColor = " + x3dom.shader.encodeGamma({}, "vec4(shadowValue, shadowValue, shadowValue, 1.0)") + ";\n" +
				"}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[ShadowRendering] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final shader program
 */
x3dom.shader.TextureRefinementShader = function (gl) {
    this.program = gl.createProgram();

    var vertexShader = this.generateVertexShader(gl);
    var fragmentShader = this.generateFragmentShader(gl);

    gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);

    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");

    gl.linkProgram(this.program);

    return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.TextureRefinementShader.prototype.generateVertexShader = function (gl) {
    var shader = "attribute vec2 position;\n" +
                 "varying vec2 fragTexCoord;\n" +
                 "\n" +
                 "void main(void) {\n" +
                 "    fragTexCoord = (position.xy + 1.0) / 2.0;\n" +
                 "    gl_Position = vec4(position, -1.0, 1.0);\n" +
                 "}\n";

    var vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);

    if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
        x3dom.debug.logError("[TextureRefinementShader] VertexShader " + gl.getShaderInfoLog(vertexShader));
    }

    return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.TextureRefinementShader.prototype.generateFragmentShader = function (gl) {
    var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n" +
                 " precision highp float;\n" +
                 "#else\n" +
                 " precision mediump float;\n" +
                 "#endif\n\n";

    shader += "uniform sampler2D stamp;\n" +
              "uniform sampler2D lastTex;\n" +
              "uniform sampler2D curTex;\n" +
              "uniform int mode;\n" +
              "uniform vec2 repeat;\n" +
              "varying vec2 fragTexCoord;\n" +
              "\n" +
              "void init(void);\n" +
              "void refine(void);\n" +
              "\n" +
              "void main(void) {\n" +
              "    if (mode == 0) { init(); }\n" +
              "    else { refine(); }\n" +
              "}\n" +
              "\n" +
              "void init(void) {\n" +
              "    gl_FragColor = texture2D(curTex, fragTexCoord);\n" +
              "}\n" +
              "\n" +
              "void refine(void) {\n" +
              "    vec3 red = texture2D(stamp, repeat * fragTexCoord).rgb;\n" +
              "    vec3 v1  = texture2D(lastTex, fragTexCoord).rgb;\n" +
              "    vec3 v2  = texture2D(curTex, fragTexCoord).rgb;\n" +
              "    if (red.r <= 0.5) {\n" +
              "        gl_FragColor = vec4(v1, 1.0);\n" +
              "    }\n" +
              "    else {\n" +
              "        gl_FragColor = vec4(v2, 1.0);\n" +
              "    }\n" +
              "}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);

    if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
        x3dom.debug.logError("[TextureRefinementShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));
    }

    return fragmentShader;
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/**
 * Generate the final BlurShader program 
 * (gaussian blur for 3x3, 5x5 and 7x7 kernels)
 */
x3dom.shader.BlurShader = function(gl)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl);
	var fragmentShader 	= this.generateFragmentShader(gl);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.BlurShader.prototype.generateVertexShader = function(gl)
{
	var shader = "";
	shader += "attribute vec2 position;\n";

	shader += "varying vec2 vPosition;\n";
	
	shader += "void main(void) {\n";
	shader += " vPosition = position;\n";
	shader += " gl_Position = vec4(position, -1.0, 1.0);\n";
	shader += "}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[BlurShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.BlurShader.prototype.generateFragmentShader = function(gl)
{
  var shader = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += "precision highp float;\n";
  shader += "#else\n";
  shader += " precision mediump float;\n";
  shader += "#endif\n\n";

	shader += "varying vec2 vPosition;\n" +
					"uniform sampler2D texture;\n" +
					"uniform bool horizontal;\n" +
					"uniform float pixelSizeHor;\n" +
					"uniform float pixelSizeVert;\n" +
					"uniform int filterSize;\n";

	if (!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE){
	shader +=		x3dom.shader.rgbaPacking() +
	
					"void main(void) {\n" +
					"	vec2 texCoords = (vPosition + 1.0)*0.5;\n" +
					"	vec2 offset;\n" +
					"	if (horizontal) offset = vec2(pixelSizeHor, 0.0);\n" +
					"	else offset = vec2(0.0, pixelSizeVert);\n" +
					"	float depth = unpackDepth(texture2D(texture, texCoords));\n" +
					"	if (filterSize == 3){\n" +		
					"		depth = depth * 0.3844;\n" +
					"		depth += 0.3078*unpackDepth(texture2D(texture, texCoords-offset));\n" +
					"		depth += 0.3078*unpackDepth(texture2D(texture, texCoords+offset));\n" +
					"	} else if (filterSize == 5){\n" +	
					"		depth = depth * 0.2921;\n" +
					"		depth += 0.2339*unpackDepth(texture2D(texture, texCoords-offset));\n" +
					"		depth += 0.2339*unpackDepth(texture2D(texture, texCoords+offset));\n" +	
					"		depth += 0.1201*unpackDepth(texture2D(texture, texCoords-2.0*offset));\n" +
					"		depth += 0.1201*unpackDepth(texture2D(texture, texCoords+2.0*offset));\n" +
					"	} else if (filterSize == 7){\n" +	
					"		depth = depth * 0.2161;\n" +
					"		depth += 0.1907*unpackDepth(texture2D(texture, texCoords-offset));\n" +
					"		depth += 0.1907*unpackDepth(texture2D(texture, texCoords+offset));\n" +	
					"		depth += 0.1311*unpackDepth(texture2D(texture, texCoords-2.0*offset));\n" +
					"		depth += 0.1311*unpackDepth(texture2D(texture, texCoords+2.0*offset));\n" +
					"		depth += 0.0702*unpackDepth(texture2D(texture, texCoords-3.0*offset));\n" +
					"		depth += 0.0702*unpackDepth(texture2D(texture, texCoords+3.0*offset));\n" +					
					"	}\n" + 
					"	gl_FragColor = packDepth(depth);\n" + 
					"}\n";
	} else{
	shader +=		"void main(void) {\n" +
					"	vec2 texCoords = (vPosition + 1.0)*0.5;\n" +
					"	vec2 offset;\n" +
					"	if (horizontal) offset = vec2(pixelSizeHor, 0.0);\n" +
					"	else offset = vec2(0.0, pixelSizeVert);\n" +
					"	vec4 color = texture2D(texture, texCoords);\n" +
					"	if (filterSize == 3){\n" +		
					"		color = color * 0.3844;\n" +
					"		color += 0.3078*texture2D(texture, texCoords-offset);\n" +
					"		color += 0.3078*texture2D(texture, texCoords+offset);\n" +
					"	} else if (filterSize == 5){\n" +	
					"		color = color * 0.2921;\n" +
					"		color += 0.2339*texture2D(texture, texCoords-offset);\n" +
					"		color += 0.2339*texture2D(texture, texCoords+offset);\n" +	
					"		color += 0.1201*texture2D(texture, texCoords-2.0*offset);\n" +
					"		color += 0.1201*texture2D(texture, texCoords+2.0*offset);\n" +
					"	} else if (filterSize == 7){\n" +	
					"		color = color * 0.2161;\n" +
					"		color += 0.1907*texture2D(texture, texCoords-offset);\n" +
					"		color += 0.1907*texture2D(texture, texCoords+offset);\n" +	
					"		color += 0.1311*texture2D(texture, texCoords-2.0*offset);\n" +
					"		color += 0.1311*texture2D(texture, texCoords+2.0*offset);\n" +
					"		color += 0.0702*texture2D(texture, texCoords-3.0*offset);\n" +
					"		color += 0.0702*texture2D(texture, texCoords+3.0*offset);\n" +					
					"	}\n" + 
					"	gl_FragColor = color;\n" + 
					"}\n";
	}				

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[BlurShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};

/**
 * Created by Sven Kluge on 27.06.2016.
 */

x3dom.shader.KHRMaterialCommonsShader = function(gl, properties)
{
    this.program = gl.createProgram();

    var vertexShader 	= this.generateVertexShader(gl);
    var fragmentShader 	= this.generateFragmentShader(gl, properties);

    gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);

    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");

    gl.linkProgram(this.program);

    return this.program;
};

x3dom.shader.KHRMaterialCommonsShader.prototype.generateVertexShader = function(gl)
{
    var shader = "precision highp float;\n"+
                "attribute vec3 position;"+
                "attribute vec3 normal;"+
                "attribute vec3 texcoord;"+
                "varying vec3 v_eye;"+
                "varying vec3 v_normal;"+
                "varying vec3 v_texcoord;"+
                "uniform mat4 modelViewProjectionMatrix;"+
                "uniform mat4 modelViewMatrix;"+
                "uniform mat4 normalMatrix;"+
                "void main (void)"+
                "{"+
                "    vec4 pos = modelViewProjectionMatrix * vec4(position, 1.0);"+
                "    v_eye = (modelViewMatrix * vec4(position, 1.0)).xyz;"+
                "    v_normal = (normalMatrix * vec4(normal,1.0)).xyz;"+
                "    v_texcoord = texcoord;"+
                "    gl_Position = pos;"+
                "}";

    var vertexShader = gl.createShader(gl.VERTEX_SHADER);
    gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);

    if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
        x3dom.debug.logError("[KHRMaterialCommonsShader] VertexShader " + gl.getShaderInfoLog(vertexShader));
    }

    return vertexShader;
};

x3dom.shader.KHRMaterialCommonsShader.prototype.generateFragmentShader = function(gl, properties)
{
    var shader = "precision highp float;\n"+
    "varying vec3 v_eye;\n"+
    "varying vec3 v_normal;\n"+
    "varying vec3 v_texcoord;\n"+
    "uniform vec4 lightVector;\n"+
    "uniform vec4 ambient;\n";

    if(properties.LIGHTS || properties.CLIPPLANES)
    {
        shader += "varying vec4 fragPosition;\n";
        shader += "uniform float isOrthoView;\n";
    }

    //Lights
    if(properties.LIGHTS) {

        if(properties.NORMALMAP && properties.NORMALSPACE == "OBJECT") {
            //do nothing
        } else {
            shader += "varying vec3 fragNormal;\n";
        }

        shader += x3dom.shader.light(properties.LIGHTS);
    }

    if(properties.USE_DIFFUSE_TEX == 0)
        shader += "uniform vec4 diffuse;\n";
    else
        shader += "uniform sampler2D diffuseTex;\n";

    if(properties.USE_EMISSION_TEX == 0)
        shader += "uniform vec4 emission;\n";
    else
        shader += "uniform sampler2D emissionTex;\n";

    if(properties.USE_SPECULAR_TEX == 0)
    shader += "uniform vec4 specular;\n";
    else
        shader += "uniform sampler2D specularTex;\n";


    shader +=
    "uniform float shininess;\n"+
    "uniform float transparency;\n"+
    "uniform float ambientIntensity;\n"+
    "uniform vec4 ambientLight;\n"+
    "uniform int technique;\n"+
    "void main(void)\n"+
    "{\n"+
        "vec4 I = -vec4(normalize(v_eye),1.0);\n"+
        "vec4 N = vec4(normalize(v_normal),1.0);\n"+
        "vec4 al = ambientLight;\n"+
        "vec4 L = normalize(lightVector-vec4(v_eye,1.0));\n";

        if(properties.USE_DIFFUSE_TEX == 0)
            shader += "vec4 _diffuse = diffuse;\n";
        else
            shader += "vec4 _diffuse = texture2D(diffuseTex, v_texcoord.xy);\n";

        if(properties.USE_SPECULAR_TEX == 0)
            shader += "vec4 _specularColor = specular;\n";
        else
            shader += "vec4 _specularColor = texture2D(specularTex, v_texcoord.xy);\n";

        if(properties.USE_EMISSION_TEX == 0)
            shader += "vec4 _emission = emission;\n";
        else
            shader += "vec4 _emission = texture2D(emissionTex, v_texcoord.xy);\n";

        shader +=
            "vec4 color;\n"+
            "if(technique == 0) // BLINN\n"+
            "{\n"+
                "vec4 H = normalize(I+L);\n"+
                "color = _emission + ambient * al + _diffuse * max(dot(N,L),0.0) + _specularColor * pow(max(dot(H,N),0.0),shininess);\n"+
            "}\n"+
            "else if(technique==1) // PHONG\n"+
            "{\n"+
                "vec4 R = -reflect(L,N);\n"+
                "color = _emission + ambient * al + _diffuse * max(dot(N,L),0.0) + _specularColor * pow(max(dot(R,I),0.0),shininess);\n"+
            "}\n"+
            "else if(technique==2) // LAMBERT\n"+
            "{\n"+
                "color = _emission + ambient * al + _diffuse * max(dot(N,L), 0.0);\n"+
            "}\n"+
            "else if(technique==3) // CONSTANT\n"+
            "{\n"+
                "color = _emission + ambient * al;\n"+
            "}\n";

        //Calculate lights
        if (properties.LIGHTS) {
            shader += "vec3 ambient   = vec3(0.0, 0.0, 0.0);\n";
            shader += "vec3 diffuse   = vec3(0.0, 0.0, 0.0);\n";
            shader += "vec3 specular  = vec3(0.0, 0.0, 0.0);\n";
            shader += "vec3 eye;\n";
            shader += "if ( isOrthoView > 0.0 ) {\n";
            shader += "    eye = vec3(0.0, 0.0, 1.0);\n";
            shader += "} else {\n";
            shader += "    eye = -v_eye.xyz;\n";
            shader += "}\n";

            shader += "vec3 ads;\n";

            for(var l=0; l<properties.LIGHTS; l++) {
                var lightCol = "light"+l+"_Color";
                shader += "ads = lighting(light"+l+"_Type, " +
                    "light"+l+"_Location, " +
                    "light"+l+"_Direction, " +
                    lightCol + ", " +
                    "light"+l+"_Attenuation, " +
                    "light"+l+"_Radius, " +
                    "light"+l+"_Intensity, " +
                    "light"+l+"_AmbientIntensity, " +
                    "light"+l+"_BeamWidth, " +
                    "light"+l+"_CutOffAngle, " +
                    "v_normal, eye, shininess, ambientIntensity);\n";
                shader += "ambient  += " + lightCol + " * ads.r;\n" +
                    "diffuse  += " + lightCol + " * ads.g;\n" +
                    "specular += " + lightCol + " * ads.b;\n";
            }

            shader += "ambient = max(ambient, 0.0);\n";
            shader += "diffuse = max(diffuse, 0.0);\n";
            shader += "specular = max(specular, 0.0);\n";

            shader += "color.rgb = (_emission.rgb + max(ambient + diffuse, 0.0) * color.rgb + specular*_specularColor.rgb);\n";
        }

        shader += "gl_FragColor = vec4(color.rgb, 1.0-transparency);\n"+
    "}";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
    gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);

    if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
        x3dom.debug.logError("[KHRMaterialCommonsShader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));
    }

    return fragmentShader;
};
/**
 * Generate the final ShadowShader program
 */
x3dom.shader.SSAOShader = function(gl)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl);
	var fragmentShader 	= this.generateFragmentShader(gl);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");
    
	gl.linkProgram(this.program);
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.SSAOShader.prototype.generateVertexShader = function(gl)
{
	var shader = 	"attribute vec3 position;\n" +
					"varying vec2 depthTexCoord;\n" +
					"varying vec2 randomTexCoord;\n"+
					"uniform vec2 randomTextureTilingFactor;\n"+
					"\n" +
					"void main(void) {\n" +
					"    vec2 texCoord = (position.xy + 1.0) * 0.5;\n" +
					"    depthTexCoord = texCoord;\n" +
					"	 randomTexCoord = randomTextureTilingFactor*texCoord;\n"+
					"    gl_Position = vec4(position.xy, 0.0, 1.0);\n" +
					"}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[SSAOShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Returns the code for a function "getDepth(vec2 depthTexCoord)" that returns the linear depth.
 * It also introduces two uniform floats depthReconstructionConstantA and depthReconstructionConstantB,
 * which are needed for the depth reconstruction.
 */
x3dom.shader.SSAOShader.depthReconsructionFunctionCode = function()
{
	var code = 	"uniform float depthReconstructionConstantA;\n"+
				"uniform float depthReconstructionConstantB;\n";
	if (!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE) 
		code += 	x3dom.shader.rgbaPacking();
		
	code+= 	"float getDepth(vec2 depthTexCoord) {\n"+
				"    vec4 col = texture2D(depthTexture, depthTexCoord);\n"+
				"    float d;\n";
	
	if (!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE){
		code+="    d = unpackDepth(col);\n";
	} else {
		code+="    d = col.b;\n"
	}
	code+=    "    return depthReconstructionConstantB/(depthReconstructionConstantA+d);\n";
	code+=	"}\n";
	return code;
}

/**
 * Generate the fragment shader
 */
x3dom.shader.SSAOShader.prototype.generateFragmentShader = function(gl)
{
  var shader = 	"#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
  shader += 	"precision highp float;\n";
  shader += 	"#else\n";
  shader += 	" precision mediump float;\n";
  shader += 	"#endif\n\n";

	shader += 	"uniform sampler2D depthTexture;\n" +
				"uniform sampler2D randomTexture;\n"+
				"uniform float nearPlane;\n"+
				"uniform float farPlane;\n"+
				"uniform float radius;\n"+
				"uniform float depthBufferEpsilon;\n"+
				"uniform vec3 samples[16];\n"+
				"varying vec2 depthTexCoord;\n" +
				"varying vec2 randomTexCoord;\n";

	shader += 	x3dom.shader.SSAOShader.depthReconsructionFunctionCode();
		
	shader+=	"void main(void) {\n" +
				"    float referenceDepth = getDepth(depthTexCoord);\n" +
				"    if(referenceDepth == 1.0)\n"+ //background
                "    {\n"+
                "        gl_FragColor = vec4(1.0,1.0,1.0, 1.0);\n"+
                "        return;\n"+
                "    }\n"+
				"    int numOcclusions = 0;\n"+
				"    for(int i = 0; i<16; ++i){\n"+
				"        float scale  = 1.0/referenceDepth;\n"+
				"        vec3 samplepos = reflect(samples[i],texture2D(randomTexture,randomTexCoord).xyz*2.0-vec3(1.0,1.0,1.0));\n"+
				"        float sampleDepth = getDepth(depthTexCoord+samplepos.xy*scale*radius);\n"+
				"        //if(abs(sampleDepth-referenceDepth)<=radius*(1.0/nearPlane))\n"+ //leads to bright halos
				"        if( sampleDepth < referenceDepth-depthBufferEpsilon) {\n"+
				"            ++numOcclusions;\n"+
				"        }\n"+
				"    }\n"+
				"    float r = 1.0-float(numOcclusions)/16.0;\n"+
				"    r*=2.0;\n"+
				"    gl_FragColor = vec4(r,r,r, 1.0);\n" +
				"}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[SSAOhader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};


/**
 * Generate the final ShadowShader program
 */
x3dom.shader.SSAOBlurShader = function(gl)
{
	this.program = gl.createProgram();
	
	var vertexShader 	= this.generateVertexShader(gl);
	var fragmentShader 	= this.generateFragmentShader(gl);
	
	gl.attachShader(this.program, vertexShader);
    gl.attachShader(this.program, fragmentShader);
    
    // optional, but position should be at location 0 for performance reasons
    gl.bindAttribLocation(this.program, 0, "position");

    gl.linkProgram(this.program);
	return this.program;
};

/**
 * Generate the vertex shader
 */
x3dom.shader.SSAOBlurShader.prototype.generateVertexShader = function(gl)
{
	var shader = 	"attribute vec3 position;\n" +
					"varying vec2 fragTexCoord;\n" +
					"\n" +
					"void main(void) {\n" +
					"    vec2 texCoord = (position.xy + 1.0) * 0.5;\n" +
					"    fragTexCoord = texCoord;\n" +
					"    gl_Position = vec4(position.xy, 0.0, 1.0);\n" +
					"}\n";

	var vertexShader = gl.createShader(gl.VERTEX_SHADER);
	gl.shaderSource(vertexShader, shader);
    gl.compileShader(vertexShader);
		
	if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[SSAOShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.SSAOBlurShader.prototype.generateFragmentShader = function(gl)
{
  var shader = 	"#ifdef GL_FRAGMENT_PRECISION_HIGH\n";
	  shader += "precision highp float;\n";
	  shader += "#else\n";
	  shader += " precision mediump float;\n";
	  shader += "#endif\n\n";
  

	shader += 	"uniform sampler2D SSAOTexture;\n" +
				"uniform sampler2D depthTexture;\n" +
				"uniform float nearPlane;\n"+
				"uniform float farPlane;\n"+
				"uniform float amount;\n"+
				"uniform vec2 pixelSize;\n"+
				"uniform float depthThreshold;\n"+
				"varying vec2 fragTexCoord;\n";


	shader += 	x3dom.shader.SSAOShader.depthReconsructionFunctionCode();

	shader+="void main(void) {\n" +
			"    float sum = 0.0;\n"+
			"    float numSamples = 0.0;\n"+
			"    float referenceDepth = getDepth(fragTexCoord);\n"+
			"    for(int i = -2; i<2;i++){\n"+
			"        for(int j = -2; j<2;j++){\n"+
			"            vec2 sampleTexCoord = fragTexCoord+vec2(pixelSize.x*float(i),pixelSize.y*float(j));\n"+
			"            if(abs(referenceDepth - getDepth(sampleTexCoord))<depthThreshold){\n"+
			"                sum+= texture2D(SSAOTexture,sampleTexCoord).r;\n"+
			"                numSamples++;\n"+
			"    }}}\n"+
			"    float intensity = mix(1.0,sum/numSamples,amount);\n"+
			"    gl_FragColor = vec4(intensity,intensity,intensity,1.0);\n"+
			"}\n";

    var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
	gl.shaderSource(fragmentShader, shader);
    gl.compileShader(fragmentShader);
		
	if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)){
		x3dom.debug.logError("[SSAOhader] FragmentShader " + gl.getShaderInfoLog(fragmentShader));		
	}
	
	return fragmentShader;
};


x3dom.SSAO = {};
x3dom.SSAO.isEnabled = function(scene){return scene.getEnvironment()._vf.SSAO};

/**
 * Reinitializes the shaders if they were not created yet or need to be updated.
 */
x3dom.SSAO.reinitializeShadersIfNecessary = function(gl){
	if(x3dom.SSAO.shaderProgram === undefined){
		x3dom.SSAO.shaderProgram = x3dom.Utils.wrapProgram(gl, new x3dom.shader.SSAOShader(gl), "ssao");
	}
	if(x3dom.SSAO.blurShaderProgram === undefined){
		x3dom.SSAO.blurShaderProgram = x3dom.Utils.wrapProgram(gl, new x3dom.shader.SSAOBlurShader(gl), "ssao-blur");
	}
};

/**
 * Reinitializes the random Texture if it was not created yet or if it's size changed.
 */
x3dom.SSAO.reinitializeRandomTextureIfNecessary = function(gl,scene){
	var sizeHasChanged = scene.getEnvironment()._vf.SSAOrandomTextureSize != x3dom.SSAO.currentRandomTextureSize;

	if(x3dom.SSAO.randomTexture === undefined){
		//create random texture
		x3dom.SSAO.randomTexture = gl.createTexture();
	}

	if(x3dom.SSAO.randomTexture === undefined || sizeHasChanged){
		gl.bindTexture(gl.TEXTURE_2D,x3dom.SSAO.randomTexture);
		var rTexSize = x3dom.SSAO.currentRandomTextureSize = scene.getEnvironment()._vf.SSAOrandomTextureSize;
		var randomImageBuffer = new ArrayBuffer(rTexSize*rTexSize*4); //rTexSize^2 pixels with 4 bytes each
		var randomImageView = new Uint8Array(randomImageBuffer);
		//fill the image with random unit Vectors in the z-y-plane:
		for(var i = 0; i<rTexSize*rTexSize;++i){
			var x = Math.random()*2.0-1.0;
			var y = Math.random()*2.0-1.0;
			var z = 0;
			var length = Math.sqrt(x*x+y*y+z*z);
			x /=length;
			y /=length;
			randomImageView[4*i] = (x+1.0)*0.5*255.0;
			randomImageView[4*i+1] = (y+1.0)*0.5*255.0;
			randomImageView[4*i+2] = (z+1.0)*0.5*255.0;
			randomImageView[4*i+3] = 255;
		}
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,rTexSize,rTexSize,0, gl.RGBA,gl.UNSIGNED_BYTE, randomImageView);
		gl.bindTexture(gl.TEXTURE_2D,null);
	}
};

/**
 * Reinitializes the FBO render target for the SSAO if it wasn't created yet or if the canvas size changed.
 */
x3dom.SSAO.reinitializeFBOIfNecessary = function(gl,canvas){

	var dimensionsHaveChanged =
		x3dom.SSAO.currentFBOWidth != canvas.width || 
		x3dom.SSAO.currentFBOHeight != canvas.height;

	if(x3dom.SSAO.fbo === undefined || dimensionsHaveChanged)
	{
		x3dom.SSAO.currentFBOWidth = canvas.width;
		x3dom.SSAO.currentFBOHeight = canvas.height;
		var oldfbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
		if(x3dom.SSAO.fbo === undefined){
			//create framebuffer
			x3dom.SSAO.fbo = gl.createFramebuffer();
		}
		gl.bindFramebuffer(gl.FRAMEBUFFER, x3dom.SSAO.fbo);
		if(x3dom.SSAO.fbotex === undefined){
			//create render texture
			x3dom.SSAO.fbotex = gl.createTexture();
		}
		gl.bindTexture(gl.TEXTURE_2D,x3dom.SSAO.fbotex);
		gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,x3dom.SSAO.currentFBOWidth,
			x3dom.SSAO.currentFBOHeight,0, gl.RGBA,gl.UNSIGNED_BYTE, null);
		gl.bindTexture(gl.TEXTURE_2D,null);
		gl.framebufferTexture2D(gl.FRAMEBUFFER,
			gl.COLOR_ATTACHMENT0,
			gl.TEXTURE_2D,
			x3dom.SSAO.fbotex,
			0);
		gl.bindFramebuffer(gl.FRAMEBUFFER, oldfbo);
	}
};

/*
 * Renders a sparsely sampled Screen-Space Ambient Occlusion Factor.
 * @param stateManager x3dom webgl stateManager
 * @param gl WebGL context
 * @param scene Scene Node
 * @param tex depth texture
 * @param canvas Canvas the scene is rendered on (needed for dimensions)
 * @param fbo FrameBufferObject handle that should be used as a target (null to use curent fbo)
 */
x3dom.SSAO.render = function(stateManager,gl,scene,tex,canvas,fbo) {
	//save previous fbo
	var oldfbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
	if(fbo != null){
		gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
	}
	
	stateManager.frontFace(gl.CCW);
	stateManager.disable(gl.CULL_FACE);
	stateManager.disable(gl.DEPTH_TEST);
	
	var sp = x3dom.SSAO.shaderProgram;
	
	stateManager.useProgram(sp);

	//set up uniforms
	sp.depthTexture = 0;
	sp.randomTexture = 1;
	sp.radius = scene.getEnvironment()._vf.SSAOradius;
	sp.randomTextureTilingFactor = [canvas.width/x3dom.SSAO.currentRandomTextureSize,canvas.height/x3dom.SSAO.currentRandomTextureSize];

	var viewpoint = scene.getViewpoint();
	var nearPlane = viewpoint.getNear();
	var farPlane = viewpoint.getFar();
	sp.nearPlane = nearPlane;
	sp.farPlane = farPlane;
	sp.depthReconstructionConstantA = (farPlane+nearPlane)/(nearPlane-farPlane);
	sp.depthReconstructionConstantB = (2.0*farPlane*nearPlane)/(nearPlane-farPlane);
	sp.depthBufferEpsilon = 0.0001*(farPlane-nearPlane);
	//16 samples with a well distributed pseudo random opposing-pairs sampling pattern:
	sp.samples = [0.03800223814729654,0.10441029119843426,-0.04479934806797181,
				-0.03800223814729654,-0.10441029119843426,0.04479934806797181,
				-0.17023209847088397,0.1428416910414532,0.6154407640895228,
				0.17023209847088397,-0.1428416910414532,-0.6154407640895228,
				-0.288675134594813,-0.16666666666666646,-0.3774214123135722,
				0.288675134594813,0.16666666666666646,0.3774214123135722,
				0.07717696785196887,-0.43769233467209245,-0.5201284112706428,
				-0.07717696785196887,0.43769233467209245,0.5201284112706428,
				0.5471154183401156,-0.09647120981496134,-0.15886420745887797,
				-0.5471154183401156,0.09647120981496134,0.15886420745887797,
				0.3333333333333342,0.5773502691896253,-0.8012446019636266,
				-0.3333333333333342,-0.5773502691896253,0.8012446019636266,
				-0.49994591864508653,0.5958123446480936,-0.15385106176844343,
				0.49994591864508653,-0.5958123446480936,0.15385106176844343,
				-0.8352823295874743,-0.3040179051783715,0.7825440557226517,
				0.8352823295874743,0.3040179051783715,-0.7825440557226517];
	if (!sp.tex) {
		sp.tex = 0;
	}

	//depth texture
	gl.activeTexture(gl.TEXTURE0);
	gl.bindTexture(gl.TEXTURE_2D, tex);

	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	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);
	
	//random texture:
	gl.activeTexture(gl.TEXTURE1);
	gl.bindTexture(gl.TEXTURE_2D, x3dom.SSAO.randomTexture);

	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);

	gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, scene._fgnd._webgl.buffers[0]);
	gl.bindBuffer(gl.ARRAY_BUFFER, scene._fgnd._webgl.buffers[1]);
	gl.vertexAttribPointer(sp.position, 3, gl.FLOAT, false, 0, 0);
	gl.enableVertexAttribArray(sp.position);

	gl.drawElements(scene._fgnd._webgl.primType, scene._fgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0);

	gl.disableVertexAttribArray(sp.position);

	gl.activeTexture(gl.TEXTURE0);
	gl.bindTexture(gl.TEXTURE_2D, null);
	gl.activeTexture(gl.TEXTURE1);
	gl.bindTexture(gl.TEXTURE_2D, null);
	
	//restore prevoius fbo
	if(fbo != null){
		gl.bindFramebuffer(gl.FRAMEBUFFER, oldfbo);
	}
};

/**
 * Applies a depth-aware averaging filter.
 * @param stateManager x3dom webgl stateManager
 * @param gl WebGL context
 * @param scene Scene Node
 * @param ssaoTexture texture that contains the SSAO factor
 * @param depthTexture depth texture
 * @param canvas Canvas the scene is rendered on (needed for dimensions)
 * @param fbo FrameBufferObject handle that should be used as a target (null to use curent fbo)
 */
x3dom.SSAO.blur = function(stateManager,gl,scene,ssaoTexture,depthTexture,canvas,fbo) {

	//save previous fbo
	var oldfbo = gl.getParameter(gl.FRAMEBUFFER_BINDING);
	if(fbo != null){
		gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
	}

	stateManager.frontFace(gl.CCW);
	stateManager.disable(gl.CULL_FACE);
	stateManager.disable(gl.DEPTH_TEST);
	
	var sp = x3dom.SSAO.blurShaderProgram;
	
	stateManager.useProgram(sp);

	sp.SSAOTexture = 0;
	sp.depthTexture = 1;

	sp.depthThreshold = scene.getEnvironment()._vf.SSAOblurDepthTreshold;

	var viewpoint = scene.getViewpoint();
	var nearPlane = viewpoint.getNear();
	var farPlane = viewpoint.getFar();
	sp.nearPlane = nearPlane;
	sp.farPlane = farPlane;
	sp.depthReconstructionConstantA = (farPlane+nearPlane)/(nearPlane-farPlane);
	sp.depthReconstructionConstantB = (2.0*farPlane*nearPlane)/(nearPlane-farPlane);
	sp.pixelSize = [1.0/canvas.width,1.0/canvas.height];
	sp.amount = scene.getEnvironment()._vf.SSAOamount;

	//ssao texture
	gl.activeTexture(gl.TEXTURE0);
	gl.bindTexture(gl.TEXTURE_2D, ssaoTexture);

	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
	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);

	//depth texture
	gl.activeTexture(gl.TEXTURE1);
	gl.bindTexture(gl.TEXTURE_2D, depthTexture);

	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
	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.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, scene._fgnd._webgl.buffers[0]);
	gl.bindBuffer(gl.ARRAY_BUFFER, scene._fgnd._webgl.buffers[1]);
	gl.vertexAttribPointer(sp.position, 3, gl.FLOAT, false, 0, 0);
	gl.enableVertexAttribArray(sp.position);

	gl.drawElements(scene._fgnd._webgl.primType, scene._fgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0);

	gl.disableVertexAttribArray(sp.position);

	gl.activeTexture(gl.TEXTURE0);
	gl.bindTexture(gl.TEXTURE_2D, null);
	gl.activeTexture(gl.TEXTURE1);
	gl.bindTexture(gl.TEXTURE_2D, null);
	
	//restore previous fbo
	if(fbo != null){
		gl.bindFramebuffer(gl.FRAMEBUFFER, oldfbo);
	}
};

/**
 * Renders Screen-Space Ambeint Occlusion multiplicatively on top of the scene.
 * @param stateManager state manager for the WebGL context
 * @param gl WebGL context
 * @param scene current scene
 * @param canvas canvas that the scene is rendered to
 */
x3dom.SSAO.renderSSAO = function(stateManager, gl, scene, canvas) {

	//set up resources if they are non-existent or if they are outdated:
	this.reinitializeShadersIfNecessary(gl);
	this.reinitializeRandomTextureIfNecessary(gl,scene);
	this.reinitializeFBOIfNecessary(gl,canvas);
		
	stateManager.viewport(0,0,canvas.width, canvas.height);

    //render SSAO into fbo
    this.render(stateManager,gl, scene, scene._webgl.fboScene.tex,canvas,x3dom.SSAO.fbo);
    //render blurred SSAO multiplicatively
    gl.enable(gl.BLEND);
    gl.blendFunc(gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA);
    this.blur(stateManager,gl, scene, x3dom.SSAO.fbotex,scene._webgl.fboScene.tex,canvas,null);		
    gl.disable(gl.BLEND);
};

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


x3dom.gfx_webgl = (function () {
    "use strict";

    /*****************************************************************************
     * Context constructor
     *****************************************************************************/
    function Context(ctx3d, canvas, name, x3dElem) {
        this.ctx3d = ctx3d;
        this.canvas = canvas;
        this.name = name;
        this.x3dElem = x3dElem;
        this.IG_PositionBuffer = null;
        this.cache = new x3dom.Cache();
        this.stateManager = new x3dom.StateManager(ctx3d);
    }


    /*****************************************************************************
     * Return context name
     *****************************************************************************/
    Context.prototype.getName = function () {
        return this.name;
    };


    /*****************************************************************************
     * Setup the 3D context and init some things
     *****************************************************************************/
    function setupContext(canvas, forbidMobileShaders, forceMobileShaders, tryWebGL2, x3dElem) {
        var validContextNames = ['webgl', 'experimental-webgl', 'moz-webgl', 'webkit-3d'];

        if (tryWebGL2) {
            validContextNames = ['experimental-webgl2'].concat(validContextNames);
        }

        var ctx = null;

        // TODO; FIXME; this is an ugly hack, don't look for elements like this 
        // (e.g., Bindable nodes may only exist in backend etc.)
        var envNodes   = x3dElem.getElementsByTagName("Environment");
        var ssaoEnabled = (envNodes && envNodes[0] && envNodes[0].hasAttribute("SSAO") && 
                    envNodes[0].getAttribute("SSAO").toLowerCase() === 'true') ? true : false;

        // Context creation params
        var ctxAttribs = {
            alpha: true,
            depth: true,
            stencil: true,
            antialias: !ssaoEnabled,
            premultipliedAlpha: false,
            preserveDrawingBuffer: true,
            failIfMajorPerformanceCaveat : true
        };

        for (var i = 0; i < validContextNames.length; i++) {
            try {
				
                ctx = canvas.getContext(validContextNames[i], ctxAttribs);

                //If context creation fails, retry the creation with failIfMajorPerformanceCaveat = false
                if ( !ctx ) {
                    x3dom.caps.RENDERMODE = "SOFTWARE";
                    ctxAttribs.failIfMajorPerformanceCaveat = false;
                    ctx = canvas.getContext(validContextNames[i], ctxAttribs);
                }

                if (ctx) {
                    var newCtx = new Context(ctx, canvas, 'webgl', x3dElem);

                    try {
                        //Save CAPS
                        x3dom.caps.VENDOR = ctx.getParameter(ctx.VENDOR);
                        x3dom.caps.VERSION = ctx.getParameter(ctx.VERSION);
                        x3dom.caps.RENDERER = ctx.getParameter(ctx.RENDERER);
                        x3dom.caps.SHADING_LANGUAGE_VERSION = ctx.getParameter(ctx.SHADING_LANGUAGE_VERSION);
                        x3dom.caps.RED_BITS = ctx.getParameter(ctx.RED_BITS);
                        x3dom.caps.GREEN_BITS = ctx.getParameter(ctx.GREEN_BITS);
                        x3dom.caps.BLUE_BITS = ctx.getParameter(ctx.BLUE_BITS);
                        x3dom.caps.ALPHA_BITS = ctx.getParameter(ctx.ALPHA_BITS);
                        x3dom.caps.DEPTH_BITS = ctx.getParameter(ctx.DEPTH_BITS);
                        x3dom.caps.MAX_VERTEX_ATTRIBS = ctx.getParameter(ctx.MAX_VERTEX_ATTRIBS);
                        x3dom.caps.MAX_VERTEX_TEXTURE_IMAGE_UNITS = ctx.getParameter(ctx.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
                        x3dom.caps.MAX_VARYING_VECTORS = ctx.getParameter(ctx.MAX_VARYING_VECTORS);
                        x3dom.caps.MAX_VERTEX_UNIFORM_VECTORS = ctx.getParameter(ctx.MAX_VERTEX_UNIFORM_VECTORS);
                        x3dom.caps.MAX_COMBINED_TEXTURE_IMAGE_UNITS = ctx.getParameter(ctx.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
                        x3dom.caps.MAX_TEXTURE_SIZE = ctx.getParameter(ctx.MAX_TEXTURE_SIZE);
                        x3dom.caps.MAX_TEXTURE_IMAGE_UNITS = ctx.getParameter(ctx.MAX_TEXTURE_IMAGE_UNITS);
                        x3dom.caps.MAX_CUBE_MAP_TEXTURE_SIZE = ctx.getParameter(ctx.MAX_CUBE_MAP_TEXTURE_SIZE);
                        x3dom.caps.COMPRESSED_TEXTURE_FORMATS = ctx.getParameter(ctx.COMPRESSED_TEXTURE_FORMATS);
                        x3dom.caps.MAX_RENDERBUFFER_SIZE = ctx.getParameter(ctx.MAX_RENDERBUFFER_SIZE);
                        x3dom.caps.MAX_VIEWPORT_DIMS = ctx.getParameter(ctx.MAX_VIEWPORT_DIMS);
                        x3dom.caps.ALIASED_LINE_WIDTH_RANGE = ctx.getParameter(ctx.ALIASED_LINE_WIDTH_RANGE);
                        x3dom.caps.ALIASED_POINT_SIZE_RANGE = ctx.getParameter(ctx.ALIASED_POINT_SIZE_RANGE);
                        x3dom.caps.SAMPLES = ctx.getParameter(ctx.SAMPLES);
                        x3dom.caps.INDEX_UINT = ctx.getExtension("OES_element_index_uint");
                        x3dom.caps.FP_TEXTURES = ctx.getExtension("OES_texture_float");
                        x3dom.caps.FPL_TEXTURES = ctx.getExtension("OES_texture_float_linear");
                        x3dom.caps.STD_DERIVATIVES = ctx.getExtension("OES_standard_derivatives");
                        x3dom.caps.DRAW_BUFFERS = ctx.getExtension("WEBGL_draw_buffers");
						x3dom.caps.DEPTH_TEXTURE = ctx.getExtension("WEBGL_depth_texture");
                        x3dom.caps.DEBUGRENDERINFO = ctx.getExtension("WEBGL_debug_renderer_info");
						x3dom.caps.EXTENSIONS = ctx.getSupportedExtensions();
						
						//Enabled WebGL2 breaks picking if we use the depth_texture extension for the picking fbo
						if ( x3dom.Utils.isWebGL2Enabled() )
						{
							x3dom.caps.DEPTH_TEXTURE = null;
						}
                        
                        if ( x3dom.caps.DEBUGRENDERINFO ) {
                            x3dom.caps.UNMASKED_RENDERER_WEBGL = ctx.getParameter( x3dom.caps.DEBUGRENDERINFO.UNMASKED_RENDERER_WEBGL );
                            x3dom.caps.UNMASKED_VENDOR_WEBGL = ctx.getParameter( x3dom.caps.DEBUGRENDERINFO.UNMASKED_VENDOR_WEBGL );
                        } else {
                            x3dom.caps.UNMASKED_RENDERER_WEBGL = "";
                            x3dom.caps.UNMASKED_VENDOR_WEBGL = "";
                        }

                        var extString = x3dom.caps.EXTENSIONS.toString().replace(/,/g, ", ");
                        x3dom.debug.logInfo(validContextNames[i] + " context found\nVendor: " + x3dom.caps.VENDOR +
                            " " + x3dom.caps.UNMASKED_VENDOR_WEBGL + ", Renderer: " + x3dom.caps.RENDERER +
                            " " + x3dom.caps.UNMASKED_RENDERER_WEBGL + ", " + "Version: " + x3dom.caps.VERSION + ", " +
                            "ShadingLangV.: " + x3dom.caps.SHADING_LANGUAGE_VERSION
                            + ", MSAA samples: " + x3dom.caps.SAMPLES + "\nExtensions: " + extString);

                        if (x3dom.caps.INDEX_UINT) {
                            x3dom.Utils.maxIndexableCoords = 4294967295;
                        }

                        x3dom.caps.MOBILE = (function (a) {
                            return (/android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|e\-|e\/|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\-|2|g)|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4)))
                        })(navigator.userAgent || navigator.vendor || window.opera);

                        // explicitly disable for iPad and the like
                        if (x3dom.caps.RENDERER.indexOf("PowerVR") >= 0 ||
                            navigator.appVersion.indexOf("Mobile") > -1 ||
                            // coarse guess to find out old SM 2.0 hardware (e.g. Intel):
                            x3dom.caps.MAX_VARYING_VECTORS <= 8 ||
                            x3dom.caps.MAX_VERTEX_TEXTURE_IMAGE_UNITS < 2) {
                            x3dom.caps.MOBILE = true;
                        }

                        if (x3dom.caps.MOBILE) {
                            if (forbidMobileShaders) {
                                x3dom.caps.MOBILE = false;
                                x3dom.debug.logWarning("Detected mobile graphics card! " +
                                    "But being forced to desktop shaders which might not work!");
                            }
                            else {
                                x3dom.debug.logWarning("Detected mobile graphics card! " +
                                    "Using low quality shaders without ImageGeometry support!");
                            }
                        }
                        else {
                            if (forceMobileShaders) {
                                x3dom.caps.MOBILE = true;
                                x3dom.debug.logWarning("Detected desktop graphics card! " +
                                    "But being forced to mobile shaders with lower quality!");
                            }
                        }
                    }
                    catch (ex) {
                        x3dom.debug.logWarning(
                            "Your browser probably supports an older WebGL version. " +
                            "Please try the old mobile runtime instead:\n" +
                            "http://www.x3dom.org/x3dom/src_mobile/x3dom.js");
                        newCtx = null;
                    }

                    return newCtx;
                }
            }
            catch (e) { x3dom.debug.logWarning(e); }
        }
        return null;
    }


    /*****************************************************************************
     * Setup GL objects for given shape
     *****************************************************************************/
    Context.prototype.setupShape = function (gl, drawable, viewarea) {
        var q = 0, q6;
        var textures, t;
        var vertices, positionBuffer;
        var texCoordBuffer, normalBuffer, colorBuffer;
        var indicesBuffer, indexArray;

        var shape = drawable.shape;
        var geoNode = shape._cf.geometry.node;

        if (shape._webgl !== undefined) {
            var needFullReInit = false;

            // TODO; do same for texcoords etc.!
            if (shape._dirty.colors === true &&
                shape._webgl.shader.color === undefined && geoNode._mesh._colors[0].length) {
                // required since otherwise shape._webgl.shader.color stays undefined
                // and thus the wrong shader will be chosen although there are colors
                needFullReInit = true;
            }

            // cleanup vertex buffer objects
            if (needFullReInit && shape._cleanupGLObjects) {
                shape._cleanupGLObjects(true, false);
            }

            //Check for dirty Textures
            if (shape._dirty.texture === true) {
                //Check for Texture add or remove
                if (shape._webgl.texture.length != shape.getTextures().length) {
                    //Delete old Textures
                    for (t = 0; t < shape._webgl.texture.length; ++t) {
                        shape._webgl.texture.pop();
                    }

                    //Generate new Textures
                    textures = shape.getTextures();

                    for (t = 0; t < textures.length; ++t) {
                        shape._webgl.texture.push(new x3dom.Texture(gl, shape._nameSpace.doc, this.cache, textures[t]));
                    }

                    //Set dirty shader
                    shape._dirty.shader = true;

                    //Set dirty texture Coordinates
                    if (shape._webgl.shader.texcoord === undefined)
                        shape._dirty.texcoords = true;
                }
                else {
                    //If someone remove and append at the same time, texture count don't change
                    //and we have to check if all nodes the same as before
                    textures = shape.getTextures();

                    for (t = 0; t < textures.length; ++t) {
                        if (textures[t] === shape._webgl.texture[t].node) {
                            //only update the texture
                            shape._webgl.texture[t].update();
                        }
                        else {
                            //Set texture to null for recreation
                            shape._webgl.texture[t].texture = null;

                            //Set new node
                            shape._webgl.texture[t].node = textures[t];

                            //Update new node
                            shape._webgl.texture[t].update();
                        }
                    }
                }
                shape._dirty.texture = false;
            }

            //Check if we need a new shader
            shape._webgl.shader = this.cache.getShaderByProperties(gl, shape, shape.getShaderProperties(viewarea));

            if (!needFullReInit && shape._webgl.binaryGeometry == 0)    // THINKABOUTME: What about PopGeo & Co.?
            {
                for (q = 0; q < shape._webgl.positions.length; q++)
                {
                    q6 = 6 * q;

                    if (shape._dirty.positions == true || shape._dirty.indexes == true) {
                        if (shape._webgl.shader.position !== undefined) {
                            shape._webgl.indexes[q] = geoNode._mesh._indices[q];

                            gl.deleteBuffer(shape._webgl.buffers[q6]);

                            indicesBuffer = gl.createBuffer();
                            shape._webgl.buffers[q6] = indicesBuffer;

                            // explicitly check first positions array for consistency
                            if (x3dom.caps.INDEX_UINT && (geoNode._mesh._positions[0].length / 3 > 65535)) {
                                indexArray = new Uint32Array(shape._webgl.indexes[q]);
                                shape._webgl.indexType = gl.UNSIGNED_INT;
                            }
                            else {
                                indexArray = new Uint16Array(shape._webgl.indexes[q]);
                                shape._webgl.indexType = gl.UNSIGNED_SHORT;
                            }

                            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
                            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW);

                            indexArray = null;

                            // vertex positions
                            shape._webgl.positions[q] = geoNode._mesh._positions[q];

                            // TODO; don't delete VBO but use glMapBuffer() and DYNAMIC_DRAW
                            gl.deleteBuffer(shape._webgl.buffers[q6 + 1]);

                            positionBuffer = gl.createBuffer();
                            shape._webgl.buffers[q6 + 1] = positionBuffer;

                            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
                            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, shape._webgl.buffers[q6]);

                            vertices = new Float32Array(shape._webgl.positions[q]);

                            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
                            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

                            gl.vertexAttribPointer(shape._webgl.shader.position,
                                geoNode._mesh._numPosComponents,
                                shape._webgl.coordType, false,
                                shape._coordStrideOffset[0], shape._coordStrideOffset[1]);

                            vertices = null;
                        }

                        shape._dirty.positions = false;
                        shape._dirty.indexes = false;
                    }

                    if (shape._dirty.colors == true) {
                        if (shape._webgl.shader.color !== undefined) {
                            shape._webgl.colors[q] = geoNode._mesh._colors[q];

                            gl.deleteBuffer(shape._webgl.buffers[q6 + 4]);

                            colorBuffer = gl.createBuffer();
                            shape._webgl.buffers[q6 + 4] = colorBuffer;

                            colors = new Float32Array(shape._webgl.colors[q]);

                            gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
                            gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);

                            gl.vertexAttribPointer(shape._webgl.shader.color,
                                geoNode._mesh._numColComponents,
                                shape._webgl.colorType, false,
                                shape._colorStrideOffset[0], shape._colorStrideOffset[1]);

                            colors = null;
                        }

                        shape._dirty.colors = false;
                    }

                    if (shape._dirty.normals == true) {
                        if (shape._webgl.shader.normal !== undefined) {
                            shape._webgl.normals[q] = geoNode._mesh._normals[q];

                            gl.deleteBuffer(shape._webgl.buffers[q6 + 2]);

                            normalBuffer = gl.createBuffer();
                            shape._webgl.buffers[q6 + 2] = normalBuffer;

                            normals = new Float32Array(shape._webgl.normals[q]);

                            gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
                            gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);

                            gl.vertexAttribPointer(shape._webgl.shader.normal,
                                geoNode._mesh._numNormComponents,
                                shape._webgl.normalType, false,
                                shape._normalStrideOffset[0], shape._normalStrideOffset[1]);

                            normals = null;
                        }

                        shape._dirty.normals = false;
                    }

                    if (shape._dirty.texcoords == true) {
                        if (shape._webgl.shader.texcoord !== undefined) {
                            shape._webgl.texcoords[q] = geoNode._mesh._texCoords[q];

                            gl.deleteBuffer(shape._webgl.buffers[q6 + 3]);

                            texCoordBuffer = gl.createBuffer();
                            shape._webgl.buffers[q6 + 3] = texCoordBuffer;

                            texCoords = new Float32Array(shape._webgl.texcoords[q]);

                            gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
                            gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);

                            gl.vertexAttribPointer(shape._webgl.shader.texCoord,
                                geoNode._mesh._numTexComponents,
                                shape._webgl.texCoordType, false,
                                shape._texCoordStrideOffset[0], shape._texCoordStrideOffset[1]);

                            texCoords = null;
                        }

                        shape._dirty.texcoords = false;
                    }

                    if (shape._dirty.specialAttribs == true) {
                        if (shape._webgl.shader.particleSize !== undefined) {
                            var szArr = geoNode._vf.size.toGL();

                            if (szArr.length) {
                                gl.deleteBuffer(shape._webgl.buffers[q6 + 5]);
                                shape._webgl.buffers[q6 + 5] = gl.createBuffer();

                                gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[q6 + 5]);
                                gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(szArr), gl.STATIC_DRAW);
                            }

                            shape._dirty.specialAttribs = false;
                        }
                        // Maybe other special attribs here, though e.g. AFAIK only BG (which not handled here) has ids.
                    }
                }
            }
            else
            {
                // TODO; does not yet work with shared objects
                /*
                var spOld = shape._webgl.shader;
                if (shape._cleanupGLObjects && needFullReInit)
                    shape._cleanupGLObjects(true, false);

                // complete setup is sort of brute force, thus optimize!
                x3dom.BinaryContainerLoader.setupBinGeo(shape, spOld, gl, viewarea, this);
                shape.unsetGeoDirty();
                */
            }

            if (shape._webgl.imageGeometry != 0) {
                for (t = 0; t < shape._webgl.texture.length; ++t) {
                    shape._webgl.texture[t].updateTexture();
                }

                geoNode.unsetGeoDirty();
                shape.unsetGeoDirty();
            }

            if (!needFullReInit) {
                // we're done
                return;
            }
        }
        else if (!(x3dom.isa(geoNode, x3dom.nodeTypes.Text) ||
                   x3dom.isa(geoNode, x3dom.nodeTypes.BinaryGeometry) ||
                   x3dom.isa(geoNode, x3dom.nodeTypes.PopGeometry)    ||
                   x3dom.isa(geoNode, x3dom.nodeTypes.ExternalGeometry)||
                   x3dom.isa(shape, x3dom.nodeTypes.ExternalShape)) &&
                  (!geoNode || geoNode._mesh._positions[0].length < 1))
        {
            if (x3dom.caps.MAX_VERTEX_TEXTURE_IMAGE_UNITS < 2 &&
                x3dom.isa(geoNode, x3dom.nodeTypes.ImageGeometry)) {
                x3dom.debug.logError("Can't render ImageGeometry nodes with only " +
                    x3dom.caps.MAX_VERTEX_TEXTURE_IMAGE_UNITS +
                    " vertex texture units. Please upgrade your GPU!");
            }
            else {
                x3dom.debug.logError("NO VALID MESH OR NO VERTEX POSITIONS SET!");
            }
            return;
        }

        // we're on init, thus reset all dirty flags
        shape.unsetDirty();

        // dynamically attach clean-up method for GL objects
        if (!shape._cleanupGLObjects)
        {
            shape._cleanupGLObjects = function (force, delGL)
            {
                // FIXME; what if complete tree is removed? Then _parentNodes.length may be greater 0.
                if (this._webgl && ((arguments.length > 0 && force) || this._parentNodes.length == 0))
                {
                    var sp = this._webgl.shader;

                    for (var q = 0; q < this._webgl.positions.length; q++) {
                        var q6 = 6 * q;

                        if (sp.position !== undefined) {
                            gl.deleteBuffer(this._webgl.buffers[q6 + 1]);
                            gl.deleteBuffer(this._webgl.buffers[q6]);
                        }

                        if (sp.normal !== undefined) {
                            gl.deleteBuffer(this._webgl.buffers[q6 + 2]);
                        }

                        if (sp.texcoord !== undefined) {
                            gl.deleteBuffer(this._webgl.buffers[q6 + 3]);
                        }

                        if (sp.color !== undefined) {
                            gl.deleteBuffer(this._webgl.buffers[q6 + 4]);
                        }

                        if (sp.id !== undefined) {
                            gl.deleteBuffer(this._webgl.buffers[q6 + 5]);
                        }
                    }

                    for (var df = 0; df < this._webgl.dynamicFields.length; df++) {
                        var attrib = this._webgl.dynamicFields[df];

                        if (sp[attrib.name] !== undefined) {
                            gl.deleteBuffer(attrib.buf);
                        }
                    }

                    if (delGL === undefined)
                        delGL = true;

                    if (delGL) {
                        delete this._webgl;

                        // be optimistic, one shape removed makes room for another one
                        x3dom.BinaryContainerLoader.outOfMemory = false;
                    }
                }
            };  // shape._cleanupGLObjects()
        }


        shape._webgl = {
            positions: geoNode._mesh._positions,
            normals: geoNode._mesh._normals,
            texcoords: geoNode._mesh._texCoords,
            colors: geoNode._mesh._colors,
            indexes: geoNode._mesh._indices,
            //indicesBuffer,positionBuffer,normalBuffer,texcBuffer,colorBuffer
            //buffers: [{},{},{},{},{}],
            indexType: gl.UNSIGNED_SHORT,
            coordType: gl.FLOAT,
            normalType: gl.FLOAT,
            texCoordType: gl.FLOAT,
            colorType: gl.FLOAT,
            texture: [],
            dirtyLighting: x3dom.Utils.checkDirtyLighting(viewarea),
            imageGeometry: 0,   // 0 := no IG,  1 := indexed IG, -1  := non-indexed IG
            binaryGeometry: 0,  // 0 := no BG,  1 := indexed BG, -1  := non-indexed BG
            popGeometry: 0,     // 0 : no PG,  1 : indexed PG, -1  : non-indexed PG
            externalGeometry: 0 // 0 : no EG,  1 : indexed EG, -1 : non-indexed EG
        };

        //Set Textures		
        textures = shape.getTextures();
        for (t = 0; t < textures.length; ++t) {
            shape._webgl.texture.push(new x3dom.Texture(gl, shape._nameSpace.doc, this.cache, textures[t]));
        }

        //Set Shader
        //shape._webgl.shader = this.cache.getDynamicShader(gl, viewarea, shape);
        //shape._webgl.shader = this.cache.getShaderByProperties(gl, drawable.properties);
        shape._webgl.shader = this.cache.getShaderByProperties(gl, shape, shape.getShaderProperties(viewarea));

        // init vertex attribs
        var sp = shape._webgl.shader;
        var currAttribs = 0;

        shape._webgl.buffers = [];
        shape._webgl.dynamicFields = [];

        //Set Geometry Primitive Type
        if (x3dom.isa(geoNode, x3dom.nodeTypes.X3DBinaryContainerGeometryNode))
        {
            shape._webgl.primType = [];

            for (var primCnt = 0; primCnt < geoNode._vf.primType.length; ++primCnt)
            {
                shape._webgl.primType.push(x3dom.Utils.primTypeDic(gl, geoNode._vf.primType[primCnt]));
            }
        }
        else
        {
            shape._webgl.primType = x3dom.Utils.primTypeDic(gl, geoNode._mesh._primType);
        }

        // Binary container geometries need special handling
        if (x3dom.isa(geoNode, x3dom.nodeTypes.ExternalGeometry))
        {
            geoNode.update(shape, sp, gl, viewarea, this);
        }
        else if(x3dom.isa(shape, x3dom.nodeTypes.ExternalShape))
        {
            shape.update(shape, sp, gl, viewarea, this);
        }
        else if (x3dom.isa(geoNode, x3dom.nodeTypes.BinaryGeometry))
        {
            x3dom.BinaryContainerLoader.setupBinGeo(shape, sp, gl, viewarea, this);
        }
        else if (x3dom.isa(geoNode, x3dom.nodeTypes.PopGeometry))
        {
            x3dom.BinaryContainerLoader.setupPopGeo(shape, sp, gl, viewarea, this);
        }
        else if (x3dom.isa(geoNode, x3dom.nodeTypes.ImageGeometry))
        {
            x3dom.BinaryContainerLoader.setupImgGeo(shape, sp, gl, viewarea, this);
        }
        else // No special BinaryMesh, but IFS or similar
        {
            for (q = 0; q < shape._webgl.positions.length; q++)
            {
                q6 = 6 * q;

                if (sp.position !== undefined) {
                    // bind indices for drawElements() call
                    indicesBuffer = gl.createBuffer();
                    shape._webgl.buffers[q6] = indicesBuffer;

                    // explicitly check first positions array for consistency
                    if (x3dom.caps.INDEX_UINT && (shape._webgl.positions[0].length / 3 > 65535)) {
                        indexArray = new Uint32Array(shape._webgl.indexes[q]);
                        shape._webgl.indexType = gl.UNSIGNED_INT;
                    }
                    else {
                        indexArray = new Uint16Array(shape._webgl.indexes[q]);
                        shape._webgl.indexType = gl.UNSIGNED_SHORT;
                    }

                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
                    gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW);

                    indexArray = null;

                    positionBuffer = gl.createBuffer();
                    shape._webgl.buffers[q6 + 1] = positionBuffer;
                    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
                    
                    vertices = new Float32Array(shape._webgl.positions[q]);

                    gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
                    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

                    gl.vertexAttribPointer(sp.position,
                        geoNode._mesh._numPosComponents,
                        shape._webgl.coordType, false,
                        shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
                    gl.enableVertexAttribArray(sp.position);

                    vertices = null;
                }
                if (sp.normal !== undefined || shape._webgl.normals[q]) {
                    normalBuffer = gl.createBuffer();
                    shape._webgl.buffers[q6 + 2] = normalBuffer;

                    var normals = new Float32Array(shape._webgl.normals[q]);

                    gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
                    gl.bufferData(gl.ARRAY_BUFFER, normals, gl.STATIC_DRAW);

                    gl.vertexAttribPointer(sp.normal,
                        geoNode._mesh._numNormComponents,
                        shape._webgl.normalType, false,
                        shape._normalStrideOffset[0], shape._normalStrideOffset[1]);
                    gl.enableVertexAttribArray(sp.normal);

                    normals = null;
                }
                if (sp.texcoord !== undefined) {
                    var texcBuffer = gl.createBuffer();
                    shape._webgl.buffers[q6 + 3] = texcBuffer;

                    var texCoords = new Float32Array(shape._webgl.texcoords[q]);

                    gl.bindBuffer(gl.ARRAY_BUFFER, texcBuffer);
                    gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);

                    gl.vertexAttribPointer(sp.texcoord,
                        geoNode._mesh._numTexComponents,
                        shape._webgl.texCoordType, false,
                        shape._texCoordStrideOffset[0], shape._texCoordStrideOffset[1]);
                    gl.enableVertexAttribArray(sp.texcoord);

                    texCoords = null;
                }
                if (sp.color !== undefined) {
                    colorBuffer = gl.createBuffer();
                    shape._webgl.buffers[q6 + 4] = colorBuffer;

                    var colors = new Float32Array(shape._webgl.colors[q]);

                    gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
                    gl.bufferData(gl.ARRAY_BUFFER, colors, gl.STATIC_DRAW);

                    gl.vertexAttribPointer(sp.color,
                        geoNode._mesh._numColComponents,
                        shape._webgl.colorType, false,
                        shape._colorStrideOffset[0], shape._colorStrideOffset[1]);
                    gl.enableVertexAttribArray(sp.color);

                    colors = null;
                }
                if (sp.particleSize !== undefined) {
                    var sizeArr = geoNode._vf.size.toGL();

                    if (sizeArr.length) {
                        var sizeBuffer = gl.createBuffer();
                        shape._webgl.buffers[q6 + 5] = sizeBuffer;

                        gl.bindBuffer(gl.ARRAY_BUFFER, sizeBuffer);
                        gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sizeArr), gl.STATIC_DRAW);
                    }
                }
            }

            // TODO; FIXME; handle geometry with split mesh that has dynamic fields!
            for (var df in geoNode._mesh._dynamicFields)
            {
                if (!geoNode._mesh._dynamicFields.hasOwnProperty(df))
                    continue;

                var attrib = geoNode._mesh._dynamicFields[df];

                shape._webgl.dynamicFields[currAttribs] = {
                    buf: {}, name: df, numComponents: attrib.numComponents };

                if (sp[df] !== undefined) {
                    var attribBuffer = gl.createBuffer();
                    shape._webgl.dynamicFields[currAttribs++].buf = attribBuffer;

                    var attribs = new Float32Array(attrib.value);

                    gl.bindBuffer(gl.ARRAY_BUFFER, attribBuffer);
                    gl.bufferData(gl.ARRAY_BUFFER, attribs, gl.STATIC_DRAW);

                    gl.vertexAttribPointer(sp[df], attrib.numComponents, gl.FLOAT, false, 0, 0);

                    attribs = null;
                }
            }
        } // Standard geometry
    };


    /*****************************************************************************
     * Mainly manages rendering of backgrounds and buffer clearing
     *****************************************************************************/
    Context.prototype.setupScene = function (gl, bgnd) {
        var sphere = null;
        var texture = null;

        var that = this;

        if (bgnd._webgl !== undefined) {
            if (!bgnd._dirty) {
                return;
            }

            if (bgnd._webgl.texture !== undefined && bgnd._webgl.texture) {
                gl.deleteTexture(bgnd._webgl.texture);
            }
            if (bgnd._cleanupGLObjects) {
                bgnd._cleanupGLObjects();
            }
            bgnd._webgl = {};
        }

        bgnd._dirty = false;

        var url = bgnd.getTexUrl();
        var i = 0;
        var w = 1, h = 1;

        if (url.length > 0 && url[0].length > 0) {
            if (url.length >= 6 && url[1].length > 0 && url[2].length > 0 &&
                url[3].length > 0 && url[4].length > 0 && url[5].length > 0) {
                sphere = new x3dom.nodeTypes.Sphere();

                bgnd._webgl = {
                    positions: sphere._mesh._positions[0],
                    indexes: sphere._mesh._indices[0],
                    buffers: [
                        {}, {}
                    ]
                };

                bgnd._webgl.primType = gl.TRIANGLES;

                bgnd._webgl.shader = this.cache.getShader(gl, x3dom.shader.BACKGROUND_CUBETEXTURE);

                bgnd._webgl.texture = x3dom.Utils.createTextureCube(gl, bgnd._nameSpace.doc, url,
                    true, bgnd._vf.crossOrigin, true, false);
            }
            else {
                bgnd._webgl = {
                    positions: [-w, -h, 0, -w, h, 0, w, -h, 0, w, h, 0],
                    indexes: [0, 1, 2, 3],
                    buffers: [
                        {}, {}
                    ]
                };

                url = bgnd._nameSpace.getURL(url[0]);

                bgnd._webgl.texture = x3dom.Utils.createTexture2D(gl, bgnd._nameSpace.doc, url,
                    true, bgnd._vf.crossOrigin, false, false);

                bgnd._webgl.primType = gl.TRIANGLE_STRIP;

                bgnd._webgl.shader = this.cache.getShader(gl, x3dom.shader.BACKGROUND_TEXTURE);
            }
        }
        else {
            if (bgnd.getSkyColor().length > 1 || bgnd.getGroundColor().length) {
                sphere = new x3dom.nodeTypes.Sphere();
                texture = gl.createTexture();

                bgnd._webgl = {
                    positions: sphere._mesh._positions[0],
                    texcoords: sphere._mesh._texCoords[0],
                    indexes: sphere._mesh._indices[0],
                    buffers: [
                        {}, {}, {}
                    ],
                    texture: texture,
                    primType: gl.TRIANGLES
                };

                var N = x3dom.Utils.nextHighestPowerOfTwo(
                    bgnd.getSkyColor().length + bgnd.getGroundColor().length + 2);
                N = (N < 512) ? 512 : N;

                var n = bgnd._vf.groundAngle.length;
                var tmp = [], arr = [];
                var colors = [], sky = [0];

                for (i = 0; i < bgnd._vf.skyColor.length; i++) {
                    colors[i] = bgnd._vf.skyColor[i];
                }

                for (i = 0; i < bgnd._vf.skyAngle.length; i++) {
                    sky[i + 1] = bgnd._vf.skyAngle[i];
                }

                if (n > 0 || bgnd._vf.groundColor.length == 1) {
                    if (sky[sky.length - 1] < Math.PI / 2) {
                        sky[sky.length] = Math.PI / 2 - x3dom.fields.Eps;
                        colors[colors.length] = colors[colors.length - 1];
                    }

                    for (i = n - 1; i >= 0; i--) {
                        if ((i == n - 1) && (Math.PI - bgnd._vf.groundAngle[i] <= Math.PI / 2)) {
                            sky[sky.length] = Math.PI / 2;
                            colors[colors.length] = bgnd._vf.groundColor[bgnd._vf.groundColor.length - 1];
                        }
                        sky[sky.length] = Math.PI - bgnd._vf.groundAngle[i];
                        colors[colors.length] = bgnd._vf.groundColor[i + 1];
                    }

                    if (n == 0 && bgnd._vf.groundColor.length == 1) {
                        sky[sky.length] = Math.PI / 2;
                        colors[colors.length] = bgnd._vf.groundColor[0];
                    }
                    sky[sky.length] = Math.PI;
                    colors[colors.length] = bgnd._vf.groundColor[0];
                }
                else {
                    if (sky[sky.length - 1] < Math.PI) {
                        sky[sky.length] = Math.PI;
                        colors[colors.length] = colors[colors.length - 1];
                    }
                }

                for (i = 0; i < sky.length; i++) {
                    sky[i] /= Math.PI;
                }

                if (sky.length != colors.length) {
                    x3dom.debug.logError("Number of background colors and corresponding angles are different!");
                    var minArrayLength = (sky.length < colors.length) ? sky.length : colors.length;
                    sky.length = minArrayLength;
                    colors.length = minArrayLength;
                }

                var interp = new x3dom.nodeTypes.ColorInterpolator();

                interp._vf.key = new x3dom.fields.MFFloat(sky);
                interp._vf.keyValue = new x3dom.fields.MFColor(colors);

                for (i = 0; i < N; i++) {
                    interp._vf.set_fraction = i / (N - 1.0);

                    interp.fieldChanged("set_fraction");
                    tmp[i] = interp._vf.value_changed;
                }

                tmp.reverse();

                var alpha = Math.floor((1.0 - bgnd.getTransparency()) * 255);

                for (i = 0; i < tmp.length; i++) {
                    arr.push(Math.floor(tmp[i].r * 255),
                             Math.floor(tmp[i].g * 255),
                             Math.floor(tmp[i].b * 255),
                             alpha);
                }

                var pixels = new Uint8Array(arr);
                var format = gl.RGBA;

                N = pixels.length / 4;

                gl.bindTexture(gl.TEXTURE_2D, texture);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                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.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
                gl.texImage2D(gl.TEXTURE_2D, 0, format, 1, N, 0, format, gl.UNSIGNED_BYTE, pixels);
                gl.bindTexture(gl.TEXTURE_2D, null);

                bgnd._webgl.shader = this.cache.getShader(gl, x3dom.shader.BACKGROUND_SKYTEXTURE);
            }
            else {
                // Impl. gradient bg etc., e.g. via canvas 2d? But can be done via CSS anyway...
                bgnd._webgl = {};
            }
        }

        if (bgnd._webgl.shader) {
            var sp = bgnd._webgl.shader;

            var positionBuffer = gl.createBuffer();
            bgnd._webgl.buffers[1] = positionBuffer;
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

            var vertices = new Float32Array(bgnd._webgl.positions);

            gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

            gl.vertexAttribPointer(sp.position, 3, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(sp.position);

            var indicesBuffer = gl.createBuffer();
            bgnd._webgl.buffers[0] = indicesBuffer;

            var indexArray = new Uint16Array(bgnd._webgl.indexes);

            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW);

            vertices = null;
            indexArray = null;

            if (sp.texcoord !== undefined) {
                var texcBuffer = gl.createBuffer();
                bgnd._webgl.buffers[2] = texcBuffer;

                var texcoords = new Float32Array(bgnd._webgl.texcoords);

                gl.bindBuffer(gl.ARRAY_BUFFER, texcBuffer);
                gl.bufferData(gl.ARRAY_BUFFER, texcoords, gl.STATIC_DRAW);

                gl.vertexAttribPointer(sp.texcoord, 2, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(sp.texcoord);

                texcoords = null;
            }

            bgnd._cleanupGLObjects = function () {
                var sp = this._webgl.shader;

                if (sp.position !== undefined) {
                    gl.deleteBuffer(this._webgl.buffers[0]);
                    gl.deleteBuffer(this._webgl.buffers[1]);
                }
                if (sp.texcoord !== undefined) {
                    gl.deleteBuffer(this._webgl.buffers[2]);
                }
            };
        }

        bgnd._webgl.render = function (gl, mat_view, mat_proj)
        {
            var sp = bgnd._webgl.shader;
            var alpha = 1.0 - bgnd.getTransparency();

            var mat_scene = null;
            var projMatrix_22 = mat_proj._22,
                projMatrix_23 = mat_proj._23;
            var camPos = mat_view.e3();

            if ((sp !== undefined && sp !== null) &&
                (sp.texcoord !== undefined && sp.texcoord !== null) &&
                (bgnd._webgl.texture !== undefined && bgnd._webgl.texture !== null)) {
                gl.clearColor(0, 0, 0, alpha);
                gl.clearDepth(1.0);
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

                that.stateManager.frontFace(gl.CCW);
                that.stateManager.disable(gl.CULL_FACE);
                that.stateManager.disable(gl.DEPTH_TEST);
                that.stateManager.disable(gl.BLEND);

                that.stateManager.useProgram(sp);

                if (!sp.tex) {
                    sp.tex = 0;
                }

                // adapt projection matrix to better near/far
                mat_proj._22 = 100001 / 99999;
                mat_proj._23 = 200000 / 99999;
                // center viewpoint
                mat_view._03 = 0;
                mat_view._13 = 0;
                mat_view._23 = 0;

                mat_scene = mat_proj.mult(mat_view);
                sp.modelViewProjectionMatrix = mat_scene.toGL();

                mat_view._03 = camPos.x;
                mat_view._13 = camPos.y;
                mat_view._23 = camPos.z;

                mat_proj._22 = projMatrix_22;
                mat_proj._23 = projMatrix_23;

                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, bgnd._webgl.texture);

                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                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.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bgnd._webgl.buffers[0]);

                gl.bindBuffer(gl.ARRAY_BUFFER, bgnd._webgl.buffers[1]);
                gl.vertexAttribPointer(sp.position, 3, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(sp.position);

                gl.bindBuffer(gl.ARRAY_BUFFER, bgnd._webgl.buffers[2]);
                gl.vertexAttribPointer(sp.texcoord, 2, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(sp.texcoord);

                gl.drawElements(bgnd._webgl.primType, bgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0);

                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, null);

                gl.disableVertexAttribArray(sp.position);
                gl.disableVertexAttribArray(sp.texcoord);

                gl.clear(gl.DEPTH_BUFFER_BIT);
            }
            else if (!sp || !bgnd._webgl.texture ||
                    (bgnd._webgl.texture.textureCubeReady !== undefined &&
                     bgnd._webgl.texture.textureCubeReady !== true)) {
                var bgCol = bgnd.getSkyColor().toGL();

                gl.clearColor(bgCol[0], bgCol[1], bgCol[2], alpha);
                gl.clearDepth(1.0);
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
            }
            else {
                gl.clearColor(0, 0, 0, alpha);
                gl.clearDepth(1.0);
                gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

                that.stateManager.frontFace(gl.CCW);
                that.stateManager.disable(gl.CULL_FACE);
                that.stateManager.disable(gl.DEPTH_TEST);
                that.stateManager.disable(gl.BLEND);

                that.stateManager.useProgram(sp);

                if (!sp.tex) {
                    sp.tex = 0;
                }

                if (bgnd._webgl.texture.textureCubeReady) {
                    // adapt projection matrix to better near/far
                    mat_proj._22 = 100001 / 99999;
                    mat_proj._23 = 200000 / 99999;
                    // center viewpoint
                    mat_view._03 = 0;
                    mat_view._13 = 0;
                    mat_view._23 = 0;

                    mat_scene = mat_proj.mult(mat_view);
                    sp.modelViewProjectionMatrix = mat_scene.toGL();

                    mat_view._03 = camPos.x;
                    mat_view._13 = camPos.y;
                    mat_view._23 = camPos.z;

                    mat_proj._22 = projMatrix_22;
                    mat_proj._23 = projMatrix_23;

                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, bgnd._webgl.texture);

                    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                    gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                    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);
                }
                else {
                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_2D, bgnd._webgl.texture);

                    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                    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);
					
					if ( bgnd._vf.scaling && bgnd._webgl.texture.ready )
					{
                        var ratio       = 1.0;
						var viewport    = new x3dom.fields.SFVec2f(that.canvas.width, that.canvas.height);
						var texture     = new x3dom.fields.SFVec2f(bgnd._webgl.texture.width, bgnd._webgl.texture.height);
                                          
                        if ( viewport.x > viewport.y )
                        {
                            ratio = viewport.x / texture.x
                            texture.x = viewport.x;
                            texture.y = texture.y * ratio;
                        }
                        else
                        {
                            ratio = viewport.y / texture.y
                            texture.y = viewport.y;
                            texture.x = texture.x * ratio;
                        }
                        
						var scale       = viewport.divideComponents( texture );
						var translation = texture.subtract( viewport ).multiply( 0.5 ).divideComponents( texture );
					}
					else
					{
						var scale       = new x3dom.fields.SFVec2f(1.0, 1.0);
						var translation = new x3dom.fields.SFVec2f(0.0, 0.0);
					}
					
					sp.scale = scale.toGL();
					sp.translation = translation.toGL();
                }

                gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, bgnd._webgl.buffers[0]);
                gl.bindBuffer(gl.ARRAY_BUFFER, bgnd._webgl.buffers[1]);
                gl.vertexAttribPointer(sp.position, 3, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(sp.position);

                gl.drawElements(bgnd._webgl.primType, bgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0);

                gl.disableVertexAttribArray(sp.position);

                gl.activeTexture(gl.TEXTURE0);
                if (bgnd._webgl.texture.textureCubeReady) {
                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
                }
                else {
                    gl.bindTexture(gl.TEXTURE_2D, null);
                }

                gl.clear(gl.DEPTH_BUFFER_BIT);
            }
        };
    };


    /*****************************************************************************
     * Setup Frontgrounds
     *****************************************************************************/
    Context.prototype.setupFgnds = function (gl, scene) {
        if (scene._fgnd !== undefined) {
            return;
        }

        var that = this;

        var w = 1, h = 1;
        scene._fgnd = {};

        scene._fgnd._webgl = {
            positions: [-w, -h, 0, -w, h, 0, w, -h, 0, w, h, 0],
            indexes: [0, 1, 2, 3],
            buffers: [
                {}, {}
            ]
        };

        scene._fgnd._webgl.primType = gl.TRIANGLE_STRIP;

        scene._fgnd._webgl.shader = this.cache.getShader(gl, x3dom.shader.FRONTGROUND_TEXTURE);

        var sp = scene._fgnd._webgl.shader;

        var positionBuffer = gl.createBuffer();
        scene._fgnd._webgl.buffers[1] = positionBuffer;
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

        var vertices = new Float32Array(scene._fgnd._webgl.positions);

        gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

        gl.vertexAttribPointer(sp.position, 3, gl.FLOAT, false, 0, 0);

        var indicesBuffer = gl.createBuffer();
        scene._fgnd._webgl.buffers[0] = indicesBuffer;

        var indexArray = new Uint16Array(scene._fgnd._webgl.indexes);

        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.STATIC_DRAW);

        vertices = null;
        indexArray = null;

        scene._fgnd._webgl.render = function (gl, tex) {
            scene._fgnd._webgl.texture = tex;

            that.stateManager.frontFace(gl.CCW);
            that.stateManager.disable(gl.CULL_FACE);
            that.stateManager.disable(gl.DEPTH_TEST);

            that.stateManager.useProgram(sp);

            if (!sp.tex) {
                sp.tex = 0;
            }

            //this.stateManager.enable(gl.TEXTURE_2D);
            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, scene._fgnd._webgl.texture);

            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
            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.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, scene._fgnd._webgl.buffers[0]);
            gl.bindBuffer(gl.ARRAY_BUFFER, scene._fgnd._webgl.buffers[1]);
            gl.vertexAttribPointer(sp.position, 3, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(sp.position);

            gl.drawElements(scene._fgnd._webgl.primType, scene._fgnd._webgl.indexes.length, gl.UNSIGNED_SHORT, 0);

            gl.disableVertexAttribArray(sp.position);

            gl.activeTexture(gl.TEXTURE0);
            gl.bindTexture(gl.TEXTURE_2D, null);
            //this.stateManager.disable(gl.TEXTURE_2D);
        };
    };


    /*****************************************************************************
     * Render Shadow-Pass
     *****************************************************************************/
    Context.prototype.renderShadowPass = function (gl, viewarea, mat_scene, mat_view, targetFbo, camOffset, isCameraView)
    {
        var scene = viewarea._scene;

        var indicesReady = false;

        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, targetFbo.fbo);
        this.stateManager.viewport(0, 0, targetFbo.width, targetFbo.height);

        gl.clearColor(1.0, 1.0, 1.0, 0.0);
        gl.clearDepth(1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        this.stateManager.depthFunc(gl.LEQUAL);
        this.stateManager.enable(gl.DEPTH_TEST);
        this.stateManager.enable(gl.CULL_FACE);
        this.stateManager.disable(gl.BLEND);

        var bgCenter = x3dom.fields.SFVec3f.NullVector.toGL();
        var bgSize = x3dom.fields.SFVec3f.OneVector.toGL();

        var env = scene.getEnvironment();
        var excludeTrans = env._vf.shadowExcludeTransparentObjects;

        var i, n = scene.drawableCollection.length;

        for (i = 0; i < n; i++)
        {
            var drawable = scene.drawableCollection.get(i);
            var trafo = drawable.transform;
            var shape = drawable.shape;

            var s_gl = shape._webgl;

            if (!s_gl || (excludeTrans && drawable.sortType == 'transparent')) {
                continue;
            }

            var s_geo = shape._cf.geometry.node;
            var s_app = shape._cf.appearance.node;
            var s_msh = s_geo._mesh;

            var properties = shape.getShaderProperties(viewarea);

            //Generate Dynamic picking shader
            var sp = this.cache.getShaderByProperties(gl, shape, properties, null, true);

            if (!sp) {   // error
                return;
            }

            //Bind shader
            this.stateManager.useProgram(sp);

            sp.cameraView = isCameraView;
            sp.offset = camOffset;

            sp.modelViewProjectionMatrix = mat_scene.mult(trafo).toGL();

            // BoundingBox stuff
            if (s_gl.coordType != gl.FLOAT) {
                if (!s_gl.popGeometry && (x3dom.Utils.isUnsignedType(s_geo._vf.coordType))) {
                    sp.bgCenter = s_geo.getMin().toGL();
                }
                else {
                    sp.bgCenter = s_geo._vf.position.toGL();
                }
                sp.bgSize = s_geo._vf.size.toGL();
                sp.bgPrecisionMax = s_geo.getPrecisionMax('coordType');
            }

            //===========================================================================
            // Set ClipPlanes
            //===========================================================================
            if (shape._clipPlanes) {
                sp.modelViewMatrix = mat_view.mult(trafo).toGL();
                sp.viewMatrixInverse = mat_view.inverse().toGL();
                for (var cp = 0; cp < shape._clipPlanes.length; cp++) {
                    var clip_plane = shape._clipPlanes[cp].plane;
                    var clip_trafo = shape._clipPlanes[cp].trafo;

                    sp['clipPlane' + cp + '_Plane'] = clip_trafo.multMatrixPlane(clip_plane._vf.plane).toGL();
                    sp['clipPlane' + cp + '_CappingStrength'] = clip_plane._vf.cappingStrength;
                    sp['clipPlane' + cp + '_CappingColor'] = clip_plane._vf.cappingColor.toGL();
                }
            }

            //ImageGeometry stuff
            if (s_gl.imageGeometry != 0 && !x3dom.caps.MOBILE)  // FIXME: mobile errors
            {
                sp.IG_bboxMin = s_geo.getMin().toGL();
                sp.IG_bboxMax = s_geo.getMax().toGL();
                sp.IG_implicitMeshSize = s_geo._vf.implicitMeshSize.toGL();  // FIXME

                var coordTex = x3dom.Utils.findTextureByName(s_gl.texture, "IG_coords0");
                if (coordTex) {
                    sp.IG_coordTextureWidth = coordTex.texture.width;
                    sp.IG_coordTextureHeight = coordTex.texture.height;
                }

                if (s_gl.imageGeometry == 1) {
                    var indexTex = x3dom.Utils.findTextureByName(s_gl.texture, "IG_index");
                    if (indexTex) {
                        sp.IG_indexTextureWidth = indexTex.texture.width;
                        sp.IG_indexTextureHeight = indexTex.texture.height;
                    }

                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_2D, indexTex.texture);

                    gl.activeTexture(gl.TEXTURE1);
                    gl.bindTexture(gl.TEXTURE_2D, coordTex.texture);
                }
                else {
                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_2D, coordTex.texture);
                }

                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_MAG_FILTER, gl.NEAREST);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

                var texUnit = 0;
                if (s_geo.getIndexTexture()) {
                    if (!sp.IG_indexTexture) {
                        sp.IG_indexTexture = texUnit++;
                    }
                }
                if (s_geo.getCoordinateTexture(0)) {
                    if (!sp.IG_coordinateTexture) {
                        sp.IG_coordinateTexture = texUnit++;
                    }
                }
            }
            else if ((s_gl.binaryGeometry != 0 || s_gl.externalGeometry != 0) && s_geo._vf["idsPerVertex"] == true) { //MultiPart
                var shader = s_app._shader;
                if(shader && x3dom.isa(s_app._shader, x3dom.nodeTypes.CommonSurfaceShader)) {
                    if (shader.getMultiVisibilityMap()) {
                        sp.multiVisibilityMap = 0;
                        var visTex = x3dom.Utils.findTextureByName(s_gl.texture, "multiVisibilityMap");
                        sp.multiVisibilityWidth = visTex.texture.width;
                        sp.multiVisibilityHeight = visTex.texture.height;
                        gl.activeTexture(gl.TEXTURE0);
                        gl.bindTexture(gl.TEXTURE_2D, visTex.texture);
                        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_MAG_FILTER, gl.NEAREST);
                        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                    }
                }
            }

            if (shape.isSolid()) {
                this.stateManager.enable(gl.CULL_FACE);

                if (shape.isCCW()) {
                    this.stateManager.frontFace(gl.CCW);
                }
                else {
                    this.stateManager.frontFace(gl.CW);
                }
            }
            else {
                this.stateManager.disable(gl.CULL_FACE);
            }

            //===========================================================================
            // Set DepthMode
            //===========================================================================
            var depthMode = s_app ? s_app._cf.depthMode.node : null;
            if (depthMode)
            {
                if (depthMode._vf.enableDepthTest)
                {
                    //Enable Depth Test
                    this.stateManager.enable(gl.DEPTH_TEST);

                    //Set Depth Mask
                    this.stateManager.depthMask(!depthMode._vf.readOnly);

                    //Set Depth Function
                    this.stateManager.depthFunc(x3dom.Utils.depthFunc(gl, depthMode._vf.depthFunc));

                    //Set Depth Range
                    this.stateManager.depthRange(depthMode._vf.zNearRange, depthMode._vf.zFarRange);
                }
                else
                {
                    //Disable Depth Test
                    this.stateManager.disable(gl.DEPTH_TEST);
                }
            }
            else //Set Defaults
            {
                this.stateManager.enable(gl.DEPTH_TEST);
                this.stateManager.depthMask(true);
                this.stateManager.depthFunc(gl.LEQUAL);
            }

            //PopGeometry: adapt LOD and set shader variables
            if (s_gl.popGeometry) {
                var model_view = mat_view.mult(trafo);
                // FIXME; viewarea's width/height twice as big as render buffer size, which leads to too high precision
                // the correct viewarea here would be one that holds this half-sized render buffer
                this.updatePopState(drawable, s_geo, sp, s_gl, scene, model_view, viewarea, this.x3dElem.runtime.fps);
            }

            var q_n;
            if (s_gl.externalGeometry != 0)
            {
                q_n = shape.meshes.length;
            }
            else
            {
                q_n = s_gl.positions.length;
            }
            for (var q = 0; q < q_n; q++) {
                var q6 = 6 * q;
                var v, v_n, offset;


                if (s_gl.externalGeometry != 0) {
                    var mesh = shape.meshes[q];

                    mesh.bindVertexAttribPointerPosition(gl, sp, false);
                    mesh.render(gl, null);
                }
                else
                if ( !(sp.position !== undefined && s_gl.buffers[q6 + 1] && s_gl.indexes[q]) )
                    continue;

                indicesReady = false;
                if(s_gl.externalGeometry == 0) {

                    // set buffers
                    if (s_gl.buffers[q6]) {
                        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[q6]);
                        indicesReady = true;
                    }

                    this.setVertexAttribPointerPosition(gl, shape, q6, q);

                    if (sp.id !== undefined && s_gl.buffers[q6 + 5]) {

                        gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 5]);
                        //texture coordinate hack for IDs
                        if ((s_gl.binaryGeometry != 0 || s_gl.externalGeometry != 0) && s_geo._vf["idsPerVertex"] == true) {
                            gl.vertexAttribPointer(sp.id,
                                1, gl.FLOAT, false,
                                4, 0);
                            gl.enableVertexAttribArray(sp.id);
                        }
                        else {
                            /*
                             gl.vertexAttribPointer(sp.id,
                             1, gl.FLOAT, false,
                             shape._idStrideOffset[0], shape._idStrideOffset[1]);
                             gl.enableVertexAttribArray(sp.id);
                             */
                        }
                    }

                    // render mesh
                    if (indicesReady && (s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0)) {
                        for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                            gl.drawElements(s_gl.primType[v], s_geo._vf.vertexCount[v], s_gl.indexType,
                                x3dom.Utils.getByteAwareOffset(offset, s_gl.indexType, gl));
                            offset += s_geo._vf.vertexCount[v];
                        }
                    }
                    else if (s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 || s_gl.imageGeometry) {
                        for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                            gl.drawArrays(s_gl.primType[v], offset, s_geo._vf.vertexCount[v]);
                            offset += s_geo._vf.vertexCount[v];
                        }
                    }
                    else if (s_geo.hasIndexOffset()) {
                        var indOff = shape.tessellationProperties();
                        for (v = 0, v_n = indOff.length; v < v_n; v++) {
                            gl.drawElements(s_gl.primType, indOff[v].count, s_gl.indexType,
                                indOff[v].offset * x3dom.Utils.getOffsetMultiplier(s_gl.indexType, gl));
                        }
                    }
                    else if (s_gl.indexes[q].length == 0) {
                        gl.drawArrays(s_gl.primType, 0, s_gl.positions[q].length / 3);
                    }
                    else {
                        gl.drawElements(s_gl.primType, s_gl.indexes[q].length, s_gl.indexType, 0);
                    }
                }

                gl.disableVertexAttribArray(sp.position);

                if (sp.texcoord !== undefined && s_gl.buffers[q6 + 3]) {
                    gl.disableVertexAttribArray(sp.texcoord);
                }
                if (sp.color !== undefined && s_gl.buffers[q6 + 4]) {
                    gl.disableVertexAttribArray(sp.color);
                }
                if (sp.id !== undefined && s_gl.buffers[q6 + 5]) {
                    gl.disableVertexAttribArray(sp.id);
                }
            }

            //Clean Texture units for IG
            if (s_gl.imageGeometry != 0 && !x3dom.caps.MOBILE) {
                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, null);
                if (s_gl.imageGeometry == 1) {
                    gl.activeTexture(gl.TEXTURE1);
                    gl.bindTexture(gl.TEXTURE_2D, null);
                }
            }

        }

        if (x3dom.Utils.needLineWidth) {
            this.stateManager.lineWidth(1);
        }

        if (depthMode) {
            this.stateManager.enable(gl.DEPTH_TEST);
            this.stateManager.depthMask(true);
            this.stateManager.depthFunc(gl.LEQUAL);
            this.stateManager.depthRange(0, 1);
        }

        gl.flush();

        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, null);
    };

    /*****************************************************************************
     * Render Picking-Pass
     *****************************************************************************/
    Context.prototype.renderPickingPass = function (gl, scene, mat_view, mat_scene, from, sceneSize,
                                                    pickMode, lastX, lastY, width, height)
    {
        var ps = scene._webgl.pickScale;
        var bufHeight = scene._webgl.fboPick.height;
        var x = lastX * ps;
        var y = (bufHeight - 1) - lastY * ps;

        var indicesReady = false;

        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, scene._webgl.fboPick.fbo);
        this.stateManager.viewport(0, 0, scene._webgl.fboPick.width, bufHeight);

        //gl.scissor(x, y, width, height);
        //gl.enable(gl.SCISSOR_TEST);

        gl.clearColor(0.0, 0.0, 0.0, 0.0);
        gl.clearDepth(1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        var viewarea = scene.drawableCollection.viewarea;
        var env = scene.getEnvironment();
        var n = scene.drawableCollection.length;

        if (env._vf.smallFeatureCulling && env._lowPriorityThreshold < 1 && viewarea.isMovingOrAnimating()) {
            n = Math.floor(n * env._lowPriorityThreshold);
            if (!n && scene.drawableCollection.length)
                n = 1;   // render at least one object
        }

        var bgCenter = x3dom.fields.SFVec3f.NullVector.toGL();
        var bgSize = x3dom.fields.SFVec3f.OneVector.toGL();

        this.stateManager.depthFunc(gl.LEQUAL);
        this.stateManager.enable(gl.DEPTH_TEST);
        this.stateManager.enable(gl.CULL_FACE);
        this.stateManager.disable(gl.BLEND);

        if (x3dom.Utils.needLineWidth) {
            this.stateManager.lineWidth(2);     // bigger lines for better picking
        }

        for (var i = 0; i < n; i++)
        {
            var drawable = scene.drawableCollection.get(i);
            var trafo = drawable.transform;
            var shape = drawable.shape;
            var s_gl = shape._webgl;

            if (!s_gl || shape._objectID < 1 || !shape._vf.isPickable) {
                continue;
            }

            var s_geo = shape._cf.geometry.node;
            var s_app = shape._cf.appearance.node;
            var s_msh = s_geo._mesh;

            //Get shapes shader properties
            var properties = shape.getShaderProperties(viewarea);

            //Generate Dynamic picking shader
            var sp = this.cache.getShaderByProperties(gl, shape, properties, pickMode);

            if (!sp) {   // error
                return;
            }

            //Bind shader
            this.stateManager.useProgram(sp);

            sp.modelMatrix = trafo.toGL();
            sp.modelViewProjectionMatrix = mat_scene.mult(trafo).toGL();

            sp.lowBit  = (shape._objectID & 255) / 255.0;
            sp.highBit = (shape._objectID >>> 8) / 255.0;

            sp.from = from.toGL();
            sp.sceneSize = sceneSize;

            // Set shadow ids if available
            if((s_gl.binaryGeometry != 0 || s_gl.externalGeometry != 0) && s_geo._vf["idsPerVertex"] == true) {
                sp.shadowIDs = (shape._vf.idOffset + x3dom.nodeTypes.Shape.objectID + 2);
            }

            // BoundingBox stuff
            if (s_gl.coordType != gl.FLOAT) {
                if (!s_gl.popGeometry && (x3dom.Utils.isUnsignedType(s_geo._vf.coordType))) {
                    sp.bgCenter = s_geo.getMin().toGL();
                }
                else {
                    sp.bgCenter = s_geo._vf.position.toGL();
                }
                sp.bgSize = s_geo._vf.size.toGL();
                sp.bgPrecisionMax = s_geo.getPrecisionMax('coordType');
            }

            if (pickMode == 1 && s_gl.colorType != gl.FLOAT) {
                sp.bgPrecisionColMax = s_geo.getPrecisionMax('colorType');
            }

            if (pickMode == 2 && s_gl.texCoordType != gl.FLOAT) {
                sp.bgPrecisionTexMax = s_geo.getPrecisionMax('texCoordType');
            }

            //===========================================================================
            // Set ClipPlanes
            //===========================================================================
            if (shape._clipPlanes) {
                sp.modelViewMatrix = mat_view.mult(trafo).toGL();
                sp.viewMatrixInverse = mat_view.inverse().toGL();
                for (var cp = 0; cp < shape._clipPlanes.length; cp++) {
                    var clip_plane = shape._clipPlanes[cp].plane;
                    var clip_trafo = shape._clipPlanes[cp].trafo;

                    sp['clipPlane' + cp + '_Plane'] = clip_trafo.multMatrixPlane(clip_plane._vf.plane).toGL();
                    sp['clipPlane' + cp + '_CappingStrength'] = clip_plane._vf.cappingStrength;
                    sp['clipPlane' + cp + '_CappingColor'] = clip_plane._vf.cappingColor.toGL();
                }
            }

            //ImageGeometry stuff
            if (s_gl.imageGeometry != 0 && !x3dom.caps.MOBILE)  // FIXME: mobile errors
            {
                sp.IG_bboxMin = s_geo.getMin().toGL();
                sp.IG_bboxMax = s_geo.getMax().toGL();
                sp.IG_implicitMeshSize = s_geo._vf.implicitMeshSize.toGL();  // FIXME

                var coordTex = x3dom.Utils.findTextureByName(s_gl.texture, "IG_coords0");
                if (coordTex) {
                    sp.IG_coordTextureWidth = coordTex.texture.width;
                    sp.IG_coordTextureHeight = coordTex.texture.height;
                }

                if (s_gl.imageGeometry == 1) {
                    var indexTex = x3dom.Utils.findTextureByName(s_gl.texture, "IG_index");
                    if (indexTex) {
                        sp.IG_indexTextureWidth = indexTex.texture.width;
                        sp.IG_indexTextureHeight = indexTex.texture.height;
                    }

                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_2D, indexTex.texture);

                    gl.activeTexture(gl.TEXTURE1);
                    gl.bindTexture(gl.TEXTURE_2D, coordTex.texture);
                }
                else {
                    gl.activeTexture(gl.TEXTURE0);
                    gl.bindTexture(gl.TEXTURE_2D, coordTex.texture);
                }

                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_MAG_FILTER, gl.NEAREST);
                gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);

                var texUnit = 0;
                if (s_geo.getIndexTexture()) {
                    if (!sp.IG_indexTexture) {
                        sp.IG_indexTexture = texUnit++;
                    }
                }
                if (s_geo.getCoordinateTexture(0)) {
                    if (!sp.IG_coordinateTexture) {
                        sp.IG_coordinateTexture = texUnit++;
                    }
                }
            }
            else if ((s_gl.binaryGeometry != 0 || s_gl.externalGeometry != 0) && s_geo._vf["idsPerVertex"] == true) { //MultiPart
                var shader = s_app._shader;
                if(shader && x3dom.isa(s_app._shader, x3dom.nodeTypes.CommonSurfaceShader)) {
                    if (shader.getMultiVisibilityMap()) {
                        sp.multiVisibilityMap = 0;
                        var visTex = x3dom.Utils.findTextureByName(s_gl.texture, "multiVisibilityMap");
                        sp.multiVisibilityWidth = visTex.texture.width;
                        sp.multiVisibilityHeight = visTex.texture.height;
                        gl.activeTexture(gl.TEXTURE0);
                        gl.bindTexture(gl.TEXTURE_2D, visTex.texture);
                        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_MAG_FILTER, gl.NEAREST);
                        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
                    }
                }
            }

            if (shape.isSolid()) {
                this.stateManager.enable(gl.CULL_FACE);

                if (shape.isCCW()) {
                    this.stateManager.frontFace(gl.CCW);
                }
                else {
                    this.stateManager.frontFace(gl.CW);
                }
            }
            else {
                this.stateManager.disable(gl.CULL_FACE);
            }


            //===========================================================================
            // Set DepthMode
            //===========================================================================
            var depthMode = s_app ? s_app._cf.depthMode.node : null;
            if (depthMode)
            {
                if (depthMode._vf.enableDepthTest)
                {
                    //Enable Depth Test
                    this.stateManager.enable(gl.DEPTH_TEST);

                    //Set Depth Mask
                    this.stateManager.depthMask(!depthMode._vf.readOnly);

                    //Set Depth Function
                    this.stateManager.depthFunc(x3dom.Utils.depthFunc(gl, depthMode._vf.depthFunc));

                    //Set Depth Range
                    this.stateManager.depthRange(depthMode._vf.zNearRange, depthMode._vf.zFarRange);
                }
                else
                {
                    //Disable Depth Test
                    this.stateManager.disable(gl.DEPTH_TEST);
                }
            }
            else //Set Defaults
            {
                this.stateManager.enable(gl.DEPTH_TEST);
                this.stateManager.depthMask(true);
                this.stateManager.depthFunc(gl.LEQUAL);
            }

            //PopGeometry: adapt LOD and set shader variables
            if (s_gl.popGeometry) {
                var model_view = mat_view.mult(trafo);
                // FIXME; viewarea's width/height twice as big as render buffer size, which leads to too high precision
                // the correct viewarea here would be one that holds this half-sized render buffer
                this.updatePopState(drawable, s_geo, sp, s_gl, scene, model_view, viewarea, this.x3dElem.runtime.fps);
            }

            var q_n;
            if (s_gl.externalGeometry != 0)
            {
                q_n = shape.meshes.length;
            }
            else
            {
                q_n = s_gl.positions.length;
            }
            for (var q = 0; q < q_n; q++) {
                var q6 = 6 * q;
                var v, v_n, offset;

                if (s_gl.externalGeometry != 0) {
                    var mesh = shape.meshes[q];

                    mesh.bindVertexAttribPointerPosition(gl, sp, false);
                    mesh.render(gl, null);
                }
                else
                    if ( !(sp.position !== undefined && s_gl.buffers[q6 + 1] && s_gl.indexes[q]) )
                        continue;

                indicesReady = false;
                if(s_gl.externalGeometry == 0) {

                    // set buffers
                    if (s_gl.buffers[q6]) {
                        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[q6]);
                        indicesReady = true;
                    }

                    this.setVertexAttribPointerPosition(gl, shape, q6, q);

                    if (pickMode == 1) {
                        this.setVertexAttribPointerColor(gl, shape, q6, q);
                    }

                    if (pickMode == 2 && sp.texcoord !== undefined && s_gl.buffers[q6 + 3]) {
                        this.setVertexAttribPointerTexCoord(gl, shape, q6, q);
                    }

                    if (sp.id !== undefined && s_gl.buffers[q6 + 5]) {

                        gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 5]);
                        //texture coordinate hack for IDs
                        if ((s_gl.binaryGeometry != 0 || s_gl.externalGeometry != 0) && s_geo._vf["idsPerVertex"] == true) {
                            gl.vertexAttribPointer(sp.id,
                                1, gl.FLOAT, false,
                                4, 0);
                            gl.enableVertexAttribArray(sp.id);
                        }
                        else {
                            /*
                             gl.vertexAttribPointer(sp.id,
                             1, gl.FLOAT, false,
                             shape._idStrideOffset[0], shape._idStrideOffset[1]);
                             gl.enableVertexAttribArray(sp.id);
                             */
                        }
                    }

                    // render mesh
                    if (indicesReady && (s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0)) {
                        for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                            gl.drawElements(s_gl.primType[v], s_geo._vf.vertexCount[v], s_gl.indexType,
                                x3dom.Utils.getByteAwareOffset(offset, s_gl.indexType, gl));
                            offset += s_geo._vf.vertexCount[v];
                        }
                    }
                    else if (s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 || s_gl.imageGeometry) {
                        for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                            gl.drawArrays(s_gl.primType[v], offset, s_geo._vf.vertexCount[v]);
                            offset += s_geo._vf.vertexCount[v];
                        }
                    }
                    else if (s_geo.hasIndexOffset()) {
                        var indOff = shape.tessellationProperties();
                        for (v = 0, v_n = indOff.length; v < v_n; v++) {
                            gl.drawElements(s_gl.primType, indOff[v].count, s_gl.indexType,
                                indOff[v].offset * x3dom.Utils.getOffsetMultiplier(s_gl.indexType, gl));
                        }
                    }
                    else if (s_gl.indexes[q].length == 0) {
                        gl.drawArrays(s_gl.primType, 0, s_gl.positions[q].length / 3);
                    }
                    else {
                        gl.drawElements(s_gl.primType, s_gl.indexes[q].length, s_gl.indexType, 0);
                    }

                }

                gl.disableVertexAttribArray(sp.position);

                if (sp.texcoord !== undefined && s_gl.buffers[q6 + 3]) {
                    gl.disableVertexAttribArray(sp.texcoord);
                }
                if (sp.color !== undefined && s_gl.buffers[q6 + 4]) {
                    gl.disableVertexAttribArray(sp.color);
                }
                if (sp.id !== undefined && s_gl.buffers[q6 + 5]) {
                    gl.disableVertexAttribArray(sp.id);
                }
            }

            //Clean Texture units for IG
            if (s_gl.imageGeometry != 0 && !x3dom.caps.MOBILE) {
                gl.activeTexture(gl.TEXTURE0);
                gl.bindTexture(gl.TEXTURE_2D, null);
                if (s_gl.imageGeometry == 1) {
                    gl.activeTexture(gl.TEXTURE1);
                    gl.bindTexture(gl.TEXTURE_2D, null);
                }
            }
        }

        if (x3dom.Utils.needLineWidth) {
            this.stateManager.lineWidth(1);
        }
        
        if (depthMode) {
            this.stateManager.enable(gl.DEPTH_TEST);
            this.stateManager.depthMask(true);
            this.stateManager.depthFunc(gl.LEQUAL);
            this.stateManager.depthRange(0, 1);
        }

        gl.flush();

        try {
            // 4 = 1 * 1 * 4; then take width x height window (exception pickRect)
            var data = new Uint8Array(4 * width * height);

            gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, data);

            scene._webgl.fboPick.pixelData = data;
        }
        catch (se) {
            scene._webgl.fboPick.pixelData = [];
            // No Exception on file:// when starting with additional flags:
            //    chrome.exe --disable-web-security
            x3dom.debug.logException(se + " (cannot pick)");
        }

        //gl.disable(gl.SCISSOR_TEST);

        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, null);
    };

    /*****************************************************************************
     * Render single Shape
     *****************************************************************************/
    Context.prototype.renderShape = function (drawable, viewarea, slights, numLights, mat_view, mat_scene,
                                              mat_light, mat_proj, gl)
    {
		// Variable to indicate that the indices are successful bind
		var indicesReady = false;
		
        var shape = drawable.shape;
        var transform = drawable.transform;

        if (!shape || !shape._webgl || !transform) {
            x3dom.debug.logError("[Context|RenderShape] No valid Shape!");
            return;
        }

        var s_gl = shape._webgl;
        var sp = s_gl.shader;

        if (!sp) {
            x3dom.debug.logError("[Context|RenderShape] No Shader is set!");
            return;
        }

        var changed = this.stateManager.useProgram(sp);

        //===========================================================================
        // Set special Geometry variables
        //===========================================================================
        var s_app = shape._cf.appearance.node;
        var s_geo = shape._cf.geometry.node;
        var s_msh = s_geo._mesh;

        var scene = viewarea._scene;
        var tex = null;

        if (s_gl.coordType != gl.FLOAT) {
            if (!s_gl.popGeometry && (x3dom.Utils.isUnsignedType(s_geo._vf.coordType))) {
                sp.bgCenter = s_geo.getMin().toGL();
            }
            else {
                sp.bgCenter = s_geo._vf.position.toGL();
            }
            sp.bgSize = s_geo._vf.size.toGL();
            sp.bgPrecisionMax = s_geo.getPrecisionMax('coordType');
        }
        else {
            sp.bgCenter = [0, 0, 0];
            sp.bgSize = [1, 1, 1];
            sp.bgPrecisionMax = 1;
        }
        if (s_gl.colorType != gl.FLOAT) {
            sp.bgPrecisionColMax = s_geo.getPrecisionMax('colorType');
        }
        else {
            sp.bgPrecisionColMax = 1;
        }
        if (s_gl.texCoordType != gl.FLOAT) {
            sp.bgPrecisionTexMax = s_geo.getPrecisionMax('texCoordType');
        }
        else {
            sp.bgPrecisionTexMax = 1;
        }
        if (s_gl.normalType != gl.FLOAT) {
            sp.bgPrecisionNorMax = s_geo.getPrecisionMax('normalType');
        }
        else {
            sp.bgPrecisionNorMax = 1;
        }

        if (s_gl.imageGeometry != 0) {
            sp.IG_bboxMin = s_geo.getMin().toGL();
            sp.IG_bboxMax = s_geo.getMax().toGL();
            sp.IG_implicitMeshSize = s_geo._vf.implicitMeshSize.toGL();  // FIXME

            tex = x3dom.Utils.findTextureByName(s_gl.texture, "IG_coords0");
            if (tex) {
                sp.IG_coordTextureWidth = tex.texture.width;
                sp.IG_coordTextureHeight = tex.texture.height;
            }

            if (s_gl.imageGeometry == 1) {
                tex = x3dom.Utils.findTextureByName(s_gl.texture, "IG_index");
                if (tex) {
                    sp.IG_indexTextureWidth = tex.texture.width;
                    sp.IG_indexTextureHeight = tex.texture.height;
                }
            }
            tex = null;
        }

        //===========================================================================
        // Set fog
        //===========================================================================
        // TODO: when no state/shader switch happens, all light/fog/... uniforms don't need to be set again
        var fog = scene.getFog();

        // THINKABOUTME: changed flag only works as long as lights and fog are global
        if (fog && changed) {
            sp.fogColor = fog._vf.color.toGL();
            sp.fogRange = fog._vf.visibilityRange;
            sp.fogType = (fog._vf.fogType == "LINEAR") ? 0.0 : 1.0;
        }

        //===========================================================================
        // Set Material
        //===========================================================================
        var mat = s_app ? s_app._cf.material.node : null;
        var shader = s_app ? s_app._shader : null;
        var twoSidedMat = false;

        var isUserDefinedShader = shader && x3dom.isa(shader, x3dom.nodeTypes.ComposedShader);

        if (s_gl.csshader) {
            sp.diffuseColor = shader._vf.diffuseFactor.toGL();
            sp.specularColor = shader._vf.specularFactor.toGL();
            sp.emissiveColor = shader._vf.emissiveFactor.toGL();
            sp.shininess = shader._vf.shininessFactor;
            sp.ambientIntensity = (shader._vf.ambientFactor.x +
                                   shader._vf.ambientFactor.y +
                                   shader._vf.ambientFactor.z) / 3;
            sp.transparency = 1.0 - shader._vf.alphaFactor;
            sp.environmentFactor = shader._vf.environmentFactor.x;

            if (shader.getDisplacementMap()) {
              tex = x3dom.Utils.findTextureByName(s_gl.texture, "displacementMap");
              sp.displacementWidth = tex.texture.width;
              sp.displacementHeight = tex.texture.height;
              sp.displacementFactor = shader._vf.displacementFactor;
              sp.displacementAxis = (shader._vf.displacementAxis == "x") ? 0.0 :
                                    (shader._vf.displacementAxis == "y") ? 1.0 : 2.0;
            }
            else if (shader.getDiffuseDisplacementMap()) {
                tex = x3dom.Utils.findTextureByName(s_gl.texture, "diffuseDisplacementMap");
                sp.displacementWidth = tex.texture.width;
                sp.displacementHeight = tex.texture.height;
                sp.displacementFactor = shader._vf.displacementFactor;
                sp.displacementAxis = (shader._vf.displacementAxis == "x") ? 0.0 :
                                      (shader._vf.displacementAxis == "y") ? 1.0 : 2.0;
            }
            if (shader.getMultiDiffuseAlphaMap()) {
                tex = x3dom.Utils.findTextureByName(s_gl.texture, "multiDiffuseAlphaMap");
                sp.multiDiffuseAlphaWidth = tex.texture.width;
                sp.multiDiffuseAlphaHeight = tex.texture.height;
            }
            if (shader.getMultiEmissiveAmbientMap()) {
                tex = x3dom.Utils.findTextureByName(s_gl.texture, "multiEmissiveAmbientMap");
                sp.multiEmissiveAmbientWidth = tex.texture.width;
                sp.multiEmissiveAmbientHeight = tex.texture.height;
            }
            if (shader.getMultiSpecularShininessMap()) {
                tex = x3dom.Utils.findTextureByName(s_gl.texture, "multiSpecularShininessMap");
                sp.multiSpecularShininessWidth = tex.texture.width;
                sp.multiSpecularShininessHeight = tex.texture.height;
            }
            if (shader.getMultiVisibilityMap()) {
                tex = x3dom.Utils.findTextureByName(s_gl.texture, "multiVisibilityMap");
                sp.multiVisibilityWidth = tex.texture.width;
                sp.multiVisibilityHeight = tex.texture.height;
            }
        }
        else if (mat) {
            sp.diffuseColor = mat._vf.diffuseColor.toGL();
            sp.specularColor = mat._vf.specularColor.toGL();
            sp.emissiveColor = mat._vf.emissiveColor.toGL();
            sp.shininess = mat._vf.shininess;
            sp.ambientIntensity = mat._vf.ambientIntensity;
            sp.transparency = mat._vf.transparency;
            sp.environmentFactor = 0.0;
            if (x3dom.isa(mat, x3dom.nodeTypes.TwoSidedMaterial)) {
                twoSidedMat = true;
                sp.backDiffuseColor = mat._vf.backDiffuseColor.toGL();
                sp.backSpecularColor = mat._vf.backSpecularColor.toGL();
                sp.backEmissiveColor = mat._vf.backEmissiveColor.toGL();
                sp.backShininess = mat._vf.backShininess;
                sp.backAmbientIntensity = mat._vf.backAmbientIntensity;
                sp.backTransparency = mat._vf.backTransparency;
            }
        }
        else {
            sp.diffuseColor = [1.0, 1.0, 1.0];
            sp.specularColor = [0.0, 0.0, 0.0];
            sp.emissiveColor = [0.0, 0.0, 0.0];
            sp.shininess = 0.0;
            sp.ambientIntensity = 1.0;
            sp.transparency = 0.0;
        }

        //Look for user-defined shaders
        if (shader) {
            if (isUserDefinedShader) {
                for (var fName in shader._vf) {
                    if (shader._vf.hasOwnProperty(fName) && fName !== 'language') {
                        var field = shader._vf[fName];
                        if (field !== undefined && field !== null) {
                            if (field.toGL) {
                                sp[fName] = field.toGL();
                            }
                            else {
                                sp[fName] = field;
                            }
                        }
                    }
                }
            }
            else if (x3dom.isa(shader, x3dom.nodeTypes.CommonSurfaceShader)) {
                s_gl.csshader = shader;
            }
        }

        //===========================================================================
        // Set Lights
        //===========================================================================
        for (var p = 0; p < numLights && changed; p++) {
            // FIXME; getCurrentTransform() doesn't work for shared lights/objects!
            var light_transform = mat_view.mult(slights[p].getCurrentTransform());

            if (x3dom.isa(slights[p], x3dom.nodeTypes.DirectionalLight)) {
                sp['light' + p + '_Type'] = 0.0;
                sp['light' + p + '_On'] = (slights[p]._vf.on) ? 1.0 : 0.0;
                sp['light' + p + '_Color'] = slights[p]._vf.color.toGL();
                sp['light' + p + '_Intensity'] = slights[p]._vf.intensity;
                sp['light' + p + '_AmbientIntensity'] = slights[p]._vf.ambientIntensity;
                sp['light' + p + '_Direction'] = light_transform.multMatrixVec(slights[p]._vf.direction).toGL();
                sp['light' + p + '_Attenuation'] = [1.0, 1.0, 1.0];
                sp['light' + p + '_Location'] = [1.0, 1.0, 1.0];
                sp['light' + p + '_Radius'] = 0.0;
                sp['light' + p + '_BeamWidth'] = 0.0;
                sp['light' + p + '_CutOffAngle'] = 0.0;
                sp['light' + p + '_ShadowIntensity'] = slights[p]._vf.shadowIntensity;
            }
            else if (x3dom.isa(slights[p], x3dom.nodeTypes.PointLight)) {
                sp['light' + p + '_Type'] = 1.0;
                sp['light' + p + '_On'] = (slights[p]._vf.on) ? 1.0 : 0.0;
                sp['light' + p + '_Color'] = slights[p]._vf.color.toGL();
                sp['light' + p + '_Intensity'] = slights[p]._vf.intensity;
                sp['light' + p + '_AmbientIntensity'] = slights[p]._vf.ambientIntensity;
                sp['light' + p + '_Direction'] = [1.0, 1.0, 1.0];
                sp['light' + p + '_Attenuation'] = slights[p]._vf.attenuation.toGL();
                sp['light' + p + '_Location'] = light_transform.multMatrixPnt(slights[p]._vf.location).toGL();
                sp['light' + p + '_Radius'] = slights[p]._vf.radius;
                sp['light' + p + '_BeamWidth'] = 0.0;
                sp['light' + p + '_CutOffAngle'] = 0.0;
                sp['light' + p + '_ShadowIntensity'] = slights[p]._vf.shadowIntensity;
            }
            else if (x3dom.isa(slights[p], x3dom.nodeTypes.SpotLight)) {
                sp['light' + p + '_Type'] = 2.0;
                sp['light' + p + '_On'] = (slights[p]._vf.on) ? 1.0 : 0.0;
                sp['light' + p + '_Color'] = slights[p]._vf.color.toGL();
                sp['light' + p + '_Intensity'] = slights[p]._vf.intensity;
                sp['light' + p + '_AmbientIntensity'] = slights[p]._vf.ambientIntensity;
                sp['light' + p + '_Direction'] = light_transform.multMatrixVec(slights[p]._vf.direction).toGL();
                sp['light' + p + '_Attenuation'] = slights[p]._vf.attenuation.toGL();
                sp['light' + p + '_Location'] = light_transform.multMatrixPnt(slights[p]._vf.location).toGL();
                sp['light' + p + '_Radius'] = slights[p]._vf.radius;
                sp['light' + p + '_BeamWidth'] = slights[p]._vf.beamWidth;
                sp['light' + p + '_CutOffAngle'] = slights[p]._vf.cutOffAngle;
                sp['light' + p + '_ShadowIntensity'] = slights[p]._vf.shadowIntensity;
            }
        }

        //===========================================================================
        // Set HeadLight
        //===========================================================================
        var nav = scene.getNavigationInfo();

        if (nav._vf.headlight && changed) {
            numLights = (numLights) ? numLights : 0;
            sp['light' + numLights + '_Type'] = 0.0;
            sp['light' + numLights + '_On'] = 1.0;
            sp['light' + numLights + '_Color'] = [1.0, 1.0, 1.0];
            sp['light' + numLights + '_Intensity'] = 1.0;
            sp['light' + numLights + '_AmbientIntensity'] = 0.0;
            sp['light' + numLights + '_Direction'] = [0.0, 0.0, -1.0];
            sp['light' + numLights + '_Attenuation'] = [1.0, 1.0, 1.0];
            sp['light' + numLights + '_Location'] = [1.0, 1.0, 1.0];
            sp['light' + numLights + '_Radius'] = 0.0;
            sp['light' + numLights + '_BeamWidth'] = 0.0;
            sp['light' + numLights + '_CutOffAngle'] = 0.0;
            sp['light' + numLights + '_ShadowIntensity'] = 0.0;
        }

        //===========================================================================
        // Set ClipPlanes
        //===========================================================================
        if (shape._clipPlanes) {
            for (var cp = 0; cp < shape._clipPlanes.length; cp++) {
                var clip_plane = shape._clipPlanes[cp].plane;
                var clip_trafo = shape._clipPlanes[cp].trafo;

                sp['clipPlane' + cp + '_Plane'] = clip_trafo.multMatrixPlane(clip_plane._vf.plane).toGL();
                sp['clipPlane' + cp + '_CappingStrength'] = clip_plane._vf.cappingStrength;
                sp['clipPlane' + cp + '_CappingColor'] = clip_plane._vf.cappingColor.toGL();
            }
        }


        //===========================================================================
        // Set DepthMode
        //===========================================================================
        var depthMode = s_app ? s_app._cf.depthMode.node : null;
        if (depthMode)
        {
            if (depthMode._vf.enableDepthTest)
            {
                //Enable Depth Test
                this.stateManager.enable(gl.DEPTH_TEST);

                //Set Depth Mask
                this.stateManager.depthMask(!depthMode._vf.readOnly);
                
                //Set Depth Function
                this.stateManager.depthFunc(x3dom.Utils.depthFunc(gl, depthMode._vf.depthFunc));

                //Set Depth Range
                this.stateManager.depthRange(depthMode._vf.zNearRange, depthMode._vf.zFarRange);
            }
            else
            {
                //Disable Depth Test
                this.stateManager.disable(gl.DEPTH_TEST);
            }
        } 
        else //Set Defaults
        {
            this.stateManager.enable(gl.DEPTH_TEST);
            this.stateManager.depthMask(true);
            this.stateManager.depthFunc(gl.LEQUAL);
        }

        //===========================================================================
        // Set BlendMode
        //===========================================================================
        var blendMode = s_app ? s_app._cf.blendMode.node : null;
        if (blendMode)
        {
            var srcFactor  = x3dom.Utils.blendFunc(gl, blendMode._vf.srcFactor);
            var destFactor = x3dom.Utils.blendFunc(gl, blendMode._vf.destFactor);

            if (srcFactor && destFactor)
            {
                //Enable Blending
                this.stateManager.enable(gl.BLEND);

                //Set Blend Function
                this.stateManager.blendFuncSeparate(srcFactor, destFactor, gl.ONE, gl.ONE);

                //Set Blend Color
                this.stateManager.blendColor(blendMode._vf.color.r,
                                             blendMode._vf.color.g,
                                             blendMode._vf.color.b,
                                             1.0 - blendMode._vf.colorTransparency);

                //Set Blend Equation
                this.stateManager.blendEquation(x3dom.Utils.blendEquation(gl, blendMode._vf.equation));
            }
            else
            {
                this.stateManager.disable(gl.BLEND);
            }
        }
        else //Set Defaults
        {
            this.stateManager.enable(gl.BLEND);
            this.stateManager.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
        }

        //===========================================================================
        // Set ColorMaskMode
        //===========================================================================
        var colorMaskMode = s_app ? s_app._cf.colorMaskMode.node : null;
        if (colorMaskMode)
        {
            this.stateManager.colorMask(colorMaskMode._vf.maskR,
                                        colorMaskMode._vf.maskG,
                                        colorMaskMode._vf.maskB,
                                        colorMaskMode._vf.maskA);
        }
        else //Set Defaults
        {
            this.stateManager.colorMask(true, true, true, true);
        }

        //===========================================================================
        // Set LineProperties (only linewidthScaleFactor, interpreted as lineWidth)
        //===========================================================================
        var lineProperties = s_app ? s_app._cf.lineProperties.node : null;
        if (lineProperties)
        {
            this.stateManager.lineWidth(lineProperties._vf.linewidthScaleFactor);
        }
        else if (x3dom.Utils.needLineWidth) //Set Defaults
        {
            this.stateManager.lineWidth(1);
        }

        if (shape.isSolid() && !twoSidedMat) {
            this.stateManager.enable(gl.CULL_FACE);

            if (shape.isCCW()) {
                this.stateManager.frontFace(gl.CCW);
            }
            else {
                this.stateManager.frontFace(gl.CW);
            }
        }
        else {
            this.stateManager.disable(gl.CULL_FACE);
        }

        // transformation matrices
        var model_view = mat_view.mult(transform);
        var model_view_inv = model_view.inverse();

        sp.isOrthoView = ( mat_proj._33 == 1 ) ? 1.0 : 0.0;

        sp.modelViewMatrix = model_view.toGL();
        sp.viewMatrix = mat_view.toGL();

        sp.normalMatrix = model_view_inv.transpose().toGL();
        sp.modelViewMatrixInverse = model_view_inv.toGL();

        sp.modelViewProjectionMatrix = mat_scene.mult(transform).toGL();

        if (isUserDefinedShader || shape._clipPlanes && shape._clipPlanes.length)
        {
            sp.viewMatrixInverse = mat_view.inverse().toGL();
        }

        // only calculate on "request" (maybe of interest for users)
        // may be used by external materials
        if (isUserDefinedShader || s_gl.externalGeometry != 0) {
            sp.model = transform.toGL();
            sp.projectionMatrix = mat_proj.toGL();

            sp.worldMatrix = transform.toGL();
            sp.worldInverseTranspose = transform.inverse().transpose().toGL();

        }

        //PopGeometry: adapt LOD and set shader variables
        if (s_gl.popGeometry) {
            this.updatePopState(drawable, s_geo, sp, s_gl, scene, model_view, viewarea, this.x3dElem.runtime.fps);
        }

        for (var cnt = 0, cnt_n = s_gl.texture.length; cnt < cnt_n; cnt++) {
            tex = s_gl.texture[cnt];

            gl.activeTexture(gl.TEXTURE0 + cnt);
            gl.bindTexture(tex.type, tex.texture);
            gl.texParameteri(tex.type, gl.TEXTURE_WRAP_S, tex.wrapS);
            gl.texParameteri(tex.type, gl.TEXTURE_WRAP_T, tex.wrapT);
            gl.texParameteri(tex.type, gl.TEXTURE_MAG_FILTER, tex.magFilter);
            gl.texParameteri(tex.type, gl.TEXTURE_MIN_FILTER, tex.minFilter);

            if (!shader || !isUserDefinedShader) {
                if (!sp[tex.samplerName])
                    sp[tex.samplerName] = cnt;
            }
        }

        if (s_app && s_app._cf.textureTransform.node) {
            var texTrafo = s_app.texTransformMatrix();
            sp.texTrafoMatrix = texTrafo.toGL();
        }


        // TODO; FIXME; what if geometry with split mesh has dynamic fields?
        var attrib = null;
        var df, df_n = s_gl.dynamicFields.length;

        for (df = 0; df < df_n; df++) {
            attrib = s_gl.dynamicFields[df];

            if (sp[attrib.name] !== undefined) {
                gl.bindBuffer(gl.ARRAY_BUFFER, attrib.buf);

                gl.vertexAttribPointer(sp[attrib.name], attrib.numComponents, gl.FLOAT, false, 0, 0);
                gl.enableVertexAttribArray(sp[attrib.name]);
            }
        }

        // render object
        var v, v_n, offset, q_n;
        var isParticleSet = false;

        if (x3dom.isa(s_geo, x3dom.nodeTypes.ParticleSet)) {
            isParticleSet = true;
        }

        if (s_gl.externalGeometry != 0) {
            q_n = shape.meshes.length;
        }
        else
        {
            q_n = s_gl.positions.length;
        }

        for (var q = 0; q < q_n; q++) {
            var q6 = 6 * q;

            if (s_gl.externalGeometry != 0) {
                var mesh = shape.meshes[q];
                var exGeomShaderProgram = sp;

                if(mesh.material!=null){
                    if(mesh.material.program!=null){
                        exGeomShaderProgram = mesh.material.program;
                    }

                    if(mesh.material.setShader != null)
                        mesh.material.setShader(gl,this.cache, shape, shape.getShaderProperties(viewarea));

                    mesh.material.bind(gl, sp, this.cache, shape.getShaderProperties(viewarea));
                }

                mesh.bindVertexAttribPointer(gl, exGeomShaderProgram);
                var renderMode = viewarea.getRenderMode();

                var polyMode = null;
                if (renderMode > 0)
                    polyMode = (renderMode == 1) ? gl.POINTS : gl.LINES;

                mesh.render(gl, polyMode);
            }
            else
                if ( !(sp.position !== undefined && s_gl.buffers[q6 + 1] && s_gl.indexes[q]) )
                    continue;

            indicesReady = false;
            if(s_gl.externalGeometry == 0){

                if (!(sp.position !== undefined && s_gl.buffers[q6 + 1] && (s_gl.indexes[q])))
                    continue;

                if (s_gl.buffers[q6]) {
                    if (isParticleSet && s_geo.drawOrder() != "any") {  // sort
                        var indexArray, zPos = [];
                        var pnts = s_geo._cf.coord.node.getPoints();
                        var pn = (pnts.length == s_gl.indexes[q].length) ? s_gl.indexes[q].length : 0;

                        for (var i = 0; i < pn; i++) {
                            var center = model_view.multMatrixPnt(pnts[i]);
                            zPos.push([i, center.z]);
                        }

                        if (s_geo.drawOrder() == "backtofront")
                            zPos.sort(function (a, b) {
                                return a[1] - b[1];
                            });
                        else
                            zPos.sort(function (b, a) {
                                return a[1] - b[1];
                            });

                        for (i = 0; i < pn; i++) {
                            shape._webgl.indexes[q][i] = zPos[i][0];
                        }

                        if (x3dom.caps.INDEX_UINT && (pn > 65535)) {
                            indexArray = new Uint32Array(shape._webgl.indexes[q]);
                            shape._webgl.indexType = gl.UNSIGNED_INT;
                        }
                        else {
                            indexArray = new Uint16Array(shape._webgl.indexes[q]);
                            shape._webgl.indexType = gl.UNSIGNED_SHORT;
                        }

                        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[q6]);
                        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexArray, gl.DYNAMIC_DRAW);

                        indexArray = null;
                    }

                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[q6]);
                    indicesReady = true;
                }

                this.setVertexAttribPointerPosition(gl, shape, q6, q);
                this.setVertexAttribPointerNormal(gl, shape, q6, q);
                this.setVertexAttribPointerTexCoord(gl, shape, q6, q);
                this.setVertexAttribPointerColor(gl, shape, q6, q);

                if ((sp.id !== undefined || sp.particleSize !== undefined) && shape._webgl.buffers[q6 + 5]) {
                    gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[q6 + 5]);
                    //texture coordinate hack for IDs
                    if ((s_gl.binaryGeometry != 0 || s_gl.externalGeometry != 0) && s_geo._vf["idsPerVertex"] == true) {
                        gl.vertexAttribPointer(sp.id,
                            1, gl.FLOAT, false, 4, 0);
                        gl.enableVertexAttribArray(sp.id);
                    }
                    else if (isParticleSet) {
                        gl.vertexAttribPointer(sp.particleSize,
                            3, gl.FLOAT, false, 0, 0);
                        gl.enableVertexAttribArray(sp.particleSize);
                    }
                }
                if (s_gl.popGeometry != 0 && s_gl.buffers[q6 + 5]) {
                    //special case: mimic gl_VertexID
                    gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 5]);

                    gl.vertexAttribPointer(sp.PG_vertexID, 1, gl.FLOAT, false, 4, 0);
                    gl.enableVertexAttribArray(sp.PG_vertexID);
                }

                // TODO: implement surface with additional wireframe render mode (independent from poly mode)
                var indOff, renderMode = viewarea.getRenderMode();

                if (renderMode > 0) {
                    var polyMode = (renderMode == 1) ? gl.POINTS : gl.LINES;

                    if (indicesReady && (s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0)) {
                        for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                            gl.drawElements(polyMode, s_geo._vf.vertexCount[v], s_gl.indexType,
                                x3dom.Utils.getByteAwareOffset(offset, s_gl.indexType, gl));
                            offset += s_geo._vf.vertexCount[v];
                        }
                    }
                    else if (s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 || s_gl.imageGeometry) {
                        for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                            gl.drawArrays(polyMode, offset, s_geo._vf.vertexCount[v]);
                            offset += s_geo._vf.vertexCount[v];
                        }
                    }
                    else if (s_geo.hasIndexOffset()) {
                        // IndexedTriangleStripSet with primType TRIANGLE_STRIP,
                        // and Patch geometry from external BVHRefiner component
                        indOff = shape.tessellationProperties();
                        for (v = 0, v_n = indOff.length; v < v_n; v++) {
                            gl.drawElements(polyMode, indOff[v].count, s_gl.indexType,
                                indOff[v].offset * x3dom.Utils.getOffsetMultiplier(s_gl.indexType, gl));
                        }
                    }
                    else if (s_gl.indexes[q].length == 0) {
                        gl.drawArrays(polyMode, 0, s_gl.positions[q].length / 3);
                    }
                    else {
                        gl.drawElements(polyMode, s_gl.indexes[q].length, s_gl.indexType, 0);
                    }
                }
                else {
                    if (indicesReady && (s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0)) {
                        for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                            gl.drawElements(s_gl.primType[v], s_geo._vf.vertexCount[v], s_gl.indexType,
                                x3dom.Utils.getByteAwareOffset(offset, s_gl.indexType, gl));
                            offset += s_geo._vf.vertexCount[v];
                        }
                    }
                    else if (s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 || s_gl.imageGeometry) {
                        for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                            gl.drawArrays(s_gl.primType[v], offset, s_geo._vf.vertexCount[v]);
                            offset += s_geo._vf.vertexCount[v];
                        }
                    }
                    else if (s_geo.hasIndexOffset()) {
                        // IndexedTriangleStripSet with primType TRIANGLE_STRIP,
                        // and Patch geometry from external BVHRefiner component
                        indOff = shape.tessellationProperties();
                        for (v = 0, v_n = indOff.length; v < v_n; v++) {
                            gl.drawElements(s_gl.primType, indOff[v].count, s_gl.indexType,
                                indOff[v].offset * x3dom.Utils.getOffsetMultiplier(s_gl.indexType, gl));
                        }
                    }
                    else if (s_gl.indexes[q].length == 0) {
                        gl.drawArrays(s_gl.primType, 0, s_gl.positions[q].length / 3);
                    }
                    else {
                        gl.drawElements(s_gl.primType, s_gl.indexes[q].length, s_gl.indexType, 0);
                    }
                }
            }

            // disable all used vertex attributes
            gl.disableVertexAttribArray(sp.position);

            if (sp.normal !== undefined) {
                gl.disableVertexAttribArray(sp.normal);
            }
            if (sp.texcoord !== undefined) {
                gl.disableVertexAttribArray(sp.texcoord);
            }
            if (sp.color !== undefined) {
                gl.disableVertexAttribArray(sp.color);
            }
            if (s_gl.buffers[q6 + 5]) {
                if (sp.id !== undefined)
                    gl.disableVertexAttribArray(sp.id);
                else if (sp.particleSize !== undefined)
                    gl.disableVertexAttribArray(sp.particleSize);
            }
            if (s_gl.popGeometry != 0 && sp.PG_vertexID !== undefined) {
                gl.disableVertexAttribArray(sp.PG_vertexID);    // mimic gl_VertexID
            }
        } // end for loop over attrib arrays

        for (df = 0; df < df_n; df++) {
            attrib = s_gl.dynamicFields[df];

            if (sp[attrib.name] !== undefined) {
                gl.disableVertexAttribArray(sp[attrib.name]);
            }
        }

        // update stats
        if (s_gl.imageGeometry) {
            v_n = s_geo._vf.vertexCount.length;
            this.numDrawCalls += v_n;

            for (v = 0; v < v_n; v++) {
                if (s_gl.primType[v] == gl.TRIANGLE_STRIP)
                    this.numFaces += (s_geo._vf.vertexCount[v] - 2);
                else
                    this.numFaces += (s_geo._vf.vertexCount[v] / 3);

                this.numCoords += s_geo._vf.vertexCount[v];
            }
        }
        else {
            this.numCoords += s_msh._numCoords;
            this.numFaces  += s_msh._numFaces;

            if (s_gl.binaryGeometry || s_gl.popGeometry) {
                this.numDrawCalls += s_geo._vf.vertexCount.length;
            }
            else if (s_geo.hasIndexOffset()) {
                this.numDrawCalls += shape.tessellationProperties().length;
            }
            else {
                this.numDrawCalls += q_n;
            }
        }

        // reset to default values for possibly user defined render states
        if (depthMode) {
            this.stateManager.enable(gl.DEPTH_TEST);
            this.stateManager.depthMask(true);
            this.stateManager.depthFunc(gl.LEQUAL);
            this.stateManager.depthRange(0, 1);
        }

        if (blendMode) {
            this.stateManager.enable(gl.BLEND);
            this.stateManager.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
            this.stateManager.blendColor(1, 1, 1, 1);
            this.stateManager.blendEquation(gl.FUNC_ADD);
        }

        if (colorMaskMode) {
            this.stateManager.colorMask(true, true, true, true);
        }

        if (lineProperties) {
            this.stateManager.lineWidth(1);
        }

        // cleanup textures
        var s_gl_tex = s_gl.texture;
        cnt_n = s_gl_tex ? s_gl_tex.length : 0;

        for (cnt = 0; cnt < cnt_n; cnt++) {
            if (!s_gl_tex[cnt])
                continue;

            if (s_app && s_app._cf.texture.node) {
                tex = s_app._cf.texture.node.getTexture(cnt);
                gl.activeTexture(gl.TEXTURE0 + cnt);

                if (x3dom.isa(tex, x3dom.nodeTypes.X3DEnvironmentTextureNode)) {
                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
                }
                else {
                    gl.bindTexture(gl.TEXTURE_2D, null);
                }
            }
        }
    };

    /*****************************************************************************
     * PopGeometry: adapt LOD and set shader variables
     *****************************************************************************/
    Context.prototype.updatePopState = function (drawable, popGeo, sp, s_gl, scene, model_view, viewarea, currFps)
    {
        var tol = x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor * popGeo._vf.precisionFactor;

        if (currFps <= 1 || viewarea.isMovingOrAnimating()) {
            tol *= x3dom.nodeTypes.PopGeometry.PrecisionFactorOnMove;
        }

        var currentLOD = 16;

        if (tol > 0) {

            //BEGIN CLASSIC CODE
            var viewpoint = scene.getViewpoint();
            var imgPlaneHeightAtDistOne = viewpoint.getImgPlaneHeightAtDistOne();
            var near = viewpoint.getNear();
            var center = model_view.multMatrixPnt(popGeo._vf.position);

            var tightRad   = model_view.multMatrixVec(popGeo._vf.size).length()      * 0.5;
            var largestRad = model_view.multMatrixVec(popGeo._vf.maxBBSize).length() * 0.5;

            //distance is estimated conservatively using the bounding sphere
            var dist = Math.max(-center.z - tightRad, near);
            var projPixelLength = dist * (imgPlaneHeightAtDistOne / viewarea._height);

            //compute LOD using bounding sphere
            var arg = (2 * largestRad) / (tol * projPixelLength);
            //END CLASSIC CODE

            //BEGIN EXPERIMENTAL CODE
            //compute LOD using screen-space coverage of bounding sphere
            //@todo: the coverage should be distinct from priority
            //var cov = drawable.priority;
            //@todo: here, we need to decide whether we want to keep the ModF-encoding with
            //       respect to the largest bounding box... if not, change this and the shaders
            //cov *= (popGeo._vf.maxBBSize.length() / popGeo._vf.size.length());
            //var arg = cov / tol;
            //END EXPERIMENTAL CODE

            // use precomputed log(2.0) = 0.693147180559945
            currentLOD = Math.ceil(Math.log(arg) / 0.693147180559945);
            currentLOD = (currentLOD < 1) ? 1 : ((currentLOD > 16) ? 16 : currentLOD);
        }

        //take care of user-controlled min and max values
        var minPrec = popGeo._vf.minPrecisionLevel, maxPrec = popGeo._vf.maxPrecisionLevel;

        currentLOD = (minPrec != -1 && currentLOD < minPrec) ? minPrec : currentLOD;
        currentLOD = (maxPrec != -1 && currentLOD > maxPrec) ? maxPrec : currentLOD;

        //assign rendering resolution, according to currently loaded data and LOD
        var currentLOD_min = (s_gl.levelsAvailable < currentLOD) ? s_gl.levelsAvailable : currentLOD;
        currentLOD = currentLOD_min;

        //@todo: only for demonstration purposes!!!
        if (tol <= 1)
            currentLOD = (currentLOD == popGeo.getNumLevels()) ? 16 : currentLOD;

        //here, we tell X3DOM how many faces / vertices get displayed in the stats
        var hasIndex = popGeo._vf.indexedRendering;
        var p_msh = popGeo._mesh;

        p_msh._numCoords = 0;
        p_msh._numFaces = 0;

        //@todo: this assumes pure TRIANGLES data (and gets overwritten from shadow/picking pass!!!)
        for (var i = 0; i < currentLOD_min; ++i) {  // currentLOD breaks loop
            var numVerticesAtLevel_i = s_gl.numVerticesAtLevel[i];
            p_msh._numCoords += numVerticesAtLevel_i;
            p_msh._numFaces += (hasIndex ? popGeo.getNumIndicesByLevel(i) : numVerticesAtLevel_i) / 3;
        }

        x3dom.nodeTypes.PopGeometry.numRenderedVerts += p_msh._numCoords;
        x3dom.nodeTypes.PopGeometry.numRenderedTris += p_msh._numFaces;

        //this field is mainly thought for the use with external statistics
        //@todo: does not work with instances
        p_msh.currentLOD = currentLOD;

        //here, we tell X3DOM how many vertices get rendered
        //@todo: this assumes pure TRIANGLES data
        popGeo.adaptVertexCount(hasIndex ? p_msh._numFaces * 3 : p_msh._numCoords);

        // finally set shader variables...
        sp.PG_maxBBSize = popGeo._vf.maxBBSize.toGL();

        sp.PG_bbMin = popGeo._bbMinBySize;  // floor(bbMin / maxBBSize)

        sp.PG_numAnchorVertices = popGeo._vf.numAnchorVertices;

        sp.PG_bbMaxModF    = popGeo._vf.bbMaxModF.toGL();
        sp.PG_bboxShiftVec = popGeo._vf.bbShiftVec.toGL();

        sp.PG_precisionLevel = currentLOD;

        //mimics Math.pow(2.0, 16.0 - currentLOD);
        sp.PG_powPrecision = x3dom.nodeTypes.PopGeometry.powLUT[currentLOD - 1];
    };


    /*****************************************************************************
     * Render ColorBuffer-Pass for picking
     *****************************************************************************/
    Context.prototype.pickValue = function (viewarea, x, y, buttonState, viewMat, sceneMat)
    {
        x3dom.Utils.startMeasure("picking");
        
        var scene = viewarea._scene;
        
        var gl = this.ctx3d;
        
        // method requires that scene has already been rendered at least once
        if (!gl || !scene || !scene._webgl || !scene.drawableCollection) {
            return false;
        }

        var pm = scene._vf.pickMode.toLowerCase();
        var pickMode = 0;

        switch (pm) {
            case "box":      return false;
            case "idbuf":    pickMode = 0; break;
            case "idbuf24":  pickMode = 3; break;
            case "idbufid":  pickMode = 4; break;
            case "color":    pickMode = 1; break;
            case "texcoord": pickMode = 2; break;
        }

        
        // ViewMatrix and ViewProjectionMatrix
        var mat_view, mat_scene;

        if (arguments.length > 4) {
            mat_view = viewMat;
            mat_scene = sceneMat;
        }
        else {
            mat_view = viewarea._last_mat_view;
            mat_scene = viewarea._last_mat_scene;
        }

        // remember correct scene bbox
        var min = x3dom.fields.SFVec3f.copy(scene._lastMin);
        var max = x3dom.fields.SFVec3f.copy(scene._lastMax);
        // get current camera position
        var from = mat_view.inverse().e3();

        // get bbox of scene bbox and camera position
        var _min = x3dom.fields.SFVec3f.copy(from);
        var _max = x3dom.fields.SFVec3f.copy(from);

        if (_min.x > min.x) { _min.x = min.x; }
        if (_min.y > min.y) { _min.y = min.y; }
        if (_min.z > min.z) { _min.z = min.z; }

        if (_max.x < max.x) { _max.x = max.x; }
        if (_max.y < max.y) { _max.y = max.y; }
        if (_max.z < max.z) { _max.z = max.z; }

        // temporarily set scene size to include camera
        scene._lastMin.setValues(_min);
        scene._lastMax.setValues(_max);

        // get scalar scene size and adapted projection matrix
        var sceneSize = scene._lastMax.subtract(scene._lastMin).length();
        var cctowc = viewarea.getCCtoWCMatrix();

        // restore correct scene bbox
        scene._lastMin.setValues(min);
        scene._lastMax.setValues(max);

        // for deriving shadow ids together with shape ids
        var baseID = x3dom.nodeTypes.Shape.objectID + 2;


        // render to texture for reading pixel values
        this.renderPickingPass(gl, scene, mat_view, mat_scene, from, sceneSize, pickMode, x, y, 2, 2);

        // the pixel values under mouse cursor
        var pixelData = scene._webgl.fboPick.pixelData;

        if (pixelData && pixelData.length)
        {
            var pickPos = new x3dom.fields.SFVec3f(0, 0, 0);
            var pickNorm = new x3dom.fields.SFVec3f(0, 0, 1);

            var index = 0;
            var objId = pixelData[index + 3], shapeId;

            var pixelOffset = 1.0 / scene._webgl.pickScale;
            var denom = 1.0 / 256.0;
            var dist, line, lineoff, right, up;

            if (pickMode == 0) {
                objId += 256 * pixelData[index + 2];

                dist = (pixelData[index    ] / 255.0) * denom +
                       (pixelData[index + 1] / 255.0);

                line = viewarea.calcViewRay(x, y, cctowc);

                pickPos = line.pos.add(line.dir.multiply(dist * sceneSize));

                index = 4;      // get right pixel
                dist = (pixelData[index    ] / 255.0) * denom +
                       (pixelData[index + 1] / 255.0);

                lineoff = viewarea.calcViewRay(x + pixelOffset, y, cctowc);

                right = lineoff.pos.add(lineoff.dir.multiply(dist * sceneSize));
                right = right.subtract(pickPos).normalize();

                index = 8;      // get top pixel
                dist = (pixelData[index    ] / 255.0) * denom +
                       (pixelData[index + 1] / 255.0);

                lineoff = viewarea.calcViewRay(x, y - pixelOffset, cctowc);

                up = lineoff.pos.add(lineoff.dir.multiply(dist * sceneSize));
                up = up.subtract(pickPos).normalize();

                pickNorm = right.cross(up).normalize();
            }
            else if (pickMode == 3) {
                objId +=   256 * pixelData[index + 2] +
                         65536 * pixelData[index + 1];

                dist = pixelData[index] / 255.0;

                line = viewarea.calcViewRay(x, y, cctowc);

                pickPos = line.pos.add(line.dir.multiply(dist * sceneSize));

                index = 4;      // get right pixel
                dist = pixelData[index] / 255.0;

                lineoff = viewarea.calcViewRay(x + pixelOffset, y, cctowc);

                right = lineoff.pos.add(lineoff.dir.multiply(dist * sceneSize));
                right = right.subtract(pickPos).normalize();

                index = 8;      // get top pixel
                dist = pixelData[index] / 255.0;

                lineoff = viewarea.calcViewRay(x, y - pixelOffset, cctowc);

                up = lineoff.pos.add(lineoff.dir.multiply(dist * sceneSize));
                up = up.subtract(pickPos).normalize();

                pickNorm = right.cross(up).normalize();
            }
            else if (pickMode == 4) {
                objId += 256 * pixelData[index + 2];

                shapeId  =       pixelData[index + 1];
                shapeId += 256 * pixelData[index    ];

                // check if standard shape picked without special shadow id
                if (objId == 0 && (shapeId > 0 && shapeId < baseID)) {
                    objId = shapeId;
                }
            }
            else {
                pickPos.x = pixelData[index    ];
                pickPos.y = pixelData[index + 1];
                pickPos.z = pixelData[index + 2];
            }
            //x3dom.debug.logInfo(pickPos + " / " + objId);

            var eventType = "shadowObjectIdChanged";
            var shadowObjectIdChanged, event;
            var button = Math.max(buttonState >>> 8, buttonState & 255);

            if (objId >= baseID) {
                objId -= baseID;

                var hitObject;

                if (pickMode != 4) {
                    viewarea._pickingInfo.pickPos = pickPos;
                    viewarea._pick.setValues(pickPos);

                    viewarea._pickingInfo.pickNorm = pickNorm;
                    viewarea._pickNorm.setValues(pickNorm);

                    viewarea._pickingInfo.pickObj = null;
                    viewarea._pickingInfo.lastClickObj = null;

                    hitObject = scene._xmlNode;
                }
                else {
                    viewarea._pickingInfo.pickObj = x3dom.nodeTypes.Shape.idMap.nodeID[shapeId];

                    hitObject = viewarea._pickingInfo.pickObj._xmlNode;
                }


                //Check if there are MultiParts
                if (scene._multiPartMap) {
                    var mp, multiPart;

                    //Find related MultiPart
                    for (mp=0; mp<scene._multiPartMap.multiParts.length; mp++)
                    {
                        multiPart = scene._multiPartMap.multiParts[mp];
                        if (objId >= multiPart._minId && objId <= multiPart._maxId)
                        {
                            hitObject = multiPart._xmlNode;

                            event = {
                                target: multiPart._xmlNode,
                                button: button, mouseup: ((buttonState >>> 8) > 0),
                                layerX: x, layerY: y,
                                pickedId: objId,
                                worldX: pickPos.x, worldY: pickPos.y, worldZ: pickPos.z,
                                normalX: pickNorm.x, normalY: pickNorm.y, normalZ: pickNorm.z,
                                hitPnt: pickPos.toGL(),
                                hitObject: hitObject,
                                cancelBubble: false,
                                stopPropagation: function () { this.cancelBubble = true; },
                                preventDefault:  function () { this.cancelBubble = true; }
                            };

                            multiPart.handleEvents(event);
                        }
                        else
                        {
                            event = {
                                target: multiPart._xmlNode,
                                button: button, mouseup: ((buttonState >>> 8) > 0),
                                layerX: x, layerY: y,
                                pickedId: -1,
                                cancelBubble: false,
                                stopPropagation: function () { this.cancelBubble = true; },
                                preventDefault:  function () { this.cancelBubble = true; }
                            };

                            multiPart.handleEvents(event);
                        }
                    }
                }

                shadowObjectIdChanged = (viewarea._pickingInfo.shadowObjectId != objId);
                viewarea._pickingInfo.lastShadowObjectId = viewarea._pickingInfo.shadowObjectId;
                viewarea._pickingInfo.shadowObjectId = objId;
                //x3dom.debug.logInfo(baseID + " + " + objId);

                if ((shadowObjectIdChanged || button) && scene._xmlNode &&
                    (scene._xmlNode["on" + eventType] || scene._xmlNode.hasAttribute("on" + eventType) ||
                     scene._listeners[eventType]))
                {
                    event = {
                        target: scene._xmlNode,
                        type: eventType,
                        button: button, mouseup: ((buttonState >>> 8) > 0),
                        layerX: x, layerY: y,
                        shadowObjectId: objId,
                        worldX: pickPos.x, worldY: pickPos.y, worldZ: pickPos.z,
                        normalX: pickNorm.x, normalY: pickNorm.y, normalZ: pickNorm.z,
                        hitPnt: pickPos.toGL(),
                        hitObject: hitObject,
                        cancelBubble: false,
                        stopPropagation: function () { this.cancelBubble = true; },
                        preventDefault:  function () { this.cancelBubble = true; }
                    };
                    scene.callEvtHandler(("on" + eventType), event);
                }

                if (scene._shadowIdMap && scene._shadowIdMap.mapping &&
                    objId < scene._shadowIdMap.mapping.length) {
                    var shIds = scene._shadowIdMap.mapping[objId].usage;
                    var n, c, shObj;

                    if (!line) {
                        line = viewarea.calcViewRay(x, y, cctowc);
                    }
                    // find corresponding dom tree object
                    for (c = 0; c < shIds.length; c++) {
                        shObj = scene._nameSpace.defMap[shIds[c]];
                        // FIXME; bbox test too coarse (+ should include trafo)
                        if (shObj && shObj.doIntersect(line)) {
                            viewarea._pickingInfo.pickObj = shObj;
                            break;
                        }
                    }
                    //Check for other namespaces e.g. Inline/Multipart (FIXME; check recursively)
                    for (n = 0; n<scene._nameSpace.childSpaces.length; n++)
                    {
                        for (c = 0; c < shIds.length; c++) {
                            shObj = scene._nameSpace.childSpaces[n].defMap[shIds[c]];
                            // FIXME; bbox test too coarse (+ should include trafo)
                            if (shObj && shObj.doIntersect(line)) {
                                viewarea._pickingInfo.pickObj = shObj;
                                break;
                            }
                        }
                    }
                }
            }
            else {
                //Check if there are MultiParts
                if (scene._multiPartMap) {

                    //Find related MultiPart
                    for (mp=0; mp<scene._multiPartMap.multiParts.length; mp++)
                    {
                        multiPart = scene._multiPartMap.multiParts[mp];

                        event = {
                            target: multiPart._xmlNode,
                            button: button, mouseup: ((buttonState >>> 8) > 0),
                            layerX: x, layerY: y,
                            pickedId: -1,
                            cancelBubble: false,
                            stopPropagation: function () { this.cancelBubble = true; },
                            preventDefault:  function () { this.cancelBubble = true; }
                        };

                        multiPart.handleEvents(event);
                    }
                }


                shadowObjectIdChanged = (viewarea._pickingInfo.shadowObjectId != -1);
                viewarea._pickingInfo.shadowObjectId = -1;     // nothing hit

                if ( shadowObjectIdChanged && scene._xmlNode &&
                    (scene._xmlNode["on" + eventType] || scene._xmlNode.hasAttribute("on" + eventType) ||
                     scene._listeners[eventType]) )
                {
                    event = {
                        target: scene._xmlNode,
                        type: eventType,
                        button: button, mouseup: ((buttonState >>> 8) > 0),
                        layerX: x, layerY: y,
                        shadowObjectId: viewarea._pickingInfo.shadowObjectId,
                        cancelBubble: false,
                        stopPropagation: function () { this.cancelBubble = true; },
                        preventDefault:  function () { this.cancelBubble = true; }
                    };
                    scene.callEvtHandler(("on" + eventType), event);
                }

                if (objId > 0) {
                    //x3dom.debug.logInfo(x3dom.nodeTypes.Shape.idMap.nodeID[objId]._DEF + " // " +
                    //                    x3dom.nodeTypes.Shape.idMap.nodeID[objId]._xmlNode.localName);
                    viewarea._pickingInfo.pickPos = pickPos;
                    viewarea._pickingInfo.pickNorm = pickNorm;
                    viewarea._pickingInfo.pickObj = x3dom.nodeTypes.Shape.idMap.nodeID[objId];
                }
                else {
                    viewarea._pickingInfo.pickObj = null;
                    //viewarea._pickingInfo.lastObj = null;
                    viewarea._pickingInfo.lastClickObj = null;
                }
            }
        }
        var pickTime = x3dom.Utils.stopMeasure("picking");
        this.x3dElem.runtime.addMeasurement('PICKING', pickTime);

        return true;
    };

    /*****************************************************************************
     * Render ColorBuffer-Pass for picking sub window
     *****************************************************************************/
    Context.prototype.pickRect = function (viewarea, x1, y1, x2, y2)
    {
        var gl = this.ctx3d;
        var scene = viewarea ? viewarea._scene : null;

        // method requires that scene has already been rendered at least once
        if (!gl || !scene || !scene._webgl || !scene.drawableCollection)
            return false;

        // values not fully correct but unnecessary anyway, just to feed the shader
        var from = viewarea._last_mat_view.inverse().e3();
        var sceneSize = scene._lastMax.subtract(scene._lastMin).length();

        var x = (x1 <= x2) ? x1 : x2;
        var y = (y1 >= y2) ? y1 : y2;
        var width  = (1 + Math.abs(x2 - x1)) * scene._webgl.pickScale;
        var height = (1 + Math.abs(y2 - y1)) * scene._webgl.pickScale;

        // render to texture for reading pixel values
        this.renderPickingPass(gl, scene, viewarea._last_mat_view, viewarea._last_mat_scene,
            from, sceneSize, 0, x, y, (width < 1) ? 1 : width, (height < 1) ? 1 : height);

        var index;
        var pickedObjects = [];

        // get objects in rectangle
        for (index = 0; scene._webgl.fboPick.pixelData &&
        index < scene._webgl.fboPick.pixelData.length; index += 4) {
            var objId = scene._webgl.fboPick.pixelData[index + 3] +
                scene._webgl.fboPick.pixelData[index + 2] * 256;

            if (objId > 0)
                pickedObjects.push(objId);
        }
        pickedObjects.sort();

        // make found object IDs unique
        var pickedObjectsTemp = (function (arr) {
            var a = [], l = arr.length;
            for (var i = 0; i < l; i++) {
                for (var j = i + 1; j < l; j++) {
                    if (arr[i] === arr[j])
                        j = ++i;
                }
                a.push(arr[i]);
            }
            return a;
        })(pickedObjects);
        pickedObjects = pickedObjectsTemp;

        var pickedNode, pickedNodes = [];

        var hitObject;

        // for deriving shadow ids together with shape ids
        var baseID = x3dom.nodeTypes.Shape.objectID + 2;

        for (index = 0; index < pickedObjects.length; index++) {
            objId = pickedObjects[index];

            if (objId >= baseID)
            {
                objId -= baseID;

                //Check if there are MultiParts
                if (scene._multiPartMap) {
                    var mp, multiPart, colorMap, emissiveMap, specularMap, visibilityMap, partID;

                    //Find related MultiPart
                    for (mp = 0; mp < scene._multiPartMap.multiParts.length; mp++) {
                        multiPart = scene._multiPartMap.multiParts[mp];
                        colorMap = multiPart._inlineNamespace.defMap["MultiMaterial_ColorMap"];
                        emissiveMap = multiPart._inlineNamespace.defMap["MultiMaterial_EmissiveMap"];
                        specularMap = multiPart._inlineNamespace.defMap["MultiMaterial_SpecularMap"];
                        visibilityMap = multiPart._inlineNamespace.defMap["MultiMaterial_VisibilityMap"];
                        if (objId >= multiPart._minId && objId <= multiPart._maxId) {
                            partID = multiPart._idMap.mapping[objId - multiPart._minId].name;
                            hitObject = new x3dom.Parts(multiPart, [objId], colorMap, emissiveMap, specularMap, visibilityMap);

                            pickedNode = {"partID": partID, "part":hitObject};

                            pickedNodes.push(pickedNode);
                        }
                    }
                }

            }
            else
            {
                hitObject = x3dom.nodeTypes.Shape.idMap.nodeID[objId];
                hitObject = (hitObject && hitObject._xmlNode) ? hitObject._xmlNode : null;

                if (hitObject)
                    pickedNodes.push(hitObject);
            }
        }

        return pickedNodes;
    };

    /*****************************************************************************
     * Render Scene (Main-Pass)
     *****************************************************************************/
    Context.prototype.renderScene = function (viewarea)
    {
        var gl = this.ctx3d;
        var scene = viewarea._scene;

        if (gl === null || scene === null) {
            return;
        }

        var rentex = viewarea._doc._nodeBag.renderTextures;
        var rt_tex, rtl_i, rtl_n = rentex.length;
        var texProp = null;

        // for initFBO
        var type = gl.UNSIGNED_BYTE;
        var shadowType = gl.UNSIGNED_BYTE;
        var nearestFilt = false;

        if (x3dom.caps.FP_TEXTURES && !x3dom.caps.MOBILE) {
            type = gl.FLOAT;
            shadowType = gl.FLOAT;
            if (!x3dom.caps.FPL_TEXTURES) {
                nearestFilt = true;             // TODO: use correct filtering for fp-textures
            }
        }

        var shadowedLights, numShadowMaps;
        var i, j, n, size, sizeAvailable;
        var texType, refinementPos;
        var vertices = [-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1];

        scene.updateVolume();
		
        if (!scene._webgl)
        {
            scene._webgl = {};

            this.setupFgnds(gl, scene);

            // scale factor for mouse coords and width/ height (low res for speed-up)
            scene._webgl.pickScale = 0.5;

            scene._webgl._currFboWidth = Math.round(this.canvas.width * scene._webgl.pickScale);
            scene._webgl._currFboHeight = Math.round(this.canvas.height * scene._webgl.pickScale);

            // TODO: FIXME when spec ready: readPixels not (yet?) available for float textures
            // https://bugzilla.mozilla.org/show_bug.cgi?id=681903
            // https://www.khronos.org/webgl/public-mailing-list/archives/1108/msg00025.html
            scene._webgl.fboPick = x3dom.Utils.initFBO(gl,
                                   scene._webgl._currFboWidth, scene._webgl._currFboHeight, gl.UNSIGNED_BYTE, false, true);
            scene._webgl.fboPick.pixelData = null;

            //Set picking shaders
            /*scene._webgl.pickShader = this.cache.getShader(gl, x3dom.shader.PICKING);
            scene._webgl.pickShader24 = this.cache.getShader(gl, x3dom.shader.PICKING_24);
            scene._webgl.pickShaderId = this.cache.getShader(gl, x3dom.shader.PICKING_ID);
            scene._webgl.pickColorShader = this.cache.getShader(gl, x3dom.shader.PICKING_COLOR);
            scene._webgl.pickTexCoordShader = this.cache.getShader(gl, x3dom.shader.PICKING_TEXCOORD);*/

            scene._webgl.normalShader = this.cache.getShader(gl, x3dom.shader.NORMAL);

            //Initialize shadow maps
			scene._webgl.fboShadow = [];
			
			shadowedLights = viewarea.getShadowedLights();
            n = shadowedLights.length;

			for (i=0; i<n; i++)
            {
				size = shadowedLights[i]._vf.shadowMapSize;

				if (!x3dom.isa(shadowedLights[i], x3dom.nodeTypes.PointLight))
					//cascades for directional lights
					numShadowMaps = Math.max(1,Math.min(shadowedLights[i]._vf.shadowCascades,6));		
				else 
					//six maps for point lights
					numShadowMaps = 6;
					
				scene._webgl.fboShadow[i] = [];
				
				for (j=0; j < numShadowMaps; j++)
					scene._webgl.fboShadow[i][j] = x3dom.Utils.initFBO(gl, size, size, shadowType, false, true);
			}
			
			if (scene._webgl.fboShadow.length > 0 || x3dom.SSAO.isEnabled(scene))
				scene._webgl.fboScene = x3dom.Utils.initFBO(gl, this.canvas.width, this.canvas.height, shadowType, false, true);
			scene._webgl.fboBlur = [];
						
			//initialize blur fbo (different fbos for different sizes)
			for (i=0; i<n; i++)
            {
				size = scene._webgl.fboShadow[i][0].height;
				sizeAvailable = false;

				for (j = 0; j < scene._webgl.fboBlur.length; j++){
					if (size == scene._webgl.fboBlur[j].height) 
						sizeAvailable = true;
				}
				if (!sizeAvailable) 
					scene._webgl.fboBlur[scene._webgl.fboBlur.length] = x3dom.Utils.initFBO(gl, size, size, shadowType, false, true);
			}
			
			//initialize Data for post processing
			scene._webgl.ppBuffer = gl.createBuffer();
			gl.bindBuffer(gl.ARRAY_BUFFER, scene._webgl.ppBuffer);
			gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);		
			
			//scene._webgl.shadowShader = this.cache.getShader(gl, x3dom.shader.SHADOW);

            // TODO; cleanup on shutdown and lazily create on first use like size-dependent variables below
            scene._webgl.refinement = {
                stamps: new Array(2),
                positionBuffer: gl.createBuffer()
            };
            gl.bindBuffer(gl.ARRAY_BUFFER, scene._webgl.refinement.positionBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
            
            // This must be refreshed on node change!
            for (rtl_i = 0; rtl_i < rtl_n; rtl_i++) {
                rt_tex = rentex[rtl_i];

                texProp = rt_tex._cf.textureProperties.node;
                texType = rt_tex.requirePingPong() ? gl.UNSIGNED_BYTE : type;
                rt_tex._webgl = {};
                rt_tex._webgl.fbo = x3dom.Utils.initFBO(gl,
                    rt_tex._vf.dimensions[0], rt_tex._vf.dimensions[1], texType,
                    (texProp && texProp._vf.generateMipMaps), rt_tex._vf.depthMap || !rt_tex.requirePingPong());

                rt_tex._cleanupGLObjects = function(retainTex) {
                    if (!retainTex)
                        gl.deleteTexture(this._webgl.fbo.tex);
                    if (this._webgl.fbo.dtex)
                        gl.deleteTexture(this._webgl.fbo.dtex);
                    if (this._webgl.fbo.rbo)
                        gl.deleteFramebuffer(this._webgl.fbo.rbo);
                    gl.bindFramebuffer(gl.FRAMEBUFFER, null);
                    gl.deleteFramebuffer(this._webgl.fbo.fbo);
                    this._webgl.fbo.rbo = null;
                    this._webgl.fbo.fbo = null;
                };

                if (rt_tex.requirePingPong()) {
                    refinementPos = rt_tex._vf.dimensions[0] + "x" + rt_tex._vf.dimensions[1];
                    if (scene._webgl.refinement[refinementPos] === undefined) {
                        scene._webgl.refinement[refinementPos] = x3dom.Utils.initFBO(gl,
                            rt_tex._vf.dimensions[0], rt_tex._vf.dimensions[1], texType, false, false);
                    }
                    rt_tex._webgl.texture = null;
                }
            }

            viewarea._last_mat_view = x3dom.fields.SFMatrix4f.identity();
            viewarea._last_mat_proj = x3dom.fields.SFMatrix4f.identity();
            viewarea._last_mat_scene = x3dom.fields.SFMatrix4f.identity();

            this._calledViewpointChangedHandler = false;
        }
        else // updates needed?
        {
            var fboWidth = Math.round(this.canvas.width * scene._webgl.pickScale);
            var fboHeight = Math.round(this.canvas.height * scene._webgl.pickScale);

            if (scene._webgl._currFboWidth !== fboWidth ||
                scene._webgl._currFboHeight !== fboHeight) {
                scene._webgl._currFboWidth = fboWidth;
                scene._webgl._currFboHeight = fboHeight;

                scene._webgl.fboPick = x3dom.Utils.initFBO(gl, fboWidth, fboHeight, scene._webgl.fboPick.type, false, true);
                scene._webgl.fboPick.pixelData = null;

                x3dom.debug.logInfo("Refreshed picking FBO to size (" + fboWidth + ", " + fboHeight + ")");
            }

            for (rtl_i = 0; rtl_i < rtl_n; rtl_i++) {
                rt_tex = rentex[rtl_i];
                if (rt_tex._webgl && rt_tex._webgl.fbo &&
                    rt_tex._webgl.fbo.width  == rt_tex._vf.dimensions[0] &&
                    rt_tex._webgl.fbo.height == rt_tex._vf.dimensions[1])
                    continue;

                rt_tex.invalidateGLObject();
                if (rt_tex._cleanupGLObjects)
                    rt_tex._cleanupGLObjects();
                else
                    rt_tex._cleanupGLObjects = function(retainTex) {
                        if (!retainTex)
                            gl.deleteTexture(this._webgl.fbo.tex);
                        if (this._webgl.fbo.dtex)
                            gl.deleteTexture(this._webgl.fbo.dtex);
                        if (this._webgl.fbo.rbo)
                            gl.deleteRenderbuffer(this._webgl.fbo.rbo);
                        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
                        gl.deleteFramebuffer(this._webgl.fbo.fbo);
                        this._webgl.fbo.rbo = null;
                        this._webgl.fbo.fbo = null;
                    };

                texProp = rt_tex._cf.textureProperties.node;
                texType = rt_tex.requirePingPong() ? gl.UNSIGNED_BYTE : type;
                rt_tex._webgl = {};
                rt_tex._webgl.fbo = x3dom.Utils.initFBO(gl,
                                    rt_tex._vf.dimensions[0], rt_tex._vf.dimensions[1], texType,
                                    (texProp && texProp._vf.generateMipMaps), rt_tex._vf.depthMap || !rt_tex.requirePingPong());

                if (rt_tex.requirePingPong()) {
                    refinementPos = rt_tex._vf.dimensions[0] + "x" + rt_tex._vf.dimensions[1];
                    if (scene._webgl.refinement[refinementPos] === undefined) {
                        scene._webgl.refinement[refinementPos] = x3dom.Utils.initFBO(gl,
                            rt_tex._vf.dimensions[0], rt_tex._vf.dimensions[1], texType, false, false);
                    }
                    rt_tex._webgl.texture = null;
                }

                x3dom.debug.logInfo("Init/resize RenderedTexture_" + rtl_i + " to size " +
                                    rt_tex._vf.dimensions[0] + " x " + rt_tex._vf.dimensions[1]);
            }			
			
			//reinitialize shadow fbos if necessary
			shadowedLights = viewarea.getShadowedLights();
            n = shadowedLights.length;

			for (i=0; i<n; i++) {
				size = shadowedLights[i]._vf.shadowMapSize;

				if (!x3dom.isa(shadowedLights[i], x3dom.nodeTypes.PointLight))
					//cascades for directional lights
					numShadowMaps = Math.max(1,Math.min(shadowedLights[i]._vf.shadowCascades,6));				
				else 
					//six maps for point lights
					numShadowMaps = 6;		
				
				if (typeof scene._webgl.fboShadow[i] === "undefined" ||
                    scene._webgl.fboShadow[i].length != numShadowMaps ||
					scene._webgl.fboShadow[i][0].height != size) {
					scene._webgl.fboShadow[i] = [];
					for (j=0;j<numShadowMaps;j++){
						scene._webgl.fboShadow[i][j] = x3dom.Utils.initFBO(gl, size, size, shadowType, false, true);
					}
				}			
			}
			
			//reinitialize blur fbos if necessary
			for (i=0; i<n; i++){
				size = scene._webgl.fboShadow[i][0].height;
				
				sizeAvailable = false;
				for (j = 0; j < scene._webgl.fboBlur.length; j++){
					if (size == scene._webgl.fboBlur[j].height) 
						sizeAvailable = true;
				}
				if (!sizeAvailable) 
					scene._webgl.fboBlur[scene._webgl.fboBlur.length] = x3dom.Utils.initFBO(gl, size, size, shadowType, false, true);
			}

			if ((x3dom.SSAO.isEnabled(scene) ||scene._webgl.fboShadow.length > 0) && typeof scene._webgl.fboScene == "undefined" || scene._webgl.fboScene &&
				(this.canvas.width != scene._webgl.fboScene.width || this.canvas.height != scene._webgl.fboScene.height)) {
				scene._webgl.fboScene = x3dom.Utils.initFBO(gl, this.canvas.width, this.canvas.height, shadowType, false, true);
			}
        }

        var env = scene.getEnvironment();
        // update internal flags
        env.checkSanity();

        var bgnd = scene.getBackground();
        // setup or update bgnd
        this.setupScene(gl, bgnd);

        this.numFaces = 0;
        this.numCoords = 0;
        this.numDrawCalls = 0;

        var mat_proj = viewarea.getProjectionMatrix();
        var mat_view = viewarea.getViewMatrix();

        // fire viewpointChanged event
        if (!this._calledViewpointChangedHandler || !viewarea._last_mat_view.equals(mat_view)) {
            var e_viewpoint = scene.getViewpoint();
            var e_eventType = "viewpointChanged";

            try {
                if ( e_viewpoint._xmlNode &&
                    (e_viewpoint._xmlNode["on" + e_eventType] ||
                     e_viewpoint._xmlNode.hasAttribute("on" + e_eventType) ||
                     e_viewpoint._listeners[e_eventType]) ) {
                    var e_viewtrafo = e_viewpoint.getCurrentTransform();
                    e_viewtrafo = e_viewtrafo.inverse().mult(mat_view);
                    var e_mat = e_viewtrafo.inverse();

                    var e_rotation = new x3dom.fields.Quaternion(0, 0, 1, 0);
                    e_rotation.setValue(e_mat);
                    var e_translation = e_mat.e3();

                    var e_event = {
                        target: e_viewpoint._xmlNode,
                        type: e_eventType,
                        matrix: e_viewtrafo,
                        position: e_translation,
                        orientation: e_rotation.toAxisAngle(),
                        cancelBubble: false,
                        stopPropagation: function () { this.cancelBubble = true; },
                        preventDefault:  function () { this.cancelBubble = true; }
                    };

                    e_viewpoint.callEvtHandler(("on" + e_eventType), e_event);

                    this._calledViewpointChangedHandler = true;
                }
            }
            catch (e_e) {
                x3dom.debug.logException(e_e);
            }
        }

        viewarea._last_mat_view = mat_view;
        viewarea._last_mat_proj = mat_proj;

        var mat_scene = mat_proj.mult(mat_view);  //viewarea.getWCtoCCMatrix();
        viewarea._last_mat_scene = mat_scene;


        //===========================================================================
        // Collect drawables (traverse)
        //===========================================================================
        scene.drawableCollection = null;  // Always update needed?

        if (!scene.drawableCollection)
        {
            var drawableCollectionConfig = {
                viewArea: viewarea,
                sortTrans: env._vf.sortTrans,
                viewMatrix: mat_view,
                projMatrix: mat_proj,
                sceneMatrix: mat_scene,
                frustumCulling: true,
                smallFeatureThreshold: env._smallFeatureThreshold,
                context: this,
                gl: gl
            };

            scene.drawableCollection = new x3dom.DrawableCollection(drawableCollectionConfig);

            x3dom.Utils.startMeasure('traverse');

            scene.collectDrawableObjects(x3dom.fields.SFMatrix4f.identity(), scene.drawableCollection, true, false, 0, []);

            var traverseTime = x3dom.Utils.stopMeasure('traverse');
            this.x3dElem.runtime.addMeasurement('TRAVERSE', traverseTime);
        }

        //===========================================================================
        // Sort drawables
        //===========================================================================      
        x3dom.Utils.startMeasure('sorting');

        scene.drawableCollection.sort();

        var sortTime = x3dom.Utils.stopMeasure('sorting');
        this.x3dElem.runtime.addMeasurement('SORT', sortTime);

        //===========================================================================
        // Render Shadow Pass
        //===========================================================================
        var slights = viewarea.getLights();
        var numLights = slights.length;
        var mat_light;
        var WCToLCMatrices = [];
        var lMatrices = [];
        var shadowCount = 0;

        x3dom.Utils.startMeasure('shadow');

        for (var p = 0; p < numLights; p++) {
            if (slights[p]._vf.shadowIntensity > 0.0) {

                var lightMatrix = viewarea.getLightMatrix()[p];
                shadowMaps = scene._webgl.fboShadow[shadowCount];
                var offset = Math.max(0.0, Math.min(1.0, slights[p]._vf.shadowOffset));

                if (!x3dom.isa(slights[p], x3dom.nodeTypes.PointLight)) {
                    //get cascade count
                    var numCascades = Math.max(1, Math.min(slights[p]._vf.shadowCascades, 6));

                    //calculate transformation matrices
                    mat_light = viewarea.getWCtoLCMatricesCascaded(lightMatrix, slights[p], mat_proj);

                    //render shadow pass
                    for (i = 0; i < numCascades; i++) {
                        this.renderShadowPass(gl, viewarea, mat_light[i], mat_view, shadowMaps[i], offset, false);
                    }
                }
                else {
                    //for point lights 6 render passes
                    mat_light = viewarea.getWCtoLCMatricesPointLight(lightMatrix, slights[p], mat_proj);
                    for (i = 0; i < 6; i++) {
                        this.renderShadowPass(gl, viewarea, mat_light[i], mat_view, shadowMaps[i], offset, false);
                    }
                }
                shadowCount++;

                //save transformations for shadow rendering
                WCToLCMatrices[WCToLCMatrices.length] = mat_light;
                lMatrices[lMatrices.length] = lightMatrix;
            }
        }

        //One pass for depth of scene from camera view (to enable post-processing shading)
        if (shadowCount > 0 || x3dom.SSAO.isEnabled(scene)) {
            this.renderShadowPass(gl, viewarea, mat_scene, mat_view, scene._webgl.fboScene, 0.0, true);
            var shadowTime = x3dom.Utils.stopMeasure('shadow');
            this.x3dElem.runtime.addMeasurement('SHADOW', shadowTime);
        }
        else {
            this.x3dElem.runtime.removeMeasurement('SHADOW');
        }

        mat_light = viewarea.getWCtoLCMatrix(viewarea.getLightMatrix()[0]);

        for (rtl_i = 0; rtl_i < rtl_n; rtl_i++) {
            this.renderRTPass(gl, viewarea, rentex[rtl_i]);
        }

        // rendering
        x3dom.Utils.startMeasure('render');

        this.stateManager.viewport(0, 0, this.canvas.width, this.canvas.height);

        // calls gl.clear etc. (bgnd stuff)
        bgnd._webgl.render(gl, mat_view, mat_proj);

        x3dom.nodeTypes.PopGeometry.numRenderedVerts = 0;
        x3dom.nodeTypes.PopGeometry.numRenderedTris = 0;

        n = scene.drawableCollection.length;

        // Very, very experimental priority culling, currently coupled with frustum and small feature culling
        // TODO; what about shadows?
        if (env._vf.smallFeatureCulling && env._lowPriorityThreshold < 1 && viewarea.isMovingOrAnimating()) {
            n = Math.floor(n * env._lowPriorityThreshold);
            if (!n && scene.drawableCollection.length)
                n = 1;   // render at least one object
        }

        this.stateManager.unsetProgram();

        // render all remaining shapes
        for (i = 0; i < n; i++) {
            var drawable = scene.drawableCollection.get(i);

            this.renderShape(drawable, viewarea, slights, numLights, mat_view, mat_scene, mat_light, mat_proj, gl);
        }

        if (shadowCount > 0)
            this.renderShadows(gl, viewarea, shadowedLights, WCToLCMatrices, lMatrices, mat_view, mat_proj, mat_scene);

        this.stateManager.disable(gl.BLEND);
        this.stateManager.disable(gl.DEPTH_TEST);

        viewarea._numRenderedNodes = n;
        
        if(x3dom.SSAO.isEnabled(scene))
            x3dom.SSAO.renderSSAO(this.stateManager, gl, scene, this.canvas);
        
        // if _visDbgBuf then show helper buffers in foreground for debugging
        if (viewarea._visDbgBuf !== undefined && viewarea._visDbgBuf)
        {
            var pm = scene._vf.pickMode.toLowerCase();

            if (pm.indexOf("idbuf") == 0 || pm == "color" || pm == "texcoord") {
                this.stateManager.viewport(0, 3 * this.canvas.height / 4,
                                           this.canvas.width / 4, this.canvas.height / 4);
                scene._fgnd._webgl.render(gl, scene._webgl.fboPick.tex);
            }

            if (shadowCount > 0 || x3dom.SSAO.isEnabled(scene)) {
                this.stateManager.viewport(this.canvas.width / 4, 3 * this.canvas.height / 4,
                                           this.canvas.width / 4, this.canvas.height / 4);
                scene._fgnd._webgl.render(gl, scene._webgl.fboScene.tex);
            }

            var row = 3, col = 2;
            for (i = 0; i < shadowCount; i++) {
                var shadowMaps = scene._webgl.fboShadow[i];
                for (j = 0; j < shadowMaps.length; j++) {
                    this.stateManager.viewport(col * this.canvas.width / 4, row * this.canvas.height / 4,
                                               this.canvas.width / 4, this.canvas.height / 4);
                    scene._fgnd._webgl.render(gl, shadowMaps[j].tex);
                    if (col < 2) {
                        col++;
                    } else {
                        col = 0;
                        row--;
                    }
                }
            }

            for (rtl_i = 0; rtl_i < rtl_n; rtl_i++) {
                rt_tex = rentex[rtl_i];
                if (!rt_tex._webgl.fbo.fbo) // might be deleted (--> RefinementTexture when finished)
                    continue;

                this.stateManager.viewport(rtl_i * this.canvas.width / 8, 5 * this.canvas.height / 8,
                                           this.canvas.width / 8, this.canvas.height / 8);
                scene._fgnd._webgl.render(gl, rt_tex._webgl.fbo.tex);
            }
        }

        gl.finish();
        //gl.flush();

        var renderTime = x3dom.Utils.stopMeasure('render');

        this.x3dElem.runtime.addMeasurement('RENDER', renderTime);
        this.x3dElem.runtime.addMeasurement('DRAW', (n ? renderTime / n : 0));

        this.x3dElem.runtime.addInfo('#NODES:', scene.drawableCollection.numberOfNodes);
        this.x3dElem.runtime.addInfo('#SHAPES:', viewarea._numRenderedNodes);
        this.x3dElem.runtime.addInfo("#DRAWS:", this.numDrawCalls);
        this.x3dElem.runtime.addInfo("#POINTS:", this.numCoords);
        this.x3dElem.runtime.addInfo("#TRIS:", this.numFaces);

        //scene.drawableObjects = null;
    };

    /*****************************************************************************
     * Render special PingPong-Pass
     *****************************************************************************/
    Context.prototype.renderPingPongPass = function (gl, viewarea, rt) {
        var scene = viewarea._scene;
        var refinementPos = rt._vf.dimensions[0] + "x" + rt._vf.dimensions[1];
        var refinementFbo = scene._webgl.refinement[refinementPos];

        // load stamp textures
        if (rt._currLoadLevel == 0 && (!scene._webgl.refinement.stamps[0] || !scene._webgl.refinement.stamps[1])) {
            scene._webgl.refinement.stamps[0] = this.cache.getTexture2D(gl, rt._nameSpace.doc,
                                    rt._nameSpace.getURL(rt._vf.stamp0), false, false, false, false);
            scene._webgl.refinement.stamps[1] = this.cache.getTexture2D(gl, rt._nameSpace.doc,
                                    rt._nameSpace.getURL(rt._vf.stamp1), false, false, false, false);
        }

        // load next level of image
        if (rt._currLoadLevel < rt._loadLevel) {
            rt._currLoadLevel++;

            if (rt._webgl.texture)
                gl.deleteTexture(rt._webgl.texture);

            var filename = rt._vf.url[0] + "/" + rt._currLoadLevel + "." + rt._vf.format;

            rt._webgl.texture = x3dom.Utils.createTexture2D(gl, rt._nameSpace.doc,
                                rt._nameSpace.getURL(filename), false, false, false, false);

            if (rt._vf.iterations % 2 === 0)
                (rt._currLoadLevel % 2 !== 0) ? rt._repeat.x *= 2.0 : rt._repeat.y *= 2.0;
            else
                (rt._currLoadLevel % 2 === 0) ? rt._repeat.x *= 2.0 : rt._repeat.y *= 2.0;
        }

        if (!rt._webgl.texture.ready ||
            !scene._webgl.refinement.stamps[0].ready || !scene._webgl.refinement.stamps[1].ready)
            return;

        // first pass
        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, refinementFbo.fbo);
        this.stateManager.viewport(0, 0, refinementFbo.width, refinementFbo.height);

        this.stateManager.disable(gl.BLEND);
        this.stateManager.disable(gl.CULL_FACE);
        this.stateManager.disable(gl.DEPTH_TEST);

        gl.clearColor(0, 0, 0, 1);
        gl.clearDepth(1);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        var sp = this.cache.getShader(gl, x3dom.shader.TEXTURE_REFINEMENT);
        this.stateManager.useProgram(sp);

        gl.bindBuffer(gl.ARRAY_BUFFER, scene._webgl.refinement.positionBuffer);
        gl.vertexAttribPointer(sp.position, 2, gl.FLOAT, false, 0, 0);
        gl.enableVertexAttribArray(sp.position);

        sp.stamp = 0;
        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, scene._webgl.refinement.stamps[(rt._currLoadLevel + 1) % 2]);    // draw stamp
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);

        if (rt._currLoadLevel > 1) {
            sp.lastTex = 1;
            gl.activeTexture(gl.TEXTURE1);
            gl.bindTexture(gl.TEXTURE_2D, rt._webgl.fbo.tex);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
            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);
        }

        sp.curTex = 2;
        gl.activeTexture(gl.TEXTURE2);
        gl.bindTexture(gl.TEXTURE_2D, rt._webgl.texture);    // draw level image to fbo
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        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);

        sp.mode = rt._currLoadLevel - 1;
        sp.repeat = rt._repeat.toGL();

        gl.drawArrays(gl.TRIANGLES, 0, 6);

        // second pass
        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, rt._webgl.fbo.fbo);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

        sp.mode = 0;
        sp.curTex = 2;
        gl.activeTexture(gl.TEXTURE2);
        gl.bindTexture(gl.TEXTURE_2D, refinementFbo.tex);   // draw result to fbo
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        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.drawArrays(gl.TRIANGLES, 0, 6);

        gl.activeTexture(gl.TEXTURE0);
        gl.bindTexture(gl.TEXTURE_2D, null);

        gl.disableVertexAttribArray(sp.position);

        // pass done
        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, null);
        this.stateManager.viewport(0, 0, this.canvas.width, this.canvas.height);

        if (rt._vf.autoRefinement)
            rt.nextLevel();

        if (rt._currLoadLevel == rt._vf.maxLevel)
            rt._currLoadLevel++;

        if (rt._webgl.fbo.mipMap) {
            gl.bindTexture(gl.TEXTURE_2D, rt._webgl.fbo.tex);
            gl.generateMipmap(gl.TEXTURE_2D);
            gl.bindTexture(gl.TEXTURE_2D, null);
        }

        // we're finally done: cleanup/delete all helper FBOs
        if (!rt.requirePingPong()) {
            gl.deleteTexture(rt._webgl.texture);
            delete rt._webgl.texture;

            rt._cleanupGLObjects(true);
        }

        rt._renderedImage++;
    };

    /*****************************************************************************
     * Render RenderedTexture-Pass
     *****************************************************************************/
    Context.prototype.renderRTPass = function (gl, viewarea, rt)
    {
        /// begin special case (progressive image refinement)
        if (x3dom.isa(rt, x3dom.nodeTypes.RefinementTexture)) {
            if (rt.requirePingPong()) {
                this.renderPingPongPass(gl, viewarea, rt);
            }
            return;
        }
        /// end special case

        switch (rt._vf.update.toUpperCase()) {
            case "NONE":
                return;
            case "NEXT_FRAME_ONLY":
                if (!rt._needRenderUpdate) {
                    return;
                }
                rt._needRenderUpdate = false;
                break;
            case "ALWAYS":
            default:
                break;
        }

        var scene = viewarea._scene;
        var bgnd = null;

        var mat_view = rt.getViewMatrix();
        var mat_proj = rt.getProjectionMatrix();
        var mat_scene = mat_proj.mult(mat_view);

        var lightMatrix = viewarea.getLightMatrix()[0];
        var mat_light = viewarea.getWCtoLCMatrix(lightMatrix);

        var i, n, m = rt._cf.excludeNodes.nodes.length;

        var arr = new Array(m);
        for (i = 0; i < m; i++) {
            var render = rt._cf.excludeNodes.nodes[i]._vf.render;
            if (render === undefined) {
                arr[i] = -1;
            }
            else {
                if (render === true) {
                    arr[i] = 1;
                } else {
                    arr[i] = 0;
                }
            }
            rt._cf.excludeNodes.nodes[i]._vf.render = false;
        }

        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, rt._webgl.fbo.fbo);

        this.stateManager.viewport(0, 0, rt._webgl.fbo.width, rt._webgl.fbo.height);

        if (rt._cf.background.node === null) {
            gl.clearColor(0, 0, 0, 1);
            gl.clearDepth(1.0);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);
        }
        else if (rt._cf.background.node === scene.getBackground()) {
            bgnd = scene.getBackground();
            bgnd._webgl.render(gl, mat_view, mat_proj);
        }
        else {
            bgnd = rt._cf.background.node;
            this.setupScene(gl, bgnd);
            bgnd._webgl.render(gl, mat_view, mat_proj);
        }

        this.stateManager.depthFunc(gl.LEQUAL);
        this.stateManager.enable(gl.DEPTH_TEST);
        this.stateManager.enable(gl.CULL_FACE);

        this.stateManager.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE);
        this.stateManager.enable(gl.BLEND);

        var slights = viewarea.getLights();
        var numLights = slights.length;

        var transform, shape, drawable;
        var locScene = rt._cf.scene.node;

        if (!locScene || locScene === scene) {
            n = scene.drawableCollection.length;

            if (rt._vf.showNormals) {
                this.renderNormals(gl, scene, scene._webgl.normalShader, mat_view, mat_scene);
            }
            else {
                this.stateManager.unsetProgram();

                for (i = 0; i < n; i++) {
                    drawable = scene.drawableCollection.get(i);

                    this.renderShape(drawable, viewarea, slights, numLights,
                                     mat_view, mat_scene, mat_light, mat_proj, gl);
                }
            }
        }
        else {
            var env = scene.getEnvironment();

            var drawableCollectionConfig = {
                viewArea: viewarea,
                sortTrans: env._vf.sortTrans,
                viewMatrix: mat_view,
                projMatrix: mat_proj,
                sceneMatrix: mat_scene,
                frustumCulling: false,
                smallFeatureThreshold: 1,
                context: this,
                gl: gl
            };

            locScene.numberOfNodes = 0;
            locScene.drawableCollection = new x3dom.DrawableCollection(drawableCollectionConfig);

            locScene.collectDrawableObjects(x3dom.fields.SFMatrix4f.identity(),
                                            locScene.drawableCollection, true, false, 0, []);

            locScene.drawableCollection.sort();

            n = locScene.drawableCollection.length;

            if (rt._vf.showNormals) {
                this.renderNormals(gl, locScene, scene._webgl.normalShader, mat_view, mat_scene);
            }
            else {
                this.stateManager.unsetProgram();

                for (i = 0; i < n; i++) {
                    drawable = locScene.drawableCollection.get(i);

                    if (!drawable.shape._vf.render) {
                        continue;
                    }

                    this.renderShape(drawable, viewarea, slights, numLights,
                                     mat_view, mat_scene, mat_light, mat_proj, gl);
                }
            }
        }

        this.stateManager.disable(gl.BLEND);
        this.stateManager.disable(gl.DEPTH_TEST);

        gl.flush();
        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, null);

        if (rt._webgl.fbo.mipMap) {
            gl.bindTexture(gl.TEXTURE_2D, rt._webgl.fbo.tex);
            gl.generateMipmap(gl.TEXTURE_2D);
            gl.bindTexture(gl.TEXTURE_2D, null);
        }

        for (i = 0; i < m; i++) {
            if (arr[i] !== 0) {
                rt._cf.excludeNodes.nodes[i]._vf.render = true;
            }
        }
    };

    /*****************************************************************************
     * Render Normals
     *****************************************************************************/
    Context.prototype.renderNormals = function (gl, scene, sp, mat_view, mat_scene)
    {
        if (!sp || !scene) {  // error
            return;
        }

        this.stateManager.depthFunc(gl.LEQUAL);
        this.stateManager.enable(gl.DEPTH_TEST);
        this.stateManager.enable(gl.CULL_FACE);
        this.stateManager.disable(gl.BLEND);

        this.stateManager.useProgram(sp);

        var bgCenter = x3dom.fields.SFVec3f.NullVector.toGL();
        var bgSize = x3dom.fields.SFVec3f.OneVector.toGL();

        for (var i = 0, n = scene.drawableCollection.length; i < n; i++)
        {
            var drawable = scene.drawableCollection.get(i);
            var trafo = drawable.transform;
            var shape = drawable.shape;
            var s_gl = shape._webgl;

            if (!s_gl || !shape || !shape._vf.render) {
                continue;
            }

            var s_geo = shape._cf.geometry.node;
            var s_msh = s_geo._mesh;

            var model_view_inv = mat_view.mult(trafo).inverse();
            sp.normalMatrix = model_view_inv.transpose().toGL();
            sp.modelViewProjectionMatrix = mat_scene.mult(trafo).toGL();

            //Set ImageGeometry switch (TODO; also impl. in Shader!)
            sp.imageGeometry = s_gl.imageGeometry;

            if (s_gl.coordType != gl.FLOAT) {
                if (s_gl.popGeometry != 0 ||
                    (s_msh._numPosComponents == 4 && x3dom.Utils.isUnsignedType(s_geo._vf.coordType)))
                    sp.bgCenter = s_geo.getMin().toGL();
                else
                    sp.bgCenter = s_geo._vf.position.toGL();
                sp.bgSize = s_geo._vf.size.toGL();
                sp.bgPrecisionMax = s_geo.getPrecisionMax('coordType');
            }
            else {
                sp.bgCenter = bgCenter;
                sp.bgSize = bgSize;
                sp.bgPrecisionMax = 1;
            }
            if (s_gl.normalType != gl.FLOAT) {
                sp.bgPrecisionNorMax = s_geo.getPrecisionMax('normalType');
            }
            else {
                sp.bgPrecisionNorMax = 1;
            }

            if (shape.isSolid()) {
                this.stateManager.enable(gl.CULL_FACE);

                if (shape.isCCW()) {
                    this.stateManager.frontFace(gl.CCW);
                }
                else {
                    this.stateManager.frontFace(gl.CW);
                }
            }
            else {
                this.stateManager.disable(gl.CULL_FACE);
            }


            // render shape
            for (var q = 0, q_n = s_gl.positions.length; q < q_n; q++) {
                var q6 = 6 * q;
                var v, v_n, offset;

                if (s_gl.externalGeometry != 0) {
                    var mesh = shape.meshes[q];
                    mesh.bindVertexAttribPointer(gl, sp);

                    mesh.render(gl);
                }
                else
                if ( !(sp.position !== undefined && s_gl.buffers[q6 + 1] && s_gl.indexes[q]) )
                    continue;

                // bind buffers
                if (s_gl.buffers[q6]) {
                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[q6]);
                }

                this.setVertexAttribPointerPosition(gl, shape, q6, q);
                this.setVertexAttribPointerNormal(gl, shape, q6, q);

                // draw mesh
                if (s_gl.binaryGeometry > 0 || s_gl.popGeometry > 0) {
                    for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                        gl.drawElements(s_gl.primType[v], s_geo._vf.vertexCount[v], s_gl.indexType,
                                        x3dom.Utils.getByteAwareOffset(offset, s_gl.indexType, gl));
                        offset += s_geo._vf.vertexCount[v];
                    }
                }
                else if (s_gl.binaryGeometry < 0 || s_gl.popGeometry < 0 || s_gl.imageGeometry) {
                    for (v = 0, offset = 0, v_n = s_geo._vf.vertexCount.length; v < v_n; v++) {
                        gl.drawArrays(s_gl.primType[v], offset, s_geo._vf.vertexCount[v]);
                        offset += s_geo._vf.vertexCount[v];
                    }
                }
                else if (s_geo.hasIndexOffset()) {
                    var indOff = shape.tessellationProperties();
                    for (v = 0, v_n = indOff.length; v < v_n; v++) {
                        gl.drawElements(s_gl.primType, indOff[v].count, s_gl.indexType,
                            indOff[v].offset * x3dom.Utils.getOffsetMultiplier(s_gl.indexType, gl));
                    }
                }
                else if (s_gl.indexes[q].length == 0) {
                    gl.drawArrays(s_gl.primType, 0, s_gl.positions[q].length / 3);
                }
                else {
                    gl.drawElements(s_gl.primType, s_gl.indexes[q].length, s_gl.indexType, 0);
                }

                gl.disableVertexAttribArray(sp.position);

                if (sp.normal !== undefined) {
                    gl.disableVertexAttribArray(sp.normal);
                }
            }
        }
    };

    /*****************************************************************************
     * Cleanup
     *****************************************************************************/
    Context.prototype.shutdown = function (viewarea) {
        var gl = this.ctx3d;
        var scene = viewarea._scene;

        if (gl == null || !scene) {
            return;
        }

        var bgnd = scene.getBackground();
        if (bgnd._webgl.position !== undefined) {
            gl.deleteBuffer(bgnd._webgl.buffers[1]);
            gl.deleteBuffer(bgnd._webgl.buffers[0]);
        }
        var fgnd = scene._fgnd;
        if (fgnd._webgl.position !== undefined) {
            gl.deleteBuffer(fgnd._webgl.buffers[1]);
            gl.deleteBuffer(fgnd._webgl.buffers[0]);
        }

        var n = scene.drawableCollection ? scene.drawableCollection.length : 0;
        for (var i = 0; i < n; i++) {
            var shape = scene.drawableCollection.get(i).shape;

            if (shape._cleanupGLObjects)
                shape._cleanupGLObjects(true);
        }

        //Release Texture and Shader Resources
        this.cache.Release(gl);
    };
	
	/*****************************************************************************
    * Draw shadows on screen
    *****************************************************************************/
	Context.prototype.renderShadows = function(gl, viewarea, shadowedLights, wctolc, lMatrices,
                                               mat_view, mat_proj, mat_scene)
    {
		var scene = viewarea._scene;
		
		//don't render shadows with less than 7 textures per fragment shader
		var texLimit = x3dom.caps.MAX_TEXTURE_IMAGE_UNITS;
		
		if (texLimit < 7)
            return;
		
		var texUnits = 1;
		var renderSplit = [ 0 ];

        var shadowMaps, numShadowMaps;
        var i, j, k;
		
		//filter shadow maps and determine, if multiple render passes are needed		
		for (i = 0; i < shadowedLights.length; i++)
        {
            var filterSize = shadowedLights[i]._vf.shadowFilterSize;
            shadowMaps = scene._webgl.fboShadow[i];
            numShadowMaps = shadowMaps.length;

            //filtering
            for (j=0; j<numShadowMaps;j++){
                this.blurTex(gl, scene, shadowMaps[j], filterSize);
            }

            //shader consumes 6 tex units per lights (even if less are bound)
            texUnits+=6;

            if (texUnits > texLimit){
                renderSplit[renderSplit.length] = i;
                texUnits = 7;
            }
		}
		renderSplit[renderSplit.length] = shadowedLights.length;
		
		//render shadows for current render split
        var n = renderSplit.length - 1;
        var mat_proj_inv = mat_proj.inverse();
        var mat_scene_inv = mat_scene.inverse();

        //enable (multiplicative) blending
        this.stateManager.enable(gl.BLEND);
        this.stateManager.blendFunc(gl.DST_COLOR, gl.ZERO);

		for (var s=0; s<n; s++)
        {
			var startIndex = renderSplit[s];
			var endIndex = renderSplit[s+1];
		
			var currentLights = [];
			
			for (k=startIndex; k<endIndex; k++)
				currentLights[currentLights.length] = shadowedLights[k];

			var sp = this.cache.getShadowRenderingShader(gl, currentLights);

            this.stateManager.useProgram(sp);
			
			gl.bindBuffer(gl.ARRAY_BUFFER, scene._webgl.ppBuffer);
			gl.vertexAttribPointer(sp.position, 2, gl.FLOAT, false, 0, 0);
			gl.enableVertexAttribArray(sp.position);
			
			//bind depth texture (depth from camera view)
			sp.sceneMap = 0;
			gl.activeTexture(gl.TEXTURE0);
			gl.bindTexture(gl.TEXTURE_2D, scene._webgl.fboScene.tex);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
			gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
			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);
			
			//compute inverse projection matrix
			sp.inverseProj = mat_proj_inv.toGL();
			
			//compute inverse view projection matrix
			sp.inverseViewProj = mat_scene_inv.toGL();

			var mat_light;
			var lightMatrix;
			var shadowIndex = 0;

			for (var p=0, pn=currentLights.length; p<pn; p++) {
				//get light matrices and shadow maps for current light
				lightMatrix = lMatrices[p+startIndex];
				mat_light = wctolc[p+startIndex];
				shadowMaps = scene._webgl.fboShadow[p+startIndex]; 
				
				numShadowMaps = mat_light.length;
				
				for (i=0; i< numShadowMaps; i++){
                    gl.activeTexture(gl.TEXTURE1 + shadowIndex);
                    gl.bindTexture(gl.TEXTURE_2D, shadowMaps[i].tex);
                    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
                    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
                    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);

                    sp['light'+p+'_'+i+'_ShadowMap'] = shadowIndex+1;
                    sp['light'+p+'_'+i+'_Matrix'] = mat_light[i].toGL();
                    shadowIndex++;
				}
				sp['light'+p+'_ViewMatrix'] = lightMatrix.toGL();						

				//cascade depths for directional and spot light
				if (!x3dom.isa(currentLights[p], x3dom.nodeTypes.PointLight)){
					for (j=0; j< numShadowMaps; j++){
						var numCascades = Math.max(1,Math.min(currentLights[p]._vf.shadowCascades,6));
						var splitFactor = Math.max(0,Math.min(currentLights[p]._vf.shadowSplitFactor,1));					
						var splitOffset = Math.max(0,Math.min(currentLights[p]._vf.shadowSplitOffset,1));						
						
						var splitDepths = viewarea.getShadowSplitDepths(numCascades, splitFactor, splitOffset, false, mat_proj);
						sp['light'+p+'_'+j+'_Split'] = splitDepths[j+1];
					}
				}
			
				//assign light properties
				var light_transform = mat_view.mult(currentLights[p].getCurrentTransform());
				if(x3dom.isa(currentLights[p], x3dom.nodeTypes.DirectionalLight))
				{
					sp['light'+p+'_Type']             = 0.0;
					sp['light'+p+'_On']               = (currentLights[p]._vf.on) ? 1.0 : 0.0;
					sp['light'+p+'_Direction']        = light_transform.multMatrixVec(currentLights[p]._vf.direction).toGL();
					sp['light'+p+'_Attenuation']      = [1.0, 1.0, 1.0];
					sp['light'+p+'_Location']         = [1.0, 1.0, 1.0];
					sp['light'+p+'_Radius']           = 0.0;
					sp['light'+p+'_BeamWidth']        = 0.0;
					sp['light'+p+'_CutOffAngle']      = 0.0;
					sp['light'+p+'_ShadowIntensity']  = currentLights[p]._vf.shadowIntensity;
					sp['light'+p+'_ShadowCascades']   = currentLights[p]._vf.shadowCascades;
					sp['light'+p+'_ShadowOffset']     = Math.max(0.0,Math.min(1.0,currentLights[p]._vf.shadowOffset));
				}
				else if(x3dom.isa(currentLights[p], x3dom.nodeTypes.PointLight))
				{
					sp['light'+p+'_Type']             = 1.0;
					sp['light'+p+'_On']               = (currentLights[p]._vf.on) ? 1.0 : 0.0;
					sp['light'+p+'_Direction']        = [1.0, 1.0, 1.0];
					sp['light'+p+'_Attenuation']      = currentLights[p]._vf.attenuation.toGL();
					sp['light'+p+'_Location']         = light_transform.multMatrixPnt(currentLights[p]._vf.location).toGL();
					sp['light'+p+'_Radius']           = currentLights[p]._vf.radius;
					sp['light'+p+'_BeamWidth']        = 0.0;
					sp['light'+p+'_CutOffAngle']      = 0.0;
					sp['light'+p+'_ShadowIntensity']  = currentLights[p]._vf.shadowIntensity;
					sp['light'+p+'_ShadowOffset']	  = Math.max(0.0,Math.min(1.0,currentLights[p]._vf.shadowOffset));
				}
				else if(x3dom.isa(currentLights[p], x3dom.nodeTypes.SpotLight))
				{
					sp['light'+p+'_Type']             = 2.0;
					sp['light'+p+'_On']               = (currentLights[p]._vf.on) ? 1.0 : 0.0;
					sp['light'+p+'_Direction']        = light_transform.multMatrixVec(currentLights[p]._vf.direction).toGL();
					sp['light'+p+'_Attenuation']      = currentLights[p]._vf.attenuation.toGL();
					sp['light'+p+'_Location']         = light_transform.multMatrixPnt(currentLights[p]._vf.location).toGL();
					sp['light'+p+'_Radius']           = currentLights[p]._vf.radius;
					sp['light'+p+'_BeamWidth']        = currentLights[p]._vf.beamWidth;
					sp['light'+p+'_CutOffAngle']      = currentLights[p]._vf.cutOffAngle;
					sp['light'+p+'_ShadowIntensity']  = currentLights[p]._vf.shadowIntensity;
					sp['light'+p+'_ShadowCascades']   = currentLights[p]._vf.shadowCascades;
					sp['light'+p+'_ShadowOffset']     = Math.max(0.0,Math.min(1.0,currentLights[p]._vf.shadowOffset));
				}
			}
		
			gl.drawArrays(gl.TRIANGLES,0,6);

			//cleanup
            var nk = shadowIndex + 1;
			for (k=0; k<nk; k++) {
				gl.activeTexture(gl.TEXTURE0 + k);
				gl.bindTexture(gl.TEXTURE_2D, null);	
			} 
			gl.disableVertexAttribArray(sp.position);
		}

        this.stateManager.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
    };
	
	/*****************************************************************************
    * Blur texture associated with given fbo
    *****************************************************************************/	
	Context.prototype.blurTex = function(gl, scene, targetFbo, filterSize)
    {
		if (filterSize <= 0)
            return;
		else if (filterSize < 5)
			filterSize = 3;
		else if (filterSize < 7)
			filterSize = 5;
		else
            filterSize = 7;
		
		//first pass (horizontal blur), result stored in fboBlur
		var width = targetFbo.width;
		var height = targetFbo.height;
		var fboBlur = null;
		
		for (var i=0, n=scene._webgl.fboBlur.length; i<n; i++)
			if (height == scene._webgl.fboBlur[i].height) {
                fboBlur = scene._webgl.fboBlur[i];
                break; // THINKABOUTME
            }

		this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, fboBlur.fbo);
		this.stateManager.viewport(0, 0, width, height);
		
		this.stateManager.enable(gl.BLEND);
		this.stateManager.blendFunc(gl.ONE, gl.ZERO);
		this.stateManager.disable(gl.CULL_FACE);
		this.stateManager.disable(gl.DEPTH_TEST);
		
		gl.clearColor(1.0, 1.0, 1.0, 0.0);
		gl.clearDepth(1.0);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		
		var sp = this.cache.getShader(gl, x3dom.shader.BLUR);

        this.stateManager.useProgram(sp);
		
		//initialize Data for post processing
		gl.bindBuffer(gl.ARRAY_BUFFER, scene._webgl.ppBuffer);
		gl.vertexAttribPointer(sp.position, 2, gl.FLOAT, false, 0, 0);
		gl.enableVertexAttribArray(sp.position);
		
		sp.pixelSizeHor = 1.0/width;
		sp.pixelSizeVert = 1.0/height;
		sp.filterSize = filterSize;
		sp.horizontal = true;
		
		sp.texture = 0;
		
		//bind texture 
		gl.activeTexture(gl.TEXTURE0);
		gl.bindTexture(gl.TEXTURE_2D, targetFbo.tex);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
		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.drawArrays(gl.TRIANGLES,0,6);
		
		//second pass (vertical blur), result stored in targetFbo
		this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, targetFbo.fbo);
		
		gl.clearColor(1.0, 1.0, 1.0, 0.0);
		gl.clearDepth(1.0);
		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
		
		sp.horizontal = false;
		
		gl.activeTexture(gl.TEXTURE0);
		gl.bindTexture(gl.TEXTURE_2D, fboBlur.tex);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
		gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
		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.drawArrays(gl.TRIANGLES,0,6);

		//cleanup
		gl.activeTexture(gl.TEXTURE0);
		gl.bindTexture(gl.TEXTURE_2D, null);
		gl.disableVertexAttribArray(sp.position);
        gl.flush();

        this.stateManager.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, null);
		this.stateManager.viewport(0, 0, this.canvas.width, this.canvas.height);
	};

    Context.prototype.setVertexAttribPointerPosition = function(gl, shape, q6, q)
    {
        var sp = shape._webgl.shader;
        if (sp.position !== undefined && shape._webgl.buffers[q6 + 1])
        {
            var s_geo = shape._cf.geometry.node;

            gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[q6 + 1]);
            gl.vertexAttribPointer(sp.position,
                s_geo._mesh._numPosComponents, shape._webgl.coordType, false,
                shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
            gl.enableVertexAttribArray(sp.position);

        }
    };

    Context.prototype.setVertexAttribPointerNormal = function(gl, shape, q6, q)
    {
        var sp = shape._webgl.shader;
        if (sp.normal !== undefined && shape._webgl.buffers[q6 + 2])
        {
            var s_geo = shape._cf.geometry.node;

            gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[q6 + 2]);
            gl.vertexAttribPointer(sp.normal,
                s_geo._mesh._numNormComponents, shape._webgl.normalType, false,
                shape._normalStrideOffset[0], shape._normalStrideOffset[1]);
            gl.enableVertexAttribArray(sp.normal);

        }
    };

    Context.prototype.setVertexAttribPointerTexCoord = function(gl, shape, q6, q)
    {
        var sp = shape._webgl.shader;
        if (sp.texcoord !== undefined && shape._webgl.buffers[q6 + 3])
        {
            var s_geo = shape._cf.geometry.node;

            gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[q6 + 3]);
            gl.vertexAttribPointer(sp.texcoord,
                s_geo._mesh._numTexComponents, shape._webgl.texCoordType, false,
                shape._texCoordStrideOffset[0], shape._texCoordStrideOffset[1]);
            gl.enableVertexAttribArray(sp.texcoord);

        }
    };

    Context.prototype.setVertexAttribPointerColor = function(gl, shape, q6, q)
    {
        var sp = shape._webgl.shader;
        if (sp.color !== undefined && shape._webgl.buffers[q6 + 4])
        {
            var s_geo = shape._cf.geometry.node;

            gl.bindBuffer(gl.ARRAY_BUFFER, shape._webgl.buffers[q6 + 4]);
            gl.vertexAttribPointer(sp.color,
                s_geo._mesh._numColComponents, shape._webgl.colorType, false,
                shape._colorStrideOffset[0], shape._colorStrideOffset[1]);
            gl.enableVertexAttribArray(sp.color);
        }
    };

    return setupContext;

})();

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */

/// NodeNameSpace constructor
x3dom.NodeNameSpace = function (name, document) {
    this.name = name;
    this.doc = document;
    this.baseURL = "";
    this.defMap = {};
    this.parent = null;
    this.childSpaces = [];
};

x3dom.NodeNameSpace.prototype.addNode = function (node, name) {
    this.defMap[name] = node;
    node._nameSpace = this;
};

x3dom.NodeNameSpace.prototype.removeNode = function (name) {
    var node = name ? this.defMap[name] : null;
    if (node) {
        delete this.defMap[name];
        node._nameSpace = null;
    }
};

x3dom.NodeNameSpace.prototype.getNamedNode = function (name) {
    return this.defMap[name];
};

x3dom.NodeNameSpace.prototype.getNamedElement = function (name) {
    var node = this.defMap[name];
    return (node ? node._xmlNode : null);
};

x3dom.NodeNameSpace.prototype.addSpace = function (space) {
    this.childSpaces.push(space);
    space.parent = this;
};

x3dom.NodeNameSpace.prototype.removeSpace = function (space) {
    space.parent = null;
    for (var it=0; it<this.childSpaces.length; it++) {
        if (this.childSpaces[it] == space) {
            this.childSpaces.splice(it, 1);
        }
    }
};

x3dom.NodeNameSpace.prototype.setBaseURL = function (url) {
    var i = url.lastIndexOf ("/");
    this.baseURL = (i >= 0) ? url.substr(0,i+1) : "";

    x3dom.debug.logInfo("setBaseURL: " + this.baseURL);
};

x3dom.NodeNameSpace.prototype.getURL = function (url) {
    if (url === undefined || !url.length) {
        return "";
    }
    else {
        return ((url[0] === '/') || (url.indexOf(":") >= 0)) ? url : (this.baseURL + url);
    }
};

// helper to check an element's attribute
x3dom.hasElementAttribute = function(attrName)
{
    var ok = this.__hasAttribute(attrName);
    if (!ok && attrName) {
        ok = this.__hasAttribute(attrName.toLowerCase());
    }
    return ok;
};

// helper to get an element's attribute
x3dom.getElementAttribute = function(attrName)
{
    var attrib = this.__getAttribute(attrName);
    if (!attrib && attrib != "" && attrName) {
        attrib = this.__getAttribute(attrName.toLowerCase());
    }

    if (attrib || !this._x3domNode) {
        return attrib;
    }
    else {
        return this._x3domNode._vf[attrName];
    }
};

// helper to set an element's attribute
x3dom.setElementAttribute = function(attrName, newVal)
{
    //var prevVal = this.getAttribute(attrName);
    this.__setAttribute(attrName, newVal);
    //newVal = this.getAttribute(attrName);

    var x3dNode = this._x3domNode;
    if (x3dNode) {
        x3dNode.updateField(attrName, newVal);
        x3dNode._nameSpace.doc.needRender = true;
    }
};

/**
 * Returns the value of the field with the given name.
 * The value is returned as an object of the corresponding field type.
 *
 * @param {String} fieldName - the name of the field
 */
x3dom.getFieldValue = function(fieldName)
{
    var x3dNode = this._x3domNode;

    if (x3dNode && (x3dNode._vf[fieldName] !== undefined)) {

        var fieldValue = x3dNode._vf[fieldName];

        if(fieldValue instanceof Object && 'copy' in fieldValue)
        {
            return x3dNode._vf[fieldName].copy();
        }
        else
        {
            //f.i. SFString SFBool aren't objects
            return x3dNode._vf[fieldName];
        }
    }

    return null;
};


/**
 * Sets the value of the field with the given name to the given value.
 * The value is specified as an object of the corresponding field type.
 *
 * @param {String} fieldName  - the name of the field where the value should be set
 * @param {String} fieldvalue - the new field value
 */
x3dom.setFieldValue = function(fieldName, fieldvalue) {
    var x3dNode = this._x3domNode;
    if (x3dNode && (x3dNode._vf[fieldName] !== undefined)) {

        // SF/MF object types are cloned based on a copy function
        if(fieldvalue instanceof Object && 'copy' in fieldvalue)
        {
            x3dNode._vf[fieldName] = fieldvalue.copy();
        }
        //f.i. SFString SFBool aren't objects
        else
            x3dNode._vf[fieldName] = fieldvalue;

        x3dNode.fieldChanged(fieldName);
        x3dNode._nameSpace.doc.needRender = true;
    }
};


/**
 * Returns the field object of the field with the given name.
 * The returned object is no copy, but instead a reference to X3DOM's internal field object.
 * Changes to this object should be committed using the returnFieldRef function.
 * Note: this only works for fields with pointer types such as MultiFields!
 *
 * @param {String} fieldName - the name of the field
 */
x3dom.requestFieldRef = function(fieldName)
{
    var x3dNode = this._x3domNode;
    if (x3dNode && x3dNode._vf[fieldName])
    {
        return x3dNode._vf[fieldName];
    }

    return null;
};


/**
 * Commits all changes made to the internal field object of the field with the given name.
 * This must be done in order to notify X3DOM to process all related changes internally.
 *
 * @param {String} fieldName - the name of the field
 */
x3dom.releaseFieldRef = function(fieldName)
{
    var x3dNode = this._x3domNode;
    if (x3dNode && x3dNode._vf[fieldName])
    {
        x3dNode.fieldChanged(fieldName);
        x3dNode._nameSpace.doc.needRender = true;
    }
};


x3dom.NodeNameSpace.prototype.setupTree = function (domNode, parent) {
    var n = null;

    parent = parent || null;

    if (x3dom.isX3DElement(domNode)) {

        // return if it is already initialized
        if (domNode._x3domNode) {
            x3dom.debug.logWarning('Tree is already initialized');
            return null;
        }
        
        // workaround since one cannot find out which handlers are registered
        if ( (domNode.tagName !== undefined) &&
            (!domNode.__addEventListener) && (!domNode.__removeEventListener) )
        {
            // helper to track an element's listeners
            domNode.__addEventListener = domNode.addEventListener;
            domNode.addEventListener = function(type, func, phase) {
                if (!this._x3domNode._listeners[type]) {
                    this._x3domNode._listeners[type] = [];
                }
                this._x3domNode._listeners[type].push(func);

                //x3dom.debug.logInfo('addEventListener for ' + this.tagName + ".on" + type);
                this.__addEventListener(type, func, phase);
            };

            domNode.__removeEventListener = domNode.removeEventListener;
            domNode.removeEventListener = function(type, func, phase) {
                var list = this._x3domNode._listeners[type];
                if (list) {
                    for (var it=0; it<list.length; it++) {
                        if (list[it] == func) {
                            list.splice(it, 1);
                            //x3dom.debug.logInfo('removeEventListener for ' +
                            //                    this.tagName + ".on" + type);
                        }
                    }
                }
                this.__removeEventListener(type, func, phase);
            };
        }

        // TODO (?): dynamic update of USE attribute during runtime
        if (domNode.hasAttribute('USE') || domNode.hasAttribute('use'))
        {
            //fix usage of lowercase 'use'
            if (!domNode.hasAttribute('USE')) {
                domNode.setAttribute('USE', domNode.getAttribute('use'));
            }

            n = this.defMap[domNode.getAttribute('USE')];
            if (!n) {
                var nsName = domNode.getAttribute('USE').split('__');

                if (nsName.length >= 2) {
                    var otherNS = this;
                    while (otherNS) {
                        if (otherNS.name == nsName[0])
                            n = otherNS.defMap[nsName[1]];
                        if (n)
                            otherNS = null;
                        else
                            otherNS = otherNS.parent;
                    }
                    if (!n) {
                        n = null;
                        x3dom.debug.logWarning('Could not USE: ' + domNode.getAttribute('USE'));
                    }
                }
            }
            if (n) {
                domNode._x3domNode = n;
            }
            return n;
        }
        else {
            // check and create ROUTEs
            if (domNode.localName.toLowerCase() === 'route') {
                var route = domNode;
                var fnAtt = route.getAttribute('fromNode') || route.getAttribute('fromnode');
                var tnAtt = route.getAttribute('toNode') || route.getAttribute('tonode');
                var fromNode = this.defMap[fnAtt];
                var toNode = this.defMap[tnAtt];
                if (! (fromNode && toNode)) {
                    x3dom.debug.logWarning("Broken route - can't find all DEFs for " + fnAtt + " -> " + tnAtt);
                }
                else {
                    //x3dom.debug.logInfo("ROUTE: from=" + fromNode._DEF + ", to=" + toNode._DEF);
                    fnAtt = route.getAttribute('fromField') || route.getAttribute('fromfield');
                    tnAtt = route.getAttribute('toField') || route.getAttribute('tofield');
                    fromNode.setupRoute(fnAtt, toNode, tnAtt);
                    // Store reference to namespace for being able to remove route later on
                    route._nodeNameSpace = this;
                }
                return null;
            }

            //attach X3DOM's custom field interface functions
            domNode.requestFieldRef = x3dom.requestFieldRef;
            domNode.releaseFieldRef = x3dom.releaseFieldRef;
            domNode.getFieldValue = x3dom.getFieldValue;
            domNode.setFieldValue = x3dom.setFieldValue;

            // find the NodeType for the given dom-node
            var nodeType = x3dom.nodeTypesLC[domNode.localName.toLowerCase()];
            if (nodeType === undefined) {
                x3dom.debug.logWarning("Unrecognised X3D element &lt;" + domNode.localName + "&gt;.");
            }
            else {
                //active workaround for missing DOMAttrModified support
                if ( (x3dom.userAgentFeature.supportsDOMAttrModified === false)
                      && (domNode instanceof Element) ) {
                    if (domNode.setAttribute && !domNode.__setAttribute) {
                        domNode.__setAttribute = domNode.setAttribute;
                        domNode.setAttribute = x3dom.setElementAttribute;
                    }

                    if (domNode.getAttribute && !domNode.__getAttribute) {
                        domNode.__getAttribute = domNode.getAttribute;
                        domNode.getAttribute = x3dom.getElementAttribute;
                    }

                    if (domNode.hasAttribute && !domNode.__hasAttribute) {
                        domNode.__hasAttribute = domNode.hasAttribute;
                        domNode.hasAttribute = x3dom.hasElementAttribute;
                    }
                }

                // create x3domNode
                var ctx = {
                    doc: this.doc,
                    xmlNode: domNode,
                    nameSpace: this
                };
                n = new nodeType(ctx);

                // find and store/link _DEF name
                if (domNode.hasAttribute('DEF')) {
                    n._DEF = domNode.getAttribute('DEF');
                    this.defMap[n._DEF] = n;
                }
                else {
                    if (domNode.hasAttribute('id')) {
                        n._DEF = domNode.getAttribute('id');
                        this.defMap[n._DEF] = n;
                    }
                }
                
                // add experimental highlighting functionality
                if (domNode.highlight === undefined) 
                {
                    domNode.highlight = function(enable, colorStr) {
                        var color = x3dom.fields.SFColor.parse(colorStr);
                        this._x3domNode.highlight(enable, color);
                        this._x3domNode._nameSpace.doc.needRender = true;
                    };
                }

                // link both DOM-Node and Scene-graph-Node
                n._xmlNode = domNode;
                domNode._x3domNode = n;

                // call children
                var that = this;
                Array.forEach ( domNode.childNodes, function (childDomNode) {
                    var c = that.setupTree(childDomNode, n);
                    if (c) {
                        n.addChild(c, childDomNode.getAttribute("containerField"));
                    }
                } );

                n.nodeChanged();
                return n;
            }
        }
    }
    else if (domNode.localName)
    {
        if ( parent && domNode.localName.toLowerCase() == "x3dommetagroup" )
        {
            Array.forEach ( domNode.childNodes, function (childDomNode) {
                var c = this.setupTree(childDomNode, parent);
                if (c) {
                    parent.addChild(c, childDomNode.getAttribute("containerField"));
                }
            }.bind(this) );
        }
        else
        {
            // be nice to users who use nodes not (yet) known to the system
            x3dom.debug.logWarning("Unrecognised X3D element &lt;" + domNode.localName + "&gt;.");
            n = null;
        }
    }

    return n;
};

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### X3DNode ###
x3dom.registerNodeType(
    "X3DNode",
    "Core",
    defineClass(null, 
        /**
         * Constructor for X3DNode
         * @constructs x3dom.nodeTypes.X3DNode
         * @x3d 3.3
         * @component Core
         * @status experimental
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all nodes in the X3D system.
         */
        function (ctx) {
            // reference to DOM element
            this._xmlNode = null;

            // holds a link to the node name
            this._DEF = null;

            // links the nameSpace
            this._nameSpace = (ctx && ctx.nameSpace) ? ctx.nameSpace : null;

            // holds all value fields (e.g. SFFloat, MFVec3f, ...)
            this._vf = {};
            this._vfFieldTypes = {};

            // holds all child fields ( SFNode and MFNode )
            this._cf = {};
            this._cfFieldTypes = {};

            this._fieldWatchers = {};
            this._routes = {};

            this._listeners = {};

            this._parentNodes = [];

            // FIXME; should be removed and handled by _cf methods
            this._childNodes = [];


            /**
             * Field to add metadata information
             * @var {x3dom.fields.SFNode} metadata
             * @memberof x3dom.nodeTypes.X3DNode
             * @initvalue X3DMetadataObject
             * @field x3d
             * @instance
             */
            this.addField_SFNode('metadata', x3dom.nodeTypes.X3DMetadataObject);
        
        },
        {
            type: function () {
                return this.constructor;
            },

            typeName: function () {
                return this.constructor._typeName;
            },

            addChild: function (node, containerFieldName) {
                if (node) {
                    var field = null;
                    if (containerFieldName) {
                        field = this._cf[containerFieldName];
                    }
                    else {
                        for (var fieldName in this._cf) {
                            if (this._cf.hasOwnProperty(fieldName)) {
                                var testField = this._cf[fieldName];
                                if (x3dom.isa(node,testField.type)) {
                                    field = testField;
                                    break;
                                }
                            }
                        }
                    }
                    if (field && field.addLink(node)) {
                        node._parentNodes.push(this);
                        this._childNodes.push(node);
                        node.parentAdded(this);
                        return true;
                    }
                }
                return false;
            },

            removeChild: function (node) {
                if (node) {
                    for (var fieldName in this._cf) {
                        if (this._cf.hasOwnProperty(fieldName)) {
                            var field = this._cf[fieldName];
                            if (field.rmLink(node)) {
                                for (var i = node._parentNodes.length - 1; i >= 0; i--) {
                                    if (node._parentNodes[i] === this) {
                                        node._parentNodes.splice(i, 1);
                                        node.parentRemoved(this);
                                    }
                                }
                                for (var j = this._childNodes.length - 1; j >= 0; j--) {
                                    if (this._childNodes[j] === node) {
                                        node.onRemove();
                                        this._childNodes.splice(j, 1);
                                        return true;
                                    }
                                }
                            }
                        }
                    }
                }
                return false;
            },

            onRemove: function() {
                // to be overwritten by concrete classes
            },

            parentAdded: function(parent) {
                // to be overwritten by concrete classes
            },

            parentRemoved: function(parent) {
                // attention: overwritten by concrete classes
                for (var i=0, n=this._childNodes.length; i<n; i++) {
                    if (this._childNodes[i]) {
                        this._childNodes[i].parentRemoved(this);
                    }
                }
            },

            getCurrentTransform: function () {
                if (this._parentNodes.length >= 1) {
                    return this.transformMatrix(this._parentNodes[0].getCurrentTransform());
                }
                else {
                    return x3dom.fields.SFMatrix4f.identity();
                }
            },

            transformMatrix: function (transform) {
                return transform;
            },

            getVolume: function () {
                //x3dom.debug.logWarning("Called getVolume for unbounded node!");
                return null;
            },

            invalidateVolume: function() {
                // overwritten
            },

            invalidateCache: function() {
                // overwritten
            },

            volumeValid: function() {
                return false;
            },

            // Collects all objects to be drawn
            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes) {
                // explicitly do nothing on collect traversal for (most) nodes
            },

            highlight: function(enable, color)
            {
                if (this._vf.hasOwnProperty("diffuseColor"))
                {
                    if (enable) {
                        if (this._actDiffuseColor === undefined) {
                            this._actDiffuseColor = new x3dom.fields.SFColor();
                            this._highlightOn = false;
                        }

                        if (!this._highlightOn) {
                            this._actDiffuseColor.setValues(this._vf.diffuseColor);
                            this._highlightOn = true;
                        }
                        this._vf.diffuseColor.setValues(color);
                    }
                    else {
                        if (this._actDiffuseColor !== undefined) {
                            this._vf.diffuseColor.setValues(this._actDiffuseColor);
                            this._highlightOn = false;
                            // new/delete every frame can be very slow
                            // but prevent from copying if called not only on change
                            delete this._actDiffuseColor;
                        }
                    }
                }

                for (var i=0, n=this._childNodes.length; i<n; i++)
                {
                    if (this._childNodes[i])
                        this._childNodes[i].highlight(enable, color);
                }
            },

            findX3DDoc: function () {
                return this._nameSpace.doc;
            },

            doIntersect: function(line) {
                var isect = false;
                for (var i=0; i<this._childNodes.length; i++) {
                    if (this._childNodes[i]) {
                        isect = this._childNodes[i].doIntersect(line) || isect;
                    }
                }
                return isect;
            },

            postMessage: function (field, msg) {
                // TODO: timestamps and stuff
                this._vf[field] = msg;  // FIXME; _cf!!!
                var listeners = this._fieldWatchers[field];

                var that = this;
                if (listeners) {
                    Array.forEach(listeners, function (l) { l.call(that, msg); });
                }

                //for Web-style access to the output data of ROUTES, provide a callback function
                var eventObject = {
                    target: that._xmlNode,
                    type: "outputchange",   // event only called onxxx if used as old-fashioned attribute
                    fieldName: field,
                    value: msg
                };

                this.callEvtHandler("onoutputchange", eventObject);
            },

            // method for handling field updates
            updateField: function (field, msg) {
                var f = this._vf[field];

                if (f === undefined) {
                    for (var key in this._vf) {
                        if (key.toLowerCase() == field) {
                            field = key;
                            f = this._vf[field];
                            break;
                        }
                    }

                    var pre = "set_";
                    if (f === undefined && field.indexOf(pre) == 0) {
                        var fieldName = field.substr(pre.length, field.length - 1);
                        if (this._vf[fieldName] !== undefined) {
                            field = fieldName;
                            f = this._vf[field];
                        }
                    }
                    if (f === undefined) {
                        f = null;
                        this._vf[field] = f;
                    }
                }

                if (f !== null) {
                    try {
                        this._vf[field].setValueByStr(msg);
                    }
                    catch (exc1) {
                        try {
                            switch ((typeof(this._vf[field])).toString()) {
                                case "number":
                                    if (typeof(msg) == "number")
                                        this._vf[field] = msg;
                                    else
                                        this._vf[field] = +msg;
                                    break;
                                case "boolean":
                                    if (typeof(msg) == "boolean")
                                        this._vf[field] = msg;
                                    else
                                        this._vf[field] = (msg.toLowerCase() == "true");
                                    break;
                                case "string":
                                    this._vf[field] = msg;
                                    break;
                            }
                        }
                        catch (exc2) {
                            x3dom.debug.logError("updateField: setValueByStr() NYI for " + typeof(f));
                        }
                    }

                    // TODO: eval fieldChanged for all nodes!
                    this.fieldChanged(field);
                }
            },

            setupRoute: function (fromField, toNode, toField) {
                var pos;
                var fieldName;
                var pre = "set_", post = "_changed";

                // build correct fromField
                if (!this._vf[fromField]) {
                    pos = fromField.indexOf(pre);
                    if (pos === 0) {
                        fieldName = fromField.substr(pre.length, fromField.length - 1);
                        if (this._vf[fieldName]) {
                            fromField = fieldName;
                        }
                    } else {
                        pos = fromField.indexOf(post);
                        if (pos > 0) {
                            fieldName = fromField.substr(0, fromField.length - post.length);
                            if (this._vf[fieldName]) {
                                fromField = fieldName;
                            }
                        }
                    }
                }

                // build correct toField
                if (!toNode._vf[toField]) {
                    pos = toField.indexOf(pre);
                    if (pos === 0) {
                        fieldName = toField.substr(pre.length, toField.length - 1);
                        if (toNode._vf[fieldName]) {
                            toField = fieldName;
                        }
                    }
                    else {
                        pos = toField.indexOf(post);
                        if (pos > 0) {
                            fieldName = toField.substr(0, toField.length - post.length);
                            if (toNode._vf[fieldName]) {
                                toField = fieldName;
                            }
                        }
                    }
                }

                var where = this._DEF + "&" + fromField + "&" + toNode._DEF + "&" + toField;

                if (!this._routes[where]) {
                    if (!this._fieldWatchers[fromField]) {
                        this._fieldWatchers[fromField] = [];
                    }
                    this._fieldWatchers[fromField].push(
                        function (msg) {
                            toNode.postMessage(toField, msg);
                        }
                    );

                    if (!toNode._fieldWatchers[toField]) {
                        toNode._fieldWatchers[toField] = [];
                    }
                    toNode._fieldWatchers[toField].push(
                        // FIXME: THIS DOESN'T WORK FOR NODE (_cf) FIELDS
                        function (msg) {
                            toNode._vf[toField] = msg;
                            toNode.fieldChanged(toField);
                        }
                    );

                    // store this route to be able to delete it
                    this._routes[where] = {
                        from: this._fieldWatchers[fromField].length - 1,
                        to: toNode._fieldWatchers[toField].length - 1
                    };
                }
            },

            removeRoute: function (fromField, toNode, toField) {
                var pos;
                var fieldName;
                var pre = "set_", post = "_changed";

                // again, build correct fromField
                if (!this._vf[fromField]) {
                    pos = fromField.indexOf(pre);
                    if (pos === 0) {
                        fieldName = fromField.substr(pre.length, fromField.length - 1);
                        if (this._vf[fieldName]) {
                            fromField = fieldName;
                        }
                    } else {
                        pos = fromField.indexOf(post);
                        if (pos > 0) {
                            fieldName = fromField.substr(0, fromField.length - post.length);
                            if (this._vf[fieldName]) {
                                fromField = fieldName;
                            }
                        }
                    }
                }

                // again, build correct toField
                if (!toNode._vf[toField]) {
                    pos = toField.indexOf(pre);
                    if (pos === 0) {
                        fieldName = toField.substr(pre.length, toField.length - 1);
                        if (toNode._vf[fieldName]) {
                            toField = fieldName;
                        }
                    }
                    else {
                        pos = toField.indexOf(post);
                        if (pos > 0) {
                            fieldName = toField.substr(0, toField.length - post.length);
                            if (toNode._vf[fieldName]) {
                                toField = fieldName;
                            }
                        }
                    }
                }

                // finally, delete route
                var where = this._DEF + "&" + fromField + "&" + toNode._DEF + "&" + toField;

                if (this._routes[where]) {
                    this._fieldWatchers[fromField].splice(this._routes[where].from, 1);
                    toNode._fieldWatchers[toField].splice(this._routes[where].to, 1);

                    delete this._routes[where];
                }
            },

            fieldChanged: function (fieldName) {
                // to be overwritten by concrete classes
            },

            nodeChanged: function () {
                // to be overwritten by concrete classes
            },

            callEvtHandler: function(eventType, event) {
                var node = this;

                if (!node._xmlNode) {
                    return event.cancelBubble;
                }

                try {
                    var attrib = node._xmlNode[eventType];
                    event.target = node._xmlNode;

                    if (typeof(attrib) === "function") {
                        attrib.call(node._xmlNode, event);
                    }
                    else {
                        var funcStr = node._xmlNode.getAttribute(eventType);
                        var func = new Function('event', funcStr);
                        func.call(node._xmlNode, event);
                    }

                    var list = node._listeners[event.type];
                    if (list) {
                        for (var it=0; it<list.length; it++) {
                            list[it].call(node._xmlNode, event);
                        }
                    }
                }
                catch(ex) {
                    x3dom.debug.logException(ex);
                }

                return event.cancelBubble;
            },

            initSetter: function (xmlNode, name) {
                if (!xmlNode || !name)
                    return;

                var nameLC = name.toLowerCase();
                if (xmlNode.__defineSetter__ && xmlNode.__defineGetter__) {
                    xmlNode.__defineSetter__(name, function(value) {
                        xmlNode.setAttribute(name, value);
                    });
                    xmlNode.__defineGetter__(name, function() {
                        return xmlNode.getAttribute(name);
                    });
                    if (nameLC != name) {
                        xmlNode.__defineSetter__(nameLC, function(value) {
                            xmlNode.setAttribute(name, value);
                        });
                        xmlNode.__defineGetter__(nameLC, function() {
                            return xmlNode.getAttribute(name);
                        });
                    }
                }
                else {
                    // IE has no __define[G|S]etter__ !!!
                    Object.defineProperty(xmlNode, name, {
                        set: function(value) {
                            xmlNode.setAttribute(name, value);
                        },
                        get: function() {
                            return xmlNode.getAttribute(name);
                        },
                        configurable: true,
                        enumerable: true
                    });
                }

                if (this._vf[name] &&
                    !xmlNode.attributes[name] && !xmlNode.attributes[name.toLowerCase()]) {
                    var str = "";
                    try {
                        if (this._vf[name].toGL)
                            str = this._vf[name].toGL().toString();
                        else
                            str = this._vf[name].toString();
                    }
                    catch (e) {
                        str = this._vf[name].toString();
                    }
                    if (!str) {
                        str = "";
                    }
                    xmlNode.setAttribute(name, str);
                }
            },

            // single fields
            addField_SFInt32: function (ctx, name, n) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    parseInt(ctx.xmlNode.getAttribute(name),10) : n;

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFInt32";
            },

            addField_SFFloat: function (ctx, name, n) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    +ctx.xmlNode.getAttribute(name) : n;

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFFloat";
            },

            addField_SFDouble: function (ctx, name, n) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    +ctx.xmlNode.getAttribute(name) : n;

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFDouble";
            },

            addField_SFTime: function (ctx, name, n) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    +ctx.xmlNode.getAttribute(name) : n;

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFTime";
            },

            addField_SFBool: function (ctx, name, n) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    ctx.xmlNode.getAttribute(name).toLowerCase() === "true" : n;

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFBool";
            },

            addField_SFString: function (ctx, name, n) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    ctx.xmlNode.getAttribute(name) : n;

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFString";
            },

            addField_SFColor: function (ctx, name, r, g, b) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.SFColor.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.SFColor(r, g, b);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFColor";
            },

            addField_SFColorRGBA: function (ctx, name, r, g, b, a) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.SFColorRGBA.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.SFColorRGBA(r, g, b, a);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFColorRGBA";
            },

            addField_SFVec2f: function (ctx, name, x, y) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.SFVec2f.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.SFVec2f(x, y);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFVec2f";
            },

            addField_SFVec3f: function (ctx, name, x, y, z) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.SFVec3f.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.SFVec3f(x, y, z);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFVec3f";
            },

            addField_SFVec4f: function (ctx, name, x, y, z, w) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.SFVec4f.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.SFVec4f(x, y, z, w);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFVec4f";
            },

            addField_SFVec3d: function(ctx, name, x, y, z) {
                this.addField_SFVec3f(ctx, name, x, y, z);
                this._vfFieldTypes[name] = "SFVec3d";
            },

            addField_SFRotation: function (ctx, name, x, y, z, a) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.Quaternion.parseAxisAngle(ctx.xmlNode.getAttribute(name)) :
                    x3dom.fields.Quaternion.axisAngle(new x3dom.fields.SFVec3f(x, y, z), a);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFRotation";
            },

            addField_SFMatrix4f: function (ctx, name, _00, _01, _02, _03,
                                           _10, _11, _12, _13,
                                           _20, _21, _22, _23,
                                           _30, _31, _32, _33) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.SFMatrix4f.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.SFMatrix4f(_00, _01, _02, _03,
                        _10, _11, _12, _13,
                        _20, _21, _22, _23,
                        _30, _31, _32, _33);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFMatrix4f";
            },

            addField_SFImage: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.SFImage.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.SFImage(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "SFImage";
            },

            // multi fields
            addField_MFString: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFString.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFString(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFString";
            },

            addField_MFBoolean: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFBoolean.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFBoolean(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFBoolean";
            },

            addField_MFInt32: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFInt32.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFInt32(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFInt32";
            },

            addField_MFFloat: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFFloat.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFFloat(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFFloat";
            },

            addField_MFDouble: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFFloat.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFFloat(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFDouble";
            },

            addField_MFColor: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFColor.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFColor(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFColor";
            },

            addField_MFColorRGBA: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFColorRGBA.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFColorRGBA(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFColorRGBA";
            },

            addField_MFVec2f: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFVec2f.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFVec2f(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFVec2f";
            },

            addField_MFVec3f: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFVec3f.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFVec3f(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFVec3f";
            },

            addField_MFVec3d: function (ctx, name, def) {
                this.addField_MFVec3f(ctx, name, def);
                this._vfFieldTypes[name] = "MFVec3d";
            },

            addField_MFRotation: function (ctx, name, def) {
                this._vf[name] = ctx && ctx.xmlNode && ctx.xmlNode.hasAttribute(name) ?
                    x3dom.fields.MFRotation.parse(ctx.xmlNode.getAttribute(name)) :
                    new x3dom.fields.MFRotation(def);

                if (ctx && ctx.xmlNode) { this.initSetter(ctx.xmlNode, name); }
                this._vfFieldTypes[name] = "MFRotation";
            },

            // child node fields
            addField_SFNode: function (name, type) {
                this._cf[name] = new x3dom.fields.SFNode(type);
                this._cfFieldTypes[name] = "SFNode";
            },
            addField_MFNode: function (name, type) {
                this._cf[name] = new x3dom.fields.MFNode(type);
                this._cfFieldTypes[name] = "MFNode";
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DMetadataObject ### */
x3dom.registerNodeType(
    "X3DMetadataObject",
    "Core",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for X3DMetadataObject
         * @constructs x3dom.nodeTypes.X3DMetadataObject
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract interface is the basis for all metadata nodes. The interface is inherited by
         * all metadata nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DMetadataObject.superClass.call(this, ctx);


            /**
             * The specification of a non-empty value for the name field is required.
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.X3DMetadataObject
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");

            /**
             * The specification of the reference field is optional. If provided, it identifies the metadata standard
             * or other specification that defines the name field. If the reference field is not provided or is empty,
             * the meaning of the name field is considered implicit to the characters in the string.
             * @var {x3dom.fields.SFString} reference
             * @memberof x3dom.nodeTypes.X3DMetadataObject
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'reference', "");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MetadataBoolean ### */
x3dom.registerNodeType(
    "MetadataBoolean",
    "Core",
    defineClass(x3dom.nodeTypes.X3DMetadataObject,
        
        /**
         * Constructor for MetadataBoolean
         * @constructs x3dom.nodeTypes.MetadataBoolean
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DMetadataObject
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The metadata provided by this node is contained in the Boolean values of the value field.
         */
        function (ctx) {
            x3dom.nodeTypes.MetadataBoolean.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.MFBoolean} value
             * @memberof x3dom.nodeTypes.MetadataBoolean
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFBoolean(ctx, 'value', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MetadataDouble ### */
x3dom.registerNodeType(
    "MetadataDouble",
    "Core",
    defineClass(x3dom.nodeTypes.X3DMetadataObject,
        
        /**
         * Constructor for MetadataDouble
         * @constructs x3dom.nodeTypes.MetadataDouble
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DMetadataObject
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The metadata provided by this node is contained in the double-precision floating point numbers of
         * the value field.
         */
        function (ctx) {
            x3dom.nodeTypes.MetadataDouble.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.MFDouble} value
             * @memberof x3dom.nodeTypes.MetadataDouble
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFDouble(ctx, 'value', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MetadataFloat ### */
x3dom.registerNodeType(
    "MetadataFloat",
    "Core",
    defineClass(x3dom.nodeTypes.X3DMetadataObject,
        
        /**
         * Constructor for MetadataFloat
         * @constructs x3dom.nodeTypes.MetadataFloat
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DMetadataObject
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The metadata provided by this node is contained in the single-precision floating point numbers of
         * the value field.
         */
        function (ctx) {
            x3dom.nodeTypes.MetadataFloat.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.MFFloat} value
             * @memberof x3dom.nodeTypes.MetadataFloat
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'value', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MetadataInteger ### */
x3dom.registerNodeType(
    "MetadataInteger",
    "Core",
    defineClass(x3dom.nodeTypes.X3DMetadataObject,
        
        /**
         * Constructor for MetadataInteger
         * @constructs x3dom.nodeTypes.MetadataInteger
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DMetadataObject
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The metadata provided by this node is contained in the integers of the value field.
         */
        function (ctx) {
            x3dom.nodeTypes.MetadataInteger.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.MFInt32} value
             * @memberof x3dom.nodeTypes.MetadataInteger
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'value', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MetadataSet ### */
x3dom.registerNodeType(
    "MetadataSet",
    "Core",
    defineClass(x3dom.nodeTypes.X3DMetadataObject,
        
        /**
         * Constructor for MetadataSet
         * @constructs x3dom.nodeTypes.MetadataSet
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DMetadataObject
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The metadata provided by this node is contained in the metadata nodes of the value field.
         */
        function (ctx) {
            x3dom.nodeTypes.MetadataSet.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.MFNode} value
             * @memberof x3dom.nodeTypes.MetadataSet
             * @initvalue x3dom.nodeTypes.X3DMetadataObject
             * @field x3d
             * @instance
             */
            this.addField_MFNode('value', x3dom.nodeTypes.X3DMetadataObject);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MetadataString ### */
x3dom.registerNodeType(
    "MetadataString",
    "Core",
    defineClass(x3dom.nodeTypes.X3DMetadataObject,
        
        /**
         * Constructor for MetadataString
         * @constructs x3dom.nodeTypes.MetadataString
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DMetadataObject
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The metadata provided by this node is contained in the strings of the value field.
         */
        function (ctx) {
            x3dom.nodeTypes.MetadataString.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.MFString} value
             * @memberof x3dom.nodeTypes.MetadataString
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'value', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Field ### */
x3dom.registerNodeType(
    "Field",
    "Core",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for Field
         * @constructs x3dom.nodeTypes.Field
         * @x3d x.x
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Class represents a field of a node containing name, type and value
         */
        function (ctx) {
            x3dom.nodeTypes.Field.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.Field
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");

            /**
             *
             * @var {x3dom.fields.SFString} type
             * @memberof x3dom.nodeTypes.Field
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'type', "");

            /**
             *
             * @var {x3dom.fields.SFString} value
             * @memberof x3dom.nodeTypes.Field
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'value', "");
        
        },
        {
            fieldChanged: function(fieldName) {
                var that = this;
                if (fieldName === 'value') {
                    Array.forEach(this._parentNodes, function (node) {
                        node.fieldChanged(that._vf.name);
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DChildNode ### */
x3dom.registerNodeType(
    "X3DChildNode",
    "Core",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for X3DChildNode
         * @constructs x3dom.nodeTypes.X3DChildNode
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type indicates that the concrete nodes that are instantiated based on it may
         * be used in children, addChildren, and removeChildren fields.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DChildNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DBindableNode ### */
x3dom.registerNodeType(
    "X3DBindableNode",
    "Core",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DBindableNode
         * @constructs x3dom.nodeTypes.X3DBindableNode
         * @x3d 3.3
         * @component Core
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc X3DBindableNode is the abstract base type for all bindable children nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DBindableNode.superClass.call(this, ctx);


            /**
             * Pushes/pops the node on/from the top of the bindable stack
             * @var {x3dom.fields.SFBool} bind
             * @memberof x3dom.nodeTypes.X3DBindableNode
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'bind', false);

            /**
             * Description of the bindable node
             * @var {x3dom.fields.SFString} description
             * @memberof x3dom.nodeTypes.X3DBindableNode
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'description', "");

            /**
             *
             * @var {x3dom.fields.SFBool} isActive
             * @memberof x3dom.nodeTypes.X3DBindableNode
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'isActive', false);

            this._autoGen = (ctx && ctx.autoGen ? true : false);
            if (this._autoGen)
                this._vf.description = "default" + this.constructor.superClass._typeName;

            // Bindable stack to register node later on
            this._stack = null;
            this._bindAnimation = true;
        
        },
        {
            bind: function (value) {
                if (this._stack) {
                    if (value) {
                        this._stack.push (this);
                    }
                    else {
                        this._stack.pop  (this);
                    }
                }
                else {
                    x3dom.debug.logError ('No BindStack in ' + this.typeName() + 'Bindable');
                }
            },

            activate: function (prev) {
                this.postMessage('isActive', true);
                x3dom.debug.logInfo('activate ' + this.typeName() + 'Bindable ' +
                    this._DEF + '/' + this._vf.description);
            },

            deactivate: function (prev) {
                this.postMessage('isActive', false);
                x3dom.debug.logInfo('deactivate ' + this.typeName() + 'Bindable ' +
                    this._DEF + '/' + this._vf.description);
            },

            fieldChanged: function(fieldName) {
                if (fieldName.indexOf("bind") >= 0) {
                    this.bind(this._vf.bind);
                }
            },

            nodeChanged: function() {
                this._stack = this._nameSpace.doc._bindableBag.addBindable(this);
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DInfoNode ### */
x3dom.registerNodeType(
    "X3DInfoNode",
    "Core",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DInfoNode
         * @constructs x3dom.nodeTypes.X3DInfoNode
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for all nodes that contain only information without visual semantics.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DInfoNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### WorldInfo ### */
x3dom.registerNodeType(
    "WorldInfo",
    "Core",
    defineClass(x3dom.nodeTypes.X3DInfoNode,
        
        /**
         * Constructor for WorldInfo
         * @constructs x3dom.nodeTypes.WorldInfo
         * @x3d 3.3
         * @component Core
         * @status full
         * @extends x3dom.nodeTypes.X3DInfoNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The WorldInfo node contains information about the world. This node is strictly for documentation
         * purposes and has no effect on the visual appearance or behaviour of the world.
         */
        function (ctx) {
            x3dom.nodeTypes.WorldInfo.superClass.call(this, ctx);


            /**
             * The title field is intended to store the name or title of the world so that browsers can present this to
             * the user (perhaps in the window border).
             * @var {x3dom.fields.MFString} info
             * @memberof x3dom.nodeTypes.WorldInfo
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'info', []);

            /**
             * Information about the world can be stored in the info field, such as author information, copyright, and
             * usage instructions.
             * @var {x3dom.fields.SFString} title
             * @memberof x3dom.nodeTypes.WorldInfo
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'title', "");

            x3dom.debug.logInfo(this._vf.info);
            x3dom.debug.logInfo(this._vf.title);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### X3DSensorNode ###
x3dom.registerNodeType(
    "X3DSensorNode",
    "Core",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DSensorNode
         * @constructs x3dom.nodeTypes.X3DSensorNode
         * @x3d 3.3
         * @component Core
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all sensors.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DSensorNode.superClass.call(this, ctx);
        
		
            /**
             * Specifies whether this sensor is enabled. A disabled sensor does not produce any output.
             * @var {x3dom.fields.SFBool} enabled
             * @memberof x3dom.nodeTypes.X3DSensorNode
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'enabled', true);
			
			
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// deprecated, will be removed in 1.5
// ### Param ###
x3dom.registerNodeType(
    "Param",
    "Core",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for Param
         * @constructs x3dom.nodeTypes.Param
         * @x3d x.x
         * @component Core
         * @status experimental
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * DEPRECATED: Param element needs to be child of X3D element {@link http://x3dom.org/docs/latest/configuration.html}
         */
        function (ctx) {
            x3dom.nodeTypes.Param.superClass.call(this, ctx);

            x3dom.debug.logWarning('DEPRECATED: Param element needs to be child of X3D element '
                + '[<a href="http://x3dom.org/docs/latest/configuration.html">DOCS</a>]');
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DBoundedObject ### */
x3dom.registerNodeType(
    "X3DBoundedObject",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DBoundedObject
         * @constructs x3dom.nodeTypes.X3DBoundedObject
         * @x3d 3.3
         * @component Grouping
         * @status full
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the basis for all node types that have bounds specified as part of
         * the definition. The bboxCenter and bboxSize fields specify a bounding box that encloses the grouping node's
         * children. This is a hint that may be used for optimization purposes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DBoundedObject.superClass.call(this, ctx);


            /**
             * Flag to enable/disable rendering
             * @var {x3dom.fields.SFBool} render
             * @memberof x3dom.nodeTypes.X3DBoundedObject
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'render', true);

            /**
             * Center of the bounding box
             * @var {x3dom.fields.SFVec3f} bboxCenter
             * @memberof x3dom.nodeTypes.X3DBoundedObject
             * @initvalue 0,0,0
             * @range [-inf, inf]
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'bboxCenter', 0, 0, 0);

            /**
             * Size of the bounding box
             * @var {x3dom.fields.SFVec3f} bboxSize
             * @memberof x3dom.nodeTypes.X3DBoundedObject
             * @initvalue -1,-1,-1
             * @range [0, inf] or -1
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'bboxSize', -1, -1, -1);

            this._graph = {
                boundedNode:  this,    // backref to node object
                localMatrix:  x3dom.fields.SFMatrix4f.identity(),   // usually identity
                globalMatrix: null,    // new x3dom.fields.SFMatrix4f();
                volume:       new x3dom.fields.BoxVolume(),     // local bbox
                lastVolume:   new x3dom.fields.BoxVolume(),     // local bbox
                worldVolume:  new x3dom.fields.BoxVolume(),     // global bbox
                center:       new x3dom.fields.SFVec3f(0,0,0),  // center in eye coords
                coverage:     -1,       // currently approx. number of pixels on screen
                needCulling:  true      // to be able to disable culling per node
            };
        
        },
        {
            fieldChanged: function (fieldName) {
                // TODO; wait for sync traversal to invalidate en block
                if (this._vf.hasOwnProperty(fieldName)) {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            },

            nodeChanged: function () {
                // TODO; wait for sync traversal to invalidate en block
                this.invalidateVolume();
                //this.invalidateCache();
            },

            parentAdded: function(parent) {
                // some default behavior if not overwitten
                this.invalidateVolume();
                //this.invalidateCache();
            },

            getVolume: function()
            {
                var vol = this._graph.volume;

                if (!this.volumeValid() && this._vf.render)
                {
                    for (var i=0, n=this._childNodes.length; i<n; i++)
                    {
                        var child = this._childNodes[i];
                        // render could be undefined, but undefined != true
                        if (!child || child._vf.render !== true)
                            continue;

                        var childVol = child.getVolume();

                        if (childVol && childVol.isValid())
                            vol.extendBounds(childVol.min, childVol.max);
                    }
                }

                if ( !vol.equals( this._graph.lastVolume ) )
                {
                    this._graph.lastVolume = x3dom.fields.BoxVolume.copy( vol );

                    var event = {
                        target: this._xmlNode,
                        type: "volumechanged",   // event only called onxxx if used as old-fashioned attribute
                        volume: x3dom.fields.BoxVolume.copy( vol )
                    };

                    this.callEvtHandler("onvolumechanged", event);
                }

                return vol;
            },

            invalidateVolume: function()
            {
                var graph = this._graph;

                graph.volume.invalidate();

                // also clear cache
                graph.worldVolume.invalidate();
                graph.globalMatrix = null;

                // set parent volumes invalid, too
                for (var i=0, n=this._parentNodes.length; i<n; i++) {
                    var node = this._parentNodes[i];
                    if (node)
                        node.invalidateVolume();
                }
            },

            invalidateCache: function()
            {
                var graph = this._graph;

                //if (graph.volume.isValid() &&
                //    graph.globalMatrix == null && !graph.worldVolume.isValid())
                //    return;     // stop here, we're already done

                graph.worldVolume.invalidate();
                graph.globalMatrix = null;

                // clear children's cache, too
                //for (var i=0, n=this._childNodes.length; i<n; i++) {
                //    var node = this._childNodes[i];
                //    if (node)
                //        node.invalidateCache();
                //}
            },

            cacheInvalid: function()
            {
                return ( this._graph.globalMatrix == null ||
                    !this._graph.worldVolume.isValid() );
            },

            volumeValid: function()
            {
                return this._graph.volume.isValid();
            },

            graphState: function()
            {
                return this._graph;
            },

            forceUpdateCoverage: function()
            {
                return false;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### X3DGroupingNode ###
x3dom.registerNodeType(
    "X3DGroupingNode",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DBoundedObject,
        
        /**
         * Constructor for X3DGroupingNode
         * @constructs x3dom.nodeTypes.X3DGroupingNode
         * @x3d 3.3
         * @component Grouping
         * @status experimental
         * @extends x3dom.nodeTypes.X3DBoundedObject
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type indicates that concrete node types derived from it contain children nodes
         * and is the basis for all aggregation.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DGroupingNode.superClass.call(this, ctx);


            /**
             * Grouping nodes have a field that contains a list of children nodes. Each grouping node defines a
             * coordinate space for its children. This coordinate space is relative to the coordinate space of the node
             * of which the group node is a child. Such a node is called a parent node. This means that transformations
             * accumulate down the scene graph hierarchy.
             * @var {x3dom.fields.MFNode} children
             * @memberof x3dom.nodeTypes.X3DGroupingNode
             * @initvalue X3DChildNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('children', x3dom.nodeTypes.X3DChildNode);
            // FIXME; add addChild and removeChild slots ?
        
        },
        {
            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                // check if multi parent sub-graph, don't cache in that case
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                // an invalid world matrix or volume needs to be invalidated down the hierarchy
                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                // check if sub-graph can be culled away or render flag was set to false
                planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask);
                if (planeMask < 0) {
                    return;
                }

                var cnode, childTransform;

                if (singlePath) {
                    // rebuild cache on change and reuse world transform
                    if (!this._graph.globalMatrix) {
                        this._graph.globalMatrix = this.transformMatrix(transform);
                    }
                    childTransform = this._graph.globalMatrix;
                }
                else {
                    childTransform = this.transformMatrix(transform);
                }

                var n = this._childNodes.length;

                if (x3dom.nodeTypes.ClipPlane.count > 0) {
                    var localClipPlanes = [];

                    for (var j = 0; j < n; j++) {
                        if ( (cnode = this._childNodes[j]) ) {
                            if (x3dom.isa(cnode, x3dom.nodeTypes.ClipPlane) && cnode._vf.on && cnode._vf.enabled) {
                                localClipPlanes.push({plane: cnode, trafo: childTransform});
                            }
                        }
                    }

                    clipPlanes = localClipPlanes.concat(clipPlanes);
                }

                for (var i=0; i<n; i++) {
                    if ( (cnode = this._childNodes[i]) ) {
                        cnode.collectDrawableObjects(childTransform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                    }
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### Switch ###
x3dom.registerNodeType(
    "Switch",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for Switch
         * @constructs x3dom.nodeTypes.Switch
         * @x3d 3.3
         * @component Grouping
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Switch grouping node traverses zero or one of the nodes specified in the children field.
         * All nodes under a Switch continue to receive and send events regardless of the value of whichChoice.
         * For example, if an active TimeSensor is contained within an inactive choice of an Switch, the TimeSensor sends events regardless of the Switch's state.
         */
        function (ctx) {
            x3dom.nodeTypes.Switch.superClass.call(this, ctx);


            /**
             * The whichChoice field specifies the index of the child to traverse, with the first child having index 0.
             * If whichChoice is less than zero or greater than the number of nodes in the children field, nothing is chosen.
             * @var {x3dom.fields.SFInt32} whichChoice
             * @memberof x3dom.nodeTypes.Switch
             * @initvalue -1
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'whichChoice', -1);
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "whichChoice") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            },

            getVolume: function()
            {
                var vol = this._graph.volume;

                if (!this.volumeValid() && this._vf.render)
                {
                    if (this._vf.whichChoice >= 0 &&
                        this._vf.whichChoice < this._childNodes.length)
                    {
                        var child = this._childNodes[this._vf.whichChoice];

                        var childVol = (child && child._vf.render === true) ? child.getVolume() : null;

                        if (childVol && childVol.isValid())
                            vol.extendBounds(childVol.min, childVol.max);
                    }
                }

                return vol;
            },

            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                if (this._vf.whichChoice < 0 || this._vf.whichChoice >= this._childNodes.length ||
                    (planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask)) < 0) {
                    return;
                }

                var cnode, childTransform;

                if (singlePath) {
                    if (!this._graph.globalMatrix) {
                        this._graph.globalMatrix = this.transformMatrix(transform);
                    }
                    childTransform = this._graph.globalMatrix;
                }
                else {
                    childTransform = this.transformMatrix(transform);
                }

                if ( (cnode = this._childNodes[this._vf.whichChoice]) ) {
                    cnode.collectDrawableObjects(childTransform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                }
            },

            doIntersect: function(line)
            {
                if (this._vf.whichChoice < 0 ||
                    this._vf.whichChoice >= this._childNodes.length) {
                    return false;
                }

                var child = this._childNodes[this._vf.whichChoice];
                if (child) {
                    return child.doIntersect(line);
                }

                return false;
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### X3DTransformNode ###
x3dom.registerNodeType(
    "X3DTransformNode",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for X3DTransformNode
         * @constructs x3dom.nodeTypes.X3DTransformNode
         * @x3d x.x
         * @component Grouping
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the basis for all node types that group and transform their children.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DTransformNode.superClass.call(this, ctx);

            if (ctx)
                ctx.doc._nodeBag.trans.push(this);
            else
                x3dom.debug.logWarning("X3DTransformNode: No runtime context found!");

            // holds the current matrix (local space transform)
            this._trafo = null;

            // workaround, only check on init if getStyle is necessary, since expensive
            this._needCssStyleUpdates = true;
        
        },
        {
            tick: function (t)
            {
                var dom = this._xmlNode;

                if (dom && (dom['ontransform'] ||
                    dom.hasAttribute('ontransform') ||
                    this._listeners['transform'])) {
                    var transMatrix = this.getCurrentTransform();

                    var event = {
                        target: dom,
                        type: 'transform',
                        worldX: transMatrix._03,
                        worldY: transMatrix._13,
                        worldZ: transMatrix._23,
                        cancelBubble: false,
                        stopPropagation: function () {
                            this.cancelBubble = true;
                        }
                    };

                    this.callEvtHandler("ontransform", event);
                }

                // temporary per frame update method for CSS-Transform
                if (this._needCssStyleUpdates && dom) {
                    var trans = x3dom.getStyle(dom, "-webkit-transform") ||
                        x3dom.getStyle(dom, "-moz-transform") ||
                        x3dom.getStyle(dom, "-ms-transform") ||
                        x3dom.getStyle(dom, "transform");

                    if (trans && (trans != 'none')) {
                        this._trafo.setValueByStr(trans);

                        this.invalidateVolume();
                        //this.invalidateCache();

                        return true;
                    }
                    this._needCssStyleUpdates = false;    // no special CSS set
                }

                return false;
            },

            transformMatrix: function(transform) {
                return transform.mult(this._trafo);
            },

            getVolume: function()
            {
                var vol = this._graph.volume;

                if (!this.volumeValid() && this._vf.render)
                {
                    this._graph.localMatrix = this._trafo;

                    for (var i=0, n=this._childNodes.length; i<n; i++)
                    {
                        var child = this._childNodes[i];
                        if (!child || child._vf.render !== true)
                            continue;

                        var childVol = child.getVolume();

                        if (childVol && childVol.isValid())
                            vol.extendBounds(childVol.min, childVol.max);
                    }

                    if (vol.isValid())
                        vol.transform(this._trafo);
                }

                return vol;
            },

            doIntersect: function(line)
            {
                var isect = false;
                var mat = this._trafo.inverse();

                var tmpPos = new x3dom.fields.SFVec3f(line.pos.x, line.pos.y, line.pos.z);
                var tmpDir = new x3dom.fields.SFVec3f(line.dir.x, line.dir.y, line.dir.z);

                line.pos = mat.multMatrixPnt(line.pos);
                line.dir = mat.multMatrixVec(line.dir);

                if (line.hitObject) {
                    line.dist *= line.dir.length();
                }

                // check for _nearest_ hit object and don't stop on first!
                for (var i=0; i<this._childNodes.length; i++)
                {
                    if (this._childNodes[i]) {
                        isect = this._childNodes[i].doIntersect(line) || isect;
                    }
                }

                line.pos.setValues(tmpPos);
                line.dir.setValues(tmpDir);

                if (isect) {
                    line.hitPoint = this._trafo.multMatrixPnt(line.hitPoint);
                    line.dist *= line.dir.length();
                }

                return isect;
            },

            parentRemoved: function(parent)
            {
                var i, n;

                if (this._parentNodes.length == 0) {
                    var doc = this.findX3DDoc();

                    for (i=0, n=doc._nodeBag.trans.length; i<n; i++) {
                        if (doc._nodeBag.trans[i] === this) {
                            doc._nodeBag.trans.splice(i, 1);
                        }
                    }
                }

                for (i=0, n=this._childNodes.length; i<n; i++) {
                    if (this._childNodes[i]) {
                        this._childNodes[i].parentRemoved(this);
                    }
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### Transform ###
x3dom.registerNodeType(
    "Transform",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DTransformNode,
        
        /**
         * Constructor for Transform
         * @constructs x3dom.nodeTypes.Transform
         * @x3d 3.3
         * @component Grouping
         * @status experimental
         * @extends x3dom.nodeTypes.X3DTransformNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Transform node is a grouping node that defines a coordinate system for its children that is relative to the coordinate systems of its ancestors.
         * The translation, rotation, scale, scaleOrientation and center fields define a geometric 3D transformation.
         */
        function (ctx) {
            x3dom.nodeTypes.Transform.superClass.call(this, ctx);


            /**
             * The center field specifies a translation offset from the origin of the local coordinate system (0,0,0).
             * @var {x3dom.fields.SFVec3f} center
             * @memberof x3dom.nodeTypes.Transform
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'center', 0, 0, 0);

            /**

             * The translation field specifies a translation to the coordinate system.
             * @var {x3dom.fields.SFVec3f} translation
             * @memberof x3dom.nodeTypes.Transform
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'translation', 0, 0, 0);

            /**
             * The rotation field specifies a rotation of the coordinate system.
             * @var {x3dom.fields.SFRotation} rotation
             * @memberof x3dom.nodeTypes.Transform
             * @initvalue 0,0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'rotation', 0, 0, 1, 0);

            /**
             * The scale field specifies a non-uniform scale of the coordinate system.
             * Scale values may have any value: positive, negative (indicating a reflection), or zero. A value of zero indicates that any child geometry shall not be displayed.
             * @var {x3dom.fields.SFVec3f} scale
             * @memberof x3dom.nodeTypes.Transform
             * @initvalue 1,1,1
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'scale', 1, 1, 1);

            /**
             * The scaleOrientation specifies a rotation of the coordinate system before the scale (to specify scales in arbitrary orientations).
             * The scaleOrientation applies only to the scale operation.
             * @var {x3dom.fields.SFRotation} scaleOrientation
             * @memberof x3dom.nodeTypes.Transform
             * @initvalue 0,0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'scaleOrientation', 0, 0, 1, 0);

            // P' = T * C * R * SR * S * -SR * -C * P
            this._trafo = x3dom.fields.SFMatrix4f.translation(
                this._vf.translation.add(this._vf.center)).
                mult(this._vf.rotation.toMatrix()).
                mult(this._vf.scaleOrientation.toMatrix()).
                mult(x3dom.fields.SFMatrix4f.scale(this._vf.scale)).
                mult(this._vf.scaleOrientation.toMatrix().inverse()).
                mult(x3dom.fields.SFMatrix4f.translation(this._vf.center.negate()));
        
        },
        {
            fieldChanged: function (fieldName)
            {
                if (fieldName == "center" || fieldName == "translation" ||
                    fieldName == "rotation" || fieldName == "scale" ||
                    fieldName == "scaleOrientation")
                {
                    // P' = T * C * R * SR * S * -SR * -C * P
                    this._trafo = x3dom.fields.SFMatrix4f.translation(
                        this._vf.translation.add(this._vf.center)).
                        mult(this._vf.rotation.toMatrix()).
                        mult(this._vf.scaleOrientation.toMatrix()).
                        mult(x3dom.fields.SFMatrix4f.scale(this._vf.scale)).
                        mult(this._vf.scaleOrientation.toMatrix().inverse()).
                        mult(x3dom.fields.SFMatrix4f.translation(this._vf.center.negate()));

                    this.invalidateVolume();
                    //this.invalidateCache();
                }
                else if (fieldName == "render") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### MatrixTransform ###
x3dom.registerNodeType(
    "MatrixTransform",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DTransformNode,
        
        /**
         * Constructor for MatrixTransform
         * @constructs x3dom.nodeTypes.MatrixTransform
         * @x3d x.x
         * @component Grouping
         * @extends x3dom.nodeTypes.X3DTransformNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The MatrixTransform node is a grouping node that defines a coordinate system for its children that is relative to the coordinate systems of its ancestors.
         * The transformation is given as a matrix.
         */
        function (ctx) {
            x3dom.nodeTypes.MatrixTransform.superClass.call(this, ctx);


            /**
             * Defines the transformation matrix.
             * @var {x3dom.fields.SFMatrix4f} matrix
             * @memberof x3dom.nodeTypes.MatrixTransform
             * @initvalue 1,0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFMatrix4f(ctx, 'matrix',
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);
            this._trafo = this._vf.matrix.transpose();
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "matrix") {
                    this._trafo = this._vf.matrix.transpose();

                    this.invalidateVolume();
                    //this.invalidateCache();
                }
                else if (fieldName == "render") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### Group ###
x3dom.registerNodeType(
    "Group",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for Group
         * @constructs x3dom.nodeTypes.Group
         * @x3d 3.3
         * @component Grouping
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc A Group node contains children nodes without introducing a new transformation.
         * It is equivalent to a Transform node containing an identity transform.
         */
        function (ctx) {
            x3dom.nodeTypes.Group.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### Block ###
x3dom.registerNodeType(
    "Block",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for Block
         * @constructs x3dom.nodeTypes.Block
         * @x3d x.x
         * @component Grouping
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         */
        function (ctx) {
            x3dom.nodeTypes.Block.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.MFString} nameSpaceName
             * @memberof x3dom.nodeTypes.Block
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'nameSpaceName', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### StaticGroup ###
x3dom.registerNodeType(
    "StaticGroup",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for StaticGroup
         * @constructs x3dom.nodeTypes.StaticGroup
         * @x3d 3.3
         * @component Grouping
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The StaticGroup node contains children nodes which cannot be modified.
         * StaticGroup children are guaranteed to not change, send events, receive events or contain any USE references outside the StaticGroup.
         * This allows the browser to optimize this content for faster rendering and less memory usage.
         */
        function (ctx) {
            x3dom.nodeTypes.StaticGroup.superClass.call(this, ctx);

            // Node implements optimizations; no need to maintain the children node's
            // X3D representations, as they cannot be accessed after creation time
            x3dom.debug.logWarning("StaticGroup erroneously also bakes parent transforms, if happens use Group node!"); // Blender exports to SG

            /**
             * Enables debugging.
             * @var {x3dom.fields.SFBool} debug
             * @memberof x3dom.nodeTypes.StaticGroup
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'debug', false);

            /**
             * Enable debug box volumes.
             * @var {x3dom.fields.SFBool} showDebugBoxVolumes
             * @memberof x3dom.nodeTypes.StaticGroup
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'showDebugBoxVolumes', false);

            // type of bvh to use, supported are 'jsBIH', 'BIH' and 'OCTREE'

            /**
             * Defines the type of bvh to use. Supported are 'jsBIH', 'BIH' and 'OCTREE'.
             * @var {x3dom.fields.SFString} bvhType
             * @memberof x3dom.nodeTypes.StaticGroup
             * @initvalue 'jsBIH'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'bvhType', 'jsBIH');

            /**
             *
             * @var {x3dom.fields.SFInt32} maxObjectsPerNode
             * @memberof x3dom.nodeTypes.StaticGroup
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'maxObjectsPerNode', 1);
            // -1 sets default values, other values forces maxDepth

            /**
             *
             * @var {x3dom.fields.SFInt32} maxDepth
             * @memberof x3dom.nodeTypes.StaticGroup
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'maxDepth', -1);

            /**
             *
             * @var {x3dom.fields.SFFloat} minRelativeBBoxSize
             * @memberof x3dom.nodeTypes.StaticGroup
             * @initvalue 0.01
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'minRelativeBBoxSize', 0.01);

            this.needBvhRebuild = true;
            this.drawableCollection = null;
            this.bvh = null;
        
        },
        {
            getMaxDepth : function()
            {
                if(this._vf.maxDepth == -1 )
                {
                    return (this._vf.bvhType == ('jsBIH' || 'BIH')) ? 50 : 4;
                }
                return this._vf.maxDepth;
            },

            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                // check if multi parent sub-graph, don't cache in that case
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                // an invalid world matrix or volume needs to be invalidated down the hierarchy
                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                // check if sub-graph can be culled away or render flag was set to false
                planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask);
                if (planeMask < 0) {
                    return;
                }

                var cnode, childTransform;

                if (singlePath) {
                    // rebuild cache on change and reuse world transform
                    if (!this._graph.globalMatrix) {
                        this._graph.globalMatrix = this.transformMatrix(transform);
                    }
                    childTransform = this._graph.globalMatrix;
                }
                else {
                    childTransform = this.transformMatrix(transform);
                }

                if (this.needBvhRebuild)
                {
                    var drawableCollectionConfig = {
                        viewArea: drawableCollection.viewarea,
                        sortTrans: drawableCollection.sortTrans,
                        viewMatrix: drawableCollection.viewMatrix,
                        projMatrix: drawableCollection.projMatrix,
                        sceneMatrix: drawableCollection.sceneMatrix,
                        frustumCulling: false,
                        smallFeatureThreshold: 0,//1,    // THINKABOUTME
                        context: drawableCollection.context
                    };

                    this.drawableCollection = new x3dom.DrawableCollection(drawableCollectionConfig);

                    var i, n = this._childNodes.length;
                    for (i=0; i<n; i++) {
                        if ( (cnode = this._childNodes[i]) ) {
                            //this is only used to collect all drawables once
                            cnode.collectDrawableObjects(childTransform, this.drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                        }
                    }
                    this.drawableCollection.concat();

                    var scene = this._nameSpace.doc._scene;

                    //create settings
                    var bvhSettings = new x3dom.bvh.Settings(
                        this._vf.debug,
                        this._vf.showDebugBoxVolumes,
                        this._vf.bvhType,
                        this._vf.maxObjectsPerNode,
                        this.getMaxDepth(),
                        this._vf.minRelativeBBoxSize
                    );
                    //create bvh type
                    this.bvh = (this._vf.bvhType == 'jsBIH' ) ?
                        new x3dom.bvh.BIH(scene, bvhSettings) :
                        new x3dom.bvh.Culler(this.drawableCollection,scene, bvhSettings);

                    //add decorator for debug shapes
                    if(this._vf.debug || this._vf.showDebugBoxVolumes)
                        this.bvh = new x3dom.bvh.DebugDecorator(this.bvh, scene, bvhSettings);

                    //add drawables
                    n = this.drawableCollection.length;
                    for (i = 0; i < n; i++)
                    {
                        this.bvh.addDrawable(this.drawableCollection.get(i))
                    }

                    //compile bvh
                    this.bvh.compile();

                    if(this._vf.debug)
                        this.bvh.showCompileStats();

                    this.needBvhRebuild = false;    // TODO: re-evaluate if Inline node is child node
                }

                x3dom.Utils.startMeasure('bvhTraverse');
                //collect drawables
                this.bvh.collectDrawables(drawableCollection);
                var dt = x3dom.Utils.stopMeasure('bvhTraverse');
                this._nameSpace.doc.ctx.x3dElem.runtime.addMeasurement('BVH', dt);

                //show stats
                this.bvh.showTraverseStats(this._nameSpace.doc.ctx.x3dElem.runtime);
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### RemoteSelectionGroup ###
x3dom.registerNodeType(
    "RemoteSelectionGroup",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for RemoteSelectionGroup
         * @constructs x3dom.nodeTypes.RemoteSelectionGroup
         * @x3d x.x
         * @component Grouping
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The RemoteSelectionGroup node uses a WebSocket connection to request the results of a a  side
         *  culling.
         */
        function (ctx) {
            x3dom.nodeTypes.RemoteSelectionGroup.superClass.call(this, ctx);


            /**
             * The address for the WebSocket connection
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.RemoteSelectionGroup
             * @initvalue ["ws://localhost:35668/cstreams/0"]
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'url', ["ws://localhost:35668/cstreams/0"]);

            /**
             * Defines a list of subsequent id/object pairs.
             * @var {x3dom.fields.MFString} label
             * @memberof x3dom.nodeTypes.RemoteSelectionGroup
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'label', []);

            /**
             * Sets the maximum number of items that are rendered.
             * @var {x3dom.fields.SFInt32} maxRenderedIds
             * @range -1 or [0, inf]
             * @memberof x3dom.nodeTypes.RemoteSelectionGroup
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'maxRenderedIds', -1);

            /**
             * Sets whether a reconnect is attempted on a connection loss.
             * @var {x3dom.fields.SFBool} reconnect
             * @memberof x3dom.nodeTypes.RemoteSelectionGroup
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'reconnect', true);

            /**
             * Sets the scaling factor to reduce the number of render calls during navigation
             * @var {x3dom.fields.SFFloat} scaleRenderedIdsOnMove
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.RemoteSelectionGroup
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'scaleRenderedIdsOnMove', 1.0);

            /**
             * Defines whether culling is used. If culling is disabled the RemoteSelectionGroup works like a normal group.
             * @var {x3dom.fields.SFBool} enableCulling
             * @memberof x3dom.nodeTypes.RemoteSelectionGroup
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'enableCulling', true);

            /**
             * Defines a set of labels to disable nodes. The label must include the prefix.
             * @var {x3dom.fields.MFString} invisibleNodes
             * @memberof x3dom.nodeTypes.RemoteSelectionGroup
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'invisibleNodes', []);

            this._idList = [];          // to be updated by socket connection
            this._websocket = null;     // pointer to socket

            this._nameObjMap = {};
            this._createTime = [];
            this._visibleList = [];

            if (ctx)
                this.initializeSocket();    // init socket connection
            else
                x3dom.debug.logWarning("RemoteSelectionGroup: No runtime context found!");
        
        },
        {
            initializeSocket: function()
            {
                var that = this;

                if ("WebSocket" in window)
                {
                    var wsUrl = "ws://localhost:35668/cstreams/0";

                    if (this._vf.url.length && this._vf.url[0].length)
                        wsUrl = this._vf.url[0];

                    this._websocket = new WebSocket(wsUrl);

                    this._websocket._lastMsg = null;
                    this._websocket._lastData = "";

                    this._websocket.onopen = function(evt)
                    {
                        x3dom.debug.logInfo("WS Connected");

                        var view = that._nameSpace.doc._viewarea.getViewMatrix();
                        this._lastMsg = view.toGL().toString();

                        view = that._nameSpace.doc._viewarea.getProjectionMatrix();
                        this._lastMsg += ("," + view.toGL().toString());

                        this.send(this._lastMsg);
                        x3dom.debug.logInfo("WS Sent: " + this._lastMsg);

                        this._lastMsg = "";     // triggers first update
                        this._lastData = "";
                    };

                    this._websocket.onclose = function(evt)
                    {
                        x3dom.debug.logInfo("WS Disconnected");

                        if (that._vf.reconnect)
                        {
                            window.setTimeout(function() {
                                that.initializeSocket();
                            }, 2000);
                        }
                    };

                    this._websocket.onmessage = function(evt)
                    {
                        if (that._vf.maxRenderedIds < 0)
                        {
                            // render all sent items
                            that._idList = x3dom.fields.MFString.parse(evt.data);
                        }
                        else if (that._vf.maxRenderedIds > 0)
                        {
                            // render #maxRenderedIds items
                            that._idList = [];
                            var arr = x3dom.fields.MFString.parse(evt.data);
                            var n = Math.min(arr.length, Math.abs(that._vf.maxRenderedIds));

                            for (var i=0; i<n; ++i) {
                                that._idList[i] = arr[i];
                            }
                        }

                        if (that._vf.maxRenderedIds != 0 && this._lastData != evt.data)
                        {
                            this._lastData = evt.data;
                            that._nameSpace.doc.needRender = true;
                            //x3dom.debug.logInfo("WS Response: " + evt.data);

                            that.invalidateVolume();
                            //that.invalidateCache();
                        }
                    };

                    this._websocket.onerror = function(evt)
                    {
                        x3dom.debug.logError(evt.data);
                    };

                    this._websocket.updateCamera = function()
                    {
                        // send again
                        var view = that._nameSpace.doc._viewarea.getViewMatrix();
                        var message = view.toGL().toString();

                        view = that._nameSpace.doc._viewarea.getProjectionMatrix();
                        message += ("," + view.toGL().toString());

                        if (this._lastMsg != null && this._lastMsg != message)
                        {
                            this._lastMsg = message;
                            this.send(message);
                            //x3dom.debug.logInfo("WS Sent: " + message);
                        }
                    };

                    // if there were a d'tor this would belong there
                    // this._websocket.close();
                }
                else
                {
                    x3dom.debug.logError("Browser has no WebSocket support!");
                }
            },

            nodeChanged: function ()
            {
                var n = this._vf.label.length;

                this._nameObjMap = {};
                this._createTime = new Array(n);
                this._visibleList = new Array(n);

                for (var i=0; i<n; ++i)
                {
                    var shape = this._childNodes[i];

                    if (shape && x3dom.isa(shape, x3dom.nodeTypes.X3DShapeNode))
                    {
                        this._nameObjMap[this._vf.label[i]] = { shape: shape, pos: i };
                        this._visibleList[i] = true;
                    }
                    else {
                        this._visibleList[i] = false;
                        x3dom.debug.logError("Invalid children: " + this._vf.label[i]);
                    }

                    // init list that holds creation time of gl object
                    this._createTime[i] = 0;
                }

                this.invalidateVolume();
                //this.invalidateCache();

                x3dom.debug.logInfo("RemoteSelectionGroup has " + n + " entries.");
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "url")
                {
                    if (this._websocket) {
                        this._websocket.close();
                        this._websocket = null;
                    }
                    this.initializeSocket();
                }
                else if (fieldName == "invisibleNodes")
                {
                    for (var i=0, n=this._vf.label.length; i<n; ++i)
                    {
                        var shape = this._childNodes[i];

                        if (shape && x3dom.isa(shape, x3dom.nodeTypes.X3DShapeNode))
                        {
                            this._visibleList[i] = true;

                            for (var j=0, numInvis=this._vf.invisibleNodes.length; j<numInvis; ++j)
                            {
                                var nodeName = this._vf.invisibleNodes[j];
                                var starInd = nodeName.lastIndexOf('*');
                                var matchNameBegin = false;

                                if (starInd > 0) {
                                    nodeName = nodeName.substring(0, starInd);
                                    matchNameBegin = true;
                                }
                                if (nodeName.length <= 1)
                                    continue;

                                if ((matchNameBegin && this._vf.label[i].indexOf(nodeName) == 0) ||
                                    this._vf.label[i] == nodeName) {
                                    this._visibleList[i] = false;
                                    break;
                                }
                            }
                        }
                        else {
                            this._visibleList[i] = false;
                        }
                    }

                    this.invalidateVolume();
                    //this.invalidateCache();
                }
                else if (fieldName == "render") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            },

            getNumRenderedObjects: function(len, isMoving)
            {
                var n = len;

                if (this._vf.maxRenderedIds > 0)
                {
                    var num = Math.max(this._vf.maxRenderedIds, 16);  // set lower bound

                    var scale = 1;  // scale down on move
                    if (isMoving)
                        scale = Math.min(this._vf.scaleRenderedIdsOnMove, 1);

                    num = Math.max(Math.round(scale * num), 0);
                    n = Math.min(n, num);
                }

                return n;
            },

            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask);
                if (planeMask <= 0) {
                    return;
                }

                var viewarea = this._nameSpace.doc._viewarea;
                var isMoving = viewarea.isMovingOrAnimating();

                var ts = new Date().getTime();
                var maxLiveTime = 10000;
                var i, n, numChild = this._childNodes.length;

                if (!this._vf.enableCulling)
                {
                    n = this.getNumRenderedObjects(numChild, isMoving);

                    var cnt = 0;
                    for (i=0; i<numChild; i++)
                    {
                        var shape = this._childNodes[i];

                        if (shape)
                        {
                            var needCleanup = true;

                            if (this._visibleList[i] && cnt < n &&
                                shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes))
                            {
                                this._createTime[i] = ts;
                                cnt++;
                                needCleanup = false;
                            }

                            if (needCleanup && !isMoving && this._createTime[i] > 0 &&
                                ts - this._createTime[i] > maxLiveTime && shape._cleanupGLObjects)
                            {
                                shape._cleanupGLObjects(true);
                                this._createTime[i] = 0;
                            }
                        }
                    }

                    return;
                }

                if (this._websocket)
                    this._websocket.updateCamera();

                if (this._vf.label.length)
                {
                    n = this.getNumRenderedObjects(this._idList.length, isMoving);

                    for (i=0; i<n; i++)
                    {
                        var obj = this._nameObjMap[this._idList[i]];
                        if (obj && obj.shape) {
                            obj.shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                            this._createTime[obj.pos] = ts;
                        }
                        else
                            x3dom.debug.logError("Invalid label: " + this._idList[i]);
                    }

                    for (i=0; i<this._childNodes.length; i++)
                    {
                        if (this._childNodes[i] && !isMoving && this._createTime[i] > 0 &&
                            ts - this._createTime[i] > maxLiveTime && this._childNodes[i]._cleanupGLObjects)
                        {
                            this._childNodes[i]._cleanupGLObjects(true);
                            this._createTime[i] = 0;
                        }
                    }
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// Not a real X3D node type
// ### Scene ###
x3dom.registerNodeType(
    "Scene",
    "Grouping",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for Scene
         * @constructs x3dom.nodeTypes.Scene
         * @x3d x.x
         * @component Core
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The scene node wraps the x3d scene.
         */
        function (ctx) {
            x3dom.nodeTypes.Scene.superClass.call(this, ctx);

            // define the experimental picking mode: box, idBuf, idBuf24, idBufId, color, texCoord

            /**
             * The picking mode for the scene
             * @var {x3dom.fields.SFString} pickMode
             * @memberof x3dom.nodeTypes.Scene
             * @initvalue "idBuf"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'pickMode', "idBuf");
            // experimental field to switch off picking

            /**
             * Flag to enable/disable pick pass
             * @var {x3dom.fields.SFBool} doPickPass
             * @memberof x3dom.nodeTypes.Scene
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'doPickPass', true);

            // another experimental field for shadow DOM remapping

            /**
             * The url of the shadow object id mapping
             * @var {x3dom.fields.SFString} shadowObjectIdMapping
             * @memberof x3dom.nodeTypes.Scene
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'shadowObjectIdMapping', "");

            this._lastMin = new x3dom.fields.SFVec3f(0, 0, 0);
            this._lastMax = new x3dom.fields.SFVec3f(1, 1, 1);

            this._shadowIdMap = null;
            this.loadMapping();

            this._multiPartMap = null;
        },
        {
            /* Bindable getter (e.g. getViewpoint) are added automatically */

            fieldChanged: function(fieldName)
            {
                if (fieldName == "shadowObjectIdMapping")
                {
                    this.loadMapping();
                }
            },

            updateVolume: function()
            {
                var vol = this.getVolume();

                if (vol.isValid())
                {
                    this._lastMin = x3dom.fields.SFVec3f.copy(vol.min);
                    this._lastMax = x3dom.fields.SFVec3f.copy(vol.max);
                }
            },

            loadMapping: function()
            {
                this._shadowIdMap = null;

                if (this._vf.shadowObjectIdMapping.length == 0) {
                    return;
                }

                var that = this;
                var xhr = new XMLHttpRequest();

                xhr.open("GET", this._nameSpace.getURL(this._vf.shadowObjectIdMapping), true);
                //xhr.send();
                x3dom.RequestManager.addRequest(xhr);

                xhr.onload = function()
                {
                    that._shadowIdMap = eval("(" + xhr.response + ")");

                    if (!that._shadowIdMap || !that._shadowIdMap.mapping) {
                        x3dom.debug.logWarning("Invalid ID map: " + that._vf.shadowObjectIdMapping);
                    }
                    else {
                        x3dom.debug.assert(that._shadowIdMap.maxID <= that._shadowIdMap.mapping.length,
                                "Too few ID map entries in " + that._vf.shadowObjectIdMapping + ", " +
                                "length of mapping array is only " + that._shadowIdMap.mapping.length +
                                " instead of " + that._shadowIdMap.ids.length + "!");
                    }
                };
            }
        }
    )
);

/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 * Based on code originally provided by
 * Philip Taylor: http://philip.html5.org
 */


///////////////////////////////////////////////////////////////////////////////
// BindableStack constructor
///////////////////////////////////////////////////////////////////////////////
x3dom.BindableStack = function (doc, type, defaultType, getter) {
    this._doc = doc;
    this._type = type;
    this._defaultType = defaultType;
    this._defaultRoot = null;
    this._getter = getter;
    this._bindBag = [];
    this._bindStack = [];
};

x3dom.BindableStack.prototype.top = function () {
    return ( (this._bindStack.length > 0) ? this._bindStack[this._bindStack.length - 1] : null );
};

x3dom.BindableStack.prototype.push = function (bindable) {
    var top = this.top();

    if (top === bindable) {
        return;
    }

    if (top) {
        top.deactivate();
    }

    this._bindStack.push(bindable);
    
    bindable.activate(top);
};

x3dom.BindableStack.prototype.replaceTop = function (bindable) {
    var top = this.top();

    if (top === bindable) {
        return;
    }

    if (top) {
        top.deactivate();

        this._bindStack[this._bindStack.length - 1] = bindable;
        
        bindable.activate(top);
    }
};

x3dom.BindableStack.prototype.pop = function (bindable) {
    var top;

    if (bindable) {
        top = this.top();
        if (bindable !== top) {
            return null;
        }
    }

    top = this._bindStack.pop();

    if (top) {
        top.deactivate();
    }

    return top;
};

x3dom.BindableStack.prototype.switchTo = function (target) {
    var last = this.getActive();
    var n = this._bindBag.length;
    var toBind = 0;
    var i = 0, lastIndex = -1;

    if (n <= 1) {
        return;
    }

    switch (target)
    {
        case 'first':
            toBind = this._bindBag[0];
            break;
        case 'last':
            toBind = this._bindBag[n-1];
            break;
        default:
            for (i = 0; i < n; i++) {
                if (this._bindBag[i] == last) {
                    lastIndex = i;
                    break;
                }
            }
            if (lastIndex >= 0) {
                i = lastIndex;
                while (!toBind) {
                    if (target == 'next') {
                        i = (i < (n-1)) ? (i+1) : 0;
                    } else { // prev
                        i = (i>0) ? (i-1) : (n-1);
                    }
                    if (i == lastIndex) {
                        break;
                    }
                    if (this._bindBag[i]._vf.description.length >= 0) {
                        toBind = this._bindBag[i];
                    }
                }
            }
            break;
    }
    
    if (toBind) {
        this.replaceTop(toBind);
    } else {
        x3dom.debug.logWarning ('Cannot switch bindable; no other bindable with description found.');
    }
};

// Get currently active bindable of given stack type, creates new if none exists
x3dom.BindableStack.prototype.getActive = function () {
    if (this._bindStack.length === 0) {
        if (this._bindBag.length === 0) {
            if (this._defaultRoot) {
                x3dom.debug.logInfo ('create new ' + this._defaultType._typeName +
                                     ' for ' + this._type._typeName + '-stack');
                var obj = new this._defaultType( 
                    { doc: this._doc, nameSpace: this._defaultRoot._nameSpace, autoGen: true } );
                
                this._defaultRoot.addChild(obj);
                obj.nodeChanged();
            }
            else {
                x3dom.debug.logError ('stack without defaultRoot');
            }
        }
        else {
            x3dom.debug.logInfo ('activate first ' + this._type._typeName +
                                 ' for ' + this._type._typeName + '-stack');
        }

        this._bindStack.push(this._bindBag[0]);
        this._bindBag[0].activate();
    }
    
    return this._bindStack[this._bindStack.length - 1];
};


///////////////////////////////////////////////////////////////////////////////
// BindableBag constructor
///////////////////////////////////////////////////////////////////////////////
x3dom.BindableBag = function (doc) {
    this._stacks = [];

    this.addType ("X3DViewpointNode", "Viewpoint", "getViewpoint", doc);
    this.addType ("X3DNavigationInfoNode", "NavigationInfo", "getNavigationInfo", doc);
    this.addType ("X3DBackgroundNode", "Background", "getBackground", doc);
    this.addType ("X3DFogNode", "Fog", "getFog", doc);
    this.addType ("X3DEnvironmentNode", "Environment", "getEnvironment", doc);
};

x3dom.BindableBag.prototype.addType = function(typeName, defaultTypeName, getter, doc) {
    var type = x3dom.nodeTypes[typeName];
    var defaultType = x3dom.nodeTypes[defaultTypeName];

    if (type && defaultType) {
        var stack = new x3dom.BindableStack (doc, type, defaultType, getter);
        this._stacks.push(stack);
    }
    else {
        x3dom.debug.logWarning('Invalid Bindable type/defaultType: ' + 
                                typeName + '/' + defaultType);
    }
};

x3dom.BindableBag.prototype.setRefNode = function (node) {
    Array.forEach ( this._stacks, function (stack) {
        // set reference to Scene
        stack._defaultRoot = node;
        node[stack._getter] = function () { return stack.getActive(); };
    } );
};

x3dom.BindableBag.prototype.addBindable = function(node) {
    for (var i = 0, n = this._stacks.length; i < n; i++) {
        var stack = this._stacks[i];
        
        if ( x3dom.isa (node, stack._type) ) {
            x3dom.debug.logInfo('register ' + node.typeName() + 'Bindable ' + 
                                node._DEF + '/' + node._vf.description);
            
            stack._bindBag.push(node);
            
            var top = stack.top();
            
            if (top && top._autoGen) {
                stack.replaceTop(node);
                
                // remove auto-generated default bindable
        		for (var j = 0, m = stack._bindBag.length; j < m; j++) {
                    if (stack._bindBag[j] === top) {
                        stack._bindBag.splice(j, 1);
                        break;
                    }
                }
        		stack._defaultRoot.removeChild(top);
            }
            
            return stack;
        }
    }
    
    x3dom.debug.logError (node.typeName() + ' is not a valid bindable');
    return null;
};

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DGeometryNode ### */
x3dom.registerNodeType(
    "X3DGeometryNode",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for X3DGeometryNode
         * @constructs x3dom.nodeTypes.X3DGeometryNode
         * @x3d 3.3
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for all geometry in X3D.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DGeometryNode.superClass.call(this, ctx);


            /**
             * Specifies whether backface-culling is used. If solid is TRUE only front-faces are drawn.
             * @var {x3dom.fields.SFBool} solid
             * @memberof x3dom.nodeTypes.X3DGeometryNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'solid', true);

            /**
             * The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors used in the lighting model equations.
             * @var {x3dom.fields.SFBool} ccw
             * @memberof x3dom.nodeTypes.X3DGeometryNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'ccw', true);

            /**
             * Most geo primitives use geo cache and others might later on, but one should be able to disable cache per geometry node.
             * @var {x3dom.fields.SFBool} useGeoCache
             * @memberof x3dom.nodeTypes.X3DGeometryNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'useGeoCache', true);

            /**
             * Specifies whether this geometry should be rendered with or without lighting.
             * @var {x3dom.fields.SFBool} lit
             * @memberof x3dom.nodeTypes.X3DGeometryNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'lit', true);

            // mesh object also holds volume (_vol)
            this._mesh = new x3dom.Mesh(this);
        
        },
        {
            getVolume: function() {
                // geometry doesn't hold volume, but mesh does
                return this._mesh.getVolume();
            },

            invalidateVolume: function() {
                this._mesh.invalidate();
            },

            getCenter: function() {
                return this._mesh.getCenter();
            },

            getDiameter: function() {
                return this._mesh.getDiameter();
            },

            doIntersect: function(line) {
                return this._mesh.doIntersect(line);
            },

            forceUpdateCoverage: function() {
                return false;
            },

            hasIndexOffset: function() {
                return false;
            },

            getColorTexture: function() {
                return null;
            },

            getColorTextureURL: function() {
                return null;
            },

            parentAdded: function(parent) {
                if (x3dom.isa(parent, x3dom.nodeTypes.X3DShapeNode)) {
                    if (parent._cleanupGLObjects) {
                        parent._cleanupGLObjects(true);
                    }
                    parent.setAllDirty();
                    parent.invalidateVolume();
                }
            },

            needLighting: function() {
                var hasTris = this._mesh._primType.indexOf("TRIANGLE") >= 0;
                return (this._vf.lit && hasTris);
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Mesh ### */
x3dom.registerNodeType(
    "Mesh",         // experimental WebSG geo node
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for Mesh
         * @constructs x3dom.nodeTypes.Mesh
         * @x3d x.x
         * @component Rendering
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is an experimental WebSG geo node
         */
        function (ctx) {
            x3dom.nodeTypes.Mesh.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFString} primType
             * @memberof x3dom.nodeTypes.Mesh
             * @initvalue "triangle"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'primType', "triangle");

            /**
             *
             * @var {x3dom.fields.MFInt32} index
             * @memberof x3dom.nodeTypes.Mesh
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'index', []);


            /**
             *
             * @var {x3dom.fields.MFNode} vertexAttributes
             * @memberof x3dom.nodeTypes.Mesh
             * @initvalue x3dom.nodeTypes.X3DVertexAttributeNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('vertexAttributes', x3dom.nodeTypes.X3DVertexAttributeNode);
        
        },
        {
            nodeChanged: function()
            {
                var time0 = new Date().getTime();

                var i, n = this._cf.vertexAttributes.nodes.length;

                for (i=0; i<n; i++)
                {
                    var name = this._cf.vertexAttributes.nodes[i]._vf.name;

                    switch (name.toLowerCase())
                    {
                        case "position":
                            this._mesh._positions[0] = this._cf.vertexAttributes.nodes[i]._vf.value.toGL();
                            break;
                        case "normal":
                            this._mesh._normals[0] = this._cf.vertexAttributes.nodes[i]._vf.value.toGL();
                            break;
                        case "texcoord":
                            this._mesh._texCoords[0] = this._cf.vertexAttributes.nodes[i]._vf.value.toGL();
                            break;
                        case "color":
                            this._mesh._colors[0] = this._cf.vertexAttributes.nodes[i]._vf.value.toGL();
                            break;
                        default:
                            this._mesh._dynamicFields[name] = {};
                            this._mesh._dynamicFields[name].numComponents =
                                this._cf.vertexAttributes.nodes[i]._vf.numComponents;
                            this._mesh._dynamicFields[name].value =
                                this._cf.vertexAttributes.nodes[i]._vf.value.toGL();
                            break;
                    }
                }

                this._mesh._indices[0] = this._vf.index.toGL();

                this.invalidateVolume();

                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                var time1 = new Date().getTime() - time0;
                x3dom.debug.logWarning("Mesh load time: " + time1 + " ms");
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PointSet ### */
x3dom.registerNodeType(
    "PointSet",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for PointSet
         * @constructs x3dom.nodeTypes.PointSet
         * @x3d 3.3
         * @component Rendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc PointSet is a node that contains a set of colored 3D points, represented by contained Color and Coordinate nodes.
         * Color values or a Material emissiveColor is used to draw lines and points. Hint: use a different color (or emissiveColor) than the background color.
         * Hint: insert a Shape node before adding geometry or Appearance. You can also substitute a type-matched ProtoInstance for content.
         */
        function (ctx) {
            x3dom.nodeTypes.PointSet.superClass.call(this, ctx);


            /**
             * Coordinate node specifiying the vertices used by the geometry.
             * @var {x3dom.fields.SFNode} coord
             * @memberof x3dom.nodeTypes.PointSet
             * @initvalue x3dom.nodeTypes.X3DCoordinateNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('coord', x3dom.nodeTypes.X3DCoordinateNode);

            /**
             * If NULL the geometry is rendered using the Material and texture defined in the Appearance node.
             * If not NULL the field shall contain a Color node whose colours are applied depending on the value of "colorPerVertex".
             * @var {x3dom.fields.SFNode} color
             * @memberof x3dom.nodeTypes.PointSet
             * @initvalue x3dom.nodeTypes.X3DColorNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('color', x3dom.nodeTypes.X3DColorNode);

            this._mesh._primType = 'POINTS';
        
        },
        {
            nodeChanged: function()
            {
                var time0 = new Date().getTime();

                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode, "PointSet without coord node!");
                var positions = coordNode.getPoints();

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                var colors = new x3dom.fields.MFColor();
                if (colorNode) {
                    colors = colorNode._vf.color;
                    x3dom.debug.assert(positions.length == colors.length, "Size of color and coord array differs!");

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }

                this._mesh._numColComponents = numColComponents;
                this._mesh._lit = false;

                this._mesh._indices[0] = [];
                this._mesh._positions[0] = positions.toGL();
                this._mesh._colors[0] = colors.toGL();
                this._mesh._normals[0] = [];
                this._mesh._texCoords[0] = [];

                this.invalidateVolume();
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                var time1 = new Date().getTime() - time0;
                //x3dom.debug.logInfo("Mesh load time: " + time1 + " ms");
            },

            fieldChanged: function(fieldName)
            {
                var pnts = null;

                if (fieldName == "coord")
                {
                    pnts = this._cf.coord.node.getPoints();

                    this._mesh._positions[0] = pnts.toGL();

                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color")
                {
                    pnts = this._cf.color.node._vf.color;

                    this._mesh._colors[0] = pnts.toGL();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DComposedGeometryNode ### */
x3dom.registerNodeType(
    "X3DComposedGeometryNode",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for X3DComposedGeometryNode
         * @constructs x3dom.nodeTypes.X3DComposedGeometryNode
         * @x3d 3.3
         * @component Rendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for all composed 3D geometry in X3D.
         * A composed geometry node type defines an abstract type that composes geometry from a set of nodes that define individual components.
         * Composed geometry may have color, coordinates, normal and texture coordinates supplied.
         * The rendered output of the combination of these is dependent on the concrete node definition.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DComposedGeometryNode.superClass.call(this, ctx);


            /**
             * If colorPerVertex is FALSE, colours are applied to each face. If colorPerVertex is true, colours are applied to each vertex.
             * @var {x3dom.fields.SFBool} colorPerVertex
             * @memberof x3dom.nodeTypes.X3DComposedGeometryNode
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'colorPerVertex', true);

            /**
             * If normalPerVertex is FALSE, normals are applied to each face. If normalPerVertex is true, normals are applied to each vertex.
             * @var {x3dom.fields.SFBool} normalPerVertex
             * @memberof x3dom.nodeTypes.X3DComposedGeometryNode
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'normalPerVertex', true);

            /**
             *
             * @var {x3dom.fields.SFString} normalUpdateMode
             * @memberof x3dom.nodeTypes.X3DComposedGeometryNode
             * @initvalue 'fast'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normalUpdateMode', 'fast');  // none; fast; nice


            /**
             * If the attrib field is not empty it shall contain a list of per-vertex attribute information for programmable shaders.
             * @var {x3dom.fields.MFNode} attrib
             * @memberof x3dom.nodeTypes.X3DComposedGeometryNode
             * @initvalue x3dom.nodeTypes.X3DVertexAttributeNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('attrib', x3dom.nodeTypes.X3DVertexAttributeNode);


            /**
             * Contains a Coordinate node.
             * @var {x3dom.fields.SFNode} coord
             * @memberof x3dom.nodeTypes.X3DComposedGeometryNode
             * @initvalue x3dom.nodeTypes.X3DCoordinateNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('coord', x3dom.nodeTypes.X3DCoordinateNode);

            /**
             * If the normal field is not NULL, it shall contain a Normal node whose normals are applied to the vertices or faces of the X3DComposedGeometryNode in a manner exactly equivalent to that described above for applying colours to vertices/faces (where normalPerVertex corresponds to colorPerVertex and normalIndex corresponds to colorIndex).
             * If the normal field is NULL, the browser shall automatically generate normals in accordance with the node's definition. If the node does not define a behaviour, the default is to generate an averaged normal for all faces that share that vertex.
             * @var {x3dom.fields.SFNode} normal
             * @memberof x3dom.nodeTypes.X3DComposedGeometryNode
             * @initvalue x3dom.nodeTypes.Normal
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('normal', x3dom.nodeTypes.Normal);

            /**
             * If the color field is NULL, the geometry shall be rendered normally using the Material and texture defined in the Appearance node.
             * @var {x3dom.fields.SFNode} color
             * @memberof x3dom.nodeTypes.X3DComposedGeometryNode
             * @initvalue x3dom.nodeTypes.X3DColorNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('color', x3dom.nodeTypes.X3DColorNode);

            /**
             * If the texCoord field is not NULL, it shall contain a TextureCoordinate node.
             * @var {x3dom.fields.SFNode} texCoord
             * @memberof x3dom.nodeTypes.X3DComposedGeometryNode
             * @initvalue x3dom.nodeTypes.X3DTextureCoordinateNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('texCoord', x3dom.nodeTypes.X3DTextureCoordinateNode);
        
        },
        {
            handleAttribs: function()
            {
                //var time0 = new Date().getTime();

                // TODO; handle case that more than 2^16-1 attributes are to be referenced
                var i, n = this._cf.attrib.nodes.length;

                for (i=0; i<n; i++)
                {
                    var name = this._cf.attrib.nodes[i]._vf.name;

                    switch (name.toLowerCase())
                    {
                        case "position":
                            this._mesh._positions[0] = this._cf.attrib.nodes[i]._vf.value.toGL();
                            break;
                        case "normal":
                            this._mesh._normals[0] = this._cf.attrib.nodes[i]._vf.value.toGL();
                            break;
                        case "texcoord":
                            this._mesh._texCoords[0] = this._cf.attrib.nodes[i]._vf.value.toGL();
                            break;
                        case "color":
                            this._mesh._colors[0] = this._cf.attrib.nodes[i]._vf.value.toGL();
                            break;
                        default:
                            this._mesh._dynamicFields[name] = {};
                            this._mesh._dynamicFields[name].numComponents =
                                this._cf.attrib.nodes[i]._vf.numComponents;
                            this._mesh._dynamicFields[name].value =
                                this._cf.attrib.nodes[i]._vf.value.toGL();
                            break;
                    }
                }

                //var time1 = new Date().getTime() - time0;
                //x3dom.debug.logInfo("Mesh load time: " + time1 + " ms");
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### LineSet ### */
x3dom.registerNodeType(
    "LineSet",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for LineSet
         * @constructs x3dom.nodeTypes.LineSet
         * @x3d 3.3
         * @component Rendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc LineSet is a geometry node that can contain a Color node and a Coordinate node.
         * Color values or a Material emissiveColor is used to draw lines and points.
         * Lines are not lit, are not texture-mapped, and do not participate in collision detection.
         * Hint: use a different color (or emissiveColor) than the background color.
         * Hint: if rendering Coordinate points originally defined for an IndexedFaceSet, index values may need to repeat each initial vertex to close each polygon outline.
         * Hint: insert a Shape node before adding geometry or Appearance.
         */
        function (ctx) {
            x3dom.nodeTypes.LineSet.superClass.call(this, ctx);


            /**
             * vertexCount describes how many vertices are used in each polyline from Coordinate field. Coordinates are assigned to each line by taking vertexCount[n] vertices from Coordinate field.
             * @var {x3dom.fields.MFInt32} vertexCount
             * @range [2, inf]
             * @memberof x3dom.nodeTypes.LineSet
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFInt32(ctx, 'vertexCount', []);


            /**
             * If the "attrib" field is not empty it shall contain a list of per-vertex attribute information for programmable shaders
             * @var {x3dom.fields.MFNode} attrib
             * @memberof x3dom.nodeTypes.LineSet
             * @initvalue x3dom.nodeTypes.X3DVertexAttributeNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('attrib', x3dom.nodeTypes.X3DVertexAttributeNode);

            /**
             * Coordinate node specifiying the vertices used by the geometry.
             * @var {x3dom.fields.SFNode} coord
             * @memberof x3dom.nodeTypes.LineSet
             * @initvalue x3dom.nodeTypes.X3DCoordinateNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('coord', x3dom.nodeTypes.X3DCoordinateNode);

            /**
             * If NULL the geometry is rendered using the Material and texture defined in the Appearance node. If not NULL the field shall contain a Color node whose colours are applied depending on the value of "colorPerVertex".
             * @var {x3dom.fields.SFNode} color
             * @memberof x3dom.nodeTypes.LineSet
             * @initvalue x3dom.nodeTypes.X3DColorNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('color', x3dom.nodeTypes.X3DColorNode);

            this._mesh._primType = "LINES";
            x3dom.Utils.needLineWidth = true;
        
        },
        {
            nodeChanged: function() {
                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode);
                var positions = coordNode.getPoints();

                this._mesh._positions[0] = positions.toGL();

                var colorNode = this._cf.color.node;
                if (colorNode) {
                    var colors = colorNode._vf.color;

                    this._mesh._colors[0] = colors.toGL();

                    this._mesh._numColComponents = 3;
                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        this._mesh._numColComponents = 4;
                    }
                }

                var cnt = 0;
                this._mesh._indices[0] = [];

                for (var i=0, n=this._vf.vertexCount.length; i<n; i++) {
                    var vc = this._vf.vertexCount[i];
                    if (vc < 2) {
                        x3dom.debug.logError("LineSet.vertexCount must not be smaller than 2!");
                        break;
                    }
                    for (var j=vc-2; j>=0; j--) {
                        this._mesh._indices[0].push(cnt++, cnt);
                        if (j == 0) cnt++;
                    }
                }
            },

            fieldChanged: function(fieldName) {
                if (fieldName == "coord") {
                    var pnts = this._cf.coord.node.getPoints();
                    this._mesh._positions[0] = pnts.toGL();

                    this.invalidateVolume();
                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color") {
                    var cols = this._cf.color.node._vf.color;
                    this._mesh._colors[0] = cols.toGL();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### IndexedLineSet ### */
x3dom.registerNodeType(
    "IndexedLineSet",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for IndexedLineSet
         * @constructs x3dom.nodeTypes.IndexedLineSet
         * @x3d 3.3
         * @component Rendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc IndexedLineSet is a geometry node that can contain a Color node and a Coordinate node.
         * Color values or a Material emissiveColor is used to draw lines and points. Lines are not lit, are not texture-mapped, and do not participate in collision detection.
         * Hint: use a different color (or emissiveColor) than the background color.
         * Hint: if rendering Coordinate points originally defined for an IndexedFaceSet, index values may need to repeat each initial vertex to close each polygon outline.
         * Hint: insert a Shape node before adding geometry or Appearance. You can also substitute a type-matched ProtoInstance for content.
         */
        function (ctx) {
            x3dom.nodeTypes.IndexedLineSet.superClass.call(this, ctx);


            /**
             * Whether Color node is applied per vertex (true) or per polygon (false).
             * @var {x3dom.fields.SFBool} colorPerVertex
             * @memberof x3dom.nodeTypes.IndexedLineSet
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'colorPerVertex', true);  // TODO


            /**
             * If the "attrib" field is not empty it shall contain a list of per-vertex attribute information for programmable shaders
             * @var {x3dom.fields.MFNode} attrib
             * @memberof x3dom.nodeTypes.IndexedLineSet
             * @initvalue x3dom.nodeTypes.X3DVertexAttributeNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('attrib', x3dom.nodeTypes.X3DVertexAttributeNode);

            /**
             * Coordinate node specifiying the vertices used by the geometry.
             * @var {x3dom.fields.SFNode} coord
             * @memberof x3dom.nodeTypes.IndexedLineSet
             * @initvalue x3dom.nodeTypes.X3DCoordinateNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('coord', x3dom.nodeTypes.X3DCoordinateNode);

            /**
             * If NULL the geometry is rendered using the Material and texture defined in the Appearance node. If not NULL the field shall contain a Color node whose colours are applied depending on the value of "colorPerVertex".
             * @var {x3dom.fields.SFNode} color
             * @memberof x3dom.nodeTypes.IndexedLineSet
             * @initvalue x3dom.nodeTypes.X3DColorNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('color', x3dom.nodeTypes.X3DColorNode);


            /**
             * coordIndex indices provide order in which coordinates are applied.
             * Order starts at index 0, commas are optional between sets, use -1 to separate indices for each polyline.
             * Hint: if rendering Coordinate points originally defined for an IndexedFaceSet, index values may need to repeat initial each initial vertex to close the polygons.
             * @var {x3dom.fields.MFInt32} coordIndex
             * @range [0, inf] or -1
             * @memberof x3dom.nodeTypes.IndexedLineSet
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFInt32(ctx, 'coordIndex', []);

            /**
             * colorIndex indices provide order in which colors are applied.
             * Hint: if rendering Coordinate points originally defined for an IndexedFaceSet, index values may need to repeat initial each initial vertex to close the polygons.
             * @var {x3dom.fields.MFInt32} colorIndex
             * @range [0, inf] or -1
             * @memberof x3dom.nodeTypes.IndexedLineSet
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFInt32(ctx, 'colorIndex', []);

            this._mesh._primType = 'LINES';
            x3dom.Utils.needLineWidth = true;
        
        },
        {

            _buildGeometry: function ()
            {
                var time0 = new Date().getTime();

                // this.handleAttribs();

                var indexes = this._vf.coordIndex;
                var colorInd = this._vf.colorIndex;

                var hasColor = false, hasColorInd = false;

                // TODO; implement colorPerVertex also for single index
                var colPerVert = this._vf.colorPerVertex;

                if (colorInd.length == indexes.length)
                {
                    hasColorInd = true;
                }

                var positions, colors;

                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode);

                positions = coordNode.getPoints();

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                if (colorNode)
                {
                    hasColor = true;
                    colors = colorNode._vf.color;

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }
                else {
                    hasColor = false;
                }

                this._mesh._indices[0] = [];
                this._mesh._positions[0] = [];
                this._mesh._colors[0] = [];

                var i, t, cnt, lineCnt;
                var p0, p1, c0, c1;
                
                // Found MultiIndex Mesh OR LineSet with too many vertices for 16 bit
                if ( (hasColor && hasColorInd) || positions.length > x3dom.Utils.maxIndexableCoords )
                {
                    t = 0;
                    cnt = 0;
                    lineCnt = 0;

                    for (i=0; i < indexes.length; ++i)
                    {
                        //Ignore out of Range Indices
                        if(indexes[i] > positions.length-1)
                        {
                            continue;
                        }

                        if (indexes[i] === -1) {
                            t = 0;
                            continue;
                        }

                        if (hasColorInd) {
                            x3dom.debug.assert(colorInd[i] != -1);
                        }

                        switch (t)
                        {
                            case 0:
                                p0 = +indexes[i];
                                if (hasColorInd && colPerVert) { c0 = +colorInd[i]; }
                                else { c0 = p0; }
                                t = 1;
                                break;
                            case 1:
                                p1 = +indexes[i];
                                if (hasColorInd && colPerVert) { c1 = +colorInd[i]; }
                                else if (hasColorInd && !colPerVert) { c1 = +colorInd[lineCnt]; }
                                else { c1 = p1; }

                                this._mesh._indices[0].push(cnt++, cnt++);

                                this._mesh._positions[0].push(positions[p0].x);
                                this._mesh._positions[0].push(positions[p0].y);
                                this._mesh._positions[0].push(positions[p0].z);
                                this._mesh._positions[0].push(positions[p1].x);
                                this._mesh._positions[0].push(positions[p1].y);
                                this._mesh._positions[0].push(positions[p1].z);

                                if (hasColor) {
                                    if (!colPerVert) {
                                        c0 = c1;
                                    }
                                    this._mesh._colors[0].push(colors[c0].r);
                                    this._mesh._colors[0].push(colors[c0].g);
                                    this._mesh._colors[0].push(colors[c0].b);
                                    this._mesh._colors[0].push(colors[c1].r);
                                    this._mesh._colors[0].push(colors[c1].g);
                                    this._mesh._colors[0].push(colors[c1].b);
                                }

                                t = 2;
                                lineCnt++;
                                break;
                            case 2:
                                p0 = p1;
                                c0 = c1;
                                p1 = +indexes[i];
                                if (hasColorInd && colPerVert) { c1 = +colorInd[i]; }
                                else if (hasColorInd && !colPerVert) { c1 = +colorInd[lineCnt]; }
                                else { c1 = p1; }

                                this._mesh._indices[0].push(cnt++, cnt++);

                                this._mesh._positions[0].push(positions[p0].x);
                                this._mesh._positions[0].push(positions[p0].y);
                                this._mesh._positions[0].push(positions[p0].z);
                                this._mesh._positions[0].push(positions[p1].x);
                                this._mesh._positions[0].push(positions[p1].y);
                                this._mesh._positions[0].push(positions[p1].z);

                                if (hasColor) {
                                    if (!colPerVert) {
                                        c0 = c1;
                                    }
                                    this._mesh._colors[0].push(colors[c0].r);
                                    this._mesh._colors[0].push(colors[c0].g);
                                    this._mesh._colors[0].push(colors[c0].b);
                                    this._mesh._colors[0].push(colors[c1].r);
                                    this._mesh._colors[0].push(colors[c1].g);
                                    this._mesh._colors[0].push(colors[c1].b);
                                }

                                lineCnt++;
                                break;
                            default:
                        }
                    }

                    //if the LineSet is too large for 16 bit indices, split it!
                    if (positions.length > x3dom.Utils.maxIndexableCoords)
                        this._mesh.splitMesh(2);
                } // if isMulti
                else
                {
                    var n = indexes.length;
                    t = 0;

                    for (i=0; i < n; ++i)
                    {
                        if (indexes[i] == -1) {
                            t = 0;
                            continue;
                        }

                        switch (t) {
                            case 0: p0 = +indexes[i]; t = 1; break;
                            case 1: p1 = +indexes[i]; t = 2; this._mesh._indices[0].push(p0, p1); break;
                            case 2: p0 = p1; p1 = +indexes[i]; this._mesh._indices[0].push(p0, p1); break;
                        }
                    }

                    this._mesh._positions[0] = positions.toGL();

                    if (hasColor) {
                        this._mesh._colors[0] = colors.toGL();
                        this._mesh._numColComponents = numColComponents;
                    }
                }

                this.invalidateVolume();
                this._mesh._numCoords = 0;

                for (i=0; i<this._mesh._indices.length; i++) {
                    this._mesh._numCoords += this._mesh._positions[i].length / 3;
                }

                var time1 = new Date().getTime() - time0;
                //x3dom.debug.logInfo("Mesh load time: " + time1 + " ms");
            },

            nodeChanged: function()
            {
                this._buildGeometry();
            },

            fieldChanged: function(fieldName)
            {
                var pnts = null;

                if (fieldName == "coord")
                {
                    this._buildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color")
                {
                    this._buildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
                else if (fieldName == "coordIndex") {
                    this._buildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.indexes = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "colorIndex") {
                    this._buildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### IndexedTriangleSet ### */
x3dom.registerNodeType(
    "IndexedTriangleSet",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DComposedGeometryNode,
        
        /**
         * Constructor for IndexedTriangleSet
         * @constructs x3dom.nodeTypes.IndexedTriangleSet
         * @x3d 3.3
         * @component Rendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposedGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc IndexedTriangleSet is a geometry node that can contain a Color, Coordinate, Normal and TextureCoordinate node.
         * Hint: insert a Shape node before adding geometry or Appearance.
         * You can also substitute a type-matched ProtoInstance for content.
         */
        function (ctx) {
            x3dom.nodeTypes.IndexedTriangleSet.superClass.call(this, ctx);


            /**
             * index specifies triangles by connecting Coordinate vertices.
             * @var {x3dom.fields.MFInt32} index
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.IndexedTriangleSet
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFInt32(ctx, 'index', []);
        
        },
        {
            nodeChanged: function()
            {
                var time0 = new Date().getTime();

                this.handleAttribs();

                var colPerVert  = this._vf.colorPerVertex;
                var normPerVert = this._vf.normalPerVertex;
                var ccw = this._vf.ccw;

                var indexes = this._vf.index;

                var hasNormal = false, hasTexCoord = false, hasColor = false;
                var positions, normals, texCoords, colors;

                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode);
                positions = coordNode._vf.point;

                var normalNode = this._cf.normal.node;
                if (normalNode) {
                    hasNormal = true;
                    normals = normalNode._vf.vector;
                }
                else {
                    hasNormal = false;
                }

                var texMode = "", numTexComponents = 2;
                var texCoordNode = this._cf.texCoord.node;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }
                if (texCoordNode) {
                    if (texCoordNode._vf.point) {
                        hasTexCoord = true;
                        texCoords = texCoordNode._vf.point;

                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }
                    }
                    else if (texCoordNode._vf.mode) {
                        texMode = texCoordNode._vf.mode;
                    }
                }
                else {
                    hasTexCoord = false;
                }

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                if (colorNode) {
                    hasColor = true;
                    colors = colorNode._vf.color;

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }
                else {
                    hasColor = false;
                }

                this._mesh._indices[0] = [];
                this._mesh._positions[0] = [];
                this._mesh._normals[0] = [];
                this._mesh._texCoords[0] = [];
                this._mesh._colors[0] = [];

                var i, t, cnt, faceCnt, posMax;
                var p0, p1, p2, n0, n1, n2, t0, t1, t2, c0, c1, c2;

                // if positions array too short add degenerate triangle
                while (positions.length % 3 > 0) {
                    positions.push(positions.length-1);
                }
                posMax = positions.length;

                //resolve indices, if necessary
                if (!normPerVert || !colPerVert || posMax > x3dom.Utils.maxIndexableCoords)
                {
                    t = 0;
                    cnt = 0;
                    faceCnt = 0;
                    this._mesh._multiIndIndices = [];
                    this._mesh._posSize = positions.length;

                    for (i=0; i < indexes.length; ++i)
                    {
                        // Convert non-triangular polygons to a triangle fan
                        // (TODO: this assumes polygons are convex)

                        if ((i > 0) && (i % 3 === 0 )) {
                            t = 0;
                            faceCnt++;
                        }

                        //TODO: OPTIMIZE but think about cache coherence regarding arrays!!!
                        switch (t)
                        {
                            case 0:
                                p0 = +indexes[i];
                                if (normPerVert) {
                                    n0 = p0;
                                } else if (!normPerVert) {
                                    n0 = faceCnt;
                                }
                                t0 = p0;
                                if (colPerVert) {
                                    c0 = p0;
                                } else if (!colPerVert) {
                                    c0 = faceCnt;
                                }
                                t = 1;
                                break;
                            case 1:
                                p1 = +indexes[i];
                                if (normPerVert) {
                                    n1 = p1;
                                } else if (!normPerVert) {
                                    n1 = faceCnt;
                                }
                                t1 = p1;
                                if (colPerVert) {
                                    c1 = p1;
                                } else if (!colPerVert) {
                                    c1 = faceCnt;
                                }
                                t = 2;
                                break;
                            case 2:
                                p2 = +indexes[i];
                                if (normPerVert) {
                                    n2 = p2;
                                } else if (!normPerVert) {
                                    n2 = faceCnt;
                                }
                                t2 = p2;
                                if (colPerVert) {
                                    c2 = p2;
                                } else if (!colPerVert) {
                                    c2 = faceCnt;
                                }
                                t = 3;

                                this._mesh._indices[0].push(cnt++, cnt++, cnt++);

                                this._mesh._positions[0].push(positions[p0].x);
                                this._mesh._positions[0].push(positions[p0].y);
                                this._mesh._positions[0].push(positions[p0].z);
                                this._mesh._positions[0].push(positions[p1].x);
                                this._mesh._positions[0].push(positions[p1].y);
                                this._mesh._positions[0].push(positions[p1].z);
                                this._mesh._positions[0].push(positions[p2].x);
                                this._mesh._positions[0].push(positions[p2].y);
                                this._mesh._positions[0].push(positions[p2].z);

                                if (hasNormal) {
                                    this._mesh._normals[0].push(normals[n0].x);
                                    this._mesh._normals[0].push(normals[n0].y);
                                    this._mesh._normals[0].push(normals[n0].z);
                                    this._mesh._normals[0].push(normals[n1].x);
                                    this._mesh._normals[0].push(normals[n1].y);
                                    this._mesh._normals[0].push(normals[n1].z);
                                    this._mesh._normals[0].push(normals[n2].x);
                                    this._mesh._normals[0].push(normals[n2].y);
                                    this._mesh._normals[0].push(normals[n2].z);
                                }
                                else {
                                    this._mesh._multiIndIndices.push(p0, p1, p2);
                                    //this._mesh._multiIndIndices.push(cnt-3, cnt-2, cnt-1);
                                }

                                if (hasColor) {
                                    this._mesh._colors[0].push(colors[c0].r);
                                    this._mesh._colors[0].push(colors[c0].g);
                                    this._mesh._colors[0].push(colors[c0].b);
                                    if (numColComponents === 4) {
                                        this._mesh._colors[0].push(colors[c0].a);
                                    }
                                    this._mesh._colors[0].push(colors[c1].r);
                                    this._mesh._colors[0].push(colors[c1].g);
                                    this._mesh._colors[0].push(colors[c1].b);
                                    if (numColComponents === 4) {
                                        this._mesh._colors[0].push(colors[c1].a);
                                    }
                                    this._mesh._colors[0].push(colors[c2].r);
                                    this._mesh._colors[0].push(colors[c2].g);
                                    this._mesh._colors[0].push(colors[c2].b);
                                    if (numColComponents === 4) {
                                        this._mesh._colors[0].push(colors[c2].a);
                                    }
                                }

                                if (hasTexCoord) {
                                    this._mesh._texCoords[0].push(texCoords[t0].x);
                                    this._mesh._texCoords[0].push(texCoords[t0].y);
                                    if (numTexComponents === 3) {
                                        this._mesh._texCoords[0].push(texCoords[t0].z);
                                    }
                                    this._mesh._texCoords[0].push(texCoords[t1].x);
                                    this._mesh._texCoords[0].push(texCoords[t1].y);
                                    if (numTexComponents === 3) {
                                        this._mesh._texCoords[0].push(texCoords[t1].z);
                                    }
                                    this._mesh._texCoords[0].push(texCoords[t2].x);
                                    this._mesh._texCoords[0].push(texCoords[t2].y);
                                    if (numTexComponents === 3) {
                                        this._mesh._texCoords[0].push(texCoords[t2].z);
                                    }
                                }

                                //faceCnt++;
                                break;
                            default:
                        }
                    }

                    if (!hasNormal) {
                        this._mesh.calcNormals(normPerVert ? Math.PI : 0);
                        //normalsPerFace case needs testing
                        //this._mesh.calcNormals(normPerVert ? Math.PI : 0, ccw);
                    }
                    if (!hasTexCoord) {
                        this._mesh.calcTexCoords(texMode);
                    }

                    this._mesh.splitMesh();

                    //x3dom.debug.logInfo(this._mesh._indices.length);
                } // if isMulti
                else
                {
                    faceCnt = 0;
                    for (i=0; i<indexes.length; i++)
                    {
                        if ((i > 0) && (i % 3 === 0 )) {
                            faceCnt++;
                        }

                        this._mesh._indices[0].push(indexes[i]);

                        if(!normPerVert && hasNormal) {
                            this._mesh._normals[0].push(normals[faceCnt].x);
                            this._mesh._normals[0].push(normals[faceCnt].y);
                            this._mesh._normals[0].push(normals[faceCnt].z);
                        }
                        if(!colPerVert && hasColor) {
                            this._mesh._colors[0].push(colors[faceCnt].r);
                            this._mesh._colors[0].push(colors[faceCnt].g);
                            this._mesh._colors[0].push(colors[faceCnt].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(colors[faceCnt].a);
                            }
                        }
                    }

                    this._mesh._positions[0] = positions.toGL();

                    if (hasNormal) {
                        this._mesh._normals[0] = normals.toGL();
                    }
                    else {
                        this._mesh.calcNormals(normPerVert ? Math.PI : 0, ccw);
                    }

                    if (hasTexCoord) {
                        this._mesh._texCoords[0] = texCoords.toGL();
                        this._mesh._numTexComponents = numTexComponents;
                    }
                    else {
                        this._mesh.calcTexCoords(texMode);
                    }

                    if (hasColor && colPerVert) {
                        this._mesh._colors[0] = colors.toGL();
                        this._mesh._numColComponents = numColComponents;
                    }
                }

                this.invalidateVolume();

                this._mesh._numFaces = 0;
                this._mesh._numCoords = 0;
                for (i=0; i<this._mesh._indices.length; i++) {
                    this._mesh._numFaces += this._mesh._indices[i].length / 3;
                    this._mesh._numCoords += this._mesh._positions[i].length / 3;
                }

                var time1 = new Date().getTime() - time0;
                //x3dom.debug.logInfo("Mesh load time: " + time1 + " ms");
            },

            fieldChanged: function(fieldName)
            {
                var pnts = this._cf.coord.node._vf.point;

                if ( pnts.length > x3dom.Utils.maxIndexableCoords )  // are there other problematic cases?
                {
                    // TODO; implement
                    x3dom.debug.logWarning("IndexedTriangleSet: fieldChanged with " +
                        "too many coordinates not yet implemented!");
                    return;
                }

                if (fieldName == "coord")
                {
                    this._mesh._positions[0] = pnts.toGL();

                    // tells the mesh that its bbox requires update
                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color")
                {
                    pnts = this._cf.color.node._vf.color;

                    if (this._vf.colorPerVertex) {

                        this._mesh._colors[0] = pnts.toGL();

                    } else if (!this._vf.colorPerVertex) {

                        var faceCnt = 0;
                        var numColComponents = 3;
                        if (x3dom.isa(this._cf.color.node, x3dom.nodeTypes.ColorRGBA)) {
                            numColComponents = 4;
                        }

                        this._mesh._colors[0] = [];

                        var indexes = this._vf.index;
                        for (var i=0; i < indexes.length; ++i)
                        {
                            if ((i > 0) && (i % 3 === 0 )) {
                                faceCnt++;
                            }

                            this._mesh._colors[0].push(pnts[faceCnt].r);
                            this._mesh._colors[0].push(pnts[faceCnt].g);
                            this._mesh._colors[0].push(pnts[faceCnt].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(pnts[faceCnt].a);
                            }
                        }
                    }
                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
                else if (fieldName == "normal")
                {
                    pnts = this._cf.normal.node._vf.vector;

                    if (this._vf.normalPerVertex) {

                        this._mesh._normals[0] = pnts.toGL();

                    } else if (!this._vf.normalPerVertex) {

                        var indexes = this._vf.index;
                        this._mesh._normals[0] = [];

                        var faceCnt = 0;
                        for (var i=0; i < indexes.length; ++i)
                        {
                            if ((i > 0) && (i % 3 === 0 )) {
                                faceCnt++;
                            }

                            this._mesh._normals[0].push(pnts[faceCnt].x);
                            this._mesh._normals[0].push(pnts[faceCnt].y);
                            this._mesh._normals[0].push(pnts[faceCnt].z);
                        }
                    }

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.normals = true;
                    });
                }
                else if (fieldName == "texCoord")
                {
                    var texCoordNode = this._cf.texCoord.node;
                    if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                        if (texCoordNode._cf.texCoord.nodes.length)
                            texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                    }
                    pnts = texCoordNode._vf.point;

                    this._mesh._texCoords[0] = pnts.toGL();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.texcoords = true;
                    });
                }
                // TODO: index
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### IndexedTriangleStripSet ### */
x3dom.registerNodeType(
    "IndexedTriangleStripSet",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DComposedGeometryNode,
        
        /**
         * Constructor for IndexedTriangleStripSet
         * @constructs x3dom.nodeTypes.IndexedTriangleStripSet
         * @x3d 3.3
         * @component Rendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposedGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc IndexedTriangleStripSet is a geometry node that can contain a Color, Coordinate, Normal and TextureCoordinate node.
         * Hint: insert a Shape node before adding geometry or Appearance. You can also substitute a type-matched ProtoInstance for content.
         */
        function (ctx) {
            x3dom.nodeTypes.IndexedTriangleStripSet.superClass.call(this, ctx);


            /**
             * Index specifies triangles by connecting Coordinate vertices.
             * @var {x3dom.fields.MFInt32} index
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.IndexedTriangleStripSet
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFInt32(ctx, 'index', []);

            this._hasIndexOffset = false;
            this._indexOffset = null;
        
        },
        {
            hasIndexOffset: function() {
                return this._hasIndexOffset;
            },

            nodeChanged: function()
            {
                this.handleAttribs();   // check if method is still functional

                var hasNormal = false, hasTexCoord = false, hasColor = false;

                var colPerVert = this._vf.colorPerVertex;
                var normPerVert = this._vf.normalPerVertex;

                var indexes = this._vf.index;
                //Last index value should be -1.
                if (indexes.length && indexes[indexes.length-1] != -1)
                {
                    indexes.push(-1);
                }
                var positions, normals, texCoords, colors;

                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode);
                positions = coordNode._vf.point;

                var normalNode = this._cf.normal.node;
                if (normalNode) {
                    hasNormal = true;
                    normals = normalNode._vf.vector;
                }
                else {
                    hasNormal = false;
                }

                var texMode = "", numTexComponents = 2;
                var texCoordNode = this._cf.texCoord.node;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }
                if (texCoordNode) {
                    if (texCoordNode._vf.point) {
                        hasTexCoord = true;
                        texCoords = texCoordNode._vf.point;

                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }
                    }
                    else if (texCoordNode._vf.mode) {
                        texMode = texCoordNode._vf.mode;
                    }
                }
                else {
                    hasTexCoord = false;
                }
                this._mesh._numTexComponents = numTexComponents;

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                if (colorNode) {
                    hasColor = true;
                    colors = colorNode._vf.color;

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }
                else {
                    hasColor = false;
                }
                this._mesh._numColComponents = numColComponents;

                this._mesh._indices[0] = [];
                this._mesh._positions[0] = [];
                this._mesh._normals[0] = [];
                this._mesh._texCoords[0] = [];
                this._mesh._colors[0] = [];

                this.invalidateVolume();
                this._mesh._numFaces = 0;
                this._mesh._numCoords = 0;

                var faceCnt = 0, cnt = 0;

                if (hasNormal && positions.length <= x3dom.Utils.maxIndexableCoords)
                {
                    this._hasIndexOffset = true;
                    this._indexOffset = [];
                    this._mesh._primType = 'TRIANGLESTRIP';

                    var indexOffset = [ 0 ];

                    for (i=0; i<indexes.length; i++)
                    {
                        if (indexes[i] == -1) {
                            faceCnt++;
                            indexOffset.push(this._mesh._indices[0].length);
                        }
                        else {
                            this._mesh._indices[0].push(+indexes[i]);

                            if(!normPerVert) {
                                this._mesh._normals[0].push(normals[faceCnt].x);
                                this._mesh._normals[0].push(normals[faceCnt].y);
                                this._mesh._normals[0].push(normals[faceCnt].z);
                            }
                            if(!colPerVert) {
                                this._mesh._colors[0].push(colors[faceCnt].r);
                                this._mesh._colors[0].push(colors[faceCnt].g);
                                this._mesh._colors[0].push(colors[faceCnt].b);
                                if (numColComponents === 4) {
                                    this._mesh._colors[0].push(colors[faceCnt].a);
                                }
                            }
                        }
                    }

                    this._mesh._positions[0] = positions.toGL();

                    if(normPerVert) {
                        this._mesh._normals[0] = normals.toGL();
                    }

                    if (hasTexCoord) {
                        this._mesh._texCoords[0] = texCoords.toGL();
                        this._mesh._numTexComponents = numTexComponents;
                    }
                    else {
                        x3dom.debug.logWarning("IndexedTriangleStripSet: no texCoords given and won't calculate!");
                    }

                    if (hasColor) {
                        if(colPerVert) {
                            this._mesh._colors[0] = colors.toGL();
                        }
                        this._mesh._numColComponents = numColComponents;
                    }

                    for (i=1; i<indexOffset.length; i++) {
                        var triCnt = indexOffset[i] - indexOffset[i-1];
                        this._indexOffset.push( {
                            count: triCnt,
                            offset: 2 * indexOffset[i-1]
                        } );

                        this._mesh._numFaces += (triCnt - 2);
                    }
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;
                }
                else
                {
                    this._hasIndexOffset = false;

                    var p1, p2 , p3, n1, n2, n3, t1, t2, t3, c1, c2, c3;

                    var swapOrder = false;

                    for (var i=1; i < indexes.length-2; ++i)
                    {
                        if (indexes[i+1] == -1) {
                            i = i+2;
                            faceCnt++;
                            continue;
                        }

                        // care for counterclockwise point order
                        if (swapOrder) {
                            p1 = indexes[i];
                            p2 = indexes[i-1];
                            p3 = indexes[i+1];
                        }
                        else {
                            p1 = indexes[i-1];
                            p2 = indexes[i];
                            p3 = indexes[i+1];
                        }
                        swapOrder = !swapOrder;

                        if (normPerVert) {
                            n1 = p1;
                            n2 = p2;
                            n3 = p3;
                        } else if (!normPerVert) {
                            n1 = n2 = n3 = faceCnt;
                        }

                        t1 = p1;
                        t2 = p2;
                        t3 = p3;

                        if (colPerVert) {
                            c1 = p1;
                            c2 = p2;
                            c3 = p3;
                        } else if (!colPerVert) {
                            c1 = c2 = c3 = faceCnt;
                        }

                        this._mesh._indices[0].push(cnt++, cnt++, cnt++);

                        this._mesh._positions[0].push(positions[p1].x);
                        this._mesh._positions[0].push(positions[p1].y);
                        this._mesh._positions[0].push(positions[p1].z);
                        this._mesh._positions[0].push(positions[p2].x);
                        this._mesh._positions[0].push(positions[p2].y);
                        this._mesh._positions[0].push(positions[p2].z);
                        this._mesh._positions[0].push(positions[p3].x);
                        this._mesh._positions[0].push(positions[p3].y);
                        this._mesh._positions[0].push(positions[p3].z);

                        if (hasNormal) {
                            this._mesh._normals[0].push(normals[n1].x);
                            this._mesh._normals[0].push(normals[n1].y);
                            this._mesh._normals[0].push(normals[n1].z);
                            this._mesh._normals[0].push(normals[n2].x);
                            this._mesh._normals[0].push(normals[n2].y);
                            this._mesh._normals[0].push(normals[n2].z);
                            this._mesh._normals[0].push(normals[n3].x);
                            this._mesh._normals[0].push(normals[n3].y);
                            this._mesh._normals[0].push(normals[n3].z);
                        }

                        if (hasColor) {
                            this._mesh._colors[0].push(colors[c1].r);
                            this._mesh._colors[0].push(colors[c1].g);
                            this._mesh._colors[0].push(colors[c1].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(colors[c1].a);
                            }
                            this._mesh._colors[0].push(colors[c2].r);
                            this._mesh._colors[0].push(colors[c2].g);
                            this._mesh._colors[0].push(colors[c2].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(colors[c2].a);
                            }
                            this._mesh._colors[0].push(colors[c3].r);
                            this._mesh._colors[0].push(colors[c3].g);
                            this._mesh._colors[0].push(colors[c3].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(colors[c3].a);
                            }
                        }

                        if (hasTexCoord) {
                            this._mesh._texCoords[0].push(texCoords[t1].x);
                            this._mesh._texCoords[0].push(texCoords[t1].y);
                            if (numTexComponents === 3) {
                                this._mesh._texCoords[0].push(texCoords[t1].z);
                            }
                            this._mesh._texCoords[0].push(texCoords[t2].x);
                            this._mesh._texCoords[0].push(texCoords[t2].y);
                            if (numTexComponents === 3) {
                                this._mesh._texCoords[0].push(texCoords[t2].z);
                            }
                            this._mesh._texCoords[0].push(texCoords[t3].x);
                            this._mesh._texCoords[0].push(texCoords[t3].y);
                            if (numTexComponents === 3) {
                                this._mesh._texCoords[0].push(texCoords[t3].z);
                            }
                        }
                    }

                    if (!hasNormal) {
                        this._mesh.calcNormals(Math.PI);
                    }

                    if (!hasTexCoord) {
                        this._mesh.calcTexCoords(texMode);
                    }

                    this._mesh.splitMesh();

                    this.invalidateVolume();

                    for (i=0; i<this._mesh._indices.length; i++) {
                        this._mesh._numFaces += this._mesh._indices[i].length / 3;
                        this._mesh._numCoords += this._mesh._positions[i].length / 3;
                    }
                }
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName != "coord" && fieldName != "normal" &&
                    fieldName != "texCoord" && fieldName != "color")
                {
                    x3dom.debug.logWarning("IndexedTriangleStripSet: fieldChanged for " +
                        fieldName + " not yet implemented!");
                    return;
                }

                var pnts = this._cf.coord.node._vf.point;

                if ((this._cf.normal.node === null) || (pnts.length > x3dom.Utils.maxIndexableCoords))
                {
                    if (fieldName == "coord") {
                        this._mesh._positions[0] = [];
                        this._mesh._indices[0] =[];
                        this._mesh._normals[0] = [];
                        this._mesh._texCoords[0] =[];

                        var hasNormal = false, hasTexCoord = false, hasColor = false;

                        var colPerVert = this._vf.colorPerVertex;
                        var normPerVert = this._vf.normalPerVertex;

                        var indexes = this._vf.index;
                        var positions, normals, texCoords, colors;

                        var coordNode = this._cf.coord.node;
                        x3dom.debug.assert(coordNode);
                        positions = coordNode._vf.point;

                        var normalNode = this._cf.normal.node;
                        if (normalNode) {
                            hasNormal = true;
                            normals = normalNode._vf.vector;
                        }
                        else {
                            hasNormal = false;
                        }

                        var texMode = "", numTexComponents = 2;
                        var texCoordNode = this._cf.texCoord.node;
                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                            if (texCoordNode._cf.texCoord.nodes.length)
                                texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                        }
                        if (texCoordNode) {
                            if (texCoordNode._vf.point) {
                                hasTexCoord = true;
                                texCoords = texCoordNode._vf.point;

                                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                                    numTexComponents = 3;
                                }
                            }
                            else if (texCoordNode._vf.mode) {
                                texMode = texCoordNode._vf.mode;
                            }
                        }
                        else {
                            hasTexCoord = false;
                        }
                        this._mesh._numTexComponents = numTexComponents;

                        var numColComponents = 3;
                        var colorNode = this._cf.color.node;
                        if (colorNode) {
                            hasColor = true;
                            colors = colorNode._vf.color;

                            if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                                numColComponents = 4;
                            }
                        }
                        else {
                            hasColor = false;
                        }
                        this._mesh._numColComponents = numColComponents;

                        this._mesh._indices[0] = [];
                        this._mesh._positions[0] = [];
                        this._mesh._normals[0] = [];
                        this._mesh._texCoords[0] = [];
                        this._mesh._colors[0] = [];

                        var faceCnt = 0, cnt = 0;
                        var p1, p2 , p3, n1, n2, n3, t1, t2, t3, c1, c2, c3;
                        var swapOrder = false;

                        if ( hasNormal  || hasTexCoord || hasColor) {

                            for (var i=1; i < indexes.length-2; ++i)
                            {
                                if (indexes[i+1] == -1) {
                                    i = i+2;
                                    faceCnt++;
                                    continue;
                                }

                                if (swapOrder) {
                                    p1 = indexes[i];
                                    p2 = indexes[i-1];
                                    p3 = indexes[i+1];
                                }
                                else {
                                    p1 = indexes[i-1];
                                    p2 = indexes[i];
                                    p3 = indexes[i+1];
                                }
                                swapOrder = !swapOrder;

                                if (normPerVert) {
                                    n1 = p1;
                                    n2 = p2;
                                    n3 = p3;
                                } else if (!normPerVert) {
                                    n1 = n2 = n3 = faceCnt;
                                }

                                t1 = p1;
                                t2 = p2;
                                t3 = p3;

                                if (colPerVert) {
                                    c1 = p1;
                                    c2 = p2;
                                    c3 = p3;
                                } else if (!colPerVert) {
                                    c1 = c2 = c3 = faceCnt;
                                }

                                this._mesh._indices[0].push(cnt++, cnt++, cnt++);

                                this._mesh._positions[0].push(positions[p1].x);
                                this._mesh._positions[0].push(positions[p1].y);
                                this._mesh._positions[0].push(positions[p1].z);
                                this._mesh._positions[0].push(positions[p2].x);
                                this._mesh._positions[0].push(positions[p2].y);
                                this._mesh._positions[0].push(positions[p2].z);
                                this._mesh._positions[0].push(positions[p3].x);
                                this._mesh._positions[0].push(positions[p3].y);
                                this._mesh._positions[0].push(positions[p3].z);

                                if (hasNormal) {
                                    this._mesh._normals[0].push(normals[n1].x);
                                    this._mesh._normals[0].push(normals[n1].y);
                                    this._mesh._normals[0].push(normals[n1].z);
                                    this._mesh._normals[0].push(normals[n2].x);
                                    this._mesh._normals[0].push(normals[n2].y);
                                    this._mesh._normals[0].push(normals[n2].z);
                                    this._mesh._normals[0].push(normals[n3].x);
                                    this._mesh._normals[0].push(normals[n3].y);
                                    this._mesh._normals[0].push(normals[n3].z);
                                }

                                if (hasColor) {
                                    this._mesh._colors[0].push(colors[c1].r);
                                    this._mesh._colors[0].push(colors[c1].g);
                                    this._mesh._colors[0].push(colors[c1].b);
                                    if (numColComponents === 4) {
                                        this._mesh._colors[0].push(colors[c1].a);
                                    }
                                    this._mesh._colors[0].push(colors[c2].r);
                                    this._mesh._colors[0].push(colors[c2].g);
                                    this._mesh._colors[0].push(colors[c2].b);
                                    if (numColComponents === 4) {
                                        this._mesh._colors[0].push(colors[c2].a);
                                    }
                                    this._mesh._colors[0].push(colors[c3].r);
                                    this._mesh._colors[0].push(colors[c3].g);
                                    this._mesh._colors[0].push(colors[c3].b);
                                    if (numColComponents === 4) {
                                        this._mesh._colors[0].push(colors[c3].a);
                                    }
                                }

                                if (hasTexCoord) {
                                    this._mesh._texCoords[0].push(texCoords[t1].x);
                                    this._mesh._texCoords[0].push(texCoords[t1].y);
                                    if (numTexComponents === 3) {
                                        this._mesh._texCoords[0].push(texCoords[t1].z);
                                    }
                                    this._mesh._texCoords[0].push(texCoords[t2].x);
                                    this._mesh._texCoords[0].push(texCoords[t2].y);
                                    if (numTexComponents === 3) {
                                        this._mesh._texCoords[0].push(texCoords[t2].z);
                                    }
                                    this._mesh._texCoords[0].push(texCoords[t3].x);
                                    this._mesh._texCoords[0].push(texCoords[t3].y);
                                    if (numTexComponents === 3) {
                                        this._mesh._texCoords[0].push(texCoords[t3].z);
                                    }
                                }
                            }

                            if (!hasNormal) {
                                this._mesh.calcNormals(Math.PI);
                            }

                            if (!hasTexCoord) {
                                this._mesh.calcTexCoords(texMode);
                            }

                            this._mesh.splitMesh();

                        } else {
                            var swapOrder = false;
                            for (var i = 1; i < indexes.length; ++i)
                            {
                                if (indexes[i+1] == -1) {
                                    i = i+2;
                                    continue;
                                }

                                if (swapOrder) {
                                    this._mesh._indices[0].push(indexes[i]);
                                    this._mesh._indices[0].push(indexes[i-1]);
                                    this._mesh._indices[0].push(indexes[i+1]);
                                }
                                else {
                                    this._mesh._indices[0].push(indexes[i-1]);
                                    this._mesh._indices[0].push(indexes[i]);
                                    this._mesh._indices[0].push(indexes[i+1]);
                                }
                                swapOrder = !swapOrder;
                            }

                            this._mesh._positions[0] = positions.toGL();

                            if (hasNormal) {
                                this._mesh._normals[0] = normals.toGL();
                            }
                            else {
                                this._mesh.calcNormals(Math.PI);
                            }
                            if (hasTexCoord) {
                                this._mesh._texCoords[0] = texCoords.toGL();
                                this._mesh._numTexComponents = numTexComponents;
                            }
                            else {
                                this._mesh.calcTexCoords(texMode);
                            }
                            if (hasColor) {
                                this._mesh._colors[0] = colors.toGL();
                                this._mesh._numColComponents = numColComponents;
                            }

                        }

                        this.invalidateVolume();
                        this._mesh._numFaces = 0;
                        this._mesh._numCoords = 0;

                        for (i=0; i<this._mesh._indices.length; i++) {
                            this._mesh._numFaces += this._mesh._indices[i].length / 3;
                            this._mesh._numCoords += this._mesh._positions[i].length / 3;
                        }

                        Array.forEach(this._parentNodes, function (node) {
                            node.setAllDirty();
                            node.invalidateVolume();
                        });
                    }
                    else if (fieldName == "color") {
                        var col = this._cf.color.node._vf.color;
                        var faceCnt = 0;
                        var c1 = c2 = c3 = 0;

                        var numColComponents = 3;

                        if (x3dom.isa(this._cf.color.node, x3dom.nodeTypes.ColorRGBA)) {
                            numColComponents = 4;
                        }

                        this._mesh._colors[0] = [];

                        var indexes = this._vf.index;
                        var swapOrder = false;

                        for (i=1; i < indexes.length-2; ++i)
                        {
                            if (indexes[i+1] == -1) {
                                i = i+2;
                                faceCnt++;
                                continue;
                            }

                            if (this._vf.colorPerVertex) {
                                if (swapOrder) {
                                    c1 = indexes[i];
                                    c2 = indexes[i-1];
                                    c3 = indexes[i+1];
                                }
                                else {
                                    c1 = indexes[i-1];
                                    c2 = indexes[i];
                                    c3 = indexes[i+1];
                                }
                                swapOrder = !swapOrder;
                            } else if (!this._vf.colorPerVertex) {
                                c1 = c2 = c3 = faceCnt;
                            }
                            this._mesh._colors[0].push(col[c1].r);
                            this._mesh._colors[0].push(col[c1].g);
                            this._mesh._colors[0].push(col[c1].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(col[c1].a);
                            }
                            this._mesh._colors[0].push(col[c2].r);
                            this._mesh._colors[0].push(col[c2].g);
                            this._mesh._colors[0].push(col[c2].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(col[c2].a);
                            }
                            this._mesh._colors[0].push(col[c3].r);
                            this._mesh._colors[0].push(col[c3].g);
                            this._mesh._colors[0].push(col[c3].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(col[c3].a);
                            }
                        }

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.colors = true;
                        });
                    }
                    else if (fieldName == "normal") {
                        var nor = this._cf.normal.node._vf.vector;
                        var faceCnt = 0;
                        var n1 = n2 = n3 = 0;

                        this._mesh._normals[0] = [];

                        var indexes = this._vf.index;
                        var swapOrder = false;

                        for (i=1; i < indexes.length-2; ++i)
                        {
                            if (indexes[i+1] == -1) {
                                i = i+2;
                                faceCnt++;
                                continue;
                            }

                            if (this._vf.normalPerVertex) {
                                if (swapOrder) {
                                    n1 = indexes[i];
                                    n2 = indexes[i-1];
                                    n3 = indexes[i+1];
                                }
                                else {
                                    n1 = indexes[i-1];
                                    n2 = indexes[i];
                                    n3 = indexes[i+1];
                                }
                                swapOrder = !swapOrder;
                            } else if (!this._vf.normalPerVertex) {
                                n1 = n2 = n3 = faceCnt;
                            }
                            this._mesh._normals[0].push(nor[n1].x);
                            this._mesh._normals[0].push(nor[n1].y);
                            this._mesh._normals[0].push(nor[n1].z);
                            this._mesh._normals[0].push(nor[n2].x);
                            this._mesh._normals[0].push(nor[n2].y);
                            this._mesh._normals[0].push(nor[n2].z);
                            this._mesh._normals[0].push(nor[n3].x);
                            this._mesh._normals[0].push(nor[n3].y);
                            this._mesh._normals[0].push(nor[n3].z);
                        }

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.normals = true;
                        });
                    }
                    else if (fieldName == "texCoord") {
                        var texCoordNode = this._cf.texCoord.node;
                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                            if (texCoordNode._cf.texCoord.nodes.length)
                                texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                        }
                        var tex = texCoordNode._vf.point;
                        var t1 = t2 = t3 = 0;

                        var numTexComponents = 2;

                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }

                        this._mesh._texCoords[0] = [];
                        var indexes = this._vf.index;
                        var swapOrder = false;

                        for (i=1; i < indexes.length-2; ++i)
                        {
                            if (indexes[i+1] == -1) {
                                i = i+2;
                                continue;
                            }

                            if (swapOrder) {
                                t1 = indexes[i];
                                t2 = indexes[i-1];
                                t3 = indexes[i+1];
                            }
                            else {
                                t1 = indexes[i-1];
                                t2 = indexes[i];
                                t3 = indexes[i+1];
                            }
                            swapOrder = !swapOrder;

                            this._mesh._texCoords[0].push(tex[t1].x);
                            this._mesh._texCoords[0].push(tex[t1].y);
                            if (numTexComponents === 3) {
                                this._mesh._texCoords[0].push(tex[t1].z);
                            }
                            this._mesh._texCoords[0].push(tex[t2].x);
                            this._mesh._texCoords[0].push(tex[t2].y);
                            if (numTexComponents === 3) {
                                this._mesh._texCoords[0].tex(col[t2].z);
                            }
                            this._mesh._texCoords[0].push(tex[t3].x);
                            this._mesh._texCoords[0].push(tex[t3].y);
                            if (numTexComponents === 3) {
                                this._mesh._texCoords[0].push(tex[t3].z);
                            }
                        }

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.texcoords = true;
                        });
                    }
                }
                else
                {
                    if (fieldName == "coord")
                    {
                        this._mesh._positions[0] = pnts.toGL();

                        // tells the mesh that its bbox requires update
                        this.invalidateVolume();

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.positions = true;
                            node.invalidateVolume();
                        });
                    }
                    else if (fieldName == "color")
                    {
                        pnts = this._cf.color.node._vf.color;

                        if (this._vf.colorPerVertex) {

                            this._mesh._colors[0] = pnts.toGL();

                        } else if (!this._vf.colorPerVertex) {

                            var faceCnt = 0;
                            var numColComponents = 3;

                            if (x3dom.isa(this._cf.color.node, x3dom.nodeTypes.ColorRGBA)) {
                                numColComponents = 4;
                            }

                            this._mesh._colors[0] = [];

                            var indexes = this._vf.index;
                            for (i=0; i < indexes.length; ++i)
                            {
                                if (indexes[i] == -1) {
                                    faceCnt++;
                                    continue;
                                }

                                this._mesh._colors[0].push(pnts[faceCnt].r);
                                this._mesh._colors[0].push(pnts[faceCnt].g);
                                this._mesh._colors[0].push(pnts[faceCnt].b);
                                if (numColComponents === 4) {
                                    this._mesh._colors[0].push(pnts[faceCnt].a);
                                }
                            }
                        }

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.colors = true;
                        });
                    }
                    else if (fieldName == "normal")
                    {
                        pnts = this._cf.normal.node._vf.vector;

                        if (this._vf.normalPerVertex) {

                            this._mesh._normals[0] = pnts.toGL();

                        } else if (!this._vf.normalPerVertex) {

                            var indexes = this._vf.index;
                            this._mesh._normals[0] = [];

                            var faceCnt = 0;
                            for (i=0; i < indexes.length; ++i)
                            {
                                if (indexes[i] == -1) {
                                    faceCnt++;
                                    continue;
                                }

                                this._mesh._normals[0].push(pnts[faceCnt].x);
                                this._mesh._normals[0].push(pnts[faceCnt].y);
                                this._mesh._normals[0].push(pnts[faceCnt].z);
                            }
                        }

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.normals = true;
                        });
                    }
                    else if (fieldName == "texCoord")
                    {
                        var texCoordNode = this._cf.texCoord.node;
                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                            if (texCoordNode._cf.texCoord.nodes.length)
                                texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                        }
                        pnts = texCoordNode._vf.point;

                        this._mesh._texCoords[0] = pnts.toGL();

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.texcoords = true;
                        });
                    }
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */


/* ### TriangleSet ### */
x3dom.registerNodeType(
    "TriangleSet",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DComposedGeometryNode,

        /**
         * Constructor for TriangleSet
         * @constructs x3dom.nodeTypes.TriangleSet
         * @x3d 3.3
         * @component Rendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposedGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc TriangleSet is a geometry node that can contain a Color, Coordinate, Normal and TextureCoordinate node.
         * Hint: insert a Shape node before adding geometry or Appearance.
         * You can also substitute a type-matched ProtoInstance for content.
         */
        function (ctx) {
            x3dom.nodeTypes.TriangleSet.superClass.call(this, ctx);

        },
        {
            _buildGeometry: function()
            {
                var colPerVert = this._vf.colorPerVertex;
                var normPerVert = this._vf.normalPerVertex;
                var ccw = this._vf.ccw;

                var hasNormal = false, hasTexCoord = false, hasColor = false;
                var positions, normals, texCoords, colors;

                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode);

                if(!coordNode || coordNode._vf.point.length < 3)
                {
                    this._vf.render = false;
                    return;
                }

                positions = coordNode._vf.point;

                var normalNode = this._cf.normal.node;
                if (normalNode) {
                    hasNormal = true;
                    normals = normalNode._vf.vector;
                }
                else {
                    hasNormal = false;
                }

                var texMode = "", numTexComponents = 2;
                var texCoordNode = this._cf.texCoord.node;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }
                if (texCoordNode) {
                    if (texCoordNode._vf.point) {
                        hasTexCoord = true;
                        texCoords = texCoordNode._vf.point;

                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }
                    }
                    else if (texCoordNode._vf.mode) {
                        texMode = texCoordNode._vf.mode;
                    }
                }
                else {
                    hasTexCoord = false;
                }

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                if (colorNode) {
                    hasColor = true;
                    colors = colorNode._vf.color;

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }
                else {
                    hasColor = false;
                }

                while (positions.length % 3 > 0) {
                    positions.pop();
                }

                this._mesh._indices[0] = new Array(positions.length);
                this._mesh._positions[0] = [];
                this._mesh._normals[0] = [];
                this._mesh._texCoords[0] = [];
                this._mesh._colors[0] = [];

                var posMax = positions.length / 3;
                var faceCnt, i = 0;

                for (faceCnt=0; faceCnt<posMax; faceCnt++)
                {
                    // FIXME; get rid of useless internal index field, but this requires modification of renderer
                    this._mesh._indices[0][i] = i++;
                    this._mesh._indices[0][i] = i++;
                    this._mesh._indices[0][i] = i++;

                    if(!normPerVert && hasNormal) {
                        this._mesh._normals[0].push(normals[faceCnt].x);
                        this._mesh._normals[0].push(normals[faceCnt].y);
                        this._mesh._normals[0].push(normals[faceCnt].z);
                    }
                    if(!colPerVert && hasColor) {
                        this._mesh._colors[0].push(colors[faceCnt].r);
                        this._mesh._colors[0].push(colors[faceCnt].g);
                        this._mesh._colors[0].push(colors[faceCnt].b);
                        if (numColComponents === 4) {
                            this._mesh._colors[0].push(colors[faceCnt].a);
                        }
                    }
                }

                this._mesh._positions[0] = positions.toGL();

                if (hasNormal) {
                    this._mesh._normals[0] = normals.toGL();
                }
                else {
                    this._mesh.calcNormals(normPerVert ? Math.PI : 0, ccw);
                }

                if (hasTexCoord) {
                    this._mesh._texCoords[0] = texCoords.toGL();
                    this._mesh._numTexComponents = numTexComponents;
                }
                else {
                    this._mesh.calcTexCoords(texMode);
                }

                if (hasColor && colPerVert) {
                    this._mesh._colors[0] = colors.toGL();
                    this._mesh._numColComponents = numColComponents;
                }

                this._mesh._numFaces = posMax;
                this._mesh._numCoords = positions.length;

                this.invalidateVolume();

            },

            nodeChanged: function()
            {
                this._buildGeometry();
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "coord")
                {
                    this._buildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color")
                {
                    this._buildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
                else if (fieldName == "normal")
                {
                    this._buildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.normals = true;
                    });
                }
                else if (fieldName == "texCoord")
                {
                    this._buildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.texcoords = true;
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DGeometricPropertyNode ### */
x3dom.registerNodeType(
    "X3DGeometricPropertyNode",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for X3DGeometricPropertyNode
         * @constructs x3dom.nodeTypes.X3DGeometricPropertyNode
         * @x3d 3.3
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for all geometric property node types defined in X3D.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DGeometricPropertyNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DCoordinateNode ### */
x3dom.registerNodeType(
    "X3DCoordinateNode",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DGeometricPropertyNode,
        
        /**
         * Constructor for X3DCoordinateNode
         * @constructs x3dom.nodeTypes.X3DCoordinateNode
         * @x3d 3.3
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DGeometricPropertyNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for all coordinate node types in X3D.
         * All coordinates are specified in nodes derived from this abstract node type.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DCoordinateNode.superClass.call(this, ctx);
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName === "coord" || fieldName === "point") {
                    Array.forEach(this._parentNodes, function (node) {
                        node.fieldChanged("coord");
                    });
                }
            },

            parentAdded: function (parent) {
                if (parent._mesh && parent._cf.coord.node !== this) {
                    parent.fieldChanged("coord");
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Coordinate ### */
x3dom.registerNodeType(
    "Coordinate",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DCoordinateNode,
        
        /**
         * Constructor for Coordinate
         * @constructs x3dom.nodeTypes.Coordinate
         * @x3d 3.3
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DCoordinateNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Coordinate builds geometry using a set of 3D coordinates.
         * Coordinate is used by IndexedFaceSet, IndexedLineSet, LineSet and PointSet.
         */
        function (ctx) {
            x3dom.nodeTypes.Coordinate.superClass.call(this, ctx);


            /**
             * Contains the 3D coordinates
             * @var {x3dom.fields.MFVec3f} point
             * @memberof x3dom.nodeTypes.Coordinate
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'point', []);
        
        },
        {
            getPoints: function() {
                return this._vf.point;
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Normal ### */
x3dom.registerNodeType(
    "Normal",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DGeometricPropertyNode,
        
        /**
         * Constructor for Normal
         * @constructs x3dom.nodeTypes.Normal
         * @x3d 3.3
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DGeometricPropertyNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Normal is a set of 3D surface-normal vectors Normal values are optional perpendicular directions, used per-polygon or per-vertex for lighting and shading.
         * Hint: used by IndexedFaceSet and ElevationGrid.
         */
        function (ctx) {
            x3dom.nodeTypes.Normal.superClass.call(this, ctx);


            /**
             * set of unit-length normal vectors, corresponding to indexed polygons or vertices.
             * @var {x3dom.fields.MFVec3f} vector
             * @range [-1, 1]
             * @memberof x3dom.nodeTypes.Normal
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFVec3f(ctx, 'vector', []);
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName === "normal" || fieldName === "vector") {
                    Array.forEach(this._parentNodes, function (node) {
                        node.fieldChanged("normal");
                    });
                }
            },

            parentAdded: function (parent) {
                if (parent._mesh && //parent._cf.coord.node &&
                    parent._cf.normal.node !== this) {
                    parent.fieldChanged("normal");
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DColorNode ### */
x3dom.registerNodeType(
    "X3DColorNode",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DGeometricPropertyNode,
        
        /**
         * Constructor for X3DColorNode
         * @constructs x3dom.nodeTypes.X3DColorNode
         * @x3d 3.3
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DGeometricPropertyNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for color specifications in X3D.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DColorNode.superClass.call(this, ctx);
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName === "color") {
                    Array.forEach(this._parentNodes, function (node) {
                        node.fieldChanged("color");
                    });
                }
            },

            parentAdded: function (parent) {
                if (parent._mesh && //parent._cf.coord.node &&
                    parent._cf.color.node !== this) {
                    parent.fieldChanged("color");
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Color ### */
x3dom.registerNodeType(
    "Color",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DColorNode,
        
        /**
         * Constructor for Color
         * @constructs x3dom.nodeTypes.Color
         * @x3d 3.3
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DColorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This node defines a set of RGB colors to be used in the fields of another node.
         * Color nodes are only used to specify multiple colours for a single geometric shape, such as colours for the faces or vertices of an IndexedFaceSet.
         * A Material node is used to specify the overall material parameters of lit geometry.
         * If both a Material node and a Color node are specified for a geometric shape, the colours shall replace the diffuse component of the material.
         * RGB or RGBA textures take precedence over colours; specifying both an RGB or RGBA texture and a Color node for geometric shape will result in the Color node being ignored.
         */
        function (ctx) {
            x3dom.nodeTypes.Color.superClass.call(this, ctx);


            /**
             * The RGB colors.
             * @var {x3dom.fields.MFColor} color
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.Color
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFColor(ctx, 'color', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ColorRGBA ### */
x3dom.registerNodeType(
    "ColorRGBA",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DColorNode,
        
        /**
         * Constructor for ColorRGBA
         * @constructs x3dom.nodeTypes.ColorRGBA
         * @x3d 3.3
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DColorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This node defines a set of RGBA colours to be used in the fields of another node.
         * RGBA color nodes are only used to specify multiple colours with alpha for a single geometric shape, such as colours for the faces or vertices of an IndexedFaceSet.
         * A Material node is used to specify the overall material parameters of lit geometry.
         * If both a Material node and a ColorRGBA node are specified for a geometric shape, the colours shall replace the diffuse and transparency components of the material.
         * RGB or RGBA textures take precedence over colours; specifying both an RGB or RGBA texture and a ColorRGBA node for geometric shape will result in the ColorRGBA node being ignored.
         */
        function (ctx) {
            x3dom.nodeTypes.ColorRGBA.superClass.call(this, ctx);


            /**
             * The set of RGBA colors
             * @var {x3dom.fields.MFColorRGBA} color
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.ColorRGBA
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFColorRGBA(ctx, 'color', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* This is only a first stub */

/* ### ParticleSet ### */
x3dom.registerNodeType(
    "ParticleSet",
    "Rendering",
    defineClass(x3dom.nodeTypes.PointSet,

        /**
         * Constructor for ParticleSet
         * @constructs x3dom.nodeTypes.ParticleSet
         * @x3d x.x
         * @component Rendering
         * @status experimental
         * @extends x3dom.nodeTypes.PointSet
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ParticleSet is a geometry node used in combination with a ParticleSystem node.
         *  Attention: So far this is only a stub.
         */
         function (ctx) {
            x3dom.nodeTypes.ParticleSet.superClass.call(this, ctx);

            /**
             * Drawing mode: "ViewDirQuads" - Draws quads directed to the viewpoint (default). "Points" - Draw points.
             * "Lines" - Draw lines. These modes must not match the finally supported modes.
             * @var {x3dom.fields.SFString} mode
             * @memberof x3dom.nodeTypes.ParticleSet
             * @initvalue ViewDirQuads
             * @range [ViewDirQuads, Points, Lines, Arrows, ViewerArrows, ViewerQuads, Rectangles]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'mode', 'ViewDirQuads'); // only default value supported

            /**
             * Defines the drawing order for the particles. Possible values: "Any" - The order is undefined.
             *  "BackToFront" - Draw from back to front. "FrontToBack" - Draw from front to back.
             * @var {x3dom.fields.SFString} drawOrder
             * @memberof x3dom.nodeTypes.ParticleSet
             * @initvalue Any
             * @range [Any, BackToFront, FrontToBack]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'drawOrder', 'Any');

            // THINKABOUTME; does this very special field makes sense for being impl. in WebGL?
            //this.addField_SFNode('secCoord', x3dom.nodeTypes.X3DCoordinateNode); // NOT YET SUPPORTED!

            /**
             * Stores a Normal node containing the normals of the particles.
             * @var {x3dom.fields.SFNode} normal
             * @memberof x3dom.nodeTypes.ParticleSet
             * @initvalue null
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('normal', x3dom.nodeTypes.Normal); // NOT YET SUPPORTED

            /**
             * An MFVec3f field containing the sizes of the particles.
             * @var {x3dom.fields.MFVec3f} size
             * @memberof x3dom.nodeTypes.ParticleSet
             * @field x3dom
             * @instance
             */
            this.addField_MFVec3f(ctx, 'size', []);

            /**
             * An MFInt32 field containing indices which specify the order of the vertices in the "coord" field.
             * @var {x3dom.fields.MFInt32} index
             * @memberof x3dom.nodeTypes.ParticleSet
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'index', []);

            /**
             * An MFFloat field containing z-values for the texture of a particle (used with 3D textures).
             * @var {x3dom.fields.MFFloat} textureZ
             * @memberof x3dom.nodeTypes.ParticleSet
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'textureZ', []); // NOT YET SUPPORTED! (3D textures not supported in WebGL)

            this._mesh._primType = 'POINTS';
        },
        {
            drawOrder: function() {
                return this._vf.drawOrder.toLowerCase();
            },

            nodeChanged: function()
            {
                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode, "ParticleSet without coord node!");
                var positions = coordNode.getPoints();

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                var colors = new x3dom.fields.MFColor();
                if (colorNode) {
                    colors = colorNode._vf.color;
                    x3dom.debug.assert(positions.length == colors.length, "Size of color and coord array differs!");

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }

                var normalNode = this._cf.normal.node;
                var normals = new x3dom.fields.MFVec3f();
                if (normalNode) {
                    normals = normalNode._vf.vector;
                }

                var indices = [];
                if (this.drawOrder() != "any") {
                    indices = this._vf.index.toGL();

                    // generate indices since also used for sorting
                    if (indices.length == 0) {
                        var i, n = positions.length;
                        indices = new Array(n);
                        for (i = 0; i < n; i++) {
                            indices[i] = i;
                        }
                    }
                }

                this._mesh._numColComponents = numColComponents;
                this._mesh._lit = false;

                this._mesh._indices[0] = indices;
                this._mesh._positions[0] = positions.toGL();
                this._mesh._colors[0] = colors.toGL();
                this._mesh._normals[0] = normals.toGL();
                this._mesh._texCoords[0] = [];

                this.invalidateVolume();
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function(fieldName)
            {
                var pnts = null;

                if (fieldName == "index")
                {
                    this._mesh._indices[0] = this._vf.index.toGL();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.indexes = true;
                    });
                }
                else if (fieldName == "size")
                {
                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.specialAttribs = true;
                    });
                }
                else if (fieldName == "coord")
                {
                    pnts = this._cf.coord.node.getPoints();

                    this._mesh._positions[0] = pnts.toGL();

                    var indices = [];
                    if (this.drawOrder() != "any") {
                        indices = this._vf.index.toGL();

                        // generate indices since also used for sorting
                        if (indices.length == 0) {
                            var i, n = pnts.length;
                            indices = new Array(n);
                            for (i = 0; i < n; i++) {
                                indices[i] = i;
                            }
                        }
                    }
                    this._mesh._indices[0] = indices;

                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node._dirty.indexes = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color")
                {
                    pnts = this._cf.color.node._vf.color;

                    this._mesh._colors[0] = pnts.toGL();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ClipPlane ### */
x3dom.registerNodeType(
    "ClipPlane",
    "Rendering",
    defineClass(x3dom.nodeTypes.X3DChildNode,

        /**
         * Constructor for ClipPlane
         * @constructs x3dom.nodeTypes.ClipPlane
         * @x3d 3.2
         * @component Rendering
         * @status full
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc A clip plane is defined as a plane that generates two half-spaces. The effected geometry in the
         * half-space that is defined as being outside the plane is removed from the rendered image as a result of a
         * clipping operation.
         */
            function (ctx) {
            x3dom.nodeTypes.ClipPlane.superClass.call(this, ctx);


            /**
             * Defines activation state of the clip plane.
             * @var {x3dom.fields.SFBool} enabled
             * @memberof x3dom.nodeTypes.ClipPlane
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'enabled', true);

            /**
             * The ClipPlane node specifies a single plane equation that will be used to clip the geometry.
             * The plane field specifies a four-component plane equation that describes the inside and outside half
             * space. The first three components are a normalized vector describing the direction of the plane's
             * normal direction.
             * @var {x3dom.fields.SFVec4f} plane
             * @memberof x3dom.nodeTypes.ClipPlane
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec4f(ctx, 'plane', 0, 1, 0, 0);

            /**
             * Defines the strength of the capping.
             * @var {x3dom.fields.SFFloat} cappingStrength
             * @memberof x3dom.nodeTypes.ClipPlane
             * @initvalue 0.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'cappingStrength', 0.0);

            /**
             * Defines the color of the capping.
             * @var {x3dom.fields.SFColor} cappingColor
             * @memberof x3dom.nodeTypes.ClipPlane
             * @initvalue 1.0,1.0,1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'cappingColor', 1.0, 1.0, 1.0);


            /**
             * Enables/disables this effector (e.g. light)
             * @var {x3dom.fields.SFBool} on
             * @memberof x3dom.nodeTypes.ClipPlane
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'on', true);
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "enabled" || fieldName == "on") {
                    //TODO
                }
            },

            nodeChanged: function () {
                x3dom.nodeTypes.ClipPlane.count++;
            },

            onRemove: function() {
                x3dom.nodeTypes.ClipPlane.count--;
            },

            parentAdded: function(parent) {
            },

            parentRemoved: function(parent) {
                //TODO
            }
        }
    )
);

/** Static class ID counter (needed for caching) */
x3dom.nodeTypes.ClipPlane.count = 0;

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DAppearanceNode ### */
x3dom.registerNodeType(
    "X3DAppearanceNode",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for X3DAppearanceNode
         * @constructs x3dom.nodeTypes.X3DAppearanceNode
         * @x3d 3.3
         * @component Shape
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for all Appearance nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DAppearanceNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Appearance ### */
x3dom.registerNodeType(
    "Appearance",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DAppearanceNode,
        
        /**
         * Constructor for Appearance
         * @constructs x3dom.nodeTypes.Appearance
         * @x3d 3.3
         * @component Shape
         * @status experimental
         * @extends x3dom.nodeTypes.X3DAppearanceNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Appearance node specifies the visual properties of geometry.
         * The value for each of the fields in this node may be NULL.
         * However, if the field is non-NULL, it shall contain one node of the appropriate type.
         */
        function (ctx) {
            x3dom.nodeTypes.Appearance.superClass.call(this, ctx);


            /**
             * The material field, if specified, shall contain a Material node.
             * If the material field is NULL or unspecified, lighting is off (all lights are ignored during rendering of the object that references this Appearance) and the unlit object colour is (1, 1, 1).
             * @var {x3dom.fields.SFNode} material
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue x3dom.nodeTypes.X3DMaterialNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('material', x3dom.nodeTypes.X3DMaterialNode);

            /**
             * The texture field, if specified, shall contain a texture nodes.
             * If the texture node is NULL or the texture field is unspecified, the object that references this Appearance is not textured.
             * @var {x3dom.fields.SFNode} texture
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('texture',  x3dom.nodeTypes.X3DTextureNode);

            /**
             * The textureTransform field, if specified, shall contain a TextureTransform node. If the textureTransform is NULL or unspecified, the textureTransform field has no effect.
             * @var {x3dom.fields.SFNode} textureTransform
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue x3dom.nodeTypes.X3DTextureTransformNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('textureTransform', x3dom.nodeTypes.X3DTextureTransformNode);

            /**
             * The lineProperties field, if specified, shall contain a LineProperties node. If lineProperties is NULL or unspecified, the lineProperties field has no effect.
             * @var {x3dom.fields.SFNode} lineProperties
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue x3dom.nodeTypes.LineProperties
             * @field x3d
             * @instance
             */
            this.addField_SFNode('lineProperties', x3dom.nodeTypes.LineProperties);

            /**
             * Holds a ColorMaskMode node.
             * @var {x3dom.fields.SFNode} colorMaskMode
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue x3dom.nodeTypes.ColorMaskMode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('colorMaskMode', x3dom.nodeTypes.ColorMaskMode);

            /**
             * Holds the  BlendMode node, that is needed for correct transparency.
             * @var {x3dom.fields.SFNode} blendMode
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue x3dom.nodeTypes.BlendMode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('blendMode', x3dom.nodeTypes.BlendMode);

            /**
             * Holds the depthMode node.
             * @var {x3dom.fields.SFNode} depthMode
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue x3dom.nodeTypes.DepthMode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('depthMode', x3dom.nodeTypes.DepthMode);

            /**
             * Contains ProgramShader (Cg) or ComposedShader (GLSL).
             * @var {x3dom.fields.MFNode} shaders
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue x3dom.nodeTypes.X3DShaderNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('shaders', x3dom.nodeTypes.X3DShaderNode);

            /**
             * Defines the shape type for sorting.
             * @var {x3dom.fields.SFString} sortType
             * @range [auto, transparent, opaque]
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue 'auto'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'sortType', 'auto');

            /**
             * Change render order manually.
             * @var {x3dom.fields.SFInt32} sortKey
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'sortKey', 0);
			
			/**
             * Specify the threshold for the alpha clipping
             * @var {x3dom.fields.SFFloat} alphaClipThreshold
             * @memberof x3dom.nodeTypes.Appearance
             * @initvalue 0.1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'alphaClipThreshold', 0.1);

            // shortcut to shader program
            this._shader = null;
        
        },
        {
			fieldChanged: function(fieldName) {
				if (fieldName == "alphaClipThreshold") {
				
					Array.forEach(this._parentNodes, function (shape) {
						shape.setAppDirty();
					});
				
				}
			},
		
            nodeChanged: function() {
                //TODO delete this if all works fine
                if (!this._cf.material.node) {
                    //Unlit
                    //this.addChild(x3dom.nodeTypes.Material.defaultNode());
                }

                if (this._cf.shaders.nodes.length) {
                    this._shader = this._cf.shaders.nodes[0];
                }
                else if(this._shader)
                    this._shader=null;

                Array.forEach(this._parentNodes, function (shape) {
                    shape.setAppDirty();
                });

                this.checkSortType();
            },

            checkSortType: function() {
                if (this._vf.sortType == 'auto') {
                    if (this._cf.material.node && (this._cf.material.node._vf.transparency > 0 ||
                        this._cf.material.node._vf.backTransparency && this._cf.material.node._vf.backTransparency > 0)) {
                        this._vf.sortType = 'transparent';
                    }
                    else if (this._cf.texture.node && this._cf.texture.node._vf.url.length) {
                        // uhh, this is a rather coarse guess...
                        if (this._cf.texture.node._vf.url[0].toLowerCase().indexOf('.'+'png') >= 0) {
                            this._vf.sortType = 'transparent';
                        }
                        else {
                            this._vf.sortType = 'opaque';
                        }
                    }
                    else {
                        this._vf.sortType = 'opaque';
                    }
                }
            },

            texTransformMatrix: function() {
                if (this._cf.textureTransform.node === null) {
                    return x3dom.fields.SFMatrix4f.identity();
                }
                else {
                    return this._cf.textureTransform.node.texTransformMatrix();
                }
            },

            parentAdded: function(parent) {
                if (this != x3dom.nodeTypes.Appearance._defaultNode) {
                    /*if (parent._cleanupGLObjects) {
                     parent._cleanupGLObjects(true);
                     }*/
                    parent.setAppDirty();
                }
            }
        }
    )
);

x3dom.nodeTypes.Appearance.defaultNode = function() {
    if (!x3dom.nodeTypes.Appearance._defaultNode) {
        x3dom.nodeTypes.Appearance._defaultNode = new x3dom.nodeTypes.Appearance();
        x3dom.nodeTypes.Appearance._defaultNode.nodeChanged();
    }
    return x3dom.nodeTypes.Appearance._defaultNode;
};
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DAppearanceChildNode ### */
x3dom.registerNodeType(
    "X3DAppearanceChildNode",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for X3DAppearanceChildNode
         * @constructs x3dom.nodeTypes.X3DAppearanceChildNode
         * @x3d 3.3
         * @component Shape
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for the child nodes of the X3DAppearanceNode type.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DAppearanceChildNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### BlendMode ### */
x3dom.registerNodeType(
    "BlendMode",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DAppearanceChildNode,
        
        /**
         * Constructor for BlendMode
         * @constructs x3dom.nodeTypes.BlendMode
         * @x3d x.x
         * @component Shape
         * @extends x3dom.nodeTypes.X3DAppearanceChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The BlendMode controls blending and alpha test.
         * Pixels can be drawn using a function that blends the incoming (source) RGBA values with the RGBA values that are already in the frame buffer (the destination values).
         */
        function (ctx) {
            x3dom.nodeTypes.BlendMode.superClass.call(this, ctx);


            /**
             * The incoming pixel is scaled according to the method defined by the source factor.
             * @var {x3dom.fields.SFString} srcFactor
             * @range [none, zero, one, dst_color, src_color, one_minus_dst_color, one_minus_src_color, src_alpha, one_minus_src_alpha, dst_alpha, one_minus_dst_alpha, src_alpha_saturate, constant_color, one_minus_constant_color, constant_alpha, one_minus_constant_alpha]
             * @memberof x3dom.nodeTypes.BlendMode
             * @initvalue "src_alpha"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'srcFactor', "src_alpha");

            /**
             * The frame buffer pixel is scaled according to the method defined by the destination factor.
             * @var {x3dom.fields.SFString} destFactor
             * @range [none, zero, one, dst_color, src_color, one_minus_dst_color, one_minus_src_color, src_alpha, one_minus_src_alpha, dst_alpha, one_minus_dst_alpha, src_alpha_saturate, constant_color, one_minus_constant_color, constant_alpha, one_minus_constant_alpha]
             * @memberof x3dom.nodeTypes.BlendMode
             * @initvalue "one_minus_src_alpha"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'destFactor', "one_minus_src_alpha");

            /**
             * This is the constant color used by blend modes constant.
             * @var {x3dom.fields.SFColor} color
             * @memberof x3dom.nodeTypes.BlendMode
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'color', 1, 1, 1);

            /**
             * This is the constant alpha used by blend modes constant.
             * @var {x3dom.fields.SFFloat} colorTransparency
             * @memberof x3dom.nodeTypes.BlendMode
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'colorTransparency', 0);

            /**
             *
             * @var {x3dom.fields.SFString} alphaFunc
             * @memberof x3dom.nodeTypes.BlendMode
             * @initvalue "none"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'alphaFunc', "none");

            /**
             * The alphaFunc defines how fragments which do not fulfill a certain condition are handled.
             * @var {x3dom.fields.SFFloat} alphaFuncValue
             * @range [none, never, less, equal, lequal, greater, notequal, gequal, always]
             * @memberof x3dom.nodeTypes.BlendMode
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'alphaFuncValue', 0);

            /**
             * An additional equation used to combine source, destination and the constant value.
             * @var {x3dom.fields.SFString} equation
             * @range [none, func_add, func_subtract, func_reverse_subtract, min, max, logic_op]
             * @memberof x3dom.nodeTypes.BlendMode
             * @initvalue "none"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'equation', "none");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### DepthMode ### */
x3dom.registerNodeType(
    "DepthMode",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DAppearanceChildNode,
        
        /**
         * Constructor for DepthMode
         * @constructs x3dom.nodeTypes.DepthMode
         * @x3d x.x
         * @component Shape
         * @extends x3dom.nodeTypes.X3DAppearanceChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The depth mode contains the parameters that are specific for depth control, like the value used for depth buffer comparisons.
         */
        function (ctx) {
            x3dom.nodeTypes.DepthMode.superClass.call(this, ctx);


            /**
             * Whether the depth test should be enabled or not.
             * @var {x3dom.fields.SFBool} enableDepthTest
             * @memberof x3dom.nodeTypes.DepthMode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'enableDepthTest', true);

            /**
             * The depth function to use. If "none", it's not changed, the default is "lequal".
             * @var {x3dom.fields.SFString} depthFunc
             * @range [NONE, NEVER, LESS, EQUAL, LEQUAL, GREATER, NOTEQUAL, GEQUAL, ALWAYS]
             * @memberof x3dom.nodeTypes.DepthMode
             * @initvalue "none"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'depthFunc', "none");

            /**
             * Whether the depth buffer is enabled for writing or not.
             * @var {x3dom.fields.SFBool} readOnly
             * @memberof x3dom.nodeTypes.DepthMode
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'readOnly', false);

            /**
             * The near value for the depth range. Ignored if less than 0, defaults to -1.
             * @var {x3dom.fields.SFFloat} zNearRange
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.DepthMode
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zNearRange', -1);

            /**
             * The far value for the depth range. Ignored if less than 0, defaults to -1.
             * @var {x3dom.fields.SFFloat} zFarRange
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.DepthMode
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zFarRange', -1);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ColorMaskMode ### */
x3dom.registerNodeType(
    "ColorMaskMode",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DAppearanceChildNode,
        
        /**
         * Constructor for ColorMaskMode
         * @constructs x3dom.nodeTypes.ColorMaskMode
         * @x3d x.x
         * @component Shape
         * @extends x3dom.nodeTypes.X3DAppearanceChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ColorMaskMode node affects drawing in RGBA mode. The 4 masks control whether the corresponding component is written.
         */
        function (ctx) {
            x3dom.nodeTypes.ColorMaskMode.superClass.call(this, ctx);


            /**
             * Masks r color channel.
             * @var {x3dom.fields.SFBool} maskR
             * @memberof x3dom.nodeTypes.ColorMaskMode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'maskR', true);

            /**
             * Masks g color channel.
             * @var {x3dom.fields.SFBool} maskG
             * @memberof x3dom.nodeTypes.ColorMaskMode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'maskG', true);

            /**
             * Masks b color channel.
             * @var {x3dom.fields.SFBool} maskB
             * @memberof x3dom.nodeTypes.ColorMaskMode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'maskB', true);

            /**
             * Masks a color channel.
             * @var {x3dom.fields.SFBool} maskA
             * @memberof x3dom.nodeTypes.ColorMaskMode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'maskA', true);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### LineProperties ### */
x3dom.registerNodeType(
    "LineProperties",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DAppearanceChildNode,
        
        /**
         * Constructor for LineProperties
         * @constructs x3dom.nodeTypes.LineProperties
         * @x3d 3.3
         * @component Shape
         * @status experimental
         * @extends x3dom.nodeTypes.X3DAppearanceChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The LineProperties node specifies additional properties to be applied to all line geometry. The colour of the line is specified by the associated Material node.
         */
        function (ctx) {
            x3dom.nodeTypes.LineProperties.superClass.call(this, ctx);

            // http://www.web3d.org/files/specifications/19775-1/V3.2/Part01/components/shape.html#LineProperties
            // THINKABOUTME: to my mind, the only useful, but missing, field is linewidth (scaleFactor is overhead)

            /**
             * The linetype and linewidth shall only be applied when the applied field has value TRUE.
             * When the value of the applied field is FALSE, a solid line of nominal width shall be produced.
             * @var {x3dom.fields.SFBool} applied
             * @memberof x3dom.nodeTypes.LineProperties
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'applied', true);

            /**
             * The linetype field selects a line pattern.
             * @var {x3dom.fields.SFInt32} linetype
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.LineProperties
             * @initvalue 1
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'linetype', 1);

            /**
             * The linewidthScaleFactor is a multiplicative value that scales a the linewidth. This resulting value shall then be mapped to the nearest available line width. A value less than or equal to zero refers to the minimum available line width.
             * @var {x3dom.fields.SFFloat} linewidthScaleFactor
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.LineProperties
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'linewidthScaleFactor', 0);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DMaterialNode ### */
x3dom.registerNodeType(
    "X3DMaterialNode",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DAppearanceChildNode,
        
        /**
         * Constructor for X3DMaterialNode
         * @constructs x3dom.nodeTypes.X3DMaterialNode
         * @x3d 3.3
         * @component Shape
         * @status full
         * @extends x3dom.nodeTypes.X3DAppearanceChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for all Material nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DMaterialNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Material ### */
x3dom.registerNodeType(
    "Material",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DMaterialNode,
        
        /**
         * Constructor for Material
         * @constructs x3dom.nodeTypes.Material
         * @x3d 3.3
         * @component Shape
         * @status full
         * @extends x3dom.nodeTypes.X3DMaterialNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Material node specifies surface material properties for associated geometry nodes and is used by the X3D lighting equations during rendering.
         * All of the fields in the Material node range from 0.0 to 1.0.
         */
        function (ctx) {
            x3dom.nodeTypes.Material.superClass.call(this, ctx);

            /**
             * The ambientIntensity field specifies how much ambient light from light sources this surface shall reflect.
             * Ambient light is omnidirectional and depends only on the number of light sources, not their positions with respect to the surface.
             * Ambient colour is calculated as ambientIntensity × diffuseColor.
             * @var {x3dom.fields.SFFloat} ambientIntensity
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.X3DMaterialNode
             * @initvalue 0.2
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'ambientIntensity', 0.2);

            /**
             * The diffuseColor field reflects all X3D light sources depending on the angle of the surface with respect to the light source.
             * The more directly the surface faces the light, the more diffuse light reflects.
             * The emissiveColor field models "glowing" objects.
             * This can be useful for displaying pre-lit models (where the light energy of the room is computed explicitly), or for displaying scientific data.
             * @var {x3dom.fields.SFColor} diffuseColor
             * @memberof x3dom.nodeTypes.X3DMaterialNode
             * @initvalue 0.8,0.8,0.8
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'diffuseColor', 0.8, 0.8, 0.8);

            /**
             * The emissiveColor field models "glowing" objects.
             * This can be useful for displaying pre-lit models (where the light energy of the room is computed explicitly), or for displaying scientific data.
             * @var {x3dom.fields.SFColor} emissiveColor
             * @memberof x3dom.nodeTypes.X3DMaterialNode
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'emissiveColor', 0, 0, 0);

            /**
             * The specularColor and shininess fields determine the specular highlights (e.g., the shiny spots on an apple).
             * When the angle from the light to the surface is close to the angle from the surface to the viewer, the specularColor is added to the diffuse and ambient colour calculations.
             * Lower shininess values produce soft glows, while higher values result in sharper, smaller highlights.
             * @var {x3dom.fields.SFFloat} shininess
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.X3DMaterialNode
             * @initvalue 0.2
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'shininess', 0.2);

            /**
             * The specularColor and shininess fields determine the specular highlights (e.g., the shiny spots on an apple).
             * When the angle from the light to the surface is close to the angle from the surface to the viewer, the specularColor is added to the diffuse and ambient colour calculations.
             * Lower shininess values produce soft glows, while higher values result in sharper, smaller highlights.
             * @var {x3dom.fields.SFColor} specularColor
             * @memberof x3dom.nodeTypes.X3DMaterialNode
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'specularColor', 0, 0, 0);

            /**
             * The transparency field specifies how "clear" an object is, with 1.0 being completely transparent, and 0.0 completely opaque.
             * @var {x3dom.fields.SFFloat} transparency
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.X3DMaterialNode
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'transparency', 0);
        
        },
        {
            fieldChanged: function(fieldName) {
                if (fieldName == "ambientIntensity" || fieldName == "diffuseColor" ||
                    fieldName == "emissiveColor" || fieldName == "shininess" ||
                    fieldName == "specularColor" || fieldName == "transparency")
                {
                    Array.forEach(this._parentNodes, function (app) {
                        Array.forEach(app._parentNodes, function (shape) {
                            shape._dirty.material = true;
                        });
                        app.checkSortType();
                    });
                }
            }
        }
    )
);

x3dom.nodeTypes.Material.defaultNode = function() {
    if (!x3dom.nodeTypes.Material._defaultNode) {
        x3dom.nodeTypes.Material._defaultNode = new x3dom.nodeTypes.Material();
        x3dom.nodeTypes.Material._defaultNode.nodeChanged();
    }
    return x3dom.nodeTypes.Material._defaultNode;
};
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TwoSidedMaterial ### */
x3dom.registerNodeType(
    "TwoSidedMaterial",
    "Shape",
    defineClass(x3dom.nodeTypes.Material,
        
        /**
         * Constructor for TwoSidedMaterial
         * @constructs x3dom.nodeTypes.TwoSidedMaterial
         * @x3d 3.3
         * @component Shape
         * @status full
         * @extends x3dom.nodeTypes.X3DMaterialNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This node defines material properties that can effect both the front and back side of a polygon individually.
         * These materials are used for both the front and back side of the geometry whenever the X3D lighting model is active.
         */
        function (ctx) {
            x3dom.nodeTypes.TwoSidedMaterial.superClass.call(this, ctx);


            /**
             * Defines the ambient intensity for the back side.
             * @var {x3dom.fields.SFFloat} backAmbientIntensity
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.TwoSidedMaterial
             * @initvalue 0.2
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'backAmbientIntensity', 0.2);

            /**
             * Defines the diffuse color for the back side.
             * @var {x3dom.fields.SFColor} backDiffuseColor
             * @memberof x3dom.nodeTypes.TwoSidedMaterial
             * @initvalue 0.8,0.8,0.8
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'backDiffuseColor', 0.8, 0.8, 0.8);

            /**
             * Defines the emissive color for the back side.
             * @var {x3dom.fields.SFColor} backEmissiveColor
             * @memberof x3dom.nodeTypes.TwoSidedMaterial
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'backEmissiveColor', 0, 0, 0);

            /**
             * Defines the shininess for the back side.
             * @var {x3dom.fields.SFFloat} backShininess
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.TwoSidedMaterial
             * @initvalue 0.2
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'backShininess', 0.2);

            /**
             * Defines the specular color for the back side.
             * @var {x3dom.fields.SFColor} backSpecularColor
             * @memberof x3dom.nodeTypes.TwoSidedMaterial
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'backSpecularColor', 0, 0, 0);

            /**
             * Defines the transparency for the back side.
             * @var {x3dom.fields.SFFloat} backTransparency
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.TwoSidedMaterial
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'backTransparency', 0);

            /**
             * If the separateBackColor field is set to TRUE, the rendering shall render the front and back faces of the geometry with different values.
             * If the value is FALSE, the front colours are used for both the front and back side of the polygon, as per the existing X3D lighting rules.
             * @var {x3dom.fields.SFBool} separateBackColor
             * @memberof x3dom.nodeTypes.TwoSidedMaterial
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'separateBackColor', false);
        
        },
        {
            fieldChanged: function(fieldName) {
                if (fieldName == "ambientIntensity" || fieldName == "diffuseColor" ||
                    fieldName == "emissiveColor" || fieldName == "shininess" ||
                    fieldName == "specularColor" || fieldName == "transparency" ||
                    fieldName == "backAmbientIntensity" || fieldName == "backDiffuseColor" ||
                    fieldName == "backEmissiveColor" || fieldName == "backShininess" ||
                    fieldName == "backSpecularColor" || fieldName == "backTransparency" ||
                    fieldName == "separateBackColor")
                {
                    Array.forEach(this._parentNodes, function (app) {
                        Array.forEach(app._parentNodes, function (shape) {
                            shape._dirty.material = true;
                        });
                        app.checkSortType();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DShapeNode ### */
x3dom.registerNodeType(
    "X3DShapeNode",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DBoundedObject,
        
        /**
         * Constructor for X3DShapeNode
         * @constructs x3dom.nodeTypes.X3DShapeNode
         * @x3d 3.3
         * @component Shape
         * @status full
         * @extends x3dom.nodeTypes.X3DBoundedObject
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the base node type for all Shape nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DShapeNode.superClass.call(this, ctx);


            /**
             * Defines whether the shape is pickable.
             * @var {x3dom.fields.SFBool} isPickable
             * @memberof x3dom.nodeTypes.X3DShapeNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'isPickable', true);

            /**
             * Holds the id offset for MultiPart picking.
             * @var {x3dom.fields.SFInt32} isPickable
             * @memberof x3dom.nodeTypes.X3DShapeNode
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'idOffset', 0);

            /**
             * Holds the appearance node.
             * @var {x3dom.fields.SFNode} appearance
             * @memberof x3dom.nodeTypes.X3DShapeNode
             * @initvalue x3dom.nodeTypes.X3DAppearanceNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('appearance', x3dom.nodeTypes.X3DAppearanceNode);

            /**
             * Holds the geometry node.
             * @var {x3dom.fields.SFNode} geometry
             * @memberof x3dom.nodeTypes.X3DShapeNode
             * @initvalue x3dom.nodeTypes.X3DGeometryNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('geometry', x3dom.nodeTypes.X3DGeometryNode);

            this._objectID = 0;
            this._shaderProperties = null;
            this._clipPlanes = [];

            // in WebGL-based renderer a clean-up function is attached
            this._cleanupGLObjects = null;

            this._dirty = {
                positions: true,
                normals: true,
                texcoords: true,
                colors: true,
                specialAttribs: true,   // e.g., particleSize, IDs,...
                indexes: true,
                texture: true,
                material: true,
                text: true,
                shader: true,
                ids: true
            };

            // FIXME; move somewhere else and allow generic values!!!
            this._coordStrideOffset = [0, 0];
            this._normalStrideOffset = [0, 0];
            this._texCoordStrideOffset = [0, 0];
            this._colorStrideOffset = [0, 0];
            this._idStrideOffset = [0, 0];

            this._tessellationProperties = [];
        },
        {
            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                // attention, in contrast to other collectDrawableObjects()
                // this one has boolean return type to better work with RSG
                var graphState = this.graphState();

                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                if (!this._cf.geometry.node ||
                    drawableCollection.cull(transform, graphState, singlePath, planeMask) <= 0) {
                    return false;
                }

                if (singlePath && !this._graph.globalMatrix)
                    this._graph.globalMatrix = transform;

                if (this._clipPlanes.length != clipPlanes.length)
                {
                    this._dirty.shader = true;
                }

                this._clipPlanes = clipPlanes;

                drawableCollection.addShape(this, transform, graphState);

                return true;
            },

            getVolume: function()
            {
                var vol = this._graph.volume;

                if (!this.volumeValid() && this._vf.render)
                {
                    var geo = this._cf.geometry.node;
                    var childVol = geo ? geo.getVolume() : null;

                    if (childVol && childVol.isValid())
                        vol.extendBounds(childVol.min, childVol.max);
                }

                return vol;
            },

            getCenter: function() {
                var geo = this._cf.geometry.node;
                return (geo ? geo.getCenter() : new x3dom.fields.SFVec3f(0,0,0));
            },

            getDiameter: function() {
                var geo = this._cf.geometry.node;
                return (geo ? geo.getDiameter() : 0);
            },

            doIntersect: function(line) {
                return this._cf.geometry.node.doIntersect(line);
            },

            forceUpdateCoverage: function()
            {
                var geo = this._cf.geometry.node;
                return (geo ? geo.forceUpdateCoverage() : false);
            },

            tessellationProperties: function()
            {
                // some geometries require offset and count into index array
                var geo = this._cf.geometry.node;
                if (geo && geo._indexOffset)
                    return geo._indexOffset;      // IndexedTriangleStripSet
                else
                    return this._tessellationProperties; // BVHRefiner-Patch
            },

            isLit: function() {
                return this._cf.geometry.node._vf.lit;
            },

            isSolid: function() {
                var twoSidedMat = (this._cf.appearance.node && this._cf.appearance.node._cf.material.node &&
                                   x3dom.isa(this._cf.appearance.node._cf.material.node, x3dom.nodeTypes.TwoSidedMaterial));
                return this._cf.geometry.node._vf.solid && !twoSidedMat;
            },

            isCCW: function() {
                return this._cf.geometry.node._vf.ccw;
            },

            parentRemoved: function(parent) {
                for (var i=0, n=this._childNodes.length; i<n; i++) {
                    var child = this._childNodes[i];
                    if (child) {
                        child.parentRemoved(this);
                    }
                }

                if (parent)
                    parent.invalidateVolume();
                if (this._parentNodes.length > 0)
                    this.invalidateVolume();

                // Cleans all GL objects for WebGL-based renderer
                if (this._cleanupGLObjects) {
                    this._cleanupGLObjects();
                }
            },

            unsetDirty: function () {
                // vertex attributes
                this._dirty.positions = false;
                this._dirty.normals = false;
                this._dirty.texcoords = false;
                this._dirty.colors = false;
                this._dirty.specialAttribs = false;
                // indices/topology
                this._dirty.indexes = false;
                // appearance properties
                this._dirty.texture = false;
                this._dirty.material = false;
                this._dirty.text = false;
                this._dirty.shader = false;
            },

            unsetGeoDirty: function () {
                this._dirty.positions = false;
                this._dirty.normals = false;
                this._dirty.texcoords = false;
                this._dirty.colors = false;
                this._dirty.specialAttribs = false;
                this._dirty.indexes = false;
            },

            setAllDirty: function () {
                // vertex attributes
                this._dirty.positions = true;
                this._dirty.normals = true;
                this._dirty.texcoords = true;
                this._dirty.colors = true;
                this._dirty.specialAttribs = true;
                // indices/topology
                this._dirty.indexes = true;
                // appearance properties
                this._dirty.texture = true;
                this._dirty.material = true;
                this._dirty.text = true;
                this._dirty.shader = true;
                // finally invalidate volume
                this.invalidateVolume();
            },

            setAppDirty: function () {
                // appearance properties
                this._dirty.texture = true;
                this._dirty.material = true;
                //this._dirty.text = true;
                this._dirty.shader = true;
            },

            setGeoDirty: function () {
                this._dirty.positions = true;
                this._dirty.normals = true;
                this._dirty.texcoords = true;
                this._dirty.colors = true;
                this._dirty.specialAttribs = true;
                this._dirty.indexes = true;
                // finally invalidate volume
                this.invalidateVolume();
            },

            getShaderProperties: function(viewarea)
            {
                if (this._shaderProperties == null ||
                    this._dirty.shader == true     ||
                    (this._webgl !== undefined &&
                     this._webgl.dirtyLighting != x3dom.Utils.checkDirtyLighting(viewarea) ) ||
                    x3dom.Utils.checkDirtyEnvironment(viewarea, this._shaderProperties) == true)
                {
                    this._shaderProperties = x3dom.Utils.generateProperties(viewarea, this);

                    this._dirty.shader = false;
                    if (this._webgl !== undefined)
                    {
                        this._webgl.dirtyLighting = x3dom.Utils.checkDirtyLighting(viewarea);
                    }
                }

                return this._shaderProperties;
            },

            getTextures: function() {
                var textures = [];

                var appearance = this._cf.appearance.node;
                if (appearance) {
                    var tex = appearance._cf.texture.node;
                    if(tex) {
                        if(x3dom.isa(tex, x3dom.nodeTypes.MultiTexture)) {
                            textures = textures.concat(tex.getTextures());
                        }
                        else {
                            textures.push(tex);
                        }
                    }

                    var shader = appearance._cf.shaders.nodes[0];
                    if(shader) {
                        if(x3dom.isa(shader, x3dom.nodeTypes.CommonSurfaceShader)) {
                            textures = textures.concat(shader.getTextures());
                        }
                    }
                }

                var geometry = this._cf.geometry.node;
                if (geometry) {
                    if(x3dom.isa(geometry, x3dom.nodeTypes.ImageGeometry)) {
                        textures = textures.concat(geometry.getTextures());
                    }
                    else if(x3dom.isa(geometry, x3dom.nodeTypes.Text)) {
                        textures = textures.concat(geometry);
                    }
                }

                return textures;
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Shape ### */
x3dom.registerNodeType(
    "Shape",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DShapeNode,
        
        /**
         * Constructor for Shape
         * @constructs x3dom.nodeTypes.Shape
         * @x3d 3.3
         * @component Shape
         * @status full
         * @extends x3dom.nodeTypes.X3DShapeNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Shape node has two fields, appearance and geometry, that are used to create rendered objects in the world.
         * The appearance field contains an Appearance node that specifies the visual attributes (e.g., material and texture) to be applied to the geometry.
         * The geometry field contains a geometry node. The specified geometry node is rendered with the specified appearance nodes applied.
         */
        function (ctx) {
            x3dom.nodeTypes.Shape.superClass.call(this, ctx);
        
        },
        {
            nodeChanged: function () {
                //TODO delete this if all works fine
                if (!this._cf.appearance.node) {
                    //Unlit
                    //this.addChild(x3dom.nodeTypes.Appearance.defaultNode());
                }
                if (!this._cf.geometry.node) {
                    if (this._DEF)
                        x3dom.debug.logError("No geometry given in Shape/" + this._DEF);
                }
                else if (!this._objectID) {
                    this._objectID = ++x3dom.nodeTypes.Shape.objectID;
                    x3dom.nodeTypes.Shape.idMap.nodeID[this._objectID] = this;
                }
                this.invalidateVolume();
            }
        }
    )
);

/** Static class ID counter (needed for caching) */
x3dom.nodeTypes.Shape.shaderPartID = 0;

/** Static class ID counter (needed for picking) */
x3dom.nodeTypes.Shape.objectID = 0;

/** Map for Shape node IDs (needed for picking) */
x3dom.nodeTypes.Shape.idMap = {
    nodeID: {},
    remove: function(obj) {
        for (var prop in this.nodeID) {
            if (this.nodeID.hasOwnProperty(prop)) {
                var val = this.nodeID[prop];
                if (val._objectID  && obj._objectID &&
                    val._objectID === obj._objectID)
                {
                    delete this.nodeID[prop];
                    x3dom.debug.logInfo("Unreg " + val._objectID);
                    // FIXME; handle node removal to unreg from map,
                    // and put free'd ID back to ID pool for reuse
                }
            }
        }
    }
};
/** @namespace x3dom.nodeTypes */
/**
 * Created by Sven Kluge on 27.06.2016.
 */
x3dom.registerNodeType(
    "ExternalShape",
    "Shape",
    defineClass(x3dom.nodeTypes.X3DShapeNode,

        /**
         * Constructor for ExternalShape
         * @constructs x3dom.nodeTypes.ExternalShape
         * @x3d 3.3
         * @component Shape
         * @status full
         * @extends x3dom.nodeTypes.X3DShapeNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc
         */
        function (ctx) {
            x3dom.nodeTypes.ExternalShape.superClass.call(this, ctx);

            /**
             * Defines the url to the openGL Transfer Format (glTF) file.
             * A suffix with a leading # can be used to reference single meshes inside a SRC: "path/to/data.src#mesh0".
             * Multiple urls specify alternatives (if downloading fails).
             *
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.ExternalGeometry
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'url', []);

            this._currentURLIdx = 0;
            this._cf.geometry.node = new x3dom.nodeTypes.X3DSpatialGeometryNode(ctx);
            this.loaded = false;
        },
        {
            update: function(shape, shaderProgram, gl, viewarea, context) {
                var that = this;

                if (this._vf['url'].length == 0 ||
                    this._currentURLIdx >= this._vf['url'].length)
                {
                    return;
                }

                var xhr = new XMLHttpRequest();

                xhr.open("GET", this._nameSpace.getURL(this._vf['url'][this._currentURLIdx]), true);

                xhr.responseType = "arraybuffer";

                xhr.send(null);

                xhr.onerror = function() {
                    x3dom.debug.logError("Unable to load SRC data from URL \"" + that._vf['url'][that._currentURLIdx] + "\"");
                };

                xhr.onload = function() {

                    if ((xhr.status == 200 || xhr.status == 0)) {
                        var glTF = new x3dom.glTF.glTFLoader(xhr.response);

                        if (glTF.header.sceneLength > 0)
                        {
                            glTF.loaded = {};
                            glTF.loaded.meshes = {};
                            glTF.loaded.meshCount = 0;

                            that.glTF = glTF;

                            var url = that._vf['url'][that._currentURLIdx];
                            if(url.includes('#'))
                            {
                                var split = url.split('#');
                                var meshName = split[split.length-1];
                                glTF.getMesh(shape, shaderProgram, gl, meshName);
                            }
                            else
                            {
                                glTF.getScene(shape, shaderProgram, gl);
                            }

                            for(var key in glTF._mesh){
                                if(!glTF._mesh.hasOwnProperty(key))continue;
                                that._cf.geometry.node._mesh[key] = glTF._mesh[key];
                            }

                        }
                        else
                        {
                            if ((that._currentURLIdx + 1) < that._vf['url'].length)
                            {
                                x3dom.debug.logWarning("Invalid SRC data, loaded from URL \"" +
                                    that._vf['url'][that._currentURLIdx] +
                                    "\", trying next specified URL");

                                //try next URL
                                ++that._currentURLIdx;
                                that.update(shape, shaderProgram, gl, viewarea, context);
                            }
                            else
                            {
                                x3dom.debug.logError("Invalid SRC data, loaded from URL \"" +
                                    that._vf['url'][that._currentURLIdx] + "\"," +
                                    " no other URLs left to try.");
                            }
                        }
                    }
                    else
                    {
                        if ((that._currentURLIdx + 1) < that._vf['url'].length)
                        {
                            x3dom.debug.logWarning("Invalid SRC data, loaded from URL \"" +
                                that._vf['url'][that._currentURLIdx] +
                                "\", trying next specified URL");

                            //try next URL
                            ++that._currentURLIdx;
                            that.update(shape, shaderProgram, gl, viewarea, context);
                        }
                        else
                        {
                            x3dom.debug.logError("Invalid SRC data, loaded from URL \"" +
                                that._vf['url'][that._currentURLIdx] + "\"," +
                                " no other URLs left to try.");
                        }
                    }
                };
            },


            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                // attention, in contrast to other collectDrawableObjects()
                // this one has boolean return type to better work with RSG
                var graphState = this.graphState();

                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                if (singlePath && !this._graph.globalMatrix)
                    this._graph.globalMatrix = transform;

                if (this._clipPlanes.length != clipPlanes.length)
                {
                    this._dirty.shader = true;
                }

                this._clipPlanes = clipPlanes;

                drawableCollection.addShape(this, transform, graphState);
                return true;
            },

            getShaderProperties: function(viewarea)
            {
                
                var properties = x3dom.Utils.generateProperties(viewarea, this);
                properties.CSHADER = -1;

                properties.LIGHTS = viewarea.getLights().length + (viewarea._scene.getNavigationInfo()._vf.headlight);

                properties.EMPTY_SHADER = 1;

                return properties;
            },

            nodeChanged: function ()
            {
                if (!this._objectID) {
                    this._objectID = ++x3dom.nodeTypes.Shape.objectID;
                    x3dom.nodeTypes.Shape.idMap.nodeID[this._objectID] = this;
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DLightNode ### */
x3dom.registerNodeType(
    "X3DLightNode",
    "Lighting",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DLightNode
         * @constructs x3dom.nodeTypes.X3DLightNode
         * @x3d 3.3
         * @component Lighting
         * @status full
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The X3DLightNode abstract node type is the base type from which all node types that serve as light sources are derived.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DLightNode.superClass.call(this, ctx);

            if (ctx)
                ctx.doc._nodeBag.lights.push(this);
            else
                x3dom.debug.logWarning("X3DLightNode: No runtime context found!");

            this._lightID = 0;
            this._dirty = true;


            /**
             * The ambientIntensity specifies the intensity of the ambient emission from the light. Light intensity may range from 0.0 (no light emission) to 1.0 (full intensity).
             * @var {x3dom.fields.SFFloat} ambientIntensity
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'ambientIntensity', 0);

            /**
             * The color field specifies the spectral colour properties of both the direct and ambient light emission as an RGB value.
             * @var {x3dom.fields.SFColor} color
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue 1,1,1
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'color', 1, 1, 1);

            /**
             * The intensity field specifies the brightness of the direct emission from the light. Light intensity may range from 0.0 (no light emission) to 1.0 (full intensity).
             * @var {x3dom.fields.SFFloat} intensity
             * @range [0, 1]
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue 1
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'intensity', 1);

            /**
             * Specifies whether the light is global or scoped.
             * Global lights illuminate all objects that fall within their volume of lighting influence.
             * Scoped lights only illuminate objects that are in the same transformation hierarchy as the light; i.e., only the children and descendants of its enclosing parent group are illuminated.
             * @var {x3dom.fields.SFBool} global
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'global', false);

            /**
             * The on field specifies whether the light is enabled or disabled.
             * @var {x3dom.fields.SFBool} on
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'on', true);

            /**
             * Defines the attenuation of the shadows
             * @var {x3dom.fields.SFFloat} shadowIntensity
             * @range [o, 1]
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'shadowIntensity', 0);

            /**
             * Specifies the resolution of the used shadow map.
             * @var {x3dom.fields.SFInt32} shadowMapSize
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue 1024
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'shadowMapSize', 1024);

            /**
             * Sets the smoothness of the shadow umbra.
             * @var {x3dom.fields.SFInt32} shadowFilterSize
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'shadowFilterSize', 0);

            /**
             * Defines the shadow offset for the back projection of the shadow map.
             * @var {x3dom.fields.SFFloat} shadowOffset
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'shadowOffset', 0);

            /**
             * Specifies the placement of the near plane of the light projection.
             * Objects that are closer to the light source than the near plane do not cast shadows.
             * If the zNear value is not set, the near plane is placed automatically.
             * @var {x3dom.fields.SFFloat} zNear
             * @range -1 or [0, inf]
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zNear', -1);

            /**
             * Specifies the placement of the far plane of the light projection.
             * Objects that are farther away from the light source than the far plane do not cast shadows.
             * If the zFar value is not set, the far plane is placed automatically.
             * @var {x3dom.fields.SFFloat} zFar
             * @range -1 or [0, inf]
             * @memberof x3dom.nodeTypes.X3DLightNode
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zFar', -1);
        
        },
        {
            getViewMatrix: function(vec) {
                return x3dom.fields.SFMatrix4f.identity;
            },

            nodeChanged: function () {
                if(!this._lightID) {
                    this._lightID = ++x3dom.nodeTypes.X3DLightNode.lightID;
                }
            },

            fieldChanged: function(fieldName)
            {
                if (this._vf.hasOwnProperty(fieldName)) {
                    this._dirty = true;
                }
            },

            parentRemoved: function(parent)
            {
                if (this._parentNodes.length === 1 && this._parentNodes[0] == parent) {
                    var doc = this.findX3DDoc();

                    for (var i=0, n=doc._nodeBag.lights.length; i<n; i++) {
                        if (doc._nodeBag.lights[i] === this) {
                            doc._nodeBag.lights.splice(i, 1);
                        }
                    }
                }
            },
            onRemove: function()
            {
                //console.log("remove");
            }
        }
    )
);

/** Static class ID counter (needed for flash performance up) */
x3dom.nodeTypes.X3DLightNode.lightID = 0;

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### DirectionalLight ### */
x3dom.registerNodeType(
    "DirectionalLight",
    "Lighting",
    defineClass(x3dom.nodeTypes.X3DLightNode,
        
        /**
         * Constructor for DirectionalLight
         * @constructs x3dom.nodeTypes.DirectionalLight
         * @x3d 3.3
         * @component Lighting
         * @status experimental
         * @extends x3dom.nodeTypes.X3DLightNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The DirectionalLight node defines a directional light source that illuminates along rays parallel to a given 3-dimensional vector.
         * A directional light source illuminates only the objects in its enclosing parent group.
         * The light may illuminate everything within this coordinate system, including all children and descendants of its parent group.
         * The accumulated transformations of the parent nodes affect the light.
         * DirectionalLight nodes do not attenuate with distance.
         */
        function (ctx) {
            x3dom.nodeTypes.DirectionalLight.superClass.call(this, ctx);


            /**
             * The direction field specifies the direction vector of the illumination emanating from the light source in the local coordinate system.
             * @var {x3dom.fields.SFVec3f} direction
             * @memberof x3dom.nodeTypes.DirectionalLight
             * @initvalue 0,0,-1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'direction', 0, 0, -1);

            /**
             *
             * @var {x3dom.fields.SFInt32} shadowCascades
             * @memberof x3dom.nodeTypes.DirectionalLight
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'shadowCascades', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} shadowSplitFactor
             * @memberof x3dom.nodeTypes.DirectionalLight
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'shadowSplitFactor', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} shadowSplitOffset
             * @memberof x3dom.nodeTypes.DirectionalLight
             * @initvalue 0.1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'shadowSplitOffset', 0.1);
        
        },
        {
            getViewMatrix: function(vec) {
                var dir = this.getCurrentTransform().multMatrixVec(this._vf.direction).normalize();
                var orientation = x3dom.fields.Quaternion.rotateFromTo(
                    new x3dom.fields.SFVec3f(0, 0, -1), dir);
                return orientation.toMatrix().transpose().
                    mult(x3dom.fields.SFMatrix4f.translation(vec.negate()));
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PointLight ### */
x3dom.registerNodeType(
    "PointLight",
    "Lighting",
    defineClass(x3dom.nodeTypes.X3DLightNode,
        
        /**
         * Constructor for PointLight
         * @constructs x3dom.nodeTypes.PointLight
         * @x3d 3.3
         * @component Lighting
         * @status full
         * @extends x3dom.nodeTypes.X3DLightNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PointLight node specifies a point light source at a 3D location in the local coordinate system.
         * A point light source emits light equally in all directions; that is, it is omnidirectional.
         * PointLight nodes are specified in the local coordinate system and are affected by ancestor transformations.
         */
        function (ctx) {
            x3dom.nodeTypes.PointLight.superClass.call(this, ctx);


            /**
             * PointLight node's illumination falls off with distance as specified by three attenuation coefficients. The attenuation factor is:
             * 1/max(attenuation[0] + attenuation[1] × r + attenuation[2] × r^2, 1)
             * where r is the distance from the light to the surface being illuminated.
             * The default is no attenuation.
             * An attenuation value of (0, 0, 0) is identical to (1, 0, 0). Attenuation values shall be greater than or equal to zero.
             * @var {x3dom.fields.SFVec3f} attenuation
             * @memberof x3dom.nodeTypes.PointLight
             * @initvalue 1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'attenuation', 1, 0, 0);

            /**
             * The position of the Light
             * @var {x3dom.fields.SFVec3f} location
             * @memberof x3dom.nodeTypes.PointLight
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'location', 0, 0, 0);

            /**
             * A PointLight node illuminates geometry within radius length base units of its location.
             * Radius is affected by ancestors' transformations.
             * @var {x3dom.fields.SFFloat} radius
             * @memberof x3dom.nodeTypes.PointLight
             * @initvalue 100
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', 100);

            this._vf.global = true;
        
        },
        {
            getViewMatrix: function(vec) {
                var pos = this.getCurrentTransform().multMatrixPnt(this._vf.location);
                var orientation = x3dom.fields.Quaternion.rotateFromTo(
                    new x3dom.fields.SFVec3f(0, 0, -1), vec);
                return orientation.toMatrix().transpose().
                    mult(x3dom.fields.SFMatrix4f.translation(pos.negate()));
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### SpotLight ### */
x3dom.registerNodeType(
    "SpotLight",
    "Lighting",
    defineClass(x3dom.nodeTypes.X3DLightNode,
        
        /**
         * Constructor for SpotLight
         * @constructs x3dom.nodeTypes.SpotLight
         * @x3d 3.3
         * @component Lighting
         * @status full
         * @extends x3dom.nodeTypes.X3DLightNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The SpotLight node defines a light source that emits light from a specific point along a specific direction vector and constrained within a solid angle.
         * Spotlights may illuminate geometry nodes that respond to light sources and intersect the solid angle defined by the SpotLight.
         * Spotlight nodes are specified in the local coordinate system and are affected by ancestors' transformations.
         */
        function (ctx) {
            x3dom.nodeTypes.SpotLight.superClass.call(this, ctx);


            /**
             * The direction field specifies the direction vector of the light's central axis defined in the local coordinate system.
             * @var {x3dom.fields.SFVec3f} direction
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 0,0,-1
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'direction', 0, 0, -1);

            /**
             * SpotLight node's illumination falls off with distance as specified by three attenuation coefficients. The attenuation factor is:
             * 1/max(attenuation[0] + attenuation[1] × r + attenuation[2] × r^2, 1)
             * where r is the distance from the light to the surface being illuminated.
             * The default is no attenuation.
             * An attenuation value of (0, 0, 0) is identical to (1, 0, 0). Attenuation values shall be greater than or equal to zero.
             * @var {x3dom.fields.SFVec3f} attenuation
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'attenuation', 1, 0, 0);

            /**
             * The location field specifies a translation offset of the centre point of the light source from the light's local coordinate system origin.
             * This point is the apex of the solid angle which bounds light emission from the given light source.
             * Location is affected by ancestors' transformations.
             * @var {x3dom.fields.SFVec3f} location
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'location', 0, 0, 0);

            /**
             * The radius field specifies the radial extent of the solid angle and the maximum distance from location that may be illuminated by the light source.
             * The light source does not emit light outside this radius. The radius shall be greater than or equal to zero.
             * Radius is affected by ancestors' transformations.
             * @var {x3dom.fields.SFFloat} radius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 100
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', 100);

            /**
             *  The beamWidth field specifies an inner solid angle in which the light source emits light at uniform full intensity.
             *  The light source's emission intensity drops off from the inner solid angle (beamWidth) to the outer solid angle (cutOffAngle).
             *  If the beamWidth is greater than the cutOffAngle, beamWidth is defined to be equal to the cutOffAngle and the light source emits full intensity within the entire solid angle defined by cutOffAngle.
             * @var {x3dom.fields.SFFloat} beamWidth
             * @range [0, pi/2]
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 1.5707963
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'beamWidth', 1.5707963);

            /**
             * The cutOffAngle field specifies the outer bound of the solid angle. The light source does not emit light outside of this solid angle.
             * The light source's emission intensity drops off from the inner solid angle (beamWidth) to the outer solid angle (cutOffAngle).
             * If the beamWidth is greater than the cutOffAngle, beamWidth is defined to be equal to the cutOffAngle and the light source emits full intensity within the entire solid angle defined by cutOffAngle.
             * @range [0, pi/2]
             * @var {x3dom.fields.SFFloat} cutOffAngle
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 1.5707963
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'cutOffAngle', 1.5707963);

            /**
             *
             * @var {x3dom.fields.SFInt32} shadowCascades
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'shadowCascades', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} shadowSplitFactor
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'shadowSplitFactor', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} shadowSplitOffset
             * @memberof x3dom.nodeTypes.SpotLight
             * @initvalue 0.1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'shadowSplitOffset', 0.1);

            this._vf.global = true;
        
        },
        {
            getViewMatrix: function(vec) {
                var pos = this.getCurrentTransform().multMatrixPnt(this._vf.location);
                var dir = this.getCurrentTransform().multMatrixVec(this._vf.direction).normalize();
                var orientation = x3dom.fields.Quaternion.rotateFromTo(
                    new x3dom.fields.SFVec3f(0, 0, -1), dir);
                return orientation.toMatrix().transpose().
                    mult(x3dom.fields.SFMatrix4f.translation(pos.negate()));
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DFollowerNode ### */
x3dom.registerNodeType(
    "X3DFollowerNode",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DFollowerNode
         * @constructs x3dom.nodeTypes.X3DFollowerNode
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc An X3DFollowerNode maintains an internal state that consists of a current value and a destination
         *  value. Both values are of the same data type into which the term [S|M]F<type> evaluatesfor a given
         *  specialization. It is the 'data type of the node'. In certain cases of usage, the terms input and output fit
         *  better for destination value and current value, respectively.
         *  Whenever the current value differs from the destination value, the current value gradually changes until it
         *  reaches the destination value producing a smooth transition. It generally moves towards the destination
         *  value but, if a transition triggered by a prevous destination value is still in progress, it may take a
         *  short while until the movement becomes a movement towards the new destination value.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DFollowerNode.superClass.call(this, ctx);

            if (ctx)
                ctx.doc._nodeBag.followers.push(this);
            else
                x3dom.debug.logWarning("X3DFollowerNode: No runtime context found!");


            /**
             * isActive shows if the sensor is active
             * @var {x3dom.fields.SFBool} isActive
             * @memberof x3dom.nodeTypes.X3DFollowerNode
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'isActive', false);

            // http://www.web3d.org/files/specifications/19775-1/V3.3/Part01/components/followers.html
            // [S|M]F<type> [in]     set_destination
            // [S|M]F<type> [in]     set_value
            // [S|M]F<type> [out]    value
            // SFBool       [out]    isActive
            // [S|M]F<type> []       initialDestination
            // [S|M]F<type> []       initialValue

            this._eps = x3dom.fields.Eps; //0.001;
        
        },
        {
            parentRemoved: function(parent)
            {
                if (this._parentNodes.length === 0) {
                    var doc = this.findX3DDoc();

                    for (var i=0, n=doc._nodeBag.followers.length; i<n; i++) {
                        if (doc._nodeBag.followers[i] === this) {
                            doc._nodeBag.followers.splice(i, 1);
                        }
                    }
                }
            },

            tick: function(t) {
                return false;
            },

            stepResponse: function(t)
            {
                if (t <= 0) {
                    return 0;
                }

                if (t >= this._vf.duration) {
                    return 1;
                }

                // When optimizing for speed, the above two if(.) cases can be omitted,
                // as this function will not be called for values outside of 0..duration.
                return this.stepResponseCore(t / this._vf.duration);
            },

            // This function defines the shape of how the output responds to the initialDestination.
            // It must accept values for T in the range 0 <= T <= 1.
            // In this._vf.order to create a smooth animation, it should return 0 for T == 0,
            // 1 for T == 1 and be sufficient smooth in the range 0 <= T <= 1.
            //
            // It should be optimized for speed, in this._vf.order for high performance. It's
            // executed _buffer.length + 1 times each simulation tick.
            stepResponseCore: function(T)
            {
                return 0.5 - 0.5 * Math.cos(T * Math.PI);
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DChaserNode ### */
x3dom.registerNodeType(
    "X3DChaserNode",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DFollowerNode,
        
        /**
         * Constructor for X3DChaserNode
         * @constructs x3dom.nodeTypes.X3DChaserNode
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DFollowerNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The X3DChaserNode abstract node type calculates the output on value_changed as a finite impulse
         *  response (FIR).
         */
        function (ctx) {
            x3dom.nodeTypes.X3DChaserNode.superClass.call(this, ctx);


            /**
             * Duration of the transition
             * @var {x3dom.fields.SFTime} duration
             * @memberof x3dom.nodeTypes.X3DChaserNode
             * @initvalue 1
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'duration', 1);

            this._initDone = false;
            this._stepTime = 0;
            this._currTime = 0;
            this._bufferEndTime = 0;
            this._numSupports = 60;
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DDamperNode ### */
x3dom.registerNodeType(
    "X3DDamperNode",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DFollowerNode,
        
        /**
         * Constructor for X3DDamperNode
         * @constructs x3dom.nodeTypes.X3DDamperNode
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DFollowerNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The X3DDamperNode abstract node type creates an IIR response that approaches the destination
         *  value according to the shape of the e-function only asymptotically but very quickly. An X3DDamperNode node
         *  is parameterized by the tau, order and tolerance fields. Internally, it consists of a set of linear
         *  first-order filters each of which processes the output of the previous filter.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DDamperNode.superClass.call(this, ctx);


            /**
             * The field tau specifies the time-constant of the internal filters and thus the speed that the output of
             *  an X3DDamperNode responds to the input. A value of zero for tau means immediate response and the events
             *  received on set_destination are forwarded directly. The field tau specifies how long it takes the output
             *  of an internal filter to reach the value of its input by 63% (1 - 1/e). The remainder after that period
             *  is reduced by 63% during another period of tau seconds provided that the input of the filter does not
             *  change. This behavior can be exposed if order is set to one.
             * @var {x3dom.fields.SFTime} tau
             * @memberof x3dom.nodeTypes.X3DDamperNode
             * @initvalue 0.3
             * @range [0,inf)
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'tau', 0.3);

            /**
             * If tolerance is set to its default value -1, the browser implementation is allowed to find a good way for
             *  detecting the end of a transition. Browsers that do not have an elaborate algorithm can just use .001 as
             *  the tolerance value instead. If a value larger than zero is specified for tolerance, the browser shall
             *  calculate the difference between output and input for each internal filter being used and stop the
             *  animation only when all filters fall below that limit or are equal to it. If zero is specified for
             *  tolerance, a transition should be stopped only if input and output match exactly for all internal
             *  filters.
             * @var {x3dom.fields.SFFloat} tolerance
             * @memberof x3dom.nodeTypes.X3DDamperNode
             * @initvalue -1
             * @range -1 or [0,inf)
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'tolerance', -1);

            /**
             * The order field specifies the smoothness of the transition.
             * @var {x3dom.fields.SFInt32} order
             * @memberof x3dom.nodeTypes.X3DDamperNode
             * @initvalue 3
             * @range [0..5]
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'order', 3);

            this._eps = this._vf.tolerance < 0 ? this._eps : this._vf.tolerance;
            this._lastTick = 0;
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ColorChaser ### */
x3dom.registerNodeType(
    "ColorChaser",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DChaserNode,
        
        /**
         * Constructor for ColorChaser
         * @constructs x3dom.nodeTypes.ColorChaser
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChaserNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ColorChaser animates transitions for single color values. Whenever the set_destination
         *  field receives a floating point number, the value_changed creates a transition from its current value to
         *  the newly set number. It creates a smooth transition that ends duration seconds after the last number has
         *  been received.
         */
        function (ctx) {
            x3dom.nodeTypes.ColorChaser.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value as initialValue unless a transition to a
             *  certain value is to be created right after the scene is loaded or right after the ColorChaser node is
             *  created dynamically.
             * @var {x3dom.fields.SFColor} initialDestination
             * @memberof x3dom.nodeTypes.ColorChaser
             * @initvalue 0.8,0.8,0.8
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'initialDestination', 0.8, 0.8, 0.8);

            /**
             * The field initialValue can be used to set the initial value.
             * @var {x3dom.fields.SFColor} initialValue
             * @memberof x3dom.nodeTypes.ColorChaser
             * @initvalue 0.8,0.8,0.8
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'initialValue', 0.8, 0.8, 0.8);


            /**
             * The current color value
             * @var {x3dom.fields.SFColor} value
             * @memberof x3dom.nodeTypes.ColorChaser
             * @initvalue 0,0,0
             * @range [0,1]
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'value', 0, 0, 0);

            /**
             * The target color value
             * @var {x3dom.fields.SFColor} destination
             * @memberof x3dom.nodeTypes.ColorChaser
             * @initvalue 0,0,0
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'destination', 0, 0, 0);

            this._buffer = new x3dom.fields.MFColor();
            this._previousValue = new x3dom.fields.SFColor(0, 0, 0);
            this._value = new x3dom.fields.SFColor(0, 0, 0);

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName.indexOf("destination") >= 0)
                {
                    this.initialize();
                    this.updateBuffer(this._currTime);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this.initialize();

                    this._previousValue.setValues(this._vf.value);
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C].setValues(this._vf.value);
                    }

                    this.postMessage('value', this._vf.value);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
            },

            /** The following handler code is copy & paste from PositionChaser
             */
            initialize: function()
            {
                if (!this._initDone)
                {
                    this._initDone = true;

                    this._vf.destination = this._vf.initialDestination;

                    this._buffer.length = this._numSupports;

                    this._buffer[0] = this._vf.initialDestination;
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C] = this._vf.initialValue;
                    }

                    this._previousValue = this._vf.initialValue;

                    this._stepTime = this._vf.duration / this._numSupports;

                    var active = !this._buffer[0].equals(this._buffer[1], this._eps);
                    if (this._vf.isActive !== active) {
                        this.postMessage('isActive', active);
                    }
                }
            },

            tick: function(now)
            {
                this.initialize();
                this._currTime = now;

                //if (!this._vf.isActive)
                //    return false;

                if (!this._bufferEndTime)
                {
                    this._bufferEndTime = now;  // on init

                    this._value = this._vf.initialValue;

                    this.postMessage('value', this._value);

                    return true;
                }

                var Frac = this.updateBuffer(now);

                var Output = this._previousValue;

                var DeltaIn = this._buffer[this._buffer.length - 1].subtract(this._previousValue);

                var DeltaOut = DeltaIn.multiply(this.stepResponse((this._buffer.length - 1 + Frac) * this._stepTime));

                Output = Output.add(DeltaOut);

                for (var C=this._buffer.length - 2; C>=0; C--)
                {
                    DeltaIn = this._buffer[C].subtract(this._buffer[C + 1]);

                    DeltaOut = DeltaIn.multiply(this.stepResponse((C + Frac) * this._stepTime));

                    Output = Output.add(DeltaOut);
                }

                if ( !Output.equals(this._value, this._eps) ) {
                    this._value.setValues(Output);

                    this.postMessage('value', this._value);
                }
                else {
                    this.postMessage('isActive', false);
                }

                return this._vf.isActive;
            },

            updateBuffer: function(now)
            {
                var Frac = (now - this._bufferEndTime) / this._stepTime;
                var C;
                var NumToShift;
                var Alpha;

                if (Frac >= 1)
                {
                    NumToShift = Math.floor(Frac);
                    Frac -= NumToShift;

                    if( NumToShift < this._buffer.length)
                    {
                        this._previousValue = this._buffer[this._buffer.length - NumToShift];

                        for (C=this._buffer.length - 1; C>=NumToShift; C--) {
                            this._buffer[C] = this._buffer[C - NumToShift];
                        }

                        for (C=0; C<NumToShift; C++)
                        {
                            Alpha = C / NumToShift;

                            this._buffer[C] = this._buffer[NumToShift].multiply(Alpha).add(this._vf.destination.multiply((1 - Alpha)));
                        }
                    }
                    else
                    {
                        this._previousValue = (NumToShift == this._buffer.length) ? this._buffer[0] : this._vf.destination;

                        for (C= 0; C<this._buffer.length; C++) {
                            this._buffer[C] = this._vf.destination;
                        }
                    }
                    this._bufferEndTime += NumToShift * this._stepTime;
                }
                return Frac;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ColorDamper ### */
x3dom.registerNodeType(
    "ColorDamper",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DDamperNode,
        
        /**
         * Constructor for ColorDamper
         * @constructs x3dom.nodeTypes.ColorDamper
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDamperNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ColorDamper animates color values. Whenever the it receives a color, the ColorDamper node
         *  creates a transition from the current color to the newly set color. The transition created approaches the
         *  newly set position asymptotically during a time period of approximately three to four times the value of
         *  the field tau depending on the desired accuracy and the value of order. The order field specifies the
         *  smoothness of the transition.
         */
        function (ctx) {
            x3dom.nodeTypes.ColorDamper.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain color is to be created right after the scene is loaded or right after the ColorDamper node is
             *  created dynamically.
             * @var {x3dom.fields.SFColor} initialDestination
             * @memberof x3dom.nodeTypes.ColorDamper
             * @initvalue 0.8,0.8,0.8
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'initialDestination', 0.8, 0.8, 0.8);

            /**
             * The field initialValue can be used to set the initial color.
             * @var {x3dom.fields.SFColor} initialValue
             * @memberof x3dom.nodeTypes.ColorDamper
             * @initvalue 0.8,0.8,0.8
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'initialValue', 0.8, 0.8, 0.8);


            /**
             * The current color value
             * @var {x3dom.fields.SFColor} value
             * @memberof x3dom.nodeTypes.ColorDamper
             * @initvalue 0,0,0
             * @range [0,1]
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'value', 0, 0, 0);

            /**
             * The target color value
             * @var {x3dom.fields.SFColor} destination
             * @memberof x3dom.nodeTypes.ColorDamper
             * @initvalue 0,0,0
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'destination', 0, 0, 0);

            this._value0 = new x3dom.fields.SFColor(0, 0, 0);
            this._value1 = new x3dom.fields.SFColor(0, 0, 0);
            this._value2 = new x3dom.fields.SFColor(0, 0, 0);
            this._value3 = new x3dom.fields.SFColor(0, 0, 0);
            this._value4 = new x3dom.fields.SFColor(0, 0, 0);
            this._value5 = new x3dom.fields.SFColor(0, 0, 0);

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName === "tolerance")
                {
                    this._eps = this._vf.tolerance < 0 ? 0.001 : this._vf.tolerance;
                }
                else if (fieldName.indexOf("destination") >= 0)
                {
                    if ( !this._value0.equals(this._vf.destination, this._eps) ) {
                        this._value0 = this._vf.destination;

                        if (!this._vf.isActive) {
                            //this._lastTick = 0;
                            this.postMessage('isActive', true);
                        }
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this._value1.setValues(this._vf.value);
                    this._value2.setValues(this._vf.value);
                    this._value3.setValues(this._vf.value);
                    this._value4.setValues(this._vf.value);
                    this._value5.setValues(this._vf.value);
                    this._lastTick = 0;

                    this.postMessage('value', this._value5);

                    if (!this._vf.isActive) {
                        this._lastTick = 0;
                        this.postMessage('isActive', true);
                    }
                }
            },

            initialize: function()
            {
                this._value0.setValues(this._vf.initialDestination);
                this._value1.setValues(this._vf.initialValue);
                this._value2.setValues(this._vf.initialValue);
                this._value3.setValues(this._vf.initialValue);
                this._value4.setValues(this._vf.initialValue);
                this._value5.setValues(this._vf.initialValue);
                this._lastTick = 0;

                var active = !this._value0.equals(this._value1, this._eps);
                if (this._vf.isActive !== active) {
                    this.postMessage('isActive', active);
                }
            },

            distance: function(a, b)
            {
                var diff = a.subtract(b);
                return Math.sqrt(diff.r*diff.r + diff.g*diff.g + diff.b*diff.b);
            },

            // The ColorDamper animates SFColor values not in HSV space
            // but as proposed in the original PROTO code in RGB space.
            tick: function(now)
            {
                //if (!this._vf.isActive)
                //    return false;

                if (!this._lastTick)
                {
                    this._lastTick = now;
                    return false;
                }

                var delta = now - this._lastTick;

                var alpha = Math.exp(-delta / this._vf.tau);

                this._value1 = this._vf.order > 0 && this._vf.tau ?
                    this._value0.add(this._value1.subtract(this._value0).multiply(alpha)) :
                    new x3dom.fields.SFColor(this._value0.r, this._value0.g, this._value0.b);

                this._value2 = this._vf.order > 1 && this._vf.tau ?
                    this._value1.add(this._value2.subtract(this._value1).multiply(alpha)) :
                    new x3dom.fields.SFColor(this._value1.r, this._value1.g, this._value1.b);

                this._value3 = this._vf.order > 2 && this._vf.tau ?
                    this._value2.add(this._value3.subtract(this._value2).multiply(alpha)) :
                    new x3dom.fields.SFColor(this._value2.r, this._value2.g, this._value2.b);

                this._value4 = this._vf.order > 3 && this._vf.tau ?
                    this._value3.add(this._value4.subtract(this._value3).multiply(alpha)) :
                    new x3dom.fields.SFColor(this._value3.r, this._value3.g, this._value3.b);

                this._value5 = this._vf.order > 4 && this._vf.tau ?
                    this._value4.add(this._value5.subtract(this._value4).multiply(alpha)) :
                    new x3dom.fields.SFColor(this._value4.r, this._value4.g, this._value4.b);

                var dist = this.distance(this._value1, this._value0);

                if (this._vf.order > 1)
                {
                    var dist2 = this.distance(this._value2, this._value1);
                    if (dist2 > dist) { dist = dist2; }
                }
                if (this._vf.order > 2)
                {
                    var dist3 = this.distance(this._value3, this._value2);
                    if (dist3 > dist) { dist = dist3; }
                }
                if (this._vf.order > 3)
                {
                    var dist4 = this.distance(this._value4, this._value3);
                    if (dist4 > dist) { dist = dist4; }
                }
                if (this._vf.order > 4)
                {
                    var dist5 = this.distance(this._value5, this._value4);
                    if (dist5 > dist) { dist = dist5; }
                }

                if (dist <= this._eps)
                {
                    this._value1.setValues(this._value0);
                    this._value2.setValues(this._value0);
                    this._value3.setValues(this._value0);
                    this._value4.setValues(this._value0);
                    this._value5.setValues(this._value0);

                    this.postMessage('value', this._value0);
                    this.postMessage('isActive', false);

                    this._lastTick = 0;

                    return false;
                }

                this.postMessage('value', this._value5);

                this._lastTick = now;

                return true;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### OrientationChaser ### */
x3dom.registerNodeType(
    "OrientationChaser",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DChaserNode,
        
        /**
         * Constructor for OrientationChaser
         * @constructs x3dom.nodeTypes.OrientationChaser
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChaserNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The OrientationChaser animates transitions for orientations. If it is routed to a rotation field
         *  of a Transform node that contains an object, whenever the set_destination field receives an orientation, the
         *  OrientationChaser node rotates the object from its current orientation to the newly set orientation.
         *  It creates a smooth transition that ends duration seconds after the last orientation has been received.
         */
        function (ctx) {
            x3dom.nodeTypes.OrientationChaser.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain orientation is to be created right after the scene is loaded or right after the
             *  OrientationChaser node is created dynamically.
             * @var {x3dom.fields.SFRotation} initialDestination
             * @memberof x3dom.nodeTypes.OrientationChaser
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'initialDestination', 0, 1, 0, 0);

            /**
             * The field initialValue can be used to set the initial orientation of the object.
             * @var {x3dom.fields.SFRotation} initialValue
             * @memberof x3dom.nodeTypes.OrientationChaser
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'initialValue', 0, 1, 0, 0);


            /**
             * The current orientation value.
             * @var {x3dom.fields.SFRotation} value
             * @memberof x3dom.nodeTypes.OrientationChaser
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'value', 0, 1, 0, 0);

            /**
             * The target orientation value.
             * @var {x3dom.fields.SFRotation} destination
             * @memberof x3dom.nodeTypes.OrientationChaser
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'destination', 0, 1, 0, 0);

            this._numSupports = 30;
            this._buffer = new x3dom.fields.MFRotation();
            this._previousValue = new x3dom.fields.Quaternion(0, 1, 0, 0);
            this._value = new x3dom.fields.Quaternion(0, 1, 0, 0);

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName.indexOf("destination") >= 0)
                {
                    this.initialize();
                    this.updateBuffer(this._currTime);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this.initialize();

                    this._previousValue.setValues(this._vf.value);
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C].setValues(this._vf.value);
                    }

                    this.postMessage('value', this._vf.value);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
            },

            /** The following handler code was basically taken from
             *  http://www.hersto.com/X3D/Followers
             */
            initialize: function()
            {
                if (!this._initDone)
                {
                    this._initDone = true;

                    this._vf.destination = x3dom.fields.Quaternion.copy(this._vf.initialDestination);

                    this._buffer.length = this._numSupports;

                    this._buffer[0] = x3dom.fields.Quaternion.copy(this._vf.initialDestination);
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C] = x3dom.fields.Quaternion.copy(this._vf.initialValue);
                    }

                    this._previousValue = x3dom.fields.Quaternion.copy(this._vf.initialValue);

                    this._stepTime = this._vf.duration / this._numSupports;

                    var active = !this._buffer[0].equals(this._buffer[1], this._eps);
                    if (this._vf.isActive !== active) {
                        this.postMessage('isActive', active);
                    }
                }
            },

            tick: function(now)
            {
                this.initialize();
                this._currTime = now;

                //if (!this._vf.isActive)
                //    return false;

                if (!this._bufferEndTime)
                {
                    this._bufferEndTime = now; // first event we received, so we are in the initialization phase.

                    this._value = x3dom.fields.Quaternion.copy(this._vf.initialValue);

                    this.postMessage('value', this._value);

                    return true;
                }

                var Frac = this.updateBuffer(now);
                // Frac is a value in   0 <= Frac < 1.

                // now we can calculate the output.
                // This means we calculate the delta between each entry in _buffer and its previous
                // entries, calculate the step response of each such step and add it to form the output.

                // The oldest value _buffer[_buffer.length - 1] needs some extra thought, because it has
                // no previous value. More exactly, we haven't stored a previous value anymore.
                // However, the step response of that missing previous value has already reached its
                // destination, so we can - would we have that previous value - use this as a start point
                // for adding the step responses.
                // Actually updateBuffer(.) maintains this value in

                var Output = x3dom.fields.Quaternion.copy(this._previousValue);

                var DeltaIn = this._previousValue.inverse().multiply(this._buffer[this._buffer.length - 1]);

                Output = Output.slerp(Output.multiply(DeltaIn), this.stepResponse((this._buffer.length - 1 + Frac) * this._stepTime));

                for (var C=this._buffer.length - 2; C>=0; C--)
                {
                    DeltaIn = this._buffer[C + 1].inverse().multiply(this._buffer[C]);

                    Output = Output.slerp(Output.multiply(DeltaIn), this.stepResponse((C + Frac) * this._stepTime));
                }

                if ( !Output.equals(this._value, this._eps) ) {
                    Output = Output.normalize(Output);
                    this._value.setValues(Output);

                    this.postMessage('value', this._value);
                }
                else {
                    this.postMessage('isActive', false);
                }

                return this._vf.isActive;
            },

            updateBuffer: function(now)
            {
                var Frac = (now - this._bufferEndTime) / this._stepTime;
                var C;
                var NumToShift;
                var Alpha;
                // is normally < 1. When it has grown to be larger than 1, we have to shift the array because the step response
                // of the oldest entry has already reached its destination, and it's time for a newer entry.
                // In the case of a very low frame rate, or a very short _stepTime we may need to shift by more than one entry.

                if (Frac >= 1)
                {
                    NumToShift = Math.floor(Frac);
                    Frac -= NumToShift;

                    if( NumToShift < this._buffer.length)
                    {
                        // normal case
                        this._previousValue = x3dom.fields.Quaternion.copy(this._buffer[this._buffer.length - NumToShift]);

                        for (C=this._buffer.length - 1; C>=NumToShift; C--) {
                            this._buffer[C] = x3dom.fields.Quaternion.copy(this._buffer[C - NumToShift]);
                        }

                        for (C=0; C<NumToShift; C++)
                        {
                            // Hmm, we have a destination value, but don't know how it has
                            // reached the current state.
                            // Therefore we do a linear interpolation from the latest value in the buffer to destination.
                            Alpha = C / NumToShift;

                            this._buffer[C] = this._vf.destination.slerp(this._buffer[NumToShift], Alpha);
                        }
                    }
                    else
                    {
                        // degenerated case:
                        //
                        // We have a _VERY_ low frame rate...
                        // we can only guess how we should fill the array.
                        // Maybe we could write part of a linear interpolation
                        // from this._buffer[0] to destination, that goes from this._bufferEndTime to now
                        // (possibly only the end of the interpolation is to be written),
                        // but if we reach here we are in a very degenerate case...
                        // Thus we just write destination to the buffer.

                        this._previousValue = x3dom.fields.Quaternion.copy((NumToShift == this._buffer.length) ?
                            this._buffer[0] : this._vf.destination);

                        for (C= 0; C<this._buffer.length; C++) {
                            this._buffer[C] = x3dom.fields.Quaternion.copy(this._vf.destination);
                        }
                    }

                    this._bufferEndTime += NumToShift * this._stepTime;
                }

                return Frac;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### OrientationDamper ### */
x3dom.registerNodeType(
    "OrientationDamper",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DDamperNode,
        
        /**
         * Constructor for OrientationDamper
         * @constructs x3dom.nodeTypes.OrientationDamper
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDamperNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The OrientationDamper animates transitions of orientations. If its value is routed to an
         *  orientation field of a Transform node that contains an object, then, whenever the destination field receives
         *  an orientation, the OrientationDamper node rotates the object from its current orientation to the newly set
         *  orientation. It creates a transition that approaches the newly set orientation asymptotically during a time
         *  period of approximately three to four times the value of the field tau depending on the desired accuracy and
         *  the value of order. Through this asymptotic approach of the destination orientation, a very smooth
         *  transition is created.
         */
        function (ctx) {
            x3dom.nodeTypes.OrientationDamper.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain orientation is to be created right after the scene is loaded or right after the
             *  OrientationDamper node is created dynamically.
             * @var {x3dom.fields.SFRotation} initialDestination
             * @memberof x3dom.nodeTypes.OrientationDamper
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'initialDestination', 0, 1, 0, 0);

            /**
             * The field initialValue can be used to set the initial orientation of the object.
             * @var {x3dom.fields.SFRotation} initialValue
             * @memberof x3dom.nodeTypes.OrientationDamper
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'initialValue', 0, 1, 0, 0);


            /**
             * The current orientation value.
             * @var {x3dom.fields.SFRotation} value
             * @memberof x3dom.nodeTypes.OrientationDamper
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'value', 0, 1, 0, 0);

            /**
             * The target orientation value
             * @var {x3dom.fields.SFRotation} destination
             * @memberof x3dom.nodeTypes.OrientationDamper
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'destination', 0, 1, 0, 0);

            this._value0 = new x3dom.fields.Quaternion(0, 1, 0, 0);
            this._value1 = new x3dom.fields.Quaternion(0, 1, 0, 0);
            this._value2 = new x3dom.fields.Quaternion(0, 1, 0, 0);
            this._value3 = new x3dom.fields.Quaternion(0, 1, 0, 0);
            this._value4 = new x3dom.fields.Quaternion(0, 1, 0, 0);
            this._value5 = new x3dom.fields.Quaternion(0, 1, 0, 0);

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName === "tolerance")
                {
                    this._eps = this._vf.tolerance < 0 ? 0.001 : this._vf.tolerance;
                }
                else if (fieldName.indexOf("destination") >= 0)
                {
                    if ( !this._value0.equals(this._vf.destination, this._eps) ) {
                        this._value0 = this._vf.destination;

                        if (!this._vf.isActive) {
                            //this._lastTick = 0;
                            this.postMessage('isActive', true);
                        }
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this._value1.setValues(this._vf.value);
                    this._value2.setValues(this._vf.value);
                    this._value3.setValues(this._vf.value);
                    this._value4.setValues(this._vf.value);
                    this._value5.setValues(this._vf.value);
                    this._lastTick = 0;

                    this.postMessage('value', this._value5);

                    if (!this._vf.isActive) {
                        this._lastTick = 0;
                        this.postMessage('isActive', true);
                    }
                }
            },

            initialize: function()
            {
                this._value0.setValues(this._vf.initialDestination);
                this._value1.setValues(this._vf.initialValue);
                this._value2.setValues(this._vf.initialValue);
                this._value3.setValues(this._vf.initialValue);
                this._value4.setValues(this._vf.initialValue);
                this._value5.setValues(this._vf.initialValue);
                this._lastTick = 0;

                var active = !this._value0.equals(this._value1, this._eps);
                if (this._vf.isActive !== active) {
                    this.postMessage('isActive', active);
                }
            },

            tick: function(now)
            {
                //if (!this._vf.isActive)
                //    return false;

                if (!this._lastTick)
                {
                    this._lastTick = now;
                    return false;
                }

                var delta = now - this._lastTick;

                var alpha = Math.exp(-delta / this._vf.tau);

                this._value1 = this._vf.order > 0 && this._vf.tau ?
                    this._value0.slerp(this._value1, alpha) :
                    new x3dom.fields.Quaternion(this._value0.x, this._value0.y, this._value0.z, this._value0.w);

                this._value2 = this._vf.order > 1 && this._vf.tau ?
                    this._value1.slerp(this._value2, alpha) :
                    new x3dom.fields.Quaternion(this._value1.x, this._value1.y, this._value1.z, this._value1.w);

                this._value3 = this._vf.order > 2 && this._vf.tau ?
                    this._value2.slerp(this._value3, alpha) :
                    new x3dom.fields.Quaternion(this._value2.x, this._value2.y, this._value2.z, this._value2.w);

                this._value4 = this._vf.order > 3 && this._vf.tau ?
                    this._value3.slerp(this._value4, alpha) :
                    new x3dom.fields.Quaternion(this._value3.x, this._value3.y, this._value3.z, this._value3.w);

                this._value5 = this._vf.order > 4 && this._vf.tau ?
                    this._value4.slerp(this._value5, alpha) :
                    new x3dom.fields.Quaternion(this._value4.x, this._value4.y, this._value4.z, this._value4.w);

                var dist = Math.abs(this._value1.inverse().multiply(this._value0).angle());

                if(this._vf.order > 1)
                {
                    var dist2 = Math.abs(this._value2.inverse().multiply(this._value1).angle());
                    if (dist2 > dist)  { dist = dist2; }
                }
                if(this._vf.order > 2)
                {
                    var dist3 = Math.abs(this._value3.inverse().multiply(this._value2).angle());
                    if (dist3 > dist) { dist = dist3; }
                }
                if(this._vf.order > 3)
                {
                    var dist4 = Math.abs(this._value4.inverse().multiply(this._value3).angle());
                    if (dist4 > dist)  { dist = dist4; }
                }
                if(this._vf.order > 4)
                {
                    var dist5 = Math.abs(this._value5.inverse().multiply(this._value4).angle());
                    if (dist5 > dist)  { dist = dist5; }
                }

                if (dist <= this._eps)
                {
                    this._value1.setValues(this._value0);
                    this._value2.setValues(this._value0);
                    this._value3.setValues(this._value0);
                    this._value4.setValues(this._value0);
                    this._value5.setValues(this._value0);

                    this.postMessage('value', this._value0);
                    this.postMessage('isActive', false);

                    this._lastTick = 0;

                    return false;
                }

                this.postMessage('value', this._value5);

                this._lastTick = now;

                return true;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PositionChaser ### */
x3dom.registerNodeType(
    "PositionChaser",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DChaserNode,
        
        /**
         * Constructor for PositionChaser
         * @constructs x3dom.nodeTypes.PositionChaser
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChaserNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PositionChaser animates transitions for 3D vectors. If its value field is routed to a
         *  translation field of a Transform node that contains an object, then, whenever the destination field
         *  receives a 3D position, the PositionChaser node moves the object from its current position to the newly set
         *  position. It creates a smooth transition that ends duration seconds after the last position has been
         *  received.
         */
        function (ctx) {
            x3dom.nodeTypes.PositionChaser.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain position is to be created right after the scene is loaded or right after the PositionChaser node
             *  is created dynamically.
             * @var {x3dom.fields.SFVec3f} initialDestination
             * @memberof x3dom.nodeTypes.PositionChaser
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'initialDestination', 0, 0, 0);

            /**
             * The field initialValue can be used to set the initial position of the object.
             * @var {x3dom.fields.SFVec3f} initialValue
             * @memberof x3dom.nodeTypes.PositionChaser
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'initialValue', 0, 0, 0);


            /**
             * The current orientation value.
             * @var {x3dom.fields.SFVec3f} value
             * @memberof x3dom.nodeTypes.PositionChaser
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'value', 0, 0, 0);

            /**
             * The target orientation value.
             * @var {x3dom.fields.SFVec3f} destination
             * @memberof x3dom.nodeTypes.PositionChaser
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'destination', 0, 0, 0);

            this._buffer = new x3dom.fields.MFVec3f();
            this._previousValue = new x3dom.fields.SFVec3f(0, 0, 0);
            this._value = new x3dom.fields.SFVec3f(0, 0, 0);

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName.indexOf("destination") >= 0)
                {
                    this.initialize();
                    this.updateBuffer(this._currTime);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this.initialize();

                    this._previousValue.setValues(this._vf.value);
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C].setValues(this._vf.value);
                    }

                    this.postMessage('value', this._vf.value);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
            },

            /** The following handler code was basically taken from
             *  http://www.hersto.com/X3D/Followers
             */
            initialize: function()
            {
                if (!this._initDone)
                {
                    this._initDone = true;

                    this._vf.destination = x3dom.fields.SFVec3f.copy(this._vf.initialDestination);

                    this._buffer.length = this._numSupports;

                    this._buffer[0] = x3dom.fields.SFVec3f.copy(this._vf.initialDestination);
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C] = x3dom.fields.SFVec3f.copy(this._vf.initialValue);
                    }

                    this._previousValue = x3dom.fields.SFVec3f.copy(this._vf.initialValue);

                    this._stepTime = this._vf.duration / this._numSupports;

                    var active = !this._buffer[0].equals(this._buffer[1], this._eps);
                    if (this._vf.isActive !== active) {
                        this.postMessage('isActive', active);
                    }
                }
            },

            tick: function(now)
            {
                this.initialize();
                this._currTime = now;

                //if (!this._vf.isActive)
                //    return false;

                if (!this._bufferEndTime)
                {
                    this._bufferEndTime = now; // first event we received, so we are in the initialization phase.

                    this._value = x3dom.fields.SFVec3f.copy(this._vf.initialValue);

                    this.postMessage('value', this._value);

                    return true;
                }

                var Frac = this.updateBuffer(now);
                // Frac is a value in   0 <= Frac < 1.

                // now we can calculate the output.
                // This means we calculate the delta between each entry in _buffer and its previous
                // entries, calculate the step response of each such step and add it to form the output.

                // The oldest value _buffer[_buffer.length - 1] needs some extra thought, because it has
                // no previous value. More exactly, we haven't stored a previous value anymore.
                // However, the step response of that missing previous value has already reached its
                // destination, so we can - would we have that previous value - use this as a start point
                // for adding the step responses.
                // Actually updateBuffer(.) maintains this value in

                var Output = x3dom.fields.SFVec3f.copy(this._previousValue);

                var DeltaIn = this._buffer[this._buffer.length - 1].subtract(this._previousValue);

                var DeltaOut = DeltaIn.multiply(this.stepResponse((this._buffer.length - 1 + Frac) * this._stepTime));

                Output = Output.add(DeltaOut);

                for (var C=this._buffer.length - 2; C>=0; C--)
                {
                    DeltaIn = this._buffer[C].subtract(this._buffer[C + 1]);

                    DeltaOut = DeltaIn.multiply(this.stepResponse((C + Frac) * this._stepTime));

                    Output = Output.add(DeltaOut);
                }

                if ( !Output.equals(this._value, this._eps) ) {
                    this._value.setValues(Output);

                    this.postMessage('value', this._value);
                }
                else {
                    this.postMessage('isActive', false);
                }

                return this._vf.isActive;
            },

            updateBuffer: function(now)
            {
                var Frac = (now - this._bufferEndTime) / this._stepTime;
                var C;
                var NumToShift;
                var Alpha;
                // is normally < 1. When it has grown to be larger than 1, we have to shift the array because the step response
                // of the oldest entry has already reached its destination, and it's time for a newer entry.
                // In the case of a very low frame rate, or a very short _stepTime we may need to shift by more than one entry.

                if (Frac >= 1)
                {
                    NumToShift = Math.floor(Frac);
                    Frac -= NumToShift;

                    if( NumToShift < this._buffer.length)
                    {
                        // normal case
                        this._previousValue = x3dom.fields.SFVec3f.copy(this._buffer[this._buffer.length - NumToShift]);

                        for (C=this._buffer.length - 1; C>=NumToShift; C--) {
                            this._buffer[C] = x3dom.fields.SFVec3f.copy(this._buffer[C - NumToShift]);
                        }

                        for (C=0; C<NumToShift; C++)
                        {
                            // Hmm, we have a destination value, but don't know how it has
                            // reached the current state.
                            // Therefore we do a linear interpolation from the latest value in the buffer to destination.
                            Alpha = C / NumToShift;

                            this._buffer[C] = this._buffer[NumToShift].multiply(Alpha).add(this._vf.destination.multiply((1 - Alpha)));
                        }
                    }
                    else
                    {
                        // degenerated case:
                        //
                        // We have a _VERY_ low frame rate...
                        // we can only guess how we should fill the array.
                        // Maybe we could write part of a linear interpolation
                        // from this._buffer[0] to destination, that goes from this._bufferEndTime to now
                        // (possibly only the end of the interpolation is to be written),
                        // but if we reach here we are in a very degenerate case...
                        // Thus we just write destination to the buffer.

                        this._previousValue = x3dom.fields.SFVec3f.copy((NumToShift == this._buffer.length) ?
                            this._buffer[0] : this._vf.destination);

                        for (C= 0; C<this._buffer.length; C++) {
                            this._buffer[C] = x3dom.fields.SFVec3f.copy(this._vf.destination);
                        }
                    }

                    this._bufferEndTime += NumToShift * this._stepTime;
                }

                return Frac;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PositionChaser2D ### */
x3dom.registerNodeType(
    "PositionChaser2D",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DChaserNode,
        
        /**
         * Constructor for PositionChaser2D
         * @constructs x3dom.nodeTypes.PositionChaser2D
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChaserNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PositionChaser2D animates transitions for 2D vectors. Whenever its destination field receives
         *  a 2D vector it creates a transition from its current 2D vector value to the newly set value. It creates a
         *  smooth transition that ends duration seconds after the last 2D vector has been received.
         */
        function (ctx) {
            x3dom.nodeTypes.PositionChaser2D.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain 2D vector value is to be created right after the scene is loaded or right after the
             *  PositionChaser2D node is created dynamically.
             * @var {x3dom.fields.SFVec2f} initialDestination
             * @memberof x3dom.nodeTypes.PositionChaser2D
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'initialDestination', 0, 0);

            /**
             * The field initialValue can be used to set the initial initial value.
             * @var {x3dom.fields.SFVec2f} initialValue
             * @memberof x3dom.nodeTypes.PositionChaser2D
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'initialValue', 0, 0);


            /**
             * The current 2D position.
             * @var {x3dom.fields.SFVec2f} value
             * @memberof x3dom.nodeTypes.PositionChaser2D
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'value', 0, 0);

            /**
             * The target 2D position.
             * @var {x3dom.fields.SFVec2f} destination
             * @memberof x3dom.nodeTypes.PositionChaser2D
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'destination', 0, 0);

            this._buffer = new x3dom.fields.MFVec2f();
            this._previousValue = new x3dom.fields.SFVec2f(0, 0);
            this._value = new x3dom.fields.SFVec2f(0, 0);

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName.indexOf("destination") >= 0)
                {
                    this.initialize();
                    this.updateBuffer(this._currTime);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this.initialize();

                    this._previousValue.setValues(this._vf.value);
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C].setValues(this._vf.value);
                    }

                    this.postMessage('value', this._vf.value);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
            },

            /** The following handler code is copy & paste from PositionChaser
             */
            initialize: function()
            {
                if (!this._initDone)
                {
                    this._initDone = true;

                    this._vf.destination = x3dom.fields.SFVec2f.copy(this._vf.initialDestination);

                    this._buffer.length = this._numSupports;

                    this._buffer[0] = x3dom.fields.SFVec2f.copy(this._vf.initialDestination);
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C] = x3dom.fields.SFVec2f.copy(this._vf.initialValue);
                    }

                    this._previousValue = x3dom.fields.SFVec2f.copy(this._vf.initialValue);

                    this._stepTime = this._vf.duration / this._numSupports;

                    var active = !this._buffer[0].equals(this._buffer[1], this._eps);
                    if (this._vf.isActive !== active) {
                        this.postMessage('isActive', active);
                    }
                }
            },

            tick: function(now)
            {
                this.initialize();
                this._currTime = now;

                //if (!this._vf.isActive)
                //    return false;

                if (!this._bufferEndTime)
                {
                    this._bufferEndTime = now;

                    this._value = x3dom.fields.SFVec2f.copy(this._vf.initialValue);

                    this.postMessage('value', this._value);

                    return true;
                }

                var Frac = this.updateBuffer(now);

                var Output = x3dom.fields.SFVec2f.copy(this._previousValue);

                var DeltaIn = this._buffer[this._buffer.length - 1].subtract(this._previousValue);

                var DeltaOut = DeltaIn.multiply(this.stepResponse((this._buffer.length - 1 + Frac) * this._stepTime));

                Output = Output.add(DeltaOut);

                for (var C=this._buffer.length - 2; C>=0; C--)
                {
                    DeltaIn = this._buffer[C].subtract(this._buffer[C + 1]);

                    DeltaOut = DeltaIn.multiply(this.stepResponse((C + Frac) * this._stepTime));

                    Output = Output.add(DeltaOut);
                }

                if ( !Output.equals(this._value, this._eps) ) {
                    this._value.setValues(Output);

                    this.postMessage('value', this._value);
                }
                else {
                    this.postMessage('isActive', false);
                }

                return this._vf.isActive;
            },

            updateBuffer: function(now)
            {
                var Frac = (now - this._bufferEndTime) / this._stepTime;
                var C;
                var NumToShift;
                var Alpha;

                if (Frac >= 1)
                {
                    NumToShift = Math.floor(Frac);
                    Frac -= NumToShift;

                    if( NumToShift < this._buffer.length)
                    {
                        this._previousValue = x3dom.fields.SFVec2f.copy(this._buffer[this._buffer.length - NumToShift]);

                        for (C=this._buffer.length - 1; C>=NumToShift; C--) {
                            this._buffer[C]= x3dom.fields.SFVec2f.copy(this._buffer[C - NumToShift]);
                        }

                        for (C=0; C<NumToShift; C++)
                        {
                            Alpha = C / NumToShift;

                            this._buffer[C] = this._buffer[NumToShift].multiply(Alpha).add(this._vf.destination.multiply((1 - Alpha)));
                        }
                    }
                    else
                    {
                        this._previousValue = x3dom.fields.SFVec2f.copy((NumToShift == this._buffer.length) ?
                            this._buffer[0] : this._vf.destination);

                        for (C= 0; C<this._buffer.length; C++) {
                            this._buffer[C] = x3dom.fields.SFVec2f.copy(this._vf.destination);
                        }
                    }

                    this._bufferEndTime += NumToShift * this._stepTime;
                }

                return Frac;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PositionDamper ### */
x3dom.registerNodeType(
    "PositionDamper",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DDamperNode,
        
        /**
         * Constructor for PositionDamper
         * @constructs x3dom.nodeTypes.PositionDamper
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDamperNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PositionDamper animates transitions for 3D vectors. If its value field is routed to a
         *  translation field of a Transform node that contains an object, then, whenever the destination field receives
         *  a 3D position, the PositionDamper node moves the object from its current position to the newly set position.
         */
        function (ctx) {
            x3dom.nodeTypes.PositionDamper.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialvalue unless a transition to a
             *  certain position is to be created right after the scene is loaded or right after the PositionDamper node
             *  is created dynamically.
             * @var {x3dom.fields.SFVec3f} initialDestination
             * @memberof x3dom.nodeTypes.PositionDamper
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'initialDestination', 0, 0, 0);

            /**
             * The field initialValue can be used to set the initial position of the object.
             * @var {x3dom.fields.SFVec3f} initialValue
             * @memberof x3dom.nodeTypes.PositionDamper
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'initialValue', 0, 0, 0);


            /**
             * The current position value.
             * @var {x3dom.fields.SFVec3f} value
             * @memberof x3dom.nodeTypes.PositionDamper
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'value', 0, 0, 0);

            /**
             * The target position value.
             * @var {x3dom.fields.SFVec3f} destination
             * @memberof x3dom.nodeTypes.PositionDamper
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'destination', 0, 0, 0);

            this._value0 = new x3dom.fields.SFVec3f(0, 0, 0);
            this._value1 = new x3dom.fields.SFVec3f(0, 0, 0);
            this._value2 = new x3dom.fields.SFVec3f(0, 0, 0);
            this._value3 = new x3dom.fields.SFVec3f(0, 0, 0);
            this._value4 = new x3dom.fields.SFVec3f(0, 0, 0);
            this._value5 = new x3dom.fields.SFVec3f(0, 0, 0);

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName === "tolerance")
                {
                    this._eps = this._vf.tolerance < 0 ? 0.001 : this._vf.tolerance;
                }
                else if (fieldName.indexOf("destination") >= 0)
                {
                    if ( !this._value0.equals(this._vf.destination, this._eps) ) {
                        this._value0 = this._vf.destination;

                        if (!this._vf.isActive) {
                            //this._lastTick = 0;
                            this.postMessage('isActive', true);
                        }
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this._value1.setValues(this._vf.value);
                    this._value2.setValues(this._vf.value);
                    this._value3.setValues(this._vf.value);
                    this._value4.setValues(this._vf.value);
                    this._value5.setValues(this._vf.value);
                    this._lastTick = 0;

                    this.postMessage('value', this._value5);

                    if (!this._vf.isActive) {
                        this._lastTick = 0;
                        this.postMessage('isActive', true);
                    }
                }
            },

            initialize: function()
            {
                this._value0.setValues(this._vf.initialDestination);
                this._value1.setValues(this._vf.initialValue);
                this._value2.setValues(this._vf.initialValue);
                this._value3.setValues(this._vf.initialValue);
                this._value4.setValues(this._vf.initialValue);
                this._value5.setValues(this._vf.initialValue);
                this._lastTick = 0;

                var active = !this._value0.equals(this._value1, this._eps);
                if (this._vf.isActive !== active) {
                    this.postMessage('isActive', active);
                }
            },

            tick: function(now)
            {
                //if (!this._vf.isActive)
                //    return false;

                if (!this._lastTick)
                {
                    this._lastTick = now;
                    return false;
                }

                var delta = now - this._lastTick;

                var alpha = Math.exp(-delta / this._vf.tau);

                this._value1 = this._vf.order > 0 && this._vf.tau ?
                    this._value0.add(this._value1.subtract(this._value0).multiply(alpha)) :
                    new x3dom.fields.SFVec3f(this._value0.x, this._value0.y, this._value0.z);

                this._value2 = this._vf.order > 1 && this._vf.tau ?
                    this._value1.add(this._value2.subtract(this._value1).multiply(alpha)) :
                    new x3dom.fields.SFVec3f(this._value1.x, this._value1.y, this._value1.z);

                this._value3 = this._vf.order > 2 && this._vf.tau ?
                    this._value2.add(this._value3.subtract(this._value2).multiply(alpha)) :
                    new x3dom.fields.SFVec3f(this._value2.x, this._value2.y, this._value2.z);

                this._value4 = this._vf.order > 3 && this._vf.tau ?
                    this._value3.add(this._value4.subtract(this._value3).multiply(alpha)) :
                    new x3dom.fields.SFVec3f(this._value3.x, this._value3.y, this._value3.z);

                this._value5 = this._vf.order > 4 && this._vf.tau ?
                    this._value4.add(this._value5.subtract(this._value4).multiply(alpha)) :
                    new x3dom.fields.SFVec3f(this._value4.x, this._value4.y, this._value4.z);

                var dist = this._value1.subtract(this._value0).length();

                if (this._vf.order > 1)
                {
                    var dist2 = this._value2.subtract(this._value1).length();
                    if (dist2 > dist) {dist = dist2;}
                }
                if (this._vf.order > 2)
                {
                    var dist3 = this._value3.subtract(this._value2).length();
                    if (dist3 > dist) {dist = dist3;}
                }
                if (this._vf.order > 3)
                {
                    var dist4 = this._value4.subtract(this._value3).length();
                    if (dist4 > dist) {dist = dist4;}
                }
                if (this._vf.order > 4)
                {
                    var dist5 = this._value5.subtract(this._value4).length();
                    if (dist5 > dist) {dist = dist5;}
                }

                if (dist <= this._eps)
                {
                    this._value1.setValues(this._value0);
                    this._value2.setValues(this._value0);
                    this._value3.setValues(this._value0);
                    this._value4.setValues(this._value0);
                    this._value5.setValues(this._value0);

                    this.postMessage('value', this._value0);
                    this.postMessage('isActive', false);

                    this._lastTick = 0;

                    return false;
                }

                this.postMessage('value', this._value5);

                this._lastTick = now;

                return true;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PositionDamper2D ### */
x3dom.registerNodeType(
    "PositionDamper2D",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DDamperNode,
        
        /**
         * Constructor for PositionDamper2D
         * @constructs x3dom.nodeTypes.PositionDamper2D
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDamperNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PositionDamper2D animates transitions for 2D vectors. Whenever the destination field receives
         * a 2D vector, it creates a transition from its current 2D vector value to the newly set value.
         */
        function (ctx) {
            x3dom.nodeTypes.PositionDamper2D.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue  unless a transition to a
             *  certain 2D vector value is to be created right after the scene is loaded or right after the
             *  PositinChaser2D node is created dynamically.
             * @var {x3dom.fields.SFVec2f} initialDestination
             * @memberof x3dom.nodeTypes.PositionDamper2D
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'initialDestination', 0, 0);

            /**
             * The field initialValue can be used to set the initial initial value.
             * @var {x3dom.fields.SFVec2f} initialValue
             * @memberof x3dom.nodeTypes.PositionDamper2D
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'initialValue', 0, 0);


            /**
             * The current 2D position value.
             * @var {x3dom.fields.SFVec2f} value
             * @memberof x3dom.nodeTypes.PositionDamper2D
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'value', 0, 0);

            /**
             * The target 2D position value.
             * @var {x3dom.fields.SFVec2f} destination
             * @memberof x3dom.nodeTypes.PositionDamper2D
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'destination', 0, 0);

            this._value0 = new x3dom.fields.SFVec2f(0, 0);
            this._value1 = new x3dom.fields.SFVec2f(0, 0);
            this._value2 = new x3dom.fields.SFVec2f(0, 0);
            this._value3 = new x3dom.fields.SFVec2f(0, 0);
            this._value4 = new x3dom.fields.SFVec2f(0, 0);
            this._value5 = new x3dom.fields.SFVec2f(0, 0);

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName === "tolerance")
                {
                    this._eps = this._vf.tolerance < 0 ? 0.001 : this._vf.tolerance;
                }
                else if (fieldName.indexOf("destination") >= 0)
                {
                    if ( !this._value0.equals(this._vf.destination, this._eps) ) {
                        this._value0 = this._vf.destination;

                        if (!this._vf.isActive) {
                            //this._lastTick = 0;
                            this.postMessage('isActive', true);
                        }
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this._value1.setValues(this._vf.value);
                    this._value2.setValues(this._vf.value);
                    this._value3.setValues(this._vf.value);
                    this._value4.setValues(this._vf.value);
                    this._value5.setValues(this._vf.value);
                    this._lastTick = 0;

                    this.postMessage('value', this._value5);

                    if (!this._vf.isActive) {
                        this._lastTick = 0;
                        this.postMessage('isActive', true);
                    }
                }
            },

            initialize: function()
            {
                this._value0.setValues(this._vf.initialDestination);
                this._value1.setValues(this._vf.initialValue);
                this._value2.setValues(this._vf.initialValue);
                this._value3.setValues(this._vf.initialValue);
                this._value4.setValues(this._vf.initialValue);
                this._value5.setValues(this._vf.initialValue);
                this._lastTick = 0;

                var active = !this._value0.equals(this._value1, this._eps);
                if (this._vf.isActive !== active) {
                    this.postMessage('isActive', active);
                }
            },

            tick: function(now)
            {
                //if (!this._vf.isActive)
                //    return false;

                if (!this._lastTick)
                {
                    this._lastTick = now;
                    return false;
                }

                var delta = now - this._lastTick;

                var alpha = Math.exp(-delta / this._vf.tau);

                this._value1 = this._vf.order > 0 && this._vf.tau ?
                    this._value0.add(this._value1.subtract(this._value0).multiply(alpha)) :
                    new x3dom.fields.SFVec2f(this._value0.x, this._value0.y, this._value0.z);

                this._value2 = this._vf.order > 1 && this._vf.tau ?
                    this._value1.add(this._value2.subtract(this._value1).multiply(alpha)) :
                    new x3dom.fields.SFVec2f(this._value1.x, this._value1.y, this._value1.z);

                this._value3 = this._vf.order > 2 && this._vf.tau ?
                    this._value2.add(this._value3.subtract(this._value2).multiply(alpha)) :
                    new x3dom.fields.SFVec2f(this._value2.x, this._value2.y, this._value2.z);

                this._value4 = this._vf.order > 3 && this._vf.tau ?
                    this._value3.add(this._value4.subtract(this._value3).multiply(alpha)) :
                    new x3dom.fields.SFVec2f(this._value3.x, this._value3.y, this._value3.z);

                this._value5 = this._vf.order > 4 && this._vf.tau ?
                    this._value4.add(this._value5.subtract(this._value4).multiply(alpha)) :
                    new x3dom.fields.SFVec2f(this._value4.x, this._value4.y, this._value4.z);

                var dist = this._value1.subtract(this._value0).length();

                if (this._vf.order > 1)
                {
                    var dist2 = this._value2.subtract(this._value1).length();
                    if (dist2 > dist) {dist = dist2;}
                }
                if (this._vf.order > 2)
                {
                    var dist3 = this._value3.subtract(this._value2).length();
                    if (dist3 > dist) {dist = dist3;}
                }
                if (this._vf.order > 3)
                {
                    var dist4 = this._value4.subtract(this._value3).length();
                    if (dist4 > dist) {dist = dist4;}
                }
                if (this._vf.order > 4)
                {
                    var dist5 = this._value5.subtract(this._value4).length();
                    if (dist5 > dist) {dist = dist5;}
                }

                if (dist <= this._eps)
                {
                    this._value1.setValues(this._value0);
                    this._value2.setValues(this._value0);
                    this._value3.setValues(this._value0);
                    this._value4.setValues(this._value0);
                    this._value5.setValues(this._value0);

                    this.postMessage('value', this._value0);
                    this.postMessage('isActive', false);

                    this._lastTick = 0;

                    return false;
                }

                this.postMessage('value', this._value5);

                this._lastTick = now;

                return true;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ScalarChaser ### */
x3dom.registerNodeType(
    "ScalarChaser",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DChaserNode,
        
        /**
         * Constructor for ScalarChaser
         * @constructs x3dom.nodeTypes.ScalarChaser
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChaserNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ScalarChaser animates transitions for single float values. Whenever the destination field
         *  receives a floating point number, it creates a transition from its current value to the newly set number.
         *  It creates a smooth transition that ends duration seconds after the last number has been received.
         */
        function (ctx) {
            x3dom.nodeTypes.ScalarChaser.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain value is to be created right after the scene is loaded or right after the ScalarChaser node is
             *  created dynamically.
             * @var {x3dom.fields.SFFloat} initialDestination
             * @memberof x3dom.nodeTypes.ScalarChaser
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'initialDestination', 0);

            /**
             * The field initialValue can be used to set the initial initial value.
             * @var {x3dom.fields.SFFloat} initialValue
             * @memberof x3dom.nodeTypes.ScalarChaser
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'initialValue', 0);


            /**
             * The current value.
             * @var {x3dom.fields.SFFloat} value
             * @memberof x3dom.nodeTypes.ScalarChaser
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'value', 0);

            /**
             * The target value.
             * @var {x3dom.fields.SFFloat} destination
             * @memberof x3dom.nodeTypes.ScalarChaser
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'destination', 0);

            this._buffer = [];
            this._previousValue = 0;
            this._value = 0;

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName.indexOf("destination") >= 0)
                {
                    this.initialize();
                    this.updateBuffer(this._currTime);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this.initialize();

                    this._previousValue = this._vf.value;
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C] = this._vf.value;
                    }

                    this.postMessage('value', this._vf.value);

                    if (!this._vf.isActive) {
                        this.postMessage('isActive', true);
                    }
                }
            },

            initialize: function()
            {
                if (!this._initDone)
                {
                    this._initDone = true;

                    this._vf.destination = this._vf.initialDestination;

                    this._buffer.length = this._numSupports;

                    this._buffer[0] = this._vf.initialDestination;
                    for (var C=1; C<this._buffer.length; C++) {
                        this._buffer[C] = this._vf.initialValue;
                    }

                    this._previousValue = this._vf.initialValue;

                    this._stepTime = this._vf.duration / this._numSupports;

                    var active = (Math.abs(this._buffer[0] - this._buffer[1]) > this._eps);
                    if (this._vf.isActive !== active) {
                        this.postMessage('isActive', active);
                    }
                }
            },

            tick: function(now)
            {
                this.initialize();
                this._currTime = now;

                //if (!this._vf.isActive)
                //    return false;

                if (!this._bufferEndTime)
                {
                    this._bufferEndTime = now;

                    this._value = this._vf.initialValue;

                    this.postMessage('value', this._value);

                    return true;
                }

                var Frac = this.updateBuffer(now);

                var Output = this._previousValue;

                var DeltaIn = this._buffer[this._buffer.length - 1] - this._previousValue;

                var DeltaOut = DeltaIn * (this.stepResponse((this._buffer.length - 1 + Frac) * this._stepTime));

                Output = Output + DeltaOut;

                for (var C=this._buffer.length - 2; C>=0; C--)
                {
                    DeltaIn = this._buffer[C] - this._buffer[C + 1];

                    DeltaOut = DeltaIn * (this.stepResponse((C + Frac) * this._stepTime));

                    Output = Output + DeltaOut;
                }

                if (Math.abs(Output - this._value) > this._eps) {
                    this._value = Output;

                    this.postMessage('value', this._value);
                }
                else {
                    this.postMessage('isActive', false);
                }

                return this._vf.isActive;
            },

            updateBuffer: function(now)
            {
                var Frac = (now - this._bufferEndTime) / this._stepTime;
                var C;
                var NumToShift;
                var Alpha;

                if (Frac >= 1)
                {
                    NumToShift = Math.floor(Frac);
                    Frac -= NumToShift;

                    if (NumToShift < this._buffer.length)
                    {
                        this._previousValue = this._buffer[this._buffer.length - NumToShift];

                        for (C=this._buffer.length - 1; C>=NumToShift; C--) {
                            this._buffer[C] = this._buffer[C - NumToShift];
                        }

                        for (C=0; C<NumToShift; C++)
                        {
                            Alpha = C / NumToShift;

                            this._buffer[C] = this._buffer[NumToShift] * Alpha + this._vf.destination * (1 - Alpha);
                        }
                    }
                    else
                    {
                        this._previousValue = (NumToShift == this._buffer.length) ? this._buffer[0] : this._vf.destination;

                        for (C = 0; C<this._buffer.length; C++) {
                            this._buffer[C] = this._vf.destination;
                        }
                    }

                    this._bufferEndTime += NumToShift * this._stepTime;
                }

                return Frac;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ScalarDamper ### */
x3dom.registerNodeType(
    "ScalarDamper",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DDamperNode,
        
        /**
         * Constructor for ScalarDamper
         * @constructs x3dom.nodeTypes.ScalarDamper
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDamperNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ScalarDamper animates transitions for single float values. If the value field is routed to a
         *  transparency field of a Material node, then, whenever the destination field receives a single float value,
         *  the ScalarDamper node creates a transition from its current value to the newly set value.
         */
        function (ctx) {
            x3dom.nodeTypes.ScalarDamper.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain value is to be created right after the scene is loaded or right after the ScalarDamper node is
             *  created dynamically.
             * @var {x3dom.fields.SFFloat} initialDestination
             * @memberof x3dom.nodeTypes.ScalarDamper
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'initialDestination', 0);

            /**
             * The field initialValue can be used to set the initial value of the node.
             * @var {x3dom.fields.SFFloat} initialValue
             * @memberof x3dom.nodeTypes.ScalarDamper
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'initialValue', 0);


            /**
             * The current value.
             * @var {x3dom.fields.SFFloat} value
             * @memberof x3dom.nodeTypes.ScalarDamper
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'value', 0);

            /**
             * The target value.
             * @var {x3dom.fields.SFFloat} destination
             * @memberof x3dom.nodeTypes.ScalarDamper
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'destination', 0);

            this._value0 = 0;
            this._value1 = 0;
            this._value2 = 0;
            this._value3 = 0;
            this._value4 = 0;
            this._value5 = 0;

            this.initialize();
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName === "tolerance")
                {
                    this._eps = this._vf.tolerance < 0 ? 0.001 : this._vf.tolerance;
                }
                else if (fieldName.indexOf("destination") >= 0)
                {
                    if (Math.abs(this._value0 - this._vf.destination) > this._eps) {
                        this._value0 = this._vf.destination;

                        if (!this._vf.isActive) {
                            //this._lastTick = 0;
                            this.postMessage('isActive', true);
                        }
                    }
                }
                else if (fieldName.indexOf("value") >= 0)
                {
                    this._value1 = this._vf.value;
                    this._value2 = this._vf.value;
                    this._value3 = this._vf.value;
                    this._value4 = this._vf.value;
                    this._value5 = this._vf.value;
                    this._lastTick = 0;

                    this.postMessage('value', this._value5);

                    if (!this._vf.isActive) {
                        this._lastTick = 0;
                        this.postMessage('isActive', true);
                    }
                }
            },

            initialize: function()
            {
                this._value0 = this._vf.initialDestination;
                this._value1 = this._vf.initialValue;
                this._value2 = this._vf.initialValue;
                this._value3 = this._vf.initialValue;
                this._value4 = this._vf.initialValue;
                this._value5 = this._vf.initialValue;
                this._lastTick = 0;

                var active = (Math.abs(this._value0 - this._value1) > this._eps);
                if (this._vf.isActive !== active) {
                    this.postMessage('isActive', active);
                }
            },

            tick: function(now)
            {
                //if (!this._vf.isActive)
                //    return false;

                if (!this._lastTick)
                {
                    this._lastTick = now;
                    return false;
                }

                var delta = now - this._lastTick;

                var alpha = Math.exp(-delta / this._vf.tau);

                this._value1 = this._vf.order > 0 && this._vf.tau ?
                    this._value0 + alpha * (this._value1 - this._value0) : this._value0;

                this._value2 = this._vf.order > 1 && this._vf.tau ?
                    this._value1 + alpha * (this._value2 - this._value1) : this._value1;

                this._value3 = this._vf.order > 2 && this._vf.tau ?
                    this._value2 + alpha * (this._value3 - this._value2) : this._value2;

                this._value4 = this._vf.order > 3 && this._vf.tau ?
                    this._value3 + alpha * (this._value4 - this._value3) : this._value3;

                this._value5 = this._vf.order > 4 && this._vf.tau ?
                    this._value4 + alpha * (this._value5 - this._value4) : this._value4;

                var dist = Math.abs(this._value1 - this._value0);

                if (this._vf.order > 1)
                {
                    var dist2 = Math.abs(this._value2 - this._value1);
                    if (dist2 > dist) {dist = dist2;}
                }
                if (this._vf.order > 2)
                {
                    var dist3 = Math.abs(this._value3 - this._value2);
                    if (dist3 > dist) {dist = dist3;}
                }
                if (this._vf.order > 3)
                {
                    var dist4 = Math.abs(this._value4 - this._value3);
                    if (dist4 > dist) {dist = dist4;}
                }
                if (this._vf.order > 4)
                {
                    var dist5 = Math.abs(this._value5 - this._value4);
                    if (dist5 > dist) {dist = dist5;}
                }

                if (dist <= this._eps)
                {
                    this._value1 = this._value0;
                    this._value2 = this._value0;
                    this._value3 = this._value0;
                    this._value4 = this._value0;
                    this._value5 = this._value0;

                    this.postMessage('value', this._value0);
                    this.postMessage('isActive', false);

                    this._lastTick = 0;

                    return false;
                }

                this.postMessage('value', this._value5);

                this._lastTick = now;

                return true;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### CoordinateDamper ### */
x3dom.registerNodeType(
    "CoordinateDamper",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DDamperNode,
        
        /**
         * Constructor for CoordinateDamper
         * @constructs x3dom.nodeTypes.CoordinateDamper
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDamperNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The CoordinateChaser animates transitions for array of 3D vectors (e.g., the coordinates of a
         *  mesh). Whenever it receives an array of 3D vectors, the value_changed creates a transition from its
         *  current value to the newly set number. It creates a smooth transition that ends duration seconds after the
         *  last number has been received.
         */
        function (ctx) {
            x3dom.nodeTypes.CoordinateDamper.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain value is to be created right after the scene is loaded or right after the CoordinateChaser node
             *  is created dynamically.
             * @var {x3dom.fields.MFVec3f} initialDestination
             * @memberof x3dom.nodeTypes.CoordinateDamper
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'initialDestination', []);

            /**
             * The field initialValue can be used to set the initial value.
             * @var {x3dom.fields.MFVec3f} initialValue
             * @memberof x3dom.nodeTypes.CoordinateDamper
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'initialValue', []);


            /**
             * The current coordinate value
             * @var {x3dom.fields.MFVec3f} value
             * @memberof x3dom.nodeTypes.CoordinateDamper
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'value', []);

            /**
             * The target coordinate value
             * @var {x3dom.fields.MFVec3f} destination
             * @memberof x3dom.nodeTypes.CoordinateDamper
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFVec3f(ctx, 'destination', []);

            x3dom.debug.logWarning("CoordinateDamper NYI");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TexCoordDamper2D ### */
x3dom.registerNodeType(
    "TexCoordDamper2D",
    "Followers",
    defineClass(x3dom.nodeTypes.X3DDamperNode,
        
        /**
         * Constructor for TexCoordDamper2D
         * @constructs x3dom.nodeTypes.TexCoordDamper2D
         * @x3d 3.3
         * @component Followers
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDamperNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The TexCoordDamper2D node animates transitions for an array of 2D vectors (e.g., the texture
         *  coordinates of a mesh). Whenever the destination field receives an array of 2D vectors, value begins
         *  sending an array of the same length, where each element moves from its current value towards the value at
         *  the same position in the array received.
         */
        function (ctx) {
            x3dom.nodeTypes.TexCoordDamper2D.superClass.call(this, ctx);


            /**
             * The field initialDestination should be set to the same value than initialValue unless a transition to a
             *  certain 2D vector value is to be created right after the scene is loaded or right after the
             *  CoordinateDamper node is created dynamically.
             * @var {x3dom.fields.MFVec2f} initialDestination
             * @memberof x3dom.nodeTypes.TexCoordDamper2D
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'initialDestination', []);

            /**
             * The field initialValue can be used to set the initial value of value_changed.
             * @var {x3dom.fields.MFVec2f} initialValue
             * @memberof x3dom.nodeTypes.TexCoordDamper2D
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'initialValue', []);


            /**
             * The current value.
             * @var {x3dom.fields.MFVec2f} value
             * @memberof x3dom.nodeTypes.TexCoordDamper2D
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'value', []);

            /**
             * The target value.
             * @var {x3dom.fields.MFVec2f} destination
             * @memberof x3dom.nodeTypes.TexCoordDamper2D
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'destination', []);

            x3dom.debug.logWarning("TexCoordDamper2D NYI");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### X3DInterpolatorNode ###
x3dom.registerNodeType(
    "X3DInterpolatorNode",
    "Interpolation",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DInterpolatorNode
         * @constructs x3dom.nodeTypes.X3DInterpolatorNode
         * @x3d 3.3
         * @component Interpolation
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The abstract node X3DInterpolatorNode forms the basis for all types of interpolators.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DInterpolatorNode.superClass.call(this, ctx);


            /**
             * The key field contains the list of key times, the keyValue field contains values for the target field, one complete set of values for each key.
             * Interpolator nodes containing no keys in the key field shall not produce any events.
             * However, an input event that replaces an empty key field with one that contains keys will cause the interpolator node to produce events the next time that a set_fraction event is received.
             * @var {x3dom.fields.MFFloat} key
             * @memberof x3dom.nodeTypes.X3DInterpolatorNode
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'key', []);

            /**
             * The set_fraction inputOnly field receives an SFFloat event and causes the interpolator node function to evaluate, resulting in a value_changed output event of the specified type with the same timestamp as the set_fraction event.
             * @var {x3dom.fields.SFFloat} set_fraction
             * @memberof x3dom.nodeTypes.X3DInterpolatorNode
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'set_fraction', 0);
        
        },
        {
            linearInterp: function (time, interp) {
                if (time <= this._vf.key[0])
                    return this._vf.keyValue[0];

                else if (time >= this._vf.key[this._vf.key.length-1])
                    return this._vf.keyValue[this._vf.key.length-1];

                for (var i = 0; i < this._vf.key.length-1; ++i) {
                    if ((this._vf.key[i] < time) && (time <= this._vf.key[i+1]))
                        return interp( this._vf.keyValue[i], this._vf.keyValue[i+1],
                                (time - this._vf.key[i]) / (this._vf.key[i+1] - this._vf.key[i]) );
                }
                return this._vf.keyValue[0];
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### OrientationInterpolator ###
x3dom.registerNodeType(
    "OrientationInterpolator",
    "Interpolation",
    defineClass(x3dom.nodeTypes.X3DInterpolatorNode,
        
        /**
         * Constructor for OrientationInterpolator
         * @constructs x3dom.nodeTypes.OrientationInterpolator
         * @x3d 3.3
         * @component Interpolation
         * @status full
         * @extends x3dom.nodeTypes.X3DInterpolatorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The OrientationInterpolator node interpolates among a list of rotation values specified in the keyValue field to produce an SFRotation value_changed event.
         * These rotations are absolute in object space and therefore are not cumulative.
         * The keyValue field shall contain exactly as many rotations as there are key frames in the key field.
         * An orientation represents the final position of an object after a rotation has been applied.
         * An OrientationInterpolator interpolates between two orientations by computing the shortest path on the unit sphere between the two orientations.
         * The interpolation is linear in arc length along this path. The results are undefined if the two orientations are diagonally opposite.
         */
        function (ctx) {
            x3dom.nodeTypes.OrientationInterpolator.superClass.call(this, ctx);


            /**
             * Defines the set of data points, that are used for interpolation.
             * If two consecutive keyValue values exist such that the arc length between them is greater than π, the interpolation will take place on the arc complement.
             * For example, the interpolation between the orientations (0, 1, 0, 0) and (0, 1, 0, 5.0) is equivalent to the rotation between the orientations (0, 1, 0, 2π) and (0, 1, 0, 5.0).
             * @var {x3dom.fields.MFRotation} keyValue
             * @memberof x3dom.nodeTypes.OrientationInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFRotation(ctx, 'keyValue', []);
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if(fieldName === "set_fraction")
                {
                    var value = this.linearInterp(this._vf.set_fraction, function (a, b, t) {
                        return a.slerp(b, t);
                    });
                    this.postMessage('value_changed', value);
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### PositionInterpolator ###
x3dom.registerNodeType(
    "PositionInterpolator",
    "Interpolation",
    defineClass(x3dom.nodeTypes.X3DInterpolatorNode,
        
        /**
         * Constructor for PositionInterpolator
         * @constructs x3dom.nodeTypes.PositionInterpolator
         * @x3d 3.3
         * @component Interpolation
         * @status full
         * @extends x3dom.nodeTypes.X3DInterpolatorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PositionInterpolator node linearly interpolates among a list of 3D vectors to produce an SFVec3f value_changed event. The keyValue field shall contain exactly as many values as in the key field.
         */
        function (ctx) {
            x3dom.nodeTypes.PositionInterpolator.superClass.call(this, ctx);


            /**
             * Defines the set of data points, that are used for interpolation.
             * @var {x3dom.fields.MFVec3f} keyValue
             * @memberof x3dom.nodeTypes.PositionInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'keyValue', []);
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if(fieldName === "set_fraction")
                {
                    var value = this.linearInterp(this._vf.set_fraction, function (a, b, t) {
                        return a.multiply(1.0-t).add(b.multiply(t));
                    });

                    this.postMessage('value_changed', value);
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### NormalInterpolator ###
x3dom.registerNodeType(
    "NormalInterpolator",
    "Interpolation",
    defineClass(x3dom.nodeTypes.X3DInterpolatorNode,
        
        /**
         * Constructor for NormalInterpolator
         * @constructs x3dom.nodeTypes.NormalInterpolator
         * @x3d 3.3
         * @component Interpolation
         * @status full
         * @extends x3dom.nodeTypes.X3DInterpolatorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The NormalInterpolator node interpolates among a list of normal vector sets specified by the keyValue field to produce an MFVec3f value_changed event.
         * The output vector, value_changed, shall be a set of normalized vectors.
         * Values in the keyValue field shall be of unit length.
         * The number of normals in the keyValue field shall be an integer multiple of the number of key frames in the key field.
         * That integer multiple defines how many normals will be contained in the value_changed events.
         */
        function (ctx) {
            x3dom.nodeTypes.NormalInterpolator.superClass.call(this, ctx);


            /**
             * Defines the set of data points, that are used for interpolation.
             * Values in the keyValue field shall be of unit length.
             * @var {x3dom.fields.MFVec3f} keyValue
             * @memberof x3dom.nodeTypes.NormalInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'keyValue', []);
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if(fieldName === "set_fraction")
                {
                    var value = this.linearInterp(this._vf.set_fraction, function (a, b, t) {
                        return a.multiply(1.0-t).add(b.multiply(t)).normalize();
                    });

                    this.postMessage('value_changed', value);
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### ColorInterpolator ###
x3dom.registerNodeType(
    "ColorInterpolator",
    "Interpolation",
    defineClass(x3dom.nodeTypes.X3DInterpolatorNode,
        
        /**
         * Constructor for ColorInterpolator
         * @constructs x3dom.nodeTypes.ColorInterpolator
         * @x3d 3.3
         * @component Interpolation
         * @status full
         * @extends x3dom.nodeTypes.X3DInterpolatorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ColorInterpolator node interpolates among a list of MFColor key values to produce an SFColor (RGB) value_changed event.
         * The number of colours in the keyValue field shall be equal to the number of key frames in the key field.
         * A linear interpolation using the value of set_fraction as input is performed in HSV space.
         * The results are undefined when interpolating between two consecutive keys with complementary hues.
         */
        function (ctx) {
            x3dom.nodeTypes.ColorInterpolator.superClass.call(this, ctx);


            /**
             * Defines the set of data points, that are used for interpolation.
             * @var {x3dom.fields.MFColor} keyValue
             * @memberof x3dom.nodeTypes.ColorInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFColor(ctx, 'keyValue', []);
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if(fieldName === "set_fraction")
                {
                    // FIXME; perform color interpolation in HSV space
                    var value = this.linearInterp(this._vf.set_fraction, function (a, b, t) {
                        return a.multiply(1.0-t).add(b.multiply(t));
                    });

                    this.postMessage('value_changed', value);
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### ScalarInterpolator ###
x3dom.registerNodeType(
    "ScalarInterpolator",
    "Interpolation",
    defineClass(x3dom.nodeTypes.X3DInterpolatorNode,
        
        /**
         * Constructor for ScalarInterpolator
         * @constructs x3dom.nodeTypes.ScalarInterpolator
         * @x3d 3.3
         * @component Interpolation
         * @status full
         * @extends x3dom.nodeTypes.X3DInterpolatorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ScalarInterpolator node linearly interpolates among a list of SFFloat values to produce an SFFloat value_changed event.
         * This interpolator is appropriate for any parameter defined using a single floating point value.
         */
        function (ctx) {
            x3dom.nodeTypes.ScalarInterpolator.superClass.call(this, ctx);


            /**
             * Defines the set of data points, that are used for interpolation.
             * @var {x3dom.fields.MFFloat} keyValue
             * @memberof x3dom.nodeTypes.ScalarInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'keyValue', []);
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if(fieldName === "set_fraction")
                {
                    var value = this.linearInterp(this._vf.set_fraction, function (a, b, t) {
                        return (1.0-t)*a + t*b;
                    });

                    this.postMessage('value_changed', value);
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### CoordinateInterpolator ###
x3dom.registerNodeType(
    "CoordinateInterpolator",
    "Interpolation",
    defineClass(x3dom.nodeTypes.X3DInterpolatorNode,
        
        /**
         * Constructor for CoordinateInterpolator
         * @constructs x3dom.nodeTypes.CoordinateInterpolator
         * @x3d 3.3
         * @component Interpolation
         * @status full
         * @extends x3dom.nodeTypes.X3DInterpolatorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The CoordinateInterpolator node linearly interpolates among a list of MFVec3f values to produce an MFVec3f value_changed event.
         * The number of coordinates in the keyValue field shall be an integer multiple of the number of key frames in the key field.
         * That integer multiple defines how many coordinates will be contained in the value_changed events.
         */
        function (ctx) {
            x3dom.nodeTypes.CoordinateInterpolator.superClass.call(this, ctx);


            /**
             * Defines the set of data points, that are used for interpolation.
             * @var {x3dom.fields.MFVec3f} keyValue
             * @memberof x3dom.nodeTypes.CoordinateInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'keyValue', []);

            if (ctx && ctx.xmlNode.hasAttribute('keyValue')) {
                this._vf.keyValue = [];     // FIXME!!!

                var arr = x3dom.fields.MFVec3f.parse(ctx.xmlNode.getAttribute('keyValue'));
                var key = this._vf.key.length > 0 ? this._vf.key.length : 1;
                var len = arr.length / key;
                for (var i=0; i<key; i++) {
                    var val = new x3dom.fields.MFVec3f();
                    for (var j=0; j<len; j++) {
                        val.push( arr[i*len+j] );
                    }
                    this._vf.keyValue.push(val);
                }
            }
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if(fieldName === "set_fraction")
                {
                    var value = this.linearInterp(this._vf.set_fraction, function (a, b, t) {
                        var val = new x3dom.fields.MFVec3f();
                        for (var i=0; i<a.length; i++)
                            val.push(a[i].multiply(1.0-t).add(b[i].multiply(t)));

                        return val;
                    });

                    this.postMessage('value_changed', value);
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * Based on code originally provided by
 * http://www.x3dom.org
 * 
 * (C)2014 Toshiba Corporation, Japan.
 * Dual licensed under the MIT and GPL
 */

// ### SplinePositionInterpolator ###
x3dom.registerNodeType(
    "SplinePositionInterpolator",
    "Interpolation",
    defineClass(x3dom.nodeTypes.X3DInterpolatorNode,
        
        /**
         * Constructor for SplinePositionInterpolator
         * @constructs x3dom.nodeTypes.SplinePositionInterpolator
         * @x3d 3.3
         * @component Interpolation
         * @status experimental
         * @extends x3dom.nodeTypes.X3DInterpolatorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The SplinePositionInterpolator node non-linearly interpolates among a list of 3D vectors to produce an SFVec3f value_changed event. The keyValue, keyVelocity, and key fields shall each have the same number of values.
         */
        function (ctx) {
            x3dom.nodeTypes.SplinePositionInterpolator.superClass.call(this, ctx);

            /**
             * Defines the set of data points, that are used for interpolation.
             * @var {x3dom.fields.MFVec3f} keyValue
             * @memberof x3dom.nodeTypes.SplinePositionInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'keyValue', []);
			
			/**
             * Defines the set of velocity vectors, that are used for interpolation.
             * @var {x3dom.fields.MFVec3f} keyVelocity
             * @memberof x3dom.nodeTypes.SplinePositionInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
			this.addField_MFVec3f(ctx, 'keyVelocity', []);
			
			/**
             * Specifies whether the interpolator should provide a closed loop, with continuous velocity vectors as the interpolator transitions from the last key to the first key.
             * @var {x3dom.fields.SFBool} closed
             * @memberof x3dom.nodeTypes.SplinePositionInterpolator
             * @initvalue false
             * @field x3d
             * @instance
             */
			this.addField_SFBool(ctx, 'closed', false);
			
			/**
             * Specifies whether the velocity vectors are to be transformed into normalized tangency vectors.
             * @var {x3dom.fields.SFBool} normalizeVelocity
             * @memberof x3dom.nodeTypes.SplinePositionInterpolator
             * @initvalue false
             * @field x3d
             * @instance
             */
			this.addField_SFBool(ctx, 'normalizeVelocity', false);
			 
			/******** Private variables and functions ***********/
			
			/* dtot is the sum of the distance between all adjacent keys.
			 * dtot = SUM{i=0, i < n-1}(|vi - vi+1|)
			 */
			this.dtot = 0.0;
			
			/* Non-uniform interval adjusted velocity vectors
			 */
			this.T0 = [];
			this.T1 = [];
			
			/* Checks sanity. Node is sane if (|key| == |key_value|) and (|key| == |key_velocity| or |key_velocity| == 0 or (|key_velocity| == 2 and |key| >= 2))
			 */
			this.checkSanity = function() {
				var sane = (this._vf.key.length == this._vf.keyValue.length) &&
						   ((this._vf.key.length == this._vf.keyVelocity.length) || (this._vf.keyVelocity.length == 2 && this._vf.key.length >= 2) || (this._vf.keyVelocity.length == 0));
				if(!sane)
					x3dom.debug.logWarning("SplinePositionInterpolator Node: 'key' , 'keyValue' and/or 'keyVelocity' fields have inappropriate sizes");
			};
			
			/* Calculate dtot (sum of distances between all adjacent keys)
			 */
			this.calcDtot = function()
			{
				this.dtot = 0.0;
				for(var i = 0; i < this._vf.key.length-1; i++) 
				{
					this.dtot += Math.abs(this._vf.key[i] - this._vf.key[i+1]);
				}
			};
			 
			/* Calculate non-uniform interval adjusted velocity vectors
			 */
			this.calcAdjustedKeyVelocity = function()
			{
				var i, Ti, F_plus_i, F_minus_i;
				var N = this._vf.key.length;
				
				// If velocities are defined at all the control points, ignore 'closed' field
				if(this._vf.keyVelocity.length == N)
				{
					for(i = 0; i < N; i++)
					{
						Ti = this._vf.keyVelocity[i];
						
						if(this._vf.normalizeVelocity)
							Ti = Ti.multiply(this.dtot / Ti.length());
						
						F_plus_i = (i == 0 || i == N-1) ? 1.0 : 2.0 * (this._vf.key[i] - this._vf.key[i-1]) / (this._vf.key[i+1] - this._vf.key[i-1]);
						F_minus_i= (i == 0 || i == N-1) ? 1.0 : 2.0 * (this._vf.key[i+1] - this._vf.key[i]) / (this._vf.key[i+1] - this._vf.key[i-1]);
						
						this.T0[i] =  Ti.multiply(F_plus_i);
						this.T1[i] =  Ti.multiply(F_minus_i);
					}
				}
				// if only first and last velocities are specified, ignore 'closed' field
				else if(this._vf.keyVelocity.length == 2 && N > 2)
				{
					for(i = 0; i < N; i++)
					{
						if(i == 0)
							Ti = this._vf.keyVelocity[0];
						else if(i == N-1)
							Ti = this._vf.keyVelocity[1];
						else
							Ti = this._vf.keyValue[i+1].subtract(this._vf.keyValue[i-1]).multiply(0.5);
						
						if(this._vf.normalizeVelocity)
							Ti = Ti.multiply(this.dtot / Ti.length());
						
						F_plus_i = (i == 0 || i == N-1) ? 1.0 : 2.0 * (this._vf.key[i] - this._vf.key[i-1]) / (this._vf.key[i+1] - this._vf.key[i-1]);
						F_minus_i= (i == 0 || i == N-1) ? 1.0 : 2.0 * (this._vf.key[i+1] - this._vf.key[i]) / (this._vf.key[i+1] - this._vf.key[i-1]);
						
						this.T0[i] =  Ti.multiply(F_plus_i);
						this.T1[i] =  Ti.multiply(F_minus_i);
					}
				}
				// velocities are unspecified
				else
				{
					// ignore closed if first and last keyValues are not equal
					var closed = this._vf.closed && this._vf.keyValue[0].equals(this._vf.keyValue[N-1], 0.00001);
					
					for(i = 0; i < N; i++)
					{
						if((i == 0 || i == N-1) && !closed)
						{
							this.T0[i] = new x3dom.fields.SFVec3f(0, 0, 0);
							this.T1[i] = new x3dom.fields.SFVec3f(0, 0, 0);
							continue;
						}
						else if((i == 0 || i == N-1) && closed)
						{
							Ti = this._vf.keyValue[1].subtract(this._vf.keyValue[N-2]).multiply(0.5);
							if(i == 0) {
								F_plus_i = 2.0 * (this._vf.key[0] - this._vf.key[N-2]) / (this._vf.key[1] - this._vf.key[N-2]);
								F_minus_i= 2.0 * (this._vf.key[1] - this._vf.key[0]) / (this._vf.key[1] - this._vf.key[N-2]);
							}
							else {
								F_plus_i = 2.0 * (this._vf.key[N-1] - this._vf.key[N-2]) / (this._vf.key[1] - this._vf.key[N-2]);
								F_minus_i= 2.0 * (this._vf.key[1] - this._vf.key[N-1]) / (this._vf.key[1] - this._vf.key[N-2]);
							}
							F_plus_i = 2.0 * (this._vf.key[N-1] - this._vf.key[N-2]) / (this._vf.key[N-2] - this._vf.key[1]);
							F_minus_i= 2.0 * (this._vf.key[1] - this._vf.key[0]) / (this._vf.key[N-2] - this._vf.key[1]);
						}
						else
						{
							Ti = this._vf.keyValue[i+1].subtract(this._vf.keyValue[i-1]).multiply(0.5);
							F_plus_i = 2.0 * (this._vf.key[i] - this._vf.key[i-1]) / (this._vf.key[i+1] - this._vf.key[i-1]);
							F_minus_i= 2.0 * (this._vf.key[i+1] - this._vf.key[i]) / (this._vf.key[i+1] - this._vf.key[i-1]);
						}
						
						this.T0[i] =  Ti.multiply(F_plus_i);
						this.T1[i] =  Ti.multiply(F_minus_i);
					}
				}
			};
			
			this.checkSanity();
			this.calcDtot();
			this.calcAdjustedKeyVelocity();
        },
        {
            fieldChanged: function(fieldName)
            {
				switch(fieldName)
				{
					case 'key':
					case 'keyValue':
					case 'keyVelocity':
					{
						this.checkSanity();
						this.calcDtot();
						this.calcAdjustedKeyVelocity();
						break;
					}
					case 'closed':
					case 'normalizeVelocity':
					{
						this.calcAdjustedKeyVelocity();
						break;
					}
					case 'set_fraction':
					{
						var value;
						
						if(this._vf.key.length > 0.0) {
							if (this._vf.set_fraction <= this._vf.key[0])
								value = x3dom.fields.SFVec3f.copy(this._vf.keyValue[0]);

							else if (this._vf.set_fraction >= this._vf.key[this._vf.key.length-1])
								value = x3dom.fields.SFVec3f.copy(this._vf.keyValue[this._vf.key.length-1]);
						}
						
						for(var i = 0; i < this._vf.key.length-1; i++) {
							if ((this._vf.key[i] < this._vf.set_fraction) && (this._vf.set_fraction <= this._vf.key[i+1])) {
								var s = (this._vf.set_fraction - this._vf.key[i]) / (this._vf.key[i+1]-this._vf.key[i]);
								
								var S_H = new x3dom.fields.SFVec4f(2.0*s*s*s - 3.0*s*s + 1.0, -2.0*s*s*s + 3.0*s*s, s*s*s - 2.0*s*s + s, s*s*s - s*s);
								value = new x3dom.fields.SFVec3f(S_H.x * this._vf.keyValue[i].x + S_H.y * this._vf.keyValue[i+1].x + S_H.z * this.T0[i].x + S_H.w * this.T1[i+1].x,
															     S_H.x * this._vf.keyValue[i].y + S_H.y * this._vf.keyValue[i+1].y + S_H.z * this.T0[i].y + S_H.w * this.T1[i+1].y,
															     S_H.x * this._vf.keyValue[i].z + S_H.y * this._vf.keyValue[i+1].z + S_H.z * this.T0[i].z + S_H.w * this.T1[i+1].z);
								break;
							}
						}
						
						if(value !== undefined)
							this.postMessage('value_changed', value);
						else
							x3dom.debug.logWarning("SplinePositionInterpolator Node: value_changed is undefined!");
					}
				}
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### TimeSensor ###
x3dom.registerNodeType(
    "TimeSensor",
    "Time",
    defineClass(x3dom.nodeTypes.X3DSensorNode,
        
        /**
         * Constructor for TimeSensor
         * @constructs x3dom.nodeTypes.TimeSensor
         * @x3d 3.3
         * @component Time
         * @status full
         * @extends x3dom.nodeTypes.X3DSensorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc TimeSensor nodes generate events as time passes.
         */
        function (ctx) {
            x3dom.nodeTypes.TimeSensor.superClass.call(this, ctx);

            if (ctx)
                ctx.doc._nodeBag.timer.push(this);
            else
                x3dom.debug.logWarning("TimeSensor: No runtime context found!");


            /**
             * The "cycle" of a TimeSensor node lasts for cycleInterval seconds. The value of cycleInterval shall be greater than zero.
             * @var {x3dom.fields.SFTime} cycleInterval
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 1
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'cycleInterval', 1);


            /**
             * Specifies whether the timer cycle loops.
             * @var {x3dom.fields.SFBool} loop
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'loop', false);

            /**
             * Sets the startTime for the cycle.
             * @var {x3dom.fields.SFTime} startTime
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'startTime', 0);

            /**
             * Sets a time for the timer to stop.
             * @var {x3dom.fields.SFTime} stopTime
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'stopTime', 0);

            /**
             * Sets a time for the timer to pause.
             * @var {x3dom.fields.SFTime} pauseTime
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'pauseTime', 0);

            /**
             * Sets a time for the timer to resume from pause.
             * @var {x3dom.fields.SFTime} resumeTime
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'resumeTime', 0);


            /**
             * A cycleTime outputOnly field can be used for synchronization purposes such as sound with animation.
             * The value of a cycleTime event will be equal to the time at the beginning of the current cycle. A cycleTime event is generated at the beginning of every cycle, including the cycle starting at startTime.
             * The first cycleTime event for a TimeSensor node can be used as an alarm (single pulse at a specified time).
             * @var {x3dom.fields.SFTime} cycleTime
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'cycleTime', 0);

            /**
             * The elapsedTime outputOnly field delivers the current elapsed time since the TimeSensor was activated and running, cumulative in seconds and not counting any time while in a paused state.
             * @var {x3dom.fields.SFTime} elapsedTime
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'elapsedTime', 0);

            /**
             * fraction_changed events output a floating point value in the closed interval [0, 1]. At startTime the value of fraction_changed is 0. After startTime, the value of fraction_changed in any cycle will progress through the range (0.0, 1.0].
             * @var {x3dom.fields.SFFloat} fraction_changed
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'fraction_changed', 0);

            /**
             * Outputs whether the timer is active.
             * @var {x3dom.fields.SFBool} isActive
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'isActive', false);

            /**
             * Outputs whether the timer is paused.
             * @var {x3dom.fields.SFBool} isPaused
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'isPaused', false);

            /**
             * The time event sends the absolute time for a given tick of the TimeSensor node.
             * @var {x3dom.fields.SFTime} time
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'time', 0);


            /**
             *
             * @var {x3dom.fields.SFBool} first
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx,'first', true);

            /**
             *
             * @var {x3dom.fields.SFFloat} firstCycle
             * @memberof x3dom.nodeTypes.TimeSensor
             * @initvalue 0.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx,'firstCycle', 0.0);

            this._prevCycle = -1;
            this._lastTime = 0;
            this._cycleStopTime = 0;
            this._activatedTime = 0;

            if (this._vf.startTime > 0) {
                this._updateCycleStopTime();
            }

            this._backupStartTime = this._vf.startTime;
            this._backupStopTime = this._vf.stopTime;
            this._backupCycleInterval = this._vf.cycleInterval;
        
        },
        {
            tick: function (time)
            {
                if (!this._vf.enabled) {
                    this._lastTime = time;
                    return false;
                }

                var isActive = ( this._vf.cycleInterval > 0 &&
                    time >= this._vf.startTime &&
                    (time < this._vf.stopTime || this._vf.stopTime <= this._vf.startTime) &&
                    (this._vf.loop == true || (this._vf.loop == false && time < this._cycleStopTime)) );

                if (isActive && !this._vf.isActive) {
                    this.postMessage('isActive', true);
                    this._activatedTime = time;
                }

                // Checking for this._vf.isActive allows the dispatch of 'final events' (before deactivation)
                if (isActive || this._vf.isActive) {
                    this.postMessage('elapsedTime', time - this._activatedTime);

                    var isPaused = ( time >= this._vf.pauseTime && this._vf.pauseTime > this._vf.resumeTime );

                    if (isPaused && !this._vf.isPaused) {
                        this.postMessage('isPaused', true);
                        this.postMessage('pauseTime', time);
                    } else if (!isPaused && this._vf.isPaused) {
                        this.postMessage('isPaused', false);
                        this.postMessage('resumeTime', time);
                    }

                    if (!isPaused) {
                        var cycleFrac = this._getCycleAt(time);
                        var cycle = Math.floor(cycleFrac);

                        var cycleTime = this._vf.startTime + cycle*this._vf.cycleInterval;
                        var adjustTime = 0;

                        if (this._vf.stopTime > this._vf.startTime &&
                            this._lastTime < this._vf.stopTime && time >= this._vf.stopTime)
                            adjustTime = this._vf.stopTime;
                        else if (this._lastTime < cycleTime && time >= cycleTime)
                            adjustTime = cycleTime;

                        if( adjustTime > 0 ) {
                            time = adjustTime;
                            cycleFrac = this._getCycleAt(time);
                            cycle = Math.floor(cycleFrac);
                        }

                        var fraction = cycleFrac - cycle;

                        if (fraction < x3dom.fields.Eps) {
                            fraction = ( this._lastTime < this._vf.startTime ? 0.0 : 1.0 );
                            this.postMessage('cycleTime', time);
                        }

                        this.postMessage('fraction_changed', fraction);

                        this.postMessage('time', time);
                    }
                }

                if (!isActive && this._vf.isActive)
                    this.postMessage('isActive', false);

                this._lastTime = time;

                return true;
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "enabled") {
                    // TODO; eval other relevant outputs
                    if (!this._vf.enabled && this._vf.isActive) {
                        this.postMessage('isActive', false);
                    }
                }
                else if (fieldName == "startTime") {
                    // Spec: Should be ignored when active. (Restore old value)
                    if (this._vf.isActive) {
                        this._vf.startTime = this._backupStartTime;
                        return;
                    }

                    this._backupStartTime = this._vf.startTime;
                    this._updateCycleStopTime();
                }
                else if (fieldName == "stopTime") {
                    // Spec: Should be ignored when active and less than startTime. (Restore old value)
                    if (this._vf.isActive && this._vf.stopTime <= this._vf.startTime) {
                        this._vf.stopTime = this._backupStopTime;
                        return;
                    }

                    this._backupStopTime = this._vf.stopTime;
                }
                else if (fieldName == "cycleInterval") {
                    // Spec: Should be ignored when active. (Restore old value)
                    if (this._vf.isActive) {
                        this._vf.cycleInterval = this._backupCycleInterval;
                        return;
                    }

                    this._backupCycleInterval = this._vf.cycleInterval;
                }
                else if (fieldName == "loop") {
                    this._updateCycleStopTime();
                }
            },

            parentRemoved: function(parent)
            {
                if (this._parentNodes.length === 0) {
                    var doc = this.findX3DDoc();

                    for (var i=0, n=doc._nodeBag.timer.length; i<n; i++) {
                        if (doc._nodeBag.timer[i] === this) {
                            doc._nodeBag.timer.splice(i, 1);
                        }
                    }
                }
            },

            _getCycleAt: function(time)
            {
                return Math.max( 0.0, time - this._vf.startTime ) / this._vf.cycleInterval;
            },

            _updateCycleStopTime: function()
            {
                if (this._vf.loop == false) {
                    var now = new Date().getTime() / 1000;
                    var cycleToStop = Math.floor(this._getCycleAt(now)) + 1;

                    this._cycleStopTime = this._vf.startTime + cycleToStop*this._vf.cycleInterval;
                }
                else {
                    this._cycleStopTime = 0;
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DTimeDependentNode ### */
x3dom.registerNodeType(
    "X3DTimeDependentNode",
    "Time",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DTimeDependentNode
         * @constructs x3dom.nodeTypes.X3DTimeDependentNode
         * @x3d 3.3
         * @component Time
         * @status experimental
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base node type from which all time-dependent nodes are derived.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DTimeDependentNode.superClass.call(this, ctx);


            /**
             * Specifies whether the timer cycle loops.
             * @var {x3dom.fields.SFBool} loop
             * @memberof x3dom.nodeTypes.X3DTimeDependentNode
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'loop', false);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### Anchor ###
x3dom.registerNodeType(
    "Anchor",
    "Networking",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for Anchor
         * @constructs x3dom.nodeTypes.Anchor
         * @x3d 3.3
         * @component Networking
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Anchor is a Grouping node that can contain most nodes. Clicking Anchored geometry loads content specified by the url field.
         * Loaded content completely replaces current content, if parameter is same window.
         * Hint: insert a Shape node before adding geometry or Appearance.
         */
        function (ctx) {
            x3dom.nodeTypes.Anchor.superClass.call(this, ctx);


            /**
             * Address of replacement world, activated by clicking Anchor geometry. Hint: jump to a world's internal viewpoint by appending viewpoint name (e.g. #ViewpointName, someOtherCoolWorld.wrl#GrandTour). Hint: jump to a local viewpoint by only using viewpoint name (e.g. #GrandTour). Hint: Strings can have multiple values, so separate each string by quote marks [ 'http://www.url1.org' 'http://www.url2.org' 'etc.' ]. Hint: XML encoding for ' is ampersandquot; (a character entity). Warning: strictly match directory and filename capitalization for http links! Hint: can replace embedded blank(s) in url queries with %20 for each blank character.
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.Anchor
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'url', []);

            /**
             * Passed parameter that signals web browser how to redirect url loading. Each string shall consist of "keyword=value" pairs. Hint: set parameter to target=_blank or target=_extern to load target url with a system-specific application. target=_self or target=_intern will open url in current x3d-browser window. Hint: set parameter to target=frame_name to load target url into another frame. Hint: Strings can have multiple values, so separate each string by quote marks. [ 'http://www.url1.org' 'http://www.url2.org' 'etc.' ]. Interchange profile hint: this field may be ignored.
             * @var {x3dom.fields.MFString} parameter
             * @memberof x3dom.nodeTypes.Anchor
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'parameter', []);

            /**
             * The description field in the Anchor node specifies a textual description of the Anchor node.
             * This may be used by browser-specific user interfaces that wish to present users with more detailed information about the Anchor.
             * @var {x3dom.fields.SFString} description
             * @memberof x3dom.nodeTypes.Anchor
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'description', "");
        
        },
        {
            doIntersect: function(line) {
                var isect = false;
                for (var i=0; i<this._childNodes.length; i++) {
                    if (this._childNodes[i]) {
                        isect = this._childNodes[i].doIntersect(line) || isect;
                    }
                }
                return isect;
            },

            handleTouch: function() {
                var url = this._vf.url.length ? this._vf.url[0] : "";
                var aPos = url.search("#");
                var anchor = "";
                if (aPos >= 0)
                    anchor = url.slice(aPos+1);

                var param = this._vf.parameter.length ? this._vf.parameter[0] : "";
                var tPos = param.search("target=");
                var target = "";
                if (tPos >= 0)
                    target = param.slice(tPos+7);

                // TODO: implement #Viewpoint bind
                // http://www.web3d.org/files/specifications/19775-1/V3.2/Part01/components/networking.html#Anchor
                x3dom.debug.logInfo("Anchor url=" + url + ", target=" + target + ", #viewpoint=" + anchor);

                if(target.length !=0 || target != "_self") {
                    window.open(this._nameSpace.getURL(url), target);
                }
                else {
                    window.location = this._nameSpace.getURL(url);
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### Inline ###
x3dom.registerNodeType(
    "Inline",
    "Networking",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for Inline
         * @constructs x3dom.nodeTypes.Inline
         * @x3d 3.3
         * @component Networking
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Inline is a Grouping node that can load nodes from another X3D scene via url.
         */
        function (ctx) {
            x3dom.nodeTypes.Inline.superClass.call(this, ctx);


            /**
             * Each specified URL shall refer to a valid X3D file that contains a list of children nodes, prototypes and routes at the top level. Hint: Strings can have multiple values, so separate each string by quote marks. Warning: strictly match directory and filename capitalization for http links!
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.Inline
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'url', []);

            /**
             * Specifies whether the X3D file specified by the url field is loaded. Hint: use LoadSensor to detect when loading is complete. TRUE: load immediately (it's also possible to load the URL at a later time by sending a TRUE event to the load field); FALSE: no action is taken (by sending a FALSE event to the load field of a previously loaded Inline, the contents of the Inline will be unloaded from the scene graph)
             * @var {x3dom.fields.SFBool} load
             * @memberof x3dom.nodeTypes.Inline
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'load', true);

            /**
             * Specifies the namespace of the Inline node.
             * @var {x3dom.fields.MFString} nameSpaceName
             * @memberof x3dom.nodeTypes.Inline
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'nameSpaceName', []);

            /**
             * Specifies whether the DEF value is used as id when no other id is set.
             * @var {x3dom.fields.SFBool} mapDEFToID
             * @memberof x3dom.nodeTypes.Inline
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'mapDEFToID', false);

            this.initDone = false;
            this.count = 0;
            this.numRetries = x3dom.nodeTypes.Inline.MaximumRetries;
        
        },
        {
            fieldChanged: function (fieldName)
            {
                if (fieldName == "url") {

                    //Remove the childs of the x3domNode
                    for (var i=0; i<this._childNodes.length; i++)
                    {
                        this.removeChild(this._childNodes[i]);
                    }

                    //if reflected to DOM remove the childs of the domNode
                    if (this._vf.nameSpaceName.length != 0) {
                        var node = this._xmlNode;
                        if (node && node.hasChildNodes())
                        {
                            while ( node.childNodes.length >= 1 )
                            {
                                node.removeChild( node.firstChild );
                            }
                        }
                    }
                    this.loadInline();
                }
                else if (fieldName == "render") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            },

            nodeChanged: function ()
            {
                if (!this.initDone) {
                    this.initDone = true;
                    this.loadInline();
                }
            },

            fireEvents: function(eventType)
            {
                if ( this._xmlNode &&
                    (this._xmlNode['on'+eventType] ||
                        this._xmlNode.hasAttribute('on'+eventType) ||
                        this._listeners[eventType]) )
                {
                    var event = {
                        target: this._xmlNode,
                        type: eventType,
                        error: (eventType == "error") ? "XMLHttpRequest Error" : "",
                        cancelBubble: false,
                        stopPropagation: function() { this.cancelBubble = true; }
                    };

                    try {
                        var attrib = this._xmlNode["on" + eventType];

                        if (typeof(attrib) === "function") {
                            attrib.call(this._xmlNode, event);
                        }
                        else {
                            var funcStr = this._xmlNode.getAttribute("on" + eventType);
                            var func = new Function('event', funcStr);
                            func.call(this._xmlNode, event);
                        }

                        var list = this._listeners[eventType];
                        if (list) {
                            for (var i = 0; i < list.length; i++) {
                                list[i].call(this._xmlNode, event);
                            }
                        }
                    }
                    catch(ex) {
                        x3dom.debug.logException(ex);
                    }
                }
            },

            loadInline: function ()
            {
                var that = this;

                var xhr = new window.XMLHttpRequest();
                if (xhr.overrideMimeType)
                    xhr.overrideMimeType('text/xml');   //application/xhtml+xml

                xhr.onreadystatechange = function ()
                {
                    if (xhr.readyState != 4) {
                        // still loading
                        //x3dom.debug.logInfo('Loading inlined data... (readyState: ' + xhr.readyState + ')');
                        return xhr;
                    }

                    if (xhr.status === x3dom.nodeTypes.Inline.AwaitTranscoding) {
                        if (that.count < that.numRetries)
                        {
                            that.count++;
                            var refreshTime = +xhr.getResponseHeader("Refresh") || 5;
                            x3dom.debug.logInfo('XHR status: ' + xhr.status + ' - Await Transcoding (' + that.count + '/' + that.numRetries + '): ' + 
                                                'Next request in ' + refreshTime + ' seconds');
                      
                            window.setTimeout(function() {
                                that._nameSpace.doc.downloadCount -= 1;
                                that.loadInline();
                            }, refreshTime * 1000);
                            return xhr;
                        }
                        else
                        {
                            x3dom.debug.logError('XHR status: ' + xhr.status + ' - Await Transcoding (' + that.count + '/' + that.numRetries + '): ' + 
                                                 'No Retries left');
                            that._nameSpace.doc.downloadCount -= 1;
                            that.count = 0;
                            return xhr;
                        }
                    }
                    else if ((xhr.status !== 200) && (xhr.status !== 0)) {
                        that.fireEvents("error");
                        x3dom.debug.logError('XHR status: ' + xhr.status + ' - XMLHttpRequest requires web server running!');

                        that._nameSpace.doc.downloadCount -= 1;
                        that.count = 0;
                        return xhr;
                    }
                    else if ((xhr.status == 200) || (xhr.status == 0)) {
                        that.count = 0;
                    }

                    x3dom.debug.logInfo('Inline: downloading '+that._vf.url[0]+' done.');

                    var inlScene = null, newScene = null, nameSpace = null, xml = null;

                    if (navigator.appName != "Microsoft Internet Explorer")
                        xml = xhr.responseXML;
                    else
                        xml = new DOMParser().parseFromString(xhr.responseText, "text/xml");

                    //TODO; check if exists and FIXME: it's not necessarily the first scene in the doc!
                    if (xml !== undefined && xml !== null)
                    {
                        inlScene = xml.getElementsByTagName('Scene')[0] ||
                            xml.getElementsByTagName('scene')[0];
                    }
                    else {
                        that.fireEvents("error");
                    }

                    if (inlScene)
                    {
                        var nsName = (that._vf.nameSpaceName.length != 0) ?
                            that._vf.nameSpaceName.toString().replace(' ','') : "";
                        nameSpace = new x3dom.NodeNameSpace(nsName, that._nameSpace.doc);

                        var url = that._vf.url.length ? that._vf.url[0] : "";
                        if ((url[0] === '/') || (url.indexOf(":") >= 0))
                            nameSpace.setBaseURL(url);
                        else
                            nameSpace.setBaseURL(that._nameSpace.baseURL + url);

                        newScene = nameSpace.setupTree(inlScene);
                        that._nameSpace.addSpace(nameSpace);

                        if(that._vf.nameSpaceName.length != 0)
                        {
                            Array.forEach ( inlScene.childNodes, function (childDomNode)
                            {
                                if(childDomNode instanceof Element)
                                {
                                    setNamespace(that._vf.nameSpaceName, childDomNode, that._vf.mapDEFToID);
                                    that._xmlNode.appendChild(childDomNode);
                                }
                            } );
                        }
                    }
                    else {
                        if (xml && xml.localName)
                            x3dom.debug.logError('No Scene in ' + xml.localName);
                        else
                            x3dom.debug.logError('No Scene in resource');
                    }

                    // trick to free memory, assigning a property to global object, then deleting it
                    var global = x3dom.getGlobal();

                    if (that._childNodes.length > 0 && that._childNodes[0] && that._childNodes[0]._nameSpace)
                        that._nameSpace.removeSpace(that._childNodes[0]._nameSpace);

                    while (that._childNodes.length !== 0)
                        global['_remover'] = that.removeChild(that._childNodes[0]);

                    delete global['_remover'];

                    if (newScene)
                    {
                        that.addChild(newScene);

                        that.invalidateVolume();
                        //that.invalidateCache();

                        that._nameSpace.doc.downloadCount -= 1;
                        that._nameSpace.doc.needRender = true;
                        x3dom.debug.logInfo('Inline: added ' + that._vf.url[0] + ' to scene.');

                        // recalc changed scene bounding box twice
                        var theScene = that._nameSpace.doc._scene;

                        if (theScene) {
                            theScene.invalidateVolume();
                            //theScene.invalidateCache();

                            window.setTimeout( function() {
                                that.invalidateVolume();
                                //that.invalidateCache();

                                theScene.updateVolume();
                                that._nameSpace.doc.needRender = true;
                            }, 1000 );
                        }

                        that.fireEvents("load");
                    }

                    newScene = null;
                    nameSpace = null;
                    inlScene = null;
                    xml = null;

                    return xhr;
                };

                if (this._vf.url.length && this._vf.url[0].length)
                {
                    var xhrURI = this._nameSpace.getURL(this._vf.url[0]);

                    xhr.open('GET', xhrURI, true);

                    this._nameSpace.doc.downloadCount += 1;

                    try {
                        //xhr.send(null);
                        x3dom.RequestManager.addRequest(xhr);
                    }
                    catch(ex) {
                        this.fireEvents("error");
                        x3dom.debug.logError(this._vf.url[0] + ": " + ex);
                    }
                }
            }
        }
    )
);

x3dom.nodeTypes.Inline.AwaitTranscoding = 202;      // Parameterizable retry state for Transcoder
x3dom.nodeTypes.Inline.MaximumRetries = 15;         // Parameterizable maximum number of retries

function setNamespace(prefix, childDomNode, mapDEFToID)
{
    if(childDomNode instanceof Element && childDomNode.__setAttribute !== undefined) {

        if(childDomNode.hasAttribute('id') )	{
            childDomNode.__setAttribute('id', prefix.toString().replace(' ','') +'__'+ childDomNode.getAttribute('id'));
        } else if (childDomNode.hasAttribute('DEF') && mapDEFToID){
            childDomNode.__setAttribute('id', prefix.toString().replace(' ','') +'__'+ childDomNode.getAttribute('DEF'));
            // workaround for Safari
            if (!childDomNode.id)
                childDomNode.id = childDomNode.__getAttribute('id');
        }
    }

    if(childDomNode.hasChildNodes()){
        Array.forEach ( childDomNode.childNodes, function (children) {
            setNamespace(prefix, children, mapDEFToID);
        } );
    }
}

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### MultiPart ###
x3dom.registerNodeType(
    "MultiPart",
    "Networking",
    defineClass(x3dom.nodeTypes.Inline,

        /**
         * Constructor for MultiPart
         * @constructs x3dom.nodeTypes.MultiPart
         * @x3d x.x
         * @component Networking
         * @extends x3dom.nodeTypes.Inline
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Multipart node
         */
            function (ctx) {
            x3dom.nodeTypes.MultiPart.superClass.call(this, ctx);

            /**
             * Specifies the url to the IDMap.
             * @var {x3dom.fields.MFString} urlIDMap
             * @memberof x3dom.nodeTypes.MultiPart
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'urlIDMap', []);

            /**
             * Defines whether the shape is pickable.
             * @var {x3dom.fields.SFBool} isPickable
             * @memberof x3dom.nodeTypes.MultiPart
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'isPickable', true);
            
            /**
             * Defines the shape type for sorting.
             * @var {x3dom.fields.SFString} sortType
             * @range [auto, transparent, opaque]
             * @memberof x3dom.nodeTypes.MultiPart
             * @initvalue 'auto'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'sortType', 'auto');

            /**
             * Specifies whether backface-culling is used. If solid is TRUE only front-faces are drawn.
             * @var {x3dom.fields.SFBool} solid
             * @memberof x3dom.nodeTypes.MultiPart
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'solid', false);

            /**
             * Change render order manually.
             * @var {x3dom.fields.SFInt32} sortKey
             * @memberof x3dom.nodeTypes.MultiPart
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'sortKey', 0);

            /**
             * Set the initial visibility.
             * @var {x3dom.fields.SFInt32} initialVisibility
             * @range [auto, visible, invisible]
             * @memberof x3dom.nodeTypes.MultiPart
             * @initvalue 'auto'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'initialVisibility', 'auto');

            this._idMap = null;
            this._inlineNamespace = null;
            this._highlightedParts = [];
            this._minId = 0;
            this._maxId = 0;
            this._lastId = -1;
            this._lastClickedId = -1;
            this._lastButton = 0;
            this._identifierToPartId = [];
            this._identifierToAppId = [];
            this._visiblePartsPerShape = [];
            this._partVolume = [];
            this._partVisibility = [];
			this._originalColor = [];
            this._materials = [];

        },
        {
            fieldChanged: function (fieldName)
            {
                 if (fieldName == "url") {
                    if (this._vf.nameSpaceName.length != 0) {
                        var node = this._xmlNode;
                        if (node && node.hasChildNodes())
                        {
                            while ( node.childNodes.length >= 1 )
                            {
                                node.removeChild( node.firstChild );
                            }
                        }
                    }
                    this.loadInline();
                 }
                 else if (fieldName == "render") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                 }
            },

            nodeChanged: function ()
            {
                if (!this.initDone) {
                    this.initDone = true;
                    this.loadIDMap();
                }
            },
            
            getVolume: function ()
            {
                var vol = this._graph.volume;

                if (!this.volumeValid() && this._vf.render)
                {
                    for (var i=0; i<this._partVisibility.length; i++)
                    {
                        if (!this._partVisibility[i])
                            continue;

                        var childVol = this._partVolume[i];

                        if (childVol && childVol.isValid())
                            vol.extendBounds(childVol.min, childVol.max);
                    }
                }

                if ( !vol.equals( this._graph.lastVolume ) )
                {
                    this._graph.lastVolume = x3dom.fields.BoxVolume.copy( vol );

                    var event = {
                        target: this._xmlNode,
                        type: "volumechanged",   // event only called onxxx if used as old-fashioned attribute
                        volume: x3dom.fields.BoxVolume.copy( vol )
                    };

                    this.callEvtHandler("onvolumechanged", event);
                }
                
                return vol;
            },

            handleEvents: function(e)
            {
                if( this._inlineNamespace ) {
                    var colorMap = this._inlineNamespace.defMap["MultiMaterial_ColorMap"];
                    var emissiveMap = this._inlineNamespace.defMap["MultiMaterial_EmissiveMap"];
                    var specularMap = this._inlineNamespace.defMap["MultiMaterial_SpecularMap"];
                    var visibilityMap = this._inlineNamespace.defMap["MultiMaterial_VisibilityMap"];

                    //Check for Background press and release
                    if (e.pickedId == -1 && e.button != 0) {
                        this._lastClickedId = -1;
                        this._lastButton = e.button;
                    } else if (e.pickedId == -1 && e.button == 0) {
                        this._lastClickedId = -1;
                        this._lastButton = 0;
                    }

                    if (e.pickedId != -1) {
                        e.part = new x3dom.Parts(this, [e.pickedId - this._minId], colorMap, emissiveMap, specularMap, visibilityMap);
                        e.partID = this._idMap.mapping[e.pickedId - this._minId].name;

                        //fire mousemove event
                        e.type = "mousemove";
                        this.callEvtHandler("onmousemove", e);

                        //fire mouseover event
                        e.type = "mouseover";
                        this.callEvtHandler("onmouseover", e);

                        //if some mouse button is down fire mousedown event
                        if (!e.mouseup && e.button && e.button != this._lastButton) {
                            e.type = "mousedown";
                            this._lastButton = e.button;
                            if ( this._lastClickedId == -1 ) {
                                this._lastClickedId = e.pickedId;
                            }
                            this.callEvtHandler("onmousedown", e);
                        }

                        //if some mouse button is up fire mouseup event
                        if (e.mouseup || (this._lastButton != 0 && e.button == 0)) {
                            e.type = "mouseup";
                            this.callEvtHandler("onmouseup", e);
                            this._lastButton = 0;

                            if ( e.pickedId == this._lastClickedId ) {
                                this._lastClickedId = -1;
                                e.type = "click";
                                this.callEvtHandler("onclick", e);
                            }

                            this._lastClickedId = -1;
                        }

                        //If the picked id has changed we enter+leave a part
                        if (e.pickedId != this._lastId) {
                            if (this._lastId != -1) {
                                e.part = new x3dom.Parts(this, [this._lastId - this._minId], colorMap, emissiveMap, specularMap, visibilityMap);
                                e.partID = this._idMap.mapping[this._lastId - this._minId].name;
                                e.type = "mouseleave";
                                this.callEvtHandler("onmouseleave", e);
                            }

                            e.part = new x3dom.Parts(this, [e.pickedId - this._minId], colorMap, emissiveMap, specularMap, visibilityMap);
                            e.partID = this._idMap.mapping[e.pickedId - this._minId].name;
                            e.type = "mouseenter";
                            this.callEvtHandler("onmouseenter", e);
                            this._lastId = e.pickedId;
                        }

                        this._lastId = e.pickedId;
                    }
                    else if (this._lastId != -1) {
                        e.part = new x3dom.Parts(this, [this._lastId - this._minId], colorMap, emissiveMap, specularMap, visibilityMap);
                        e.partID = this._idMap.mapping[this._lastId - this._minId].name;
                        e.type = "mouseout";
                        this.callEvtHandler("onmouseout", e);
                        e.type = "mouseleave";
                        this.callEvtHandler("onmouseleave", e);
                        this._lastId = -1;
                    }
                }

            },

            loadIDMap: function ()
            {
                if (this._vf.urlIDMap.length && this._vf.urlIDMap[0].length)
                {
                    var i;

                    var that = this;

                    var idMapURI = this._nameSpace.getURL(this._vf.urlIDMap[0]);

                    var xhr = new XMLHttpRequest();

                    xhr.open("GET", idMapURI, true);

                    xhr.onload = function()
                    {
                        that._idMap = JSON.parse(this.responseText);

                        //Check if the MultiPart map already initialized
                        if (that._nameSpace.doc._scene._multiPartMap == null) {
                            that._nameSpace.doc._scene._multiPartMap = {numberOfIds: 0, multiParts: []};
                        }

                        //Set the ID range this MultiPart is holding
                        that._minId = that._nameSpace.doc._scene._multiPartMap.numberOfIds;
                        that._maxId = that._minId + that._idMap.numberOfIDs - 1;

                        //Update the MultiPart map
                        that._nameSpace.doc._scene._multiPartMap.numberOfIds += that._idMap.numberOfIDs;
                        that._nameSpace.doc._scene._multiPartMap.multiParts.push(that);

                        //prepare internal shape map
                        for (i=0; i<that._idMap.mapping.length; i++)
                        {
                            if (!that._identifierToPartId[that._idMap.mapping[i].name]) {
                                that._identifierToPartId[that._idMap.mapping[i].name] = [];
                            }

                            if (!that._identifierToPartId[that._idMap.mapping[i].appearance]) {
                                that._identifierToPartId[that._idMap.mapping[i].appearance] = [];
                            }

                            that._identifierToPartId[that._idMap.mapping[i].name].push(i);
                            that._identifierToPartId[that._idMap.mapping[i].appearance].push(i);

                            if (!that._partVolume[i]) {
                                var min = x3dom.fields.SFVec3f.parse(that._idMap.mapping[i].min);
                                var max = x3dom.fields.SFVec3f.parse(that._idMap.mapping[i].max);

                                that._partVolume[i] = new x3dom.fields.BoxVolume(min, max);
                            }

                        }

                        //prepare internal appearance map
                        for (i=0; i<that._idMap.appearance.length; i++)
                        {
                            that._identifierToAppId[that._idMap.appearance[i].name] = i;
                        }

                        that.loadInline();
                    };

                    //xhr.send(null);
                    x3dom.RequestManager.addRequest(xhr);
                }
            },

            createMaterialData: function ()
            {
                var diffuseColor, transparency, specularColor, shininess, emissiveColor, ambientIntensity;
                var backDiffuseColor, backTransparency, backSpecularColor, backShininess, backEmissiveColor, backAmbientIntensity;
                var rgba_DT = "", rgba_SS = "", rgba_EA = "";
                var rgba_DT_B = "", rgba_SS_B = "", rgba_EA_B = "";

                var size = Math.ceil(Math.sqrt(this._idMap.numberOfIDs));

                //scale image data array size to the next highest power of two
                size = x3dom.Utils.nextHighestPowerOfTwo(size);
                var sizeTwo = size * 2.0;

                var diffuseTransparencyData = size + " " + sizeTwo + " 4";
                var specularShininessData = size + " " + sizeTwo + " 4";
                var emissiveAmbientIntensityData = size + " " + sizeTwo + " 4";

                for (var i=0; i<size*size; i++)
                {
                    if (i < this._idMap.mapping.length)
                    {
                        var appName = this._idMap.mapping[i].appearance;
                        var appID = this._identifierToAppId[appName];

                        //AmbientIntensity
                        if (this._idMap.appearance[appID].material.ambientIntensity) {
                            ambientIntensity = this._idMap.appearance[appID].material.ambientIntensity
                        } else {
                            ambientIntensity = "0.2";
                        }

                        //BackAmbientIntensity
                        if (this._idMap.appearance[appID].material.backAmbientIntensity) {
                            backAmbientIntensity = this._idMap.appearance[appID].material.backAmbientIntensity
                        } else {
                            backAmbientIntensity = ambientIntensity;
                        }

                        //DiffuseColor
                        if (this._idMap.appearance[appID].material.diffuseColor) {
                            diffuseColor = this._idMap.appearance[appID].material.diffuseColor
                        } else {
                            diffuseColor = "0.8 0.8 0.8";
                        }

                        //BackDiffuseColor
                        if (this._idMap.appearance[appID].material.backDiffuseColor) {
                            backDiffuseColor = this._idMap.appearance[appID].material.backDiffuseColor
                        } else {
                            backDiffuseColor = diffuseColor;
                        }

                        //EmissiveColor
                        if (this._idMap.appearance[appID].material.emissiveColor) {
                            emissiveColor = this._idMap.appearance[appID].material.emissiveColor
                        } else {
                            emissiveColor = "0.0 0.0 0.0";
                        }

                        //BackEmissiveColor
                        if (this._idMap.appearance[appID].material.backEmissiveColor) {
                            backEmissiveColor = this._idMap.appearance[appID].material.backEmissiveColor
                        } else {
                            backEmissiveColor = emissiveColor;
                        }

                        //Shininess
                        if (this._idMap.appearance[appID].material.shininess) {
                            shininess = this._idMap.appearance[appID].material.shininess;
                        } else {
                            shininess = "0.2";
                        }

                        //BackShininess
                        if (this._idMap.appearance[appID].material.backShininess) {
                            backShininess = this._idMap.appearance[appID].material.backShininess;
                        } else {
                            backShininess = shininess;
                        }

                        //SpecularColor
                        if (this._idMap.appearance[appID].material.specularColor) {
                            specularColor = this._idMap.appearance[appID].material.specularColor;
                        } else {
                            specularColor = "0 0 0";
                        }

                        //BackSpecularColor
                        if (this._idMap.appearance[appID].material.backSpecularColor) {
                            backSpecularColor = this._idMap.appearance[appID].material.backSpecularColor;
                        } else {
                            backSpecularColor = specularColor;
                        }

                        //Transparency
                        if (this._idMap.appearance[appID].material.transparency) {
                            transparency = this._idMap.appearance[appID].material.transparency;
                        } else {
                            transparency = 0.0;
                        }

                        //BackTransparency
                        if (this._idMap.appearance[appID].material.backTransparency) {
                            backTransparency = this._idMap.appearance[appID].material.backTransparency;
                        } else {
                            backTransparency = transparency;
                        }

                        rgba_DT +=  " " + x3dom.fields.SFColorRGBA.parse(diffuseColor + " " + (1.0 - transparency)).toUint();
                        rgba_SS +=  " " + x3dom.fields.SFColorRGBA.parse(specularColor + " " + shininess).toUint();
                        rgba_EA +=  " " + x3dom.fields.SFColorRGBA.parse(emissiveColor + " " + ambientIntensity).toUint();

                        rgba_DT_B += " " + x3dom.fields.SFColorRGBA.parse(backDiffuseColor + " " + (1.0 - backTransparency)).toUint();
                        rgba_SS_B += " " + x3dom.fields.SFColorRGBA.parse(backSpecularColor + " " + backShininess).toUint();
                        rgba_EA_B += " " + x3dom.fields.SFColorRGBA.parse(backEmissiveColor + " " + backAmbientIntensity).toUint();

                        this._originalColor[i] = rgba_DT;

                        this._materials[i] = new x3dom.MultiMaterial({
                            "ambientIntensity": ambientIntensity,
                            "diffuseColor": x3dom.fields.SFColor.parse(diffuseColor),
                            "emissiveColor": x3dom.fields.SFColor.parse(emissiveColor),
                            "shininess": shininess,
                            "specularColor": x3dom.fields.SFColor.parse(specularColor),
                            "transparency": transparency,
                            "backAmbientIntensity": backAmbientIntensity,
                            "backDiffuseColor": x3dom.fields.SFColor.parse(backDiffuseColor),
                            "backEmissiveColor": x3dom.fields.SFColor.parse(backEmissiveColor),
                            "backShininess": backShininess,
                            "backSpecularColor": x3dom.fields.SFColor.parse(backSpecularColor),
                            "backTransparency": backTransparency
                        });
                    }
                    else
                    {
                        rgba_DT += " 255";
                        rgba_SS += " 255";
                        rgba_EA += " 255";

                        rgba_DT_B += " 255";
                        rgba_SS_B += " 255";
                        rgba_EA_B += " 255";
                    }
                }

                //Combine Front and Back Data
                diffuseTransparencyData      += rgba_DT + rgba_DT_B;
                specularShininessData        += rgba_SS + rgba_SS_B;
                emissiveAmbientIntensityData += rgba_EA + rgba_EA_B;

                return {
                    "diffuseTransparency": diffuseTransparencyData,
                    "specularShininess": specularShininessData,
                    "emissiveAmbientIntensity": emissiveAmbientIntensityData
                };
            },

            createVisibilityData: function ()
            {
                var i, j;
                var size = Math.ceil(Math.sqrt(this._idMap.numberOfIDs));

                //scale image data array size to the next highest power of two
                size = x3dom.Utils.nextHighestPowerOfTwo(size);
                
                var visibilityData = size + " " + size + " 1";

                for (i=0; i<size*size; i++)
                {
                    if (i < this._idMap.mapping.length)
                    {
                        if (this._vf.initialVisibility == 'auto')
                        {
                            //TODO get the Data from JSON
                            visibilityData += " 255";

                            if (!this._partVisibility[i]) {
                                this._partVisibility[i] = true;
                            }

                            for (j=0; j<this._idMap.mapping[i].usage.length; j++)
                            {
                                if (!this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]]) {
                                    this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]] = {val:0, max:0};
                                }
                                this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]].val++;
                                this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]].max++;
                            }
                        }
                        else if (this._vf.initialVisibility == 'visible')
                        {
                            visibilityData += " 255";

                            if (!this._partVisibility[i]) {
                                this._partVisibility[i] = true;
                            }

                            for (j=0; j<this._idMap.mapping[i].usage.length; j++)
                            {
                                if (!this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]]) {
                                    this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]] = {val:0, max:0};
                                }
                                this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]].val++;
                                this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]].max++;
                            }
                        }
                        else if (this._vf.initialVisibility == 'invisible')
                        {
                            visibilityData += " 0";

                            if (!this._partVisibility[i]) {
                                this._partVisibility[i] = false;
                            }

                            for (j=0; j<this._idMap.mapping[i].usage.length; j++)
                            {
                                if (!this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]]) {
                                    this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]] = {val:0, max:0};
                                }
                                this._visiblePartsPerShape[this._idMap.mapping[i].usage[j]].max++;
                            }
                        }

                    }
                    else
                    {
                        visibilityData += " 0";
                    }
                }
                return visibilityData;
            },

            replaceMaterials: function (inlScene)
            {
                var css, shapeDEF, materialData, visibilityData, appearance;
                var firstMat = true;
                if (inlScene && inlScene.hasChildNodes())
                {
                    materialData = this.createMaterialData();
                    visibilityData = this.createVisibilityData();

                    var shapes = inlScene.getElementsByTagName("Shape");

                    for (var s=0; s<shapes.length; s++)
                    {
                        shapeDEF = shapes[s].getAttribute("DEF") ||
                                   shapes[s].getAttribute("def");

                        if(shapeDEF && this._visiblePartsPerShape[shapeDEF] && 
                           this._visiblePartsPerShape[shapeDEF].val == 0)
                        {
                            shapes[s].setAttribute("render", "false");
                        }

                        shapes[s].setAttribute("idOffset", this._minId);
                        shapes[s].setAttribute("isPickable", this._vf.isPickable);

                        var geometries = shapes[s].getElementsByTagName("BinaryGeometry");

                        if (geometries && geometries.length) {
                            for (var g = 0; g < geometries.length; g++) {
                                geometries[g].setAttribute("solid", this._vf.solid);
                            }
                        }

                        var appearances = shapes[s].getElementsByTagName("Appearance");
                        
                        if (appearances.length)
                        {
                            for (var a = 0; a < appearances.length; a++)
                            {
                                //Remove DEF/USE
                                appearances[a].removeAttribute("DEF");
                                appearances[a].removeAttribute("USE");
                                
                                appearances[a].setAttribute("sortType", this._vf.sortType);
                                appearances[a].setAttribute("sortKey", this._vf.sortKey);

                                var materials = appearances[a].getElementsByTagName("Material");

                                if (materials.length)
                                {
                                    //Replace Material
                                    if (firstMat) {
                                        firstMat = false;
                                        css = document.createElement("CommonSurfaceShader");
                                        css.setAttribute("DEF", "MultiMaterial");

                                        var ptDA = document.createElement("PixelTexture");
                                        ptDA.setAttribute("containerField", "multiDiffuseAlphaTexture");
                                        ptDA.setAttribute("id", "MultiMaterial_ColorMap");
                                        ptDA.setAttribute("image", materialData.diffuseTransparency);

                                        var ptEA = document.createElement("PixelTexture");
                                        ptEA.setAttribute("containerField", "multiEmissiveAmbientTexture");
                                        ptEA.setAttribute("id", "MultiMaterial_EmissiveMap");
                                        ptEA.setAttribute("image", materialData.emissiveAmbientIntensity);

                                        var ptSS = document.createElement("PixelTexture");
                                        ptSS.setAttribute("containerField", "multiSpecularShininessTexture");
                                        ptSS.setAttribute("id", "MultiMaterial_SpecularMap");
                                        ptSS.setAttribute("image", materialData.specularShininess);

                                        var ptV = document.createElement("PixelTexture");
                                        ptV.setAttribute("containerField", "multiVisibilityTexture");
                                        ptV.setAttribute("id", "MultiMaterial_VisibilityMap");
                                        ptV.setAttribute("image", visibilityData);

                                        css.appendChild(ptDA);
                                        css.appendChild(ptEA);
                                        css.appendChild(ptSS);
                                        css.appendChild(ptV);
                                    }
                                    else
                                    {
                                        css = document.createElement("CommonSurfaceShader");
                                        css.setAttribute("USE", "MultiMaterial");
                                    }
                                    appearances[a].replaceChild(css, materials[0]);
                                }
                                else
                                {
                                    //Add Material
                                    if (firstMat) {
                                        firstMat = false;
                                        css = document.createElement("CommonSurfaceShader");
                                        css.setAttribute("DEF", "MultiMaterial");

                                        var ptDA = document.createElement("PixelTexture");
                                        ptDA.setAttribute("containerField", "multiDiffuseAlphaTexture");
                                        ptDA.setAttribute("id", "MultiMaterial_ColorMap");
                                        ptDA.setAttribute("image", materialData.diffuseTransparency);

                                        var ptEA = document.createElement("PixelTexture");
                                        ptEA.setAttribute("containerField", "multiEmissiveAmbientTexture");
                                        ptEA.setAttribute("id", "MultiMaterial_EmissiveMap");
                                        ptEA.setAttribute("image", materialData.emissiveAmbientIntensity);

                                        var ptSS = document.createElement("PixelTexture");
                                        ptSS.setAttribute("containerField", "multiSpecularShininessTexture");
                                        ptSS.setAttribute("id", "MultiMaterial_SpecularMap");
                                        ptSS.setAttribute("image", materialData.specularShininess);

                                        var ptV = document.createElement("PixelTexture");
                                        ptV.setAttribute("containerField", "multiVisibilityTexture");
                                        ptV.setAttribute("id", "MultiMaterial_VisibilityMap");
                                        ptV.setAttribute("image", visibilityData);

                                        css.appendChild(ptDA);
                                        css.appendChild(ptEA);
                                        css.appendChild(ptSS);
                                        css.appendChild(ptV);
                                    }
                                    else
                                    {
                                        css = document.createElement("CommonSurfaceShader");
                                        css.setAttribute("USE", "MultiMaterial");
                                    }

                                    appearances[a].appendChild(css);
                                }
                            }
                        }
                        else
                        {
                            //Add Appearance
                            appearance = document.createElement("Appearance");
                            
                            //Add Material
                            if (firstMat) {
                                firstMat = false;
                                css = document.createElement("CommonSurfaceShader");
                                css.setAttribute("DEF", "MultiMaterial");

                                var ptDA = document.createElement("PixelTexture");
                                ptDA.setAttribute("containerField", "multiDiffuseAlphaTexture");
                                ptDA.setAttribute("id", "MultiMaterial_ColorMap");
                                ptDA.setAttribute("image", materialData.diffuseTransparency);

                                var ptEA = document.createElement("PixelTexture");
                                ptEA.setAttribute("containerField", "multiEmissiveAmbientTexture");
                                ptEA.setAttribute("id", "MultiMaterial_EmissiveMap");
                                ptEA.setAttribute("image", materialData.emissiveAmbientIntensity);

                                var ptSS = document.createElement("PixelTexture");
                                ptSS.setAttribute("containerField", "multiSpecularShininessTexture");
                                ptSS.setAttribute("id", "MultiMaterial_SpecularMap");
                                ptSS.setAttribute("image", materialData.specularShininess);

                                var ptV = document.createElement("PixelTexture");
                                ptV.setAttribute("containerField", "multiVisibilityTexture");
                                ptV.setAttribute("id", "MultiMaterial_VisibilityMap");
                                ptV.setAttribute("image", visibilityData);

                                css.appendChild(ptDA);
                                css.appendChild(ptEA);
                                css.appendChild(ptSS);
                                css.appendChild(ptV);
                            }
                            else
                            {
                                css = document.createElement("CommonSurfaceShader");
                                css.setAttribute("USE", "MultiMaterial");
                            }

                            appearance.appendChild(css);
                            geometries[g].appendChild(appearance);
                        }
                    }
                }
            },

            appendAPI: function ()
            {
                var multiPart = this;

                this._xmlNode.getIdList = function ()
                {
                    var i, ids = [];

                    for (i=0; i<multiPart._idMap.mapping.length; i++) {
                        ids.push( multiPart._idMap.mapping[i].name );
                    }

                    return ids;
                };

                this._xmlNode.getAppearanceIdList = function ()
                {
                    var i, ids = [];

                    for (i=0; i<multiPart._idMap.appearance.length; i++) {
                        ids.push( multiPart._idMap.appearance[i].name );
                    }

                    return ids;
                };

                this._xmlNode.getParts = function (selector)
                {
                    var i, m;
                    var selection = [];

                    if (selector == undefined) {
                        for (m=0; m<multiPart._idMap.mapping.length; m++) {
                            selection.push(m);
                        }
                    } else if (selector instanceof Array) {

                        for (i=0; i<selector.length; i++) {
                            if (multiPart._identifierToPartId[selector[i]]) {
                                selection = selection.concat(multiPart._identifierToPartId[selector[i]]);
                            }
                        }

                    } else if (selector instanceof RegExp) {

                        for (var key in multiPart._identifierToPartId) {

                            if ( key.match( selector ) ) {
                                selection = selection.concat(multiPart._identifierToPartId[ key ]);
                            }
                        }

                    }

                    var colorMap = multiPart._inlineNamespace.defMap["MultiMaterial_ColorMap"];
                    var emissiveMap = multiPart._inlineNamespace.defMap["MultiMaterial_EmissiveMap"];
                    var specularMap = multiPart._inlineNamespace.defMap["MultiMaterial_SpecularMap"];
                    var visibilityMap = multiPart._inlineNamespace.defMap["MultiMaterial_VisibilityMap"];

                    if ( selection.length == 0) {
                        return null;
                    } else {
                        return new x3dom.Parts(multiPart, selection, colorMap, emissiveMap, specularMap, visibilityMap);
                    }
                };

                /**
                 *
                 *
                 * @param left border position in screen pixel
                 * @param right border position in screen pixel
                 * @param bottom border position in screen pixel
                 * @param top border position in screen pixel
                 * @returns selected Parts
                 */
                this._xmlNode.getPartsByRect = function (left, right, bottom, top)
                {
                    var viewarea = multiPart._nameSpace.doc._viewarea;
                    var viewpoint = viewarea._scene.getViewpoint();

                    var origViewMatrix    = viewarea.getViewMatrix();
                    var origProjMatrix    = viewarea.getProjectionMatrix();

                    var upDir   = new x3dom.fields.SFVec3f(origViewMatrix._01, origViewMatrix._11, origViewMatrix._21);
                    var viewDir = new x3dom.fields.SFVec3f(origViewMatrix._02, origViewMatrix._12, origViewMatrix._22);
                    var pos = new x3dom.fields.SFVec3f(origViewMatrix._03, origViewMatrix._13, origViewMatrix._23);

                    var normalizedLeft   = (left   - viewarea._width  / 2) / (viewarea._width  / 2);
                    var normalizedRight  = (right  - viewarea._width  / 2) / (viewarea._width  / 2);
                    var normalizedTop    = (top    - viewarea._height / 2) / (viewarea._height / 2);
                    var normalizedBottom = (bottom - viewarea._height / 2) / (viewarea._height / 2);

                    /*
                        For any given distance Z from the camera,
                        the shortest distance D from the center point of a plane
                        perpendicular to the viewing vector at Z to one of its borders
                        above or below the center (really, the intersection of the plane and frustum)
                        is D = tan(FOV / 2) * Z . Add and subtract D from the center point's Y component
                        to get the maximum and minimum Y extents.
                    */

                    var fov = viewpoint._vf.fieldOfView;
                    var factorH = Math.tan(fov/2) * viewpoint._zNear;
                    var factorW = Math.tan(fov/2)* viewpoint._lastAspect * viewpoint._zNear;

                    var projMatrix = x3dom.fields.SFMatrix4f.perspectiveFrustum(
                        normalizedLeft * factorW,
                        normalizedRight * factorW,
                        normalizedBottom * factorH,
                        normalizedTop * factorH,
                        viewpoint.getNear(),
                        viewpoint.getFar());

                    var viewMatrix = x3dom.fields.SFMatrix4f.lookAt(pos,
                        pos.subtract(viewDir.multiply(5.0)),
                        upDir);

                    var frustum =  new x3dom.fields.FrustumVolume( projMatrix.mult(viewMatrix) );
                    //viewpoint._projMatrix = projMatrix;

                    //return null;

                    var selection = [];
                    var volumes = this._x3domNode._partVolume;
                    for(id in volumes){
                        if(!volumes.hasOwnProperty(id))
                            continue;

                        var intersect = frustum.intersect(volumes[id], 0);
                        if(intersect > 0)
                            selection.push(id);
                    }

                    var colorMap = multiPart._inlineNamespace.defMap["MultiMaterial_ColorMap"];
                    var emissiveMap = multiPart._inlineNamespace.defMap["MultiMaterial_EmissiveMap"];
                    var specularMap = multiPart._inlineNamespace.defMap["MultiMaterial_SpecularMap"];
                    var visibilityMap = multiPart._inlineNamespace.defMap["MultiMaterial_VisibilityMap"];

                    if ( selection.length == 0) {
                        return null;
                    } else {
                        return new x3dom.Parts(multiPart, selection, colorMap, emissiveMap, specularMap, visibilityMap);
                    }

                };
            },

            loadInline: function ()
            {
                var that = this;
                var xhr = new window.XMLHttpRequest();
                if (xhr.overrideMimeType)
                    xhr.overrideMimeType('text/xml');   //application/xhtml+xml

                xhr.onreadystatechange = function ()
                {
                    if (xhr.readyState != 4) {
                        // still loading
                        //x3dom.debug.logInfo('Loading inlined data... (readyState: ' + xhr.readyState + ')');
                        return xhr;
                    }

                    if (xhr.status === x3dom.nodeTypes.Inline.AwaitTranscoding) {
                        if (that.count < that.numRetries)
                        {
                            that.count++;
                            var refreshTime = +xhr.getResponseHeader("Refresh") || 5;
                            x3dom.debug.logInfo('XHR status: ' + xhr.status + ' - Await Transcoding (' + that.count + '/' + that.numRetries + '): ' + 
                                                'Next request in ' + refreshTime + ' seconds');
                      
                            window.setTimeout(function() {
                                that._nameSpace.doc.downloadCount -= 1;
                                that.loadInline();
                            }, refreshTime * 1000);
                            return xhr;
                        }
                        else
                        {
                            x3dom.debug.logError('XHR status: ' + xhr.status + ' - Await Transcoding (' + that.count + '/' + that.numRetries + '): ' + 
                                                 'No Retries left');
                            that._nameSpace.doc.downloadCount -= 1;
                            that.count = 0;
                            return xhr;
                        }
                    }
                    else if ((xhr.status !== 200) && (xhr.status !== 0)) {
                        that.fireEvents("error");
                        x3dom.debug.logError('XHR status: ' + xhr.status + ' - XMLHttpRequest requires web server running!');

                        that._nameSpace.doc.downloadCount -= 1;
                        that.count = 0;
                        return xhr;
                    }
                    else if ((xhr.status == 200) || (xhr.status == 0)) {
                        that.count = 0;
                    }

                    x3dom.debug.logInfo('Inline: downloading '+that._vf.url[0]+' done.');

                    var inlScene = null, newScene = null, nameSpace = null, xml = null;

                    if (navigator.appName != "Microsoft Internet Explorer")
                        xml = xhr.responseXML;
                    else
                        xml = new DOMParser().parseFromString(xhr.responseText, "text/xml");

                    //TODO; check if exists and FIXME: it's not necessarily the first scene in the doc!
                    if (xml !== undefined && xml !== null)
                    {
                        inlScene = xml.getElementsByTagName('Scene')[0] ||
                            xml.getElementsByTagName('scene')[0];
                    }
                    else {
                        that.fireEvents("error");
                    }

                    if (inlScene)
                    {
                        var nsDefault = "ns" + that._nameSpace.childSpaces.length;
                        
                        var nsName = (that._vf.nameSpaceName.length != 0) ?
                                      that._vf.nameSpaceName.toString().replace(' ','') : nsDefault;

                        that._inlineNamespace = new x3dom.NodeNameSpace(nsName, that._nameSpace.doc);

                        var url = that._vf.url.length ? that._vf.url[0] : "";

                        if ((url[0] === '/') || (url.indexOf(":") >= 0))
                        {
                            that._inlineNamespace.setBaseURL(url);
                        }
                        else
                        {
                            that._inlineNamespace.setBaseURL(that._nameSpace.baseURL + url);
                        }

                        //Replace Material before setupTree()
                        that.replaceMaterials(inlScene);

                        newScene = that._inlineNamespace.setupTree(inlScene);

                        that._nameSpace.addSpace(that._inlineNamespace);

                        if(that._vf.nameSpaceName.length != 0)
                        {
                            Array.forEach ( inlScene.childNodes, function (childDomNode)
                            {
                                if(childDomNode instanceof Element)
                                {
                                    setNamespace(that._vf.nameSpaceName, childDomNode, that._vf.mapDEFToID);
                                    that._xmlNode.appendChild(childDomNode);
                                }
                            } );
                        }
                    }
                    else {
                        if (xml && xml.localName) {
                            x3dom.debug.logError('No Scene in ' + xml.localName);
                        } else {
                            x3dom.debug.logError('No Scene in resource');
                        }
                    }

                    // trick to free memory, assigning a property to global object, then deleting it
                    var global = x3dom.getGlobal();

                    if (that._childNodes.length > 0 && that._childNodes[0] && that._childNodes[0]._nameSpace) {
                        that._nameSpace.removeSpace(that._childNodes[0]._nameSpace);
                    }

                    while (that._childNodes.length !== 0) {
                        global['_remover'] = that.removeChild(that._childNodes[0]);
                    }

                    delete global['_remover'];

                    if (newScene)
                    {
                        that.addChild(newScene);

                        that.invalidateVolume();
                        //that.invalidateCache();

                        that._nameSpace.doc.downloadCount -= 1;
                        that._nameSpace.doc.needRender = true;
                        x3dom.debug.logInfo('Inline: added ' + that._vf.url[0] + ' to scene.');

                        // recalc changed scene bounding box twice
                        var theScene = that._nameSpace.doc._scene;

                        if (theScene) {
                            theScene.invalidateVolume();
                            //theScene.invalidateCache();

                            window.setTimeout( function() {
                                that.invalidateVolume();
                                //that.invalidateCache();

                                theScene.updateVolume();
                                that._nameSpace.doc.needRender = true;
                            }, 1000 );
                        }

                        that.appendAPI();
                        that.fireEvents("load");
                    }

                    newScene = null;
                    //nameSpace = null;
                    inlScene = null;
                    xml = null;

                    return xhr;
                };

                if (this._vf.url.length && this._vf.url[0].length)
                {
                    var xhrURI = this._nameSpace.getURL(this._vf.url[0]);

                    xhr.open('GET', xhrURI, true);

                    this._nameSpace.doc.downloadCount += 1;

                    try {
                        //xhr.send(null);
                        x3dom.RequestManager.addRequest(xhr);
                    }
                    catch(ex) {
                        this.fireEvents("error");
                        x3dom.debug.logError(this._vf.url[0] + ": " + ex);
                    }
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

x3dom.registerNodeType(
    "X3DBackgroundNode",
    "EnvironmentalEffects",
    defineClass(x3dom.nodeTypes.X3DBindableNode,
        
        /**
         * Constructor for X3DBackgroundNode
         * @constructs x3dom.nodeTypes.X3DBackgroundNode
         * @x3d 3.3
         * @component EnvironmentalEffects
         * @status full
         * @extends x3dom.nodeTypes.X3DBindableNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc X3DBackgroundNode is the abstract type from which all backgrounds inherit. X3DBackgroundNode is a
         * bindable node that, when bound, defines the panoramic background for the scene.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DBackgroundNode.superClass.call(this, ctx);

            var trans = (ctx && ctx.autoGen) ? 1 : 0;

            /**
             * Cross Origin Mode
             * @var {x3dom.fields.SFString} crossOrigin
             * @memberof x3dom.nodeTypes.X3DBackgroundNode
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'crossOrigin', '');

            /**
             * Color of the ground
             * @var {x3dom.fields.MFColor} groundColor
             * @memberof x3dom.nodeTypes.X3DBackgroundNode
             * @initvalue (0,0,0)
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_MFColor(ctx, 'groundColor', []);

            /**
             * Angle of the ground
             * @var {x3dom.fields.MFFloat} groundAngle
             * @memberof x3dom.nodeTypes.X3DBackgroundNode
             * @initvalue []
             * @range [0, pi]
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'groundAngle', []);

            /**
             * Color of the sky
             * @var {x3dom.fields.MFColor} skyColor
             * @memberof x3dom.nodeTypes.X3DBackgroundNode
             * @initvalue (0,0,0)
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_MFColor(ctx, 'skyColor', [new x3dom.fields.SFColor(0,0,0)]);

            /**
             * Angle of the sky
             * @var {x3dom.fields.MFFloat} skyAngle
             * @memberof x3dom.nodeTypes.X3DBackgroundNode
             * @initvalue []
             * @range [0, pi]
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'skyAngle', []);

            /**
             * Transparency of the background
             * @var {x3dom.fields.SFFloat} transparency
             * @memberof x3dom.nodeTypes.X3DBackgroundNode
             * @initvalue 0/1
             * @range [0,1]
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'transparency', trans);

            this._dirty = true;
        
        },
        {
            getSkyColor: function() {
                return new x3dom.fields.SFColor(0,0,0);
            },
            getTransparency: function() {
                return 0;
            },
            getTexUrl: function() {
                return [];
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DFogNode ### */
x3dom.registerNodeType(
    "X3DFogNode",
    "EnvironmentalEffects",
    defineClass(x3dom.nodeTypes.X3DBindableNode,
        
        /**
         * Constructor for X3DFogNode
         * @constructs x3dom.nodeTypes.X3DFogNode
         * @x3d 3.3
         * @component EnvironmentalEffects
         * @status full
         * @extends x3dom.nodeTypes.X3DBindableNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc X3DFogObject is the abstract type that describes a node that influences the lighting equation
         * through the use of fog semantics.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DFogNode.superClass.call(this, ctx);

            /**
             * Objects located outside the visibilityRange from the viewer are drawn with a constant colour of color.
             * Objects very close to the viewer are blended very little with the fog color.
             * @var {x3dom.fields.SFColor} color
             * @memberof x3dom.nodeTypes.X3DFogNode
             * @initvalue (1,1,1)
             * @range [0,1]
             * @field x3d
             * @instance
             */
            this.addField_SFColor(ctx, 'color', 1, 1, 1);

            /**
             * The fogType field controls how much of the fog colour is blended with the object as a function of
             * distance. If fogType is "LINEAR", the amount of blending is a linear function of the distance, resulting
             * in a depth cueing effect. If fogType is "EXPONENTIAL," an exponential increase in blending is used,
             * resulting in a more natural fog appearance.
             * @var {x3dom.fields.SFString} fogType
             * @memberof x3dom.nodeTypes.X3DFogNode
             * @initvalue "LINEAR"
             * @range {"LINEAR","EXPONENTIAL"}
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'fogType', "LINEAR");

            /**
             * The visibilityRange specifies the distance in length base units (in the local coordinate system) at
             * which objects are totally obscured by the fog. A visibilityRange of 0.0 disables the Fog node.
             * The visibilityRange is affected by the scaling transformations of the Fog node's parents; translations
             * and rotations have no affect on visibilityRange.
             * @var {x3dom.fields.SFFloat} visibilityRange
             * @memberof x3dom.nodeTypes.X3DFogNode
             * @initvalue 0
             * @range [0, -inf]
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'visibilityRange', 0);
        
        },
        {
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Fog ### */
x3dom.registerNodeType(
    "Fog",
    "EnvironmentalEffects",
    defineClass(x3dom.nodeTypes.X3DFogNode,
        
        /**
         * Constructor for Fog
         * @constructs x3dom.nodeTypes.Fog
         * @x3d 3.3
         * @component EnvironmentalEffects
         * @status experimental
         * @extends x3dom.nodeTypes.X3DFogNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Fog node provides a way to simulate atmospheric effects by blending objects with the colour
         * specified by the color field based on the distances of the various objects from the viewer. The distances
         * are calculated in the coordinate space of the Fog node.
         */
        function (ctx) {
            x3dom.nodeTypes.Fog.superClass.call(this, ctx);

        },
        {
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Background ### */
x3dom.registerNodeType(
    "Background",
    "EnvironmentalEffects",
    defineClass(x3dom.nodeTypes.X3DBackgroundNode,
        
        /**
         * Constructor for Background
         * @constructs x3dom.nodeTypes.Background
         * @x3d 3.3
         * @component EnvironmentalEffects
         * @status full
         * @extends x3dom.nodeTypes.X3DBackgroundNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc A background node that uses six static images to compose the backdrop.  For the backUrl,
         * bottomUrl, frontUrl, leftUrl, rightUrl, topUrl fields, browsers shall support the JPEG and PNG
         * (see ISO/IEC 15948) image file formats.
         */
        function (ctx) {
            x3dom.nodeTypes.Background.superClass.call(this, ctx);

            /**
             *
             * @var {x3dom.fields.MFString} backUrl
             * @memberof x3dom.nodeTypes.Background
             * @initvalue []
             * @range [URI]
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'backUrl', []);

            /**
             *
             * @var {x3dom.fields.MFString} bottomUrl
             * @memberof x3dom.nodeTypes.Background
             * @initvalue []
             * @range [URI]
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'bottomUrl', []);

            /**
             *
             * @var {x3dom.fields.MFString} frontUrl
             * @memberof x3dom.nodeTypes.Background
             * @initvalue []
             * @range [URI]
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'frontUrl', []);

            /**
             *
             * @var {x3dom.fields.MFString} leftUrl
             * @memberof x3dom.nodeTypes.Background
             * @initvalue []
             * @range [URI]
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'leftUrl', []);

            /**
             *
             * @var {x3dom.fields.MFString} rightUrl
             * @memberof x3dom.nodeTypes.Background
             * @initvalue []
             * @range [URI]
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'rightUrl', []);

            /**
             *
             * @var {x3dom.fields.MFString} topUrl
             * @memberof x3dom.nodeTypes.Background
             * @initvalue []
             * @range [URI]
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'topUrl', []);
			
			/**
             *
             * @var {x3dom.fields.MFString} scaling
             * @memberof x3dom.nodeTypes.Background
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'scaling', false);
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName.indexOf("Url") > 0 || fieldName == "transparency" ||
                    fieldName.search("sky") >= 0 || fieldName.search("ground") >= 0) {
                    this._dirty = true;
                }
                else if (fieldName.indexOf("bind") >= 0) {
                    this.bind(this._vf.bind);
                }
            },

            getSkyColor: function() {
                return this._vf.skyColor;
            },

            getGroundColor: function() {
                return this._vf.groundColor;
            },

            getTransparency: function() {
                return this._vf.transparency;
            },

            getTexUrl: function() {
                return [
                    this._nameSpace.getURL(this._vf.backUrl[0]),
                    this._nameSpace.getURL(this._vf.frontUrl[0]),
                    this._nameSpace.getURL(this._vf.bottomUrl[0]),
                    this._nameSpace.getURL(this._vf.topUrl[0]),
                    this._nameSpace.getURL(this._vf.leftUrl[0]),
                    this._nameSpace.getURL(this._vf.rightUrl[0])
                ];
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DEnvironmentNode ### */
x3dom.registerNodeType(
    "X3DEnvironmentNode",
    "EnvironmentalEffects",
    defineClass(x3dom.nodeTypes.X3DBindableNode,
        
        /**
         * Constructor for X3DEnvironmentNode
         * @constructs x3dom.nodeTypes.X3DEnvironmentNode
         * @x3d x.x
         * @component EnvironmentalEffects
         * @status full
         * @extends x3dom.nodeTypes.X3DBindableNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Base class for environment nodes
         */
        function (ctx) {
            x3dom.nodeTypes.X3DEnvironmentNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Environment ### */
x3dom.registerNodeType(
    "Environment",
    "EnvironmentalEffects",
    defineClass(x3dom.nodeTypes.X3DEnvironmentNode,
        
        /**
         * Constructor for Environment
         * @constructs x3dom.nodeTypes.Environment
         * @x3d x.x
         * @component EnvironmentalEffects
         * @status full
         * @extends x3dom.nodeTypes.X3DEnvironmentNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Bindable node to setup rendering and culling parameters
         */
        function (ctx) {
            x3dom.nodeTypes.Environment.superClass.call(this, ctx);

            /**
             * If TRUE, transparent objects are sorted from back to front (allows explicitly disabling sorting)
             * @var {x3dom.fields.SFBool} sortTrans
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'sortTrans', true);

            /**
             * Transparent objects like glass do not throw much shadow, enable this IR convenience flag with TRUE
             * @var {x3dom.fields.SFBool} shadowExcludeTransparentObjects
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'shadowExcludeTransparentObjects', false);

            /**
             * The gamma correction to apply by default, see lighting and gamma tutorial
             * @var {x3dom.fields.SFString} gammaCorrectionDefault
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue "linear"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'gammaCorrectionDefault', "linear");

            // boolean flags for feature (de)activation

           /**
             * If TRUE, objects outside the viewing frustum are ignored
             * @var {x3dom.fields.SFBool} frustumCulling
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'frustumCulling', true);

            /**
             * If TRUE, objects smaller than the threshold below are ignored
             * @var {x3dom.fields.SFBool} smallFeatureCulling
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'smallFeatureCulling', false);

            /**
             * Objects smaller than the threshold below are ignored
             * @var {x3dom.fields.SFFloat} smallFeatureThreshold
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'smallFeatureThreshold', 1.0);

            // defaults can be >0 since only used upon activation

            /**
             * If TRUE and occlusion culling supported, objects occluding less than the threshold below are ignored
             * @var {x3dom.fields.SFBool} occlusionCulling
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'occlusionCulling', false);

            /**
             * Objects occluding less than the threshold below are ignored
             * @var {x3dom.fields.SFFloat} occlusionVisibilityThreshold
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue 0.0
             * @range [0,1]
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'occlusionVisibilityThreshold', 0.0);

            // previously was scaleRenderedIdsOnMove; percentage of objects to be rendered, in [0,1]

            /**
             * If TRUE and occlusion culling supported, only threshold fraction of objects, sorted by their screen
             * space coverage, are rendered
             * @var {x3dom.fields.SFBool} lowPriorityCulling
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'lowPriorityCulling', false);

            /**
             * Only threshold fraction of objects, sorted by their screen space coverage, are rendered
             * @var {x3dom.fields.SFFloat} lowPriorityThreshold
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue 1.0
             * @range [0,1]
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'lowPriorityThreshold', 1.0);     // 1.0 means everything is rendered

            // shape tesselation is lowered as long as resulting error is lower than threshold

            /**
             * If TRUE, shape tesselation is lowered as long as resulting error is lower than threshold
             * @var {x3dom.fields.SFBool} tessellationDetailCulling
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'tessellationDetailCulling', false);

            /**
             * Shape tesselation is lowered as long as resulting error is lower than threshold
             * @var {x3dom.fields.SFFloat} tessellationErrorThreshold
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue 0.0
             * @range [0,1]
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'tessellationErrorThreshold', 0.0);

            /**
             * Experimental: If true ARC adjusts rendering parameters
             * @var {x3dom.fields.SFBool} enableARC
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'enableARC', false);

            /**
             * Experimental: Define minimal target frame-rate for static moments and quality-speed trade-off
             * @var {x3dom.fields.SFFloat} minFrameRate
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue 1.0
             * @range [1,inf]
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'minFrameRate',  1.0);

            /**
             * Experimental: Define maximal target frame-rate for dynamic moments and quality-speed trade-off
             * @var {x3dom.fields.SFFloat} maxFrameRate
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue 62.5
             * @range [1,inf]
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'maxFrameRate', 62.5);

            // 4 exp. factors for controlling speed-performance trade-off
            // factors could be in [0, 1] (and not evaluated if -1)

            /**
             * Experimenal: Factor of user data for controlling speed-performance trade-off
             * @var {x3dom.fields.SFFloat} userDataFactor
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue -1
             * @range [0,1] or -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'userDataFactor', -1);

            /**
             * Experimenal: Factor of small feature culling for controlling speed-performance trade-off
             * @var {x3dom.fields.SFFloat} smallFeatureFactor
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue -1
             * @range [0,1] or -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'smallFeatureFactor', -1);

            /**
             * Experimenal: Factor of occlusion culling for controlling speed-performance trade-off
             * @var {x3dom.fields.SFFloat} occlusionVisibilityFactor
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue -1
             * @range [0,1] or -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'occlusionVisibilityFactor', -1);

            /**
             * Experimenal: Factor of low priority culling for controlling speed-performance trade-off
             * @var {x3dom.fields.SFFloat} lowPriorityFactor
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue -1
             * @range [0,1] or -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'lowPriorityFactor', -1);

            /**
             * Experimenal: Factor of tesselation error for controlling speed-performance trade-off
             * @var {x3dom.fields.SFFloat} tessellationErrorFactor
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue -1
             * @range [0,1] or -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'tessellationErrorFactor', -1);

            /**
             * Flag to enable Screen Space Ambient Occlusion
             * @var {x3dom.fields.SFBool} SSAO
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue "false"
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'SSAO', false);

            /**
             * Value that determines the radius in which the SSAO is sampled in world space
             * @var {x3dom.fields.SFFloat} SSAOradius
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue "4"
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'SSAOradius',0.7);

            /**
             * Value that determines the amount of contribution of SSAO (from 0 to 1)
             * @var {x3dom.fields.SFFloat} SSAOamount
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue "1.0"
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'SSAOamount',0.3);

            /**
             * Value that determines the size of the random texture used for sparse sampling of SSAO
             * @var {x3dom.fields.SFFloat} SSAOrandomTextureSize
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue "4"
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'SSAOrandomTextureSize',4);

            /**
             * Value that determines the maximum depth difference for the SSAO blurring pass.
             * Pixels with higher depth difference to the filer kernel center are not incorporated into the average.
             * @var {x3dom.fields.SFFloat} SSAOblurDepthTreshold
             * @memberof x3dom.nodeTypes.Environment
             * @initvalue "5"
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'SSAOblurDepthTreshold',1);

            this._validGammaCorrectionTypes = [
                "none", "fastlinear", "linear"
            ];

            // init internal stuff (but should be called each frame)
            this.checkSanity();
        
        },
        {
            checkSanity: function()
            {
                var checkParam = function(flag, value, defaultOn, defaultOff)
                {
                    if(flag && (value == defaultOff))
                        return defaultOn;

                    if(!flag && (value != defaultOff))
                        return defaultOff;
                    return value;
                };

                this._smallFeatureThreshold = checkParam(this._vf.smallFeatureCulling,
                    this._vf.smallFeatureThreshold, 10, 0); // cull objects < 10 px
                this._lowPriorityThreshold = checkParam(this._vf.lowPriorityCulling,
                    this._vf.lowPriorityThreshold, 0.5, 1);  // 1 means 100% visible
                this._occlusionVisibilityThreshold = checkParam(this._vf.occlusionCulling,
                    this._vf.occlusionVisibilityThreshold, 1, 0);
                this._tessellationErrorThreshold = checkParam(this._vf.tessellationDetailCulling,
                    this._vf.tessellationErrorThreshold, 1, 0);

                var checkGamma = function(field, that) {
                    field = field.toLowerCase();

                    if (that._validGammaCorrectionTypes.indexOf(field) > -1) {
                        return field;
                    }
                    else {
                        x3dom.debug.logWarning(field + " gammaCorrectionDefault may only be linear (default), fastLinear, or none");
                        return that._validGammaCorrectionTypes[0];
                    }
                };

                this._vf.gammaCorrectionDefault = checkGamma(this._vf.gammaCorrectionDefault, this);
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DViewpointNode ### */
x3dom.registerNodeType(
    "X3DViewpointNode",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DBindableNode,
        
        /**
         * Constructor for X3DViewpointNode
         * @constructs x3dom.nodeTypes.X3DViewpointNode
         * @x3d 3.3
         * @component Navigation
         * @status experimental
         * @extends x3dom.nodeTypes.X3DBindableNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc A node of node type X3DViewpointNode defines a specific location in the local coordinate system from which the user may view the scene.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DViewpointNode.superClass.call(this, ctx);

            // attach some convenience accessor methods to dom/xml node
            if (ctx && ctx.xmlNode) {
                var domNode = ctx.xmlNode;

                if (!domNode.resetView && !domNode.getFieldOfView &&
                    !domNode.getNear && !domNode.getFar)
                {
                    domNode.resetView = function() {
                        var that = this._x3domNode;

                        that.resetView();
                        that._nameSpace.doc.needRender = true;
                    };

                    domNode.getFieldOfView = function() {
                        return this._x3domNode.getFieldOfView();
                    };

                    domNode.getNear = function() {
                        return this._x3domNode.getNear();
                    };

                    domNode.getFar = function() {
                        return this._x3domNode.getFar();
                    };
                }
            }
        
        },
        {
            activate: function (prev) {
                var viewarea = this._nameSpace.doc._viewarea;
                if (prev && this._bindAnimation) {
                    viewarea.animateTo(this, prev._autoGen ? null : prev);
                }
                viewarea._needNavigationMatrixUpdate = true;

                x3dom.nodeTypes.X3DBindableNode.prototype.activate.call(this, prev);
                //x3dom.debug.logInfo ('activate ViewBindable ' + this._DEF + '/' + this._vf.description);
            },

            deactivate: function (prev) {
                x3dom.nodeTypes.X3DBindableNode.prototype.deactivate.call(this, prev);
                //x3dom.debug.logInfo ('deactivate ViewBindable ' + this._DEF + '/' + this._vf.description);
            },

            getTransformation: function() {
                return this.getCurrentTransform();
            },

            getCenterOfRotation: function() {
                return new x3dom.fields.SFVec3f(0, 0, 0);
            },

            setCenterOfRotation: function(cor) {
                this._vf.centerOfRotation.setValues(cor);   // method overwritten by Viewfrustum
            },

            getFieldOfView: function() {
                return 1.57079633;
            },

            /**
             * Sets the (local) view matrix
             * @param newView
             */
            setView: function(newView) {
                var mat = this.getCurrentTransform();
                this._viewMatrix = newView.mult(mat);
            },

            /**
             * Sets an absolute view matrix in world coordinates
             * @param newView
             */
            setViewAbsolute: function(newView)
            {
                this._viewMatrix = newView;
            },

            setProjectionMatrix: function(matrix)
            {

            },

            resetView: function() {
                // see derived class
            },

            getNear: function() {
                return 0.1;
            },

            getFar: function() {
                return 10000;
            },

            getImgPlaneHeightAtDistOne: function() {
                return 2.0;
            },

            getViewMatrix: function() {
                return null;
            },

            getProjectionMatrix: function(aspect) {
                return null;
            },

            setZoom: function( value ) {
                
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Viewpoint ### */
x3dom.registerNodeType(
    "Viewpoint",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DViewpointNode,
        
        /**
         * Constructor for Viewpoint
         * @constructs x3dom.nodeTypes.Viewpoint
         * @x3d 3.3
         * @component Navigation
         * @status experimental
         * @extends x3dom.nodeTypes.X3DViewpointNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Viewpoint provides a specific location and direction where the user may view the scene.
         * The principalPoint extention allows to set asymmetric frustums.
         */
        function (ctx) {
            x3dom.nodeTypes.Viewpoint.superClass.call(this, ctx);


            /**
             * Preferred minimum viewing angle from this viewpoint in radians.
             * Small field of view roughly corresponds to a telephoto lens, large field of view roughly corresponds to a wide-angle lens.
             * Hint: modifying Viewpoint distance to object may be better for zooming.
             * Warning: fieldOfView may not be correct for different window sizes and aspect ratios.
             * Interchange profile hint: this field may be ignored.
             * @var {x3dom.fields.SFFloat} fieldOfView
             * @range [0, pi]
             * @memberof x3dom.nodeTypes.Viewpoint
             * @initvalue 0.785398
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'fieldOfView', 0.785398);

            /**
             * The position fields of the Viewpoint node specifies a relative location in the local coordinate system. Position is relative to the coordinate system's origin (0,0,0),
             * @var {x3dom.fields.SFVec3f} position
             * @memberof x3dom.nodeTypes.Viewpoint
             * @initvalue 0,0,10
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'position', 0, 0, 10);

            /**
             * The orientation fields of the Viewpoint node specifies relative orientation to the default orientation.
             * @var {x3dom.fields.SFRotation} orientation
             * @memberof x3dom.nodeTypes.Viewpoint
             * @initvalue 0,0,0,1
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'orientation', 0, 0, 0, 1);

            /**
             * The centerOfRotation field specifies a center about which to rotate the user's eyepoint when in EXAMINE mode.
             * @var {x3dom.fields.SFVec3f} centerOfRotation
             * @memberof x3dom.nodeTypes.Viewpoint
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'centerOfRotation', 0, 0, 0);

            /**
             * Specifies the near plane.
             * @var {x3dom.fields.SFFloat} zNear
             * @range -1 or [0, inf]
             * @memberof x3dom.nodeTypes.Viewpoint
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zNear', -1); //0.1);

            /**
             * Specifies the far plane.
             * @var {x3dom.fields.SFFloat} zFar
             * @range -1 or [0, inf]
             * @memberof x3dom.nodeTypes.Viewpoint
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zFar', -1);  //100000);

            //this._viewMatrix = this._vf.orientation.toMatrix().transpose().
            //    mult(x3dom.fields.SFMatrix4f.translation(this._vf.position.negate()));
            this._viewMatrix = x3dom.fields.SFMatrix4f.translation(this._vf.position).
                mult(this._vf.orientation.toMatrix()).inverse();

            this._projMatrix = null;
            this._lastAspect = 1.0;

            // z-ratio: a value around 5000 would be better...
            this._zRatio = 10000;
            this._zNear = this._vf.zNear;
            this._zFar = this._vf.zFar;

            // special stuff...
            this._imgPlaneHeightAtDistOne = 2.0 * Math.tan(this._vf.fieldOfView / 2.0);
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "position" || fieldName == "orientation") {
                    this.resetView();
                }
                else if (fieldName == "fieldOfView" ||
                    fieldName == "zNear" || fieldName == "zFar") {
                    this._projMatrix = null;   // only trigger refresh
                    this._zNear = this._vf.zNear;
                    this._zFar = this._vf.zFar;
                    this._imgPlaneHeightAtDistOne = 2.0 * Math.tan(this._vf.fieldOfView / 2.0);
                }
                else if (fieldName.indexOf("bind") >= 0) {
                    // FIXME; call parent.fieldChanged();
                    this.bind(this._vf.bind);
                }
            },

            setProjectionMatrix: function(matrix)
            {
                this._projMatrix = matrix;
            },

            getCenterOfRotation: function() {
                return this.getCurrentTransform().multMatrixPnt(this._vf.centerOfRotation);
            },

            getViewMatrix: function() {
                return this._viewMatrix;
            },

            getFieldOfView: function() {
                return this._vf.fieldOfView;
            },

            resetView: function() {
                this._viewMatrix = x3dom.fields.SFMatrix4f.translation(this._vf.position).
                    mult(this._vf.orientation.toMatrix()).inverse();

				//Reset navigation helpers of the viewarea
                if (this._vf.isActive && this._nameSpace && this._nameSpace.doc._viewarea) {
                    this._nameSpace.doc._viewarea.resetNavHelpers();
                }
            },

            getNear: function() {
                return this._zNear;
            },

            getFar: function() {
                return this._zFar;
            },

            getImgPlaneHeightAtDistOne: function() {
                return this._imgPlaneHeightAtDistOne;
            },

            getProjectionMatrix: function(aspect)
            {
                var fovy = this._vf.fieldOfView;
                var zfar = this._vf.zFar;
                var znear = this._vf.zNear;

                if (znear <= 0 || zfar <= 0)
                {
                    var nearScale = 0.8, farScale = 1.2;
                    var viewarea = this._nameSpace.doc._viewarea;
                    var scene = viewarea._scene;

                    // Doesn't work if called e.g. from RenderedTexture with different sub-scene
                    var min = x3dom.fields.SFVec3f.copy(scene._lastMin);
                    var max = x3dom.fields.SFVec3f.copy(scene._lastMax);

                    var dia = max.subtract(min);
                    var sRad = dia.length() / 2;

                    var mat = viewarea.getViewMatrix().inverse();
                    var vp = mat.e3();

                    // account for scales around the viewpoint
                    var translation = new x3dom.fields.SFVec3f(0,0,0),
                        scaleFactor = new x3dom.fields.SFVec3f(1,1,1);
                    var rotation = new x3dom.fields.Quaternion(0,0,1,0),
                        scaleOrientation = new x3dom.fields.Quaternion(0,0,1,0);

                    // unfortunately, decompose is a rather expensive operation
                    mat.getTransform(translation, rotation, scaleFactor, scaleOrientation);

                    var minScal = scaleFactor.x, maxScal = scaleFactor.x;

                    if (maxScal < scaleFactor.y) maxScal = scaleFactor.y;
                    if (minScal > scaleFactor.y) minScal = scaleFactor.y;
                    if (maxScal < scaleFactor.z) maxScal = scaleFactor.z;
                    if (minScal > scaleFactor.z) minScal = scaleFactor.z;

                    if (maxScal > 1)
                        nearScale /= maxScal;
                    else if (minScal > x3dom.fields.Eps && minScal < 1)
                        farScale /= minScal;
                    // near/far scale adaption done

                    var sCenter = min.add(dia.multiply(0.5));
                    var vDist = (vp.subtract(sCenter)).length();

                    if (sRad) {
                        if (vDist > sRad)
                            znear = (vDist - sRad) * nearScale;  // Camera outside scene
                        else
                            znear = 0;                           // Camera inside scene

                        zfar = (vDist + sRad) * farScale;
                    }
                    else {
                        znear = 0.1;
                        zfar = 100000;
                    }

                    var zNearLimit = zfar / this._zRatio;
                    znear = Math.max(znear, Math.max(x3dom.fields.Eps, zNearLimit));

                    if (zfar > this._vf.zNear && this._vf.zNear > 0)
                        znear = this._vf.zNear;
                    if (this._vf.zFar > znear)
                        zfar = this._vf.zFar;

                    if (zfar <= znear)
                        zfar = znear + 1;
                    //x3dom.debug.logInfo("near: " + znear + " -> far:" + zfar);
                }

                if (this._projMatrix == null)
                {
                    this._projMatrix = x3dom.fields.SFMatrix4f.perspective(fovy, aspect, znear, zfar);
                }
                else if (this._zNear != znear || this._zFar != zfar)
                {
                    var div = znear - zfar;
                    this._projMatrix._22 = (znear + zfar) / div;
                    this._projMatrix._23 = 2 * znear * zfar / div;
                }
                else if (this._lastAspect != aspect)
                {
                    this._projMatrix._00 = (1 / Math.tan(fovy / 2)) / aspect;
                    this._lastAspect = aspect;
                }

                // also needed for being able to ask for near and far
                this._zNear = znear;
                this._zFar = zfar;

                return this._projMatrix;
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### OrthoViewpoint ### */
x3dom.registerNodeType(
    "OrthoViewpoint",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DViewpointNode,
        
        /**
         * Constructor for OrthoViewpoint
         * @constructs x3dom.nodeTypes.OrthoViewpoint
         * @x3d 3.3
         * @component Navigation
         * @status experimental
         * @extends x3dom.nodeTypes.X3DViewpointNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The OrthoViewpoint node defines a viewpoint that provides an orthographic view of the scene.
         * An orthographic view is one in which all projectors are parallel to the projector from centerOfRotation to position.
         */
        function (ctx) {
            x3dom.nodeTypes.OrthoViewpoint.superClass.call(this, ctx);


            /**
             * The fieldOfView field specifies minimum and maximum extents of the view in units of the local coordinate system
             * @var {x3dom.fields.MFFloat} fieldOfView
             * @memberof x3dom.nodeTypes.OrthoViewpoint
             * @initvalue [-1,-1,1,1]
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'fieldOfView', [-1, -1, 1, 1]);

            /**
             * Position (x, y, z in meters) relative to local coordinate system.
             * @var {x3dom.fields.SFVec3f} position
             * @memberof x3dom.nodeTypes.OrthoViewpoint
             * @initvalue 0,0,10
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'position', 0, 0, 10);

            /**
             * Rotation (axis, angle in radians) of Viewpoint, relative to default -Z axis direction in local coordinate system.
             * Hint: this is orientation _change_ from default direction (0 0 -1).
             * Hint: complex rotations can be accomplished axis-by-axis using parent Transforms.
             * @var {x3dom.fields.SFRotation} orientation
             * @range [-1, 1] or [-inf, inf]
             * @memberof x3dom.nodeTypes.OrthoViewpoint
             * @initvalue 0,0,0,1
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'orientation', 0, 0, 0, 1);

            /**
             * centerOfRotation point relates to NavigationInfo EXAMINE mode.
             * @var {x3dom.fields.SFVec3f} centerOfRotation
             * @memberof x3dom.nodeTypes.OrthoViewpoint
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'centerOfRotation', 0, 0, 0);

            /**
             * z-near position; used for clipping
             * @var {x3dom.fields.SFFloat} zNear
             * @memberof x3dom.nodeTypes.OrthoViewpoint
             * @initvalue 0.1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zNear', -1);

            /**
             * z-far position; used for clipping
             * @var {x3dom.fields.SFFloat} zFar
             * @memberof x3dom.nodeTypes.OrthoViewpoint
             * @initvalue 10000
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zFar', -1);

            this._viewMatrix = null;
            this._projMatrix = null;
            this._lastAspect = 1.0;
			
			this._zRatio = 10000;
            this._zNear = this._vf.zNear;
            this._zFar = this._vf.zFar;
			this._fieldOfView = this._vf.fieldOfView.slice(0); 

            this.resetView();
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "position" || fieldName == "orientation") {
                    this.resetView();
                }
                else if(fieldName == "fieldOfView")
                {
                    this._fieldOfView = this._vf.fieldOfView;
                    this._projMatrix = null;
                }
                else if (fieldName == "zNear" || fieldName == "zFar") {
                    this._projMatrix = null;   // trigger refresh
                    this.resetView();
                }
                else if (fieldName.indexOf("bind") >= 0) {
                    this.bind(this._vf.bind);
                }
            },

            getCenterOfRotation: function() {
                return this.getCurrentTransform().multMatrixPnt(this._vf.centerOfRotation);
            },

            getViewMatrix: function() {
                return this._viewMatrix;
            },

            resetView: function() {
                var offset = x3dom.fields.SFMatrix4f.translation(new x3dom.fields.SFVec3f(
                        (this._vf.fieldOfView[0] + this._vf.fieldOfView[2]) / 2,
                        (this._vf.fieldOfView[1] + this._vf.fieldOfView[3]) / 2, 0));

                this._viewMatrix = x3dom.fields.SFMatrix4f.translation(this._vf.position).
                    mult(this._vf.orientation.toMatrix());
                this._viewMatrix = this._viewMatrix.inverse();
				
				this._projMatrix = null;
				
				//Reset navigation helpers of the viewarea
                if (this._vf.isActive && this._nameSpace && this._nameSpace.doc._viewarea) {
                    this._nameSpace.doc._viewarea.resetNavHelpers();
                }
            },

            getNear: function() {
                return this._vf.zNear;
            },

            getFar: function() {
                return this._vf.zFar;
            },
            
            getFieldOfView: function() {
                return 0.785;
            },

            setZoom: function( value ) {
                this._fieldOfView[0] = -value;
                this._fieldOfView[1] = -value;
                this._fieldOfView[2] =  value;
                this._fieldOfView[3] =  value;

                this._projMatrix = null;   // trigger refresh
                //this.resetView();
            },
            
            getZoom: function( value ) {
                return this._fieldOfView;
            },

            getProjectionMatrix: function(aspect)
            {
				var fov = this.getFieldOfView();
                var zfar = this._vf.zFar;
                var znear = this._vf.zNear;

                if (znear <= 0 || zfar <= 0)
                {
                    var scene = this._nameSpace.doc._viewarea._scene;
                    var min = x3dom.fields.SFVec3f.copy(scene._lastMin);
                    var max = x3dom.fields.SFVec3f.copy(scene._lastMax);
									
                    var dia = max.subtract(min);					
					var tanfov2 = Math.tan(fov / 2.0);
					
					var dist1 = (dia.y / 2.0) / tanfov2 + dia.z;
					var dist2 = (dia.x / 2.0) / tanfov2 + dia.z;
					
					znear = 0.00001;
					zfar = (dist1 > dist2) ? dist1 * 4 : dist2 * 4;
                }
				
                if (this._projMatrix == null || this._lastAspect != aspect ||
				    this._zNear != znear || this._zFar != zfar)
                {
                    var near = this._zNear = znear;
					var far  = this._zFar = zfar; 

                    var left   = this._fieldOfView[0];
                    var bottom = this._fieldOfView[1];
                    var right  = this._fieldOfView[2];
                    var top    = this._fieldOfView[3];

                    this._projMatrix = x3dom.fields.SFMatrix4f.ortho(left, right, bottom, top, near, far, aspect);
                }
                
                this._lastAspect = aspect;

                return this._projMatrix;
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Viewfrustum ### */
x3dom.registerNodeType(
    "Viewfrustum",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DViewpointNode,
        
        /**
         * Constructor for Viewfrustum
         * @constructs x3dom.nodeTypes.Viewfrustum
         * @x3d x.x
         * @component Navigation
         * @extends x3dom.nodeTypes.X3DViewpointNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Viewfrustum node allows to define a camera position and projection utilizing a standard OpenGL projection/modelview pair.
         */
        function (ctx) {
            x3dom.nodeTypes.Viewfrustum.superClass.call(this, ctx);


            /**
             * Camera modelview matrix
             * @var {x3dom.fields.SFMatrix4f} modelview
             * @memberof x3dom.nodeTypes.Viewfrustum
             * @initvalue 1,0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFMatrix4f(ctx, 'modelview',
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);

            /**
             * Camera projection matrix
             * @var {x3dom.fields.SFMatrix4f} projection
             * @memberof x3dom.nodeTypes.Viewfrustum
             * @initvalue 1,0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFMatrix4f(ctx, 'projection',
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);

            this._viewMatrix = this._vf.modelview.transpose().inverse();
            this._projMatrix = this._vf.projection.transpose();

            this._centerOfRotation = new x3dom.fields.SFVec3f(0, 0, 0);
            // FIXME; derive near/far from current matrix, if requested!
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "modelview") {
                    this.resetView();
                }
                else if (fieldName == "projection") {
                    this._projMatrix = this._vf.projection.transpose();
                }
                else if (fieldName.indexOf("bind") >= 0) {
                    this.bind(this._vf.bind);
                }
            },

            getCenterOfRotation: function() {
                return this.getCurrentTransform().multMatrixPnt(this._centerOfRotation);  // this field is only a little helper for examine mode
            },

            setCenterOfRotation: function(cor) {
                this._centerOfRotation.setValues(cor);   // update internal helper field
            },

            getViewMatrix: function() {
                return this._viewMatrix;
            },

            getFieldOfView: function() {
                return (2.0 * Math.atan(1.0 / this._projMatrix._11));
            },

            getImgPlaneHeightAtDistOne: function() {
                return 2.0 / this._projMatrix._11;
            },

            resetView: function() {
                this._viewMatrix = this._vf.modelview.transpose().inverse();
                this._centerOfRotation = new x3dom.fields.SFVec3f(0, 0, 0);       // reset helper, too
            },

            getProjectionMatrix: function(aspect) {
                return this._projMatrix;
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DNavigationInfoNode ### */
x3dom.registerNodeType(
    "X3DNavigationInfoNode",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DBindableNode,
        
        /**
         * Constructor for X3DNavigationInfoNode
         * @constructs x3dom.nodeTypes.X3DNavigationInfoNode
         * @x3d x.x
         * @component Navigation
         * @extends x3dom.nodeTypes.X3DBindableNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         */
        function (ctx) {
            x3dom.nodeTypes.X3DNavigationInfoNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### NavigationInfo ### */
x3dom.registerNodeType(
    "NavigationInfo",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DNavigationInfoNode,
        
        /**
         * Constructor for NavigationInfo
         * @constructs x3dom.nodeTypes.NavigationInfo
         * @x3d 3.3
         * @component Navigation
         * @status experimental
         * @extends x3dom.nodeTypes.X3DNavigationInfoNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc NavigationInfo describes the viewing model and physical characteristics of the viewer's avatar.
         * Hint: for inspection of simple objects, usability often improves with type='EXAMINE' 'ANY' Hint: NavigationInfo types ''WALK' 'FLY'' support camera-to-object collision detection.
         * Background, Fog, NavigationInfo, TextureBackground and Viewpoint are bindable nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.NavigationInfo.superClass.call(this, ctx);


            /**
             * Enable/disable directional light that always points in the direction the user is looking.
             * @var {x3dom.fields.SFBool} headlight
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'headlight', true);

            /**
             * defines the navigation type
             * @var {x3dom.fields.MFString} type
             * @range {"ANY","WALK","EXAMINE","FLY","LOOKAT","NONE","EXPLORE",...}
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue ["EXAMINE","ANY"]
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'type', ["EXAMINE","ANY"]);

            /**
             * Specifies the view angle and height for helicopter mode and min/max rotation angle for turntable in ]0, PI[, starting from +y (0) down to -y (PI)
             * @var {x3dom.fields.MFFloat} typeParams
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue [-0.4,60,0.05,2.8]
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'typeParams', [-0.4, 60, 0.05, 2.8]);

            /**
             * allows restricting examine and turntable navigation, overrides mouse buttons (useful for special viewers)
             * @range [all, pan, zoom, rotate, none]
             * @var {x3dom.fields.SFString} explorationMode
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue 'all'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'explorationMode', 'all');
            // TODO; use avatarSize + visibilityLimit for projection matrix (near/far)

            /**
             * avatarSize triplet values are:
             * (a) collision distance between user and geometry (near culling plane of the view frustrum)
             * (b) viewer height above terrain
             * (c) tallest height viewer can WALK over.
             * Hint: keep (avatarSize.CollisionDistance / visibilityLimit) less then; 10,000 to avoid aliasing artifacts (i.e. polygon 'tearing').
             * Interchange profile hint: this field may be ignored.
             * @var {x3dom.fields.MFFloat} avatarSize
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue [0.25,1.6,0.75]
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'avatarSize', [0.25, 1.6, 0.75]);

            /**
             * Geometry beyond the visibilityLimit may not be rendered (far culling plane of the view frustrum).
             * visibilityLimit=0.0 indicates an infinite visibility limit.
             * Hint: keep visibilityLimit greater than zero.
             * Hint: keep (avatarSize.CollisionDistance / visibilityLimit) less than 10,000 to avoid aliasing artifacts (i.e. polygon 'tearing').
             * Interchange profile hint: this field may be ignored.
             * @var {x3dom.fields.SFFloat} visibilityLimit
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue 0.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'visibilityLimit', 0.0);

            /**
             * Default rate at which viewer travels through scene, meters/second.
             * Warning: default 1 m/s usually seems slow for ordinary navigation.
             * Interchange profile hint: this field may be ignored.
             * @var {x3dom.fields.SFFloat} speed
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'speed', 1.0);
            // for 'jumping' between viewpoints (bind transition time)

            /**
             * The transitionTime field specifies the duration of any viewpoint transition
             * @var {x3dom.fields.SFTime} transitionTime
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'transitionTime', 1.0);

            /**
             * Specifies the transition mode.
             * @var {x3dom.fields.MFString} transitionType
             * @range [LINEAR, TELEPORT, ANIMATE, ...]
             * @memberof x3dom.nodeTypes.NavigationInfo
             * @initvalue ["LINEAR"]
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'transitionType', ["LINEAR"]);

            this._validTypes = [
                "none", "examine", "turntable",
                "fly", "freefly", "lookat", "lookaround",
                "walk", "game", "helicopter", "any"
            ];
            
            this._typeMapping = {
              "default":x3dom.DefaultNavigation,
              "turntable":x3dom.TurntableNavigation  
            };
            
            this._heliUpdated = false;

            var type = this.setType(this.getType());
            x3dom.debug.logInfo("NavType: " + type);
        
        },
        {
            fieldChanged: function(fieldName) {
                if (fieldName == "typeParams") {
                    this._heliUpdated = false;
                }
                else if (fieldName == "type") {
                    this.setType(this.getType());
                }
            },

            setType: function(type, viewarea) {
                var navType = this.checkType(type.toLowerCase());
                var oldType = this.checkType(this.getType());

                if(oldType !== navType || this._impl == null){
                    if(this._typeMapping[navType] == null)
                        this._impl = new this._typeMapping['default'](this);    
                    else
                        this._impl = new this._typeMapping[navType](this);                    
                    
                    switch (navType) {
                        case 'game':
                            if (viewarea)
                                viewarea.initMouseState();
                            else
                                this._nameSpace.doc._viewarea.initMouseState();
                            break;
                        case 'helicopter':
                            this._heliUpdated = false;
                            break;
                        case "turntable":
                            if (viewarea) {
                                viewarea.initMouseState();
                            }
                            else if(this._nameSpace.doc._viewarea){
                                this._nameSpace.doc._viewarea.initMouseState();
                            }
                            break;
                        default:
                            break;
                    }
                    if (this._nameSpace.doc._viewarea)
                        this._impl.init(this._nameSpace.doc._viewarea, false);
                }

                this._vf.type[0] = navType;
                x3dom.debug.logInfo("Switch to " + navType + " mode.");
            },

            getType: function() {
                var type = this._vf.type[0].toLowerCase();
                // FIXME; the following if's aren't nice!
                if (type.length <= 1)
                    type = "none";
                else if (type == "any")
                    type = "examine";
                return type;
            },

            getTypeParams: function() {
                var length = this._vf.typeParams.length;

                var theta  = (length >= 1) ? this._vf.typeParams[0] : -0.4;
                var height = (length >= 2) ? this._vf.typeParams[1] : 60.0;
                var minAngle = (length >= 3) ? this._vf.typeParams[2] : x3dom.fields.Eps;
                var maxAngle = (length >= 4) ? this._vf.typeParams[3] : Math.PI - x3dom.fields.Eps;

                // experimental HACK to switch between clamp to CoR (params[4]>0) and CoR translation in turntable mode
               var params = [theta, height, minAngle, maxAngle];
               if (length >= 5)
               {
                   // SPREAD OPERATOR KILLS IE
                   // adding rest parameters
                   //params.push(...this._vf.typeParams.slice(4));
                   
                   params = params.concat( this._vf.typeParams.slice(4) );
                   
               }
               console.log(params);
               return params;
            },

            setTypeParams: function(params) {
                for (var i=0; i<params.length; i++) {
                    this._vf.typeParams[i] = params[i];
                }
            },

            checkType: function(type) {
                if (this._validTypes.indexOf(type) > -1) {
                    return type;
                }
                else {
                    x3dom.debug.logWarning(type + " is no valid navigation type, use one of " +
                        this._validTypes.toString());
                    return "examine";
                }
            },

            getExplorationMode: function() {
                switch (this._vf.explorationMode.toLowerCase()) {
                    case "all":    return 7;
                    case "rotate": return 1; //left btn
                    case "zoom":   return 2; //right btn
                    case "pan":    return 4; //middle btn
                    case "none":   return 0; //type 'none'
                    default:       return 7;
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Billboard ### */
x3dom.registerNodeType(
    "Billboard",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for Billboard
         * @constructs x3dom.nodeTypes.Billboard
         * @x3d 3.3
         * @component Navigation
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Billboard is a Grouping node that can contain most nodes.
         * Content faces the user, rotating about the specified axis. Set axisOfRotation=0 0 0 to fully face the user's camera.
         * Hint: Put Billboard as close to the geometry as possible, nested inside Transform for local coordinate system.
         * Hint: don't put Viewpoint inside a Billboard.
         * Hint: insert a Shape node before adding geometry or Appearance.
         */
        function (ctx) {
            x3dom.nodeTypes.Billboard.superClass.call(this, ctx);

            /**
             * axisOfRotation direction is relative to local coordinate system. Hint: axis 0 0 0 always faces viewer.
             * @var {x3dom.fields.SFVec3f} axisOfRotation
             * @memberof x3dom.nodeTypes.Billboard
             * @initvalue 0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'axisOfRotation', 0, 1, 0);

            this._eye = new x3dom.fields.SFVec3f(0, 0, 0);
            this._eyeViewUp = new x3dom.fields.SFVec3f(0, 0, 0);
            this._eyeLook = new x3dom.fields.SFVec3f(0, 0, 0);
        
        },
        {
            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask);
                if (planeMask < 0) {
                    return;
                }

                // no caching later on as transform changes almost every frame anyway
                singlePath = false;

                var vol = this.getVolume();

                var min = x3dom.fields.SFVec3f.MAX();
                var max = x3dom.fields.SFVec3f.MIN();
                vol.getBounds(min, max);

                var mat_view = drawableCollection.viewMatrix;

                var center = new x3dom.fields.SFVec3f(0, 0, 0); // eye
                center = mat_view.inverse().multMatrixPnt(center);

                var mat_view_model = mat_view.mult(transform);
                this._eye = transform.inverse().multMatrixPnt(center);
                this._eyeViewUp = new x3dom.fields.SFVec3f(mat_view_model._10, mat_view_model._11, mat_view_model._12);
                this._eyeLook = new x3dom.fields.SFVec3f(mat_view_model._20, mat_view_model._21, mat_view_model._22);

                var rotMat = x3dom.fields.SFMatrix4f.identity();
                var mid = max.add(min).multiply(0.5);
                var billboard_to_viewer = this._eye.subtract(mid);

                if(this._vf.axisOfRotation.equals(new x3dom.fields.SFVec3f(0, 0, 0), x3dom.fields.Eps)) {
                    var rot1 = x3dom.fields.Quaternion.rotateFromTo(
                        billboard_to_viewer, new x3dom.fields.SFVec3f(0, 0, 1));
                    rotMat = rot1.toMatrix().transpose();

                    var yAxis = rotMat.multMatrixPnt(new x3dom.fields.SFVec3f(0, 1, 0)).normalize();
                    var zAxis = rotMat.multMatrixPnt(new x3dom.fields.SFVec3f(0, 0, 1)).normalize();

                    if(!this._eyeViewUp.equals(new x3dom.fields.SFVec3f(0, 0, 0), x3dom.fields.Eps)) {
                        // new local z-axis aligned with camera z-axis
                        var rot2 = x3dom.fields.Quaternion.rotateFromTo(this._eyeLook, zAxis);
                        // new: local y-axis rotated by rot2
                        var rotatedyAxis = rot2.toMatrix().transpose().multMatrixVec(yAxis);
                        // new: rotated local y-axis aligned with camera y-axis
                        var rot3 = x3dom.fields.Quaternion.rotateFromTo(this._eyeViewUp, rotatedyAxis);

                        rotMat = rot2.toMatrix().transpose().mult(rotMat);
                        rotMat = rot3.toMatrix().transpose().mult(rotMat);
                    }
                }
                else {
                    var normalPlane = this._vf.axisOfRotation.cross(billboard_to_viewer).normalize();

                    if(this._eye.z < 0) {
                        normalPlane = normalPlane.multiply(-1);
                    }

                    var degreesToRotate = Math.asin(normalPlane.dot(new x3dom.fields.SFVec3f(0, 0, 1)));

                    if(this._eye.z < 0) {
                        degreesToRotate += Math.PI;
                    }

                    rotMat = x3dom.fields.SFMatrix4f.parseRotation(
                            this._vf.axisOfRotation.x + ", " + this._vf.axisOfRotation.y + ", " +
                            this._vf.axisOfRotation.z + ", " + degreesToRotate*(-1));
                }

                var childTransform = this.transformMatrix(transform.mult(rotMat));

                for (var i=0, i_n=this._childNodes.length; i<i_n; i++)
                {
                    var cnode = this._childNodes[i];
                    if (cnode) {
                        cnode.collectDrawableObjects(childTransform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                    }
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### Collision ###
x3dom.registerNodeType(
    "Collision",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for Collision
         * @constructs x3dom.nodeTypes.Collision
         * @x3d 3.3
         * @component Navigation
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Collision detects camera-to-object contact using current Viewpoint and NavigationInfo avatarSize.
         * Collision is a Grouping node that handles collision detection for its children.
         * Collision can contain a single proxy child node for substitute collision-detection geometry.
         * Note: proxy geometry is not rendered.
         * Note: PointSet, IndexedLineSet, LineSet and Text do not trigger collisions.
         * Hint: improve performance using proxy for simpler contact-calculation geometry.
         * Hint: NavigationInfo types ''WALK' 'FLY'' support camera-to-object collision detection.
         * Hint: insert a Shape node before adding geometry or Appearance.
         */
        function (ctx) {
            x3dom.nodeTypes.Collision.superClass.call(this, ctx);


            /**
             * Enables/disables collision detection for children and all descendants. Hint: former name quotecollidequote in VRML 97 specification.
             * @var {x3dom.fields.SFBool} enabled
             * @memberof x3dom.nodeTypes.Collision
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool (ctx, "enabled", true);

            /**
             * alternate object to be checked for collision, in place of the children of this node.
             * @var {x3dom.fields.SFNode} proxy
             * @memberof x3dom.nodeTypes.Collision
             * @initvalue x3dom.nodeTypes.X3DGroupingNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode ("proxy", x3dom.nodeTypes.X3DGroupingNode);


            // TODO; add Slots: collideTime, isActive
            /**
             * NOT YET IMPLEMENTED. The time of collision.
             * @var {x3dom.fields.SFTime} collideTime
             * @memberof x3dom.nodeTypes.Collision
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime (ctx, "collideTime", 0);

            /**
             * NOT YET IMPLEMENTED. The value of the isActive field indicates the current state of the Collision node.
             * An isActive TRUE event is generated when a collision occurs. An isActive FALSE event is generated when a collision no longer occurs.
             * @var {x3dom.fields.SFBool} isActive
             * @memberof x3dom.nodeTypes.Collision
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool (ctx, "isActive", true);
        },
        {
            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask);
                if (planeMask < 0) {
                    return;
                }

                var cnode, childTransform;

                if (singlePath) {
                    if (!this._graph.globalMatrix) {
                        this._graph.globalMatrix = this.transformMatrix(transform);
                    }
                    childTransform = this._graph.globalMatrix;
                }
                else {
                    childTransform = this.transformMatrix(transform);
                }

                for (var i=0, n=this._childNodes.length; i<n; i++)
                {
                    if ((cnode = this._childNodes[i]) && (cnode !== this._cf.proxy.node)) {
                        cnode.collectDrawableObjects(childTransform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                    }
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### X3DLODNode ###
x3dom.registerNodeType(
    "X3DLODNode",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for X3DLODNode
         * @constructs x3dom.nodeTypes.X3DLODNode
         * @x3d x.x
         * @component Navigation
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         */
        function (ctx) {
            x3dom.nodeTypes.X3DLODNode.superClass.call(this, ctx);


            /**
             * The forceTransitions field specifies whether browsers are allowed to disregard level distances in order to provide better performance.
             * @var {x3dom.fields.SFBool} forceTransitions
             * @memberof x3dom.nodeTypes.X3DLODNode
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool (ctx, "forceTransitions", false);

            /**
             * The center field is a translation offset in the local coordinate system that specifies the centre of the LOD node for distance calculations.
             * @var {x3dom.fields.SFVec3f} center
             * @memberof x3dom.nodeTypes.X3DLODNode
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, "center", 0, 0, 0);

            this._eye = new x3dom.fields.SFVec3f(0, 0, 0);
        
        },
        {
            collectDrawableObjects: function(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask);
                if (planeMask < 0) {
                    return;
                }

                // at the moment, no caching here as children may change every frame
                singlePath = false;

                this.visitChildren(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);

                //out.LODs.push( [transform, this] );
            },

            visitChildren: function(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes) {
                // overwritten
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### LOD ###
x3dom.registerNodeType(
    "LOD",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DLODNode,
        
        /**
         * Constructor for LOD
         * @constructs x3dom.nodeTypes.LOD
         * @x3d 3.3
         * @component Navigation
         * @status experimental
         * @extends x3dom.nodeTypes.X3DLODNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc LOD (Level Of Detail) uses camera-to-object distance to switch among contained child levels.
         * (Contained nodes are now called 'children' rather than 'level', for consistent naming among all GroupingNodeType nodes.)
         * LOD range values go from near to far (as child geometry gets simpler for better performance).
         * For n range values, you must have n+1 children levels! Only the currently selected children level is rendered, but all levels continue to send/receive events.
         */
        function (ctx) {
            x3dom.nodeTypes.LOD.superClass.call(this, ctx);


            /**
             * Camera-to-object distance transitions for each child level, where range values go from near to far. For n range values, you must have n+1 child levels! Hint: can add an empty Group node as nonrendering final child.
             * @var {x3dom.fields.MFFloat} range
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.LOD
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, "range", []);

            this._lastRangePos = -1;
        
        },
        {
            visitChildren: function(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                var i=0, n=this._childNodes.length;

                var mat_view = drawableCollection.viewMatrix;

                var center = new x3dom.fields.SFVec3f(0, 0, 0); // eye
                center = mat_view.inverse().multMatrixPnt(center);

                //transform eye point to the LOD node's local coordinate system
                this._eye = transform.inverse().multMatrixPnt(center);

                var len = this._vf.center.subtract(this._eye).length();

                //calculate range check for viewer distance d (with range in local coordinates)
                //N+1 children nodes for N range values (L0, if d < R0, ... Ln-1, if d >= Rn-1)
                while (i < this._vf.range.length && len > this._vf.range[i]) {
                    i++;
                }
                if (i && i >= n) {
                    i = n - 1;
                }
                this._lastRangePos = i;

                var cnode = this._childNodes[i];
                if (n && cnode)
                {
                    var childTransform = this.transformMatrix(transform);
                    cnode.collectDrawableObjects(childTransform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                }
            },

            getVolume: function()
            {
                var vol = this._graph.volume;

                if (!this.volumeValid() && this._vf.render)
                {
                    var child, childVol;

                    if (this._lastRangePos >= 0) {
                        child = this._childNodes[this._lastRangePos];

                        childVol = (child && child._vf.render === true) ? child.getVolume() : null;

                        if (childVol && childVol.isValid())
                            vol.extendBounds(childVol.min, childVol.max);
                    }
                    else {  // first time we're here
                        for (var i=0, n=this._childNodes.length; i<n; i++)
                        {
                            if (!(child = this._childNodes[i]) || child._vf.render !== true)
                                continue;

                            childVol = child.getVolume();

                            if (childVol && childVol.isValid())
                                vol.extendBounds(childVol.min, childVol.max);
                        }
                    }
                }

                return vol;
            },

            nodeChanged: function() {
                //this._needReRender = true;
                this.invalidateVolume();
                //this.invalidateCache();
            },

            fieldChanged: function(fieldName) {
                //this._needReRender = true;
                if (fieldName == "render" ||
                    fieldName == "center" ||
                    fieldName == "range") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### DynamicLOD ###
x3dom.registerNodeType(
    "DynamicLOD",
    "Navigation",
    defineClass(x3dom.nodeTypes.X3DLODNode,
        
        /**
         * Constructor for DynamicLOD
         * @constructs x3dom.nodeTypes.DynamicLOD
         * @x3d x.x
         * @component Navigation
         * @extends x3dom.nodeTypes.X3DLODNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         */
        function (ctx) {
            x3dom.nodeTypes.DynamicLOD.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFFloat} subScale
             * @memberof x3dom.nodeTypes.DynamicLOD
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subScale', 0.5);

            /**
             *
             * @var {x3dom.fields.SFVec2f} size
             * @memberof x3dom.nodeTypes.DynamicLOD
             * @initvalue 2,2
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'size', 2, 2);

            /**
             *
             * @var {x3dom.fields.SFVec2f} subdivision
             * @memberof x3dom.nodeTypes.DynamicLOD
             * @initvalue 1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'subdivision', 1, 1);

            /**
             *
             * @var {x3dom.fields.SFNode} root
             * @memberof x3dom.nodeTypes.DynamicLOD
             * @initvalue x3dom.nodeTypes.X3DShapeNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode ('root', x3dom.nodeTypes.X3DShapeNode);


            /**
             *
             * @var {x3dom.fields.SFString} urlHead
             * @memberof x3dom.nodeTypes.DynamicLOD
             * @initvalue "http://r"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'urlHead', "http://r");

            /**
             *
             * @var {x3dom.fields.SFString} urlCenter
             * @memberof x3dom.nodeTypes.DynamicLOD
             * @initvalue ".ortho.tiles.virtualearth.net/tiles/h"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'urlCenter', ".ortho.tiles.virtualearth.net/tiles/h");

            /**
             *
             * @var {x3dom.fields.SFString} urlTail
             * @memberof x3dom.nodeTypes.DynamicLOD
             * @initvalue ".png?g=-1"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'urlTail', ".png?g=-1");

            this.rootGeometry = new x3dom.nodeTypes.Plane(ctx);
            this.level = 0;
            this.quadrant = 4;
            this.cell = "";
        
        },
        {
            nodeChanged: function()
            {
                var root = this._cf.root.node;

                if (root == null || root._cf.geometry.node != null)
                    return;

                this.rootGeometry._vf.size.setValues(this._vf.size);
                this.rootGeometry._vf.subdivision.setValues(this._vf.subdivision);
                this.rootGeometry._vf.center.setValues(this._vf.center);
                this.rootGeometry.fieldChanged("subdivision");   // trigger update

                this._cf.root.node.addChild(this.rootGeometry);  // add to shape
                this.rootGeometry.nodeChanged();

                this._cf.root.node.nodeChanged();

                this._nameSpace.doc.needRender = true;
            },

            visitChildren: function(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                var root = this._cf.root.node;

                if (root == null)
                    return;

                var mat_view = drawableCollection.viewMatrix;

                var center = new x3dom.fields.SFVec3f(0, 0, 0); // eye
                center = mat_view.inverse().multMatrixPnt(center);

                //var mat_view_model = mat_view.mult(transform);
                this._eye = transform.inverse().multMatrixPnt(center);

                var l, len = this._vf.center.subtract(this._eye).length();

                //calculate range check for viewer distance d (with range in local coordinates)
                if (len > x3dom.fields.Eps && len * this._vf.subScale <= this._vf.size.length()) {
                    /*  Quadrants per level: (TODO; make parameterizable, e.g. 0 and 1 might be swapped)
                     0 | 1
                     -----
                     2 | 3
                     */
                    if (this._childNodes.length <= 1) {
                        var offset = new Array(
                            new x3dom.fields.SFVec3f(-0.25*this._vf.size.x,  0.25*this._vf.size.y, 0),
                            new x3dom.fields.SFVec3f( 0.25*this._vf.size.x,  0.25*this._vf.size.y, 0),
                            new x3dom.fields.SFVec3f(-0.25*this._vf.size.x, -0.25*this._vf.size.y, 0),
                            new x3dom.fields.SFVec3f( 0.25*this._vf.size.x, -0.25*this._vf.size.y, 0)
                        );

                        for (l=0; l<4; l++) {
                            var node = new x3dom.nodeTypes.DynamicLOD();

                            node._nameSpace = this._nameSpace;
                            node._eye.setValues(this._eye);

                            node.level = this.level + 1;
                            node.quadrant = l;
                            node.cell = this.cell + l;

                            node._vf.urlHead = this._vf.urlHead;
                            node._vf.urlCenter = this._vf.urlCenter;
                            node._vf.urlTail = this._vf.urlTail;

                            node._vf.center = this._vf.center.add(offset[l]);
                            node._vf.size = this._vf.size.multiply(0.5);
                            node._vf.subdivision.setValues(this._vf.subdivision);

                            var app = new x3dom.nodeTypes.Appearance();

                            //var mat = new x3dom.nodeTypes.Material();
                            //mat._vf.diffuseColor = new x3dom.fields.SFVec3f(Math.random(),Math.random(),Math.random());
                            //
                            //app.addChild(mat);
                            //mat.nodeChanged();

                            var tex = new x3dom.nodeTypes.ImageTexture();
                            tex._nameSpace = this._nameSpace;
                            tex._vf.url[0] = this._vf.urlHead + node.quadrant + this._vf.urlCenter + node.cell + this._vf.urlTail;
                            //x3dom.debug.logInfo(tex._vf.url[0]);

                            app.addChild(tex);
                            tex.nodeChanged();

                            var shape = new x3dom.nodeTypes.Shape();
                            shape._nameSpace = this._nameSpace;

                            shape.addChild(app);
                            app.nodeChanged();

                            node.addChild(shape, "root");
                            shape.nodeChanged();

                            this.addChild(node);
                            node.nodeChanged();
                        }
                    }
                    else {
                        for (l=1; l<this._childNodes.length; l++) {
                            this._childNodes[l].collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                        }
                    }
                }
                else {
                    root.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                }
            },

            getVolume: function() {
                var vol = this._graph.volume;

                if (!vol.isValid()) {
                    vol.min.setValues(this._vf.center);
                    vol.min.x -= 0.5 * this._vf.size.x;
                    vol.min.y -= 0.5 * this._vf.size.y;
                    vol.min.z -= x3dom.fields.Eps;

                    vol.max.setValues(this._vf.center);
                    vol.max.x += 0.5 * this._vf.size.x;
                    vol.max.y += 0.5 * this._vf.size.y;
                    vol.max.z += x3dom.fields.Eps;
                }

                return vol;
            }
        }
    )
);


x3dom.DefaultNavigation = function(navigationNode)
{
    this.navi = navigationNode;
};

x3dom.DefaultNavigation.prototype.onMousePress = function(view,x, y, buttonState)
{
    
};

x3dom.DefaultNavigation.prototype.onMouseReleased = function(view,x, y, buttonState, prevButton)
{
    
};

x3dom.DefaultNavigation.prototype.init = function(view, flyTo)
{
    
};

x3dom.DefaultNavigation.prototype.zoom = function(view, zoomAmount)
{
    var navi = this.navi;

    var viewpoint = view._scene.getViewpoint();

    var d = (view._scene._lastMax.subtract(view._scene._lastMin)).length();
    d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed;

    vec = new x3dom.fields.SFVec3f(0, 0, d*(zoomAmount)/view._height);

    if (x3dom.isa(viewpoint, x3dom.nodeTypes.OrthoViewpoint))
    {
        viewpoint.setZoom( Math.abs( viewpoint._fieldOfView[0] ) - vec.z );
    }
    else
    {
        if ( navi._vf.typeParams.length >= 6 ) {

            var min = -navi._vf.typeParams[ 5 ];
            var max =  navi._vf.typeParams[ 4 ];

            view._movement.z = Math.min( Math.max( view._movement.z, min ), max );

        }

        view._movement = view._movement.add(vec);
        mat = view.getViewpointMatrix().mult(view._transMat);
        //TODO; move real distance along viewing ray
        view._transMat = mat.inverse().
                            mult(x3dom.fields.SFMatrix4f.translation(view._movement)).
                            mult(mat);
    }
}

x3dom.DefaultNavigation.prototype.moveForward = function(view)
{
     var navi = this.navi;
    
    if (navi.getType() === "game")
    {
        var avatarRadius = 0.25;
        var avatarHeight = 1.6;

        if (navi._vf.avatarSize.length > 2) {
            avatarRadius = navi._vf.avatarSize[0];
            avatarHeight = navi._vf.avatarSize[1];
        }

        var speed = 5 * view._deltaT * navi._vf.speed;
        var yRotRad = (view._yaw / 180 * Math.PI);
        var xRotRad = (view._pitch / 180 * Math.PI);

        var dist = 0;
        var fMat = view._flyMat.inverse();

        // check front for collisions
        view._scene._nameSpace.doc.ctx.pickValue(view, view._width/2, view._height/2, view._lastButton);

        if (view._pickingInfo.pickObj)
        {
            dist = view._pickingInfo.pickPos.subtract(fMat.e3()).length();

            if (dist <= 2 * avatarRadius) {
                //x3dom.debug.logWarning("Collision at dist=" + dist.toFixed(4));
            }
            else {
                view._eyePos.x -= Math.sin(yRotRad) * speed;
                view._eyePos.z += Math.cos(yRotRad) * speed;
                view._eyePos.y += Math.sin(xRotRad) * speed;
            }
        }
    }
};

x3dom.DefaultNavigation.prototype.moveBackwards = function(view)
{
    var navi = this.navi;
    
    if (navi.getType() === "game")
    {
        var speed = 5 * view._deltaT * navi._vf.speed;
        var yRotRad = (view._yaw / 180 * Math.PI);
        var xRotRad = (view._pitch / 180 * Math.PI);

        view._eyePos.x += Math.sin(yRotRad) * speed;
        view._eyePos.z -= Math.cos(yRotRad) * speed;
        view._eyePos.y -= Math.sin(xRotRad) * speed;
    }
};

x3dom.DefaultNavigation.prototype.strafeLeft = function(view)
{
    var navi = this.navi;
    
    if (navi.getType() === "game")
    {
        var speed = 5 * view._deltaT * navi._vf.speed;
        var yRotRad = (view._yaw / 180 * Math.PI);

        view._eyePos.x += Math.cos(yRotRad) * speed;
        view._eyePos.z += Math.sin(yRotRad) * speed;
    }
};

x3dom.DefaultNavigation.prototype.strafeRight = function(view)
{
    var navi = this.navi;
    
    if (navi.getType() === "game")
    {
        var speed = 5 * view._deltaT * navi._vf.speed;
        var yRotRad = (view._yaw / 180 * Math.PI);

        view._eyePos.x -= Math.cos(yRotRad) * speed;
        view._eyePos.z -= Math.sin(yRotRad) * speed;
    }
};

x3dom.DefaultNavigation.prototype.navigateTo = function(view, timeStamp)
{
    var navi = this.navi;
    var navType = navi.getType();
    var savedPickingInfo = null;
    
    var needNavAnim = (view._currentInputType == x3dom.InputTypes.NAVIGATION) &&
                      ( navType === "game" ||
                        (view._lastButton > 0 &&
                        (navType.indexOf("fly") >= 0 ||
                         navType === "walk" ||
                         navType === "helicopter" ||
                         navType.substr(0, 5) === "looka")) );
    
    view._deltaT = timeStamp - view._lastTS;
    
    var removeZeroMargin = function(val, offset) {
        if (val > 0) {
            if (val <= offset) {
                return 0;
            } else {
                return val - offset;
            }
        } else if (val <= 0) {
            if (val >= -offset) {
                return 0;
            } else {
                return val + offset;
            }
        }
    };
    
    // slightly increasing slope function
    var humanizeDiff = function(scale, diff) {
        return ((diff < 0) ? -1 : 1 ) * Math.pow(scale * Math.abs(diff), 1.65 /*lower is easier on the novice*/);
    };

    if (needNavAnim)
    {

        //Save picking info if available
        if( view._pickingInfo.pickObj !== null ) {

            savedPickingInfo = {
                pickPos: view._pickingInfo.pickPos,
                pickNorm: view._pickingInfo.pickNorm,
                pickObj: view._pickingInfo.pickObj,
                firstObj: view._pickingInfo.firstObj,
                lastObj: view._pickingInfo.lastObj,
                lastClickObj: view._pickingInfo.lastClickObj,
                shadowObjectId: view._pickingInfo.shadowObjectId
            };
        }

        var avatarRadius = 0.25;
        var avatarHeight = 1.6;
        var avatarKnee = 0.75;  // TODO; check max. step size

        if (navi._vf.avatarSize.length > 2) {
            avatarRadius = navi._vf.avatarSize[0];
            avatarHeight = navi._vf.avatarSize[1];
            avatarKnee = navi._vf.avatarSize[2];
        }
        
        

        // get current view matrix
        var currViewMat = view.getViewMatrix();
        var dist = 0;
        
        // estimate one screen size for motion puposes so navigation behaviour
        // is less dependent on screen geometry. view makes no sense for very
        // anisotropic cases, so it should probably be configurable.
        var screenSize = Math.min(view._width, view._height);
        var rdeltaX = removeZeroMargin((view._pressX - view._lastX) / screenSize, 0.01);
        var rdeltaY = removeZeroMargin((view._pressY - view._lastY) / screenSize, 0.01);
        
        var userXdiff = humanizeDiff(1, rdeltaX);
        var userYdiff = humanizeDiff(1, rdeltaY);

        // check if forwards or backwards (on right button)
        var step = (view._lastButton & 2) ? -1 : 1;
        step *= (view._deltaT * navi._vf.speed);
        
        // factor in delta time and the nav speed setting
        var userXstep = view._deltaT * navi._vf.speed * userXdiff;
        var userYstep = view._deltaT * navi._vf.speed * userYdiff;
        
        var phi = Math.PI * view._deltaT * userXdiff;
        var theta = Math.PI * view._deltaT * userYdiff;
        
        if (view._needNavigationMatrixUpdate === true)
        {
            view._needNavigationMatrixUpdate = false;
          
            // reset examine matrices to identity
            view._rotMat = x3dom.fields.SFMatrix4f.identity();
            view._transMat = x3dom.fields.SFMatrix4f.identity();
            view._movement = new x3dom.fields.SFVec3f(0, 0, 0);

            var angleX = 0;
            var angleY = Math.asin(currViewMat._02);
            var C = Math.cos(angleY);
            
            if (Math.abs(C) > 0.0001) {
                angleX = Math.atan2(-currViewMat._12 / C, currViewMat._22 / C);
            }

            // too many inversions here can lead to distortions
            view._flyMat = currViewMat.inverse();
            
            view._from = view._flyMat.e3();
            view._at = view._from.subtract(view._flyMat.e2());

            if (navType === "helicopter")
                view._at.y = view._from.y;

            /*

             //lookat, lookaround
             if (navType.substr(0, 5) === "looka")
             {
             view._up = view._flyMat.e1();
             }
             //all other modes
             else
             {
             //initially read up-vector from current orientation and keep it
             if (typeof view._up == 'undefined')
             {
             view._up = view._flyMat.e1();
             }
             }

             */

            view._up = view._flyMat.e1();

            view._pitch = angleX * 180 / Math.PI;
            view._yaw = angleY * 180 / Math.PI;
            view._eyePos = view._from.negate();
        }

        var tmpAt = null, tmpUp = null, tmpMat = null;
        var q, temp, fin;
        var lv, sv, up;

        if (navType === "game")
        {
            view._pitch += view._dy;
            view._yaw   += view._dx;

            if (view._pitch >=  89) view._pitch = 89;
            if (view._pitch <= -89) view._pitch = -89;
            if (view._yaw >=  360) view._yaw -= 360;
            if (view._yaw < 0) view._yaw = 360 + view._yaw;
            
            view._dx = 0;
            view._dy = 0;

            var xMat = x3dom.fields.SFMatrix4f.rotationX(view._pitch / 180 * Math.PI);
            var yMat = x3dom.fields.SFMatrix4f.rotationY(view._yaw / 180 * Math.PI);

            var fPos = x3dom.fields.SFMatrix4f.translation(view._eyePos);

            view._flyMat = xMat.mult(yMat).mult(fPos);

            // Finally check floor for terrain following (TODO: optimize!)
            var flyMat = view._flyMat.inverse();

            var tmpFrom = flyMat.e3();
            tmpUp = new x3dom.fields.SFVec3f(0, -1, 0);

            tmpAt = tmpFrom.add(tmpUp);
            tmpUp = flyMat.e0().cross(tmpUp).normalize();

            tmpMat = x3dom.fields.SFMatrix4f.lookAt(tmpFrom, tmpAt, tmpUp);
            tmpMat = tmpMat.inverse();

            view._scene._nameSpace.doc.ctx.pickValue(view, view._width/2, view._height/2,
                        view._lastButton, tmpMat, view.getProjectionMatrix().mult(tmpMat));

            if (view._pickingInfo.pickObj)
            {
                dist = view._pickingInfo.pickPos.subtract(tmpFrom).length();
                //x3dom.debug.logWarning("Floor collision at dist=" + dist.toFixed(4));

                tmpFrom.y += (avatarHeight - dist);
                flyMat.setTranslate(tmpFrom);

                view._eyePos = flyMat.e3().negate();
                view._flyMat = flyMat.inverse();

                view._pickingInfo.pickObj = null;
            }

            view._scene.getViewpoint().setView(view._flyMat);

            return needNavAnim;
        }   // game
        else if (navType === "helicopter") {
            var typeParams = navi.getTypeParams();

            

            if (view._lastButton & 2) // up/down levelling
            {
                var stepUp = 200 * userYstep;
                typeParams[1] += stepUp;
                navi.setTypeParams(typeParams);
            }

            if (view._lastButton & 1) {  // forward/backward motion
                step = 300 * userYstep;
            }
            else {
                step = 0;
            }
            
            theta = typeParams[0];
            view._from.y = typeParams[1];
            view._at.y = view._from.y;

            // rotate around the up vector
            q = x3dom.fields.Quaternion.axisAngle(view._up, phi);
            temp = q.toMatrix();

            fin = x3dom.fields.SFMatrix4f.translation(view._from);
            fin = fin.mult(temp);

            temp = x3dom.fields.SFMatrix4f.translation(view._from.negate());
            fin = fin.mult(temp);

            view._at = fin.multMatrixPnt(view._at);

            // rotate around the side vector
            lv = view._at.subtract(view._from).normalize();
            sv = lv.cross(view._up).normalize();
            up = sv.cross(lv).normalize();

            lv = lv.multiply(step);

            view._from = view._from.add(lv);
            view._at = view._at.add(lv);

            // rotate around the side vector
            q = x3dom.fields.Quaternion.axisAngle(sv, theta);
            temp = q.toMatrix();

            fin = x3dom.fields.SFMatrix4f.translation(view._from);
            fin = fin.mult(temp);

            temp = x3dom.fields.SFMatrix4f.translation(view._from.negate());
            fin = fin.mult(temp);

            var at = fin.multMatrixPnt(view._at);

            view._flyMat = x3dom.fields.SFMatrix4f.lookAt(view._from, at, up);

            view._scene.getViewpoint().setView(view._flyMat.inverse());

            return needNavAnim;
        }   // helicopter

        // rotate around the up vector
        q = x3dom.fields.Quaternion.axisAngle(view._up, phi);
        temp = q.toMatrix();

        fin = x3dom.fields.SFMatrix4f.translation(view._from);
        fin = fin.mult(temp);

        temp = x3dom.fields.SFMatrix4f.translation(view._from.negate());
        fin = fin.mult(temp);

        view._at = fin.multMatrixPnt(view._at);

        // rotate around the side vector
        lv = view._at.subtract(view._from).normalize();
        sv = lv.cross(view._up).normalize();
        up = sv.cross(lv).normalize();
        //view._up = up;

        q = x3dom.fields.Quaternion.axisAngle(sv, theta);
        temp = q.toMatrix();

        fin = x3dom.fields.SFMatrix4f.translation(view._from);
        fin = fin.mult(temp);

        temp = x3dom.fields.SFMatrix4f.translation(view._from.negate());
        fin = fin.mult(temp);

        view._at = fin.multMatrixPnt(view._at);

        // forward along view vector
        if (navType.substr(0, 5) !== "looka")
        {
            var currProjMat = view.getProjectionMatrix();

            if (navType !== "freefly") {
                if (step < 0) {
                    // backwards: negate viewing direction
                    tmpMat = new x3dom.fields.SFMatrix4f();
                    tmpMat.setValue(view._last_mat_view.e0(), view._last_mat_view.e1(),
                                    view._last_mat_view.e2().negate(), view._last_mat_view.e3());

                    view._scene._nameSpace.doc.ctx.pickValue(view, view._width/2, view._height/2,
                                view._lastButton, tmpMat, currProjMat.mult(tmpMat));
                }
                else {
                    view._scene._nameSpace.doc.ctx.pickValue(view, view._width/2, view._height/2, view._lastButton);
                }
                if (view._pickingInfo.pickObj)
                {
                    dist = view._pickingInfo.pickPos.subtract(view._from).length();

                    if (dist <= avatarRadius) {
                        step = 0;
                    }
                }
            }

            lv = view._at.subtract(view._from).normalize().multiply(step);

            view._at = view._at.add(lv);
            view._from = view._from.add(lv);

            // finally attach to ground when walking
            if (navType === "walk")
            {
                tmpAt = view._from.addScaled(up, -1.0);
                tmpUp = sv.cross(up.negate()).normalize();  // lv

                tmpMat = x3dom.fields.SFMatrix4f.lookAt(view._from, tmpAt, tmpUp);
                tmpMat = tmpMat.inverse();

                view._scene._nameSpace.doc.ctx.pickValue(view, view._width/2, view._height/2,
                            view._lastButton, tmpMat, currProjMat.mult(tmpMat));

                if (view._pickingInfo.pickObj)
                {
                    dist = view._pickingInfo.pickPos.subtract(view._from).length();

                    view._at = view._at.add(up.multiply(avatarHeight - dist));
                    view._from = view._from.add(up.multiply(avatarHeight - dist));
                }
            }
            view._pickingInfo.pickObj = null;
        }
        
        view._flyMat = x3dom.fields.SFMatrix4f.lookAt(view._from, view._at, up);

        view._scene.getViewpoint().setView(view._flyMat.inverse());

        //Restore picking info if available
        if( savedPickingInfo !== null ) {

            view._pickingInfo = savedPickingInfo;
            
        }
    }

    return needNavAnim;
};

x3dom.DefaultNavigation.prototype.animateTo = function(view, target, prev, dur)
{
    var navi = this.navi;
    
    if (x3dom.isa(target, x3dom.nodeTypes.X3DViewpointNode)) {
        target = target.getViewMatrix().mult(target.getCurrentTransform().inverse());
    }

    if (navi._vf.transitionType[0].toLowerCase() !== "teleport" && dur != 0 && navi.getType() !== "game")
    {
        if (prev && x3dom.isa(prev, x3dom.nodeTypes.X3DViewpointNode)) {
            prev = prev.getViewMatrix().mult(prev.getCurrentTransform().inverse()).
                         mult(view._transMat).mult(view._rotMat);

            view._mixer.beginTime = view._lastTS;

            if (arguments.length >= 4 && arguments[3] != null) {
                // for lookAt to assure travel speed of 1 m/s
                view._mixer.endTime = view._lastTS + dur;
            }
            else {
                view._mixer.endTime = view._lastTS + navi._vf.transitionTime;
            }

            view._mixer.setBeginMatrix (prev);
            view._mixer.setEndMatrix (target);
            
            view._scene.getViewpoint().setView(prev);
        }
        else {
            view._scene.getViewpoint().setView(target);
        }
    }
    else
    {
        view._scene.getViewpoint().setView(target);
    }

    view._rotMat = x3dom.fields.SFMatrix4f.identity();
    view._transMat = x3dom.fields.SFMatrix4f.identity();
    view._movement = new x3dom.fields.SFVec3f(0, 0, 0);
    view._needNavigationMatrixUpdate = true;
};

x3dom.DefaultNavigation.prototype.orthoAnimateTo = function( view, target, prev, duration )
{
    var navi = this.navi;

    duration = duration || navi._vf.transitionTime;

    view._interpolator.beginValue = prev;
    view._interpolator.endValue = target;

    view._interpolator.beginTime = view._lastTS;
    view._interpolator.endTime = view._lastTS + duration;
};

x3dom.DefaultNavigation.prototype.resetView = function(view)
{
    var navi = this.navi;

    if (navi._vf.transitionType[0].toLowerCase() !== "teleport" && navi.getType() !== "game")
    {
		var viewpoint = view._scene.getViewpoint();
		
        view._mixer.beginTime = view._lastTS;
        view._mixer.endTime = view._lastTS + navi._vf.transitionTime;

        view._mixer.setBeginMatrix(view.getViewMatrix());
		
		if (x3dom.isa(viewpoint, x3dom.nodeTypes.OrthoViewpoint))
		{
			this.orthoAnimateTo(view, Math.abs(viewpoint._vf.fieldOfView[0]), Math.abs(viewpoint._fieldOfView[0]));
		}

        var target = view._scene.getViewpoint();
        target.resetView();

        target = target.getViewMatrix().mult(target.getCurrentTransform().inverse());

        view._mixer.setEndMatrix(target);
    } else
    {
        view._scene.getViewpoint().resetView();
    }

    view.resetNavHelpers();
    navi._heliUpdated = false;
};

x3dom.DefaultNavigation.prototype.onDrag = function(view, x, y, buttonState)
{
    var navi = this.navi;

    var navType = navi.getType();
    var navRestrict = navi.getExplorationMode();

    if (navType === "none" || navRestrict == 0) {
        return;
    }

    var viewpoint = view._scene.getViewpoint();

    var dx = x - view._lastX;
    var dy = y - view._lastY;
    var d, vec, cor, mat = null;
    var alpha, beta;

    buttonState = (!navRestrict || (navRestrict != 7 && buttonState == 1)) ? navRestrict : buttonState;
    
    if (buttonState & 1) //left
    {
        alpha = (dy * 2 * Math.PI) / view._width;
        beta = (dx * 2 * Math.PI) / view._height;
        mat = view.getViewMatrix();

        var mx = x3dom.fields.SFMatrix4f.rotationX(alpha);
        var my = x3dom.fields.SFMatrix4f.rotationY(beta);

        var center = viewpoint.getCenterOfRotation();
        mat.setTranslate(new x3dom.fields.SFVec3f(0,0,0));

        view._rotMat = view._rotMat.
                        mult(x3dom.fields.SFMatrix4f.translation(center)).
                        mult(mat.inverse()).mult(mx).mult(my).mult(mat).
                        mult(x3dom.fields.SFMatrix4f.translation(center.negate()));
    }
    if (buttonState & 4) //middle
    {
        d = (view._scene._lastMax.subtract(view._scene._lastMin)).length();
        d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed;

        vec = new x3dom.fields.SFVec3f(d*dx/view._width, d*(-dy)/view._height, 0);
        view._movement = view._movement.add(vec);

        mat = view.getViewpointMatrix().mult(view._transMat);
        //TODO; move real distance along viewing plane
        view._transMat = mat.inverse().
                            mult(x3dom.fields.SFMatrix4f.translation(view._movement)).
                            mult(mat);
    }
    if (buttonState & 2) //right
    {
        d = (view._scene._lastMax.subtract(view._scene._lastMin)).length();
        d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed;

        vec = new x3dom.fields.SFVec3f(0, 0, d*(dx+dy)/view._height);

        if (x3dom.isa(viewpoint, x3dom.nodeTypes.OrthoViewpoint))
        {
            viewpoint.setZoom( Math.abs( viewpoint._fieldOfView[0] ) - vec.z );
        }
        else
        {
            if ( navi._vf.typeParams.length >= 6 ) {

                var min = -navi._vf.typeParams[ 5 ];
                var max =  navi._vf.typeParams[ 4 ];

                view._movement.z = Math.min( Math.max( view._movement.z, min ), max );

            }

            view._movement = view._movement.add(vec);
            mat = view.getViewpointMatrix().mult(view._transMat);
            //TODO; move real distance along viewing ray
            view._transMat = mat.inverse().
                                mult(x3dom.fields.SFMatrix4f.translation(view._movement)).
                                mult(mat);
        }
    }

    view._isMoving = true;
    
    
    view._dx = dx;
    view._dy = dy;

    view._lastX = x;
    view._lastY = y;
};

x3dom.DefaultNavigation.prototype.onTouchStart = function(view, evt, touches)
{
    
};

x3dom.DefaultNavigation.prototype.onTouchDrag = function(view,evt, touches, translation, rotation)
{
    if (view._currentInputType == x3dom.InputTypes.NAVIGATION)
    {
        var navi = this.navi;
        var viewpoint = view._scene.getViewpoint();

        if (navi.getType() === "examine")
        {
            if (translation)
            {
                var distance = (view._scene._lastMax.subtract(view._scene._lastMin)).length();
                distance = ((distance < x3dom.fields.Eps) ? 1 : distance) * navi._vf.speed;

                translation = translation.multiply(distance);
                view._movement = view._movement.add(translation);

                view._transMat = viewpoint.getViewMatrix().inverse().
                    mult(x3dom.fields.SFMatrix4f.translation(view._movement)).
                    mult(viewpoint.getViewMatrix());
            }

            if (rotation)
            {
                var center = viewpoint.getCenterOfRotation();
                var mat = view.getViewMatrix();
                mat.setTranslate(new x3dom.fields.SFVec3f(0,0,0));

                view._rotMat = view._rotMat.
                               mult(x3dom.fields.SFMatrix4f.translation(center)).
                               mult(mat.inverse()).mult(rotation).mult(mat).
                               mult(x3dom.fields.SFMatrix4f.translation(center.negate()));
            }

            view._isMoving = true;
        }
    }
};

x3dom.DefaultNavigation.prototype.onTouchEnd = function(evt, touches)
{
    
};

x3dom.DefaultNavigation.prototype.onDoubleClick = function (view, x, y)
{
    if (view._doc._x3dElem.hasAttribute('disableDoubleClick') &&
        view._doc._x3dElem.getAttribute('disableDoubleClick') === 'true') {
        return;
    }
    
    var navi = view._scene.getNavigationInfo();
    
    if (navi.getType() == "none") {
        return;
    }

    var pickMode = view._scene._vf.pickMode.toLowerCase();

    if ((pickMode == "color" || pickMode == "texcoord")) {
         return;
    }

    var viewpoint = view._scene.getViewpoint();

    viewpoint.setCenterOfRotation(view._pick);
    x3dom.debug.logInfo("New center of Rotation:  " + view._pick);

    var mat = view.getViewMatrix().inverse();

    var from = mat.e3();
    var at = view._pick;
    var up = mat.e1();

    var norm = mat.e0().cross(up).normalize();
    // get distance between look-at point and viewing plane
    var dist = norm.dot(view._pick.subtract(from));
    
    from = at.addScaled(norm, -dist);
    mat = x3dom.fields.SFMatrix4f.lookAt(from, at, up);
    
    x3dom.debug.logInfo("New camera position:  " + from);
    view.animateTo(mat.inverse(), viewpoint);
};

x3dom.TurntableNavigation = function(navigationNode)
{
    x3dom.DefaultNavigation.call(this, navigationNode);

    this.panAxisX = null;
    this.panAxisY = null;

    this.panEnabled = true;
};

x3dom.TurntableNavigation.prototype = Object.create(x3dom.DefaultNavigation.prototype);
x3dom.TurntableNavigation.prototype.constructor = x3dom.TurntableNavigation;

x3dom.TurntableNavigation.prototype.onDrag = function(view, x, y, buttonState)
{ 
    navi = this.navi; 
    
    if (!view._flyMat)
        this.initTurnTable(view, false);
    
    var navType = navi.getType();       
    var navRestrict = navi.getExplorationMode();

    if (navType === "none" || navRestrict == 0) {
        return;
    }    

    var dx = x - view._lastX;
    var dy = y - view._lastY;
    
    var d = null;
    var alpha, beta;

    buttonState = (!navRestrict || (navRestrict != 7 && buttonState == 1)) ? navRestrict : buttonState;
    
    if (buttonState & 1) //left
    {
        alpha = (dy * 2 * Math.PI) / view._height;
        beta = (dx * 2 * Math.PI) / view._width;
        
        this.rotate(view, alpha, beta);
    }
    else if (buttonState & 2) //right
    {
        d = (view._scene._lastMax.subtract(view._scene._lastMin)).length();
        d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed;

        var zoomAmount = d * (dx + dy) / view._height;

        this.zoom(view, zoomAmount);        
    }
    else if ((buttonState & 4) && this.panEnabled == true) //middle
    {
        
        d = (view._scene._lastMax.subtract(view._scene._lastMin)).length();
        d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed * 0.75;

        var tx = -d * dx / view._width;
        var ty =  d * dy / view._height;

        this.pan(view, tx, ty);
    }

    view._isMoving = true;
    
    view._dx = dx;
    view._dy = dy;

    view._lastX = x;
    view._lastY = y;
};

x3dom.TurntableNavigation.prototype.pan = function(view, tx, ty)
{
    if(this.target != null) {
        var target = this.target;
        var bbox = target._x3domNode.getVolume();

        var viewpoint = view._scene.getViewpoint();

        view._up = view._flyMat.e1();
        view._from = view._flyMat.e3(); // eye

        // add xy offset to look-at position  ^
        var cor = view._at;

        cor = cor.addScaled(this.panAxisY, ty);
        var temp = cor;
        if (cor.y > bbox.max.y || cor.y < bbox.min.y)
            temp = view._at;
        else
            view._from = view._from.addScaled(this.panAxisY, ty);


        cor = temp.addScaled(this.panAxisX, tx);
        if (cor.x > bbox.max.x || cor.x < bbox.min.x)
            cor = temp;
        else
            view._from = view._from.addScaled(this.panAxisX, tx);

        view._at = cor;

        // update camera matrix with lookAt() and invert
        view._flyMat = x3dom.fields.SFMatrix4f.lookAt(view._from, cor, view._up);
        viewpoint.setViewAbsolute(view._flyMat.inverse());
    } else if(this.panAxisX != null && this.panAxisY != null) {
        var viewpoint = view._scene.getViewpoint();

        view._up = view._flyMat.e1();
        view._from = view._flyMat.e3(); // eye

        // add xy offset to look-at position  ^
        var cor = view._at;

        cor = cor.addScaled(this.panAxisY, ty);
        var temp = cor;
        view._from = view._from.addScaled(this.panAxisY, ty);

        cor = temp.addScaled(this.panAxisX, tx);
        view._from = view._from.addScaled(this.panAxisX, tx);

        view._at = cor;

        // update camera matrix with lookAt() and invert
        view._flyMat = x3dom.fields.SFMatrix4f.lookAt(view._from, cor, view._up);
        viewpoint.setViewAbsolute(view._flyMat.inverse());
    }else{
        var vec = new x3dom.fields.SFVec3f(-tx * navi._vf.speed, -ty * navi._vf.speed, 0);

        view._movement = view._movement.add(vec);
        var mat = view.getViewpointMatrix().mult(view._transMat);
        view._transMat = mat.inverse().
                            mult(x3dom.fields.SFMatrix4f.translation(view._movement)).
                            mult(mat);
    }
    
};

x3dom.TurntableNavigation.prototype.rotate = function(view, alpha, beta)
{
    var viewpoint = view._scene.getViewpoint();
    
    view._flyMat = this.calcOrbit(view, alpha, beta);
    viewpoint.setView(view._flyMat.inverse());
};

x3dom.TurntableNavigation.prototype.zoom = function(view, zoomAmount)
{
    var navi = this.navi;
    var viewpoint = view._scene.getViewpoint();
    
    view._up   = view._flyMat.e1();
    view._from = view._flyMat.e3(); // eye

    // zoom in/out
    cor = view._at;

    var lastDir  = cor.subtract(view._from);
    var lastDirL = lastDir.length();
    lastDir = lastDir.normalize();
    
    // maintain minimum distance (value given in typeParams[4]) to prevent orientation flips
    var newDist = Math.min(zoomAmount, lastDirL - navi._vf.typeParams[6]);
    newDist = Math.max(newDist, lastDirL - navi._vf.typeParams[7]);
    
    // move along viewing ray, scaled with zoom factor
    view._from = view._from.addScaled(lastDir, newDist);

    // move along viewing ray, scaled with zoom factor
    //view._from = view._from.addScaled(lastDir, zoomAmount);

    // update camera matrix with lookAt() and invert again
    view._flyMat = x3dom.fields.SFMatrix4f.lookAt(view._from, cor, view._up);
    viewpoint.setView(view._flyMat.inverse());
};

x3dom.TurntableNavigation.prototype.calcOrbit = function (view, alpha, beta)
{
    navi = this.navi;
    
    view._up   = view._flyMat.e1();
    view._from = view._flyMat.e3();

    var offset = view._from.subtract(view._at);

    // angle in xz-plane
    var phi = Math.atan2(offset.x, offset.z);

    // angle from y-axis
    var theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.z * offset.z), offset.y);

    phi -= beta;
    theta -= alpha;

    // clamp theta
    theta = Math.max(navi._vf.typeParams[2], Math.min(navi._vf.typeParams[3], theta));
    
    if(navi._vf.typeParams[4] <= navi._vf.typeParams[5])
        phi = Math.max(navi._vf.typeParams[4], Math.min(navi._vf.typeParams[5], phi));
    else{
        if(beta > 0 && phi < navi._vf.typeParams[4] && phi > navi._vf.typeParams[5]) phi = navi._vf.typeParams[4];
        else if(beta < 0 && phi > navi._vf.typeParams[5] && phi < navi._vf.typeParams[4]) phi = navi._vf.typeParams[5];     
   }
        

    var radius = offset.length();

    // calc new cam position
    var rSinPhi = radius * Math.sin(theta);

    offset.x = rSinPhi * Math.sin(phi);
    offset.y = radius  * Math.cos(theta);
    offset.z = rSinPhi * Math.cos(phi);

    offset = view._at.add(offset);

    // calc new up vector
    theta -= Math.PI / 2;

    var sinPhi = Math.sin(theta);
    var cosPhi = Math.cos(theta);
    var up = new x3dom.fields.SFVec3f(sinPhi * Math.sin(phi), cosPhi, sinPhi * Math.cos(phi));

    if (up.y < 0)
        up = up.negate();

    return x3dom.fields.SFMatrix4f.lookAt(offset, view._at, up);
};

x3dom.TurntableNavigation.prototype.initTurnTable = function(view, flyTo)
{
    var navi = this.navi;
    
    flyTo = (flyTo == undefined) ? true : flyTo;

    var currViewMat = view.getViewMatrix();

    var viewpoint = view._scene.getViewpoint();
    var center = x3dom.fields.SFVec3f.copy(viewpoint.getCenterOfRotation());

    view._flyMat = currViewMat.inverse();

    view._from = viewpoint._vf.position;
    view._at = center;
    view._up = new x3dom.fields.SFVec3f(0,1,0);

    view._flyMat = x3dom.fields.SFMatrix4f.lookAt(view._from, view._at, view._up);
    view._flyMat = this.calcOrbit(view, 0, 0);

    var dur = 0.0;

    if (flyTo) {
        dur = 0.2 / navi._vf.speed;   // fly to pivot point
    }

    view.animateTo(view._flyMat.inverse(), viewpoint, dur);
    view.resetNavHelpers();
};

x3dom.TurntableNavigation.prototype.onMousePress = function(view,x, y, buttonState)
{
    if (!view._flyMat)
        this.initTurnTable(view, false);
};

x3dom.TurntableNavigation.prototype.init = function(view, flyTo)
{
    this.initTurnTable(view, false);
};

x3dom.TurntableNavigation.prototype.resetView = function(view)
{
    view._mixer.beginTime = view._lastTS;
    view._mixer.endTime = view._lastTS + this.navi._vf.transitionTime;

    view._mixer.setBeginMatrix(view.getViewMatrix());

    var target = view._scene.getViewpoint();
    target.resetView();
    
    target = x3dom.fields.SFMatrix4f.lookAt(target._vf.position, target.getCenterOfRotation(), new x3dom.fields.SFVec3f(0,1,0));

    view._mixer.setEndMatrix(target.inverse());
    
    this.updateFlyMat(view);
}

x3dom.TurntableNavigation.prototype.updateFlyMat = function(view, nextViewpoint)
{
    if (!view._flyMat)
        this.initTurnTable(view, false);
    
    var currViewMat = view.getViewMatrix();
    
    var viewpoint = nextViewpoint;
    if(viewpoint == null || !x3dom.isa(viewpoint,x3dom.nodeTypes.X3DViewpointNode))
        viewpoint = view._scene.getViewpoint();
    var center = x3dom.fields.SFVec3f.copy(viewpoint.getCenterOfRotation());

    view._flyMat = currViewMat.inverse();

    view._from = viewpoint._vf.position;
    view._at = center;
    view._up = new x3dom.fields.SFVec3f(0,1,0);
    
    view._flyMat = x3dom.fields.SFMatrix4f.lookAt(view._from, view._at, view._up);    
}

x3dom.TurntableNavigation.prototype.animateTo = function(view, target, prev, dur)
{
    var navi = this.navi;
    var targetMat;
    
    if (x3dom.isa(target, x3dom.nodeTypes.X3DViewpointNode)) {
        targetMat = x3dom.fields.SFMatrix4f.lookAt(target._vf.position, target.getCenterOfRotation(), new x3dom.fields.SFVec3f(0,1,0));        
    }else
        targetMat = target;

    if (navi._vf.transitionType[0].toLowerCase() !== "teleport" && dur != 0 && navi.getType() !== "game")
    {
        if (prev && x3dom.isa(prev, x3dom.nodeTypes.X3DViewpointNode)) {
            prev = prev.getViewMatrix().mult(prev.getCurrentTransform().inverse()).
                         mult(view._transMat).mult(view._rotMat);

            view._mixer.beginTime = view._lastTS;

            if (arguments.length >= 4 && arguments[3] != null) {
                // for lookAt to assure travel speed of 1 m/s
                view._mixer.endTime = view._lastTS + dur;
            }
            else {
                view._mixer.endTime = view._lastTS + navi._vf.transitionTime;
            }

            view._mixer.setBeginMatrix (prev);
            view._mixer.setEndMatrix (targetMat.inverse());
            
            view._scene.getViewpoint().setView(prev);
        }
        else {
            view._scene.getViewpoint().setView(targetMat.inverse());
        }
    }
    else
    {
        view._scene.getViewpoint().setView(target);
    }

    view._rotMat = x3dom.fields.SFMatrix4f.identity();
    view._transMat = x3dom.fields.SFMatrix4f.identity();
    view._movement = new x3dom.fields.SFVec3f(0, 0, 0);
    view._needNavigationMatrixUpdate = true;
    
    this.updateFlyMat(view, target);
}

x3dom.TurntableNavigation.prototype.onTouchStart = function(view, evt, touches)
{
    console.log("touchStart "+evt.touches.length);
    console.log(evt);
    
    view._numTouches = evt.touches.length;
    
    view._lastX = evt.touches[0].screenX;
    view._lastY = evt.touches[0].screenY;
};

x3dom.TurntableNavigation.prototype.onTouchDrag = function(view, evt, touches, translation, rotation)
{
    if (view._currentInputType == x3dom.InputTypes.NAVIGATION)
    {
        if(evt.touches.length == 1)
        {
            var dx = (evt.touches[0].screenX - view._lastX);
            var dy = (evt.touches[0].screenY - view._lastY);
            
            var alpha = (dy * 2 * Math.PI) / view._height;
            var beta = (dx * 2 * Math.PI) / view._width;
        
            this.rotate(view, alpha, beta);
        
            view._lastX = evt.touches[0].screenX;
            view._lastY = evt.touches[0].screenY;            
        }
        else if(evt.touches.length >= 2)
        {
            if(this.panEnabled == true)
                this.pan(view, -translation.x * 4.0, -translation.y * 4.0);
            
            this.zoom(view, translation.z * 4.0);
        }
    }
};

x3dom.TurntableNavigation.prototype.onTouchEnd = function(view, evt, touches)
{
    console.log("touchEnd "+evt.touches.length);
    console.log(evt);

    if (view._numTouches == 2 && evt.touches.length == 1){
        view._lastX = evt.touches[0].screenX;
        view._lastY = evt.touches[0].screenY;
    }
    
    view._numTouches = evt.touches.length;
};

x3dom.TurntableNavigation.prototype.onDoubleClick = function (view, x, y)
{
    
};

x3dom.TurntableNavigation.prototype.setPanTarget = function(target)
{
    this.target = target;
};

x3dom.TurntableNavigation.prototype.setPanAxis = function(a, b)
{
    this.panAxisX = a;
    this.panAxisY = b;
};

x3dom.TurntableNavigation.prototype.setPanEnabled = function(enabled)
{
    this.panEnabled = enabled;
};
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DFontStyleNode ### */
x3dom.registerNodeType(
    "X3DFontStyleNode",
    "Text",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for X3DFontStyleNode
         * @constructs x3dom.nodeTypes.X3DFontStyleNode
         * @x3d 3.3
         * @component Text
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base node type for all font style nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DFontStyleNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### FontStyle ### */
x3dom.registerNodeType(
    "FontStyle",
    "Text",
    defineClass(x3dom.nodeTypes.X3DFontStyleNode,
        
        /**
         * Constructor for FontStyle
         * @constructs x3dom.nodeTypes.FontStyle
         * @x3d 3.3
         * @component Text
         * @status full
         * @extends x3dom.nodeTypes.X3DFontStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The FontStyle node defines the size, family, and style used for Text nodes, as well as the direction of the text strings and any language-specific rendering techniques used for non-English text.
         */
        function (ctx) {
            x3dom.nodeTypes.FontStyle.superClass.call(this, ctx);


            /**
             * Defines the text family.
             * @var {x3dom.fields.MFString} family
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue ['SERIF']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'family', ['SERIF']);

            /**
             * Specifies whether the text is shown horizontally or vertically.
             * @var {x3dom.fields.SFBool} horizontal
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'horizontal', true);

            /**
             * Specifies where the text is anchored. The default of ["MIDDLE", "MIDDLE"] deviates from the ISO spec. default
             * of ["BEGIN", "FIRST"] for backward compatibiliy reasons. 
             * @var {x3dom.fields.MFString} justify
             * @range ["BEGIN","END","FIRST","MIDDLE",""]
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue ['MIDDLE', 'MIDDLE']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'justify', ['MIDDLE', 'MIDDLE']);

            /**
             * The language field specifies the context of the language for the text string in the form of a language and a country in which that language is used.
             * @var {x3dom.fields.SFString} language
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'language', "");

            /**
             * Specifies whether the text is shown from left to right or from right to left.
             * @var {x3dom.fields.SFBool} leftToRight
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'leftToRight', true);

            /**
             * Sets the size of the text.
             * @var {x3dom.fields.SFFloat} size
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'size', 1.0);

            /**
             * Sets the spacing between lines of text, relative to the text size.
             * @var {x3dom.fields.SFFloat} spacing
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'spacing', 1.0);

            /**
             * Sets the text style.
             * @var {x3dom.fields.SFString} style
             * @range ["PLAIN","BOLD","ITALIC","BOLDITALIC",""]
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue "PLAIN"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'style', "PLAIN");

            /**
             * Specifies whether the text flows from top to bottom or from bottom to top.
             * @var {x3dom.fields.SFBool} topToBottom
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'topToBottom', true);
            
            /**
             * Sets the quality of the text rendering as an oversampling factor.
             * @var {x3dom.fields.SFFloat} quality
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue 2.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'quality', 2.0);

        
        },
        {
            fieldChanged: function(fieldName) {
                if (fieldName == 'family' || fieldName == 'horizontal' || fieldName == 'justify' ||
                    fieldName == 'language' || fieldName == 'leftToRight' || fieldName == 'size' ||
                    fieldName == 'spacing' || fieldName == 'style' || fieldName == 'topToBottom') {
                    Array.forEach(this._parentNodes, function (node) {
                        Array.forEach(node._parentNodes, function (textnode) {
                            textnode.setAllDirty();
                        });
                    });
                }
            }
        }
    )
);

x3dom.nodeTypes.FontStyle.defaultNode = function() {
    if (!x3dom.nodeTypes.FontStyle._defaultNode) {
        x3dom.nodeTypes.FontStyle._defaultNode = new x3dom.nodeTypes.FontStyle();
        x3dom.nodeTypes.FontStyle._defaultNode.nodeChanged();
    }
    return x3dom.nodeTypes.FontStyle._defaultNode;
};

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Text ### */
x3dom.registerNodeType(
    "Text",
    "Text",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for Text
         * @constructs x3dom.nodeTypes.Text
         * @x3d 3.3
         * @component Text
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Text node specifies a two-sided, flat text string object positioned in the Z=0 plane of the local coordinate system based on values defined in the fontStyle field.
         * Text nodes may contain multiple text strings specified using the UTF-8 encoding.
         * The text strings are stored in the order in which the text mode characters are to be produced as defined by the parameters in the FontStyle node.
         */
        function (ctx) {
            x3dom.nodeTypes.Text.superClass.call(this, ctx);


            /**
             * The text strings are contained in the string field.
             * @var {x3dom.fields.MFString} string
             * @memberof x3dom.nodeTypes.Text
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'string', []);

            /**
             * The length field contains an MFFloat value that specifies the length of each text string in the local coordinate system.
             * The length of each line of type is measured horizontally for horizontal text (FontStyle node: horizontal=TRUE) and vertically for vertical text (FontStyle node: horizontal=FALSE).
             * @var {x3dom.fields.MFFloat} length
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Text
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'length', []);

            /**
             * The maxExtent field limits and compresses all of the text strings if the length of the maximum string is longer than the maximum extent, as measured in the local coordinate system. If the text string with the maximum length is shorter than the maxExtent, then there is no compressing.
             * The maximum extent is measured horizontally for horizontal text (FontStyle node: horizontal=TRUE) and vertically for vertical text (FontStyle node: horizontal=FALSE).
             * @var {x3dom.fields.SFFloat} maxExtent
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Text
             * @initvalue 0.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'maxExtent', 0.0);

            /**
             * The fontStyle field contains one FontStyle node that specifies the font size, font family and style, direction of the text strings, and any specific language rendering techniques used for the text.
             * @var {x3dom.fields.SFNode} fontStyle
             * @memberof x3dom.nodeTypes.Text
             * @initvalue x3dom.nodeTypes.X3DFontStyleNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode ('fontStyle', x3dom.nodeTypes.X3DFontStyleNode);

            this._mesh._positions[0] = [0,0,0, 1,0,0, 1,1,0, 0,1,0];
            this._mesh._normals[0]   = [0,0,1, 0,0,1, 0,0,1, 0,0,1];
            this._mesh._texCoords[0] = [0,0, 1,0, 1,1, 0,1];
            this._mesh._colors[0] 	 = [];
            this._mesh._indices[0] 	 = [0,1,2, 2,3,0];
            this._mesh._invalidate 	 = true;
            this._mesh._numFaces 	 = 2;
            this._mesh._numCoords 	 = 4;
        
        },
        {
            nodeChanged: function() {
                if (!this._cf.fontStyle.node) {
                    this.addChild(x3dom.nodeTypes.FontStyle.defaultNode());
                }
                this.invalidateVolume();
            },

            fieldChanged: function(fieldName) {
                if (fieldName == 'string' || fieldName == 'length' || fieldName == 'maxExtent') {
                    this.invalidateVolume();
                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                    });
                }
            }
        }
    ) // defineClass
); // registerNodeType

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DSoundNode ### */
x3dom.registerNodeType(
    "X3DSoundNode",
    "Sound",
    defineClass(x3dom.nodeTypes.X3DChildNode,
        
        /**
         * Constructor for X3DSoundNode
         * @constructs x3dom.nodeTypes.X3DSoundNode
         * @x3d 3.3
         * @component Sound
         * @status full
         * @extends x3dom.nodeTypes.X3DChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @CLASSDESC This abstract node type is the base for all sound nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DSoundNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Sound ### */
x3dom.registerNodeType(
    "Sound",
    "Sound",
    defineClass(x3dom.nodeTypes.X3DSoundNode,
        
        /**
         * Constructor for Sound
         * @constructs x3dom.nodeTypes.Sound
         * @x3d 3.3
         * @component Sound
         * @status experimental
         * @extends x3dom.nodeTypes.X3DSoundNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Sound node specifies the spatial presentation of a sound in a X3D scene.
         */
        function (ctx) {
            x3dom.nodeTypes.Sound.superClass.call(this, ctx);


            /**
             * The source field specifies the sound source for the Sound node. If the source field is not specified, the Sound node will not emit audio.
             * The source field shall specify either an AudioClip node or a MovieTexture node.
             * If a MovieTexture node is specified as the sound source, the MovieTexture shall refer to a movie format that supports sound.
             * @var {x3dom.fields.SFNode} source
             * @memberof x3dom.nodeTypes.Sound
             * @initvalue x3dom.nodeTypes.X3DSoundSourceNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('source', x3dom.nodeTypes.X3DSoundSourceNode);
        
        },
        {
            nodeChanged: function()
            {
                if (this._cf.source.node || !this._xmlNode) {
                    return;
                }

                x3dom.debug.logInfo("No AudioClip child node given, searching for &lt;audio&gt; elements...");
                /** USAGE e.g.:
                 <sound>
                 <audio src='sound/spita.wav' loop='loop'></audio>
                 </sound>
                 */
                try {
                    Array.forEach( this._xmlNode.childNodes, function (childDomNode) {
                        if (childDomNode.nodeType === 1)
                        {
                            // For testing: look for <audio> element if no child
                            x3dom.debug.logInfo("### Found &lt;"+childDomNode.nodeName+"&gt; tag.");

                            if (childDomNode.localName.toLowerCase() === "audio")
                            {
                                var loop = childDomNode.getAttribute("loop");
                                loop = loop ? (loop.toLowerCase() === "loop") : false;

                                // TODO; check if crash still exists and clean-up code
                                // work around strange crash in Chrome
                                // by creating new audio element here

                                /*
                                 var src = childDomNode.getAttribute("src");
                                 var newNode = document.createElement('audio');
                                 newNode.setAttribute('autobuffer', 'true');
                                 newNode.setAttribute('src', src);
                                 */
                                var newNode = childDomNode.cloneNode(false);

                                childDomNode.parentNode.removeChild(childDomNode);
                                childDomNode = null;

                                if(navigator.appName != "Microsoft Internet Explorer") {
                                    document.body.appendChild(newNode);
                                }

                                var startAudio = function() {
                                    newNode.play();
                                };

                                var audioDone = function() {
                                    if (loop) {
                                        newNode.play();
                                    }
                                };

                                newNode.addEventListener("canplaythrough", startAudio, true);
                                newNode.addEventListener("ended", audioDone, true);
                            }
                        }
                    } );
                }
                catch(e) {
                    x3dom.debug.logException(e);
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DSoundSourceNode ### */
x3dom.registerNodeType(
    "X3DSoundSourceNode",
    "Sound",
    defineClass(x3dom.nodeTypes.X3DTimeDependentNode,
        
        /**
         * Constructor for X3DSoundSourceNode
         * @constructs x3dom.nodeTypes.X3DSoundSourceNode
         * @x3d 3.3
         * @component Sound
         * @status experimental
         * @extends x3dom.nodeTypes.X3DTimeDependentNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is used to derive node types that can emit audio data.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DSoundSourceNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### AudioClip ### */
x3dom.registerNodeType(
    "AudioClip",
    "Sound",
    defineClass(x3dom.nodeTypes.X3DSoundSourceNode,
        
        /**
         * Constructor for AudioClip
         * @constructs x3dom.nodeTypes.AudioClip
         * @x3d 3.3
         * @component Sound
         * @status experimental
         * @extends x3dom.nodeTypes.X3DSoundSourceNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc An AudioClip node specifies audio data that can be referenced by Sound nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.AudioClip.superClass.call(this, ctx);


            /**
             * The url field specifies the URL from which the sound is loaded.
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.AudioClip
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'url', []);

            /**
             * Specifies whether the clip is enabled.
             * @var {x3dom.fields.SFBool} enabled
             * @memberof x3dom.nodeTypes.AudioClip
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'enabled', false);

            /**
             * Specifies whether the clip loops when finished.
             * @var {x3dom.fields.SFBool} loop
             * @memberof x3dom.nodeTypes.AudioClip
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'loop', false);

            this._audio = document.createElement('audio');
            //this._audio.setAttribute('preload', 'none');
            //this._audio.setAttribute('autoplay', 'true');
            if(navigator.appName != "Microsoft Internet Explorer") {
                document.body.appendChild(this._audio);
            }

            this._sources = [];

        },
        {
            nodeChanged: function()
            {
                this._createSources = function()
                {
                    this._sources = [];
                    for (var i=0; i<this._vf.url.length; i++)
                    {
                        var audioUrl = this._nameSpace.getURL(this._vf.url[i]);
                        x3dom.debug.logInfo('Adding sound file: ' + audioUrl);
                        var src = document.createElement('source');
                        src.setAttribute('src', audioUrl);
                        this._sources.push(src);
                        this._audio.appendChild(src);
                    }
                };

                var that = this;

                this._startAudio = function()
                {
                    that._audio.loop = that._vf.loop ? "loop" : "";
                    if(that._vf.enabled === true)
                    {
                        that._audio.play();
                    }
                };

                this._stopAudio = function()
                {
                    that._audio.pause();
                };

                this._audioEnded = function()
                {
                    if (that._vf.enabled === true && that._vf.loop === true)
                    {
                        that._startAudio();
                    }
                };

                var log = function(e)
                {
                    x3dom.debug.logWarning("MediaEvent error:"+e);
                };

                this._audio.addEventListener("canplaythrough", this._startAudio, true);
                this._audio.addEventListener("ended", this._audioEnded, true);
                this._audio.addEventListener("error", log, true);
                this._audio.addEventListener("pause", this._audioEnded, true);

                this._createSources();
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName === "enabled")
                {
                    if (this._vf.enabled === true)
                    {
                        this._startAudio();
                    }
                    else
                    {
                        this._stopAudio();
                    }
                }
                else if (fieldName === "loop")
                {
                    //this._audio.loop = this._vf.loop;
                }
                else if (fieldName === "url")
                {
                    this._stopAudio();
                    while (this._audio.hasChildNodes())
                    {
                        this._audio.removeChild(this._audio.firstChild);
                    }

                    for (var i=0; i<this._vf.url.length; i++)
                    {
                        var audioUrl = this._nameSpace.getURL(this._vf.url[i]);
                        x3dom.debug.logInfo('Adding sound file: ' + audioUrl);
                        var src = document.createElement('source');
                        src.setAttribute('src', audioUrl);
                        this._audio.appendChild(src);
                    }
                }
            },

            shutdown: function() {
                if (this._audio) {
                    this._audio.pause();
                    while (this._audio.hasChildNodes()) {
                        this._audio.removeChild(this._audio.firstChild);
                    }
                    document.body.removeChild(this._audio);
                    this._audio = null;
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DTextureTransformNode ### */
x3dom.registerNodeType(
    "X3DTextureTransformNode",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DAppearanceChildNode,
        
        /**
         * Constructor for X3DTextureTransformNode
         * @constructs x3dom.nodeTypes.X3DTextureTransformNode
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DAppearanceChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all node types which specify a transformation of texture coordinates.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DTextureTransformNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TextureTransform ### */
x3dom.registerNodeType(
    "TextureTransform",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DTextureTransformNode,
        
        /**
         * Constructor for TextureTransform
         * @constructs x3dom.nodeTypes.TextureTransform
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureTransformNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The TextureTransform node defines a 2D transformation that is applied to texture coordinates. This node affects the way textures coordinates are applied to the geometric surface. The transformation consists of (in order):
         * a translation; a rotation about the centre point; a non-uniform scale about the centre point.
         */
        function (ctx) {
            x3dom.nodeTypes.TextureTransform.superClass.call(this, ctx);


            /**
             * The center field specifies a translation offset in texture coordinate space about which the rotation and scale fields are applied.
             * @var {x3dom.fields.SFVec2f} center
             * @memberof x3dom.nodeTypes.TextureTransform
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'center', 0, 0);

            /**
             * The rotation field specifies a rotation in angle base units of the texture coordinates about the center point after the scale has been applied.
             * A positive rotation value makes the texture coordinates rotate counterclockwise about the centre, thereby rotating the appearance of the texture itself clockwise.
             * @var {x3dom.fields.SFFloat} rotation
             * @memberof x3dom.nodeTypes.TextureTransform
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'rotation', 0);

            /**
             * The scale field specifies a scaling factor in S and T of the texture coordinates about the center point.
             * @var {x3dom.fields.SFVec2f} scale
             * @memberof x3dom.nodeTypes.TextureTransform
             * @initvalue 1,1
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'scale', 1, 1);

            /**
             * The translation field specifies a translation of the texture coordinates.
             * @var {x3dom.fields.SFVec2f} translation
             * @memberof x3dom.nodeTypes.TextureTransform
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'translation', 0, 0);

            //Tc' = -C * S * R * C * T * Tc
            var negCenter = new x3dom.fields.SFVec3f(-this._vf.center.x, -this._vf.center.y, 1);
            var posCenter = new x3dom.fields.SFVec3f(this._vf.center.x, this._vf.center.y, 0);
            var trans3 = new x3dom.fields.SFVec3f(this._vf.translation.x, this._vf.translation.y, 0);
            var scale3 = new x3dom.fields.SFVec3f(this._vf.scale.x, this._vf.scale.y, 0);

            this._trafo = x3dom.fields.SFMatrix4f.translation(negCenter).
                mult(x3dom.fields.SFMatrix4f.scale(scale3)).
                mult(x3dom.fields.SFMatrix4f.rotationZ(this._vf.rotation)).
                mult(x3dom.fields.SFMatrix4f.translation(posCenter.add(trans3)));
        
        },
        {
            fieldChanged: function (fieldName) {
                //Tc' = -C * S * R * C * T * Tc
                if (fieldName == 'center' || fieldName == 'rotation' ||
                    fieldName == 'scale' || fieldName == 'translation') {

                    var negCenter = new x3dom.fields.SFVec3f(-this._vf.center.x, -this._vf.center.y, 1);
                    var posCenter = new x3dom.fields.SFVec3f(this._vf.center.x, this._vf.center.y, 0);
                    var trans3 = new x3dom.fields.SFVec3f(this._vf.translation.x, this._vf.translation.y, 0);
                    var scale3 = new x3dom.fields.SFVec3f(this._vf.scale.x, this._vf.scale.y, 0);

                    this._trafo = x3dom.fields.SFMatrix4f.translation(negCenter).
                        mult(x3dom.fields.SFMatrix4f.scale(scale3)).
                        mult(x3dom.fields.SFMatrix4f.rotationZ(this._vf.rotation)).
                        mult(x3dom.fields.SFMatrix4f.translation(posCenter.add(trans3)));
                }
            },

            texTransformMatrix: function() {
                return this._trafo;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TextureProperties ### */
x3dom.registerNodeType(
    "TextureProperties",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for TextureProperties
         * @constructs x3dom.nodeTypes.TextureProperties
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc TextureProperties allows fine control over a texture's application.
         * This node can be used to set the texture properties for a node with a textureProperties field.
         * A texture with a TextureProperties node will ignore the repeatS and repeatT fields on the texture.
         */
        function (ctx) {
            x3dom.nodeTypes.TextureProperties.superClass.call(this, ctx);


            /**
             * The anisotropicDegree field describes the minimum degree of anisotropy to account for in texture filtering.
             * A value of 1 implies no anisotropic filtering.
             * Values above the system's maximum supported value will be clamped to the maximum allowed.
             * @var {x3dom.fields.SFFloat} anisotropicDegree
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'anisotropicDegree', 1.0);

            /**
             * The borderColor field describes the color to use for border pixels.
             * @var {x3dom.fields.SFColorRGBA} borderColor
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue 0,0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFColorRGBA(ctx, 'borderColor', 0, 0, 0, 0);

            /**
             * The borderWidth field describes the number of pixels to use for a texture border.
             * @var {x3dom.fields.SFInt32} borderWidth
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'borderWidth', 0);

            /**
             * The boundaryModeS field describes the way S texture coordinate boundaries are handled.
             * @var {x3dom.fields.SFString} boundaryModeS
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue "REPEAT"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'boundaryModeS', "REPEAT");

            /**
             * The boundaryModeS field describes the way T texture coordinate boundaries are handled.
             * @var {x3dom.fields.SFString} boundaryModeT
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue "REPEAT"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'boundaryModeT', "REPEAT");

            /**
             * The boundaryModeS field describes the way R texture coordinate boundaries are handled.
             * This field only applies to three dimensional textures and shall be ignored by other texture types.
             * @var {x3dom.fields.SFString} boundaryModeR
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue "REPEAT"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'boundaryModeR', "REPEAT");

            /**
             * The magnificationFilter field describes the way textures are filtered when the image is smaller than the screen space representation.
             * @var {x3dom.fields.SFString} magnificationFilter
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue "FASTEST"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'magnificationFilter', "FASTEST");

            /**
             * The minificationFilter field describes the way textures are filtered when the image is larger than the screen space representation.
             * Modes with MIPMAP in the name require mipmaps. If mipmaps are not provided, the mode shall pick the corresponding non-mipmapped mode
             * @var {x3dom.fields.SFString} minificationFilter
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue "FASTEST"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'minificationFilter', "FASTEST");

            /**
             * The textureCompression field specifies the preferred image compression method to be used during rendering.
             * @var {x3dom.fields.SFString} textureCompression
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue "FASTEST"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'textureCompression', "FASTEST");

            /**
             * The texturePriority field describes the texture residence priority for allocating texture memory. Zero indicates the lowest priority and 1 indicates the highest priority.
             * @var {x3dom.fields.SFFloat} texturePriority
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'texturePriority', 0);

            /**
             * The generateMipMaps field describes whether mipmaps should be generated for the texture. Mipmaps are required for filtering modes with MIPMAP in their value.
             * @var {x3dom.fields.SFBool} generateMipMaps
             * @memberof x3dom.nodeTypes.TextureProperties
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'generateMipMaps', false);
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (this._vf.hasOwnProperty(fieldName)) {
                    Array.forEach(this._parentNodes, function (texture) {
                        Array.forEach(texture._parentNodes, function (app) {
                            Array.forEach(app._parentNodes, function (shape) {
                                shape._dirty.texture = true;
                            });
                        });
                    });

                    this._nameSpace.doc.needRender = true;
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DTextureNode ### */
x3dom.registerNodeType(
    "X3DTextureNode",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DAppearanceChildNode,
        
        /**
         * Constructor for X3DTextureNode
         * @constructs x3dom.nodeTypes.X3DTextureNode
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DAppearanceChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all node types which specify sources for texture images.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DTextureNode.superClass.call(this, ctx);


            /**
             * Specifies the channel count of the texture. 0 means the system should figure out the count automatically.
             * @var {x3dom.fields.SFInt32} origChannelCount
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.X3DTextureNode
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'origChannelCount', 0);

            /**
             * Sets the url to a resource.
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.X3DTextureNode
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'url', []);

            /**
             * Specifies whether the texture is repeated in s direction.
             * @var {x3dom.fields.SFBool} repeatS
             * @memberof x3dom.nodeTypes.X3DTextureNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'repeatS', true);

            /**
             * Specifies whether the texture is repeated in t direction.
             * @var {x3dom.fields.SFBool} repeatT
             * @memberof x3dom.nodeTypes.X3DTextureNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'repeatT', true);

            /**
             * Specifies whether the texture is scaled to the next highest power of two. (Needed, when texture repeat is enabled and texture size is non power of two)
             * @var {x3dom.fields.SFBool} scale
             * @memberof x3dom.nodeTypes.X3DTextureNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'scale', true);

            /**
             * Cross Origin Mode
             * @var {x3dom.fields.SFString} crossOrigin
             * @memberof x3dom.nodeTypes.X3DTextureNode
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'crossOrigin', '');

            /**
             * Sets a TextureProperty node.
             * @var {x3dom.fields.SFNode} textureProperties
             * @memberof x3dom.nodeTypes.X3DTextureNode
             * @initvalue x3dom.nodeTypes.TextureProperties
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('textureProperties', x3dom.nodeTypes.TextureProperties);

            this._needPerFrameUpdate = false;
            this._isCanvas = false;
            this._type = "diffuseMap";

            this._blending = (this._vf.origChannelCount == 1 || this._vf.origChannelCount == 2);
        
        },
        {
            invalidateGLObject: function ()
            {
                Array.forEach(this._parentNodes, function (app) {
                    Array.forEach(app._parentNodes, function (shape) {
                        // THINKABOUTME: this is a bit ugly, cleanup more generically
                        if (x3dom.isa(shape, x3dom.nodeTypes.X3DShapeNode)) {
                            shape._dirty.texture = true;
                        }
                        else {
                            // Texture maybe in MultiTexture or CommonSurfaceShader
                            Array.forEach(shape._parentNodes, function (realShape) {
                                if (x3dom.isa(realShape, x3dom.nodeTypes.X3DShapeNode)) {
                                    realShape._dirty.texture = true;
                                } else {
                                    Array.forEach(realShape._parentNodes, function (realShape2) {
                                        if (x3dom.isa(realShape2, x3dom.nodeTypes.X3DShapeNode)) {
                                            realShape2._dirty.texture = true;
                                        }
                                    });
                                }
                            });
                        }
                    });
                });

                this._nameSpace.doc.needRender = true;
            },

            parentAdded: function(parent)
            {
                Array.forEach(parent._parentNodes, function (shape) {
                    // THINKABOUTME: this is a bit ugly, cleanup more generically
                    if (x3dom.isa(shape, x3dom.nodeTypes.Shape)) {
                        shape._dirty.texture = true;
                    }
                    else {
                        // Texture maybe in MultiTexture or CommonSurfaceShader
                        Array.forEach(shape._parentNodes, function (realShape) {
                            realShape._dirty.texture = true;
                        });
                    }
                });
            },

            parentRemoved: function(parent)
            {
                Array.forEach(parent._parentNodes, function (shape) {
                    // THINKABOUTME: this is a bit ugly, cleanup more generically
                    if (x3dom.isa(shape, x3dom.nodeTypes.Shape)) {
                        shape._dirty.texture = true;
                    }
                    else {
                        // Texture maybe in MultiTexture or CommonSurfaceShader
                        Array.forEach(shape._parentNodes, function (realShape) {
                            realShape._dirty.texture = true;
                        });
                    }
                });
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "url" || fieldName ==  "origChannelCount" ||
                    fieldName == "repeatS" || fieldName == "repeatT" ||
                    fieldName == "scale" || fieldName == "crossOrigin")
                {
                    var that = this;

                    Array.forEach(this._parentNodes, function (app) {
                        if (x3dom.isa(app, x3dom.nodeTypes.X3DAppearanceNode)) {
                            app.nodeChanged();
                            Array.forEach(app._parentNodes, function (shape) {
                                shape._dirty.texture = true;
                            });
                        }
                        else if (x3dom.isa(app, x3dom.nodeTypes.MultiTexture)) {
                            Array.forEach(app._parentNodes, function (realApp) {
                                realApp.nodeChanged();
                                Array.forEach(realApp._parentNodes, function (shape) {
                                    shape._dirty.texture = true;
                                });
                            });
                        }
                        else if (x3dom.isa(app, x3dom.nodeTypes.ComposedCubeMapTexture)) {
                            Array.forEach(app._parentNodes, function (realApp) {
                                realApp.nodeChanged();
                                Array.forEach(realApp._parentNodes, function (shape) {
                                    shape._dirty.texture = true;
                                });
                            });
                        }
                        else if (x3dom.isa(app, x3dom.nodeTypes.ImageGeometry)) {
                            var cf = null;
                            if (that._xmlNode && that._xmlNode.hasAttribute('containerField')) {
                                cf = that._xmlNode.getAttribute('containerField');
                                app._dirty[cf] = true;
                            }
                        }
                        else if (x3dom.nodeTypes.X3DVolumeDataNode !== undefined) {
                            if (x3dom.isa(app, x3dom.nodeTypes.X3DVolumeRenderStyleNode)) {
                                if (that._xmlNode && that._xmlNode.hasAttribute('containerField')) {
                                    if(app._volumeDataParent){
                                        app._volumeDataParent._dirty.texture = true;
                                    }else{
                                        //Texture maybe under a ComposedVolumeStyle
                                        var volumeDataParent = app._parentNodes[0];
                                        while(!x3dom.isa(volumeDataParent, x3dom.nodeTypes.X3DVolumeDataNode) && x3dom.isa(volumeDataParent, x3dom.nodeTypes.X3DNode)){
                                            volumeDataParent = volumeDataParent._parentNodes[0];
                                        }
                                        if(x3dom.isa(volumeDataParent, x3dom.nodeTypes.X3DNode)){
                                            volumeDataParent._dirty.texture = true;
                                        }
                                    }
                                }
                            } else if (x3dom.isa(app, x3dom.nodeTypes.X3DVolumeDataNode)) {
                                if (that._xmlNode && that._xmlNode.hasAttribute('containerField')) {
                                    app._dirty.texture = true;
                                }
                            }
                        }
                    });
                }
            },

            getTexture: function(pos) {
                if (pos === 0) {
                    return this;
                }
                return null;
            },

            size: function() {
                return 1;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MultiTexture ### */
x3dom.registerNodeType(
    "MultiTexture",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DTextureNode,
        
        /**
         * Constructor for MultiTexture
         * @constructs x3dom.nodeTypes.MultiTexture
         * @x3d 3.3
         * @component Texturing
         * @status experimental
         * @extends x3dom.nodeTypes.X3DTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The MultiTexture node specifies the application of several individual textures to a 3D object to achieve a more complex visual effect.
         * MultiTexture can be used as a value for the texture field in an Appearance node.
         */
        function (ctx) {
            x3dom.nodeTypes.MultiTexture.superClass.call(this, ctx);


            /**
             * The texture field contains a list of texture nodes (e.g., ImageTexture, PixelTexture, and MovieTexture). The texture field may not contain another MultiTexture node.
             * @var {x3dom.fields.MFNode} texture
             * @memberof x3dom.nodeTypes.MultiTexture
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('texture', x3dom.nodeTypes.X3DTextureNode);
        
        },
        {
            getTexture: function(pos) {
                if (pos >= 0 && pos < this._cf.texture.nodes.length) {
                    return this._cf.texture.nodes[pos];
                }
                return null;
            },

            getTextures: function() {
                return this._cf.texture.nodes;
            },

            size: function() {
                return this._cf.texture.nodes.length;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Texture ### */
// intermediate layer to avoid instantiating X3DTextureNode in web profile
x3dom.registerNodeType(
    "Texture",      // X3DTexture2DNode
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DTextureNode,
        
        /**
         * Constructor for Texture
         * @constructs x3dom.nodeTypes.Texture
         * @x3d x.x
         * @component Texturing
         * @status experimental
         * @extends x3dom.nodeTypes.X3DTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc (Abstract) class for 2D Textures.
         */
        function (ctx) {
            x3dom.nodeTypes.Texture.superClass.call(this, ctx);


            /**
             * Specifies whether the children are shown or hidden outside the texture.
             * @var {x3dom.fields.SFBool} hideChildren
             * @memberof x3dom.nodeTypes.Texture
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'hideChildren', true);

            this._video = null;
            this._intervalID = 0;
            this._canvas = null;
        
        },
        {
            nodeChanged: function()
            {
                if (this._vf.url.length || !this._xmlNode) {
                    return;
                }
                x3dom.debug.logInfo("No Texture URL given, searching for &lt;img&gt; elements...");
                var that = this;
                try {
                    Array.forEach( this._xmlNode.childNodes, function (childDomNode) {
                        if (childDomNode.nodeType === 1) {
                            var url = childDomNode.getAttribute("src");
                            // For testing: look for <img> element if url empty
                            if (url) {
                                that._vf.url.push(url);
                                x3dom.debug.logInfo(that._vf.url[that._vf.url.length-1]);

                                if (childDomNode.localName.toLowerCase() === "video") {
                                    that._needPerFrameUpdate = true;
                                    //that._video = childDomNode;

                                    that._video = document.createElement('video');
                                    that._video.setAttribute('preload', 'auto');
                                    that._video.setAttribute('muted', 'muted');
                                    var p = document.getElementsByTagName('body')[0];
                                    p.appendChild(that._video);
                                    that._video.style.display = "none";
                                    that._video.style.visibility = "hidden";
                                }
                            }
                            else if (childDomNode.localName.toLowerCase() === "canvas") {
                                that._needPerFrameUpdate = true;
                                that._isCanvas = true;
                                that._canvas = childDomNode;
                            }

                            if (childDomNode.style && that._vf.hideChildren) {
                                childDomNode.style.display = "none";
                                childDomNode.style.visibility = "hidden";
                            }
                            x3dom.debug.logInfo("### Found &lt;"+childDomNode.nodeName+"&gt; tag.");
                        }
                    } );
                }
                catch(e) {
                    x3dom.debug.logException(e);
                }
            },

            shutdown: function() {
                if (this._video) {
                    this._video.pause();
                    while (this._video.hasChildNodes()) {
                        this._video.removeChild(this._video.firstChild);
                    }
                    document.body.removeChild(this._video);
                    this._video = null;
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### RenderedTexture ### */
x3dom.registerNodeType(
    "RenderedTexture",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DTextureNode,
        
        /**
         * Constructor for RenderedTexture
         * @constructs x3dom.nodeTypes.RenderedTexture
         * @x3d x.x
         * @component Texturing
         * @extends x3dom.nodeTypes.X3DTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This extension provides the ability to dynamically render a partial scenegraph to an offscreen texture that can then be used on the geometry of a node.
         * This can be used in many different ways such as creating mirror effects, inputs to shaders etc.
         * The purpose of this component is to provide for extended visual effects, but not the complete form of offscreen rendering and buffers that would be available to lower-level rendering APIs.
         */
        function (ctx) {
            x3dom.nodeTypes.RenderedTexture.superClass.call(this, ctx);

            if (ctx)
                ctx.doc._nodeBag.renderTextures.push(this);
            else
                x3dom.debug.logWarning("RenderedTexture: No runtime context found!");

            // Original proposal taken from:  http://www.xj3d.org/extensions/render_texture.html
            // http://doc.instantreality.org/documentation/nodetype/RenderedTexture/?filter=None


            /**
             * Specifies the viewpoint that is used to render the texture content.
             * @var {x3dom.fields.SFNode} viewpoint
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue x3dom.nodeTypes.X3DViewpointNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('viewpoint', x3dom.nodeTypes.X3DViewpointNode);

            /**
             * Sets a background texture.
             * @var {x3dom.fields.SFNode} background
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue x3dom.nodeTypes.X3DBackgroundNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('background', x3dom.nodeTypes.X3DBackgroundNode);

            /**
             * Sets a fog node.
             * @var {x3dom.fields.SFNode} fog
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue x3dom.nodeTypes.X3DFogNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('fog', x3dom.nodeTypes.X3DFogNode);    //TODO

            /**
             * Sets a separate, potentially independent, subscene. If unset, the same scene is used.
             * @var {x3dom.fields.SFNode} scene
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue x3dom.nodeTypes.X3DNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('scene', x3dom.nodeTypes.X3DNode);

            /**
             * Defines a list of sibling nodes that should be ignored.
             * @var {x3dom.fields.MFNode} excludeNodes
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue x3dom.nodeTypes.X3DNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('excludeNodes', x3dom.nodeTypes.X3DNode);

            /**
             * Sets the width, height, color components (and number of MRTs).
             * @var {x3dom.fields.MFInt32} dimensions
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue [128,128,4]
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'dimensions', [128, 128, 4]);

            /**
             * Specifies when the texture is updated.
             * @var {x3dom.fields.SFString} update
             * @range ["NONE", "NEXT_FRAME_ONLY", "ALWAYS"]
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue 'NONE'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'update', 'NONE');


            /**
             *Specifies whether normals are shown.
             * @var {x3dom.fields.SFBool} showNormals
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'showNormals', false);

            /**
             * Sets render information for stereo rendering.
             * @var {x3dom.fields.SFString} stereoMode
             * @range ["NONE","LEFT_EYE","RIGHT_EYE"]
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue 'NONE'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'stereoMode', 'NONE');

            /**
             * Sets the eye distance for stereo rendering.
             * @var {x3dom.fields.SFFloat} interpupillaryDistance
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue 0.064
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'interpupillaryDistance', 0.064);

            /**
             * Sets the eye to screen distance in m for stereo rendering.
             * @var {x3dom.fields.SFFloat} eyeToScreenDistance
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue 0.041
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'eyeToScreenDistance', 0.041);

            /**
             * Sets the vertical screen size in m for stereo rendering.
             * @var {x3dom.fields.SFFloat} vScreenSize
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue 0.07074
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'vScreenSize', 0.07074);

            /**
             * Sets the lens Position on Screen.
             * @var {x3dom.fields.SFVec3f} lensCenter
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue 0.05329
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'lensCenter', 0.15197, 0, 0);
            
            /** 
             * Determines if textures shall be treated as depth map.
             * If it is TRUE, then the generated texture will also contain the depth buffer.
             * @var {x3dom.fields.SFBool} depthMap
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'depthMap', false);

            /**
             * Very experimental field to change between DK1 and DK2.
             * @var {x3dom.fields.SFFloat} oculusRiftVersion
             * @memberof x3dom.nodeTypes.RenderedTexture
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'oculusRiftVersion', 1);


            x3dom.debug.assert(this._vf.dimensions.length >= 3,
                "RenderedTexture.dimensions requires at least 3 entries.");
            this._clearParents = true;
            this._needRenderUpdate = true;
            
            this.checkDepthTextureSupport = function() {
                if(this._vf.depthMap && x3dom.caps.DEPTH_TEXTURE === null)
                    x3dom.debug.logWarning("RenderedTexture Node: depth texture extension not supported");
            };
            this.checkDepthTextureSupport();
        },
        {
            nodeChanged: function()
            {
                this._clearParents = true;
                this._needRenderUpdate = true;
            },

            fieldChanged: function(fieldName)
            {
                switch(fieldName)
                {
                    case "excludeNodes":
                        this._clearParents = true;
                        break;
                    case "update":
                        if (this._vf.update.toUpperCase() == "NEXT_FRAME_ONLY" ||
                            this._vf.update.toUpperCase() == "ALWAYS") {
                            this._needRenderUpdate = true;
                        }
                        break;
                    case "depthMap":
                        this.checkDepthTextureSupport();
                        this._x3domTexture.updateTexture();
                        this._needRenderUpdate = true;
                    default:
                        // TODO: dimensions
                        break;
                }
            },

            getViewMatrix: function ()
            {
                if (this._clearParents && this._cf.excludeNodes.nodes.length) {
                    // FIXME; avoid recursions cleverer and more generic than this
                    //        (Problem: nodes in excludeNodes field have this node
                    //         as first parent, which leads to a recursion loop in
                    //         getCurrentTransform()
                    var that = this;

                    Array.forEach(this._cf.excludeNodes.nodes, function(node) {
                        for (var i=0, n=node._parentNodes.length; i < n; i++) {
                            if (node._parentNodes[i] === that) {
                                node._parentNodes.splice(i, 1);
                                node.parentRemoved(that);
                            }
                        }
                    });

                    this._clearParents = false;
                }

                var locScene = this._cf.scene.node;
                var scene = this._nameSpace.doc._scene;
                var vbP = scene.getViewpoint();
                var view = this._cf.viewpoint.node;
                var ret_mat = null;

                if (view === null || view === vbP) {
                    ret_mat = this._nameSpace.doc._viewarea.getViewMatrix();
                }
                else if (locScene && locScene !== scene) {
                    // in case of completely local scene do not transform local viewpoint
                    ret_mat = view.getViewMatrix()
                }
                else {
                    var mat_viewpoint = view.getCurrentTransform();
                    ret_mat = view.getViewMatrix().mult(mat_viewpoint.inverse());
                }

                var stereoMode = this._vf.stereoMode.toUpperCase();
                if (stereoMode != "NONE") {
                    var d = this._vf.interpupillaryDistance / 2;
                    if (stereoMode == "RIGHT_EYE") {
                        d = -d;
                    }
                    var modifier = new x3dom.fields.SFMatrix4f(
                        1, 0, 0, d,
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        0, 0, 0, 1
                    );
                    ret_mat = modifier.mult(ret_mat);
                }

                return ret_mat;
            },

            getProjectionMatrix: function()
            {
                var doc = this._nameSpace.doc;
                var vbP = doc._scene.getViewpoint();
                var view = this._cf.viewpoint.node;
                var ret_mat = null;
                var f, w = this._vf.dimensions[0], h = this._vf.dimensions[1];
                var stereoMode = this._vf.stereoMode.toUpperCase();
                var stereo = (stereoMode != "NONE");

                if (view === null || view === vbP) {
                    ret_mat = x3dom.fields.SFMatrix4f.copy(doc._viewarea.getProjectionMatrix());
                    if (stereo) {
                        f = 2 * Math.atan(this._vf.vScreenSize / (2 * this._vf.eyeToScreenDistance));
                        f = 1 / Math.tan(f / 2);
                    }
                    else {
                        f = 1 / Math.tan(vbP._vf.fieldOfView / 2);
                    }
                    ret_mat._00 = f / (w / h);
                    ret_mat._11 = f;
                }
                else {
                    ret_mat = view.getProjectionMatrix(w / h);
                }

                if (stereo) {
                    var lensCenter = this._vf.lensCenter.copy();
                    if (stereoMode == "RIGHT_EYE") {
                        lensCenter.x = -lensCenter.x;
                    }
                    var modifier = new x3dom.fields.SFMatrix4f(
                        1, 0, 0, lensCenter.x,
                        0, 1, 0, lensCenter.y,
                        0, 0, 1, lensCenter.z,
                        0, 0, 0, 1
                    );
                    ret_mat = modifier.mult(ret_mat);
                }

                return ret_mat;
            },

            getWCtoCCMatrix: function()
            {
                var view = this.getViewMatrix();
                var proj = this.getProjectionMatrix();

                return proj.mult(view);
            },

            parentRemoved: function(parent)
            {
                if (this._parentNodes.length === 0) {
                    var doc = this.findX3DDoc();

                    for (var i=0, n=doc._nodeBag.renderTextures.length; i<n; i++) {
                        if (doc._nodeBag.renderTextures[i] === this) {
                            doc._nodeBag.renderTextures.splice(i, 1);
                        }
                    }
                }

                if (this._cf.scene.node) {
                    this._cf.scene.node.parentRemoved(this);
                }
            },

            requirePingPong: function()
            {
                return false;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### RefinementTexture ### */
x3dom.registerNodeType(
    "RefinementTexture",
    "Texturing",
    defineClass(x3dom.nodeTypes.RenderedTexture,
        
        /**
         * Constructor for RefinementTexture
         * @constructs x3dom.nodeTypes.RefinementTexture
         * @x3d x.x
         * @component Texturing
         * @extends x3dom.nodeTypes.RenderedTexture
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         */
        function (ctx) {
            x3dom.nodeTypes.RefinementTexture.superClass.call(this, ctx);

            /**
             * Specifies the first stamp texture.
             * @var {x3dom.fields.SFString} stamp0
             * @memberof x3dom.nodeTypes.RefinementTexture
             * @initvalue "gpuii/stamps/0.gif"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'stamp0', "gpuii/stamps/0.gif");

            /**
             * Specifies the second stamp texture.
             * @var {x3dom.fields.SFString} stamp1
             * @memberof x3dom.nodeTypes.RefinementTexture
             * @initvalue "gpuii/stamps/1.gif"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'stamp1', "gpuii/stamps/1.gif");

            /**
             * Defines whether texture refinement should be managed by another component.
             * @var {x3dom.fields.SFBool} autoRefinement
             * @memberof x3dom.nodeTypes.RefinementTexture
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'autoRefinement', true);

            /**
             * Specifies the image format of the dataset.
             * @var {x3dom.fields.SFString} format
             * @memberof x3dom.nodeTypes.RefinementTexture
             * @initvalue 'jpg'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'format', 'jpg');

            /**
             * Maximum level that should be loaded (if GSM is smaller than on DSL6000)
             * @var {x3dom.fields.SFInt32} iterations
             * @memberof x3dom.nodeTypes.RefinementTexture
             * @initvalue 7
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'iterations', 7);

            /**
             * Maximum level that should be loaded (if GSM is smaller than on DSL6000)
             * @var {x3dom.fields.SFInt32} maxLevel
             * @memberof x3dom.nodeTypes.RefinementTexture
             * @initvalue this._vf.iterations
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'maxLevel', this._vf.iterations);

            if (this._vf.iterations % 2 === 0) {
                var temp = this._vf.stamp0;
                this._vf.stamp0 = this._vf.stamp1;
                this._vf.stamp1 = temp;
            }

            this._vf.iterations = (this._vf.iterations > 11) ? 11 : this._vf.iterations;
            this._vf.iterations = (this._vf.iterations < 3) ? 3 : this._vf.iterations;
            this._vf.maxLevel = (this._vf.maxLevel > 11) ? 11 : this._vf.maxLevel;
            this._vf.maxLevel = (this._vf.maxLevel < 3) ? 3 : this._vf.maxLevel;
            this._vf.maxLevel = (this._vf.maxLevel > this._vf.iterations) ? this._vf.iterations : this._vf.maxLevel;

            // Additional parameters to control the refinement mechanism on shader
            // TODO: optimize
            var repeatConfig = [
                {x: 4, y: 8}, {x: 8, y: 8}, {x: 8, y: 16},
                {x: 16, y: 16}, {x: 16, y: 32}, {x: 32, y: 32},
                {x: 32, y: 64}, {x: 64, y: 64}, {x: 64, y: 128}
            ];

            // TODO: optimize
            this._repeat = new x3dom.fields.SFVec2f(
                    this._vf.dimensions[0] / repeatConfig[this._vf.iterations - 3].x,
                    this._vf.dimensions[1] / repeatConfig[this._vf.iterations - 3].y
            );
            this._renderedImage = 0;
            this._currLoadLevel = 0;
            this._loadLevel = 1;
        
        },
        {
            nextLevel: function() {
                if (this._loadLevel < this._vf.maxLevel) {
                    this._loadLevel++;
                    this._nameSpace.doc.needRender = true;
                }
            },

            requirePingPong: function() {
                return (this._currLoadLevel <= this._vf.maxLevel &&
                    this._renderedImage < this._loadLevel);
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PixelTexture ### */
x3dom.registerNodeType(
    "PixelTexture",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DTextureNode,
        
        /**
         * Constructor for PixelTexture
         * @constructs x3dom.nodeTypes.PixelTexture
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PixelTexture node defines a 2D image-based texture map as an explicit array of pixel values (image field) and parameters controlling tiling repetition of the texture onto geometry.
         */
        function (ctx) {
            x3dom.nodeTypes.PixelTexture.superClass.call(this, ctx);


            /**
             * The image that delivers the pixel data.
             * @var {x3dom.fields.SFImage} image
             * @memberof x3dom.nodeTypes.PixelTexture
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFImage(ctx, 'image', 0, 0, 0);
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName == "image") {
                    this.invalidateGLObject();
                }
            },

            getWidth: function() {
                return this._vf.image.width;
            },

            getHeight: function() {
                return this._vf.image.height;
            },

            getComponents: function() {
                return this._vf.image.comp;
            },

            setPixel: function(x, y, color, update) {
                update = (update == undefined) ? true : update;

                if (this._x3domTexture) {
                    this._x3domTexture.setPixel(x, y, [
                        color.r*255,
                        color.g*255,
                        color.b*255,
                        color.a*255 ], update );
                    this._vf.image.setPixel(x, y, color);
                }
                else
                {
                    this._vf.image.setPixel(x, y, color);
                    if( update ) {
                        this.invalidateGLObject();
                    }
                }

            },

            getPixel: function(x, y) {
                return this._vf.image.getPixel(x, y);
            },

            setPixels: function(pixels, update) {
                update = (update == undefined) ? true : update;

                this._vf.image.setPixels(pixels);

                if( update ) {
                    this.invalidateGLObject();
                }
            },

            getPixels: function() {
                return this._vf.image.getPixels();
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ImageTexture ### */
x3dom.registerNodeType(
    "ImageTexture",
    "Texturing",
    defineClass(x3dom.nodeTypes.Texture,
        
        /**
         * Constructor for ImageTexture
         * @constructs x3dom.nodeTypes.ImageTexture
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.Texture
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc ImageTexture maps a 2D-image file onto a geometric shape.
         * Texture maps have a 2D coordinate system (s, t) horizontal and vertical, with (s, t) values in range [0.0, 1.0] for opposite corners of the image.
         */
        function (ctx) {
            x3dom.nodeTypes.ImageTexture.superClass.call(this, ctx);
        
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MovieTexture ### */
x3dom.registerNodeType(
    "MovieTexture",
    "Texturing",
    defineClass(x3dom.nodeTypes.Texture,
        
        /**
         * Constructor for MovieTexture
         * @constructs x3dom.nodeTypes.MovieTexture
         * @x3d 3.3
         * @component Texturing
         * @status experimental
         * @extends x3dom.nodeTypes.Texture
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The MovieTexture node defines a time dependent texture map (contained in a movie file) and parameters for controlling the movie and the texture mapping.
         */
        function (ctx) {
            x3dom.nodeTypes.MovieTexture.superClass.call(this, ctx);


            /**
             * Specifies whether the playback restarts after finished.
             * @var {x3dom.fields.SFBool} loop
             * @memberof x3dom.nodeTypes.MovieTexture
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'loop', false);

            /**
             * Specifies the plaback speed.
             * @var {x3dom.fields.SFFloat} speed
             * @memberof x3dom.nodeTypes.MovieTexture
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'speed', 1.0);
            // TODO; implement the following fields...

            /**
             * Sets a time to pause the video.
             * @var {x3dom.fields.SFTime} pauseTime
             * @memberof x3dom.nodeTypes.MovieTexture
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'pauseTime', 0);

            /**
             *
             * @var {x3dom.fields.SFFloat} pitch
             * @memberof x3dom.nodeTypes.MovieTexture
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'pitch', 1.0);

            /**
             * Sets a time to resume from pause.
             * @var {x3dom.fields.SFTime} resumeTime
             * @memberof x3dom.nodeTypes.MovieTexture
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'resumeTime', 0);

            /**
             * Sets a start time for the video.
             * @var {x3dom.fields.SFTime} startTime
             * @memberof x3dom.nodeTypes.MovieTexture
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'startTime', 0);

            /**
             * Sets a stop time for the video.
             * @var {x3dom.fields.SFTime} stopTime
             * @memberof x3dom.nodeTypes.MovieTexture
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFTime(ctx, 'stopTime', 0);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DTextureCoordinateNode ### */
x3dom.registerNodeType(
    "X3DTextureCoordinateNode",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DGeometricPropertyNode,
        
        /**
         * Constructor for X3DTextureCoordinateNode
         * @constructs x3dom.nodeTypes.X3DTextureCoordinateNode
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DGeometricPropertyNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all node types which specify texture coordinates.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DTextureCoordinateNode.superClass.call(this, ctx);
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName === "texCoord" || fieldName === "point" ||
                    fieldName === "parameter" || fieldName === "mode")
                {
                    Array.forEach(this._parentNodes, function (node) {
                        node.fieldChanged("texCoord");
                    });
                }
            },

            parentAdded: function(parent) {
                if (parent._mesh && //parent._cf.coord.node &&
                    parent._cf.texCoord.node !== this) {
                    parent.fieldChanged("texCoord");
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TextureCoordinate ### */
x3dom.registerNodeType(
    "TextureCoordinate",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DTextureCoordinateNode,
        
        /**
         * Constructor for TextureCoordinate
         * @constructs x3dom.nodeTypes.TextureCoordinate
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureCoordinateNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The TextureCoordinate node is a geometry property node that specifies a set of 2D texture coordinates used by vertex-based geometry nodes (EXAMPLE  IndexedFaceSet and ElevationGrid) to map textures to vertices.
         */
        function (ctx) {
            x3dom.nodeTypes.TextureCoordinate.superClass.call(this, ctx);


            /**
             * Specifies the array of texture coordinates.
             * @var {x3dom.fields.MFVec2f} point
             * @memberof x3dom.nodeTypes.TextureCoordinate
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'point', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TextureCoordinateGenerator ### */
x3dom.registerNodeType(
    "TextureCoordinateGenerator",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DTextureCoordinateNode,
        
        /**
         * Constructor for TextureCoordinateGenerator
         * @constructs x3dom.nodeTypes.TextureCoordinateGenerator
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureCoordinateNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc TextureCoordinateGenerator supports the automatic generation of texture coordinates for geometric shapes.
         */
        function (ctx) {
            x3dom.nodeTypes.TextureCoordinateGenerator.superClass.call(this, ctx);


            /**
             * The mode field describes the algorithm used to compute texture coordinates.
             * @var {x3dom.fields.SFString} mode
             * @memberof x3dom.nodeTypes.TextureCoordinateGenerator
             * @initvalue "SPHERE"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'mode', "SPHERE");

            /**
             * Specify the parameters. These are mode dependent.
             * @var {x3dom.fields.MFFloat} parameter
             * @memberof x3dom.nodeTypes.TextureCoordinateGenerator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'parameter', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### MultiTextureCoordinate ### */
x3dom.registerNodeType(
    "MultiTextureCoordinate",
    "Texturing",
    defineClass(x3dom.nodeTypes.X3DTextureCoordinateNode,
        
        /**
         * Constructor for MultiTextureCoordinate
         * @constructs x3dom.nodeTypes.MultiTextureCoordinate
         * @x3d 3.3
         * @component Texturing
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureCoordinateNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc MultiTextureCoordinate supplies multiple texture coordinates per vertex. This node can be used to set the texture coordinates for the different texture channels.
         */
        function (ctx) {
            x3dom.nodeTypes.MultiTextureCoordinate.superClass.call(this, ctx);


            /**
             * Each entry in the texCoord field may contain a TextureCoordinate or TextureCoordinateGenerator node.
             * @var {x3dom.fields.MFNode} texCoord
             * @memberof x3dom.nodeTypes.MultiTextureCoordinate
             * @initvalue x3dom.nodeTypes.X3DTextureCoordinateNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('texCoord', x3dom.nodeTypes.X3DTextureCoordinateNode);
        
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ImageTextureAtlas ### */
x3dom.registerNodeType(
    "ImageTextureAtlas",
    "Texturing",
    defineClass(x3dom.nodeTypes.Texture,

        /**
         * Constructor for ImageTextureAtlas
         * @constructs x3dom.nodeTypes.ImageTextureAtlas
         * @x3d x.x
         * @component Texturing
         * @extends x3dom.nodeTypes.Texture
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is a special helper node to represent tiles for volume rendering.
         */
        function (ctx) {
            x3dom.nodeTypes.ImageTextureAtlas.superClass.call(this, ctx);


            /**
             * Specifies the number of slices in the texture atlas.
             * @var {x3dom.fields.SFInt32} numberOfSlices
             * @memberof x3dom.nodeTypes.ImageTextureAtlas
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'numberOfSlices', 0);

            /**
             * Specifies the slices in x.
             * @var {x3dom.fields.SFInt32} slicesOverX
             * @memberof x3dom.nodeTypes.ImageTextureAtlas
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'slicesOverX', 0);

            /**
             * Specifies the slices in y.
             * @var {x3dom.fields.SFInt32} slicesOverY
             * @memberof x3dom.nodeTypes.ImageTextureAtlas
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'slicesOverY', 0);

        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DEnvironmentTextureNode ### */
x3dom.registerNodeType(
    "X3DEnvironmentTextureNode",
    "CubeMapTexturing",
    defineClass(x3dom.nodeTypes.X3DTextureNode,
        
        /**
         * Constructor for X3DEnvironmentTextureNode
         * @constructs x3dom.nodeTypes.X3DEnvironmentTextureNode
         * @x3d x.x
         * @component CubeMapTexturing
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all node types that specify cubic environment map sources for texture images.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DEnvironmentTextureNode.superClass.call(this, ctx);
        
        },
        {
            getTexUrl: function() {
                return [];  //abstract accessor for gfx
            },

            getTexSize: function() {
                return -1;  //abstract accessor for gfx
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ComposedCubeMapTexture ### */
x3dom.registerNodeType(
    "ComposedCubeMapTexture",
    "CubeMapTexturing",
    defineClass(x3dom.nodeTypes.X3DEnvironmentTextureNode,
        
        /**
         * Constructor for ComposedCubeMapTexture
         * @constructs x3dom.nodeTypes.ComposedCubeMapTexture
         * @x3d 3.3
         * @component CubeMapTexturing
         * @status full
         * @extends x3dom.nodeTypes.X3DEnvironmentTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ComposedCubeMapTexture node defines a cubic environment map source as an explicit set of
         * images drawn from individual 2D texture nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.ComposedCubeMapTexture.superClass.call(this, ctx);


            /**
             * Texture for the back of the cubemap
             * @var {x3dom.fields.SFNode} back
             * @memberof x3dom.nodeTypes.ComposedCubeMapTexture
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3d
             * @instance
             */
            this.addField_SFNode('back',  x3dom.nodeTypes.Texture);

            /**
             * Texture for the front of the cubemap
             * @var {x3dom.fields.SFNode} front
             * @memberof x3dom.nodeTypes.ComposedCubeMapTexture
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3d
             * @instance
             */
            this.addField_SFNode('front',  x3dom.nodeTypes.Texture);

            /**
             * Texture for the bottom of the cubemap
             * @var {x3dom.fields.SFNode} bottom
             * @memberof x3dom.nodeTypes.ComposedCubeMapTexture
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3d
             * @instance
             */
            this.addField_SFNode('bottom', x3dom.nodeTypes.Texture);

            /**
             * Texture for the top of the cubemap
             * @var {x3dom.fields.SFNode} top
             * @memberof x3dom.nodeTypes.ComposedCubeMapTexture
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3d
             * @instance
             */
            this.addField_SFNode('top',    x3dom.nodeTypes.Texture);

            /**
             * Texture for the left side of the cubemap
             * @var {x3dom.fields.SFNode} left
             * @memberof x3dom.nodeTypes.ComposedCubeMapTexture
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3d
             * @instance
             */
            this.addField_SFNode('left',   x3dom.nodeTypes.Texture);

            /**
             * Texture for the right side of the cubemap
             * @var {x3dom.fields.SFNode} right
             * @memberof x3dom.nodeTypes.ComposedCubeMapTexture
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3d
             * @instance
             */
            this.addField_SFNode('right',  x3dom.nodeTypes.Texture);
            this._type = "environmentMap";
        
        },
        {
            getTexUrl: function() {
                return [
                    this._nameSpace.getURL(this._cf.back.node._vf.url[0]),
                    this._nameSpace.getURL(this._cf.front.node._vf.url[0]),
                    this._nameSpace.getURL(this._cf.bottom.node._vf.url[0]),
                    this._nameSpace.getURL(this._cf.top.node._vf.url[0]),
                    this._nameSpace.getURL(this._cf.left.node._vf.url[0]),
                    this._nameSpace.getURL(this._cf.right.node._vf.url[0])
                ];
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### GeneratedCubeMapTexture ### */
x3dom.registerNodeType(
    "GeneratedCubeMapTexture",
    "CubeMapTexturing",
    defineClass(x3dom.nodeTypes.X3DEnvironmentTextureNode,
        
        /**
         * Constructor for GeneratedCubeMapTexture
         * @constructs x3dom.nodeTypes.GeneratedCubeMapTexture
         * @x3d 3.3
         * @component CubeMapTexturing
         * @status experimental
         * @extends x3dom.nodeTypes.X3DEnvironmentTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeneratedCubeMapTexture node defines a cubic environment map that sources its data from
         * internally generated images, rendered from a virtual situated perspective in the scene.
         */
        function (ctx) {
            x3dom.nodeTypes.GeneratedCubeMapTexture.superClass.call(this, ctx);


            /**
             * The size field indicates the resolution of the generated images in number of pixels per side.
             * @var {x3dom.fields.SFInt32} size
             * @memberof x3dom.nodeTypes.GeneratedCubeMapTexture
             * @initvalue 128
             * @range (0, inf)
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'size', 128);

            /**
             * NOT YET IMPLEMENTED:
             * The update field can be used to request a regeneration of the texture. Setting this field to "ALWAYS"
             * will cause the texture to be rendered every frame. A value of "NONE" will stop rendering so that no
             * further updates are performed even if the contained scene graph changes. When the value is set to
             * "NEXT_FRAME_ONLY", it is an instruction to render the texture at the end of this frame, and then not
             * render it again. In this case, the update frame indicator is set to this frame; at the start of the next
             * frame, the update value shall be automatically set back to "NONE" to indicate that the rendering has already taken place.
             * @var {x3dom.fields.SFString} update
             * @memberof x3dom.nodeTypes.GeneratedCubeMapTexture
             * @initvalue 'NONE'
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'update', 'NONE');  // ("NONE"|"NEXT_FRAME_ONLY"|"ALWAYS")

            this._type = "cubeMap";
            x3dom.debug.logWarning("GeneratedCubeMapTexture NYI");   // TODO; impl. in gfx when fbo type ready
        
        },
        {
            getTexSize: function() {
                return this._vf.size;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Uniform ### */
x3dom.registerNodeType(
    "Uniform",
    "Shaders",
    defineClass(x3dom.nodeTypes.Field,
        
        /**
         * Constructor for Uniform
         * @constructs x3dom.nodeTypes.Uniform
         * @x3d x.x
         * @component Shaders
         * @extends x3dom.nodeTypes.Field
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Node representing a uniform.
         */
        function (ctx) {
            x3dom.nodeTypes.Uniform.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### SurfaceShaderTexture ### */
x3dom.registerNodeType(
    "SurfaceShaderTexture",
    "Shaders",
    defineClass(x3dom.nodeTypes.X3DTextureNode,
        
        /**
         * Constructor for SurfaceShaderTexture
         * @constructs x3dom.nodeTypes.SurfaceShaderTexture
         * @x3d x.x
         * @component Shaders
         * @extends x3dom.nodeTypes.X3DTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc A texture reference that can be used as a child of SurfaceShader.
         */
        function (ctx) {
            x3dom.nodeTypes.SurfaceShaderTexture.superClass.call(this, ctx);


            /**
             * Texture coordinate channel to use for this texture.
             * @var {x3dom.fields.SFInt32} textureCoordinatesId
             * @memberof x3dom.nodeTypes.SurfaceShaderTexture
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'textureCoordinatesId', 0);

            /**
             * Texture channels to use for this texture in the form of a glsl swizzle (e.g. "rgb", "abgr", "a").
             *  "DEFAULT" will use the default channels for the slot ("rgb" for colors and normals, "a" for alpha,
             *  shininess, ...).
             * @var {x3dom.fields.SFString} channelMask
             * @memberof x3dom.nodeTypes.SurfaceShaderTexture
             * @initvalue "DEFAULT"
             * @range [rgb,abgr,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'channelMask', "DEFAULT");

            /**
             * Whether texture contains sRGB content and need to be linearized (NOT IMPLEMENTED!).
             * @var {x3dom.fields.SFBool} isSRGB
             * @memberof x3dom.nodeTypes.SurfaceShaderTexture
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'isSRGB', false);

            /**
             * The texture to use.
             * @var {x3dom.fields.SFNode} texture
             * @memberof x3dom.nodeTypes.SurfaceShaderTexture
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('texture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * An optional texture transform.
             * @var {x3dom.fields.SFNode} textureTransform
             * @memberof x3dom.nodeTypes.SurfaceShaderTexture
             * @initvalue x3dom.nodeTypes.X3DTextureTransformNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('textureTransform', x3dom.nodeTypes.X3DTextureTransformNode);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DShaderNode ### */
x3dom.registerNodeType(
    "X3DShaderNode",
    "Shaders",
    defineClass(x3dom.nodeTypes.X3DAppearanceChildNode,
        
        /**
         * Constructor for X3DShaderNode
         * @constructs x3dom.nodeTypes.X3DShaderNode
         * @x3d 3.3
         * @component Shaders
         * @status experimental
         * @extends x3dom.nodeTypes.X3DAppearanceChildNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all node types that specify a programmable shader.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DShaderNode.superClass.call(this, ctx);


            /**
             * The language field is used to indicate to the browser which shading language is used for the source
             *  file(s). This field may be used as a hint for the browser if the shading language is not immediately
             *  determinable from the source (e.g., a generic MIME type of text/plain is returned). A browser may use
             *  this field for determining which node instance will be selected and to ignore languages that it is not
             *  capable of supporting. Three basic language types are defined for this specification and others may be
             *  optionally supported by a browser.
             * @var {x3dom.fields.SFString} language
             * @memberof x3dom.nodeTypes.X3DShaderNode
             * @initvalue ""
             * @range ["Cg"|"GLSL"|"HLSL"|...]
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'language', "");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### CommonSurfaceShader ### */
x3dom.registerNodeType(
    "CommonSurfaceShader",
    "Shaders",
    defineClass(x3dom.nodeTypes.X3DShaderNode,
        
        /**
         * Constructor for CommonSurfaceShader
         * @constructs x3dom.nodeTypes.CommonSurfaceShader
         * @x3d x.x
         * @component Shaders
         * @extends x3dom.nodeTypes.X3DShaderNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Implements the Blinn-Phong BRDF with normal mapping and a perfect specular component.
         */
        function (ctx) {
            x3dom.nodeTypes.CommonSurfaceShader.superClass.call(this, ctx);


            /**
             * Texture coordinate channel that contains the tangents in u.
             * @var {x3dom.fields.SFInt32} tangentTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'tangentTextureCoordinatesId', -1);

            /**
             * Texture coordinate channel that contains the tangents in v.
             * @var {x3dom.fields.SFInt32} binormalTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'binormalTextureCoordinatesId', -1);

            /**
             * The value of emissiveTexture is multiplied by this value. If no texture is set, the value is used
             *  directly.
             * @var {x3dom.fields.SFVec3f} emissiveFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'emissiveFactor', 0, 0, 0);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} emissiveTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'emissiveTextureId', -1);

            /**
             * Texture coordinate channel to use for emissiveTexture.
             * @var {x3dom.fields.SFInt32} emissiveTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'emissiveTextureCoordinatesId', 0);

            /**
             * ChannelMask for emissiveTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} emissiveTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'rgb'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'emissiveTextureChannelMask', 'rgb');

            /**
             * The value of ambientTexture is multiplied by this value. If no texture is set, the value is used
             *  directly.
             * @var {x3dom.fields.SFVec3f} ambientFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0.2,0.2,0.2
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'ambientFactor', 0.2, 0.2, 0.2);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} ambientTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'ambientTextureId', -1);

            /**
             * Texture coordinate channel to use for ambientTexture.
             * @var {x3dom.fields.SFInt32} ambientTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'ambientTextureCoordinatesId', 0);

            /**
             * ChannelMask for ambientTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} ambientTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'rgb'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'ambientTextureChannelMask', 'rgb');

            /**
             * The value of diffuseTexture is multiplied by this value. If no texture is set, the value is used
             *  directly.
             * @var {x3dom.fields.SFVec3f} diffuseFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0.8,0.8,0.8
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'diffuseFactor', 0.8, 0.8, 0.8);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} diffuseTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'diffuseTextureId', -1);

            /**
             * Texture coordinate channel to use for diffuseTexture.
             * @var {x3dom.fields.SFInt32} diffuseTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'diffuseTextureCoordinatesId', 0);

            /**
             * ChannelMask for diffuseTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} diffuseTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'rgb'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'diffuseTextureChannelMask', 'rgb');

            /**
             * The value of specularTexture is multiplied by this value. If no texture is set, the value is used
             *  directly.
             * @var {x3dom.fields.SFVec3f} specularFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'specularFactor', 0, 0, 0);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} specularTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'specularTextureId', -1);

            /**
             * Texture coordinate channel to use for specularTexture.
             * @var {x3dom.fields.SFInt32} specularTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'specularTextureCoordinatesId', 0);

            /**
             * ChannelMask for specularTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} specularTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'rgb'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'specularTextureChannelMask', 'rgb');

            /**
             * The value of shininessTexture is multiplied by this value. If no texture is set, the value is used
             *  directly.
             * @var {x3dom.fields.SFFloat} shininessFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0.2
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'shininessFactor', 0.2);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} shininessTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'shininessTextureId', -1);

            /**
             * Texture coordinate channel to use for shininessTexture.
             * @var {x3dom.fields.SFInt32} shininessTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'shininessTextureCoordinatesId', 0);

            /**
             * ChannelMask for shininessTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} shininessTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'a'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'shininessTextureChannelMask', 'a');

            /**
             * How normals are stored in normalTexture. Currently only "UNORM" (each component packed into a
             *  [0,1] color channel) is supported.
             * @var {x3dom.fields.SFString} normalFormat
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'UNORM'
             * @range [UNORM]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normalFormat', 'UNORM');

            /**
             * Space in which normals in normalTexture are defined.
             * @var {x3dom.fields.SFString} normalSpace
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'TANGENT'
             * @range [TANGENT, OBJECT]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normalSpace', 'TANGENT');

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} normalTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'normalTextureId', -1);

            /**
             * Texture coordinate channel to use for normalTexture.
             * @var {x3dom.fields.SFInt32} normalTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'normalTextureCoordinatesId', 0);

            /**
             * ChannelMask for normalTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} normalTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'rgb'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normalTextureChannelMask', 'rgb');

            /**
             * The value of reflectionTexture is multiplied by this value. If no texture is set, the value is used
             *  directly.
             * @var {x3dom.fields.SFVec3f} reflectionFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'reflectionFactor', 0, 0, 0);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} reflectionTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'reflectionTextureId', -1);

            /**
             * Texture coordinate channel to use for reflectionTexture.
             * @var {x3dom.fields.SFInt32} reflectionTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'reflectionTextureCoordinatesId', 0);

            /**
             * ChannelMask for reflectionTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} reflectionTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'rgb'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'reflectionTextureChannelMask', 'rgb');

            /**
             * The value of transmissionTexture is multiplied by this value. If no texture is set, the value is used
             *  directly.
             * @var {x3dom.fields.SFVec3f} transmissionFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'transmissionFactor', 0, 0, 0);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} transmissionTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'transmissionTextureId', -1);

            /**
             * Texture coordinate channel to use for transmissionTexture.
             * @var {x3dom.fields.SFInt32} transmissionTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'transmissionTextureCoordinatesId', 0);

            /**
             * ChannelMask for transmissionTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} transmissionTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'rgb'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'transmissionTextureChannelMask', 'rgb');

            /**
             * The value of environmentTexture is multiplied by this value. If no texture is set, the value is used
             *  directly.
             * @var {x3dom.fields.SFVec3f} environmentFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'environmentFactor', 1, 1, 1);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} environmentTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'environmentTextureId', -1);

            /**
             * [Currently not used, coordinates are computed in shader.]
             * @var {x3dom.fields.SFInt32} environmentTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'environmentTextureCoordinatesId', 0);

            /**
             * ChannelMask for environmentTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} environmentTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'rgb'
             * @range [rgb,a,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'environmentTextureChannelMask', 'rgb');

            /**
             * Relative IOR for perfect specular component.
             * @var {x3dom.fields.SFFloat} relativeIndexOfRefraction
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'relativeIndexOfRefraction', 1);

            /**
             * To what degree the Fresnel equation for dielectrics should be used to blend the perfect specular
             *  reflection and transmission.
             * @var {x3dom.fields.SFFloat} fresnelBlend
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'fresnelBlend', 0);

            /**
             * Axis along which the vertices are displacement
             * @var {x3dom.fields.SFString} displacementAxis
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'y'
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'displacementAxis', 'y');

            /**
             * Factor for the displacement.
             * @var {x3dom.fields.SFFloat} displacementFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 255.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'displacementFactor', 255.0);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} displacementTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'displacementTextureId', -1);

            /**
             * Texture coordinate channel to use for displacementTexture.
             * @var {x3dom.fields.SFInt32} displacementTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'displacementTextureCoordinatesId', 0);

            /**
             * Texture containing emissive component.
             * @var {x3dom.fields.SFNode} emissiveTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('emissiveTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing ambient component.
             * @var {x3dom.fields.SFNode} ambientTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('ambientTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing diffuse component.
             * @var {x3dom.fields.SFNode} diffuseTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('diffuseTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing specular component.
             * @var {x3dom.fields.SFNode} specularTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('specularTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing shininess component.
             * @var {x3dom.fields.SFNode} shininessTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('shininessTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing normal component for normal mapping.
             * @var {x3dom.fields.SFNode} normalTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('normalTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing reflection component.
             * @var {x3dom.fields.SFNode} reflectionTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('reflectionTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing transmission component.
             * @var {x3dom.fields.SFNode} transmissionTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('transmissionTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Cube texture containing the environment for perfect specular reflection and transmission.
             * @var {x3dom.fields.SFNode} environmentTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('environmentTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing displacement component.
             * @var {x3dom.fields.SFNode} displacementTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('displacementTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Texture containing diffuse displacement component.
             * @var {x3dom.fields.SFNode} diffuseDisplacementTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('diffuseDisplacementTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Multi diffuse alpha texture.
             * @var {x3dom.fields.SFNode} multiDiffuseAlphaTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('multiDiffuseAlphaTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Multi specular shininess texture.
             * @var {x3dom.fields.SFNode} multiSpecularShininessTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('multiSpecularShininessTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Multi emissive ambientIntensity texture.
             * @var {x3dom.fields.SFNode} multiEmmisiveAmbientIntensityTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('multiEmissiveAmbientTexture', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Multi visibility texture.
             * @var {x3dom.fields.SFNode} multiVisibilityTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('multiVisibilityTexture', x3dom.nodeTypes.X3DTextureNode);


            //this.addField_MFBool(ctx, 'textureTransformEnabled', []);     // MFBool NYI

            /**
             * scale to apply to normal sampled from normalTexture
             * @var {x3dom.fields.SFVec3f} normalScale
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 2,2,2
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'normalScale', 2, 2, 2);

            /**
             * Bias to apply to normal sampled from normalTexture
             * @var {x3dom.fields.SFVec3f} normalBias
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1,-1,-1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'normalBias', -1, -1, -1);

            /**
             * The value of alphaTexture is multiplied by this value. If no texture is set, the value is used directly.
             * @var {x3dom.fields.SFFloat} alphaFactor
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'alphaFactor', 1);

            /**
             * If true, (1-sampledValue) is used as alpha. If false the sampled value is used.
             * @var {x3dom.fields.SFBool} invertAlphaTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'invertAlphaTexture', false);

            /**
             * The texture unit.
             * @var {x3dom.fields.SFInt32} alphaTextureId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'alphaTextureId', -1);

            /**
             * Texture coordinate channel to use for alphaTexture.
             * @var {x3dom.fields.SFInt32} alphaTextureCoordinatesId
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'alphaTextureCoordinatesId', 0);

            /**
             * ChannelMask for alphaTexture in the form of a glsl swizzle (e.g. "rgb", "a").
             * @var {x3dom.fields.SFString} alphaTextureChannelMask
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'a'
             * @range [a,rgb,..]
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'alphaTextureChannelMask', 'a');

            /**
             * Texture containing alpha component.
             * @var {x3dom.fields.SFNode} alphaTexture
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('alphaTexture', x3dom.nodeTypes.X3DTextureNode);

            this._dirty = {
                // TODO; cp. Shape, allow for dynamic texture updates in gfx
            };
        
        },
        {
            getDiffuseMap: function()
            {
                if(this._cf.diffuseTexture.node) {
                    if (x3dom.isa(this._cf.diffuseTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.diffuseTexture.node._cf.texture.node._type = "diffuseMap";
                        return this._cf.diffuseTexture.node._cf.texture.node;
                    } else {
                        this._cf.diffuseTexture.node._type = "diffuseMap";
                        return this._cf.diffuseTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getEnvironmentMap: function()
            {
                if(this._cf.environmentTexture.node) {
                    if (x3dom.isa(this._cf.environmentTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.environmentTexture.node._cf.texture.node._type = "environmentMap";
                        return this._cf.environmentTexture.node._cf.texture.node;
                    } else {
                        this._cf.environmentTexture.node._type = "environmentMap";
                        return this._cf.environmentTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getNormalMap: function()
            {
                if(this._cf.normalTexture.node) {
                    if (x3dom.isa(this._cf.normalTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.normalTexture.node._cf.texture.node._type = "normalMap";
                        return this._cf.normalTexture.node._cf.texture.node;
                    } else {
                        this._cf.normalTexture.node._type = "normalMap";
                        return this._cf.normalTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getAmbientMap: function()
            {
                if(this._cf.ambientTexture.node) {
                    if (x3dom.isa(this._cf.ambientTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.ambientTexture.node._cf.texture.node._type = "ambientMap";
                        return this._cf.ambientTexture.node._cf.texture.node;
                    } else {
                        this._cf.ambientTexture.node._type = "ambientMap";
                        return this._cf.ambientTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getSpecularMap: function()
            {
                if(this._cf.specularTexture.node) {
                    if (x3dom.isa(this._cf.specularTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.specularTexture.node._cf.texture.node._type = "specularMap";
                        return this._cf.specularTexture.node._cf.texture.node;
                    } else {
                        this._cf.specularTexture.node._type = "specularMap";
                        return this._cf.specularTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getShininessMap: function()
            {
                if(this._cf.shininessTexture.node) {
                    if (x3dom.isa(this._cf.shininessTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.shininessTexture.node._cf.texture.node._type = "shininessMap";
                        return this._cf.shininessTexture.node._cf.texture.node;
                    } else {
                        this._cf.shininessTexture.node._type = "shininessMap";
                        return this._cf.shininessTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getAlphaMap: function()
            {
                if(this._cf.alphaTexture.node) {
                    if (x3dom.isa(this._cf.alphaTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.alphaTexture.node._cf.texture.node._type = "alphaMap";
                        return this._cf.alphaTexture.node._cf.texture.node;
                    } else {
                        this._cf.alphaTexture.node._type = "alphaMap";
                        return this._cf.alphaTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getDisplacementMap: function()
            {
                if(this._cf.displacementTexture.node) {
                    if (x3dom.isa(this._cf.displacementTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.displacementTexture.node._cf.texture.node._type = "displacementMap";
                        return this._cf.displacementTexture.node._cf.texture.node;
                    } else {
                        this._cf.displacementTexture.node._type = "displacementMap";
                        return this._cf.displacementTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getDiffuseDisplacementMap: function()
            {
                if(this._cf.diffuseDisplacementTexture.node) {
                    if (x3dom.isa(this._cf.diffuseDisplacementTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.diffuseDisplacementTexture.node._cf.texture.node._type = "diffuseDisplacementMap";
                        return this._cf.diffuseDisplacementTexture.node._cf.texture.node;
                    } else {
                        this._cf.diffuseDisplacementTexture.node._type = "diffuseDisplacementMap";
                        return this._cf.diffuseDisplacementTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getMultiDiffuseAlphaMap: function()
            {
                if(this._cf.multiDiffuseAlphaTexture.node) {
                    if (x3dom.isa(this._cf.multiDiffuseAlphaTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.multiDiffuseAlphaTexture.node._cf.texture.node._type = "multiDiffuseAlphaMap";
                        return this._cf.multiDiffuseAlphaTexture.node._cf.texture.node;
                    } else {
                        this._cf.multiDiffuseAlphaTexture.node._type = "multiDiffuseAlphaMap";
                        return this._cf.multiDiffuseAlphaTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getMultiEmissiveAmbientMap: function()
            {
                if(this._cf.multiEmissiveAmbientTexture.node) {
                    if (x3dom.isa(this._cf.multiEmissiveAmbientTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.multiEmissiveAmbientTexture.node._cf.texture.node._type = "multiEmissiveAmbientMap";
                        return this._cf.multiEmissiveAmbientTexture.node._cf.texture.node;
                    } else {
                        this._cf.multiEmissiveAmbientTexture.node._type = "multiEmissiveAmbientMap";
                        return this._cf.multiEmissiveAmbientTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getMultiSpecularShininessMap: function()
            {
                if(this._cf.multiSpecularShininessTexture.node) {
                    if (x3dom.isa(this._cf.multiSpecularShininessTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.multiSpecularShininessTexture.node._cf.texture.node._type = "multiSpecularShininessMap";
                        return this._cf.multiSpecularShininessTexture.node._cf.texture.node;
                    } else {
                        this._cf.multiSpecularShininessTexture.node._type = "multiSpecularShininessMap";
                        return this._cf.multiSpecularShininessTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getMultiVisibilityMap: function()
            {
                if(this._cf.multiVisibilityTexture.node) {
                    if (x3dom.isa(this._cf.multiVisibilityTexture.node, x3dom.nodeTypes.SurfaceShaderTexture)) {
                        this._cf.multiVisibilityTexture.node._cf.texture.node._type = "multiVisibilityMap";
                        return this._cf.multiVisibilityTexture.node._cf.texture.node;
                    } else {
                        this._cf.multiVisibilityTexture.node._type = "multiVisibilityMap";
                        return this._cf.multiVisibilityTexture.node;
                    }
                } else {
                    return null;
                }
            },

            getTextures: function()
            {
                var textures = [];

                var diff = this.getDiffuseMap();
                if(diff) textures.push(diff);

                var norm = this.getNormalMap();
                if(norm) textures.push(norm);

                var spec = this.getSpecularMap();
                if(spec) textures.push(spec);

                var shin = this.getShininessMap();
                if(shin) textures.push(shin);

                var env = this.getEnvironmentMap();
                if(env) textures.push(env);

                var displacement = this.getDisplacementMap();
                if(displacement) textures.push(displacement);

                var diffuseDisplacement = this.getDiffuseDisplacementMap();
                if(diffuseDisplacement) textures.push(diffuseDisplacement);

                var multiDiffuseAlpha = this.getMultiDiffuseAlphaMap();
                if(multiDiffuseAlpha) textures.push(multiDiffuseAlpha);

                var multiEmissiveAmbient = this.getMultiEmissiveAmbientMap();
                if(multiEmissiveAmbient) textures.push(multiEmissiveAmbient);

                var multiSpecularShininess = this.getMultiSpecularShininessMap();
                if(multiSpecularShininess) textures.push(multiSpecularShininess);

                var multiVisibility = this.getMultiVisibilityMap();
                if(multiVisibility) textures.push(multiVisibility);

                return textures;
            },

            needTexcoords: function()
            {

               return ( this.getDiffuseMap()      || this.getNormalMap()    ||
                        this.getSpecularMap()     || this.getShininessMap() ||
                        this.getDisplacementMap() || this.getDiffuseDisplacementMap() ||
                        this.getEnvironmentMap() ) ? true : false;

            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ComposedShader ### */
x3dom.registerNodeType(
    "ComposedShader",
    "Shaders",
    defineClass(x3dom.nodeTypes.X3DShaderNode,
        
        /**
         * Constructor for ComposedShader
         * @constructs x3dom.nodeTypes.ComposedShader
         * @x3d 3.3
         * @component Shaders
         * @status experimental
         * @extends x3dom.nodeTypes.X3DShaderNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ComposedShader node defines a shader where the individual source files are not individually
         *  programmable. All access to the shading capabilities is defined through a single interface that applies to
         *  all parts.
         */
        function (ctx) {
            x3dom.nodeTypes.ComposedShader.superClass.call(this, ctx);


            /**
             * Contains all fields of shader parts.
             * @var {x3dom.fields.MFNode} fields
             * @memberof x3dom.nodeTypes.ComposedShader
             * @initvalue x3dom.nodeTypes.Field
             * @field x3d
             * @instance
             */
            this.addField_MFNode('fields', x3dom.nodeTypes.Field);

            /**
             * List of shader parts.
             * @var {x3dom.fields.MFNode} parts
             * @memberof x3dom.nodeTypes.ComposedShader
             * @initvalue x3dom.nodeTypes.ShaderPart
             * @field x3d
             * @instance
             */
            this.addField_MFNode('parts', x3dom.nodeTypes.ShaderPart);

            // shortcut to shader parts
            this._vertex = null;
            this._fragment = null;
            this._id = null;

            if (!x3dom.nodeTypes.ComposedShader.ShaderInfoMsgShown) {
                x3dom.debug.logInfo("Current ComposedShader node implementation limitations:\n" +
                    "Vertex attributes (if given in the standard X3D fields 'coord', 'color', " +
                    "'normal', 'texCoord'), matrices and texture are provided as follows...\n" +
                    "(see also <a href='http://x3dom.org/x3dom/doc/help/composedShader.html'>" +
                    "http://x3dom.org/x3dom/doc/help/composedShader.html</a>)\n" +
                    "    attribute vec3 position;\n" +
                    "    attribute vec3 normal;\n" +
                    "    attribute vec2 texcoord;\n" +
                    "    attribute vec3 color;\n" +
                    "    uniform mat4 modelViewProjectionMatrix;\n" +
                    "    uniform mat4 modelViewMatrix;\n" +
                    "    uniform mat4 normalMatrix;\n" +
                    "    uniform mat4 viewMatrix;\n" +
                    "    uniform sampler2D tex;\n");
                x3dom.nodeTypes.ComposedShader.ShaderInfoMsgShown = true;
            }
        
        },
        {
            nodeChanged: function()
            {
                var i, n = this._cf.parts.nodes.length;

                for (i=0; i<n; i++)
                {
                    if (this._cf.parts.nodes[i]._vf.type.toLowerCase() == 'vertex') {
                        this._vertex = this._cf.parts.nodes[i];
                        this._id = this._cf.parts.nodes[i]._id;
                    }
                    else if (this._cf.parts.nodes[i]._vf.type.toLowerCase() == 'fragment') {
                        this._fragment = this._cf.parts.nodes[i];
                        this._id += " - " + this._cf.parts.nodes[i]._id;
                    }
                }

                var ctx = {};
                n = this._cf.fields.nodes.length;

                for (i=0; i<n; i++)
                {
                    var fieldName = this._cf.fields.nodes[i]._vf.name;
                    ctx.xmlNode = this._cf.fields.nodes[i]._xmlNode;

                    var needNode = false;

                    if (ctx.xmlNode === undefined || ctx.xmlNode === null) {
                        ctx.xmlNode = document.createElement("field");
                        needNode = true;
                    }

                    ctx.xmlNode.setAttribute(fieldName, this._cf.fields.nodes[i]._vf.value);

                    var funcName = "this.addField_" + this._cf.fields.nodes[i]._vf.type + "(ctx, name);";
                    var func = new Function('ctx', 'name', funcName);

                    func.call(this, ctx, fieldName);

                    if (needNode) {
                        ctx.xmlNode = null;    // cleanup
                    }
                }

                Array.forEach(this._parentNodes, function (app) {
                    Array.forEach(app._parentNodes, function (shape) {
                        //shape.setAppDirty();
                        if (shape._cleanupGLObjects)
                            shape._cleanupGLObjects();
                        shape.setAllDirty();
                    });
                });
            },

            fieldChanged: function(fieldName)
            {
                var i, n = this._cf.fields.nodes.length;

                for (i=0; i<n; i++)
                {
                    var field = this._cf.fields.nodes[i]._vf.name;

                    if (field === fieldName)
                    {
                        var msg = this._cf.fields.nodes[i]._vf.value;

                        try {
                            this._vf[field].setValueByStr(msg);
                        }
                        catch (exc1) {
                            try {
                                switch ((typeof(this._vf[field])).toString()) {
                                    case "number":
                                        this._vf[field] = +msg;
                                        break;
                                    case "boolean":
                                        this._vf[field] = (msg.toLowerCase() === "true");
                                        break;
                                    case "string":
                                        this._vf[field] = msg;
                                        break;
                                }
                            }
                            catch (exc2) {
                                x3dom.debug.logError("setValueByStr() NYI for " + typeof(this._vf[field]));
                            }
                        }

                        break;
                    }
                }

                if (field === 'url')
                {
                    Array.forEach(this._parentNodes, function (app) {
                        Array.forEach(app._parentNodes, function (shape) {
                            shape._dirty.shader = true;
                        });
                    });
                }
            },

            parentAdded: function(parent)
            {
                //Array.forEach(this._parentNodes, function (app) {
                //	app.nodeChanged();
                //});
                parent.nodeChanged();
            }
        }
    )
);

x3dom.nodeTypes.ComposedShader.ShaderInfoMsgShown = false;
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ShaderPart ### */
x3dom.registerNodeType(
    "ShaderPart",
    "Shaders",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for ShaderPart
         * @constructs x3dom.nodeTypes.ShaderPart
         * @x3d 3.3
         * @component Shaders
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ShaderPart node defines the source for a single object to be used by a ComposedShader node.
         *  The source is not required to be a complete shader for all of the vertex/fragment processing.
         */
        function (ctx) {
            x3dom.nodeTypes.ShaderPart.superClass.call(this, ctx);


            /**
             * The shader source is read from the URL specified by the url field. When the url field contains no values
             *  ([]), this object instance is ignored.
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.ShaderPart
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'url', []);

            /**
             * The type field indicates whether this object shall be compiled as a vertex shader, fragment shader, or
             *  other future-defined shader type.
             * @var {x3dom.fields.SFString} type
             * @memberof x3dom.nodeTypes.ShaderPart
             * @initvalue "VERTEX"
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'type', "VERTEX");

            this._id = (ctx && ctx.xmlNode && ctx.xmlNode.id != "") ?
                ctx.xmlNode.id : ++x3dom.nodeTypes.Shape.shaderPartID;

            x3dom.debug.assert(this._vf.type.toLowerCase() == 'vertex' ||
                               this._vf.type.toLowerCase() == 'fragment',
                               "Unknown shader part type!");
        },
        {
            nodeChanged: function()
            {
                var ctx = {};
                ctx.xmlNode = this._xmlNode;

                if (ctx.xmlNode !== undefined && ctx.xmlNode !== null)
                {
                    var that = this;

                    if (that._vf.url.length && that._vf.url[0].indexOf('\n') == -1)
                    {
                        var xhr = new XMLHttpRequest();
                        xhr.open("GET", that._nameSpace.getURL(that._vf.url[0]), false);
                        xhr.onload = function() {
                            that._vf.url = new x3dom.fields.MFString( [] );
                            that._vf.url.push(xhr.response);
                        };
                        xhr.onerror = function() {
                            x3dom.debug.logError("Could not load file '" + that._vf.url[0] + "'.");
                        };
                        //xhr.send(null);
                        x3dom.RequestManager.addRequest( xhr );
                    }
                    else
                    {
                        if (that._vf.url.length) {
                            that._vf.url = new x3dom.fields.MFString( [] );
                        }
                        try {
                            that._vf.url.push(ctx.xmlNode.childNodes[1].nodeValue);
                            ctx.xmlNode.removeChild(ctx.xmlNode.childNodes[1]);
                        }
                        catch(e) {
                            Array.forEach( ctx.xmlNode.childNodes, function (childDomNode) {
                                if (childDomNode.nodeType === 3) {
                                    that._vf.url.push(childDomNode.nodeValue);
                                }
                                else if (childDomNode.nodeType === 4) {
                                    that._vf.url.push(childDomNode.data);
                                }
                                childDomNode.parentNode.removeChild(childDomNode);
                            } );
                        }
                    }
                }
                // else hope that url field was already set somehow

                Array.forEach(this._parentNodes, function (shader) {
                    shader.nodeChanged();
                });
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName === "url") {
                    Array.forEach(this._parentNodes, function (shader) {
                        shader.fieldChanged("url");
                    });
                }
            },

            parentAdded: function(parent)
            {
                //Array.forEach(this._parentNodes, function (shader) {
                //	shader.nodeChanged();
                //});
                parent.nodeChanged();
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DVertexAttributeNode ### */
x3dom.registerNodeType(
    "X3DVertexAttributeNode",
    "Shaders",
    defineClass(x3dom.nodeTypes.X3DGeometricPropertyNode,
        
        /**
         * Constructor for X3DVertexAttributeNode
         * @constructs x3dom.nodeTypes.X3DVertexAttributeNode
         * @x3d 3.3
         * @component Shaders
         * @status full
         * @extends x3dom.nodeTypes.X3DGeometricPropertyNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all node types that specify per-vertex attribute
         *  information to the shader.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DVertexAttributeNode.superClass.call(this, ctx);


            /**
             * The name field describes a name that is mapped to the shading language-specific name for describing
             *  per-vertex data. The appropriate shader language annex contains language-specific binding information.
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.X3DVertexAttributeNode
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### FloatVertexAttribute ### */
x3dom.registerNodeType(
    "FloatVertexAttribute",
    "Shaders",
    defineClass(x3dom.nodeTypes.X3DVertexAttributeNode,
        
        /**
         * Constructor for FloatVertexAttribute
         * @constructs x3dom.nodeTypes.FloatVertexAttribute
         * @x3d 3.3
         * @component Shaders
         * @status experimental
         * @extends x3dom.nodeTypes.X3DVertexAttributeNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The FloatVertexAttribute node defines a set of per-vertex single-precision floating point
         *  attributes.
         */
        function (ctx) {
            x3dom.nodeTypes.FloatVertexAttribute.superClass.call(this, ctx);


            /**
             * The numComponents field specifies how many consecutive floating point values should be grouped together
             *  per vertex. The length of the value field shall be a multiple of numComponents.
             * @var {x3dom.fields.SFInt32} numComponents
             * @memberof x3dom.nodeTypes.FloatVertexAttribute
             * @initvalue 4
             * @range [1..4]
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'numComponents', 4);

            /**
             * The value field specifies an arbitrary collection of floating point values that will be passed to the
             *  shader as per-vertex information. The specific type mapping to the individual shading language data
             *  types is in the appropriate language-specific annex.
             * @var {x3dom.fields.MFFloat} value
             * @memberof x3dom.nodeTypes.FloatVertexAttribute
             * @initvalue []
             * @range (-inf, inf)
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'value', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DSpatialGeometryNode ### */
x3dom.registerNodeType(
    "X3DSpatialGeometryNode",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for X3DSpatialGeometryNode
         * @constructs x3dom.nodeTypes.X3DSpatialGeometryNode
         * @x3d x.x
         * @component Geometry3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This is the abstract node for spatial geometry nodes.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DSpatialGeometryNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Plane ### */
x3dom.registerNodeType(
    "Plane",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Plane
         * @constructs x3dom.nodeTypes.Plane
         * @x3d x.x
         * @component Geometry3D
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @class The plane node describes a plane shape that extents in x and y direction.
         */
        function (ctx) {
            x3dom.nodeTypes.Plane.superClass.call(this, ctx);


            /**
             * The edge lengths of the plane.
             * @var {x3dom.fields.SFVec2f} size
             * @memberof x3dom.nodeTypes.Plane
             * @initvalue 2,2
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'size', 2, 2);

            /**
             * Defines the number of single elements that are generated to represent the plane.
             * @var {x3dom.fields.SFVec2f} subdivision
             * @memberof x3dom.nodeTypes.Plane
             * @initvalue 1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'subdivision', 1, 1);

            /**
             * Defines the center point in the local coordinate system.
             * @var {x3dom.fields.SFVec3f} center
             * @memberof x3dom.nodeTypes.Plane
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'center', 0, 0, 0);

            /**
             * Specifies the primitive type that is used to build the plane.
             * @var {x3dom.fields.MFString} primType
             * @memberof x3dom.nodeTypes.Plane
             * @initvalue ['TRIANGLES']
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'primType', ['TRIANGLES']);

            // this way currently an initialize only field
            if (this._vf.primType.length)
                this._mesh._primType = this._vf.primType[0];

            var sx = this._vf.size.x, sy = this._vf.size.y;
            var subx = this._vf.subdivision.x, suby = this._vf.subdivision.y;

            var geoCacheID = 'Plane_' + sx + '-' + sy + '-' + subx + '-' + suby + '-' +
                this._vf.center.x + '-' + this._vf.center.y + '-' + this._vf.center.z;

            // Attention: DynamicLOD node internally creates Plane nodes, but MUST NOT
            //            use geoCache, therefore only use cache if "ctx" is defined!
            // TODO: move mesh generation of all primitives to nodeChanged()
            if (ctx && this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Plane from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else {
                var x = 0, y = 0;
                var xstep = sx / subx;
                var ystep = sy / suby;

                sx /= 2; sy /= 2;

                for (y = 0; y <= suby; y++) {
                    for (x = 0; x <= subx; x++) {
                        this._mesh._positions[0].push(this._vf.center.x + x * xstep - sx);
                        this._mesh._positions[0].push(this._vf.center.y + y * ystep - sy);
                        this._mesh._positions[0].push(this._vf.center.z);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(1);
                        this._mesh._texCoords[0].push(x / subx);
                        this._mesh._texCoords[0].push(y / suby);
                    }
                }

                for (y = 1; y <= suby; y++) {
                    for (x = 0; x < subx; x++) {
                        this._mesh._indices[0].push((y - 1) * (subx + 1) + x);
                        this._mesh._indices[0].push((y - 1) * (subx + 1) + x + 1);
                        this._mesh._indices[0].push(y * (subx + 1) + x);

                        this._mesh._indices[0].push(y * (subx + 1) + x);
                        this._mesh._indices[0].push((y - 1) * (subx + 1) + x + 1);
                        this._mesh._indices[0].push(y * (subx + 1) + x + 1);
                    }
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "size" || fieldName == "center") {
                    this._mesh._positions[0] = [];

                    var sx = this._vf.size.x, sy = this._vf.size.y;
                    var subx = this._vf.subdivision.x, suby = this._vf.subdivision.y;
                    var x = 0, y = 0;
                    var xstep = sx / subx;
                    var ystep = sy / suby;

                    sx /= 2; sy /= 2;

                    for (y = 0; y <= suby; y++) {
                        for (x = 0; x <= subx; x++) {
                            this._mesh._positions[0].push(this._vf.center.x + x * xstep - sx);
                            this._mesh._positions[0].push(this._vf.center.y + y * ystep - sy);
                            this._mesh._positions[0].push(this._vf.center.z);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "subdivision") {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] = [];

                    var sx = this._vf.size.x, sy = this._vf.size.y;
                    var subx = this._vf.subdivision.x, suby = this._vf.subdivision.y;

                    var x = 0, y = 0;
                    var xstep = sx / subx;
                    var ystep = sy / suby;

                    sx /= 2; sy /= 2;

                    for (y = 0; y <= suby; y++) {
                        for (x = 0; x <= subx; x++) {
                            this._mesh._positions[0].push(this._vf.center.x + x * xstep - sx);
                            this._mesh._positions[0].push(this._vf.center.y + y * ystep - sy);
                            this._mesh._positions[0].push(this._vf.center.z);
                            this._mesh._normals[0].push(0);
                            this._mesh._normals[0].push(0);
                            this._mesh._normals[0].push(1);
                            this._mesh._texCoords[0].push(x / subx);
                            this._mesh._texCoords[0].push(y / suby);
                        }
                    }

                    for (y = 1; y <= suby; y++) {
                        for (x = 0; x < subx; x++) {
                            this._mesh._indices[0].push((y - 1) * (subx + 1) + x);
                            this._mesh._indices[0].push((y - 1) * (subx + 1) + x + 1);
                            this._mesh._indices[0].push(y * (subx + 1) + x);

                            this._mesh._indices[0].push(y * (subx + 1) + x);
                            this._mesh._indices[0].push((y - 1) * (subx + 1) + x + 1);
                            this._mesh._indices[0].push(y * (subx + 1) + x + 1);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Box ### */
x3dom.registerNodeType(
    "Box",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Box
         * @constructs x3dom.nodeTypes.Box
         * @x3d 3.3
         * @component Geometry3D
         * @status full
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Box node specifies a rectangular parallelepiped box centred at (0, 0, 0) in the local coordinate system and aligned with the local coordinate axes. By default, the box measures 2 units in each dimension, from -1 to +1.
         */
        function (ctx) {
            x3dom.nodeTypes.Box.superClass.call(this, ctx);


            /**
             * The size field specifies the extents of the box along the X-, Y-, and Z-axes respectively and each component value shall be greater than zero.
             * @var {x3dom.fields.SFVec3f} size
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Box
             * @initvalue 2,2,2
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'size', 2, 2, 2);

            /**
             * Specifies whether helper colors should be used, which will color each vertex with a different color. This will overwrite the color of the corresponding appearance node.
             * @var {x3dom.fields.SFBool} hasHelperColors
             * @memberof x3dom.nodeTypes.Box
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'hasHelperColors', false);

            var sx = this._vf.size.x,
                sy = this._vf.size.y,
                sz = this._vf.size.z;

            var geoCacheID = 'Box_'+sx+'-'+sy+'-'+sz;

            if( this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined )
            {
                //x3dom.debug.logInfo("Using Box from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else
            {
                sx /= 2; sy /= 2; sz /= 2;

                this._mesh._positions[0] = [
                    -sx,-sy,-sz,  -sx, sy,-sz,   sx, sy,-sz,   sx,-sy,-sz, //hinten 0,0,-1
                    -sx,-sy, sz,  -sx, sy, sz,   sx, sy, sz,   sx,-sy, sz, //vorne 0,0,1
                    -sx,-sy,-sz,  -sx,-sy, sz,  -sx, sy, sz,  -sx, sy,-sz, //links -1,0,0
                    sx,-sy,-sz,   sx,-sy, sz,   sx, sy, sz,   sx, sy,-sz, //rechts 1,0,0
                    -sx, sy,-sz,  -sx, sy, sz,   sx, sy, sz,   sx, sy,-sz, //oben 0,1,0
                    -sx,-sy,-sz,  -sx,-sy, sz,   sx,-sy, sz,   sx,-sy,-sz  //unten 0,-1,0
                ];
                this._mesh._normals[0] = [
                    0,0,-1,  0,0,-1,   0,0,-1,   0,0,-1,
                    0,0,1,  0,0,1,   0,0,1,   0,0,1,
                    -1,0,0,  -1,0,0,  -1,0,0,  -1,0,0,
                    1,0,0,   1,0,0,   1,0,0,   1,0,0,
                    0,1,0,  0,1,0,   0,1,0,   0,1,0,
                    0,-1,0,  0,-1,0,   0,-1,0,   0,-1,0
                ];
                this._mesh._texCoords[0] = [
                    1,0, 1,1, 0,1, 0,0,
                    0,0, 0,1, 1,1, 1,0,
                    0,0, 1,0, 1,1, 0,1,
                    1,0, 0,0, 0,1, 1,1,
                    0,1, 0,0, 1,0, 1,1,
                    0,0, 0,1, 1,1, 1,0
                ];
                if (this._vf.hasHelperColors) {
                    this._mesh._colors[0] = [
                        0, 0, 0,  0, 1, 0,  1, 1, 0,  1, 0, 0,
                        0, 0, 1,  0, 1, 1,  1, 1, 1,  1, 0, 1,
                        0, 0, 0,  0, 0, 1,  0, 1, 1,  0, 1, 0,
                        1, 0, 0,  1, 0, 1,  1, 1, 1,  1, 1, 0,
                        0, 1, 0,  0, 1, 1,  1, 1, 1,  1, 1, 0,
                        0, 0, 0,  0, 0, 1,  1, 0, 1,  1, 0, 0
                    ];
                }
                this._mesh._indices[0] = [
                    0,1,2, 2,3,0,
                    4,7,5, 5,7,6,
                    8,9,10, 10,11,8,
                    12,14,13, 14,12,15,
                    16,17,18, 18,19,16,
                    20,22,21, 22,20,23
                ];
                this._mesh._invalidate = true;
                this._mesh._numFaces = 12;
                this._mesh._numCoords = 24;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName)
            {
                if (fieldName === "size") {
                    var sx = this._vf.size.x / 2,
                        sy = this._vf.size.y / 2,
                        sz = this._vf.size.z / 2;

                    this._mesh._positions[0] = [
                        -sx,-sy,-sz,  -sx, sy,-sz,   sx, sy,-sz,   sx,-sy,-sz, //back   0,0,-1
                        -sx,-sy, sz,  -sx, sy, sz,   sx, sy, sz,   sx,-sy, sz, //front  0,0,1
                        -sx,-sy,-sz,  -sx,-sy, sz,  -sx, sy, sz,  -sx, sy,-sz, //left   -1,0,0
                        sx,-sy,-sz,   sx,-sy, sz,   sx, sy, sz,   sx, sy,-sz, //right  1,0,0
                        -sx, sy,-sz,  -sx, sy, sz,   sx, sy, sz,   sx, sy,-sz, //top    0,1,0
                        -sx,-sy,-sz,  -sx,-sy, sz,   sx,-sy, sz,   sx,-sy,-sz  //bottom 0,-1,0
                    ];

                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName === "hasHelperColors") {
                    if (this._vf.hasHelperColors) {
                        this._mesh._colors[0] = [
                            0, 0, 0,  0, 1, 0,  1, 1, 0,  1, 0, 0,
                            0, 0, 1,  0, 1, 1,  1, 1, 1,  1, 0, 1,
                            0, 0, 0,  0, 0, 1,  0, 1, 1,  0, 1, 0,
                            1, 0, 0,  1, 0, 1,  1, 1, 1,  1, 1, 0,
                            0, 1, 0,  0, 1, 1,  1, 1, 1,  1, 1, 0,
                            0, 0, 0,  0, 0, 1,  1, 0, 1,  1, 0, 0
                        ];
                    }
                    else {
                        this._mesh._colors[0] = [];
                    }

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Sphere ### */
x3dom.registerNodeType(
    "Sphere",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Sphere
         * @constructs x3dom.nodeTypes.Sphere
         * @x3d 3.3
         * @component Geometry3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Sphere node specifies a sphere centred at (0, 0, 0) in the local coordinate system.
         */
        function (ctx) {
            x3dom.nodeTypes.Sphere.superClass.call(this, ctx);

            // sky box background creates sphere with r = 10000

            /**
             * The radius field specifies the radius of the sphere.
             * @var {x3dom.fields.SFFloat} radius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Sphere
             * @initvalue ctx?1:10000
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', ctx ? 1 : 10000);

            /**
             * Specifies the number of faces that are generated to approximate the surface of the sphere.
             * @var {x3dom.fields.SFVec2f} subdivision
             * @memberof x3dom.nodeTypes.Sphere
             * @initvalue 24,24
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'subdivision', 24, 24);

            var qfactor = 1.0;
            var r = this._vf.radius;
            var subx = this._vf.subdivision.x, suby = this._vf.subdivision.y;

            var geoCacheID = 'Sphere_' + r + '-' + subx + '-' + suby;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Sphere from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else {
                if(ctx) {
                    qfactor = ctx.doc.properties.getProperty("PrimitiveQuality", "Medium");
                }
                if (!x3dom.Utils.isNumber(qfactor)) {
                    switch (qfactor.toLowerCase()) {
                        case "low":
                            qfactor = 0.3;
                            break;
                        case "medium":
                            qfactor = 0.5;
                            break;
                        case "high":
                            qfactor = 1.0;
                            break;
                    }
                } else {
                    qfactor = parseFloat(qfactor);
                }

                this._quality = qfactor;

                var latNumber, longNumber;
                var latitudeBands = Math.floor(subx * qfactor);
                var longitudeBands = Math.floor(suby * qfactor);

                var theta, sinTheta, cosTheta;
                var phi, sinPhi, cosPhi;
                var x, y, z, u, v;

                for (latNumber = 0; latNumber <= latitudeBands; latNumber++) {
                    theta = (latNumber * Math.PI) / latitudeBands;
                    sinTheta = Math.sin(theta);
                    cosTheta = Math.cos(theta);

                    for (longNumber = 0; longNumber <= longitudeBands; longNumber++) {
                        phi = (longNumber * 2.0 * Math.PI) / longitudeBands;
                        sinPhi = Math.sin(phi);
                        cosPhi = Math.cos(phi);

                        x = -cosPhi * sinTheta;
                        y = -cosTheta;
                        z = -sinPhi * sinTheta;

                        u = 0.25 - (longNumber / longitudeBands);
                        v = latNumber / latitudeBands;

                        this._mesh._positions[0].push(r * x);
                        this._mesh._positions[0].push(r * y);
                        this._mesh._positions[0].push(r * z);
                        this._mesh._normals[0].push(x);
                        this._mesh._normals[0].push(y);
                        this._mesh._normals[0].push(z);
                        this._mesh._texCoords[0].push(u);
                        this._mesh._texCoords[0].push(v);
                    }
                }

                var first, second;

                for (latNumber = 0; latNumber < latitudeBands; latNumber++) {
                    for (longNumber = 0; longNumber < longitudeBands; longNumber++) {
                        first = (latNumber * (longitudeBands + 1)) + longNumber;
                        second = first + longitudeBands + 1;

                        this._mesh._indices[0].push(first);
                        this._mesh._indices[0].push(second);
                        this._mesh._indices[0].push(first + 1);

                        this._mesh._indices[0].push(second);
                        this._mesh._indices[0].push(second + 1);
                        this._mesh._indices[0].push(first + 1);
                    }
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function(fieldName) {
                if (fieldName === "radius") {
                    this._mesh._positions[0] = [];
                    var r = this._vf.radius;
                    var subx = this._vf.subdivision.x, suby = this._vf.subdivision.y;
                    var qfactor = this._quality;

                    var latNumber, longNumber;
                    var latitudeBands = Math.floor(subx * qfactor);
                    var longitudeBands = Math.floor(suby * qfactor);

                    var theta, sinTheta, cosTheta;
                    var phi, sinPhi, cosPhi;
                    var x, y, z;

                    for (latNumber = 0; latNumber <= latitudeBands; latNumber++) {
                        theta = (latNumber * Math.PI) / latitudeBands;
                        sinTheta = Math.sin(theta);
                        cosTheta = Math.cos(theta);

                        for (longNumber = 0; longNumber <= longitudeBands; longNumber++) {
                            phi = (longNumber * 2.0 * Math.PI) / longitudeBands;
                            sinPhi = Math.sin(phi);
                            cosPhi = Math.cos(phi);

                            x = -cosPhi * sinTheta;
                            y = -cosTheta;
                            z = -sinPhi * sinTheta;

                            this._mesh._positions[0].push(r * x);
                            this._mesh._positions[0].push(r * y);
                            this._mesh._positions[0].push(r * z);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName === "subdivision") {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] =[];
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] =[];

                    var r = this._vf.radius;
                    var subx = this._vf.subdivision.x, suby = this._vf.subdivision.y;
                    var qfactor = this._quality;

                    var latNumber, longNumber;
                    var latitudeBands = Math.floor(subx * qfactor);
                    var longitudeBands = Math.floor(suby * qfactor);

                    var theta, sinTheta, cosTheta;
                    var phi, sinPhi, cosPhi;
                    var x, y, z, u, v;

                    for (latNumber = 0; latNumber <= latitudeBands; latNumber++) {
                        theta = (latNumber * Math.PI) / latitudeBands;
                        sinTheta = Math.sin(theta);
                        cosTheta = Math.cos(theta);

                        for (longNumber = 0; longNumber <= longitudeBands; longNumber++) {
                            phi = (longNumber * 2.0 * Math.PI) / longitudeBands;
                            sinPhi = Math.sin(phi);
                            cosPhi = Math.cos(phi);

                            x = -cosPhi * sinTheta;
                            y = -cosTheta;
                            z = -sinPhi * sinTheta;

                            u = 0.25 - (longNumber / longitudeBands);
                            v = latNumber / latitudeBands;

                            this._mesh._positions[0].push(r * x);
                            this._mesh._positions[0].push(r * y);
                            this._mesh._positions[0].push(r * z);
                            this._mesh._normals[0].push(x);
                            this._mesh._normals[0].push(y);
                            this._mesh._normals[0].push(z);
                            this._mesh._texCoords[0].push(u);
                            this._mesh._texCoords[0].push(v);
                        }
                    }

                    var first, second;

                    for (latNumber = 0; latNumber < latitudeBands; latNumber++) {
                        for (longNumber = 0; longNumber < longitudeBands; longNumber++) {
                            first = (latNumber * (longitudeBands + 1)) + longNumber;
                            second = first + longitudeBands + 1;

                            this._mesh._indices[0].push(first);
                            this._mesh._indices[0].push(second);
                            this._mesh._indices[0].push(first + 1);

                            this._mesh._indices[0].push(second);
                            this._mesh._indices[0].push(second + 1);
                            this._mesh._indices[0].push(first + 1);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Torus ### */
x3dom.registerNodeType(
    "Torus",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Torus
         * @constructs x3dom.nodeTypes.Torus
         * @x3d x.x
         * @component Geometry3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Torus node specifies a torus shape centred at (0, 0, 0) in the local coordinate system.
         */
        function (ctx) {
            x3dom.nodeTypes.Torus.superClass.call(this, ctx);

            var twoPi = 2.0 * Math.PI;


            /**
             * Specifies the inner radius of the torus.
             * @var {x3dom.fields.SFFloat} innerRadius
             * @memberof x3dom.nodeTypes.Torus
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'innerRadius', 0.5);

            /**
             * Specifies the outer radius of the torus.
             * @var {x3dom.fields.SFFloat} outerRadius
             * @memberof x3dom.nodeTypes.Torus
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'outerRadius', 1.0);

            /**
             * Specifies the size of the torus as an angle.
             * @var {x3dom.fields.SFFloat} angle
             * @range [0, 2*pi]
             * @memberof x3dom.nodeTypes.Torus
             * @initvalue twoPi
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'angle', twoPi);

            /**
             * Specifies whether the torus ends are closed with caps (when angle is smaller than a full circle).
             * @var {x3dom.fields.SFBool} caps
             * @memberof x3dom.nodeTypes.Torus
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'caps', true);

            /**
             * Specifies the number of faces that are generated to approximate the torus.
             * @var {x3dom.fields.SFVec2f} subdivision
             * @memberof x3dom.nodeTypes.Torus
             * @initvalue 24,24
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'subdivision', 24, 24);

            /**
             * Use a different interpretation mode for the inside and outside radius.
             * @var {x3dom.fields.SFBool} insideOutsideRadius
             * @memberof x3dom.nodeTypes.Torus
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'insideOutsideRadius', false);

            // assure that angle in [0, 2 * PI]
            if (this._vf.angle < 0)
                this._vf.angle = 0;
            else if (this._vf.angle > twoPi)
                this._vf.angle = twoPi;

            this._origCCW = this._vf.ccw;

            var innerRadius = this._vf.innerRadius;
            var outerRadius = this._vf.outerRadius;

            if (this._vf.insideOutsideRadius == true)
            {
                if (innerRadius > outerRadius) {
                    var tmp = innerRadius;
                    innerRadius = outerRadius;
                    outerRadius = tmp;
                }

                var rad = (outerRadius - innerRadius) / 2;

                outerRadius = innerRadius + rad;
                innerRadius = rad;

                // fix wrong face orientation in case of clockwise rotation
                this._vf.ccw = !this._origCCW;
            }

            var rings = this._vf.subdivision.x, sides = this._vf.subdivision.y;
            rings = Math.max(3, Math.round((this._vf.angle / twoPi) * rings));

            // FIXME; check/update geoCache on field update (for ALL primitives)!
            var geoCacheID = 'Torus_'+innerRadius+'_'+outerRadius+'_'+this._vf.angle+'_'+
                this._vf.subdivision+'-'+this._vf.caps;

            if( this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined )
            {
                //x3dom.debug.logInfo("Using Torus from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else
            {
                var ringDelta = this._vf.angle / rings;
                var sideDelta = twoPi / sides;
                var a, b, theta, phi;
                var cosTheta, sinTheta, cosPhi, sinPhi, dist;

                for (a=0, theta=0; a <= rings; a++, theta+=ringDelta)
                {
                    cosTheta = Math.cos(theta);
                    sinTheta = Math.sin(theta);

                    for (b=0, phi=0; b<=sides; b++, phi+=sideDelta)
                    {
                        cosPhi = Math.cos(phi);
                        sinPhi = Math.sin(phi);
                        dist = outerRadius + innerRadius * cosPhi;

                        if (this._vf.insideOutsideRadius) {
                            this._mesh._positions[0].push(cosTheta * dist, innerRadius * sinPhi, -sinTheta * dist);
                            this._mesh._normals[0].push(cosTheta * cosPhi, sinPhi, -sinTheta * cosPhi);
                        }
                        else {
                            this._mesh._positions[0].push(cosTheta * dist, -sinTheta * dist, innerRadius * sinPhi);
                            this._mesh._normals[0].push(cosTheta * cosPhi, -sinTheta * cosPhi, sinPhi);
                        }
                        this._mesh._texCoords[0].push(-a / rings, b / sides);
                    }
                }

                for (a=0; a<sides; a++)
                {
                    for (b=0; b<rings; b++)
                    {
                        this._mesh._indices[0].push(b * (sides+1) + a);
                        this._mesh._indices[0].push(b * (sides+1) + a + 1);
                        this._mesh._indices[0].push((b + 1) * (sides+1) + a);

                        this._mesh._indices[0].push(b * (sides+1) + a + 1);
                        this._mesh._indices[0].push((b + 1) * (sides+1) + a + 1);
                        this._mesh._indices[0].push((b + 1) * (sides+1) + a);
                    }
                }

                if (this._vf.angle < twoPi && this._vf.caps == true)
                {
                    // create first cap
                    var origPos = this._mesh._positions[0].length / 3;

                    if (this._vf.insideOutsideRadius) {
                        this._mesh._positions[0].push(outerRadius, 0, 0);
                        this._mesh._normals[0].push(0, 0, 1);
                    }
                    else {
                        this._mesh._positions[0].push(outerRadius, 0, 0);
                        this._mesh._normals[0].push(0, 1, 0);
                    }
                    this._mesh._texCoords[0].push(0.5, 0.5);

                    for (b=0, phi=0; b<=sides; b++, phi+=sideDelta)
                    {
                        cosPhi = Math.cos(phi);
                        sinPhi = Math.sin(phi);
                        dist = outerRadius + innerRadius * cosPhi;

                        if (this._vf.insideOutsideRadius) {
                            this._mesh._positions[0].push(dist, sinPhi * innerRadius, 0);
                            this._mesh._normals[0].push(0, 0, 1);
                        }
                        else {
                            this._mesh._positions[0].push(dist, 0, sinPhi * innerRadius);
                            this._mesh._normals[0].push(0, 1, 0);
                        }
                        this._mesh._texCoords[0].push((1 + cosPhi) * 0.5, (1 - sinPhi) * 0.5);

                        if (b > 0) {
                            this._mesh._indices[0].push(origPos);
                            this._mesh._indices[0].push(origPos + b);
                            this._mesh._indices[0].push(origPos + b - 1);
                        }
                        if (b == sides) {
                            this._mesh._indices[0].push(origPos);
                            this._mesh._indices[0].push(origPos + 1);
                            this._mesh._indices[0].push(origPos + b);
                        }
                    }

                    // second cap
                    cosTheta = Math.cos(this._vf.angle);
                    sinTheta = Math.sin(this._vf.angle);

                    origPos = this._mesh._positions[0].length / 3;
                    var nx = -sinTheta, ny = -cosTheta;

                    if (this._vf.insideOutsideRadius) {
                        this._mesh._positions[0].push(cosTheta * outerRadius, 0, -sinTheta * outerRadius);
                        this._mesh._normals[0].push(nx, 0, ny);
                    }
                    else {
                        this._mesh._positions[0].push(cosTheta * outerRadius, -sinTheta * outerRadius, 0);
                        this._mesh._normals[0].push(nx, ny, 0);
                    }
                    this._mesh._texCoords[0].push(0.5, 0.5);

                    for (b=0, phi=0; b<=sides; b++, phi+=sideDelta)
                    {
                        cosPhi = Math.cos(phi);
                        sinPhi = Math.sin(phi);
                        dist = outerRadius + innerRadius * cosPhi;

                        if (this._vf.insideOutsideRadius) {
                            this._mesh._positions[0].push(cosTheta * dist, sinPhi * innerRadius, -sinTheta * dist);
                            this._mesh._normals[0].push(nx, 0, ny);
                        }
                        else {
                            this._mesh._positions[0].push(cosTheta * dist, -sinTheta * dist, sinPhi * innerRadius);
                            this._mesh._normals[0].push(nx, ny, 0);
                        }
                        this._mesh._texCoords[0].push(1 - (1 + cosPhi) * 0.5, (1 - sinPhi) * 0.5);

                        if (b > 0) {
                            this._mesh._indices[0].push(origPos);
                            this._mesh._indices[0].push(origPos + b - 1);
                            this._mesh._indices[0].push(origPos + b);
                        }
                        if (b == sides) {
                            this._mesh._indices[0].push(origPos);
                            this._mesh._indices[0].push(origPos + b);
                            this._mesh._indices[0].push(origPos + 1);
                        }
                    }
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function(fieldName)
            {
                // TODO; invalidate geometry cache if necessary (to be fixed for all primitives)!
                if (fieldName == "innerRadius" || fieldName == "outerRadius" ||
                    fieldName == "subdivision" || fieldName == "angle" ||
                    fieldName == "insideOutsideRadius" || fieldName == "caps")
                {
                    // assure that angle in [0, 2 * PI]
                    var twoPi = 2.0 * Math.PI;

                    if (this._vf.angle < 0)
                        this._vf.angle = 0;
                    else if (this._vf.angle > twoPi)
                        this._vf.angle = twoPi;

                    var innerRadius = this._vf.innerRadius;
                    var outerRadius = this._vf.outerRadius;

                    if (this._vf.insideOutsideRadius == true)
                    {
                        if (innerRadius > outerRadius) {
                            var tmp = innerRadius;
                            innerRadius = outerRadius;
                            outerRadius = tmp;
                        }

                        var rad = (outerRadius - innerRadius) / 2;

                        outerRadius = innerRadius + rad;
                        innerRadius = rad;

                        this._vf.ccw = !this._origCCW;
                    }
                    else
                        this._vf.ccw = this._origCCW;

                    var rings = this._vf.subdivision.x, sides = this._vf.subdivision.y;
                    rings = Math.max(3, Math.round((this._vf.angle / twoPi) * rings));

                    var ringDelta = this._vf.angle / rings;
                    var sideDelta = twoPi / sides;
                    var a, b, theta, phi;
                    var cosTheta, sinTheta, cosPhi, sinPhi, dist;

                    this._mesh._positions[0] = [];
                    this._mesh._normals[0]   = [];
                    this._mesh._texCoords[0] = [];
                    this._mesh._indices[0]   = [];

                    for (a=0, theta=0; a <= rings; a++, theta+=ringDelta)
                    {
                        cosTheta = Math.cos(theta);
                        sinTheta = Math.sin(theta);

                        for (b=0, phi=0; b<=sides; b++, phi+=sideDelta)
                        {
                            cosPhi = Math.cos(phi);
                            sinPhi = Math.sin(phi);
                            dist = outerRadius + innerRadius * cosPhi;

                            if (this._vf.insideOutsideRadius) {
                                this._mesh._positions[0].push(cosTheta * dist, innerRadius * sinPhi, -sinTheta * dist);
                                this._mesh._normals[0].push(cosTheta * cosPhi, sinPhi, -sinTheta * cosPhi);
                            }
                            else {
                                this._mesh._positions[0].push(cosTheta * dist, -sinTheta * dist, innerRadius * sinPhi);
                                this._mesh._normals[0].push(cosTheta * cosPhi, -sinTheta * cosPhi, sinPhi);
                            }
                            this._mesh._texCoords[0].push(-a / rings, b / sides);
                        }
                    }

                    for (a=0; a<sides; a++)
                    {
                        for (b=0; b<rings; b++)
                        {
                            this._mesh._indices[0].push(b * (sides+1) + a);
                            this._mesh._indices[0].push(b * (sides+1) + a + 1);
                            this._mesh._indices[0].push((b + 1) * (sides+1) + a);

                            this._mesh._indices[0].push(b * (sides+1) + a + 1);
                            this._mesh._indices[0].push((b + 1) * (sides+1) + a + 1);
                            this._mesh._indices[0].push((b + 1) * (sides+1) + a);
                        }
                    }

                    if (this._vf.angle < twoPi && this._vf.caps == true)
                    {
                        // create first cap
                        var origPos = this._mesh._positions[0].length / 3;

                        if (this._vf.insideOutsideRadius) {
                            this._mesh._positions[0].push(outerRadius, 0, 0);
                            this._mesh._normals[0].push(0, 0, 1);
                        }
                        else {
                            this._mesh._positions[0].push(outerRadius, 0, 0);
                            this._mesh._normals[0].push(0, 1, 0);
                        }
                        this._mesh._texCoords[0].push(0.5, 0.5);

                        for (b=0, phi=0; b<=sides; b++, phi+=sideDelta)
                        {
                            cosPhi = Math.cos(phi);
                            sinPhi = Math.sin(phi);
                            dist = outerRadius + innerRadius * cosPhi;

                            if (this._vf.insideOutsideRadius) {
                                this._mesh._positions[0].push(dist, sinPhi * innerRadius, 0);
                                this._mesh._normals[0].push(0, 0, 1);
                            }
                            else {
                                this._mesh._positions[0].push(dist, 0, sinPhi * innerRadius);
                                this._mesh._normals[0].push(0, 1, 0);
                            }
                            this._mesh._texCoords[0].push((1 + cosPhi) * 0.5, (1 - sinPhi) * 0.5);

                            if (b > 0) {
                                this._mesh._indices[0].push(origPos);
                                this._mesh._indices[0].push(origPos + b);
                                this._mesh._indices[0].push(origPos + b - 1);
                            }
                            if (b == sides) {
                                this._mesh._indices[0].push(origPos);
                                this._mesh._indices[0].push(origPos + 1);
                                this._mesh._indices[0].push(origPos + b);
                            }
                        }

                        // second cap
                        cosTheta = Math.cos(this._vf.angle);
                        sinTheta = Math.sin(this._vf.angle);

                        origPos = this._mesh._positions[0].length / 3;
                        var nx = -sinTheta, ny = -cosTheta;

                        if (this._vf.insideOutsideRadius) {
                            this._mesh._positions[0].push(cosTheta * outerRadius, 0, -sinTheta * outerRadius);
                            this._mesh._normals[0].push(nx, 0, ny);
                        }
                        else {
                            this._mesh._positions[0].push(cosTheta * outerRadius, -sinTheta * outerRadius, 0);
                            this._mesh._normals[0].push(nx, ny, 0);
                        }
                        this._mesh._texCoords[0].push(0.5, 0.5);

                        for (b=0, phi=0; b<=sides; b++, phi+=sideDelta)
                        {
                            cosPhi = Math.cos(phi);
                            sinPhi = Math.sin(phi);
                            dist = outerRadius + innerRadius * cosPhi;

                            if (this._vf.insideOutsideRadius) {
                                this._mesh._positions[0].push(cosTheta * dist, sinPhi * innerRadius, -sinTheta * dist);
                                this._mesh._normals[0].push(nx, 0, ny);
                            }
                            else {
                                this._mesh._positions[0].push(cosTheta * dist, -sinTheta * dist, sinPhi * innerRadius);
                                this._mesh._normals[0].push(nx, ny, 0);
                            }
                            this._mesh._texCoords[0].push(1 - (1 + cosPhi) * 0.5, (1 - sinPhi) * 0.5);

                            if (b > 0) {
                                this._mesh._indices[0].push(origPos);
                                this._mesh._indices[0].push(origPos + b - 1);
                                this._mesh._indices[0].push(origPos + b);
                            }
                            if (b == sides) {
                                this._mesh._indices[0].push(origPos);
                                this._mesh._indices[0].push(origPos + b);
                                this._mesh._indices[0].push(origPos + 1);
                            }
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Cone ### */
x3dom.registerNodeType(
    "Cone",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Cone
         * @constructs x3dom.nodeTypes.Cone
         * @x3d 3.3
         * @component Geometry3D
         * @status full
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Cone node specifies a cone which is centred in the local coordinate system and whose central axis is aligned with the local Y-axis.
         * By default, the cone has a radius of 1.0 at the bottom and a height of 2.0
         */
        function (ctx) {
            x3dom.nodeTypes.Cone.superClass.call(this, ctx);


            /**
             * The bottomRadius field specifies the radius of the cone's base.
             * @var {x3dom.fields.SFFloat} bottomRadius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Cone
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'bottomRadius', 1.0);

            /**
             * The topRadius field specifies the radius of the cone at the apex.
             * @var {x3dom.fields.SFFloat} topRadius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Cone
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'topRadius', 0);

            /**
             * The height field specifies the height of the cone from the centre of the base to the apex.
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Cone
             * @initvalue 2.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 2.0);

            /**
             * The bottom field specifies whether the bottom cap of the cone is created.
             * @var {x3dom.fields.SFBool} bottom
             * @memberof x3dom.nodeTypes.Cone
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'bottom', true);

            /**
             * The side field specifies whether sides of the cone are created.
             * @var {x3dom.fields.SFBool} side
             * @memberof x3dom.nodeTypes.Cone
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'side', true);

            /**
             * The top field specifies whether the top cap of the cone is created.
             * @var {x3dom.fields.SFBool} top
             * @memberof x3dom.nodeTypes.Cone
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'top', true);

            /**
             * Specifies the number of faces that are generated to approximate the sides of the cone.
             * @var {x3dom.fields.SFFloat} subdivision
             * @range [2, inf]
             * @memberof x3dom.nodeTypes.Cone
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            var geoCacheID = 'Cone_' + this._vf.bottomRadius + '_' + this._vf.height + '_' + this._vf.top + '_' +
                this._vf.bottom + '_' + this._vf.side + '_' + this._vf.topRadius + '_' + this._vf.subdivision;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Cone from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else {
                var bottomRadius = this._vf.bottomRadius, height = this._vf.height;
                var topRadius = this._vf.topRadius, sides = this._vf.subdivision;

                var beta, x, z;
                var delta = 2.0 * Math.PI / sides;

                var incl = (bottomRadius - topRadius) / height;
                var nlen = 1.0 / Math.sqrt(1.0 + incl * incl);

                var j = 0, k = 0;
                var h, base;

                if (this._vf.side && height > 0) {
                    var px = 0, pz = 0;

                    for (j = 0, k = 0; j <= sides; j++) {
                        beta = j * delta;
                        x = Math.sin(beta);
                        z = -Math.cos(beta);

                        if (topRadius > x3dom.fields.Eps) {
                            px = x * topRadius;
                            pz = z * topRadius;
                        }

                        this._mesh._positions[0].push(px, height / 2, pz);
                        this._mesh._normals[0].push(x / nlen, incl / nlen, z / nlen);
                        this._mesh._texCoords[0].push(1.0 - j / sides, 1);

                        this._mesh._positions[0].push(x * bottomRadius, -height / 2, z * bottomRadius);
                        this._mesh._normals[0].push(x / nlen, incl / nlen, z / nlen);
                        this._mesh._texCoords[0].push(1.0 - j / sides, 0);

                        if (j > 0) {
                            this._mesh._indices[0].push(k    );
                            this._mesh._indices[0].push(k + 2);
                            this._mesh._indices[0].push(k + 1);

                            this._mesh._indices[0].push(k + 1);
                            this._mesh._indices[0].push(k + 2);
                            this._mesh._indices[0].push(k + 3);

                            k += 2;
                        }
                    }
                }

                if (this._vf.bottom && bottomRadius > 0) {
                    base = this._mesh._positions[0].length / 3;

                    for (j = sides - 1; j >= 0; j--) {
                        beta = j * delta;
                        x = bottomRadius * Math.sin(beta);
                        z = -bottomRadius * Math.cos(beta);

                        this._mesh._positions[0].push(x, -height / 2, z);
                        this._mesh._normals[0].push(0, -1, 0);
                        this._mesh._texCoords[0].push(x / bottomRadius / 2 + 0.5, z / bottomRadius / 2 + 0.5);
                    }

                    h = base + 1;

                    for (j = 2; j < sides; j++) {
                        this._mesh._indices[0].push(h);
                        this._mesh._indices[0].push(base);

                        h = base + j;
                        this._mesh._indices[0].push(h);
                    }
                }

                if (this._vf.top && topRadius > x3dom.fields.Eps) {
                    base = this._mesh._positions[0].length / 3;

                    for (j = sides - 1; j >= 0; j--) {
                        beta = j * delta;
                        x =  topRadius * Math.sin(beta);
                        z = -topRadius * Math.cos(beta);

                        this._mesh._positions[0].push(x, height / 2, z);
                        this._mesh._normals[0].push(0, 1, 0);
                        this._mesh._texCoords[0].push(x / topRadius / 2 + 0.5, 1.0 - z / topRadius / 2 + 0.5);
                    }

                    h = base + 1;

                    for (j = 2; j < sides; j++) {
                        this._mesh._indices[0].push(base);
                        this._mesh._indices[0].push(h);

                        h = base + j;
                        this._mesh._indices[0].push(h);
                    }
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName)
            {
                if (fieldName == "bottomRadius" || fieldName == "topRadius" ||
                    fieldName == "height" || fieldName == "subdivision" ||
                    fieldName == "bottom" || fieldName == "top" || fieldName == "side")
                {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] = [];

                    var bottomRadius = this._vf.bottomRadius, height = this._vf.height;
                    var topRadius = this._vf.topRadius, sides = this._vf.subdivision;

                    var beta, x, z;
                    var delta = 2.0 * Math.PI / sides;

                    var incl = (bottomRadius - topRadius) / height;
                    var nlen = 1.0 / Math.sqrt(1.0 + incl * incl);

                    var j = 0, k = 0;
                    var h, base;

                    if (this._vf.side && height > 0)
                    {
                        var px = 0, pz = 0;

                        for (j = 0, k = 0; j <= sides; j++) {
                            beta = j * delta;
                            x = Math.sin(beta);
                            z = -Math.cos(beta);

                            if (topRadius > x3dom.fields.Eps) {
                                px = x * topRadius;
                                pz = z * topRadius;
                            }

                            this._mesh._positions[0].push(px, height / 2, pz);
                            this._mesh._normals[0].push(x / nlen, incl / nlen, z / nlen);
                            this._mesh._texCoords[0].push(1.0 - j / sides, 1);

                            this._mesh._positions[0].push(x * bottomRadius, -height / 2, z * bottomRadius);
                            this._mesh._normals[0].push(x / nlen, incl / nlen, z / nlen);
                            this._mesh._texCoords[0].push(1.0 - j / sides, 0);

                            if (j > 0) {
                                this._mesh._indices[0].push(k    );
                                this._mesh._indices[0].push(k + 2);
                                this._mesh._indices[0].push(k + 1);

                                this._mesh._indices[0].push(k + 1);
                                this._mesh._indices[0].push(k + 2);
                                this._mesh._indices[0].push(k + 3);

                                k += 2;
                            }
                        }
                    }

                    if (this._vf.bottom && bottomRadius > 0)
                    {
                        base = this._mesh._positions[0].length / 3;

                        for (j = sides - 1; j >= 0; j--) {
                            beta = j * delta;
                            x = bottomRadius * Math.sin(beta);
                            z = -bottomRadius * Math.cos(beta);

                            this._mesh._positions[0].push(x, -height / 2, z);
                            this._mesh._normals[0].push(0, -1, 0);
                            this._mesh._texCoords[0].push(x / bottomRadius / 2 + 0.5, z / bottomRadius / 2 + 0.5);
                        }

                        h = base + 1;

                        for (j = 2; j < sides; j++) {
                            this._mesh._indices[0].push(h);
                            this._mesh._indices[0].push(base);

                            h = base + j;
                            this._mesh._indices[0].push(h);
                        }
                    }

                    if (this._vf.top && topRadius > x3dom.fields.Eps)
                    {
                        base = this._mesh._positions[0].length / 3;

                        for (j = sides - 1; j >= 0; j--) {
                            beta = j * delta;
                            x =  topRadius * Math.sin(beta);
                            z = -topRadius * Math.cos(beta);

                            this._mesh._positions[0].push(x, height / 2, z);
                            this._mesh._normals[0].push(0, 1, 0);
                            this._mesh._texCoords[0].push(x / topRadius / 2 + 0.5, 1.0 - z / topRadius / 2 + 0.5);
                        }

                        h = base + 1;

                        for (j = 2; j < sides; j++) {
                            this._mesh._indices[0].push(base);
                            this._mesh._indices[0].push(h);

                            h = base + j;
                            this._mesh._indices[0].push(h);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Cylinder ### */
x3dom.registerNodeType(
    "Cylinder",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Cylinder
         * @constructs x3dom.nodeTypes.Cylinder
         * @x3d 3.3
         * @component Geometry3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Cylinder node specifies a capped cylinder centred at (0,0,0) in the local coordinate system and with a central axis oriented along the local Y-axis.
         * By default, the cylinder is sized at "-1" to "+1" in all three dimensions.
         */
        function (ctx) {
            x3dom.nodeTypes.Cylinder.superClass.call(this, ctx);


            /**
             * The radius field specifies the radius of the cylinder.
             * @var {x3dom.fields.SFFloat} radius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Cylinder
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', 1.0);

            /**
             * The height field specifies the height of the cylinder along the central axis.
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Cylinder
             * @initvalue 2.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 2.0);

            /**
             * The bottom field specifies whether the bottom cap of the cylinder is created.
             * @var {x3dom.fields.SFBool} bottom
             * @memberof x3dom.nodeTypes.Cylinder
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'bottom', true);

            /**
             * The top field specifies whether the top cap of the cylinder is created.
             * @var {x3dom.fields.SFBool} top
             * @memberof x3dom.nodeTypes.Cylinder
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'top', true);

            /**
             * Specifies the number of faces that are generated to approximate the sides of the cylinder.
             * @var {x3dom.fields.SFFloat} subdivision
             * @range [2, inf]
             * @memberof x3dom.nodeTypes.Cylinder
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            /**
             * The side field specifies whether sides of the cylinder are created.
             * @var {x3dom.fields.SFBool} side
             * @memberof x3dom.nodeTypes.Cylinder
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'side', true);

            var sides = this._vf.subdivision;

            var geoCacheID = 'Cylinder_'+this._vf.radius+'_'+this._vf.height+'_'+this._vf.bottom+'_'+this._vf.top+'_'+
                this._vf.side+'_'+this._vf.subdivision;

            if( this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined )
            {
                //x3dom.debug.logInfo("Using Cylinder from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else
            {
                var radius = this._vf.radius;
                var height = this._vf.height / 2;

                var beta, x, z;
                var delta = 2.0 * Math.PI / sides;
                var j, k;

                if (this._vf.side)
                {
                    for (j=0, k=0; j<=sides; j++)
                    {
                        beta = j * delta;
                        x = Math.sin(beta);
                        z = -Math.cos(beta);

                        this._mesh._positions[0].push(x * radius, -height, z * radius);
                        this._mesh._normals[0].push(x, 0, z);
                        this._mesh._texCoords[0].push(1.0 - j / sides, 0);

                        this._mesh._positions[0].push(x * radius, height, z * radius);
                        this._mesh._normals[0].push(x, 0, z);
                        this._mesh._texCoords[0].push(1.0 - j / sides, 1);

                        if (j > 0)
                        {
                            this._mesh._indices[0].push(k    );
                            this._mesh._indices[0].push(k + 1);
                            this._mesh._indices[0].push(k + 2);

                            this._mesh._indices[0].push(k + 2);
                            this._mesh._indices[0].push(k + 1);
                            this._mesh._indices[0].push(k + 3);

                            k += 2;
                        }
                    }
                }

                if (radius > 0)
                {
                    var h, base = this._mesh._positions[0].length / 3;

                    if (this._vf.top)
                    {
                        for (j=sides-1; j>=0; j--)
                        {
                            beta = j * delta;
                            x = radius * Math.sin(beta);
                            z = -radius * Math.cos(beta);

                            this._mesh._positions[0].push(x, height, z);
                            this._mesh._normals[0].push(0, 1, 0);
                            this._mesh._texCoords[0].push(x / radius / 2 + 0.5, -z / radius / 2 + 0.5);
                        }

                        h = base + 1;

                        for (j=2; j<sides; j++)
                        {
                            this._mesh._indices[0].push(base);
                            this._mesh._indices[0].push(h);

                            h = base + j;
                            this._mesh._indices[0].push(h);
                        }

                        base = this._mesh._positions[0].length / 3;
                    }

                    if (this._vf.bottom)
                    {
                        for (j=sides-1; j>=0; j--)
                        {
                            beta = j * delta;
                            x = radius * Math.sin(beta);
                            z = -radius * Math.cos(beta);

                            this._mesh._positions[0].push(x, -height, z);
                            this._mesh._normals[0].push(0, -1, 0);
                            this._mesh._texCoords[0].push(x / radius / 2 + 0.5, z / radius / 2 + 0.5);
                        }

                        h = base + 1;

                        for (j=2; j<sides; j++)
                        {
                            this._mesh._indices[0].push(h);
                            this._mesh._indices[0].push(base);

                            h = base + j;
                            this._mesh._indices[0].push(h);
                        }
                    }
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function(fieldName) {
                if (fieldName === "radius" || fieldName === "height")
                {
                    this._mesh._positions[0] = [];

                    var radius = this._vf.radius, height = this._vf.height / 2;
                    var sides = this._vf.subdivision;

                    var beta, x, z, j;
                    var delta = 2.0 * Math.PI / sides;

                    if (this._vf.side)
                    {
                        for (j=0; j<=sides; j++)
                        {
                            beta = j * delta;
                            x = Math.sin(beta);
                            z = -Math.cos(beta);

                            this._mesh._positions[0].push(x * radius, -height, z * radius);
                            this._mesh._positions[0].push(x * radius, height, z * radius);
                        }
                    }

                    if (radius > 0)
                    {
                        var h, base = this._mesh._positions[0].length / 3;

                        if (this._vf.top)
                        {
                            for (j=sides-1; j>=0; j--)
                            {
                                beta = j * delta;
                                x = radius * Math.sin(beta);
                                z = -radius * Math.cos(beta);

                                this._mesh._positions[0].push(x, height, z);
                            }
                        }
                    }

                    if (this._vf.bottom)
                    {
                        for (j=sides-1; j>=0; j--)
                        {
                            beta = j * delta;
                            x = radius * Math.sin(beta);
                            z = -radius * Math.cos(beta);

                            this._mesh._positions[0].push(x, -height, z);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName === "subdivision" || fieldName === "bottom" ||
                    fieldName === "top" || fieldName === "side")
                {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] =[];
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] =[];

                    var radius = this._vf.radius, height = this._vf.height / 2;
                    var sides = this._vf.subdivision;

                    var beta, x, z, j;
                    var delta = 2.0 * Math.PI / sides;
                    var k = 0;

                    if (this._vf.side)
                    {
                        for (j=0, k=0; j<=sides; j++)
                        {
                            beta = j * delta;
                            x = Math.sin(beta);
                            z = -Math.cos(beta);

                            this._mesh._positions[0].push(x * radius, -height, z * radius);
                            this._mesh._normals[0].push(x, 0, z);
                            this._mesh._texCoords[0].push(1.0 - j / sides, 0);

                            this._mesh._positions[0].push(x * radius, height, z * radius);
                            this._mesh._normals[0].push(x, 0, z);
                            this._mesh._texCoords[0].push(1.0 - j / sides, 1);

                            if (j > 0)
                            {
                                this._mesh._indices[0].push(k + 0);
                                this._mesh._indices[0].push(k + 1);
                                this._mesh._indices[0].push(k + 2);

                                this._mesh._indices[0].push(k + 2);
                                this._mesh._indices[0].push(k + 1);
                                this._mesh._indices[0].push(k + 3);

                                k += 2;
                            }
                        }
                    }

                    if (radius > 0)
                    {
                        var h, base = this._mesh._positions[0].length / 3;

                        if (this._vf.top)
                        {
                            for (j=sides-1; j>=0; j--)
                            {
                                beta = j * delta;
                                x = radius * Math.sin(beta);
                                z = -radius * Math.cos(beta);

                                this._mesh._positions[0].push(x, height, z);
                                this._mesh._normals[0].push(0, 1, 0);
                                this._mesh._texCoords[0].push(x / radius / 2 + 0.5, -z / radius / 2 + 0.5);
                            }

                            h = base + 1;

                            for (j=2; j<sides; j++)
                            {
                                this._mesh._indices[0].push(base);
                                this._mesh._indices[0].push(h);

                                h = base + j;
                                this._mesh._indices[0].push(h);
                            }

                            base = this._mesh._positions[0].length / 3;
                        }

                        if (this._vf.bottom)
                        {
                            for (j=sides-1; j>=0; j--)
                            {
                                beta = j * delta;
                                x = radius * Math.sin(beta);
                                z = -radius * Math.cos(beta);

                                this._mesh._positions[0].push(x, -height, z);
                                this._mesh._normals[0].push(0, -1, 0);
                                this._mesh._texCoords[0].push(x / radius / 2 + 0.5, z / radius / 2 + 0.5);
                            }

                            h = base + 1;

                            for (j=2; j<sides; j++)
                            {
                                this._mesh._indices[0].push(h);
                                this._mesh._indices[0].push(base);

                                h = base + j;
                                this._mesh._indices[0].push(h);
                            }
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */


/* ### ExternalGeometry ### */
x3dom.registerNodeType(
    "ExternalGeometry",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,

        /**
         * Constructor for ExternalGeometry
         * @constructs x3dom.nodeTypes.ExternalGeometry
         * @x3d x.x
         * @component Geometry3D
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ExternalGeometry node loads data from a Shape Resource Container (SRC). The used data can be progressively updated during transmission.
         */
         function (ctx) {
            x3dom.nodeTypes.ExternalGeometry.superClass.call(this, ctx);

            /**
             * Defines the url to the openGL Transfer Format (glTF) file.
             * A suffix with a leading # can be used to reference single meshes inside a SRC: "path/to/data.src#mesh0".
             * Multiple urls specify alternatives (if downloading fails).
             *
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.ExternalGeometry
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'url', []);


            //initialization of rendering-related X3DOM structures
            this._mesh._invalidate = false;
            this._mesh._numCoords  = 0;
            this._mesh._numFaces   = 0;


            //index of the current URL, used to download data - if downloading fails, this index is increased
            this._currentURLIdx = 0;
        },
        {
            //----------------------------------------------------------------------------------------------------------
            // PUBLIC FUNCTIONS
            //----------------------------------------------------------------------------------------------------------

            /**
             * Updates the render data, stored in the given objects, with data from this ExternalGeometry.
             * If necessary, the referenced file is downloaded first.
             *
             * @param {Object} shape - x3dom shape node
             * @param {Object} shaderProgram - x3dom shader program
             * @param {Object} gl - WebGL context
             * @param {Object} viewarea - x3dom view area
             * @param {Object} context - x3dom context object
             */
            update: function(shape, shaderProgram, gl, viewarea, context) {
                var that = this;
                var xhr;

                if (this._vf['url'].length == 0 ||
                    this._currentURLIdx >= this._vf['url'].length)
                {
                    return;
                }

                //check if there is still memory available
                if (x3dom.BinaryContainerLoader.outOfMemory) {
                    return;
                }

                //TODO: check SOURCE child nodes
                shape._webgl.internalDownloadCount  = 1;
                shape._nameSpace.doc.downloadCount  = 1;

                //post request
                xhr = new XMLHttpRequest();

                xhr.open("GET", shape._nameSpace.getURL(this._vf['url'][this._currentURLIdx]), true);

                xhr.responseType = "arraybuffer";

                //xhr.send(null);
                x3dom.RequestManager.addRequest(xhr);

                xhr.onerror = function() {
                    x3dom.debug.logError("Unable to load SRC data from URL \"" + that._vf['url'][that._currentURLIdx] + "\"");
                };

                xhr.onload = function() {
                    shape._webgl.internalDownloadCount  = 0;
                    shape._nameSpace.doc.downloadCount  = 0;

                    shape._webgl.primType    = [];
                    shape._webgl.indexOffset = [];
                    shape._webgl.drawCount   = [];

                    if ((xhr.status == 200 || xhr.status == 0)) {
                        var glTF = new x3dom.glTF.glTFLoader(xhr.response, true);

                        if (glTF.header.sceneLength > 0)
                        {
                            glTF.loaded = {};
                            glTF.loaded.meshes = {};
                            glTF.loaded.meshCount = 0;

                            var url = that._vf['url'][that._currentURLIdx];
                            if(url.includes('#'))
                            {
                                var split = url.split('#');
                                var meshName = split[split.length-1];
                                glTF.getMesh(shape, shaderProgram, gl, meshName);
                            }
                            else
                            {
                                glTF.getScene(shape, shaderProgram, gl);
                            }

                            for(var key in glTF._mesh){
                                if(!glTF._mesh.hasOwnProperty(key))continue;
                                that._mesh[key] = glTF._mesh[key];
                            }

                        }
                        else
                        {
                            if ((that._currentURLIdx + 1) < that._vf['url'].length)
                            {
                                x3dom.debug.logWarning("Invalid SRC data, loaded from URL \"" +
                                                        that._vf['url'][that._currentURLIdx] +
                                                        "\", trying next specified URL");

                                //try next URL
                                ++that._currentURLIdx;
                                that.update(shape, shaderProgram, gl, viewarea, context);
                            }
                            else
                            {
                                x3dom.debug.logError("Invalid SRC data, loaded from URL \"" +
                                                     that._vf['url'][that._currentURLIdx] + "\"," +
                                                     " no other URLs left to try.");
                            }
                        }
                    }
                    else
                    {
                        if ((that._currentURLIdx + 1) < that._vf['url'].length)
                        {
                            x3dom.debug.logWarning("Invalid SRC data, loaded from URL \"" +
                                                    that._vf['url'][that._currentURLIdx] +
                                                    "\", trying next specified URL");

                            //try next URL
                            ++that._currentURLIdx;
                            that.update(shape, shaderProgram, gl, viewarea, context);
                        }
                        else
                        {
                            x3dom.debug.logError("Invalid SRC data, loaded from URL \"" +
                            that._vf['url'][that._currentURLIdx] + "\"," +
                            " no other URLs left to try.");
                        }
                    }
                };
            } ,

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

            //----------------------------------------------------------------------------------------------------------
            // PRIVATE FUNCTIONS
            //----------------------------------------------------------------------------------------------------------
            

            
            /**
             * Returns the node's local volume
             * @returns {x3dom.fields.BoxVolume} the local, axis-aligned bounding volume
             */
            getVolume: function()
            {
                var vol = this._mesh._vol;
                var shapeNode;

                if (!vol.isValid())
                {
                    //an ExternalGeometry node must _always_ be a child of (at least) one shape node
                    //for multiple Shape nodes using a single ExternalGeometry node,
                    //we assume that either all of them, or no one have specified a bounding volume
                    shapeNode = this._parentNodes[0];

                    if (typeof shapeNode._vf["bboxCenter"] != 'undefined' &&
                        typeof shapeNode._vf["bboxSize"]   != 'undefined'   )
                    {
                        vol.setBoundsByCenterSize(shapeNode._vf["bboxCenter"], shapeNode._vf["bboxSize"]);
                    }
                    //if no bbox information was specified for the Shape node, use information from the SRC header
                    else
                    {
                        //TODO: implement
                    }
                }

                return vol;
            }
        }        
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DBinaryContainerGeometryNode ### */
x3dom.registerNodeType(
    "X3DBinaryContainerGeometryNode",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for X3DBinaryContainerGeometryNode
         * @constructs x3dom.nodeTypes.X3DBinaryContainerGeometryNode
         * @x3d x.x
         * @component Geometry3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         */
        function (ctx) {
            x3dom.nodeTypes.X3DBinaryContainerGeometryNode.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFVec3f} position
             * @memberof x3dom.nodeTypes.X3DBinaryContainerGeometryNode
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'position', 0, 0, 0);

            /**
             *
             * @var {x3dom.fields.SFVec3f} size
             * @memberof x3dom.nodeTypes.X3DBinaryContainerGeometryNode
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'size', 1, 1, 1);

            /**
             *
             * @var {x3dom.fields.MFInt32} vertexCount
             * @memberof x3dom.nodeTypes.X3DBinaryContainerGeometryNode
             * @initvalue [0]
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'vertexCount', [0]);

            /**
             *
             * @var {x3dom.fields.MFString} primType
             * @memberof x3dom.nodeTypes.X3DBinaryContainerGeometryNode
             * @initvalue ['TRIANGLES']
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'primType', ['TRIANGLES']);

            // correct min/max of bounding volume set in BinaryContainerGeometry
            this._mesh._invalidate = false;
            this._mesh._numCoords = 0;
            this._mesh._numFaces = 0;

            this._diameter = this._vf.size.length();
        
        },
        {
            getMin: function() {
                var vol = this._mesh._vol;

                if (!vol.isValid()) {
                    vol.setBoundsByCenterSize(this._vf.position, this._vf.size);
                }

                return vol.min;
            },

            getMax: function() {
                var vol = this._mesh._vol;

                if (!vol.isValid()) {
                    vol.setBoundsByCenterSize(this._vf.position, this._vf.size);
                }

                return vol.max;
            },

            getVolume: function() {
                var vol = this._mesh._vol;

                if (!vol.isValid()) {
                    vol.setBoundsByCenterSize(this._vf.position, this._vf.size);
                }

                return vol;
            },

            invalidateVolume: function() {
                // at the moment, do nothing here since field updates are not impl.
            },

            getCenter: function() {
                return this._vf.position;
            },

            getDiameter: function() {
                return this._diameter;
            },

            needLighting: function() {
                var hasTris = (this._vf.primType.length && this._vf.primType[0].indexOf("TRIANGLE") >= 0);
                return (this._vf.lit && hasTris);
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### BinaryGeometry ### */
x3dom.registerNodeType(
    "BinaryGeometry",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DBinaryContainerGeometryNode,
        
        /**
         * Constructor for BinaryGeometry
         * @constructs x3dom.nodeTypes.BinaryGeometry
         * @x3d x.x
         * @component Geometry3D
         * @extends x3dom.nodeTypes.X3DBinaryContainerGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The BinaryGeometry node can load binary data exported by AOPT.
         */
        function (ctx) {
            x3dom.nodeTypes.BinaryGeometry.superClass.call(this, ctx);


            /**
             * The url to the binary file, that contains the index data.
             * @var {x3dom.fields.SFString} index
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'index', "");   // Uint16

            /**
             * The url to the binary file, that contains the mesh coordinates.
             * @var {x3dom.fields.SFString} coord
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'coord', "");   // Float32

            /**
             * The url to the binary file, that contains the normals.
             * @var {x3dom.fields.SFString} normal
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normal', "");

            /**
             * The url to the binary file, that contains the texture coordinates.
             * @var {x3dom.fields.SFString} texCoord
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'texCoord', "");    // THINKABOUTME: add texCoord1, texCoord2, ...?

            /**
             * The url to the binary file, that contains the colors.
             * @var {x3dom.fields.SFString} color
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'color', "");

            /**
             *
             * @var {x3dom.fields.SFString} tangent
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'tangent', "");     // TODO

            /**
             *
             * @var {x3dom.fields.SFString} binormal
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'binormal', "");    // TODO

            // Typed Array View Types
            // Int8, Uint8, Int16, Uint16, Int32, Uint32, Float32, Float64

            /**
             * Specifies the byte format of the index data.
             * @var {x3dom.fields.SFString} indexType
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue "Uint16"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'indexType', "Uint16");

            /**
             * Specifies the byte format of the coordinates.
             * @var {x3dom.fields.SFString} coordType
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue "Float32"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'coordType', "Float32");

            /**
             * Specifies the byte format of the normals.
             * @var {x3dom.fields.SFString} normalType
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue "Float32"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normalType', "Float32");

            /**
             * Specifies the byte format of the texture coordinates.
             * @var {x3dom.fields.SFString} texCoordType
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue "Float32"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'texCoordType', "Float32");

            /**
             * Specifies the byte format of the colors.
             * @var {x3dom.fields.SFString} colorType
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue "Float32"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'colorType', "Float32");

            /**
             * Specifies the byte format of the tangents.
             * @var {x3dom.fields.SFString} tangentType
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue "Float32"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'tangentType', "Float32");

            /**
             * Specifies the byte format of the binormals.
             * @var {x3dom.fields.SFString} binormalType
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue "Float32"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'binormalType', "Float32");


            /**
             * Specifies whether the normals are encoded as spherical coordinates.
             * @var {x3dom.fields.SFBool} normalAsSphericalCoordinates
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'normalAsSphericalCoordinates', false);

            /**
             * Enables RGBA colors.
             * @var {x3dom.fields.SFBool} rgbaColors
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'rgbaColors', false);

            /**
             * Specifies the number of texture coordinates per vertex.
             * @var {x3dom.fields.SFInt32} numTexCoordComponents
             * @range [1, inf]
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue 2
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'numTexCoordComponents', 2);

            /**
             * Specifies whether normals are stored per vertex or per face.
             * @var {x3dom.fields.SFBool} normalPerVertex
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'normalPerVertex', true);

            /**
             * Flag that specifies whether vertex IDs are given as texture coordinates.
             * @var {x3dom.fields.SFBool} idsPerVertex
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'idsPerVertex', false);

            /**
             * Flag that specifies whether the binary files are GZip compressed.
             * @var {x3dom.fields.SFBool} compressed
             * @memberof x3dom.nodeTypes.BinaryGeometry
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'compressed', false);

            // workaround
            this._hasStrideOffset = false;
            this._mesh._numPosComponents = this._vf.normalAsSphericalCoordinates ? 4 : 3;
            this._mesh._numTexComponents = this._vf.numTexCoordComponents;
            this._mesh._numColComponents = this._vf.rgbaColors ? 4 : 3;
            this._mesh._numNormComponents = this._vf.normalAsSphericalCoordinates ? 2 : 3;

            // info helper members
            this._vertexCountSum = 0;
            for (var i=0; i<this._vf.vertexCount.length; ++i) {
                this._vertexCountSum += this._vf.vertexCount[i];
            }
        
        },
        {
            parentAdded: function(parent)
            {
                // TODO; also handle multiple shape parents!
                var offsetInd, strideInd, offset, stride;

                offsetInd = this._vf.coord.lastIndexOf('#');
                strideInd = this._vf.coord.lastIndexOf('+');
                if (offsetInd >= 0 && strideInd >= 0) {
                    offset = +this._vf.coord.substring(++offsetInd, strideInd);
                    stride = +this._vf.coord.substring(strideInd);
                    parent._coordStrideOffset = [stride, offset];
                    this._hasStrideOffset = true;
                    if ((offset / 8) - Math.floor(offset / 8) == 0) {
                        this._mesh._numPosComponents = 4;
                    }
                    //x3dom.debug.logInfo("coord stride/offset: " + stride + ", " + offset);
                }
                else if (strideInd >= 0) {
                    stride = +this._vf.coord.substring(strideInd);
                    parent._coordStrideOffset = [stride, 0];
                    if ((stride / 8) - Math.floor(stride / 8) == 0) {
                        this._mesh._numPosComponents = 4;   // ???
                    }
                    //x3dom.debug.logInfo("coord stride: " + stride);
                }

                offsetInd = this._vf.normal.lastIndexOf('#');
                strideInd = this._vf.normal.lastIndexOf('+');
                if (offsetInd >= 0 && strideInd >= 0) {
                    offset = +this._vf.normal.substring(++offsetInd, strideInd);
                    stride = +this._vf.normal.substring(strideInd);
                    parent._normalStrideOffset = [stride, offset];
                    //x3dom.debug.logInfo("normal stride/offset: " + stride + ", " + offset);
                }
                else if (strideInd >= 0) {
                    stride = +this._vf.normal.substring(strideInd);
                    parent._normalStrideOffset = [stride, 0];
                    //x3dom.debug.logInfo("normal stride: " + stride);
                }

                offsetInd = this._vf.texCoord.lastIndexOf('#');
                strideInd = this._vf.texCoord.lastIndexOf('+');
                if (offsetInd >= 0 && strideInd >= 0) {
                    offset = +this._vf.texCoord.substring(++offsetInd, strideInd);
                    stride = +this._vf.texCoord.substring(strideInd);
                    parent._texCoordStrideOffset = [stride, offset];
                    //x3dom.debug.logInfo("texCoord stride/offset: " + stride + ", " + offset);
                }
                else if (strideInd >= 0) {
                    stride = +this._vf.texCoord.substring(strideInd);
                    parent._texCoordStrideOffset = [stride, 0];
                    //x3dom.debug.logInfo("texCoord stride: " + stride);
                }

                offsetInd = this._vf.color.lastIndexOf('#');
                strideInd = this._vf.color.lastIndexOf('+');
                if (offsetInd >= 0 && strideInd >= 0) {
                    offset = +this._vf.color.substring(++offsetInd, strideInd);
                    stride = +this._vf.color.substring(strideInd);
                    parent._colorStrideOffset = [stride, offset];
                    //x3dom.debug.logInfo("color stride/offset: " + stride + ", " + offset);
                }
                else if (strideInd >= 0) {
                    stride = +this._vf.color.substring(strideInd);
                    parent._colorStrideOffset = [stride, 0];
                    //x3dom.debug.logInfo("color stride: " + stride);
                }

                if (this._vf.indexType != "Uint16" && !x3dom.caps.INDEX_UINT)
                    x3dom.debug.logWarning("Index type " + this._vf.indexType + " problematic");
            },

            doIntersect: function(line)
            {
                var min = this.getMin();
                var max = this.getMax();
                var isect = line.intersect(min, max);

                if (isect && line.enter < line.dist) {
                    line.dist = line.enter;
                    line.hitObject = this;
                    line.hitPoint = line.pos.add(line.dir.multiply(line.enter));
                    return true;
                }
                else {
                    return false;
                }
            },

            getPrecisionMax: function(type)
            {
                switch(this._vf[type])
                {
                    case "Int8":
                        return 127.0;
                    case "Uint8":
                        return 255.0;
                    case "Int16":
                        return 32767.0;
                    case "Uint16":
                        return 65535.0;
                    case "Int32":
                        return 2147483647.0;
                    case "Uint32":
                        return 4294967295.0;
                    case "Float32":
                    case "Float64":
                    default:
                        return 1.0;
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PopGeometryLevel ### */
x3dom.registerNodeType(
    "PopGeometryLevel",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DGeometricPropertyNode,
        
        /**
         * Constructor for PopGeometryLevel
         * @constructs x3dom.nodeTypes.PopGeometryLevel
         * @x3d x.x
         * @component Geometry3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometricPropertyNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PopGeometryLevel node holds data of one refinement level for the PopGeometry node.
         */
        function (ctx) {
            x3dom.nodeTypes.PopGeometryLevel.superClass.call(this, ctx);


            /**
             * Location of the binary file that contains the data of this refinement level.
             * @var {x3dom.fields.SFString} src
             * @memberof x3dom.nodeTypes.PopGeometryLevel
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'src', "");

            /**
             * Number of indices shipped with this refinement level.
             * @var {x3dom.fields.SFInt32} numIndices
             * @memberof x3dom.nodeTypes.PopGeometryLevel
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'numIndices', 0);

            /**
             * Offset of the interleaved attribute data of this refinement level within the target vertex buffer.
             * @var {x3dom.fields.SFInt32} vertexDataBufferOffset
             * @memberof x3dom.nodeTypes.PopGeometryLevel
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'vertexDataBufferOffset', 0);
        
        },
        {
            getSrc: function () {
                return this._vf.src;
            },

            getNumIndices: function () {
                return this._vf.numIndices;
            },

            getVertexDataBufferOffset: function () {
                return this._vf.vertexDataBufferOffset;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PopGeometry ### */
x3dom.registerNodeType(
    "PopGeometry",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DBinaryContainerGeometryNode,
        
        /**
         * Constructor for PopGeometry
         * @constructs x3dom.nodeTypes.PopGeometry
         * @x3d x.x
         * @component Geometry3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DBinaryContainerGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PopGeometry node provides a first, experimental implementation of the POP Buffer algorithm for progressive streaming of triangular mesh data.
         */
        function (ctx) {
            x3dom.nodeTypes.PopGeometry.superClass.call(this, ctx);

            /**
             * The size of the bounding box of this geometry, as it is used for culling.
             * @var {x3dom.fields.SFVec3f} tightSize
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f (ctx, 'tightSize',  1, 1, 1);
            //@todo: add this on export

            /**
             * The size of the bounding box used to quantize data in this geometry,
             * which is usually the largest bounding box of all sub-meshes of a given mesh.
             * @var {x3dom.fields.SFVec3f} maxBBSize
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f (ctx, 'maxBBSize',  1, 1, 1);

            /**
             * The minimum coordinates of the bounding box, in a normalized range between [0,1],
             * and given modulo maxBBSize.
             * @var {x3dom.fields.SFVec3f} bbMinModF
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f (ctx, 'bbMinModF',  0, 0, 0);

            /**
             * The maximum coordinates of the bounding box, in a normalized range between [0,1],
             * and given modulo maxBBSize.
             * @var {x3dom.fields.SFVec3f} bbMaxModF
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f (ctx, 'bbMaxModF',  1, 1, 1);

            /**
             * Minimum coordinates of the bounding box, in object coordinates.
             * @var {x3dom.fields.SFVec3f} bbMin
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f (ctx, 'bbMin', 0, 0, 0);

            /**
             * Field for internal use.
             * @var {x3dom.fields.SFVec3f} bbShiftVec
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f (ctx, 'bbShiftVec', 0, 0, 0);

            if (this._vf.bbMinModF.x > this._vf.bbMaxModF.x)
                this._vf.bbShiftVec.x = 1.0;
            if (this._vf.bbMinModF.y > this._vf.bbMaxModF.y)
                this._vf.bbShiftVec.y = 1.0;
            if (this._vf.bbMinModF.z > this._vf.bbMaxModF.z)
                this._vf.bbShiftVec.z = 1.0;


            /**
             * Number of levels of this pop geometry.
             * @var {x3dom.fields.MFNode} levels
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue x3dom.nodeTypes.PopGeometryLevel
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('levels', x3dom.nodeTypes.PopGeometryLevel);


            /**
             * Stride of all (interleaved) attributes, given in bytes.
             * @var {x3dom.fields.SFInt32} attributeStride
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'attributeStride',   0);

            /**
             * Offset, given in bytes, for the position attribute inside the interleaved attribute array.
             * @var {x3dom.fields.SFInt32} positionOffset
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'positionOffset',    0);

            /**
             * Offset, given in bytes, for the normal attribute inside the interleaved attribute array.
             * @var {x3dom.fields.SFInt32} normalOffset
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'normalOffset',      0);

            /**
             * Offset, given in bytes, for the texture coordinate attribute inside the interleaved attribute array.
             * @var {x3dom.fields.SFInt32} texcoordOffset
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'texcoordOffset',    0);

            /**
             * Offset, given in bytes, for the color attribute inside the interleaved attribute array.
             * @var {x3dom.fields.SFInt32} colorOffset
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'colorOffset',       0);

            /**
             * Number of anchor vertices (can be 0).
             * Anchor vertices are used to keep some vertices on the bordes between sub-meshes fixed during refinement.
             * @var {x3dom.fields.SFInt32} numAnchorVertices
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'numAnchorVertices', 0);


            /**
             * Precision, given in bytes, for the components of the position attribute.
             * @var {x3dom.fields.SFInt32} positionPrecision
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 2
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'positionPrecision', 2);

            /**
             * Precision, given in bytes, for the components of the normal attribute.
             * @var {x3dom.fields.SFInt32} normalPrecision
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'normalPrecision',   1);

            /**
             * Precision, given in bytes, for the components of the texture coordinate attribute.
             * @var {x3dom.fields.SFInt32} texcoordPrecision
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 2
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'texcoordPrecision', 2);

            /**
             * Precision, given in bytes, for the components of the color attribute.
             * @var {x3dom.fields.SFInt32} colorPrecision
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'colorPrecision',    1);


            /**
             * Minimum precision level of this PopGeometry node.
             * This can be used to clamp displayed precision - if the value is -1, no clamping takes place.
             * @var {x3dom.fields.SFInt32} minPrecisionLevel
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'minPrecisionLevel', -1);

            /**
             * Maximum precision level of this PopGeometry node.
             * This can be used to clamp displayed precision - if the value is -1, no clamping takes place.
             * @var {x3dom.fields.SFInt32} maxPrecisionLevel
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'maxPrecisionLevel', -1);

            /**
             * Additional precision multiplication factor, for tuning the displayed precision.
             * @var {x3dom.fields.SFFloat} precisionFactor
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'precisionFactor',  1.0);

            //those four fields are read by the x3dom renderer

            /**
             * Field for internal use by the X3DOM renderer.
             * @var {x3dom.fields.SFString} coordType
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue "Uint16"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'coordType',    "Uint16");

            /**
             * Field for internal use by the X3DOM renderer.
             * @var {x3dom.fields.SFString} normalType
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue "Uint8"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normalType',   "Uint8");

            /**
             * Field for internal use by the X3DOM renderer.
             * @var {x3dom.fields.SFString} texCoordType
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue "Uint16"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'texCoordType', "Uint16");

            /**
             * Field for internal use by the X3DOM renderer.
             * @var {x3dom.fields.SFString} colorType
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue "Uint8"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'colorType',    "Uint8");


            /**
             * Size of the vertex buffer, used to pre-allocate the buffer before downloading data.
             * @var {x3dom.fields.SFInt32} vertexBufferSize
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'vertexBufferSize', 0);


            /**
             * Specifies whether this PopGeometry was encoded for indexed rendering.
             * @var {x3dom.fields.SFBool} indexedRendering
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'indexedRendering', true);
            //ATTENTION: Although it might be supported by aopt,
            //           X3DOM does not accept 16 bit spherical normals yet,
            //           spherical normals are assumed to be 8 bit and get
            //           encoded as the 4th 16 bit position component

            /**
             * Specifies whether this PopGeometry was encoded for rendering with spherical normals.
             * @var {x3dom.fields.SFBool} sphericalNormals
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'sphericalNormals', false);

            //needed as we manipulate vertexCount during loading

            /**
             * Vertex count at the highest possible level of precision.
             * @var {x3dom.fields.MFInt32} originalVertexCount
             * @memberof x3dom.nodeTypes.PopGeometry
             * @initvalue [0]
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'originalVertexCount', [0]);

            for (var i = 0; i < this._vf.vertexCount.length; ++i) {
                this._vf.originalVertexCount[i] = this._vf.vertexCount[i];
            }

            //@todo: remove this three lines after cleanup
            this._vf.maxBBSize = x3dom.fields.SFVec3f.copy(this._vf.size);
            this._vf.size  = this._vf.tightSize;
            this._diameter = this._vf.size.length();

            this._bbMinBySize = [ Math.floor(this._vf.bbMin.x / this._vf.maxBBSize.x),
                Math.floor(this._vf.bbMin.y / this._vf.maxBBSize.y),
                Math.floor(this._vf.bbMin.z / this._vf.maxBBSize.z) ];
            this._volRadius        = this._vf.size.length() / 2;
            this._volLargestRadius = this._vf.maxBBSize.length() / 2;

            // workaround
            this._mesh._numPosComponents  = this._vf.sphericalNormals ? 4 : 3;
            this._mesh._numNormComponents = this._vf.sphericalNormals ? 2 : 3;
            this._mesh._numTexComponents  = 2;
            this._mesh._numColComponents  = 3;

            x3dom.nodeTypes.PopGeometry.numTotalVerts += this.getVertexCount();
            x3dom.nodeTypes.PopGeometry.numTotalTris  += (this.hasIndex() ?
                this.getTotalNumberOfIndices() : this.getVertexCount()) / 3;
        
        },
        {
            forceUpdateCoverage: function() {
                return true;
            },

            getBBoxShiftVec: function() {
                return this._vf.bbShiftVec;
            },

            getBBoxSize: function() {
                return this._vf.size;
            },

            hasIndex: function() {
                return this._vf.indexedRendering;
            },

            getTotalNumberOfIndices: function() {
                if (this._vf.indexedRendering) {
                    var sum = 0;
                    for (var i = 0; i < this._vf.originalVertexCount.length; ++i) {
                        sum += this._vf.originalVertexCount[i];
                    }
                    return sum;
                }
                else  {
                    return 0;
                }
            },

            getVertexCount: function() {
                var sum = 0;
                for (var i = 0; i < this._vf.originalVertexCount.length; ++i) {
                    sum += this._vf.originalVertexCount[i];
                }
                return sum;
            },

            //adapts the vertex count according to the given total number of indices / vertices
            //which is used by the renderer
            adaptVertexCount: function(numVerts) {
                var verts = 0;
                for (var i = 0; i < this._vf.originalVertexCount.length; ++i) {
                    if ((this._vf.originalVertexCount[i] + verts) <= numVerts) {
                        this._vf.vertexCount[i] = this._vf.originalVertexCount[i];
                        verts += this._vf.originalVertexCount[i];
                    }
                    else {
                        this._vf.vertexCount[i] = numVerts - verts;
                        break;
                    }
                }
            },

            hasNormal: function() {
                return (this._vf.normalOffset != 0) && !this._vf.sphericalNormals;
            },

            hasTexCoord: function() {
                return (this._vf.texcoordOffset != 0);
            },

            hasColor: function() {
                return (this._vf.colorOffset != 0);
            },

            getPositionPrecision : function() {
                return this._vf.positionPrecision;
            },

            getNormalPrecision : function() {
                return this._vf.normalPrecision;
            },

            getTexCoordPrecision : function() {
                return this._vf.texcoordPrecision;
            },

            getColorPrecision : function() {
                return this._vf.colorPrecision;
            },

            getAttributeStride : function() {
                return this._vf.attributeStride;
            },

            getPositionOffset : function() {
                return this._vf.positionOffset;
            },

            getNormalOffset : function() {
                return this._vf.normalOffset;
            },

            getTexCoordOffset : function() {
                return this._vf.texcoordOffset;
            },

            getColorOffset : function() {
                return this._vf.colorOffset;
            },

            getBufferTypeStringFromByteCount: function(bytes) {
                switch(bytes)
                {
                    case 1:
                        return "Uint8";
                    case 2:
                        return "Uint16";
                    //case 4: //currently not supported by PopGeometry
                    //    return "Float32";
                    default:
                        return 0;
                }
            },

            getDataURLs : function() {
                var urls = [];

                for (var i = 0; i < this._cf.levels.nodes.length; ++i) {
                    urls.push(this._cf.levels.nodes[i].getSrc());
                }

                return urls;
            },

            getNumIndicesByLevel : function(lvl) {
                return this._cf.levels.nodes[lvl].getNumIndices();
            },

            getNumLevels : function(lvl) {
                return this._cf.levels.nodes.length;
            },

            getVertexDataBufferOffset : function(lvl) {
                return this._cf.levels.nodes[lvl].getVertexDataBufferOffset();
            },

            getPrecisionMax: function(type) {
                switch(this._vf[type])
                {
                    //currently, only Uint8 and Uint16 are supported
                    //case "Int8":
                    //    return 127.0;
                    case "Uint8":
                        return 255.0;
                    //case "Int16":
                    //    return 32767.0;
                    case "Uint16":
                        return 65535.0;
                    //case "Int32":
                    //return 2147483647.0;
                    //case "Uint32":
                    //return 4294967295.0;
                    //case "Float32":
                    //case "Float64":
                    default:
                        return 1.0;
                }
            }
        }
    )
);


/** Static class members (needed for stats) */
x3dom.nodeTypes.PopGeometry.ErrorToleranceFactor  = 1;
x3dom.nodeTypes.PopGeometry.PrecisionFactorOnMove = 1;
x3dom.nodeTypes.PopGeometry.numRenderedVerts      = 0;
x3dom.nodeTypes.PopGeometry.numRenderedTris       = 0;
x3dom.nodeTypes.PopGeometry.numTotalVerts         = 0;
x3dom.nodeTypes.PopGeometry.numTotalTris          = 0;

/** Static LUT for LOD computation */
x3dom.nodeTypes.PopGeometry.powLUT = [32768, 16384, 8192, 4096, 2048, 1024, 512, 256,
    128,    64,   32,   16,   8,    4,    2,   1];
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ImageGeometry ### */
x3dom.registerNodeType(
    "ImageGeometry",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DBinaryContainerGeometryNode,
        
        /**
         * Constructor for ImageGeometry
         * @constructs x3dom.nodeTypes.ImageGeometry
         * @x3d x.x
         * @component Geometry3D
         * @extends x3dom.nodeTypes.X3DBinaryContainerGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The image geometry node loads data stored in an image file.
         */
        function (ctx) {
            x3dom.nodeTypes.ImageGeometry.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFVec2f} implicitMeshSize
             * @memberof x3dom.nodeTypes.ImageGeometry
             * @initvalue 256,256
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'implicitMeshSize', 256, 256);

            /**
             * Specifies the number of color components.
             * @var {x3dom.fields.SFInt32} numColorComponents
             * @memberof x3dom.nodeTypes.ImageGeometry
             * @initvalue 3
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'numColorComponents', 3);

            /**
             * Specifies the number of texture coordinate components.
             * @var {x3dom.fields.SFInt32} numTexCoordComponents
             * @memberof x3dom.nodeTypes.ImageGeometry
             * @initvalue 2
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'numTexCoordComponents', 2);


            /**
             * Specifies the image file that contains the index data.
             * @var {x3dom.fields.SFNode} index
             * @memberof x3dom.nodeTypes.ImageGeometry
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('index', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Specifies the image file that contains the coord data.
             * @var {x3dom.fields.MFNode} coord
             * @memberof x3dom.nodeTypes.ImageGeometry
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('coord', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Specifies the image file that contains the normal data.
             * @var {x3dom.fields.SFNode} normal
             * @memberof x3dom.nodeTypes.ImageGeometry
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('normal', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Specifies the image file that contains the texcoord data.
             * @var {x3dom.fields.SFNode} texCoord
             * @memberof x3dom.nodeTypes.ImageGeometry
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('texCoord', x3dom.nodeTypes.X3DTextureNode);

            /**
             * Specifies the image file that contains the color data.
             * @var {x3dom.fields.SFNode} color
             * @memberof x3dom.nodeTypes.ImageGeometry
             * @initvalue x3dom.nodeTypes.X3DTextureNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('color', x3dom.nodeTypes.X3DTextureNode);

            this._mesh._numColComponents = this._vf.numColorComponents;
            this._mesh._numTexComponents = this._vf.numTexCoordComponents;

            if (this._vf.implicitMeshSize.y == 0)
                this._vf.implicitMeshSize.y = this._vf.implicitMeshSize.x;

            //TODO check if GPU-Version is supported (Flash, etc.)
            //Dummy mesh generation only needed for GPU-Version
            if (x3dom.caps.BACKEND == 'webgl' && x3dom.caps.MAX_VERTEX_TEXTURE_IMAGE_UNITS > 0) {

                var geoCacheID = 'ImageGeometry_' + this._vf.implicitMeshSize.x + '_' + this._vf.implicitMeshSize.y;

                if( this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined )
                {
                    //x3dom.debug.logInfo("Using ImageGeometry-Mesh from Cache");
                    this._mesh = x3dom.geoCache[geoCacheID];
                }
                else
                {
                    for(var y=0; y<this._vf.implicitMeshSize.y; y++)
                    {
                        for(var x=0; x<this._vf.implicitMeshSize.x; x++)
                        {
                            this._mesh._positions[0].push(x / this._vf.implicitMeshSize.x,
                                    y / this._vf.implicitMeshSize.y, 0);
                        }
                    }

                    //this._mesh._invalidate = true;
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    x3dom.geoCache[geoCacheID] = this._mesh;
                }
            }

            // needed because mesh is shared due to cache
            this._vol = new x3dom.fields.BoxVolume();

            this._dirty = {
                coord: true,
                normal: true,
                texCoord: true,
                color: true,
                index: true
            };
        
        },
        {
            setGeoDirty: function () {
                this._dirty.coord = true;
                this._dirty.normal = true;
                this._dirty.texCoords = true;
                this._dirty.color = true;
                this._dirty.index = true;
            },

            unsetGeoDirty: function () {
                this._dirty.coord = false;
                this._dirty.normal = false;
                this._dirty.texCoords = false;
                this._dirty.color = false;
                this._dirty.index = false;
            },

            nodeChanged: function()
            {
                Array.forEach(this._parentNodes, function (node) {
                    node._dirty.positions = true;
                    node._dirty.normals = true;
                    node._dirty.texcoords = true;
                    node._dirty.colors = true;
                });
                this._vol.invalidate();
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "index" ||fieldName == "coord" || fieldName == "normal" ||
                    fieldName == "texCoord" || fieldName == "color") {
                    this._dirty[fieldName] = true;
                    this._vol.invalidate();
                }
                else if (fieldName == "implicitMeshSize") {
                    this._vol.invalidate();
                }
            },

            getMin: function() {
                var vol = this._vol;

                if (!vol.isValid()) {
                    vol.setBoundsByCenterSize(this._vf.position, this._vf.size);
                }

                return vol.min;
            },

            getMax: function() {
                var vol = this._vol;

                if (!vol.isValid()) {
                    vol.setBoundsByCenterSize(this._vf.position, this._vf.size);
                }

                return vol.max;
            },

            getVolume: function() {
                var vol = this._vol;

                if (!vol.isValid()) {
                    vol.setBoundsByCenterSize(this._vf.position, this._vf.size);
                }

                return vol;
            },

            numCoordinateTextures: function()
            {
                return this._cf.coord.nodes.length;
            },

            getIndexTexture: function()
            {
                if(this._cf.index.node) {
                    this._cf.index.node._type = "IG_index";
                    return this._cf.index.node;
                } else {
                    return null;
                }
            },

            getIndexTextureURL: function()
            {
                if(this._cf.index.node) {
                    return this._cf.index.node._vf.url;
                } else {
                    return null;
                }
            },

            getCoordinateTexture: function(pos)
            {
                if(this._cf.coord.nodes[pos]) {
                    this._cf.coord.nodes[pos]._type = "IG_coords" + pos;
                    return this._cf.coord.nodes[pos];
                } else {
                    return null;
                }
            },

            getCoordinateTextureURL: function(pos)
            {
                if(this._cf.coord.nodes[pos]) {
                    return this._cf.coord.nodes[pos]._vf.url;
                } else {
                    return null;
                }
            },

            getCoordinateTextureURLs: function()
            {
                var urls = [];
                for(var i=0; i<this._cf.coord.nodes.length; i++)
                {
                    urls.push(this._cf.coord.nodes[i]._vf.url);
                }
                return urls;
            },

            getNormalTexture: function()
            {
                if(this._cf.normal.node) {
                    this._cf.normal.node._type = "IG_normals";
                    return this._cf.normal.node;
                } else {
                    return null;
                }
            },

            getNormalTextureURL: function()
            {
                if(this._cf.normal.node) {
                    return this._cf.normal.node._vf.url;
                } else {
                    return null;
                }
            },

            getTexCoordTexture: function()
            {
                if(this._cf.texCoord.node) {
                    this._cf.texCoord.node._type = "IG_texCoords";
                    return this._cf.texCoord.node;
                } else {
                    return null;
                }
            },

            getTexCoordTextureURL: function()
            {
                if(this._cf.texCoord.node) {
                    return this._cf.texCoord.node._vf.url;
                } else {
                    return null;
                }
            },

            getColorTexture: function()
            {
                if(this._cf.color.node) {
                    this._cf.color.node._type = "IG_colors";
                    return this._cf.color.node;
                } else {
                    return null;
                }
            },

            getColorTextureURL: function()
            {
                if(this._cf.color.node) {
                    return this._cf.color.node._vf.url;
                } else {
                    return null;
                }
            },

            getTextures: function()
            {
                var textures = [];

                var index = this.getIndexTexture();
                if(index) textures.push(index);

                for(i=0; i<this.numCoordinateTextures(); i++) {
                    var coord = this.getCoordinateTexture(i);
                    if(coord) textures.push(coord);
                }

                var normal = this.getNormalTexture();
                if(normal) textures.push(normal);

                var texCoord = this.getTexCoordTexture();
                if(texCoord) textures.push(texCoord);

                var color = this.getColorTexture();
                if(color) textures.push(color);

                return textures;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### IndexedFaceSet ### */
x3dom.registerNodeType(
    "IndexedFaceSet",
    "Geometry3D",
    defineClass(x3dom.nodeTypes.X3DComposedGeometryNode,
        
        /**
         * Constructor for IndexedFaceSet
         * @constructs x3dom.nodeTypes.IndexedFaceSet
         * @x3d 3.3
         * @component Geometry3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposedGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * The IndexedFaceSet node represents a 3D shape formed by constructing faces (polygons) from vertices listed in the coord field.
         */
        function (ctx) {
            x3dom.nodeTypes.IndexedFaceSet.superClass.call(this, ctx);


            /**
             * The creaseAngle field affects how default normals are generated.
             * If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced.
             * Crease angles shall be greater than or equal to 0.0 angle base units.
             * @var {x3dom.fields.SFFloat} creaseAngle
             * @memberof x3dom.nodeTypes.IndexedFaceSet
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'creaseAngle', 0);   // TODO

            /**
             * The convex field indicates whether all polygons in the shape are convex (TRUE).
             * A polygon is convex if it is planar, does not intersect itself, and all of the interior angles at its vertices are less than 180 degrees.
             * @var {x3dom.fields.SFBool} convex
             * @memberof x3dom.nodeTypes.IndexedFaceSet
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'convex', true);


            /**
             * The index data for the coord data.
             * @var {x3dom.fields.MFInt32} coordIndex
             * @memberof x3dom.nodeTypes.IndexedFaceSet
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'coordIndex', []);

            /**
             * The index data for the normal data.
             * @var {x3dom.fields.MFInt32} normalIndex
             * @memberof x3dom.nodeTypes.IndexedFaceSet
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'normalIndex', []);

            /**
             * The index data for the color data.
             * @var {x3dom.fields.MFInt32} colorIndex
             * @memberof x3dom.nodeTypes.IndexedFaceSet
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'colorIndex', []);

            /**
             * The index data for the texcoord data.
             * @var {x3dom.fields.MFInt32} texCoordIndex
             * @memberof x3dom.nodeTypes.IndexedFaceSet
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'texCoordIndex', []);
        
        },
        {
            nodeChanged: function()
            {
                var time0 = new Date().getTime();

                this.handleAttribs();

                var indexes = this._vf.coordIndex;

                //Last index value should be -1.
                if (indexes.length && indexes[indexes.length-1] != -1)
                {
                    indexes.push(-1);
                }

                var normalInd = this._vf.normalIndex;
                var texCoordInd = this._vf.texCoordIndex;
                var colorInd = this._vf.colorIndex;

                var hasNormal = false, hasNormalInd = false;
                var hasTexCoord = false, hasTexCoordInd = false;
                var hasColor = false, hasColorInd = false;

                var colPerVert = this._vf.colorPerVertex;
                var normPerVert = this._vf.normalPerVertex;

                if (normalInd.length > 0)
                {
                    hasNormalInd = true;
                }
                if (texCoordInd.length > 0)
                {
                    hasTexCoordInd = true;
                }
                if (colorInd.length > 0)
                {
                    hasColorInd = true;
                }

                var positions, normals, texCoords, colors;

                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode);
                positions = coordNode.getPoints();

                var normalNode = this._cf.normal.node;
                if (normalNode)
                {
                    hasNormal = true;
                    normals = normalNode._vf.vector;
                }
                else {
                    hasNormal = false;
                }

                var texMode = "", numTexComponents = 2;
                var texCoordNode = this._cf.texCoord.node;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }
                if (texCoordNode)
                {
                    if (texCoordNode._vf.point) {
                        hasTexCoord = true;
                        texCoords = texCoordNode._vf.point;

                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }
                    }
                    else if (texCoordNode._vf.mode) {
                        texMode = texCoordNode._vf.mode;
                    }
                }
                else {
                    hasTexCoord = false;
                }
                this._mesh._numTexComponents = numTexComponents;

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                if (colorNode)
                {
                    hasColor = true;
                    colors = colorNode._vf.color;

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }
                else {
                    hasColor = false;
                }
                this._mesh._numColComponents = numColComponents;

                this._mesh._indices[0] = [];
                this._mesh._positions[0] = [];
                this._mesh._normals[0] = [];
                this._mesh._texCoords[0] = [];
                this._mesh._colors[0] = [];

                var i, j, t, cnt, faceCnt;
                var p0, p1, p2, n0, n1, n2, t0, t1, t2, c0, c1, c2;

                if ( (this._vf.creaseAngle <= x3dom.fields.Eps) ||  // FIXME; what to do for ipols?
                    (positions.length > x3dom.Utils.maxIndexableCoords) ||
                    (hasNormal && hasNormalInd) ||
                    (hasTexCoord && hasTexCoordInd) ||
                    (hasColor && hasColorInd) )
                {
                    if (this._vf.creaseAngle <= x3dom.fields.Eps)
                        x3dom.debug.logWarning('Fallback to inefficient multi-index mode since creaseAngle=0.');

                    // Found MultiIndex Mesh
                    if(this._vf.convex) {
                        t = 0;
                        cnt = 0;
                        faceCnt = 0;
                        this._mesh._multiIndIndices = [];
                        this._mesh._posSize = positions.length;

                        for (i=0; i < indexes.length; ++i)
                        {
                            // Convert non-triangular polygons to a triangle fan
                            // (TODO: this assumes polygons are convex)
                            if (indexes[i] == -1) {
                                t = 0;
                                faceCnt++;
                                continue;
                            }

                            if (hasNormalInd) {
                                x3dom.debug.assert(normalInd[i] != -1);
                            }
                            if (hasTexCoordInd) {
                                x3dom.debug.assert(texCoordInd[i] != -1);
                            }
                            if (hasColorInd) {
                                x3dom.debug.assert(colorInd[i] != -1);
                            }

                            //TODO: OPTIMIZE but think about cache coherence regarding arrays!!!
                            switch (t)
                            {
                                case 0:
                                    p0 = +indexes[i];
                                    if (hasNormalInd && normPerVert) { n0 = +normalInd[i]; }
                                    else if (hasNormalInd && !normPerVert) { n0 = +normalInd[faceCnt]; }
                                    else if (normPerVert) { n0 = p0; }
                                    else { n0 = faceCnt; }

                                    if (hasTexCoordInd) { t0 = +texCoordInd[i]; }
                                    else { t0 = p0; }
                                    if (hasColorInd && colPerVert) { c0 = +colorInd[i]; }
                                    else if (hasColorInd && !colPerVert) { c0 = +colorInd[faceCnt]; }
                                    else if (colPerVert) { c0 = p0; }
                                    else { c0 = faceCnt; }
                                    t = 1;
                                    break;
                                case 1:
                                    p1 = +indexes[i];
                                    if (hasNormalInd && normPerVert) { n1 = +normalInd[i]; }
                                    else if (hasNormalInd && !normPerVert) { n1 = +normalInd[faceCnt]; }
                                    else if (normPerVert) { n1 = p1; }
                                    else { n1 = faceCnt; }

                                    if (hasTexCoordInd) { t1 = +texCoordInd[i]; }
                                    else { t1 = p1; }
                                    if (hasColorInd && colPerVert) { c1 = +colorInd[i]; }
                                    else if (hasColorInd && !colPerVert) { c1 = +colorInd[faceCnt]; }
                                    else if (colPerVert) { c1 = p1; }
                                    else { c1 = faceCnt; }
                                    t = 2;
                                    break;
                                case 2:
                                    p2 = +indexes[i];
                                    if (hasNormalInd && normPerVert) { n2 = +normalInd[i]; }
                                    else if (hasNormalInd && !normPerVert) { n2 = +normalInd[faceCnt]; }
                                    else if (normPerVert) { n2 = p2; }
                                    else { n2 = faceCnt; }

                                    if (hasTexCoordInd) { t2 = +texCoordInd[i]; }
                                    else { t2 = p2; }
                                    if (hasColorInd && colPerVert) { c2 = +colorInd[i]; }
                                    else if (hasColorInd && !colPerVert) { c2 = +colorInd[faceCnt]; }
                                    else if (colPerVert) { c2 = p2; }
                                    else { c2 = faceCnt; }
                                    t = 3;

                                    //this._mesh._indices[0].push(cnt++, cnt++, cnt++);

                                    this._mesh._positions[0].push(positions[p0].x);
                                    this._mesh._positions[0].push(positions[p0].y);
                                    this._mesh._positions[0].push(positions[p0].z);
                                    this._mesh._positions[0].push(positions[p1].x);
                                    this._mesh._positions[0].push(positions[p1].y);
                                    this._mesh._positions[0].push(positions[p1].z);
                                    this._mesh._positions[0].push(positions[p2].x);
                                    this._mesh._positions[0].push(positions[p2].y);
                                    this._mesh._positions[0].push(positions[p2].z);

                                    if (hasNormal) {
                                        this._mesh._normals[0].push(normals[n0].x);
                                        this._mesh._normals[0].push(normals[n0].y);
                                        this._mesh._normals[0].push(normals[n0].z);
                                        this._mesh._normals[0].push(normals[n1].x);
                                        this._mesh._normals[0].push(normals[n1].y);
                                        this._mesh._normals[0].push(normals[n1].z);
                                        this._mesh._normals[0].push(normals[n2].x);
                                        this._mesh._normals[0].push(normals[n2].y);
                                        this._mesh._normals[0].push(normals[n2].z);
                                    }
                                    //else {
                                    this._mesh._multiIndIndices.push(p0, p1, p2);
                                    //this._mesh._multiIndIndices.push(cnt-3, cnt-2, cnt-1);
                                    //}

                                    if (hasColor) {
                                        this._mesh._colors[0].push(colors[c0].r);
                                        this._mesh._colors[0].push(colors[c0].g);
                                        this._mesh._colors[0].push(colors[c0].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c0].a);
                                        }
                                        this._mesh._colors[0].push(colors[c1].r);
                                        this._mesh._colors[0].push(colors[c1].g);
                                        this._mesh._colors[0].push(colors[c1].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c1].a);
                                        }
                                        this._mesh._colors[0].push(colors[c2].r);
                                        this._mesh._colors[0].push(colors[c2].g);
                                        this._mesh._colors[0].push(colors[c2].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c2].a);
                                        }
                                    }

                                    if (hasTexCoord) {
                                        this._mesh._texCoords[0].push(texCoords[t0].x);
                                        this._mesh._texCoords[0].push(texCoords[t0].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t0].z);
                                        }
                                        this._mesh._texCoords[0].push(texCoords[t1].x);
                                        this._mesh._texCoords[0].push(texCoords[t1].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t1].z);
                                        }
                                        this._mesh._texCoords[0].push(texCoords[t2].x);
                                        this._mesh._texCoords[0].push(texCoords[t2].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t2].z);
                                        }
                                    }

                                    //faceCnt++;
                                    break;
                                case 3:
                                    p1 = p2;
                                    t1 = t2;
                                    if (normPerVert) {
                                        n1 = n2;
                                    }
                                    if (colPerVert) {
                                        c1 = c2;
                                    }
                                    p2 = +indexes[i];

                                    if (hasNormalInd && normPerVert) {
                                        n2 = +normalInd[i];
                                    } else if (hasNormalInd && !normPerVert) {
                                        /*n2 = +normalInd[faceCnt];*/
                                    } else if (normPerVert) {
                                        n2 = p2;
                                    } else {
                                        n2 = faceCnt;
                                    }

                                    if (hasTexCoordInd) {
                                        t2 = +texCoordInd[i];
                                    } else {
                                        t2 = p2;
                                    }

                                    if (hasColorInd && colPerVert) {
                                        c2 = +colorInd[i];
                                    } else if (hasColorInd && !colPerVert) {
                                        /*c2 = +colorInd[faceCnt];*/
                                    } else if (colPerVert) {
                                        c2 = p2;
                                    } else {
                                        c2 = faceCnt;
                                    }

                                    //this._mesh._indices[0].push(cnt++, cnt++, cnt++);

                                    this._mesh._positions[0].push(positions[p0].x);
                                    this._mesh._positions[0].push(positions[p0].y);
                                    this._mesh._positions[0].push(positions[p0].z);
                                    this._mesh._positions[0].push(positions[p1].x);
                                    this._mesh._positions[0].push(positions[p1].y);
                                    this._mesh._positions[0].push(positions[p1].z);
                                    this._mesh._positions[0].push(positions[p2].x);
                                    this._mesh._positions[0].push(positions[p2].y);
                                    this._mesh._positions[0].push(positions[p2].z);

                                    if (hasNormal) {
                                        this._mesh._normals[0].push(normals[n0].x);
                                        this._mesh._normals[0].push(normals[n0].y);
                                        this._mesh._normals[0].push(normals[n0].z);
                                        this._mesh._normals[0].push(normals[n1].x);
                                        this._mesh._normals[0].push(normals[n1].y);
                                        this._mesh._normals[0].push(normals[n1].z);
                                        this._mesh._normals[0].push(normals[n2].x);
                                        this._mesh._normals[0].push(normals[n2].y);
                                        this._mesh._normals[0].push(normals[n2].z);
                                    }
                                    //else {
                                    this._mesh._multiIndIndices.push(p0, p1, p2);
                                    //this._mesh._multiIndIndices.push(cnt-3, cnt-2, cnt-1);
                                    //}

                                    if (hasColor) {
                                        this._mesh._colors[0].push(colors[c0].r);
                                        this._mesh._colors[0].push(colors[c0].g);
                                        this._mesh._colors[0].push(colors[c0].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c0].a);
                                        }
                                        this._mesh._colors[0].push(colors[c1].r);
                                        this._mesh._colors[0].push(colors[c1].g);
                                        this._mesh._colors[0].push(colors[c1].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c1].a);
                                        }
                                        this._mesh._colors[0].push(colors[c2].r);
                                        this._mesh._colors[0].push(colors[c2].g);
                                        this._mesh._colors[0].push(colors[c2].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c2].a);
                                        }
                                    }

                                    if (hasTexCoord) {
                                        this._mesh._texCoords[0].push(texCoords[t0].x);
                                        this._mesh._texCoords[0].push(texCoords[t0].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t0].z);
                                        }
                                        this._mesh._texCoords[0].push(texCoords[t1].x);
                                        this._mesh._texCoords[0].push(texCoords[t1].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t1].z);
                                        }
                                        this._mesh._texCoords[0].push(texCoords[t2].x);
                                        this._mesh._texCoords[0].push(texCoords[t2].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t2].z);
                                        }
                                    }

                                    //faceCnt++;
                                    break;
                                default:
                            }
                        }
                    }
                    else {
                        var linklist = new x3dom.DoublyLinkedList();
                        var data = {};
                        cnt = 0; faceCnt = 0;

                        for (i = 0; i < indexes.length; ++i)
                        {
                            if (indexes[i] == -1) {
                                var multi_index_data = x3dom.EarClipping.getMultiIndexes(linklist);

                                for (j = 0; j < multi_index_data.indices.length; j++)
                                {
                                    this._mesh._indices[0].push(cnt);
                                    cnt++;

                                    this._mesh._positions[0].push(multi_index_data.point[j].x,
                                        multi_index_data.point[j].y,
                                        multi_index_data.point[j].z);
                                    if (hasNormal) {
                                        this._mesh._normals[0].push(multi_index_data.normals[j].x,
                                            multi_index_data.normals[j].y,
                                            multi_index_data.normals[j].z);
                                    }
                                    if (hasColor) {
                                        this._mesh._colors[0].push(multi_index_data.colors[j].r,
                                            multi_index_data.colors[j].g,
                                            multi_index_data.colors[j].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(multi_index_data.colors[j].a);
                                        }
                                    }
                                    if (hasTexCoord) {
                                        this._mesh._texCoords[0].push(multi_index_data.texCoords[j].x,
                                            multi_index_data.texCoords[j].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(multi_index_data.texCoords[j].z);
                                        }
                                    }
                                }

                                linklist = new x3dom.DoublyLinkedList();
                                faceCnt++;
                                continue;
                            }

                            if (hasNormal) {
                                if (hasNormalInd && normPerVert) {
                                    data.normals =  normals[normalInd[i]];
                                } else if (hasNormalInd && !normPerVert) {
                                    data.normals =  normals[normalInd[faceCnt]];
                                } else {
                                    data.normals =  normals[indexes[i]];
                                }
                            }

                            if (hasColor) {
                                if (hasColorInd && colPerVert) {
                                    data.colors =  colors[colorInd[i]];
                                } else if (hasColorInd && !colPerVert) {
                                    data.colors =  colors[colorInd[faceCnt]];
                                } else if (colPerVert) {
                                    data.colors =  colors[indexes[i]];
                                } else {
                                    data.colors =  colors[faceCnt];
                                }
                            }
                            if (hasTexCoord) {
                                if (hasTexCoordInd) {
                                    data.texCoords =  texCoords[texCoordInd[i]];
                                } else {
                                    data.texCoords =  texCoords[indexes[i]];
                                }
                            }

                            linklist.appendNode(new x3dom.DoublyLinkedList.ListNode(
                                positions[indexes[i]], indexes[i], data.normals, data.colors, data.texCoords));
                        }

                        this._mesh.splitMesh();
                    }

                    if (!hasNormal) {
                        this._mesh.calcNormals(this._vf.creaseAngle, this._vf.ccw);
                    }
                    if (!hasTexCoord) {
                        this._mesh.calcTexCoords(texMode);
                    }
                } // if isMulti
                else
                {
                    t = 0;
                    if (this._vf.convex) {
                        for (i = 0; i < indexes.length; ++i)
                        {
                            // Convert non-triangular polygons to a triangle fan
                            if (indexes[i] == -1) {
                                t = 0;
                                continue;
                            }

                            switch (t) {
                                case 0: n0 = +indexes[i]; t = 1; break;
                                case 1: n1 = +indexes[i]; t = 2; break;
                                case 2: n2 = +indexes[i]; t = 3; this._mesh._indices[0].push(n0, n1, n2); break;
                                case 3: n1 = n2; n2 = +indexes[i]; this._mesh._indices[0].push(n0, n1, n2); break;
                            }

                        }
                    }
                    else {
                        //  Convert non-triangular convex polygons to a triangle fan
                        linklist = new x3dom.DoublyLinkedList();
                        for (i = 0; i < indexes.length; ++i)
                        {
                            if (indexes[i] == -1) {
                                var linklist_indices = x3dom.EarClipping.getIndexes(linklist);

                                for (j = 0; j < linklist_indices.length; j++) {
                                    this._mesh._indices[0].push(linklist_indices[j]);
                                }
                                linklist = new x3dom.DoublyLinkedList();
                                continue;
                            }

                            linklist.appendNode(new x3dom.DoublyLinkedList.ListNode(positions[indexes[i]], indexes[i]));
                        }
                    }

                    this._mesh._positions[0] = positions.toGL();

                    if (hasNormal) {
                        this._mesh._normals[0] = normals.toGL();
                    }
                    else {
                        this._mesh.calcNormals(this._vf.creaseAngle, this._vf.ccw);
                    }
                    if (hasTexCoord) {
                        this._mesh._texCoords[0] = texCoords.toGL();
                        this._mesh._numTexComponents = numTexComponents;
                    }
                    else {
                        this._mesh.calcTexCoords(texMode);
                    }
                    if (hasColor) {
                        this._mesh._colors[0] = colors.toGL();
                        this._mesh._numColComponents = numColComponents;
                    }
                }

                this.invalidateVolume();

                this._mesh._numFaces = 0;
                this._mesh._numCoords = 0;

                for (i=0; i<this._mesh._positions.length; i++) {
                    var indexLength = this._mesh._indices[i].length;
                    var numCoords = this._mesh._positions[i].length / 3;
                    this._mesh._numCoords += numCoords;
                    if (indexLength > 0)
                        this._mesh._numFaces += indexLength / 3;
                    else
                        this._mesh._numFaces += numCoords / 3;
                }

                //var time1 = new Date().getTime() - time0;
                //x3dom.debug.logInfo("Mesh load time: " + time1 + " ms");
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName != "coord" && fieldName != "normal" &&
                    fieldName != "texCoord" && fieldName != "color" &&
                    fieldName != "coordIndex")
                {
                    x3dom.debug.logWarning("IndexedFaceSet: fieldChanged for " +
                        fieldName + " not yet implemented!");
                    return;
                }

                var pnts = this._cf.coord.node._vf.point;
                var n = pnts.length;

                var texCoordNode = this._cf.texCoord.node;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }

                if (((this._vf.creaseAngle <= x3dom.fields.Eps) || (n > x3dom.Utils.maxIndexableCoords) ||
                    (this._vf.normalIndex.length > 0 && this._cf.normal.node) ||
                    (this._vf.texCoordIndex.length > 0 && texCoordNode) ||
                    (this._vf.colorIndex.length > 0 && this._cf.color.node)) && this._mesh._multiIndIndices)
                {
                    var needNormals = !this._cf.normal.node && this._vf.normalUpdateMode.toLowerCase() != 'none';

                    n = this._mesh._multiIndIndices.length;

                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] =[];

                    // special coordinate interpolator handler
                    if (fieldName == "coord" && n)
                    {
                        if (needNormals) {
                            this._mesh._normals[0] = [];
                        }

                        for (i=0; i<n; i+=3) {
                            var ind0 = this._mesh._multiIndIndices[i  ];
                            var ind1 = this._mesh._multiIndIndices[i+1];
                            var ind2 = this._mesh._multiIndIndices[i+2];

                            var pos0 = pnts[ind0];
                            var pos1 = pnts[ind1];
                            var pos2 = pnts[ind2];

                            this._mesh._positions[0].push(pos0.x, pos0.y, pos0.z);
                            this._mesh._positions[0].push(pos1.x, pos1.y, pos1.z);
                            this._mesh._positions[0].push(pos2.x, pos2.y, pos2.z);

                            if (needNormals) {
                                var a = pos0.subtract(pos1);
                                var b = pos1.subtract(pos2);

                                var norm = a.cross(b).normalize();
                                if (!this._vf.ccw)
                                    norm = norm.negate();

                                this._mesh._normals[0].push(norm.x, norm.y, norm.z);
                                this._mesh._normals[0].push(norm.x, norm.y, norm.z);
                                this._mesh._normals[0].push(norm.x, norm.y, norm.z);
                            }
                        }

                        this.invalidateVolume();

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.positions = true;
                            if (needNormals)
                                node._dirty.normals = true;
                        });

                        return;
                    }

                    // TODO; optimize this very slow and brute force code, at least for creaseAngle=0 case!
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] =[];
                    this._mesh._colors[0] = [];

                    var indexes = this._vf.coordIndex;
                    var normalInd = this._vf.normalIndex;
                    var texCoordInd = this._vf.texCoordIndex;
                    var colorInd = this._vf.colorIndex;
                    var hasNormal = false, hasNormalInd = false;
                    var hasTexCoord = false, hasTexCoordInd = false;
                    var hasColor = false, hasColorInd = false;

                    var colPerVert = this._vf.colorPerVertex;
                    var normPerVert = this._vf.normalPerVertex;

                    if (normalInd.length > 0)
                    {
                        hasNormalInd = true;
                    }
                    if (texCoordInd.length > 0)
                    {
                        hasTexCoordInd = true;
                    }
                    if (colorInd.length > 0)
                    {
                        hasColorInd = true;
                    }

                    var positions, normals, texCoords, colors;

                    var coordNode = this._cf.coord.node;
                    x3dom.debug.assert(coordNode);
                    positions = coordNode.getPoints();

                    var normalNode = this._cf.normal.node;
                    if (normalNode)
                    {
                        hasNormal = true;
                        normals = normalNode._vf.vector;
                    }
                    else {
                        hasNormal = false;
                    }

                    var texMode = "", numTexComponents = 2;
                    texCoordNode = this._cf.texCoord.node;
                    if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                        if (texCoordNode._cf.texCoord.nodes.length)
                            texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                    }
                    if (texCoordNode)
                    {
                        if (texCoordNode._vf.point) {
                            hasTexCoord = true;
                            texCoords = texCoordNode._vf.point;

                            if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                                numTexComponents = 3;
                            }
                        }
                        else if (texCoordNode._vf.mode) {
                            texMode = texCoordNode._vf.mode;
                        }
                    }
                    else {
                        hasTexCoord = false;
                    }
                    this._mesh._numTexComponents = numTexComponents;

                    var numColComponents = 3;
                    var colorNode = this._cf.color.node;
                    if (colorNode)
                    {
                        hasColor = true;
                        colors = colorNode._vf.color;

                        if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                            numColComponents = 4;
                        }
                    }
                    else {
                        hasColor = false;
                    }
                    this._mesh._numColComponents = numColComponents;

                    var i, j, t, cnt, faceCnt;
                    var p0, p1, p2, n0, n1, n2, t0, t1, t2, c0, c1, c2;

                    if(this._vf.convex) {
                        t = 0;
                        cnt = 0;
                        faceCnt = 0;
                        this._mesh._multiIndIndices = [];
                        this._mesh._posSize = positions.length;

                        for (i=0; i < indexes.length; ++i)
                        {
                            if (indexes[i] == -1) {
                                t = 0;
                                faceCnt++;
                                continue;
                            }

                            if (hasNormalInd) {
                                x3dom.debug.assert(normalInd[i] != -1);
                            }
                            if (hasTexCoordInd) {
                                x3dom.debug.assert(texCoordInd[i] != -1);
                            }
                            if (hasColorInd) {
                                x3dom.debug.assert(colorInd[i] != -1);
                            }

                            switch (t)
                            {
                                case 0:
                                    p0 = +indexes[i];
                                    if (hasNormalInd && normPerVert) { n0 = +normalInd[i]; }
                                    else if (hasNormalInd && !normPerVert) { n0 = +normalInd[faceCnt]; }
                                    else if (normPerVert) { n0 = p0; }
                                    else { n0 = faceCnt; }

                                    if (hasTexCoordInd) { t0 = +texCoordInd[i]; }
                                    else { t0 = p0; }
                                    if (hasColorInd && colPerVert) { c0 = +colorInd[i]; }
                                    else if (hasColorInd && !colPerVert) { c0 = +colorInd[faceCnt]; }
                                    else if (colPerVert) { c0 = p0; }
                                    else { c0 = faceCnt; }
                                    t = 1;
                                    break;
                                case 1:
                                    p1 = +indexes[i];
                                    if (hasNormalInd && normPerVert) { n1 = +normalInd[i]; }
                                    else if (hasNormalInd && !normPerVert) { n1 = +normalInd[faceCnt]; }
                                    else if (normPerVert) { n1 = p1; }
                                    else { n1 = faceCnt; }

                                    if (hasTexCoordInd) { t1 = +texCoordInd[i]; }
                                    else { t1 = p1; }
                                    if (hasColorInd && colPerVert) { c1 = +colorInd[i]; }
                                    else if (hasColorInd && !colPerVert) { c1 = +colorInd[faceCnt]; }
                                    else if (colPerVert) { c1 = p1; }
                                    else { c1 = faceCnt; }
                                    t = 2;
                                    break;
                                case 2:
                                    p2 = +indexes[i];
                                    if (hasNormalInd && normPerVert) { n2 = +normalInd[i]; }
                                    else if (hasNormalInd && !normPerVert) { n2 = +normalInd[faceCnt]; }
                                    else if (normPerVert) { n2 = p2; }
                                    else { n2 = faceCnt; }

                                    if (hasTexCoordInd) { t2 = +texCoordInd[i]; }
                                    else { t2 = p2; }
                                    if (hasColorInd && colPerVert) { c2 = +colorInd[i]; }
                                    else if (hasColorInd && !colPerVert) { c2 = +colorInd[faceCnt]; }
                                    else if (colPerVert) { c2 = p2; }
                                    else { c2 = faceCnt; }
                                    t = 3;

                                    //this._mesh._indices[0].push(cnt++, cnt++, cnt++);

                                    this._mesh._positions[0].push(positions[p0].x);
                                    this._mesh._positions[0].push(positions[p0].y);
                                    this._mesh._positions[0].push(positions[p0].z);
                                    this._mesh._positions[0].push(positions[p1].x);
                                    this._mesh._positions[0].push(positions[p1].y);
                                    this._mesh._positions[0].push(positions[p1].z);
                                    this._mesh._positions[0].push(positions[p2].x);
                                    this._mesh._positions[0].push(positions[p2].y);
                                    this._mesh._positions[0].push(positions[p2].z);

                                    if (hasNormal) {
                                        this._mesh._normals[0].push(normals[n0].x);
                                        this._mesh._normals[0].push(normals[n0].y);
                                        this._mesh._normals[0].push(normals[n0].z);
                                        this._mesh._normals[0].push(normals[n1].x);
                                        this._mesh._normals[0].push(normals[n1].y);
                                        this._mesh._normals[0].push(normals[n1].z);
                                        this._mesh._normals[0].push(normals[n2].x);
                                        this._mesh._normals[0].push(normals[n2].y);
                                        this._mesh._normals[0].push(normals[n2].z);
                                    }
                                    //else {
                                    this._mesh._multiIndIndices.push(p0, p1, p2);
                                    //}

                                    if (hasColor) {
                                        this._mesh._colors[0].push(colors[c0].r);
                                        this._mesh._colors[0].push(colors[c0].g);
                                        this._mesh._colors[0].push(colors[c0].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c0].a);
                                        }
                                        this._mesh._colors[0].push(colors[c1].r);
                                        this._mesh._colors[0].push(colors[c1].g);
                                        this._mesh._colors[0].push(colors[c1].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c1].a);
                                        }
                                        this._mesh._colors[0].push(colors[c2].r);
                                        this._mesh._colors[0].push(colors[c2].g);
                                        this._mesh._colors[0].push(colors[c2].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c2].a);
                                        }
                                    }

                                    if (hasTexCoord) {
                                        this._mesh._texCoords[0].push(texCoords[t0].x);
                                        this._mesh._texCoords[0].push(texCoords[t0].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t0].z);
                                        }
                                        this._mesh._texCoords[0].push(texCoords[t1].x);
                                        this._mesh._texCoords[0].push(texCoords[t1].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t1].z);
                                        }
                                        this._mesh._texCoords[0].push(texCoords[t2].x);
                                        this._mesh._texCoords[0].push(texCoords[t2].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t2].z);
                                        }
                                    }

                                    //faceCnt++;
                                    break;
                                case 3:
                                    p1 = p2;
                                    t1 = t2;
                                    if (normPerVert) {
                                        n1 = n2;
                                    }
                                    if (colPerVert) {
                                        c1 = c2;
                                    }
                                    p2 = +indexes[i];

                                    if (hasNormalInd && normPerVert) {
                                        n2 = +normalInd[i];
                                    } else if (hasNormalInd && !normPerVert) {
                                        /*n2 = +normalInd[faceCnt];*/
                                    } else if (normPerVert) {
                                        n2 = p2;
                                    } else {
                                        n2 = faceCnt;
                                    }

                                    if (hasTexCoordInd) {
                                        t2 = +texCoordInd[i];
                                    } else {
                                        t2 = p2;
                                    }

                                    if (hasColorInd && colPerVert) {
                                        c2 = +colorInd[i];
                                    } else if (hasColorInd && !colPerVert) {
                                        /*c2 = +colorInd[faceCnt];*/
                                    } else if (colPerVert) {
                                        c2 = p2;
                                    } else {
                                        c2 = faceCnt;
                                    }

                                    //this._mesh._indices[0].push(cnt++, cnt++, cnt++);

                                    this._mesh._positions[0].push(positions[p0].x);
                                    this._mesh._positions[0].push(positions[p0].y);
                                    this._mesh._positions[0].push(positions[p0].z);
                                    this._mesh._positions[0].push(positions[p1].x);
                                    this._mesh._positions[0].push(positions[p1].y);
                                    this._mesh._positions[0].push(positions[p1].z);
                                    this._mesh._positions[0].push(positions[p2].x);
                                    this._mesh._positions[0].push(positions[p2].y);
                                    this._mesh._positions[0].push(positions[p2].z);

                                    if (hasNormal) {
                                        this._mesh._normals[0].push(normals[n0].x);
                                        this._mesh._normals[0].push(normals[n0].y);
                                        this._mesh._normals[0].push(normals[n0].z);
                                        this._mesh._normals[0].push(normals[n1].x);
                                        this._mesh._normals[0].push(normals[n1].y);
                                        this._mesh._normals[0].push(normals[n1].z);
                                        this._mesh._normals[0].push(normals[n2].x);
                                        this._mesh._normals[0].push(normals[n2].y);
                                        this._mesh._normals[0].push(normals[n2].z);
                                    }
                                    //else {
                                    this._mesh._multiIndIndices.push(p0, p1, p2);
                                    //}

                                    if (hasColor) {
                                        this._mesh._colors[0].push(colors[c0].r);
                                        this._mesh._colors[0].push(colors[c0].g);
                                        this._mesh._colors[0].push(colors[c0].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c0].a);
                                        }
                                        this._mesh._colors[0].push(colors[c1].r);
                                        this._mesh._colors[0].push(colors[c1].g);
                                        this._mesh._colors[0].push(colors[c1].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c1].a);
                                        }
                                        this._mesh._colors[0].push(colors[c2].r);
                                        this._mesh._colors[0].push(colors[c2].g);
                                        this._mesh._colors[0].push(colors[c2].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(colors[c2].a);
                                        }
                                    }

                                    if (hasTexCoord) {
                                        this._mesh._texCoords[0].push(texCoords[t0].x);
                                        this._mesh._texCoords[0].push(texCoords[t0].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t0].z);
                                        }
                                        this._mesh._texCoords[0].push(texCoords[t1].x);
                                        this._mesh._texCoords[0].push(texCoords[t1].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t1].z);
                                        }
                                        this._mesh._texCoords[0].push(texCoords[t2].x);
                                        this._mesh._texCoords[0].push(texCoords[t2].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(texCoords[t2].z);
                                        }
                                    }

                                    //faceCnt++;
                                    break;
                                default:
                            }
                        }
                    }
                    else {
                        var linklist = new x3dom.DoublyLinkedList();
                        var data = {};
                        cnt = 0; faceCnt = 0;

                        for (i = 0; i < indexes.length; ++i)
                        {
                            if (indexes[i] == -1) {
                                var multi_index_data = x3dom.EarClipping.getMultiIndexes(linklist);

                                for (j = 0; j < multi_index_data.indices.length; j++)
                                {
                                    this._mesh._indices[0].push(cnt);
                                    cnt++;

                                    this._mesh._positions[0].push(multi_index_data.point[j].x,
                                        multi_index_data.point[j].y,
                                        multi_index_data.point[j].z);
                                    if (hasNormal) {
                                        this._mesh._normals[0].push(multi_index_data.normals[j].x,
                                            multi_index_data.normals[j].y,
                                            multi_index_data.normals[j].z);
                                    }
                                    if (hasColor) {
                                        this._mesh._colors[0].push(multi_index_data.colors[j].r,
                                            multi_index_data.colors[j].g,
                                            multi_index_data.colors[j].b);
                                        if (numColComponents === 4) {
                                            this._mesh._colors[0].push(multi_index_data.colors[j].a);
                                        }
                                    }
                                    if (hasTexCoord) {
                                        this._mesh._texCoords[0].push(multi_index_data.texCoords[j].x,
                                            multi_index_data.texCoords[j].y);
                                        if (numTexComponents === 3) {
                                            this._mesh._texCoords[0].push(multi_index_data.texCoords[j].z);
                                        }
                                    }
                                }

                                linklist = new x3dom.DoublyLinkedList();
                                faceCnt++;
                                continue;
                            }

                            if (hasNormal) {
                                if (hasNormalInd && normPerVert) {
                                    data.normals =  normals[normalInd[i]];
                                } else if (hasNormalInd && !normPerVert) {
                                    data.normals =  normals[normalInd[faceCnt]];
                                } else {
                                    data.normals =  normals[indexes[i]];
                                }
                            }

                            if (hasColor) {
                                if (hasColorInd && colPerVert) {
                                    data.colors =  colors[colorInd[i]];
                                } else if (hasColorInd && !colPerVert) {
                                    data.colors =  colors[colorInd[faceCnt]];
                                } else {
                                    data.colors =  colors[indexes[i]];
                                }
                            }
                            if (hasTexCoord) {
                                if (hasTexCoordInd) {
                                    data.texCoords =  texCoords[texCoordInd[i]];
                                } else {
                                    data.texCoords =  texCoords[indexes[i]];
                                }
                            }

                            linklist.appendNode(new x3dom.DoublyLinkedList.ListNode(
                                positions[indexes[i]], indexes[i], data.normals, data.colors, data.texCoords));
                        }

                        this._mesh.splitMesh();
                    }

                    if (!hasNormal) {
                        this._mesh.calcNormals(this._vf.creaseAngle, this._vf.ccw);
                    }
                    if (!hasTexCoord) {
                        this._mesh.calcTexCoords(texMode);
                    }

                    this.invalidateVolume();

                    this._mesh._numFaces = 0;
                    this._mesh._numCoords = 0;

                    for (i=0; i<this._mesh._positions.length; i++) {
                        var indexLength = this._mesh._indices[i].length;
                        var numCoords = this._mesh._positions[i].length / 3;
                        this._mesh._numCoords += numCoords;
                        if (indexLength > 0)
                            this._mesh._numFaces += indexLength / 3;
                        else
                            this._mesh._numFaces += numCoords / 3;
                    }

                    Array.forEach(this._parentNodes, function (node) {
                        node.setGeoDirty();
                    });
                }
                else {
                    if (fieldName == "coord")
                    {
                        var needNormals = !this._cf.normal.node && this._vf.normalUpdateMode.toLowerCase() != 'none';

                        this._mesh._positions[0] = pnts.toGL();

                        if (needNormals) {
                            // position update usually also requires update of vertex normals
                            this._mesh.calcNormals(this._vf.creaseAngle, this._vf.ccw);
                        }

                        // tells the mesh that its bbox requires update
                        this.invalidateVolume();

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.positions = true;
                            if (needNormals)
                                node._dirty.normals = true;
                            node.invalidateVolume();
                        });
                    }
                    else if (fieldName == "color")
                    {
                        pnts = this._cf.color.node._vf.color;

                        this._mesh._colors[0] = pnts.toGL();

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.colors = true;
                        });
                    }
                    else if (fieldName == "normal")
                    {
                        pnts = this._cf.normal.node._vf.vector;

                        this._mesh._normals[0] = pnts.toGL();

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.normals = true;
                        });
                    }
                    else if (fieldName == "texCoord")
                    {
                        texCoordNode = this._cf.texCoord.node;
                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                            if (texCoordNode._cf.texCoord.nodes.length)
                                texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                        }
                        pnts = texCoordNode._vf.point;

                        this._mesh._texCoords[0] = pnts.toGL();

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.texcoords = true;
                        });
                    }
                    else if (fieldName == "coordIndex")
                    {
                        needNormals = !this._cf.normal.node && this._vf.normalUpdateMode.toLowerCase() != 'none';

                        indexes = this._vf.coordIndex;
                        t = 0;
                        n = indexes.length;

                        this._mesh._indices[0] = [];

                        for (i = 0; i < n; ++i) {
                            if (indexes[i] == -1) {
                                t = 0;
                            }
                            else {
                                switch (t) {
                                    case 0: p0 = +indexes[i]; t = 1; break;
                                    case 1: p1 = +indexes[i]; t = 2; break;
                                    case 2: p2 = +indexes[i]; t = 3; this._mesh._indices[0].push(p0, p1, p2); break;
                                    case 3: p1 = p2; p2 = +indexes[i]; this._mesh._indices[0].push(p0, p1, p2); break;
                                }
                            }
                        }

                        if (needNormals) {
                            // index update usually also requires update of vertex normals
                            this._mesh.calcNormals(this._vf.creaseAngle, this._vf.ccw);
                        }

                        Array.forEach(this._parentNodes, function (node) {
                            node._dirty.indexes = true;
                            if (needNormals)
                                node._dirty.normals = true;
                        });
                    }
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### X3DTexture3DNode ### */
x3dom.registerNodeType(
    "X3DTexture3DNode",
    "Texturing3D",
    defineClass(x3dom.nodeTypes.X3DTextureNode,
        
        /**
         * Constructor for X3DTexture3DNode
         * @constructs x3dom.nodeTypes.X3DTexture3DNode
         * @x3d 3.3
         * @component Texturing3D
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This abstract node type is the base type for all node types that specify 3D sources for texture images.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DTexture3DNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ComposedTexture3D ### */
x3dom.registerNodeType(
    "ComposedTexture3D",
    "Texturing3D",
    defineClass(x3dom.nodeTypes.X3DTexture3DNode,
        
        /**
         * Constructor for ComposedTexture3D
         * @constructs x3dom.nodeTypes.ComposedTexture3D
         * @x3d 3.3
         * @component Texturing3D
         * @status full
         * @extends x3dom.nodeTypes.X3DTexture3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ComposedTexture3D node defines a 3D image-based texture map as a collection of 2D texture sources at various depths and parameters controlling tiling repetition of the texture onto geometry.
         */
        function (ctx) {
            x3dom.nodeTypes.ComposedTexture3D.superClass.call(this, ctx);


            /**
             * The texture values are interpreted with the first image being at depth 0 and each following image representing an increasing depth value in the R direction.
             * A user shall provide 2^n source textures in this array. The individual source textures will ignore their repeat field values.
             * @var {x3dom.fields.MFNode} texture
             * @memberof x3dom.nodeTypes.ComposedTexture3D
             * @initvalue x3dom.nodeTypes.X3DTexture3DNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('texture', x3dom.nodeTypes.X3DTexture3DNode);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### ImageTexture3D ### */
x3dom.registerNodeType(
    "ImageTexture3D",
    "Texturing3D",
    defineClass(x3dom.nodeTypes.X3DTexture3DNode,
        
        /**
         * Constructor for ImageTexture3D
         * @constructs x3dom.nodeTypes.ImageTexture3D
         * @x3d 3.3
         * @component Texturing3D
         * @status full
         * @extends x3dom.nodeTypes.X3DTexture3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ImageTexture3D node defines a texture map by specifying a single image file that contains complete 3D data and general parameters for mapping texels to geometry.
         * The texture is read from the URL specified by the url field. When the url field contains no values ([]), texturing is disabled.
         */
        function (ctx) {
            x3dom.nodeTypes.ImageTexture3D.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### PixelTexture3D ### */
x3dom.registerNodeType(
    "PixelTexture3D",
    "Texturing3D",
    defineClass(x3dom.nodeTypes.X3DTexture3DNode,
        
        /**
         * Constructor for PixelTexture3D
         * @constructs x3dom.nodeTypes.PixelTexture3D
         * @x3d 3.3
         * @component Texturing3D
         * @status experimental
         * @extends x3dom.nodeTypes.X3DTexture3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The PixelTexture3D node defines a 3D image-based texture map as an explicit array of pixel values (image field) and parameters controlling tiling repetition of the texture onto geometry.
         */
        function (ctx) {
            x3dom.nodeTypes.PixelTexture3D.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TextureCoordinate3D ### */
x3dom.registerNodeType(
    "TextureCoordinate3D",
    "Texturing3D",
    defineClass(x3dom.nodeTypes.X3DTextureCoordinateNode,
        
        /**
         * Constructor for TextureCoordinate3D
         * @constructs x3dom.nodeTypes.TextureCoordinate3D
         * @x3d 3.3
         * @component Texturing3D
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureCoordinateNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The TextureCoordinate3D node is a geometry property node that specifies a set of 3D texture coordinates used by vertex-based geometry nodes (e.g., IndexedFaceSet and ElevationGrid) to map 3D textures to vertices.
         */
        function (ctx) {
            x3dom.nodeTypes.TextureCoordinate3D.superClass.call(this, ctx);


            /**
             * Specifies the array of texture coordinates.
             * @var {x3dom.fields.MFVec3f} point
             * @memberof x3dom.nodeTypes.TextureCoordinate3D
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'point', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TextureTransform3D ### */
x3dom.registerNodeType(
    "TextureTransform3D",
    "Texturing3D",
    defineClass(x3dom.nodeTypes.X3DTextureTransformNode,
        
        /**
         * Constructor for TextureTransform3D
         * @constructs x3dom.nodeTypes.TextureTransform3D
         * @x3d 3.3
         * @component Texturing3D
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureTransformNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * The TextureTransform3D node specifies a 3D transformation that is applied to texture coordinates.
         * This node affects the way texture coordinates are applied to the geometric surface. The transformation consists of (in order):
         * a translation; a rotation about the centre point; and a non-uniform scale about the centre point.
         */
        function (ctx) {
            x3dom.nodeTypes.TextureTransform3D.superClass.call(this, ctx);


            /**
             * The center field specifies a translation offset in texture coordinate space about which the rotation and scale fields are applied.
             * @var {x3dom.fields.SFVec3f} center
             * @memberof x3dom.nodeTypes.TextureTransform3D
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'center', 0, 0, 0);

            /**
             * The rotation field specifies a rotation in angle base units of the texture coordinates about the center point after the scale has been applied.
             * A positive rotation value makes the texture coordinates rotate counterclockwise about the centre, thereby rotating the appearance of the texture itself clockwise.
             * @var {x3dom.fields.SFRotation} rotation
             * @memberof x3dom.nodeTypes.TextureTransform3D
             * @initvalue 0,0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'rotation', 0, 0, 1, 0);

            /**
             * The scale field specifies a scaling factor in S and T of the texture coordinates about the center point.
             * @var {x3dom.fields.SFVec3f} scale
             * @memberof x3dom.nodeTypes.TextureTransform3D
             * @initvalue 1,1,1
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'scale', 1, 1, 1);

            /**
             * The translation field specifies a translation of the texture coordinates.
             * @var {x3dom.fields.SFVec3f} translation
             * @memberof x3dom.nodeTypes.TextureTransform3D
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'translation', 0, 0, 0);

            /**
             *
             * @var {x3dom.fields.SFRotation} scaleOrientation
             * @memberof x3dom.nodeTypes.TextureTransform3D
             * @initvalue 0,0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'scaleOrientation', 0, 0, 1, 0);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### TextureTransformMatrix3D ### */
x3dom.registerNodeType(
    "TextureTransformMatrix3D",
    "Texturing3D",
    defineClass(x3dom.nodeTypes.X3DTextureTransformNode,
        
        /**
         * Constructor for TextureTransformMatrix3D
         * @constructs x3dom.nodeTypes.TextureTransformMatrix3D
         * @x3d 3.3
         * @component Texturing3D
         * @status full
         * @extends x3dom.nodeTypes.X3DTextureTransformNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The TextureTransform3D node specifies a 3D transformation that is applied to texture coordinates.
         * This node affects the way texture coordinates are applied to the geometric surface.
         * The transformation consists of a transformation matrix.
         */
        function (ctx) {
            x3dom.nodeTypes.TextureTransformMatrix3D.superClass.call(this, ctx);


            /**
             * The matrix field specifies a generalized, unfiltered 4×4 transformation matrix that can be used to modify the texture. Any set of values is permitted.
             * @var {x3dom.fields.SFMatrix4f} matrix
             * @memberof x3dom.nodeTypes.TextureTransformMatrix3D
             * @initvalue 1,0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFMatrix4f(ctx, 'matrix',
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/**
 * The abstract pointing device sensor node class serves as a base class for all pointing device sensors.
 * Pointing device sensors catch pointing device events from all sibling nodes.
 */

x3dom.registerNodeType(
    "X3DPointingDeviceSensorNode",
    "PointingDeviceSensor",
    defineClass(x3dom.nodeTypes.X3DSensorNode,

        /**
         * Constructor for X3DPointingDeviceSensorNode
         * @constructs x3dom.nodeTypes.X3DPointingDeviceSensorNode
         * @x3d 3.3
         * @component PointingDeviceSensor
         * @status experimental
         * @extends x3dom.nodeTypes.X3DSensorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc An abstract base class for all pointing device sensor nodes.
         */
        function (ctx)
        {
            x3dom.nodeTypes.X3DPointingDeviceSensorNode.superClass.call(this, ctx);

            //---------------------------------------
            // FIELDS
            //---------------------------------------

            //route-able output fields
            //this.addField_SFBool(ctx, 'isOver', false);


            //---------------------------------------
            // PROPERTIES
            //---------------------------------------
        },
        {
            //----------------------------------------------------------------------------------------------------------------------
            // PUBLIC FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------

            /**
             * Function that gets called if the pointing device has been pressed over a sibling node of this sensor
             * @param {DOMEvent} event - the pointer event
             * @private
             */
            pointerPressedOverSibling: function(event)
            {
                if (this._vf.enabled)
                {
                    this._vf.isActive = true;
                    this.postMessage('isActive', true);
                }
            },

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

            /**
             * Function that gets called if the pointing device has been moved,
             * after it has been pressed over a sibling of this node
             * @param {DOMEvent} event - the pointer event
             * @private
             */
            pointerMoved: function(event)
            {

            },

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

            /**
             * Function that gets called if the pointing device has entered a sibling of this node.
             * @param {DOMEvent} event - the pointer event
             */
            pointerMovedOver: function(event)
            {
                if (this._vf.enabled)
                {
                    this.postMessage('isOver', true);
                }
            },

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

            /**
             * Function that gets called if the pointing device has left a sibling of this node.
             * @param {DOMEvent} event - the pointer event
             */
            pointerMovedOut: function(event)
            {
                if (this._vf.enabled)
                {
                    this.postMessage('isOver', false);
                }
            },

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

            /**
             * Function that gets called if the pointing device has been released,
             * after it has been pressed over a sibling of this node
             * @private
             */
            pointerReleased: function()
            {
                if (this._vf.enabled)
                {
                    this._vf.isActive = false;
                    this.postMessage('isActive', false);
                }
            }

            //----------------------------------------------------------------------------------------------------------------------
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/**
 * The abstract drag sensor node class serves as a base class for all drag-style pointing device sensors.
 */

x3dom.registerNodeType(
    "X3DDragSensorNode",
    "PointingDeviceSensor",
    defineClass(x3dom.nodeTypes.X3DPointingDeviceSensorNode,

        /**
         * Constructor for X3DDragSensorNode
         * @constructs x3dom.nodeTypes.X3DDragSensorNode
         * @x3d 3.3
         * @component PointingDeviceSensor
         * @status experimental
         * @extends x3dom.nodeTypes.X3DPointingDeviceSensorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc An abstract base class for all sensors that are processing drag gestures of the pointer.
         */
        function (ctx)
        {
            x3dom.nodeTypes.X3DDragSensorNode.superClass.call(this, ctx);

            //---------------------------------------
            // FIELDS
            //---------------------------------------

            /**
             * Determines whether offset values from previous drag gestures are remembered / accumulated.
             * @var {x3dom.fields.SFBool} autoOffset
             * @memberof x3dom.nodeTypes.X3DDragSensorNode
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'autoOffset', true);

            //route-able output fields
            //this.addField_SFVec3f(ctx, 'trackPoint_changed', 0, 0, 0);


            //---------------------------------------
            // PROPERTIES
            //---------------------------------------

            //TODO: revise if those are still needed
            /**
             * Last mouse position in x direction.
             * @var {Double} _lastX
             * @private
             */
            this._lastX = -1;

            /**
             * Last mouse position in y direction.
             * @var {Double} _lastY
             * @private
             */
            this._lastY = -1;
        },
        {
            //----------------------------------------------------------------------------------------------------------
            // PUBLIC FUNCTIONS
            //----------------------------------------------------------------------------------------------------------

            /**
             * @overrides x3dom.nodeTypes.X3DPointingDeviceSensorNode._pointerPressedOverSibling
             * @param {DOMEvent} event - the pointer event
             * @private
             */
            pointerPressedOverSibling: function(event)
            {
                x3dom.nodeTypes.X3DPointingDeviceSensorNode.prototype.pointerPressedOverSibling.call(this, event);

                this._lastX = event.layerX;
                this._lastY = event.layerY;

                this._startDragging(event.viewarea, event.layerX, event.layerX, event.worldX, event.worldY, event.worldZ);
            },

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

            /**
             * @overrides x3dom.nodeTypes.X3DPointingDeviceSensorNode._pointerMoved
             * @param {DOMEvent] event - the pointer event
             * @private
             */
            pointerMoved: function(event)
            {
                x3dom.nodeTypes.X3DPointingDeviceSensorNode.prototype.pointerMoved.call(this, event);

                if (this._vf.isActive && this._vf.enabled)
                {
                    this._process2DDrag(event.layerX,
                                        event.layerY,
                                        event.layerX-this._lastX,
                                        event.layerY-this._lastY);
                }
            },

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

            /**
             * @overrides x3dom.nodeTypes.X3DPointingDeviceSensorNode._pointerReleased
             * @private
             */
            pointerReleased: function()
            {
                x3dom.nodeTypes.X3DPointingDeviceSensorNode.prototype.pointerReleased.call(this);

                this._stopDragging();
            },

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

            //----------------------------------------------------------------------------------------------------------
            // PRIVATE FUNCTIONS
            //----------------------------------------------------------------------------------------------------------

            /**
             * Function that is called as soon as a drag action is initiated.
             * @param {x3dom.Viewarea} viewarea - the viewarea which initiated the drag operation
             * @param {Double} x - 2D pointer x coordinate at the time of the dragging initiation
             * @param {Double} y - 2D pointer y coordinate at the time of the dragging initiation
             * @param {Double} wx - 3D world x pick coordinate on the sensor geometry at the time of the dragging initiation
             * @param {Double} wy - 3D world x pick coordinate on the sensor geometry at the time of the dragging initiation
             * @param {Double} wz - 3D world z pick coordinate on the sensor geometry at the time of the dragging initiation
             * @private
             */
            _startDragging: function(viewarea, x, y, wx, wy, wz)
            {

            },

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

            /**
             * Processes a 2D drag action, using the given 2D delta values.
             * @param {Double} x - 2D pointer x coordinate at the time of the dragging initiation
             * @param {Double} y - 2D pointer y coordinate at the time of the dragging initiation
             * @param {Double} dx - delta of x, with respect to the last time the function was invoked
             * @param {Double} dy - delta of Y, with respect to the last time the function was invoked
             * @private
             */
            _process2DDrag: function(x, y, dx, dy)
            {

            },

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

            /**
             * Function that is called as soon as a drag action is initiated.
             * @private
             */
            _stopDragging: function()
            {

            }

            //----------------------------------------------------------------------------------------------------------
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

x3dom.registerNodeType(
    "X3DTouchSensorNode",
    "PointingDeviceSensor",
    defineClass(x3dom.nodeTypes.X3DPointingDeviceSensorNode,

        /**
         * Constructor for X3DTouchSensorNode
         * @constructs x3dom.nodeTypes.X3DTouchSensorNode
         * @x3d 3.3
         * @component PointingDeviceSensor
         * @status experimental
         * @extends x3dom.nodeTypes.X3DPointingDeviceSensorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc An abstract base class for all sensors that process touch events.
         */
        function (ctx)
        {
            x3dom.nodeTypes.X3DTouchSensorNode.superClass.call(this, ctx);

            //---------------------------------------
            // FIELDS
            //---------------------------------------

            //route-able output fields
            //this.addField_SFTime(ctx, 'touchTime', 0);


            //---------------------------------------
            // PROPERTIES
            //---------------------------------------
        },
        {
            //----------------------------------------------------------------------------------------------------------------------
            // PUBLIC FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------


        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

x3dom.registerNodeType(
    "TouchSensor",
    "PointingDeviceSensor",
    defineClass(x3dom.nodeTypes.X3DTouchSensorNode,

        /**
         * Constructor for TouchSensor
         * @constructs x3dom.nodeTypes.TouchSensor
         * @x3d 3.3
         * @component PointingDeviceSensor
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDragSensorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc TouchSensor tracks location and state of the pointing device, and detects when user points at
         * geometry. Hint: X3DOM, running in an HTML environment, you actually don't need this node, as you can
         * simply use HTML events (like onclick) on your nodes. However, this node is implemented to complete the
         * pointing device sensor component, and it may be useful to ensure compatibility with older X3D scene content.
         */
        function (ctx)
        {
            x3dom.nodeTypes.TouchSensor.superClass.call(this, ctx);

            //---------------------------------------
            // FIELDS
            //---------------------------------------

            //route-able output fields
            //this.addField_SFVec3f(ctx, 'hitNormal_changed',   0 0 0);
            //this.addField_SFVec3f(ctx, 'hitPoint_changed',    0 0 0);
            //this.addField_SFVec2f(ctx, 'hitTexCoord_changed', 0 0);


            //---------------------------------------
            // PROPERTIES
            //---------------------------------------
        },
        {
            //----------------------------------------------------------------------------------------------------------------------
            // PUBLIC FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------


        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/**
 * The plane sensor node translates drag gestures, performed with a pointing device like a mouse,
 * into 3D transformations.
 */


x3dom.registerNodeType(
    "PlaneSensor",
    "PointingDeviceSensor",
    defineClass(x3dom.nodeTypes.X3DDragSensorNode,

        /**
         * Constructor for PlaneSensor
         * @constructs x3dom.nodeTypes.PlaneSensor
         * @x3d 3.3
         * @component PointingDeviceSensor
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDragSensorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc PlaneSensor converts pointing device motion into 2D translation, parallel to the local Z=0 plane.
         * Hint: You can constrain translation output to one axis by setting the respective minPosition and  maxPosition
         * members to equal values for that axis.
         */
        function (ctx)
        {
            x3dom.nodeTypes.PlaneSensor.superClass.call(this, ctx);

            //---------------------------------------
            // FIELDS
            //---------------------------------------
            /**
             * The local sensor coordinate system is created by additionally applying the axisRotation field value to
             * the local coordinate system of the sensor node.
             * @var {x3dom.fields.SFRotation} axisRotation
             * @memberof x3dom.nodeTypes.PlaneSensor
             * @initvalue  0,0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'axisRotation', 0, 0, 1, 0);


            /**
             * The minPosition and maxPosition fields allow to constrain the 2D output of the plane sensor, along each
             * 2D component. If the value of a component in maxPosition is smaller than the value of a component in
             * minPosition, output is not constrained along the corresponding direction.
             * @var {x3dom.fields.SFVec2f} minPosition
             * @memberof x3dom.nodeTypes.PlaneSensor
             * @initvalue  0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'minPosition',  0,  0);


            /**
             * The minPosition and maxPosition fields allow to constrain the 2D output of the plane sensor, along each
             * 2D component. If the value of a component in maxPosition is smaller than the value of a component in
             * minPosition, output is not constrained along the corresponding direction.
             * @var {x3dom.fields.SFVec2f} maxPosition
             * @memberof x3dom.nodeTypes.PlaneSensor
             * @initvalue  -1,-1
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'maxPosition', -1, -1);


            /**
             * Offset value that is incorporated into the translation output of the sensor.
             * This value is automatically updated if the value of the autoOffset field is 'true'.
             * @var {x3dom.fields.SFVec3f} offset
             * @memberof x3dom.nodeTypes.PlaneSensor
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'offset', 0, 0, 0);

            //route-able output fields
            //this.addField_SFVec3f(ctx, 'translation_changed', 0, 0, 0);


            //---------------------------------------
            // PROPERTIES
            //---------------------------------------

            /**
             *
             * @type {x3dom.fields.Quaternion}
             * @private
             */
            //TODO: update on change
            this._rotationMatrix = this._vf.axisRotation.toMatrix();

            /**
             * World-To-Local matrix for this node, including the axisRotation of the sensor
             */
            this._worldToLocalMatrix = null;


            /**
             * Initial intersection point with the sensor's plane, at the time the sensor was activated
             * @type {x3dom.fields.SFVec3f}
             * @private
             */
            this._initialPlaneIntersection = null;

            /**
             * Plane normal, computed on drag start and used during dragging to compute plane intersections
             * @type {x3dom.fields.SFVec3f}
             * @private
             */
            this._planeNormal = null;

            /**
             * Current viewarea that is used for dragging, needed for ray setup to compute the plane intersection
             *
             * @type {x3dom.Viewarea}
             * @private
             */
            this._viewArea = null;

            /**
             * Current translation that is produced by this drag sensor
             * @type {x3dom.fields.SFVec3f}
             * @private
             */
            this._currentTranslation = new x3dom.fields.SFVec3f(0.0, 0.0, 0.0);
        },
        {
            //----------------------------------------------------------------------------------------------------------------------
            // PUBLIC FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------

            /**
             * This function returns the parent transformation of this node, combined with its current axisRotation
             * @overrides x3dom.nodeTypes.X3DPointingDeviceSensorNode.getCurrentTransform
             */
            getCurrentTransform: function ()
            {
                var parentTransform = x3dom.nodeTypes.X3DDragSensorNode.prototype.getCurrentTransform.call(this);

                return parentTransform.mult(this._rotationMatrix);
            },

            //----------------------------------------------------------------------------------------------------------------------
            // PRIVATE FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------

            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode.prototype._startDragging
             * @private
             */
            _startDragging: function(viewarea, x, y, wx, wy, wz)
            {
                x3dom.nodeTypes.X3DDragSensorNode.prototype._startDragging.call(this, viewarea, x, y, wx, wy, wz);

                this._viewArea = viewarea;

                this._currentTranslation = new x3dom.fields.SFVec3f(0.0, 0.0, 0.0).add(this._vf.offset);

                //TODO: handle multi-path nodes

                //get model matrix for this node, combined with the axis rotation
                this._worldToLocalMatrix = this.getCurrentTransform().inverse();

                //remember initial point of intersection with the plane, transform it to local sensor coordinates
                this._initialPlaneIntersection = this._worldToLocalMatrix.multMatrixPnt(new x3dom.fields.SFVec3f(wx, wy, wz));

                //compute plane normal in local coordinates
                this._planeNormal = new x3dom.fields.SFVec3f(0.0, 0.0, 1.0);
            },

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

            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode._process2DDrag
             * @private
             */
            _process2DDrag: function(x, y, dx, dy)
            {
                x3dom.nodeTypes.X3DDragSensorNode.prototype._process2DDrag.call(this, x, y, dx, dy);

                var intersectionPoint;
                var minPos, maxPos;

                if (this._initialPlaneIntersection)
                {
                    //compute point of intersection with the plane
                    var viewRay = this._viewArea.calcViewRay(x, y);

                    //transform the world coordinates, used for the ray, to local sensor coordinates
                    viewRay.pos = this._worldToLocalMatrix.multMatrixPnt(viewRay.pos);
                    viewRay.dir = this._worldToLocalMatrix.multMatrixVec(viewRay.dir.normalize());

                    intersectionPoint = viewRay.intersectPlane(this._initialPlaneIntersection, this._planeNormal);

                    //allow interaction from both sides of the plane
                    if (!intersectionPoint)
                    {
                        intersectionPoint = viewRay.intersectPlane(this._initialPlaneIntersection, this._planeNormal.negate());
                    }

                    if (intersectionPoint)
                    {
                        //compute difference between new point of intersection and initial point
                        this._currentTranslation = intersectionPoint.subtract(this._initialPlaneIntersection);
                        this._currentTranslation = this._currentTranslation.add(this._vf.offset);

                        //clamp translation components, if desired
                        minPos = this._vf.minPosition;
                        maxPos = this._vf.maxPosition;

                        if (minPos.x <= maxPos.x)
                        {
                            this._currentTranslation.x = Math.min(this._currentTranslation.x, maxPos.x);
                            this._currentTranslation.x = Math.max(this._currentTranslation.x, minPos.x);
                        }
                        if (minPos.y <= maxPos.y)
                        {
                            this._currentTranslation.y = Math.min(this._currentTranslation.y, maxPos.y);
                            this._currentTranslation.y = Math.max(this._currentTranslation.y, minPos.y);
                        }

                        //output translation_changed event
                        this.postMessage('translation_changed', x3dom.fields.SFVec3f.copy(this._currentTranslation));
                    }
                }
            },

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

            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode._stopDragging
             * @private
             */
            _stopDragging: function()
            {
                x3dom.nodeTypes.X3DDragSensorNode.prototype._stopDragging.call(this);

                if (this._vf.autoOffset)
                {
                    this._vf.offset = x3dom.fields.SFVec3f.copy(this._currentTranslation);
                    this.postMessage('offset_changed', this._vf.offset);
                }
            }

            //----------------------------------------------------------------------------------------------------------------------
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */
 
 /*
 * Based on code provided by Fraunhofer IGD.
 * (C)2014 Toshiba Corporation, Japan.
 * Dual licensed under the MIT and GPL.
 */

x3dom.registerNodeType(
    "SphereSensor",
    "PointingDeviceSensor",
    defineClass(x3dom.nodeTypes.X3DDragSensorNode,

        /**
         * Constructor for SphereSensor
         * @constructs x3dom.nodeTypes.SphereSensor
         * @x3d 3.3
         * @component PointingDeviceSensor
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDragSensorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc SphereSensor converts pointing device motion into a spherical rotation around the origin of the
         * local coordinate system.
         */
        function (ctx)
        {
            x3dom.nodeTypes.SphereSensor.superClass.call(this, ctx);

            //---------------------------------------
            // FIELDS
            //---------------------------------------
            /**
             * Offset value that is incorporated into the rotation output of the sensor.
             * This value is automatically updated if the value of the autoOffset field is 'true'.
             * @var {x3dom.fields.SFRotation} offset
             * @memberof x3dom.nodeTypes.SphereSensor
             * @initvalue 0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'offset', 0, 1, 0, 0);

            //route-able output fields
            //this.addField_SFVec3f(ctx, 'rotation_changed', 0, 0, 0);


            //---------------------------------------
            // PROPERTIES
            //---------------------------------------

            /**
             * Current rotation that is produced by this sphere sensor
             * @type {x3dom.fields.Quaternion}
             * @private
             */
            this._currentRotation = null;
			
			/**
             * Rotation matrix, derived from the current value of the offset field
             * @type {x3dom.fields.SFMatrix4f}
             * @private
             */
            this._rotationMatrix = this._vf.offset.toMatrix();
        },
        {
            //----------------------------------------------------------------------------------------------------------------------
            // PUBLIC FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------
			
			/**
             * This function returns the parent transformation of this node, combined with its current rotation
             * @overrides x3dom.nodeTypes.X3DPointingDeviceSensorNode.getCurrentTransform
             */
            getCurrentTransform: function ()
            {
                var parentTransform = x3dom.nodeTypes.X3DDragSensorNode.prototype.getCurrentTransform.call(this);
				
                return parentTransform.mult(this._rotationMatrix);
            },


            //----------------------------------------------------------------------------------------------------------------------
            // PRIVATE FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------
			
            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode.prototype._startDragging
             * @private
             */
            _startDragging: function(viewarea, x, y, wx, wy, wz)
            {
				//console.log(viewarea, x, y, wx, wy, wz);
                x3dom.nodeTypes.X3DDragSensorNode.prototype._startDragging.call(this, viewarea, x, y, wx, wy, wz);
				
				this._currentRotation = new x3dom.fields.Quaternion();
				
				this._viewArea = viewarea;	
				
				// origin of sphere in local coordinates
				this._localOrigin = new x3dom.fields.SFVec3f(0.0, 0.0, 0.0);
				
				this._inverseToWorldMatrix = this.getCurrentTransform().inverse();

				// compute initial point of intersection on the sphere sensor's geometry, in local sphere sensor's coordinate system
				var firstIntersection = this._inverseToWorldMatrix.multMatrixPnt(new x3dom.fields.SFVec3f(wx, wy, wz));
				
				this._initialSphereIntersectionVector = firstIntersection.subtract(this._localOrigin);

				this._sphereRadius = this._initialSphereIntersectionVector.length();

				this._initialSphereIntersectionVector = this._initialSphereIntersectionVector.normalize();
            },

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

            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode._process2DDrag
             * @private
             */
            _process2DDrag: function(x, y, dx, dy)
            {
                x3dom.nodeTypes.X3DDragSensorNode.prototype._process2DDrag.call(this, x, y, dx, dy);

				// We have to compute hit point on virtual sphere's geometry
				var viewRay = this._viewArea.calcViewRay(x, y);
				viewRay.pos = this._inverseToWorldMatrix.multMatrixPnt(viewRay.pos);
				viewRay.dir = this._inverseToWorldMatrix.multMatrixVec(viewRay.dir);
				
				/*
				 * S := Ray Origin	  = viewRay.pos
				 * V := Ray Direction = viewRay.dir
				 * O := Sphere Center = this._localOrigin
				 * r := Sphere Radius = this._sphereRadius
				 * alpha := Ray parameter
				 * 
				 * If the view ray intersects the virtual sphere centred at O
				 * at (S + alpha*V), it must satisfy the following equation:
				 * | S + alpha*V - O | = r
				 * dot_prod((S + alpha*V - O),(S + alpha*V - O)) = r*r
				 * or,
				 * alpha*alpha*V.V + alpha*2*(V.(S-O)) + (S.S -2O.S + O.O) - r*r = 0
				 * or,
				 * A*alpha*alpha + B*alpha + C = 0
				 */
				 
				 var A = viewRay.dir.dot(viewRay.dir);
				 var B = 2.0*(viewRay.dir.dot(viewRay.pos.subtract(this._localOrigin)));
				 var C = viewRay.pos.dot(viewRay.pos) - 2.0*this._localOrigin.dot(viewRay.pos) +
                         this._localOrigin.dot(this._localOrigin) - this._sphereRadius*this._sphereRadius;
				 
				 var determinant = (B*B) - (4.0*A*C);
				 var alpha_1;
				 var alpha_2;
				 
				 // if the roots are real i.e. the ray intersects the sphere, the determinant must be greater
				 // than or equal to zero
				 if(determinant >= 0.0) {
					alpha_1 = (-B + Math.sqrt(determinant)) / (2.0*A);
					alpha_2 = (-B - Math.sqrt(determinant)) / (2.0*A);
					
					// pick the closer of the two points
					alpha_1 = Math.min(alpha_1, alpha_2);
					
					// if the closer intersection point has alpha < 0, then we are inside the sphere and must not do anything
					if(alpha_1 >= 1.0) {
						//TODO: output trackPoint_changed event
						var hitPoint = viewRay.pos.add(viewRay.dir.multiply(alpha_1));
						
						var vecToHitPoint = hitPoint.subtract(this._localOrigin).normalize();
						
						this._currentRotation = x3dom.fields.Quaternion.rotateFromTo(this._initialSphereIntersectionVector, vecToHitPoint);
						
						this._currentRotation = this._currentRotation.multiply(this._vf.offset);
						
						// output rotationChanged_event, given in local sphere sensor coordinates
						this.postMessage('rotation_changed',  this._currentRotation);
					}
				 }
				 else {
					// do nothing, because no intersection
				 }
            },

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

            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode._stopDragging
             * @private
             */
            _stopDragging: function()
            {
                x3dom.nodeTypes.X3DDragSensorNode.prototype._stopDragging.call(this);

                if (this._vf.autoOffset)
                {
                    this._vf.offset = this._currentRotation;
					this.postMessage('offset_changed', this._vf.offset);
                }
				
				this._currentRotation = new x3dom.fields.Quaternion();
            }

            //----------------------------------------------------------------------------------------------------------------------
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

x3dom.registerNodeType(
    "CylinderSensor",
    "PointingDeviceSensor",
    defineClass(x3dom.nodeTypes.X3DDragSensorNode,

        /**
         * Constructor for CylinderSensor
         * @constructs x3dom.nodeTypes.CylinderSensor
         * @x3d 3.3
         * @component PointingDeviceSensor
         * @status experimental
         * @extends x3dom.nodeTypes.X3DDragSensorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The CylinderSensor node converts pointer motion (for example, from a mouse) into rotation values,
         * using an invisible cylinder of infinite height, aligned with local Y-axis.
         */
        function (ctx)
        {
            x3dom.nodeTypes.CylinderSensor.superClass.call(this, ctx);

            //---------------------------------------
            // FIELDS
            //---------------------------------------
            /**
             * Offset value, in radians, that is incorporated into the rotation output of the sensor.
             * This value is automatically updated if the value of the autoOffset field is 'true'.
             * @var {x3dom.fields.SFFloat} offset
             * @memberof x3dom.nodeTypes.CylinderSensor
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'offset', 0);


            /**
             * The local sensor coordinate system is created by additionally applying the axisRotation field value to
             * the local coordinate system of the sensor node.
             * @var {x3dom.fields.SFRotation} axisRotation
             * @memberof x3dom.nodeTypes.CylinderSensor
             * @initvalue  0,1,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'axisRotation', 0, 1, 0, 0);


            /**
             * Specifies whether the virtual cylinder's lateral surface or end-cap disks of virtual-geometry sensor are
             * used for manipulation: If the vertical acute angle between the vector from the viewer to the point of
             * intersection with the sensor geometry and the local Y axis of the cylinder is greater than or equal to
             * the value of this field, the sensor uses a virtual cylinder to compute rotation output.
             * Otherwise, if the angle is smaller than the value of this field, the sensor uses a virtual disk instead.
             * This value of this field is specified in radians.
             *
             * ATTENTION: The value of this field is currently ignored.
             * The cylinder sensor will always operate in cylinder mode.
             *
             * @var {x3dom.fields.SFFloat} axisRotation
             * @memberof x3dom.nodeTypes.CylinderSensor
             * @initvalue  pi/2
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'diskAngle', 0.262); //this is the official default value, PI/12


            /**
             * The minAngle and maxAngle fields, given in radians, allow to constrain the rotation output of the
             * cylinder sensor.
             * If the value of maxAngle is smaller than the value of minAngle, output is not constrained.
             * @var {x3dom.fields.SFFloat} axisRotation
             * @memberof x3dom.nodeTypes.CylinderSensor
             * @initvalue  0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'minAngle', 0);


            /**
             * The minAngle and maxAngle fields, given in radians, allow to constrain the rotation output of the
             * cylinder sensor.
             * If the value of maxAngle is smaller than the value of minAngle, output is not constrained.
             * @var {x3dom.fields.SFFloat} axisRotation
             * @memberof x3dom.nodeTypes.CylinderSensor
             * @initvalue  -1
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'maxAngle', -1);

            //route-able output fields
            //this.addField_SFRotation(ctx, 'rotation_changed', 0, 0, 1, 0);


            //---------------------------------------
            // PROPERTIES
            //---------------------------------------

            /**
             * Rotation matrix, derived from the current value of the axisRotation field
             * @type {x3dom.fields.SFMatrix4f}
             * @private
             */
            //TODO: updates
            this._rotationMatrix = this._vf.axisRotation.toMatrix();

            /**
             * Current value of the matrix that transforms world coordinates to local sensor coordinates
             * @type {x3dom.fields.SFMatrix4f}
             * @private
             */
            this._inverseToWorldMatrix = null;

            /**
             * Vector from the virtual local y-Axis to the initial intersection point with the virtual cylinder,
             * at the time the sensor was activated
             * @type {x3dom.fields.SFVec3f}
             * @private
             */
            this._initialCylinderIntersectionVector = null;

            /**
             * Current viewarea that is used for dragging, needed for ray setup to compute the cylinder intersection
             *
             * @type {x3dom.Viewarea}
             * @private
             */
            this._viewArea = null;

            /**
             * Current radius of the virtual cylinder.
             * @type {number}
             * @private
             */
            this._cylinderRadius = 0.0;

            /**
             * A line that specifies the current local, virtual y-Axis of this sensor, given in world coordinates.
             * @type {x3dom.fields.Line}
             * @private
             */
            this._yAxisLine = null;

            /**
             * Specifies whether we are currently using cylinder behavior or disk behavior.
             * @type {boolean}
             * @private
             */
            this._cylinderMode = true;

            /**
             * Current rotation that is produced by this cylinder sensor
             * @type {x3dom.fields.Quaternion}
             * @private
             */
            this._currentRotationAngle = 0.0;
        },
        {
            //----------------------------------------------------------------------------------------------------------------------
            // PUBLIC FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------

            /**
             * This function returns the parent transformation of this node, combined with its current axisRotation
             * @overrides x3dom.nodeTypes.X3DPointingDeviceSensorNode.getCurrentTransform
             */
            getCurrentTransform: function ()
            {
                var parentTransform = x3dom.nodeTypes.X3DDragSensorNode.prototype.getCurrentTransform.call(this);

                return parentTransform.mult(this._rotationMatrix);
            },

            //----------------------------------------------------------------------------------------------------------------------
            // PRIVATE FUNCTIONS
            //----------------------------------------------------------------------------------------------------------------------

            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode.prototype._startDragging
             * @private
             */
            _startDragging: function(viewarea, x, y, wx, wy, wz)
            {
                x3dom.nodeTypes.X3DDragSensorNode.prototype._startDragging.call(this, viewarea, x, y, wx, wy, wz);

                this._currentRotation = new x3dom.fields.Quaternion();

                this._viewArea = viewarea;

                //y axis line, in local sensor coordinates
                this._yAxisLine = new x3dom.fields.Line(new x3dom.fields.SFVec3f(0.0, 0.0, 0.0),
                                                        new x3dom.fields.SFVec3f(0.0, 1.0, 0.0));

                this._inverseToWorldMatrix = this.getCurrentTransform().inverse();

                //compute initial cylinder intersection point, in local sensor coordinates
                var firstIntersection = this._inverseToWorldMatrix.multMatrixPnt(new x3dom.fields.SFVec3f(wx, wy, wz));

                //TODO: add disk mode

                //compute distance between point of intersection and y-axis

                var closestPointOnYAxis = this._yAxisLine.closestPoint(firstIntersection);

                this._initialCylinderIntersectionVector = firstIntersection.subtract(closestPointOnYAxis);

                this._cylinderRadius = this._initialCylinderIntersectionVector.length();

                this._initialCylinderIntersectionVector = this._initialCylinderIntersectionVector.normalize();
            },

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

            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode._process2DDrag
             * @private
             */
            _process2DDrag: function(x, y, dx, dy)
            {
                x3dom.nodeTypes.X3DDragSensorNode.prototype._process2DDrag.call(this, x, y, dx, dy);

                //cylinder mode
                if (this._cylinderMode)
                {
                    //compute hit point on virtual cylinder geometry
                    var viewRay = this._viewArea.calcViewRay(x, y);

                    viewRay.pos = this._inverseToWorldMatrix.multMatrixPnt(viewRay.pos);
                    viewRay.dir = this._inverseToWorldMatrix.multMatrixVec(viewRay.dir);

                    //0. assume the following equation:
                    // At the point of intersection, the distance between the ray of sight and the cylinder equals
                    // the cylinder radius r.
                    // This means a ray parameter alpha must be found, so that the minimum distance between the point on
                    // the ray and the cylinder axis equals r:
                    // | ((S + alpha*V) - O) - Y*<(S + alpha*V) - O, Y> | = r
                    // with:
                    // | X | = length of vector X
                    // <X1, X2> = dot product of vectors X1, X2
                    // and variables
                    // alpha := Ray Parameter (should be found)
                    // S := Ray Origin
                    // V := Ray Direction
                    // O := Local Y-Axis Anchor Point
                    // Y := Local Y-Axis Direction

                    //1. bring equation into the following form:
                    //   | alpha * A - B | = r
                    var A = viewRay.dir.subtract(this._yAxisLine.dir.multiply(viewRay.dir.dot(this._yAxisLine.dir)));
                    var B = viewRay.pos.subtract(this._yAxisLine.pos).add(this._yAxisLine.dir.multiply(
                            this._yAxisLine.dir.dot(this._yAxisLine.pos.subtract(viewRay.pos))));

                    //2. solve quadratic formula (0, 1 or 2 solutions are possible)
                    var p = 2 * A.dot(B) / A.dot(A);
                    var q = (B.dot(B) - this._cylinderRadius*this._cylinderRadius) / A.dot(A);

                    var sqrt_part = p*p*0.25 - q;

                    var alpha_1;
                    var alpha_2;

                    //is the cylinder hit?
                    if (sqrt_part >= 0)
                    {
                        sqrt_part = Math.sqrt(sqrt_part);
                        alpha_1 = -p*0.5 + sqrt_part;
                        alpha_2 = -p*0.5 - sqrt_part;

                        //if we are inside the cylinder, do nothing, otherwise pick the closest point of intersection
                        alpha_1 = Math.min(alpha_1, alpha_2);

                        if (alpha_1 > 0.0)
                        {
                            //TODO: output trackPoint_changed event
                            var hitPoint = viewRay.pos.add(viewRay.dir.multiply(alpha_1));

                            var closestPointOnYAxis = this._yAxisLine.closestPoint(hitPoint);

                            var vecToHitPoint = hitPoint.subtract(closestPointOnYAxis).normalize();

                            this._currentRotation = x3dom.fields.Quaternion.rotateFromTo(this._initialCylinderIntersectionVector, vecToHitPoint);

                            var offsetQuat = x3dom.fields.Quaternion.axisAngle(this._yAxisLine.dir, this._vf.offset);

                            this._currentRotation = this._currentRotation.multiply(offsetQuat);

                            //output rotationChanged_event, given in local sensor coordinates
                            this.postMessage('rotation_changed', this._currentRotation);
                        }
                    }
                }
                //disk mode
                else
                {
                    //TODO: implement
                }
            },

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

            /**
             * @overrides x3dom.nodeTypes.X3DDragSensorNode._stopDragging
             * @private
             */
            _stopDragging: function()
            {
                x3dom.nodeTypes.X3DDragSensorNode.prototype._stopDragging.call(this);

                if (this._vf.autoOffset)
                {
                    this._vf.offset = this._currentRotation.angle();
                    this.postMessage('offset_changed', this._vf.offset);
                }
            }

            //----------------------------------------------------------------------------------------------------------------------
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### GeoCoordinate ### */
x3dom.registerNodeType(
    "GeoCoordinate",
    "Geospatial",
    defineClass(x3dom.nodeTypes.X3DCoordinateNode,
        
        /**
         * Constructor for GeoCoordinate
         * @constructs x3dom.nodeTypes.GeoCoordinate
         * @x3d 3.3
         * @component Geospatial
         * @status full
         * @extends x3dom.nodeTypes.X3DCoordinateNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoCoordinate node specifies a list of coordinates in a spatial reference frame.
         * It is used in the coord field of vertex-based geometry nodes including IndexedFaceSet, IndexedLineSet, and PointSet.
         */
        function (ctx) {
            x3dom.nodeTypes.GeoCoordinate.superClass.call(this, ctx);


            /**
             * The point array is used to contain the actual geospatial coordinates and should be provided in a format consistent with that specified for the particular geoSystem.
             * @var {x3dom.fields.MFVec3f} point
             * @memberof x3dom.nodeTypes.GeoCoordinate
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFVec3f(ctx, 'point', []);

            /**
             * The geoSystem field is used to define the spatial reference frame.
             * @var {x3dom.fields.MFString} geoSystem
             * @range {["GD", ...], ["UTM", ...], ["GC", ...]}
             * @memberof x3dom.nodeTypes.GeoCoordinate
             * @initvalue ['GD','WE']
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'geoSystem', ['GD', 'WE']);

            /**
             * The geoOrigin field is used to specify a local coordinate frame for extended precision.
             * @var {x3dom.fields.SFNode} geoOrigin
             * @memberof x3dom.nodeTypes.GeoCoordinate
             * @initvalue x3dom.nodeTypes.GeoOrigin
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('geoOrigin', x3dom.nodeTypes.GeoOrigin);
        
        },
        {
            elipsoideParameters:
            {
                'AA' : [ 'Airy 1830', '6377563.396', '299.3249646' ],
                'AM' : [ 'Modified Airy', '6377340.189', '299.3249646' ],
                'AN' : [ 'Australian National', '6378160', '298.25' ],
                'BN' : [ 'Bessel 1841 (Namibia)', '6377483.865', '299.1528128' ],
                'BR' : [ 'Bessel 1841 (Ethiopia Indonesia...)', '6377397.155', '299.1528128' ],
                'CC' : [ 'Clarke 1866', '6378206.4', '294.9786982' ],
                'CD' : [ 'Clarke 1880', '6378249.145', '293.465' ],
                'EA' : [ 'Everest (India 1830)', '6377276.345', '300.8017' ],
                'EB' : [ 'Everest (Sabah & Sarawak)', '6377298.556', '300.8017' ],
                'EC' : [ 'Everest (India 1956)', '6377301.243', '300.8017' ],
                'ED' : [ 'Everest (W. Malaysia 1969)', '6377295.664', '300.8017' ],
                'EE' : [ 'Everest (W. Malaysia & Singapore 1948)', '6377304.063', '300.8017' ],
                'EF' : [ 'Everest (Pakistan)', '6377309.613', '300.8017' ],
                'FA' : [ 'Modified Fischer 1960', '6378155', '298.3' ],
                'HE' : [ 'Helmert 1906', '6378200', '298.3' ],
                'HO' : [ 'Hough 1960', '6378270', '297' ],
                'ID' : [ 'Indonesian 1974', '6378160', '298.247' ],
                'IN' : [ 'International 1924', '6378388', '297' ],
                'KA' : [ 'Krassovsky 1940', '6378245', '298.3' ],
                'RF' : [ 'Geodetic Reference System 1980 (GRS 80)', '6378137', '298.257222101' ],
                'SA' : [ 'South American 1969', '6378160', '298.25' ],
                'WD' : [ 'WGS 72', '6378135', '298.26' ],
                'WE' : [ 'WGS 84', '6378137', '298.257223563' ]
            },

            fieldChanged: function(fieldName) {
                if (fieldName == "point" || fieldName == "geoSystem") {
                    Array.forEach(this._parentNodes, function (node) {
                        node.fieldChanged("coord");
                    });
                }
            },

            isLogitudeFirst: function(geoSystem) {
                for(var i=0; i<geoSystem.length; ++i)
                    if(geoSystem[i] == 'longitude_first')
                        return true;

                return false;
            },

            getElipsoideCode: function(geoSystem)
            {
                for(var i=0; i<geoSystem.length; ++i)
                {
                    var code = geoSystem[i];
                    if(this.elipsoideParameters[code])
                        return code;
                }
                //default elipsoide code
                return 'WE';
            },

            getElipsoide: function(geoSystem)
            {
                return this.elipsoideParameters[this.getElipsoideCode(geoSystem)];
            },

            getReferenceFrame: function(geoSystem)
            {
                for(var i=0; i<geoSystem.length; ++i)
                {
                    var code = geoSystem[i];

                    if(code == 'GD' || code == 'GDC')
                        return 'GD';
                    if(code == 'GC' || code == 'GCC')
                        return 'GC';
                    if(code == 'UTM')
                        return 'UTM';

                    else
                        x3dom.debug.logError('Unknown GEO system: [' + geoSystem + ']');
                }

                // default reference frame is GD WE
                return 'GD';
            },

            getUTMZone: function(geoSystem)
            {
                for(var i=0; i<geoSystem.length; ++i)
                {
                    var code = geoSystem[i];

                    if(code[0] == 'Z')
                        return code.substring(1);
                }
                // no zone found
                x3dom.debug.logError('no UTM zone but is required:' + geoSystem);
            },

            getUTMHemisphere: function(geoSystem)
            {
                for(var i=0; i<geoSystem.length; ++i)
                {
                    var code = geoSystem[i];

                    if(code == 'S')
                        return code;
                }
                // North by default according to spec
                return 'N';
            },

            isUTMEastingFirst: function(geoSystem)
            {
                for(var i=0; i<geoSystem.length; ++i)
                {
                    var code = geoSystem[i];
                    if(code == 'easting_first')
                        return true;
                }
                // Northing first by default according to spec
                return false;
            },

            UTMtoGC: function(geoSystem, coords)
            {
                //parse UTM projection parameters
                var utmzone = this.getUTMZone(geoSystem);
                if(utmzone < 1 || utmzone > 60 || utmzone === undefined)
                    return x3dom.debug.logError('invalid UTM zone: ' + utmzone + ' in geosystem ' + geoSystem);
                var hemisphere = this.getUTMHemisphere(geoSystem);
                var eastingFirst = this.isUTMEastingFirst(geoSystem);
                var elipsoide = this.getElipsoide(geoSystem);
                //below from U.W. Green Bay Prof. Dutch; returns coordinates in the input ell., not WGS84
                var a = elipsoide[1];
                var f = 1/elipsoide[2];
                var k0 = 0.9996; //scale on central meridian
                var b = a * (1 - f); //polar axis.
                var esq = (1 - (b/a)*(b/a)); //e squared for use in expansions
                var e = Math.sqrt(esq); //eccentricity
                var e0 = e/Math.sqrt(1 - esq); //Called e prime in reference
                var e0sq = esq/(1 - esq); // e0 squared - always even powers
                var zcm = 3 + 6 * (utmzone - 1) - 180; //Central meridian of zone
                var e1 = (1 - Math.sqrt(1 - esq))/(1 + Math.sqrt(1 - esq)); //Called e1 in USGS PP 1395 also
                var e1sq = e1*e1;
                //var M0 = 0; //In case origin other than zero lat - not needed for standard UTM
                var output = new x3dom.fields.MFVec3f();
                var rad2deg = 180/Math.PI;

                var f3o64 = 3/64;
                var f5o256 = 5/256;
                var f27o32 = 27/32;
                var f21o16 = 21/16;
                var f55o32 = 55/32;
                var f151o96 = 151/96;
                var f1097o512 = 1097/512;
                var fmua = 1 - esq*(0.25 + esq*(f3o64 + f5o256*esq));
                var fphi11 = e1*(1.5 - f27o32*e1sq);
                var fphi12 = e1sq*(f21o16 - f55o32*e1sq);
                
                var current, x, y, z, M, mu, phi1, cosphi1, C1, tanphi1, T1, T1sq;
                var esinphi1, oneesinphi1, N1, R1, D, Dsq, C1sq, phi, lng;
                
                for(var i=0; i<coords.length; ++i)
                {
                    x = (eastingFirst ? coords[i].x : coords[i].y);
                    y = (eastingFirst ? coords[i].y : coords[i].x);
                    z = coords[i].z;

                    //var M = M0 + y/k0; //Arc length along standard meridian.
                    //var M = y/k0;
                    //if (hemisphere == "S"){ M = M0 + (y - 10000000)/k; }
                    M = (hemisphere == "S" ? (y - 10000000) : y )/k0 ;
                    mu = M/(a * fmua);
                    phi1 = mu + fphi11*Math.sin(2*mu) + fphi12*Math.sin(4*mu); //Footprint Latitude
                    phi1 = phi1 + e1*(e1sq*(Math.sin(6*mu)*f151o96 + Math.sin(8*mu)*f1097o512));
                    cosphi1 = Math.cos(phi1);
                    C1 = e0sq*cosphi1*cosphi1;
                    tanphi1 = Math.tan(phi1);
                    T1 = tanphi1*tanphi1;
                    T1sq = T1*T1;
                    esinphi1 = e*Math.sin(phi1);
                    oneesinphi1 = 1 - esinphi1*esinphi1;
                    N1 = a/Math.sqrt(oneesinphi1);
                    R1 = N1*(1-esq)/oneesinphi1;
                    D = (x-500000)/(N1*k0);
                    Dsq = D*D;
                    C1sq = C1*C1;
                    phi = Dsq*(0.5 - Dsq*(5 + 3*T1 + 10*C1 - 4*C1sq - 9*e0sq)/24);
                    phi = phi + Math.pow(D,6)*(61 + 90*T1 + 298*C1 + 45*T1sq -252*e0sq - 3*C1sq)/720;
                    phi = phi1 - (N1*tanphi1/R1)*phi;
                    lng = D*(1 + Dsq*((-1 -2*T1 -C1)/6 + Dsq*(5 - 2*C1 + 28*T1 - 3*C1sq +8*e0sq + 24*T1sq)/120))/cosphi1;
                    current = new x3dom.fields.SFVec3f();
                    current.x = zcm + rad2deg*lng;
                    current.y = rad2deg*phi;
                    current.z = coords[i].z;
                    output.push(current);
                }
                //x3dom.debug.logInfo('transformed coords ' + output);

                //GD to GC and return
                var GDgeoSystem = new x3dom.fields.MFString();
                // there may be a better way to construct this geoSystem
                GDgeoSystem.push("GD");
                GDgeoSystem.push(this.getElipsoideCode(geoSystem));
                GDgeoSystem.push("longitude_first");
                return this.GDtoGC(GDgeoSystem, output);
            },

            //just used for GeoPositionInterpolator; after slerp
            //for coordinates in the same UTM zone stripe, linear interpolation is almost identical
            //so this is for correctness and if UTM zone is used outside stripe
            GCtoUTM: function(geoSystem, coords) {
                // geoSystem is target UTM
                // GCtoGD
                var coordsGD = this.GCtoGD(geoSystem, coords);
                // GDtoUTM
                // parse UTM projection parameters
                var utmzone = this.getUTMZone(geoSystem);
                if(utmzone < 1 || utmzone > 60 || utmzone === undefined)
                    return x3dom.debug.logError('invalid UTM zone: ' + utmzone + ' in geosystem ' + geoSystem);
                var hemisphere = this.getUTMHemisphere(geoSystem);
                var eastingFirst = this.isUTMEastingFirst(geoSystem);
                var elipsoide = this.getElipsoide(geoSystem);
                //below from U.W. Green Bay Prof. Dutch; returns coordinates in the input ell., not WGS84
                var a = elipsoide[1];
                var f = 1/elipsoide[2];
                var k0 = 0.9996; //scale on central meridian
                var b = a * (1 - f); //polar axis.
                var esq = (1 - (b/a)*(b/a)); //e squared for use in expansions
                var e = Math.sqrt(esq); //eccentricity
                //var e0 = e/Math.sqrt(1 - esq); //Called e prime in reference
                var e0sq = esq/(1 - esq); // e0 squared - always even powers
                //var e1 = (1 - Math.sqrt(1 - esq))/(1 + Math.sqrt(1 - esq)); //Called e1 in USGS PP 1395 also
                //var e1sq = e1*e1;
                var M0 = 0; //In case origin other than zero lat - not needed for standard UTM
                var deg2rad = Math.PI/180;
                var zcmrad = (3 + 6 * (utmzone - 1) - 180)*deg2rad; //Central meridian of zone
                var coordsUTM = new x3dom.fields.MFVec3f();
                var N, T, C, A, M, x, y, phi, lng, cosphi, tanphi, Asq;
                var i, current;
                var fMphi = 1 - esq*(1/4 + esq*(3/64 + 5*esq/256));
                var fM2phi = esq*(3/8 + esq*(3/32 + 45*esq/1024));
                var fM4phi = esq*esq*(15/256 + esq*45/1024);
                var fM6phi = esq*esq*esq*(35/3072);
                for (i=0; i<coordsGD.length; ++i) {
                    current = new x3dom.fields.SFVec3f();
                    phi = coordsGD[i].y*deg2rad;
                    lng = coordsGD[i].x*deg2rad;
                    cosphi = Math.cos(phi);
                    tanphi = Math.tan(phi);
                    
                    N = a/Math.sqrt(1 - Math.pow(e * Math.sin(phi), 2));
                    T = Math.pow(tanphi, 2);
                    C = e0sq*Math.pow(cosphi, 2);
                    A = (lng - zcmrad) * cosphi;
                    //Calculate M
                    M = phi*fMphi;
                    M = M - Math.sin(2*phi)*fM2phi;
                    M = M + Math.sin(4*phi)*fM4phi;
                    M = M - Math.sin(6*phi)*fM6phi;
                    M = M * a;//Arc length along standard meridian
                    //Calculate UTM Values
                    Asq = A*A;
                    x = k0*N*A*(1 + Asq*((1 - T + C)/6 + Asq*(5 - T*(18 + T) + 72*C - 58*e0sq)/120));//Easting relative to CM
                    x = x + 500000;//Easting standard 
                    y = k0*(M - M0 + N*tanphi*(Asq*(0.5 + Asq*((5 - T + 9*C + 4*C*C)/24 + Asq*(61 - T*(58 + T) + 600*C - 330*e0sq)/720))));//Northing from equator
                    if (y < 0) {
                        if (hemisphere == "N") {
                            x3dom.debug.logError('UTM zone in northern hemisphere but coordinates in southern!');
                        }
                        y = 10000000+y;}
                    current.x = eastingFirst ? x : y;
                    current.y = eastingFirst ? y : x;
                    current.z = coordsGD[i].z;
                    coordsUTM.push(current);
                }
                return coordsUTM;    
            },
            
            GDtoGC: function(geoSystem, coords) {

                var output = new x3dom.fields.MFVec3f();

                var elipsoide = this.getElipsoide(geoSystem);
                var A = elipsoide[1];
                var eccentricity = elipsoide[2];

                var longitudeFirst = this.isLogitudeFirst(geoSystem);

                // large parts of this code from freeWRL
                // var A = radius;
                var A2 = A*A;
                var F = 1.0/eccentricity;
                var C = A*(1.0-F);
                var C2 = C*C;
                var C2oA2 = C2/A2;
                var Eps2 = F*(2.0-F);
                //var Eps25 = 0.25*Eps2;

                var radiansPerDegree = 0.0174532925199432957692;
                
                var i, current, source_lat, source_lon, slat, slat2, clat, Rn, RnPh;

                // for (current in coords)
                for(i=0; i<coords.length; ++i)
                {
                    current = new x3dom.fields.SFVec3f();

                    source_lat = radiansPerDegree * (longitudeFirst == true ? coords[i].y : coords[i].x);
                    source_lon = radiansPerDegree * (longitudeFirst == true ? coords[i].x : coords[i].y);

                    slat = Math.sin(source_lat);
                    slat2 = slat*slat;
                    clat = Math.cos(source_lat);

                    /* square root approximation for Rn */
                    /* replaced by real sqrt
                     var Rn = A / ( (0.25 - Eps25 * slat2 + 0.9999944354799/4.0) +
                     (0.25-Eps25 * slat2)/(0.25 - Eps25 * slat2 + 0.9999944354799/4.0));
                     */

                    // with real sqrt; really slower ?
                    Rn = A / Math.sqrt(1.0 - Eps2 * slat2);

                    RnPh = Rn + coords[i].z;

                    current.x = RnPh * clat * Math.cos(source_lon);
                    current.y = RnPh * clat * Math.sin(source_lon);
                    current.z = (C2oA2 * Rn + coords[i].z) * slat;

                    output.push(current);
                }

                return output;
            },
            
            GCtoGD: function(geoSystem, coords) {
                // below uses http://info.ogp.org.uk/geodesy/guides/docs/G7-2.pdf
                var output = new x3dom.fields.MFVec3f();
                var rad2deg = 180/Math.PI;
                var ellipsoide = this.getElipsoide(geoSystem);
                var a = ellipsoide[1];
                var f = 1/ellipsoide[2];
                var b = a * (1 - f); //polar axis.
                var esq = (1 - (b/a)*(b/a)); //e squared for use in expansions
                //var e = Math.sqrt(esq); //eccentricity
                var eps = esq/(1 - esq);
                var i, current, x, y, z, p, q, lat, nu, elev, lon;
                for(i=0; i<coords.length; ++i) {
                    x = coords[i].x;
                    y = coords[i].y;
                    z = coords[i].z;
                    
                    p = Math.sqrt(x*x + y*y);
                    q = Math.atan((z * a) / (p * b));
                    lat = Math.atan((z + eps * b * Math.pow(Math.sin(q),3))/(p - esq * a * Math.pow(Math.cos(q),3)));
                    nu = a / Math.sqrt(1-esq * Math.pow(Math.sin(lat),2));
                    elev = p/Math.cos(lat) - nu;
                    // atan2 gets the sign correct for longitude; is exact since in circular section
                    lon = Math.atan2(y, x);
                    
                    current = new x3dom.fields.SFVec3f();
                    current.x = lon * rad2deg;
                    current.y = lat * rad2deg;
                    current.z = elev;

                    output.push(current);
                }                
                return output;
            },
        
            GEOtoGC: function(geoSystem, geoOrigin, coords) {
                
                var referenceFrame = this.getReferenceFrame(geoSystem);

                if(referenceFrame == 'GD')
                    return this.GDtoGC(geoSystem, coords);

                else if(referenceFrame == 'UTM')
                    return this.UTMtoGC(geoSystem, coords);

                else if(referenceFrame ==  'GC')
                {
                    // Performance Hack
                    // Normaly GDtoGC & UTMtoGC will create a copy
                    // If we are already in GC & have an origin: we have to copy here
                    // Else Origin will change original DOM elements

                    if(geoOrigin.node)
                    {
                        var copy = new x3dom.fields.MFVec3f();
                        for(var i=0; i<coords.length; ++i)
                        {
                            var current = new x3dom.fields.SFVec3f();

                            current.x = coords[i].x;
                            current.y = coords[i].y;
                            current.z = coords[i].z;

                            copy.push(current);
                        }
                        return copy;
                    }
                    else
                        return coords;
                }
                else {
                    x3dom.debug.logError('Unknown geoSystem: ' + geoSystem[0]);
                    return new x3dom.fields.MFVec3f();
                }
            },

            GCtoGEO: function(geoSystem, geoOrigin, coords) {
                
                var referenceFrame = this.getReferenceFrame(geoSystem);

                if(referenceFrame == 'GD') {
                    var coordsGD = this.GCtoGD(geoSystem, coords);
                    if(!this.isLogitudeFirst(geoSystem)) {
                        var currentx;
                        for(var i=0; i<coordsGD.length; ++i) {
                            currentx = coordsGD[i].x;
                            coordsGD[i].x = coordsGD[i].y;
                            coordsGD[i].y = currentx;
                        }
                    }
                    return coordsGD;
                }

                else if(referenceFrame == 'UTM')
                    return this.GCtoUTM(geoSystem, coords);

                else if(referenceFrame ==  'GC')
                {
                    // Performance Hack
                    // Normaly GCtoGD & GCtoUTM will create a copy
                    // If we are already in GC & have an origin: we have to copy here
                    // Else Origin will change original DOM elements

                    if(geoOrigin.node)
                    {
                        var copy = new x3dom.fields.MFVec3f();
                        for(var i=0; i<coords.length; ++i)
                        {
                            var current = new x3dom.fields.SFVec3f();

                            current.x = coords[i].x;
                            current.y = coords[i].y;
                            current.z = coords[i].z;

                            copy.push(current);
                        }
                        return copy;
                    }
                    else
                        return coords;
                }
                else {
                    x3dom.debug.logError('Unknown geoSystem: ' + geoSystem[0]);
                    return new x3dom.fields.MFVec3f();
                }
            },
            
            OriginToGC: function(geoOrigin)
            {
                // dummy function to send a scalar to an array function
                var geoCoords = geoOrigin.node._vf.geoCoords;
                var geoSystem = geoOrigin.node._vf.geoSystem;

                var point = new x3dom.fields.SFVec3f();
                point.x = geoCoords.x;
                point.y = geoCoords.y;
                point.z = geoCoords.z;

                var temp = new x3dom.fields.MFVec3f();
                temp.push(point);

                // transform origin to GeoCentric
                var origin = this.GEOtoGC(geoSystem, geoOrigin, temp);

                return origin[0];
            },
            
            GCtoX3D: function(geoSystem, geoOrigin, coords)
            {
                // needs geoSystem since GC can have different ellipsoids
                var gc = coords;
                // transform by origin
                if(geoOrigin.node)
                {
                    // transform points by origin
                    var origin = this.OriginToGC(geoOrigin);
                    //avoid expensive matrix inversion by just subtracting origin
                    var matrix = x3dom.fields.SFMatrix4f.translation(origin.negate());
                    
                    //also rotate Y up if requested
                    if(geoOrigin.node._vf.rotateYUp)
                    {
                        //rotation is inverse of GeoLocation rotation, eg. Up to Y and N to -Z
                        var rotmat = x3dom.nodeTypes.GeoLocation.prototype.getGeoRotMat(geoSystem, origin).inverse();
                        //first translate, then rotate
                        matrix = rotmat.mult(matrix);
                    }
                    
                    for(var i=0; i<coords.length; ++i)
                        gc[i] = matrix.multMatrixPnt(coords[i]);
                }
                
                return gc;
            },

            GEOtoX3D: function(geoSystem, geoOrigin, coords)
            {
                // transform points to GeoCentric
                var gc = this.GEOtoGC(geoSystem, geoOrigin, coords);
                return this.GCtoX3D(geoSystem, geoOrigin, gc);               
            },

            getPoints: function()
            {
                return this.GEOtoX3D(this._vf.geoSystem, this._cf.geoOrigin, this._vf.point);
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### GeoElevationGrid ### */
x3dom.registerNodeType(
    "GeoElevationGrid",
    "Geospatial",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for GeoElevationGrid
         * @constructs x3dom.nodeTypes.GeoElevationGrid
         * @x3d 3.3
         * @component Geospatial
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoElevationGrid node specifies a uniform grid of elevation values within some spatial reference frame.
         * These are then transparently transformed into a geocentric, curved-earth representation.
         */
        function (ctx) {
            x3dom.nodeTypes.GeoElevationGrid.superClass.call(this, ctx);

            /**
            * The texCoord field specifies per-vertex texture coordinates for the GeoElevationGrid node. If texCoord is NULL, default texture coordinates are applied to the geometry.
            * @var {x3dom.fields.SFNode} texCoord
            * @memberof x3dom.nodeTypes.GeoElevationGrid
            * @initvalue x3dom.nodeTypes.X3DTextureCoordinateNode
            * @field x3d
            * @instance
            */
            this.addField_SFNode('texCoord', x3dom.nodeTypes.X3DTextureCoordinateNode);

            /**
             * The geoSystem field is used to define the spatial reference frame.
             * @var {x3dom.fields.MFString} geoSystem
             * @range {["GD", ...], ["UTM", ...], ["GC", ...]}
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue ['GD','WE']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'geoSystem', ['GD', 'WE']);

            /**
             * The geoGridOrigin field specifies the geographic coordinate for the south-west corner (bottom-left) of the dataset.
             * @var {x3dom.fields.SFVec3d} geoGridOrigin
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'geoGridOrigin', 0, 0, 0);

            /**
             * The height array contains xDimension × zDimension floating point values that represent elevation above the ellipsoid or the geoid, as appropriate.
             * These values are given in row-major order from west to east, south to north.
             * @var {x3dom.fields.MFDouble} height
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue 0,0
             * @field x3d
             * @instance
             */
            this.addField_MFDouble(ctx, 'height', 0, 0);

            /**
             * The ccw field defines the ordering of the vertex coordinates of the geometry with respect to user-given or automatically generated normal vectors used in the lighting model equations.
             * @var {x3dom.fields.SFBool} ccw
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'ccw', true);
            //this.addField_SFBool(ctx, 'colorPerVertex', true);

            /**
             * The creaseAngle field affects how default normals are generated.
             * If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced.
             * Crease angles shall be greater than or equal to 0.0 angle base units.
             * @var {x3dom.fields.SFDouble} creaseAngle
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFDouble(ctx, 'creaseAngle', 0);
            //this.addField_SFBool(ctx, 'normalPerVertex', true);
            //this.addField_SFBool(ctx, 'solid', true);

            /**
             * Defines the grid size in x.
             * @var {x3dom.fields.SFInt32} xDimension
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'xDimension', 0);

            /**
             * When the geoSystem is "GD", xSpacing refers to the number of units of longitude in angle base units between adjacent height values.
             * When the geoSystem is "UTM", xSpacing refers to the number of eastings (length base units) between adjacent height values
             * @var {x3dom.fields.SFDouble} xSpacing
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFDouble(ctx, 'xSpacing', 1.0);

            /**
             * The yScale value can be used to produce a vertical exaggeration of the data when it is displayed. If this value is set greater than 1.0, all heights will appear larger than actual.
             * @var {x3dom.fields.SFFloat} yScale
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue 1
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'yScale', 1);

            /**
             * Defines the grid size in z.
             * @var {x3dom.fields.SFInt32} zDimension
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'zDimension', 0);

            /**
             * When the geoSystem is "GD", zSpacing refers to the number of units of latitude in angle base units between vertical height values.
             * When the geoSystem is "UTM", zSpacing refers to the number of northings (length base units) between vertical height values.
             * @var {x3dom.fields.SFDouble} zSpacing
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFDouble(ctx, 'zSpacing', 1.0);
            // this.addField_SFNode('color', x3dom.nodeTypes.PropertySetGeometry);
            // this.addField_SFNode('normal', x3dom.nodeTypes.PropertySetGeometry);
            // this.addField_SFNode('texCoord', x3dom.nodeTypes.PropertySetGeometry);

            /**
             * The geoOrigin field is used to specify a local coordinate frame for extended precision.
             * @var {x3dom.fields.SFNode} geoOrigin
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue x3dom.nodeTypes.GeoOrigin
             * @field x3d
             * @instance
             */
            this.addField_SFNode('geoOrigin', x3dom.nodeTypes.GeoOrigin);

            /**
             * Specifies whether this geometry should be rendered with or without lighting.
             * @var {x3dom.fields.SFBool} lit
             * @memberof x3dom.nodeTypes.GeoElevationGrid
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'lit', true);
        
        },
        {
            nodeChanged: function()
            {
                var geoSystem = this._vf.geoSystem;
                var geoOrigin = this._cf.geoOrigin;

                var height = this._vf.height;

                var yScale = this._vf.yScale;
                var xDimension = this._vf.xDimension;
                var zDimension = this._vf.zDimension;
                var xSpacing = this._vf.xSpacing;
                var zSpacing = this._vf.zSpacing;
                var geoGridOrigin = this._vf.geoGridOrigin;

                // check for no height == dimensions
                if(height.length !== (xDimension * zDimension))
                    x3dom.debug.logError('GeoElevationGrid: height.length(' + height.length +
                        ') != x/zDimension(' + xDimension + '*' + zDimension + ')');

                var longitude_first = x3dom.nodeTypes.GeoCoordinate.prototype.isLogitudeFirst(geoSystem);
                var easting_first = x3dom.nodeTypes.GeoCoordinate.prototype.isUTMEastingFirst(geoSystem);
                
                var ccw = this._vf.ccw;

                // coords, texture coords
                var delta_x = 1 / (xDimension-1);
                var delta_z = 1 / (zDimension-1);
                
                //from elevationgrid.js
                var numTexComponents = 2;

                var texCoordNode = this._cf.texCoord.node;
                var texPoints;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }

                if (texCoordNode) {
                    if (texCoordNode._vf.point) {
                        texPoints = texCoordNode._vf.point;
                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }
                    }
                }
                
                var positions = new x3dom.fields.MFVec3f();
                        
                var texCoords = new x3dom.fields.MFVec2f();
                        
                for(var z=0; z<zDimension; ++z)
                    for(var x=0; x<xDimension; ++x)
                    {
                        // texture coord
                        var tex_coord = new x3dom.fields.SFVec2f();
                        tex_coord.x = x*delta_x;
                        tex_coord.y = z*delta_z;
                        texCoords.push(tex_coord);

                        // coord
                        var coord = new x3dom.fields.SFVec3f();
                        if(longitude_first||easting_first)
                        {
                            coord.x = x * xSpacing;
                            coord.y = z * zSpacing;
                        }
                        else
                        // xSpacing is always east-west axis but geoSystem wants x north-south 
                        {
                            coord.x = z * zSpacing;
                            coord.y = x * xSpacing;
                        }
                        coord.z = height[(z*xDimension)+x] * yScale;
                        coord = coord.add(geoGridOrigin);
                        positions.push(coord);
                    }

                // indices
                var indices = new x3dom.fields.MFInt32();
                for(var z=0; z<(zDimension-1); z++)
                {
                    for(var x=0; x<(xDimension-1); x++)
                    {
                        var p0 = x + (z * xDimension);
                        var p1 = x + (z * xDimension) + 1;
                        var p2 = x + ((z + 1) * xDimension) + 1;
                        var p3 = x + ((z + 1) * xDimension);

                        if(ccw)
                        {
                            indices.push(p0);
                            indices.push(p1);
                            indices.push(p2);

                            indices.push(p0);
                            indices.push(p2);
                            indices.push(p3);
                        }
                        else
                        {
                            indices.push(p0);
                            indices.push(p3);
                            indices.push(p2);

                            indices.push(p0);
                            indices.push(p2);
                            indices.push(p1);
                        }
                    }
                }

                // convert to x3dom coord system
                var transformed = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoX3D(geoSystem, geoOrigin, positions);

                //if we want flat shading, we have to duplicate some vertices here
                //(as webgl does only support single-indexed rendering)
                if (this._vf.creaseAngle <= x3dom.fields.Eps) {

                    var that = this;

                    (function (){
                        var indicesFlat   = new x3dom.fields.MFInt32(),
                            positionsFlat = new x3dom.fields.MFVec3f(),
                            texCoordsFlat = new x3dom.fields.MFVec2f(); //typo? was 3f

                        if (texPoints) {
                            that.generateNonIndexedTriangleData(indices, transformed, null, texPoints, null,
                            positionsFlat, null, texCoordsFlat, null);
                        }
                        else {
                            that.generateNonIndexedTriangleData(indices, transformed, null, texCoords, null,
                            positionsFlat, null, texCoordsFlat, null);
                        }
                        for (var i = 0; i < positionsFlat.length; ++i) {
                            indicesFlat.push(i);
                        }

                        that._mesh._indices[0]   = indicesFlat.toGL();
                        that._mesh._positions[0] = positionsFlat.toGL();
                        that._mesh._texCoords[0] = texCoordsFlat.toGL();
                        that._mesh._numTexComponents = 2; //3 not yet generated
                    })();

                    this._mesh.calcNormals(0);
                }
                //smooth shading
                else {
                    this._mesh._indices[0]   = indices.toGL();
                    this._mesh._positions[0] = transformed.toGL();
                    if (texPoints)  {this._mesh._texCoords[0] = texPoints.toGL();}
                    else            {this._mesh._texCoords[0] = texCoords.toGL();}
                    this._mesh._numTexComponents = numTexComponents;
                    this._mesh.calcNormals(Math.PI);
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            generateNonIndexedTriangleData: function(indices, positions, normals, texCoords, colors,
                                                     newPositions, newNormals, newTexCoords, newColors)
            {
                //@todo: add support for RGBA colors and 3D texture coordinates
                //@todo: if there is any need for that, add multi-index support

                for (var i = 0; i < indices.length; i+=3) {
                    var i0 = indices[i  ],
                        i1 = indices[i+1],
                        i2 = indices[i+2];

                    if (positions) {
                        var p0 = new x3dom.fields.SFVec3f(),
                            p1 = new x3dom.fields.SFVec3f(),
                            p2 = new x3dom.fields.SFVec3f();

                        p0.setValues(positions[i0]);
                        p1.setValues(positions[i1]);
                        p2.setValues(positions[i2]);

                        newPositions.push(p0);
                        newPositions.push(p1);
                        newPositions.push(p2);
                    }

                    if (normals) {
                        var n0 = new x3dom.fields.SFVec3f(),
                            n1 = new x3dom.fields.SFVec3f(),
                            n2 = new x3dom.fields.SFVec3f();

                        n0.setValues(normals[i0]);
                        n1.setValues(normals[i1]);
                        n2.setValues(normals[i2]);

                        newNormals.push(n0);
                        newNormals.push(n1);
                        newNormals.push(n2);
                    }

                    if (texCoords) {
                        var t0 = new x3dom.fields.SFVec2f(),
                            t1 = new x3dom.fields.SFVec2f(),
                            t2 = new x3dom.fields.SFVec2f();

                        t0.setValues(texCoords[i0]);//ignores .z if 3d
                        t1.setValues(texCoords[i1]);
                        t2.setValues(texCoords[i2]); //typo? was t1

                        newTexCoords.push(t0);
                        newTexCoords.push(t1);
                        newTexCoords.push(t2);
                    }

                    if (colors) {
                        var c0 = new x3dom.fields.SFVec3f(),
                            c1 = new x3dom.fields.SFVec3f(),
                            c2 = new x3dom.fields.SFVec3f();

                        c0.setValues(texCoords[i0]);
                        c1.setValues(texCoords[i1]);
                        c1.setValues(texCoords[i2]);

                        newColors.push(c0);
                        newColors.push(c1);
                        newColors.push(c2);
                    }
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * (C)2016 Andreas Plesch, Waltham, MA, U.S.A
 * Dual licensed under the MIT and GPL
 */

/* ### GeoLOD ### */
x3dom.registerNodeType(
    "GeoLOD",
    "Geospatial",
    //defineClass(x3dom.nodeTypes.X3DLODNode,
    //official derivation, is not a real grouping node since it may not have children, only url references
    defineClass(x3dom.nodeTypes.X3DBoundedObject,
        
        /**
         * Constructor for GeoLOD
         * @constructs x3dom.nodeTypes.GeoLOD
         * @x3d 3.3
         * @component Geospatial
         * @status experimental
         * @extends x3dom.nodeTypes.X3DLODNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoLOD node provides a terrain-specialized form of the LOD node.
         * It is a grouping node that specifies two different levels of detail for an object using a tree structure, where 0 to 4 children can be specified, and also efficiently manages the loading and unloading of these levels of detail.
         */
        function (ctx) {
            x3dom.nodeTypes.GeoLOD.superClass.call(this, ctx);


            /**
             * The geoSystem field is used to define the spatial reference frame.
             * @var {x3dom.fields.MFString} geoSystem
             * @range {["GD", ...], ["UTM", ...], ["GC", ...]}
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue ['GD','WE']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'geoSystem', ['GD', 'WE']);

            /**
             * The rootUrl and rootNode fields provide two different ways to specify the geometry of the root tile.
             * You may use one or the other. The rootNode field lets you include the geometry for the root tile directly within the X3D file.
             * @var {x3dom.fields.MFString} rootUrl
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'rootUrl', []);

            /**
             * When the viewer enters the specified range, this geometry is replaced with the contents of the four children files defined by child1Url through child4Url.
             * The four children files are loaded into memory only when the user is within the specified range. Similarly, these are unloaded from memory when the user leaves this range.
             * @var {x3dom.fields.MFString} child1Url
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'child1Url', []);

            /**
             * When the viewer enters the specified range, this geometry is replaced with the contents of the four children files defined by child1Url through child4Url.
             * The four children files are loaded into memory only when the user is within the specified range. Similarly, these are unloaded from memory when the user leaves this range.
             * @var {x3dom.fields.MFString} child2Url
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'child2Url', []);

            /**
             * When the viewer enters the specified range, this geometry is replaced with the contents of the four children files defined by child1Url through child4Url.
             * The four children files are loaded into memory only when the user is within the specified range. Similarly, these are unloaded from memory when the user leaves this range.
             * @var {x3dom.fields.MFString} child3Url
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'child3Url', []);

            /**
             * When the viewer enters the specified range, this geometry is replaced with the contents of the four children files defined by child1Url through child4Url.
             * The four children files are loaded into memory only when the user is within the specified range. Similarly, these are unloaded from memory when the user leaves this range.
             * @var {x3dom.fields.MFString} child4Url
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'child4Url', []);
            
            /**
             * The level of detail is switched depending upon whether the user is closer or farther than range length base units from the geospatial coordinate center.
             * @var {x3dom.fields.SFFloat} range
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue 10
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'range', 10);

            /**
             *
             * @var {x3dom.fields.SFString} referenceBindableDescription
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'referenceBindableDescription', []);

            /**
             * The geoOrigin field is used to specify a local coordinate frame for extended precision.
             * @var {x3dom.fields.SFNode} geoOrigin
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue x3dom.nodeTypes.X3DChildNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('geoOrigin', x3dom.nodeTypes.GeoOrigin);

            /**
             * The rootUrl and rootNode fields provide two different ways to specify the geometry of the root tile. The rootUrl field lets you specify a URL for a file that contains the geometry.
             * @var {x3dom.fields.SFNode} rootNode
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue x3dom.nodeTypes.X3DChildNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('rootNode', x3dom.nodeTypes.X3DChildNode);

             /**
             * The center field is a translation offset in the local coordinate system that specifies the centre of the LOD node for distance calculations. The level of detail is switched depending upon whether the user is closer or farther than range length base units from the geospatial coordinate center. The center field should be specified as described in 25.2.4 Specifying geospatial coordinates.
             * @var {x3dom.fields.SFVec3f} center
             * @memberof x3dom.nodeTypes.GeoLOD
             * @initvalue 0,0,0
             * @field x3d
             * @instance
            */
            
            this.addField_SFVec3f(ctx, "center", 0, 0, 0);
            
            // loosely based on LOD implementation
            
            this._eye = new x3dom.fields.SFVec3f(0, 0, 0); //from X3DLODNode
            
            this._x3dcenter = new x3dom.fields.SFVec3f(0, 0, 0); 
            
            this._child1added = false;
            this._child2added = false;
            this._child3added = false;
            this._child4added = false;
            this._rootNodeLoaded = true;
            this._childUrlNodes = new x3dom.fields.MFNode(x3dom.nodeTypes.X3DChildNode);
              
        },
        {
            collectDrawableObjects: function(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
                {
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;
                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();
                planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask);
                if (planeMask <= 0) {
                    return;
                    }
                // at the moment, no caching here as children may change every frame
                singlePath = false;
                this.visitChildren(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                },
                
            visitChildren: function(transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
                {
                var i=0, n=0, cnodes, cnode;
                var mat_view = drawableCollection.viewMatrix;
                var center = new x3dom.fields.SFVec3f(0, 0, 0); // eye
                center = mat_view.inverse().multMatrixPnt(center);
                //transform eye point to the LOD node's local coordinate system
                this._eye = transform.inverse().multMatrixPnt(center);
                var len = this._x3dcenter.subtract(this._eye).length();
                
                //TODO: preload when close = 0.9 * range
                //- just load but do not add to drawables
                //- then at range just add to drawable
                //TODO: unload inlines if out of range and after (configurable) timeout (1 minute?)
                // - loaded = false and childurlnodes = null, childurlnodes = new mfnode should suffice ?
                
                if (len > this._vf.range) {
                    
                    if(!this._rootNodeLoaded) {
                        this._rootNodeLoaded = true;
                    }
                    //rootNode already has rootUrl node if empty, see nodeChanged()
                    cnodes = this._cf.rootNode.nodes;
                }
                
                else {
                    
                    if (!this._child1added) {
                        this._child1added = true;
                        this.addInlineChild(this._vf.child1Url);
                    }
                    if (!this._child2added) {
                        this._child2added = true;
                        this.addInlineChild(this._vf.child2Url);
                    }
                    if (!this._child3added) {
                        this._child3added = true;
                        this.addInlineChild(this._vf.child3Url);
                    }
                    if (!this._child4added) {
                        this._child4added = true;
                        this.addInlineChild(this._vf.child4Url);
                    }
                    
                    if (this._rootNodeLoaded) {
                        this._rootNodeLoaded = false;
                        //never null rootNode (?)
                    }
                    
                    cnodes = this._childUrlNodes.nodes;
                    
                }
                
                n = cnodes.length;
                                
                //probably not necessary to check if there are any child nodes
                if (n && cnodes)
                    {
                        var childTransform = this.transformMatrix(transform);
                        
                        /* not in original LOD, may work for GeoLOD as well
                        
                        if (x3dom.nodeTypes.ClipPlane.count > 0) {
                            var localClipPlanes = [];
                            for (var j = 0; j < n; j++) {
                                if ( (cnode = this._childNodes[j]) ) {
                                    if (x3dom.isa(cnode, x3dom.nodeTypes.ClipPlane) && cnode._vf.on && cnode._vf.enabled) {
                                        localClipPlanes.push({plane: cnode, trafo: childTransform});
                                    }
                                }
                            }
                            clipPlanes = localClipPlanes.concat(clipPlanes);
                        }
                        */
                        
                        for (i = 0; i < n; i++) {
                            if ( (cnode = cnodes[i]) ) {
                            cnode.collectDrawableObjects(childTransform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                            }
                        }
                    }
                },
            
            addInlineChild: function(url)
            {
                //check if url empty
                var inline = this.newInlineNode(url);
                this._childUrlNodes.addLink(inline);
            },
            
            newInlineNode: function(url)
            {
                var inline = new x3dom.nodeTypes.Inline();
                inline._vf.url = url;
                inline._nameSpace = this._nameSpace; // pass on nameSpace
                //inline.initDone = false; // these need to be initialized?
                //inline.count = 0;
                //inline.numRetries = x3dom.nodeTypes.Inline.MaximumRetries;
                x3dom.debug.logInfo("add url: " + url);
                inline.nodeChanged(); //is necessary and loads the inline scene
                return inline;
            },
            
            getVolume: function()
            {
                var vol = this._graph.volume;
                //below may not apply for GeoLOD
                if (!this.volumeValid() && this._vf.render)
                {
                    var child, childVol;
                    // use childUrlNodes ?
                        for (var i=0, n=this._childNodes.length; i<n; i++)
                        {
                        if (!(child = this._childNodes[i]) || child._vf.render !== true)
                            continue;
                        childVol = child.getVolume();
                        if (childVol && childVol.isValid())
                            vol.extendBounds(childVol.min, childVol.max);
                        }
                    //}
                }
                return vol;
            },
            
            nodeChanged: function() {
                //this._needReRender = true;
                //do geo-conversion
                
                var coords = new x3dom.fields.MFVec3f();
                coords.push(this._vf.center);
                this._x3dcenter = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoX3D(this._vf.geoSystem,this._cf.geoOrigin, coords)[0];
                
                //only rootNodes are ever shapes; childUrls have their own rootNodes
                //append rootnode field with inline rooturl if empty
                if (!this._cf.rootNode.nodes.length) {
                    var inline = this.newInlineNode(this._vf.rootUrl);
                    this._cf.rootNode.addLink(inline);
                }
                
                this.invalidateVolume();
            },
            
            fieldChanged: function(fieldName) {
                //this._needReRender = true;
                if (fieldName == "render" || fieldName == "range") {
                    this.invalidateVolume();
                }
                if (fieldname == "center") {                
                    var coords = new x3dom.fields.MFVec3f();
                    coords.push(this._vf.center);
                    this._x3dcenter = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoX3D(this._vf.geoSystem,this._cf.geoOrigin, coords)[0];
                
                    this.invalidateVolume();
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### GeoLocation ### */
x3dom.registerNodeType(
    "GeoLocation",
    "Geospatial",
    //was X3DGroupingNode which is how the node is defined in the spec
    defineClass(x3dom.nodeTypes.X3DTransformNode,
        
        /**
         * Constructor for GeoLocation
         * @constructs x3dom.nodeTypes.GeoLocation
         * @x3d 3.3
         * @component Geospatial
         * @status experimental
         * @extends x3dom.nodeTypes.X3DTransformNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoLocation node provides the ability to geo-reference any standard X3D model.
         * That is, to take an ordinary X3D model, contained within the children of the node, and to specify its geospatial location.
         * This node is a grouping node that can be thought of as a Transform node.
         * However, the GeoLocation node specifies an absolute location, not a relative one, so content developers should not nest GeoLocation nodes within each other.
         */
        function (ctx) {
            x3dom.nodeTypes.GeoLocation.superClass.call(this, ctx);


            /**
             * The geoSystem field is used to define the spatial reference frame.
             * @var {x3dom.fields.MFString} geoSystem
             * @range {["GD", ...], ["UTM", ...], ["GC", ...]}
             * @memberof x3dom.nodeTypes.GeoLocation
             * @initvalue ['GD','WE']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'geoSystem', ['GD', 'WE']);

            /**
             * The geometry of the nodes in children is to be specified in units of metres in X3D coordinates relative to the location specified by the geoCoords field.
             * The geoCoords field can be used to dynamically update the geospatial location of the model.
             * @var {x3dom.fields.SFVec3d} geoCoords
             * @memberof x3dom.nodeTypes.GeoLocation
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3d(ctx, 'geoCoords', 0, 0, 0);

            /**
             * The geoOrigin field is used to specify a local coordinate frame for extended precision.
             * @var {x3dom.fields.SFNode} geoOrigin
             * @memberof x3dom.nodeTypes.GeoLocation
             * @initvalue x3dom.nodeTypes.GeoOrigin
             * @field x3d
             * @instance
             */
            this.addField_SFNode('geoOrigin', x3dom.nodeTypes.GeoOrigin);
        },

        {
	        nodeChanged: function()
            {
                // similar to what transform in Grouping.js does
                var position = this._vf.geoCoords;
                var geoSystem = this._vf.geoSystem;
                var geoOrigin = this._cf.geoOrigin; // gets only populated if in nodeChanged()

                this._trafo =  this.getGeoTransRotMat(geoSystem, geoOrigin, position);
            },
        
            getGeoRotMat: function (geoSystem, positionGC)
            {
                //returns transformation matrix to align coordinate system with geoposition as required:
                //2 rotations to get required orientation
                //Up (Y) to skywards, and depth (-Z) to North
                //1) around X to point up by
                //angle between Z and new up plus 90
                //(angle between Z and orig. up)
                //2) around Z to get orig. up on longitude

                var coords = new x3dom.fields.MFVec3f();
                coords.push(positionGC);
                var positionGD = x3dom.nodeTypes.GeoCoordinate.prototype.GCtoGD(geoSystem, coords)[0];
                
                var Xaxis = new  x3dom.fields.SFVec3f(1,0,0);
                var rotlat = 180 - positionGD.y; // latitude
                var deg2rad = Math.PI/180;
                var rotUpQuat = x3dom.fields.Quaternion.axisAngle(Xaxis, rotlat*deg2rad);

                var Zaxis = new x3dom.fields.SFVec3f(0,0,1);
                var rotlon = 90 + positionGD.x;// 90 to get to prime meridian;
                var rotZQuat = x3dom.fields.Quaternion.axisAngle(Zaxis, rotlon*deg2rad);

                //return rotZQuat.toMatrix().mult(rotUpQuat.toMatrix();
                return rotZQuat.multiply(rotUpQuat).toMatrix();

            },

            getGeoTransRotMat: function (geoSystem, geoOrigin, position)
            {
                // accept geocoords, return translation/rotation transform matrix
                var coords = new x3dom.fields.MFVec3f();
                coords.push(position);

                var transformed = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoGC(geoSystem, geoOrigin, coords)[0];
                var rotMat = this.getGeoRotMat(geoSystem, transformed);

		        // account for geoOrigin with and without rotateYUp
                if (geoOrigin.node)
                {
                    var origin = x3dom.nodeTypes.GeoCoordinate.prototype.OriginToGC(geoOrigin);
                    if(geoOrigin.node._vf.rotateYUp)
                    {
                        // inverse rotation after original rotation and offset
      			            // just skipping all rotations produces incorrect position
                        var rotMatOrigin = this.getGeoRotMat(geoSystem, origin);
                        return rotMatOrigin.inverse().mult(x3dom.fields.SFMatrix4f.translation(transformed.subtract(origin)).mult(rotMat));
                    }
                    //rotate, then translate; account for geoOrigin by subtracting origin from GeoLocation
                    return x3dom.fields.SFMatrix4f.translation(transformed.subtract(origin)).mult(rotMat);
                }
                else
		        // no GeoOrigin: first rotate, then translate
                {
                    return x3dom.fields.SFMatrix4f.translation(transformed).mult(rotMat);
                }
            },

            //mimic what transform node does
            fieldChanged: function (fieldName)
            {
                if (fieldName == "geoSystem" || fieldName == "geoCoords" ||
                    fieldName == "geoOrigin")
                {
                    var position = this._vf.geoCoords;
                    var geoSystem = this._vf.geoSystem;
                    var geoOrigin = this._cf.geoOrigin;
                    this._trafo =  this.getGeoTransRotMat(geoSystem, geoOrigin, position);

                    this.invalidateVolume();
                    //this.invalidateCache();
                }
                else if (fieldName == "render") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            }
            //deal with geolocation in geolocation here? behaviour is undefined in spec

        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### GeoMetadata ### */
x3dom.registerNodeType(
    "GeoMetadata",
    "Geospatial",
    defineClass(x3dom.nodeTypes.X3DInfoNode,
        
        /**
         * Constructor for GeoMetadata
         * @constructs x3dom.nodeTypes.GeoMetadata
         * @x3d 3.3
         * @component Geospatial
         * @status full
         * @extends x3dom.nodeTypes.X3DInfoNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoMetadata node supports the specification of metadata describing any number of geospatial nodes.
         * This is similar to a WorldInfo node, but specifically for describing geospatial information.
         */
        function (ctx) {
            x3dom.nodeTypes.GeoMetadata.superClass.call(this, ctx);


            /**
             * The url field is used to specify a hypertext link to an external, complete metadata description.
             * Multiple URL strings can be specified in order to provide alternative locations for the same metadata information.
             * The summary field may be used to specify the format of the metadata in the case where this cannot be deduced easily.
             * @var {x3dom.fields.MFString} url
             * @memberof x3dom.nodeTypes.GeoMetadata
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'url', []);

            /**
             * The data field is used to list all of the other nodes in a scene by DEF name that reference the data described in the GeoMetadata node.
             * The nodes in the data field are not rendered, so DEF/USE can be used in order to first describe them and then to use them in the scene graph.
             * @var {x3dom.fields.MFNode} data
             * @memberof x3dom.nodeTypes.GeoMetadata
             * @initvalue x3dom.nodeTypes.X3DInfoNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('data', x3dom.nodeTypes.X3DInfoNode);

            /**
             * The summary string array contains a set of keyword/value pairs, with each keyword and its subsequent value contained in a separate string; i.e., there should always be an even (or zero) number of strings.
             * @var {x3dom.fields.MFString} summary
             * @memberof x3dom.nodeTypes.GeoMetadata
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'summary', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### GeoOrigin ### */
x3dom.registerNodeType(
    "GeoOrigin",
    "Geospatial",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for GeoOrigin
         * @constructs x3dom.nodeTypes.GeoOrigin
         * @x3d 3.2
         * @component Geospatial
         * @status full
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoOrigin node defines an absolute geospatial location and an implicit local coordinate frame against which geometry is referenced.
         * This node is used to translate from geographical coordinates into a local Cartesian coordinate system which can be managed by the X3D browser. This node is deprecated as of X3D 3.3
         */
        function (ctx) {
            x3dom.nodeTypes.GeoOrigin.superClass.call(this, ctx);


            /**
             * The geoSystem field is used to define the spatial reference frame.
             * @var {x3dom.fields.MFString} geoSystem
             * @range {["GD", ...], ["UTM", ...], ["GC", ...]}
             * @memberof x3dom.nodeTypes.GeoOrigin
             * @initvalue ['GD','WE']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'geoSystem', ['GD', 'WE']);

            /**
             * The geoCoords field is used to specify a local coordinate frame for extended precision.
             * @var {x3dom.fields.SFVec3d} geoCoords
             * @memberof x3dom.nodeTypes.GeoOrigin
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3d(ctx, 'geoCoords', 0, 0, 0);

            /**
             * The rotateYUp field is used to specify whether coordinates of nodes that use this GeoOrigin are to be rotated such that their up direction is aligned with the X3D Y axis.
             * The default behavior is to not perform this operation.
             * @var {x3dom.fields.SFBool} rotateYUp
             * @memberof x3dom.nodeTypes.GeoOrigin
             * @initvalue false
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'rotateYUp', false);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### GeoPositionInterpolator ### */
x3dom.registerNodeType(
    "GeoPositionInterpolator",
    "Geospatial",
    defineClass(x3dom.nodeTypes.X3DInterpolatorNode,
        
        /**
         * Constructor for GeoPositionInterpolator
         * @constructs x3dom.nodeTypes.GeoPositionInterpolator
         * @x3d 3.3
         * @component Geospatial
         * @status experimental
         * @extends x3dom.nodeTypes.X3DInterpolatorNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoPositionInterpolator node provides an interpolator capability where key values are specified in geographic coordinates and the interpolation is performed within the specified spatial reference frame.
         */
        function (ctx) {
            x3dom.nodeTypes.GeoPositionInterpolator.superClass.call(this, ctx);


            /**
             * The geoSystem field is used to define the spatial reference frame.
             * @var {x3dom.fields.MFString} geoSystem
             * @range {["GD", ...], ["UTM", ...], ["GC", ...]}
             * @memberof x3dom.nodeTypes.GeoPositionInterpolator
             * @initvalue ['GD','WE']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'geoSystem', ['GD', 'WE']);

            /**
             * The keyValue array is used to contain the actual coordinates and should be provided in a format consistent with that specified for the particular geoSystem.
             * @var {x3dom.fields.MFVec3d} keyValue
             * @memberof x3dom.nodeTypes.GeoPositionInterpolator
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'keyValue', []);

            /**
             * The geoOrigin field is used to specify a local coordinate frame for extended precision.
             * @var {x3dom.fields.SFNode} geoOrigin
             * @memberof x3dom.nodeTypes.GeoPositionInterpolator
             * @initvalue x3dom.nodeTypes.X3DInterpolatorNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('geoOrigin', x3dom.nodeTypes.GeoOrigin);
            
            /**
             * The onGreatCircle field is used to specify whether coordinates will be interpolated along a great circle path.
             * The default behavior is to not perform this operation for performance and compatibility.
             * @var {x3dom.fields.SFBool} onGreatCircle
             * @memberof x3dom.nodeTypes.GeoPositionInterpolator
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'onGreatCircle', false);
            
            /*
             * original simply does linear interpolation, then converts to geocentric for value_changed
             */             
        },
        {
            // adapted from X3DInterpolator.js
            
            // we need our own key and keyValue, uses hint for larger arrays and returns also found index
            linearInterpHintKeyValue: function (time, keyHint, key, keyValue, interp) {
                // add guess as input where to find time; should be close to index of last time?
                // do wraparound search in both directions since interpolation often goes back
                var keylength = key.length;
                
                if (time <= key[0])
                    return [0, keyValue[0]];
                
                else if (time >= key[keylength - 1])
                    return [keylength - 1, keyValue[keylength - 1]];
                
                var keyIndexStart = keyHint ;
                var i;
                // strictly loop only to keylength/2 but does not hurt
                for (var ii = 0; ii < keylength - 1; ++ii) {
                    // look forward
                    i = (keyIndexStart + ii) % keylength;
                    //i+1 can be outside array but undefined leads to false in check
                    if ((key[i] < time) && (time <= key[i+1]))
                        return [i, interp( keyValue[i], keyValue[i+1],
                                (time - key[i]) / (key[i+1] - key[i]) )];
                    // look backward
                    i = (keyIndexStart - ii + keylength) % keylength; 
                    if ((key[i] < time) && (time <= key[i+1]))
                        return [i, interp( keyValue[i], keyValue[i+1],
                                (time - key[i]) / (key[i+1] - key[i]) )];                    
                }
                return [0, keyValue[0]];
            },
            
            // adapted from fields.js
            slerp: function (a, b, t) {
                // calculate the cosine
                // since a and b are not unit vectors here; this is the only real change
                var cosom = a.dot(b)/(a.length()*b.length());
                var rot1;
            
                /* 
                 * does not apply for geometric slerp
                 * adjust signs if necessary
                if (cosom < 0.0)
                {
                    cosom = -cosom;
                    rot1 = b.negate();
                }
                else
                */
                {
                    rot1 = new x3dom.fields.SFVec3f(b.x, b.y, b.z);
                }
            
                // calculate interpolating coeffs
                var scalerot0, scalerot1;
                
                if ((1.0 - cosom) > 0.00001)
                {
                    // standard case
                    var omega = Math.acos(cosom);
                    var sinom = Math.sin(omega);
                    scalerot0 = Math.sin((1.0 - t) * omega) / sinom;
                    scalerot1 = Math.sin(t * omega) / sinom;
                }
                else
                {
                    // rot0 and rot1 very close - just do linear interp.
                    scalerot0 = 1.0 - t;
                    scalerot1 = t;
                }
            
                // build the new vector
                return a.multiply(scalerot0).add(rot1.multiply(scalerot1));
            },
            
            nodeChanged: function() {
                // set up initial values
                this._keyValueGC = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoGC(this._vf.geoSystem, this._cf.geoOrigin, this._vf.keyValue);
                this._keyHint = 0;
                // sanity check key.length vs. keyValue.length
            },
            
            // adapted from PositionInterpolator.js
            fieldChanged: function(fieldName)
            {
                if(fieldName === "set_fraction") {
                    
                    var value, indexValue, valueGC, valueX3D, coords ;
                    
                    if(this._vf.onGreatCircle) {
                            indexValue = this.linearInterpHintKeyValue(this._vf.set_fraction, this._keyHint, this._vf.key, this._keyValueGC, x3dom.nodeTypes.GeoPositionInterpolator.prototype.slerp);
                            this._keyHint = indexValue[0];
                            valueGC = indexValue[1];                            
                            coords = new x3dom.fields.MFVec3f();
                            coords.push(valueGC);
                            value = x3dom.nodeTypes.GeoCoordinate.prototype.GCtoGEO(this._vf.geoSystem, this._cf.geoOrigin, coords)[0];
                    }
                    
                    else {
                        indexValue = this.linearInterpHintKeyValue(this._vf.set_fraction, this._keyHint, this._vf.key, this._vf.keyValue, function (a, b, t) {
                            return a.multiply(1.0-t).add(b.multiply(t));                        
                        });
                        this._keyHint = indexValue[0];
                        value = indexValue[1];                            
                        coords = new x3dom.fields.MFVec3f();
                        coords.push(value);
                        valueGC = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoGC(this._vf.geoSystem, this._cf.geoOrigin, coords)[0];
                    }
                    
                    //account for GeoOrigin, eg. transform to X3D coordinates
                    coords = new x3dom.fields.MFVec3f();
                    coords.push(valueGC);
                    var GCgeoSystem = new x3dom.fields.MFString();
                    GCgeoSystem.push("GC");
                    GCgeoSystem.push(x3dom.nodeTypes.GeoCoordinate.prototype.getElipsoideCode(this._vf.geoSystem));
                    valueX3D = x3dom.nodeTypes.GeoCoordinate.prototype.GCtoX3D(GCgeoSystem, this._cf.geoOrigin, coords)[0];
                    
                    this.postMessage('value_changed', valueX3D);
                    this.postMessage('geovalue_changed', value);
                }
            }                
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * (C)2015 Andreas Plesch, Waltham, MA, U.S.A.
 * Dual licensed under the MIT and GPL
 */

/* ### GeoTransform ### */
x3dom.registerNodeType(
    "GeoTransform",
    "Geospatial",
    defineClass(x3dom.nodeTypes.X3DTransformNode, //should be X3DGroupingNode but more convenient
        
        /**
         * Constructor for GeoTransform
         * @constructs x3dom.nodeTypes.GeoTransform
         * @x3d 3.3
         * @component Geospatial
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoTransform node is a grouping node that defines a coordinate system for its children to support the translation and orientation of geometry built using GeoCoordinate nodes within the local world coordinate system.
         * The X-Z plane of a GeoTransform coordinate system is tangent to the ellipsoid of the spatial reference frame at the location specified by the geoCenter field.
         */
        function (ctx) {
            x3dom.nodeTypes.GeoTransform.superClass.call(this, ctx);


            /**
             * The geoCenter field specifies, in the spatial reference frame specified by the geoSystem field, the location at which the local coordinate system is centered.
             * @var {x3dom.fields.SFVec3d} geoCenter
             * @memberof x3dom.nodeTypes.GeoTransform
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3d(ctx, 'geoCenter', 0, 0, 0);

            /**
             * Defines the rotation component of the transformation.
             * @var {x3dom.fields.SFRotation} rotation
             * @memberof x3dom.nodeTypes.GeoTransform
             * @initvalue 0,0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'rotation', 0, 0, 1, 0);

            /**
             * Defines the scale component of the transformation.
             * @var {x3dom.fields.SFVec3f} scale
             * @memberof x3dom.nodeTypes.GeoTransform
             * @initvalue 1,1,1
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'scale', 1, 1, 1);

            /**
             * The scaleOrientation specifies a rotation of the coordinate system before the scale (to specify scales in arbitrary orientations).
             * The scaleOrientation applies only to the scale operation.
             * @var {x3dom.fields.SFRotation} scaleOrientation
             * @memberof x3dom.nodeTypes.GeoTransform
             * @initvalue 0,0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'scaleOrientation', 0, 0, 1, 0);

            /**
             * The translation field specifies a translation to the coordinate system.
             * @var {x3dom.fields.SFVec3f} translation
             * @memberof x3dom.nodeTypes.GeoTransform
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'translation', 0, 0, 0);

            /**
             * The geoOrigin field is used to specify a local coordinate frame for extended precision.
             * @var {x3dom.fields.SFNode} geoOrigin
             * @memberof x3dom.nodeTypes.GeoTransform
             * @initvalue x3dom.nodeTypes.Transform
             * @field x3d
             * @instance
             */
            this.addField_SFNode('geoOrigin', x3dom.nodeTypes.GeoOrigin);

            /**
             * The geoSystem field is used to define the spatial reference frame.
             * @var {x3dom.fields.MFString} geoSystem
             * @range {["GD", ...], ["UTM", ...], ["GC", ...]}
             * @memberof x3dom.nodeTypes.GeoTransform
             * @initvalue ['GD','WE']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'geoSystem', ['GD', 'WE']);
            
             /**
            * The globalGeoOrigin field specifies whether a GeoOrigin should be applied to child nodes.
            * The default is false which means that GeoOrigin nodes are expected to have been provided to child nodes.
            * A true value means that GeoOrigin nodes are expected to have been omitted from child nodes. In this case,
            * the GeoOrigin of the GeoTransform is applied to the child nodes as if GeoOrigin nodes had been provided to child nodes.
            * A true value in combination with provided GeoOrigin in child nodes leads to undefined behaviour.
            * @var {x3dom.fields.SFBool} globalGeoOrigin
            * @memberof x3dom.nodeTypes.GeoTransform
            * @initvalue false
            * @field x3dom
            * @instance
            */
            this.addField_SFBool(ctx, 'globalGeoOrigin', false);
        },
        {
            nodeChanged: function ()
            {
                this._trafo = this.getGeoTransform();
            },
            
            getGeoTransform: function ()
            {
                // OR x OT x C x GR x T x R x SR x S x -SR x -GR x -C x -OT x -OR
                // based on regular Transform and geoCenter defined transforms in correct order
                // OR: GeoOrigin Rotation
                // OT: GeoOrigin Translation
                // GR: GeoLocation Rotation with geoCenter
                // C: geoCenter Translation
                // regular Transform P' = T * C * R * SR * S * -SR * -C * P
                
                var geoCenterRotMat, geoCenter, scaleOrientMat, geoTransform, coords, geoCenterGC, geoSystem, geoOrigin;
                geoSystem = this._vf.geoSystem;
                geoOrigin = this._cf.geoOrigin;
                geoCenter = this._vf.geoCenter;
                skipGO = this._vf.globalGeoOrigin;
                scaleOrientMat = this._vf.scaleOrientation.toMatrix();
                coords = new x3dom.fields.MFVec3f();
                coords.push(geoCenter);
                geoCenterGC = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoGC(geoSystem, geoOrigin, coords)[0];
                geoCenterRotMat = x3dom.nodeTypes.GeoLocation.prototype.getGeoRotMat(geoSystem, geoCenterGC);
                geoTransform = 
                    x3dom.fields.SFMatrix4f.translation(geoCenterGC).
                    mult(geoCenterRotMat).
                    mult(x3dom.fields.SFMatrix4f.translation(this._vf.translation)).
                    mult(this._vf.rotation.toMatrix()).
                    mult(scaleOrientMat).
                    mult(x3dom.fields.SFMatrix4f.scale(this._vf.scale)).
                    mult(scaleOrientMat.inverse()).
                    mult(geoCenterRotMat.inverse()).
                    mult(x3dom.fields.SFMatrix4f.translation(geoCenterGC.negate()));
                //deal with geoOrigin by first reversing its effect, then reapplying it.
                if(geoOrigin.node)
                {
                    var originGC = x3dom.nodeTypes.GeoCoordinate.prototype.OriginToGC(geoOrigin);
                    if (!skipGO) 
                    {
                        //undo geoOrigin translation from child node
                        geoTransform = geoTransform.mult(x3dom.fields.SFMatrix4f.translation(originGC));
                    }
                    if(geoOrigin.node._vf.rotateYUp)
                    {
                        var rotMatOrigin = x3dom.nodeTypes.GeoLocation.prototype.getGeoRotMat(geoSystem, originGC);
                        if (!skipGO) 
                        {    
                            //undo GeoOrigin rotation from child node before translation
                            geoTransform = geoTransform.mult(rotMatOrigin);
                        }
                    }
                    //apply GeoOrigin translation. after geoTransform
                    geoTransform = x3dom.fields.SFMatrix4f.translation(originGC.negate()).mult(geoTransform);
                    if(geoOrigin.node._vf.rotateYUp)
                    {
                        //apply GeoOrigin rotation after translation
                        geoTransform = rotMatOrigin.inverse().mult(geoTransform);
                    }
                }
                return geoTransform;
            },
            
            fieldChanged: function (fieldName)
            {
                if (fieldName == "geoCenter" || fieldName == "translation" ||
                    fieldName == "rotation" || fieldName == "scale" ||
                    fieldName == "scaleOrientation")
                {
                    this._trafo = this.getGeoTransform();
                    
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
                else if (fieldName == "render") {
                    this.invalidateVolume();
                    //this.invalidateCache();
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### GeoViewpoint ### */
x3dom.registerNodeType(
    "GeoViewpoint",
    "Geospatial",
    defineClass(x3dom.nodeTypes.X3DViewpointNode,
        
        /**
         * Constructor for GeoViewpoint
         * @constructs x3dom.nodeTypes.GeoViewpoint
         * @x3d 3.3
         * @component Geospatial
         * @status experimental
         * @extends x3dom.nodeTypes.X3DViewpointNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The GeoViewpoint node allows the specification of a viewpoint in terms of a geospatial coordinate.
         * This node can be used wherever a Viewpoint node can be used and can be combined with Viewpoint nodes in the same scene.
         */
        function (ctx) {
            x3dom.nodeTypes.GeoViewpoint.superClass.call(this, ctx);


            /**
             * The geoSystem field is used to define the spatial reference frame.
             * @var {x3dom.fields.MFString} geoSystem
             * @range {["GD", ...], ["UTM", ...], ["GC", ...]}
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue ['GD','WE']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'geoSystem', ['GD', 'WE']);

            /**
             * Preferred minimum viewing angle from this viewpoint in radians.
             * Small field of view roughly corresponds to a telephoto lens, large field of view roughly corresponds to a wide-angle lens.
             * Hint: modifying Viewpoint distance to object may be better for zooming.
             * Warning: fieldOfView may not be correct for different window sizes and aspect ratios.
             * Interchange profile hint: this field may be ignored.
             * @var {x3dom.fields.SFFloat} fieldOfView
             * @range [0, pi]
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue 0.785398
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'fieldOfView', 0.785398);

            /**
             * The orientation fields of the Viewpoint node specifies relative orientation to the default orientation.
             * @var {x3dom.fields.SFRotation} orientation
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue 0,0,1,0
             * @field x3d
             * @instance
             */
            this.addField_SFRotation(ctx, 'orientation', 0, 0, 1, 0);

            /**
             * The centerOfRotation field specifies a center about which to rotate the user's eyepoint when in EXAMINE mode.
             * The coordinates are provided in the coordinate system specified by geoSystem.
             * @var {x3dom.fields.SFVec3f} centerOfRotation
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue 0,0,0
             * @field x3d
             * @instance
             */
            this.addField_SFVec3f(ctx, 'centerOfRotation', 0, 0, 0);

            /**
             * The position fields of the Viewpoint node specifies a relative location in the local coordinate system.
             * The coordinates are provided in the coordinate system specified by geoSystem. 
             * @var {x3dom.fields.SFVec3d} position
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue 0,0,100000
             * @field x3d
             * @instance
             */
            this.addField_SFVec3d(ctx, 'position', 0, 0, 100000);

            /**
             * Enable/disable directional light that always points in the direction the user is looking.
             * Removed in X3D V3.3. See NavigationInfo
             * still supported but required changing default to undefined since could be already given by NavigationInfo
             * @var {x3dom.fields.SFBool} headlight
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue undefined
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'headlight', undefined);

            /**
             * Specifies the navigation type.
             * Removed in X3D V3.3. See NavigationInfo
             * still supported but required changing default to undefined since could be already given by NavigationInfo
             * @var {x3dom.fields.MFString} navType
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue undefined
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'navType', undefined);

            /**
             * The speedFactor field of the GeoViewpoint node is used as a multiplier to the elevation-based velocity that the node sets internally; i.e., this is a relative value and not an absolute speed as is the case for the NavigationInfo node.
             * @var {x3dom.fields.SFFloat} speedFactor
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'speedFactor', 1.0);
            
            /**
             * Specifies the near plane.
             * @var {x3dom.fields.SFFloat} zNear
             * @range -1 or [0, inf]
             * @memberof x3dom.nodeTypes.Viewpoint
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zNear', -1); //0.1);
            
            /**
             * Specifies the far plane.
             * @var {x3dom.fields.SFFloat} zFar
             * @range -1 or [0, inf]
             * @memberof x3dom.nodeTypes.Viewpoint
             * @initvalue -1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'zFar', -1); //100000);
                        
            /**
             * Enable/disable elevation scaled speed for automatic adjustment of user movement as recommended in spec. 
             * Custom field to allow disabling for performance
             * @var {x3dom.fields.SFBool} elevationScaling
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'elevationScaling', true);

            /**
             * The geoOrigin field is used to specify a local coordinate frame for extended precision.
             * @var {x3dom.fields.SFNode} geoOrigin
             * @memberof x3dom.nodeTypes.GeoViewpoint
             * @initvalue x3dom.nodeTypes.X3DViewpointNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('geoOrigin', x3dom.nodeTypes.GeoOrigin);
            
            // save centerOfRotation field for reset
            this._geoCenterOfRotation = this._vf.centerOfRotation ;

        },
        {
            // redefine activate function to save initial speed
            // from X3DViewpoint.js
            activate: function (prev) {
                var viewarea = this._nameSpace.doc._viewarea;
                
                if (prev) {
                    viewarea.animateTo(this, prev._autoGen ? null : prev);
                }
                viewarea._needNavigationMatrixUpdate = true;
                
                x3dom.nodeTypes.X3DBindableNode.prototype.activate.call(this, prev);
                
                var navi = viewarea._scene.getNavigationInfo();
                this._initSpeed = navi._vf.speed;
                this._examineSpeed = navi._vf.speed;
                this._lastSpeed = navi._vf.speed;
                this._userSpeedFactor = 1.0;
                this._lastNavType = navi.getType();
                x3dom.debug.logInfo("initial navigation speed: " + this._initSpeed);
                // x3dom.debug.logInfo(this._xmlNode.hasAttribute('headlight'));
                // set headlight and navType here if they are given (but removed from spec.)
                // is there a way to check if fields are given in the document ? (dom has default values if not given)
                if (this._vf.headlight !== undefined) {navi._vf.headlight = this._vf.headlight;}
                if (this._vf.navType !== undefined) {navi._vf.navType = this._vf.navType;}
                
            },
            
            // redefine deactivate to restore initial speed
            deactivate: function (prev) {
                var viewarea = this._nameSpace.doc._viewarea;
                var navi = viewarea._scene.getNavigationInfo();
                //retain examine mode speed modifications
                navi._vf.speed = this._examineSpeed;
                x3dom.debug.logInfo("navigation speed restored to: " + this._examineSpeed);
                x3dom.nodeTypes.X3DBindableNode.prototype.deactivate.call(this, prev);
                // somehow this.getViewMatrix gets called one more time after deactivate and resets speed, check there
            },
            
            // redefine, otherwise in X3DBindableNode
            nodeChanged: function() {
                
                this._stack = this._nameSpace.doc._bindableBag.addBindable(this);
                
                // for local use
                this._geoOrigin = this._cf.geoOrigin;
                this._geoSystem = this._vf.geoSystem;
                this._position = this._vf.position;
                this._orientation = this._vf.orientation;
                
                // needs to be here because of GeoOrigin subnode
                this._viewMatrix = this.getInitViewMatrix(this._orientation, this._geoSystem, this._geoOrigin, this._position);

                // also transform centerOfRotation for initial view                
                this._vf.centerOfRotation = this.getGeoCenterOfRotation(this._geoSystem, this._geoOrigin, this._geoCenterOfRotation);
                
                // borrowed from Viewpoint.js
            
                this._projMatrix = null;
                this._lastAspect = 1.0;
 
                // z-ratio: a value around 5000 would be better...
                this._zRatio = 10000;
                // set to -1 to trigger automatic setting
                this._zNear = this._vf.zNear;
                this._zFar = this._vf.zFar ;
                
                // special stuff...
                this._imgPlaneHeightAtDistOne = 2.0 * Math.tan(this._vf.fieldOfView / 2.0);
                
            },
            
            // all borrowed from Viewpoint.js
            fieldChanged: function (fieldName) {
                if (fieldName == "position" || fieldName == "orientation") {
                    this.resetView();
                }
                else if (fieldName == "fieldOfView" ||
                    fieldName == "zNear" || fieldName == "zFar") {
                    this._projMatrix = null;   // only trigger refresh
                    this._zNear = this._vf.zNear;
                    this._zFar = this._vf.zFar ;
                    this._imgPlaneHeightAtDistOne = 2.0 * Math.tan(this._vf.fieldOfView / 2.0);
                }
                else if (fieldName.indexOf("bind") >= 0) {
                    // FIXME; call parent.fieldChanged();
                    this.bind(this._vf.bind);
                }
            },

            setProjectionMatrix: function(matrix)
            {
                this._projMatrix = matrix;
            },

            getCenterOfRotation: function() {
                // is already transformed to GC
                // return is expected in world coords.
                return this.getCurrentTransform().multMatrixPnt(this._vf.centerOfRotation);
            },
            
            getGeoCenterOfRotation: function(geoSystem, geoOrigin, geoCenterOfRotation) {
                var coords = new x3dom.fields.MFVec3f();
                coords.push(geoCenterOfRotation);
                var transformed = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoX3D(geoSystem, geoOrigin, coords);
                return transformed[0];
            },
            
            isExamineMode: function(navType) {
                return (navType == 'examine' || navType == 'turntable' || navType == 'lookaround' || navType == 'lookat');
            },
            
            getViewMatrix: function() {
                // called a lot from viewarea.js; (ab)use for updating elevation scaled speed
                // do only if elevationScaling is enabled
                // skip frames for performance ? do only every 0.1s or so ?
                // gets called once even after being deactivated
                if (this._vf.isActive && this._vf.elevationScaling) {
                    var viewarea = this._nameSpace.doc._viewarea;
                    var navi = viewarea._scene.getNavigationInfo();
                    var navType = navi.getType();
                    // manage examine mode: do not use elevation scaled speed and keep own speed
                    if (this.isExamineMode(navType)) {
                        if (!this.isExamineMode(this._lastNavType)) {
                            navi._vf.speed = this._examineSpeed;
                        }
                        this._lastNavType = navType;
                    }
                    else {
                        if (this.isExamineMode(this._lastNavType)) {
                            this._examineSpeed = navi._vf.speed;
                            x3dom.debug.logInfo("back from examine mode, resume speed: " + this._lastSpeed);
                            navi._vf.speed = this._lastSpeed;
                        }
                        this._lastNavType = navType;
                        // check if speed was modified interactively
                        if (navi._vf.speed != this._lastSpeed) {
                            this._userSpeedFactor *= navi._vf.speed / this._lastSpeed;
                            x3dom.debug.logInfo("interactive speed factor changed: " + this._userSpeedFactor);
                        }
                        
                        // get elevation above ground
                        // current position in x3d 
                        // borrowed from webgl_gfx.js
                        var viewtrafo = viewarea._scene.getViewpoint().getCurrentTransform();
                        viewtrafo = viewtrafo.inverse().mult(this._viewMatrix);
                        var position = viewtrafo.inverse().e3();
                        
                        var geoOrigin = this._geoOrigin;
                        var geoSystem = this._geoSystem;
                        // assume GC
                        var positionGC = position;
                        
                        if (geoOrigin.node) {
                            var origin = x3dom.nodeTypes.GeoCoordinate.prototype.OriginToGC(geoOrigin);
                            // first rotate if requested 
                            if(geoOrigin.node._vf.rotateYUp) {
                                // rotation is GeoLocation rotation
                                var rotmat = x3dom.nodeTypes.GeoLocation.prototype.getGeoRotMat(geoSystem, origin);
                                positionGC = rotmat.multMatrixPnt(position);
                                }
                            // then translate
                            positionGC = positionGC.add(origin);
                        }
                        
                        // x3dom.debug.logInfo("viewpoint position " + positionGC);
                        
                        var coords = new x3dom.fields.MFVec3f();
                        coords.push(positionGC);
                        // could be a bit optimized since geoSystem does not change
                        // eg., move initial settings of GCtoGD outside 
                        var positionGD = x3dom.nodeTypes.GeoCoordinate.prototype.GCtoGD(geoSystem, coords)[0];
                        // at 10m above ground a speed of 1 sounds about right; make positive if below ground                      
                        var elevationSpeed = Math.abs(positionGD.z/10);
                        // keep above 1 to be able to move close to the ground
                        elevationSpeed = elevationSpeed > 1 ? elevationSpeed : 1;
                        navi._vf.speed = elevationSpeed * this._vf.speedFactor * this._userSpeedFactor;
                        this._lastSpeed = navi._vf.speed;
                    }
                }
                return this._viewMatrix;
            },
            
            getInitViewMatrix: function(orientation, geoSystem, geoOrigin, position) {
                // orientation needs to rotated as in GeoLocation node
                var coords = new x3dom.fields.MFVec3f();
                coords.push(position);
                var positionGC = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoGC(geoSystem, geoOrigin, coords)[0];
                // x3dom.debug.logInfo("GEOVIEWPOINT at GC: " + positionGC);
                var orientMatrix = orientation.toMatrix();
                var rotMat = x3dom.nodeTypes.GeoLocation.prototype.getGeoRotMat(geoSystem, positionGC);
                var rotOrient = rotMat.mult(orientMatrix);
                // inverse for rotateYUp
                if(geoOrigin.node) {
                    if(geoOrigin.node._vf.rotateYUp) {
                        var origin = x3dom.nodeTypes.GeoCoordinate.prototype.OriginToGC(geoOrigin);
                        var rotMatOrigin = x3dom.nodeTypes.GeoLocation.prototype.getGeoRotMat(geoSystem, origin);
                        rotOrient = rotMatOrigin.inverse().mult(rotOrient);
                    }
                }
                var positionX3D = x3dom.nodeTypes.GeoCoordinate.prototype.GEOtoX3D(geoSystem, geoOrigin, coords)[0];
                // x3dom.debug.logInfo("GEOVIEWPOINT at X3D: " + positionX3D);
                return x3dom.fields.SFMatrix4f.translation(positionX3D).mult(rotOrient).inverse();
            },

            getFieldOfView: function() {
                return this._vf.fieldOfView;
            },

            resetView: function() {
                this._viewMatrix = this.getInitViewMatrix(this._vf.orientation, this._vf.geoSystem, this._cf.geoOrigin, this._vf.position);
                // also reset center of Rotation; is not done for regular viewpoint
                this._vf.centerOfRotation = this.getGeoCenterOfRotation(this._vf.geoSystem, this._cf.geoOrigin, this._geoCenterOfRotation);
                //Reset navigation helpers of the viewarea
                if(this._nameSpace.doc._viewarea) {
                    this._nameSpace.doc._viewarea.resetNavHelpers();
                }
            },

            getNear: function() {
                return this._zNear;
            },

            getFar: function() {
                return this._zFar;
            },

            getImgPlaneHeightAtDistOne: function() {
                return this._imgPlaneHeightAtDistOne;
            },

            getProjectionMatrix: function(aspect)
            {
                var fovy = this._vf.fieldOfView;

                var zfar = this._vf.zFar ;
                var znear = this._vf.zNear;

                if (znear <= 0 || zfar <= 0)
                {
                    var nearScale = 0.8, farScale = 1.2;
                    var viewarea = this._nameSpace.doc._viewarea;
                    var scene = viewarea._scene;

                    // Doesn't work if called e.g. from RenderedTexture with different sub-scene
                    var min = x3dom.fields.SFVec3f.copy(scene._lastMin);
                    var max = x3dom.fields.SFVec3f.copy(scene._lastMax);

                    var dia = max.subtract(min);
                    var sRad = dia.length() / 2;

                    var mat = viewarea.getViewMatrix().inverse();
                    var vp = mat.e3();

                    // account for scales around the viewpoint
                    var translation = new x3dom.fields.SFVec3f(0,0,0),
                        scaleFactor = new x3dom.fields.SFVec3f(1,1,1);
                    var rotation = new x3dom.fields.Quaternion(0,0,1,0),
                        scaleOrientation = new x3dom.fields.Quaternion(0,0,1,0);

                    // unfortunately, decompose is a rather expensive operation
                    mat.getTransform(translation, rotation, scaleFactor, scaleOrientation);

                    var minScal = scaleFactor.x, maxScal = scaleFactor.x;

                    if (maxScal < scaleFactor.y) maxScal = scaleFactor.y;
                    if (minScal > scaleFactor.y) minScal = scaleFactor.y;
                    if (maxScal < scaleFactor.z) maxScal = scaleFactor.z;
                    if (minScal > scaleFactor.z) minScal = scaleFactor.z;

                    if (maxScal > 1)
                        nearScale /= maxScal;
                    else if (minScal > x3dom.fields.Eps && minScal < 1)
                        farScale /= minScal;
                    // near/far scale adaption done

                    var sCenter = min.add(dia.multiply(0.5));
                    var vDist = (vp.subtract(sCenter)).length();

                    if (sRad) {
                        if (vDist > sRad)
                            znear = (vDist - sRad) * nearScale;  // Camera outside scene
                        else
                            znear = 0;                           // Camera inside scene

                        zfar = (vDist + sRad) * farScale;
                    }
                    else {
                        znear = 0.1;
                        zfar = 100000;
                    }

                    var zNearLimit = zfar / this._zRatio;
                    znear = Math.max(znear, Math.max(x3dom.fields.Eps, zNearLimit));
                    if (zfar > this._vf.zNear && this._vf.zNear > 0)
                        znear = this._vf.zNear;
                    if (this._vf.zFar > znear)
                        zfar = this._vf.zFar;
                    if (zfar <= znear)
                        zfar = znear + 1;
                    //x3dom.debug.logInfo("near: " + znear + " -> far:" + zfar);
                }

                if (this._projMatrix == null)
                {
                    this._projMatrix = x3dom.fields.SFMatrix4f.perspective(fovy, aspect, znear, zfar);
                }
                else if (this._zNear != znear || this._zFar != zfar)
                {
                    var div = znear - zfar;
                    this._projMatrix._22 = (znear + zfar) / div;
                    this._projMatrix._23 = 2 * znear * zfar / div;
                }
                else if (this._lastAspect != aspect)
                {
                    this._projMatrix._00 = (1 / Math.tan(fovy / 2)) / aspect;
                    this._lastAspect = aspect;
                }

                // also needed for being able to ask for near and far
                this._zNear = znear;
                this._zFar = zfar;

                return this._projMatrix;
            }
        }
    )
);

/*
 * Copyrigth (C) 2014 TOSHIBA
 * Dual licensed under the MIT and GPL licenses.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 *
 */

/* ### ScreenGroup ### */
x3dom.registerNodeType(
    "ScreenGroup",
    "Layout",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,

        /**
         * Constructor for ScreenGroup
         * N.B. Screengroup is not meant to work with any viewpoint other than
         * a regular, perspective viewpoint.
         * @constructs x3dom.nodeTypes.ScreenGroup
         * @x3d 3.3
         * @component Layout
         * @author armand.girier@toshiba.co.jp
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Add a class description here.
         */
        function (ctx) {
            x3dom.nodeTypes.ScreenGroup.superClass.call(this, ctx);
        },
        {
            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {

                // This is taken "as is" from Billboard

                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask);
                if (planeMask < 0) {
                    return;
                }

                // no caching later on as transform changes almost every frame anyway
                singlePath = false;

  // End of code borrowed from Billboard, Screengroup's scale computing starts
  // here.

  // Computes the scaling ratio applied to an object depending on its distance
  // to the camera. A dot　product is performed between said distance and the
  // camera direction, since it is the relevant value to compute the perspective
  // scale.
  // All positions and directions are converted to the camera's coordinates.

                var doc,        // The current X3D document.
                    vp,         // The current viewpoint.
                    minus_one,  // (0, 0, -1), original view direction.
                    zero,       // (0, 0, 0), local position of the screengroup and camera.
                    viewport_height,
                    one_to_one_pixel_depth, //The depth at which an object of size S looks S pixels big.
                    view_transform,
                    view_direction,
                    model_transform,
                    camera_position,
                    screengroup_position,
                    viewpoint_to_screengroup,
                    ratio,      // The scaling ratio to make the screen group size in pixels.
                    scale_matrix; // The matrix for the scaling.


                doc = this._nameSpace.doc;
                vp = doc._scene.getViewpoint();

                viewport_height = doc._x3dElem.clientHeight;
                one_to_one_pixel_depth = viewport_height / vp.getImgPlaneHeightAtDistOne();

                minus_one = new x3dom.fields.SFVec3f(0, 0, -1.0);
                zero = new x3dom.fields.SFVec3f(0, 0, 0);

                view_transform = drawableCollection.viewMatrix;
                model_transform = transform;

                view_direction = minus_one; // In camera's coordinates

                camera_position = zero; // In camera's coordinates

                screengroup_position = view_transform.multMatrixPnt(
                  model_transform.multMatrixPnt(zero)
                ); // In camera's coordinates


                viewpoint_to_screengroup = screengroup_position.subtract(camera_position);

                ratio = view_direction.dot(viewpoint_to_screengroup) / one_to_one_pixel_depth;

                scale_matrix = x3dom.fields.SFMatrix4f.scale(new x3dom.fields.SFVec3f(ratio, ratio, ratio));
                var childTransform = this.transformMatrix(model_transform.mult(scale_matrix));


                for (var i=0, i_n=this._childNodes.length; i<i_n; i++)
                {
                    var cnode = this._childNodes[i];
                    if (cnode) {
                        cnode.collectDrawableObjects(childTransform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                    }
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### X3DPlanarGeometryNode ### */
x3dom.registerNodeType(
    "X3DPlanarGeometryNode",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for X3DPlanarGeometryNode
         * @constructs x3dom.nodeTypes.X3DPlanarGeometryNode
         * @x3d x.x
         * @component Geometry2D
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         */
        function (ctx) {
            x3dom.nodeTypes.X3DPlanarGeometryNode.superClass.call(this, ctx);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### Arc2D ### */
x3dom.registerNodeType(
    "Arc2D",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DPlanarGeometryNode,
        
        /**
         * Constructor for Arc2D
         * @constructs x3dom.nodeTypes.Arc2D
         * @x3d 3.3
         * @component Geometry2D
         * @status full
         * @extends x3dom.nodeTypes.X3DPlanarGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Arc node specifies a linear circular arc whose center is at (0,0) and whose angles are
         *  measured starting at the positive x-axis and sweeping towards the positive y-axis.
         */
        function (ctx) {
            x3dom.nodeTypes.Arc2D.superClass.call(this, ctx);


            /**
             * The radius field specifies the radius of the circle of which the arc is a portion.
             * @var {x3dom.fields.SFFloat} radius
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 1
             * @range (0, inf)
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', 1);

            /**
             * The arc extends from the startAngle counterclockwise to the endAngle.
             * @var {x3dom.fields.SFFloat} startAngle
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 0
             * @range [-2 pi, 2pi]
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'startAngle', 0);

            /**
             * The arc extends from the startAngle counterclockwise to the endAngle.
             * @var {x3dom.fields.SFFloat} endAngle
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 1.570796
             * @range [-2 pi, 2pi]
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'endAngle', 1.570796);

            /**
             * Number of lines into which the arc is subdivided
             * @var {x3dom.fields.SFFloat} subdivision
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            this._mesh._primType = 'LINES';

            var r = this._vf.radius;
            var start = this._vf.startAngle;
            var end = this._vf.endAngle;

            // The following code ensures that:
            // 1. 0 <= startAngle < 2*Pi
            // 2. startAngle < endAngle
            // 3. endAngle - startAngle <= 2*Pi
            var Pi2 = Math.PI * 2.0;
            start -= Math.floor(start / Pi2) * Pi2;
            end -= Math.floor(end / Pi2) * Pi2;
            if (end <= start)
                end += Pi2;

            var geoCacheID = 'Arc2D_' + r + start + end;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Arc2D from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            } else {

                var anzahl = this._vf.subdivision;
                var t = (end - start) / anzahl;
                var theta = start;

                for (var i = 0; i <= anzahl + 1; i++) {
                    var x = Math.cos(theta) * r;
                    var y = Math.sin(theta) * r;

                    this._mesh._positions[0].push(x);
                    this._mesh._positions[0].push(y);
                    this._mesh._positions[0].push(0.0);
                    theta += t;
                }

                for (var j = 0; j < anzahl; j++) {
                    this._mesh._indices[0].push(j);
                    this._mesh._indices[0].push(j + 1);
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 2;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "radius" || fieldName == "subdivision" ||
                    fieldName == "startAngle" || fieldName == "endAngle") {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];

                    var r = this._vf.radius;
                    var start = this._vf.startAngle;
                    var end = this._vf.endAngle;
                    var anzahl = this._vf.subdivision;

                    var Pi2 = Math.PI * 2.0;
                    start -= Math.floor(start / Pi2) * Pi2;
                    end -= Math.floor(end / Pi2) * Pi2;
                    if (end <= start)
                        end += Pi2;

                    var t = (end - start) / anzahl;
                    var theta = start;

                    for (var i = 0; i <= anzahl + 1; i++) {
                        var x = Math.cos(theta) * r;
                        var y = Math.sin(theta) * r;

                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);
                        theta += t;
                    }

                    for (var j = 0; j < anzahl; j++) {
                        this._mesh._indices[0].push(j);
                        this._mesh._indices[0].push(j + 1);
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 2;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node._dirty.indexes = true;
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### ArcClose2D ### */
x3dom.registerNodeType(
    "ArcClose2D",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DPlanarGeometryNode,
        
        /**
         * Constructor for ArcClose2D
         * @constructs x3dom.nodeTypes.ArcClose2D
         * @x3d 3.3
         * @component Geometry2D
         * @status full
         * @extends x3dom.nodeTypes.X3DPlanarGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ArcClose node specifies a portion of a circle whose center is at (0,0) and whose angles are
         *  measured starting at the positive x-axis and sweeping towards the positive y-axis.
         */
        function (ctx) {
            x3dom.nodeTypes.ArcClose2D.superClass.call(this, ctx);


            /**
             * The end points of the arc specified are connected as defined by the closureType field.
             * @var {x3dom.fields.SFString} closureType
             * @memberof x3dom.nodeTypes.ArcClose2D
             * @initvalue "PIE"
             * @range ["PIE" | "CHORD"]
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'closureType', "PIE");

            /**
             * The radius field specifies the radius of the circle of which the arc is a portion.
             * @var {x3dom.fields.SFFloat} radius
             * @memberof x3dom.nodeTypes.ArcClose2D
             * @initvalue 1
             * @range (0, inf)
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', 1);

            /**
             * The arc extends from the startAngle counterclockwise to the endAngle.
             * @var {x3dom.fields.SFFloat} startAngle
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 0
             * @range [-2 pi, 2pi]
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'startAngle', 0);

            /**
             * The arc extends from the startAngle counterclockwise to the endAngle.
             * @var {x3dom.fields.SFFloat} endAngle
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 1.570796
             * @range [-2 pi, 2pi]
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'endAngle', 1.570796);

            /**
             * Number of lines into which the arc is subdivided
             * @var {x3dom.fields.SFFloat} subdivision
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            var r = this._vf.radius;
            var start = this._vf.startAngle;
            var end = this._vf.endAngle;
            var anzahl = this._vf.subdivision;

            // The following code ensures that:
            // 1. 0 <= startAngle < 2*Pi
            // 2. startAngle < endAngle
            // 3. endAngle - startAngle <= 2*Pi
            var Pi2 = Math.PI * 2.0;
            start -= Math.floor(start / Pi2) * Pi2;
            end -= Math.floor(end / Pi2) * Pi2;
            if (end <= start)
                end += Pi2;

            var geoCacheID = 'ArcClose2D_' + r + start + end + this._vf.closureType;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using ArcClose2D from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            } else {
                var t = (end - start) / anzahl;
                var theta = start;

                if (this._vf.closureType.toUpperCase() == 'PIE') {

                    this._mesh._positions[0].push(0.0);
                    this._mesh._positions[0].push(0.0);
                    this._mesh._positions[0].push(0.0);

                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(1);

                    this._mesh._texCoords[0].push(0.5);
                    this._mesh._texCoords[0].push(0.5);

                    for (var i = 0; i <= anzahl; i++) {
                        var x = Math.cos(theta) * r;
                        var y = Math.sin(theta) * r;

                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);

                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(1);

                        this._mesh._texCoords[0].push((x + r) / (2 * r));
                        this._mesh._texCoords[0].push((y + r) / (2 * r));

                        theta += t;
                    }

                    for (var j = 1; j <= anzahl; j++) {
                        this._mesh._indices[0].push(j + 1);
                        this._mesh._indices[0].push(0);
                        this._mesh._indices[0].push(j);
                    }

                } else {    // "CHORD"
                    for (var i = 0; i <= anzahl; i++) {
                        var x = Math.cos(theta) * r;
                        var y = Math.sin(theta) * r;

                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);

                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(1);

                        this._mesh._texCoords[0].push((x + r) / (2 * r));
                        this._mesh._texCoords[0].push((y + r) / (2 * r));

                        theta += t;
                    }

                    var x = (this._mesh._positions[0][0] + this._mesh._positions[0][this._mesh._positions[0].length - 3]) / 2;
                    var y = (this._mesh._positions[0][1] + this._mesh._positions[0][this._mesh._positions[0].length - 2]) / 2;

                    this._mesh._positions[0].push(x);
                    this._mesh._positions[0].push(y);
                    this._mesh._positions[0].push(0.0);

                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(1);

                    this._mesh._texCoords[0].push((x + r) / (2 * r));
                    this._mesh._texCoords[0].push((y + r) / (2 * r));

                    for (var j = 0; j < anzahl; j++) {
                        this._mesh._indices[0].push(j + 1);
                        this._mesh._indices[0].push(anzahl + 1);
                        this._mesh._indices[0].push(j);
                    }
                }

                this._mesh._numTexComponents = 2;
                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 2;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                var r = this._vf.radius;
                var start = this._vf.startAngle;
                var end = this._vf.endAngle;
                var anzahl = this._vf.subdivision;

                var Pi2 = Math.PI * 2.0;
                start -= Math.floor(start / Pi2) * Pi2;
                end -= Math.floor(end / Pi2) * Pi2;
                if (end <= start)
                    end += Pi2;

                var t = (end - start) / anzahl;
                var theta = start;

                if (fieldName === "radius") {
                    this._mesh._positions[0] = [];

                    if (this._vf.closureType.toUpperCase() == 'PIE') {

                        this._mesh._positions[0].push(0.0);
                        this._mesh._positions[0].push(0.0);
                        this._mesh._positions[0].push(0.0);

                        for (var i = 0; i <= anzahl; i++) {
                            var x = Math.cos(theta) * r;
                            var y = Math.sin(theta) * r;

                            this._mesh._positions[0].push(x);
                            this._mesh._positions[0].push(y);
                            this._mesh._positions[0].push(0.0);

                            theta += t;
                        }
                    } else {
                        for (var i = 0; i <= anzahl; i++) {
                            var x = Math.cos(theta) * r;
                            var y = Math.sin(theta) * r;

                            this._mesh._positions[0].push(x);
                            this._mesh._positions[0].push(y);
                            this._mesh._positions[0].push(0.0);

                            theta += t;
                        }

                        var x = (this._mesh._positions[0][0] + this._mesh._positions[0][this._mesh._positions[0].length - 3]) / 2;
                        var y = (this._mesh._positions[0][1] + this._mesh._positions[0][this._mesh._positions[0].length - 2]) / 2;

                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);
                    }

                    this.invalidateVolume();
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });

                } else if (fieldName == "closureType" || fieldName == "subdivision" ||
                           fieldName == "startAngle" || fieldName == "endAngle") {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] = [];

                    if (this._vf.closureType.toUpperCase() == 'PIE') {

                        this._mesh._positions[0].push(0.0);
                        this._mesh._positions[0].push(0.0);
                        this._mesh._positions[0].push(0.0);

                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(1);

                        this._mesh._texCoords[0].push(0.5);
                        this._mesh._texCoords[0].push(0.5);

                        for (var i = 0; i <= anzahl; i++) {
                            var x = Math.cos(theta) * r;
                            var y = Math.sin(theta) * r;

                            this._mesh._positions[0].push(x);
                            this._mesh._positions[0].push(y);
                            this._mesh._positions[0].push(0.0);

                            this._mesh._normals[0].push(0);
                            this._mesh._normals[0].push(0);
                            this._mesh._normals[0].push(1);

                            this._mesh._texCoords[0].push((x + r) / (2 * r));
                            this._mesh._texCoords[0].push((y + r) / (2 * r));

                            theta += t;
                        }

                        for (var j = 1; j <= anzahl; j++) {
                            this._mesh._indices[0].push(j + 1);
                            this._mesh._indices[0].push(0);
                            this._mesh._indices[0].push(j);
                        }

                    } else {
                        for (var i = 0; i <= anzahl; i++) {
                            var x = Math.cos(theta) * r;
                            var y = Math.sin(theta) * r;

                            this._mesh._positions[0].push(x);
                            this._mesh._positions[0].push(y);
                            this._mesh._positions[0].push(0.0);

                            this._mesh._normals[0].push(0);
                            this._mesh._normals[0].push(0);
                            this._mesh._normals[0].push(1);

                            this._mesh._texCoords[0].push((x + r) / (2 * r));
                            this._mesh._texCoords[0].push((y + r) / (2 * r));

                            theta += t;
                        }

                        var x = (this._mesh._positions[0][0] + this._mesh._positions[0][this._mesh._positions[0].length - 3]) / 2;
                        var y = (this._mesh._positions[0][1] + this._mesh._positions[0][this._mesh._positions[0].length - 2]) / 2;

                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);

                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(1);

                        this._mesh._texCoords[0].push((x + r) / (2 * r));
                        this._mesh._texCoords[0].push((y + r) / (2 * r));

                        for (var j = 0; j < anzahl; j++) {
                            this._mesh._indices[0].push(j + 1);
                            this._mesh._indices[0].push(anzahl + 1);
                            this._mesh._indices[0].push(j);
                        }
                    }

                    this._mesh._numTexComponents = 2;
                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 2;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### Circle2D ### */
x3dom.registerNodeType(
    "Circle2D",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DPlanarGeometryNode,
        
        /**
         * Constructor for Circle2D
         * @constructs x3dom.nodeTypes.Circle2D
         * @x3d 3.3
         * @component Geometry2D
         * @status full
         * @extends x3dom.nodeTypes.X3DPlanarGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Circle2D node specifies a circle centred at (0,0) in the local 2D coordinate system.
         */
        function (ctx) {
            x3dom.nodeTypes.Circle2D.superClass.call(this, ctx);


            /**
             * The radius field specifies the radius of the Circle2D. The value of radius shall be greater than zero.
             * @var {x3dom.fields.SFFloat} radius
             * @memberof x3dom.nodeTypes.Circle2D
             * @initvalue 1
             * @range (0, inf)
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', 1);

            /**
             * Number of segments the circle is composed of
             * @var {x3dom.fields.SFFloat} subdivision
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            this._mesh._primType = 'LINES';

            var r = this._vf.radius;

            var geoCacheID = 'Circle2D_' + r;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Circle2D from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            } else {

                var anzahl = this._vf.subdivision;

                for (var i = 0; i <= anzahl; i++) {
                    var theta = i * ((2 * Math.PI) / anzahl);

                    var x = Math.cos(theta) * r;
                    var y = Math.sin(theta) * r;

                    this._mesh._positions[0].push(x);
                    this._mesh._positions[0].push(y);
                    this._mesh._positions[0].push(0.0);
                }


                for (i = 0; i < anzahl; i++) {
                    this._mesh._indices[0].push(i);
                    if ((i + 1) == anzahl) {
                        this._mesh._indices[0].push(0);
                    } else {
                        this._mesh._indices[0].push(i + 1);
                    }
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 2;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "radius" || fieldName == "subdivision") {
                    var r = this._vf.radius;
                    var anzahl = this._vf.subdivision;

                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];

                    for (var i = 0; i <= anzahl; i++) {
                        var theta = i * ((2 * Math.PI) / anzahl);

                        var x = Math.cos(theta) * r;
                        var y = Math.sin(theta) * r;

                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);
                    }

                    for (i = 0; i < anzahl; i++) {
                        this._mesh._indices[0].push(i);
                        if ((i + 1) == anzahl) {
                            this._mesh._indices[0].push(0);
                        } else {
                            this._mesh._indices[0].push(i + 1);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 2;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node._dirty.indexes = true;
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### Disk2D ### */
x3dom.registerNodeType(
    "Disk2D",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DPlanarGeometryNode,
        
        /**
         * Constructor for Disk2D
         * @constructs x3dom.nodeTypes.Disk2D
         * @x3d 3.3
         * @component Geometry2D
         * @status full
         * @extends x3dom.nodeTypes.X3DPlanarGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Disk2D node specifies a circular disk which is centred at (0, 0) in the local coordinate
         *  system. If innerRadius is equal to outerRadius, a solid circular line shall be drawn using the current line
         *  properties. If innerRadius is zero, the Disk2D is completely filled. Otherwise, the area within the
         *  innerRadius forms a hole in the Disk2D.
         */
        function (ctx) {
            x3dom.nodeTypes.Disk2D.superClass.call(this, ctx);


            /**
             * The innerRadius field specifies the inner dimension of the Disk2D. The value of innerRadius shall be
             *  greater than or equal to zero and less than or equal to outerRadius.
             * @var {x3dom.fields.SFFloat} innerRadius
             * @memberof x3dom.nodeTypes.Disk2D
             * @initvalue 0
             * @range [0, inf)
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'innerRadius', 0);

            /**
             * The outerRadius field specifies the radius of the outer dimension of the Disk2D. The value of outerRadius
             *  shall be greater than zero.
             * @var {x3dom.fields.SFFloat} outerRadius
             * @memberof x3dom.nodeTypes.Disk2D
             * @initvalue 1
             * @range [0, inf)
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'outerRadius', 1);

            /**
             * Number of segments of the disc
             * @var {x3dom.fields.SFFloat} subdivision
             * @memberof x3dom.nodeTypes.Arc2D
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            var ir = this._vf.innerRadius;
            var or = this._vf.outerRadius;

            var geoCacheID = 'Disk2D_' + ir + or;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Disk2D from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            } else {

                var anzahl = this._vf.subdivision;
                for (var i = 0; i <= anzahl; i++) {

                    var theta = i * ((2 * Math.PI) / anzahl);

                    var ox = Math.cos(theta) * or;
                    var oy = Math.sin(theta) * or;
                    var ix = Math.cos(theta) * ir;
                    var iy = Math.sin(theta) * ir;
                    this._mesh._positions[0].push(ox);
                    this._mesh._positions[0].push(oy);
                    this._mesh._positions[0].push(0.0);

                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(1);

                    this._mesh._texCoords[0].push((ox + or) / (2 * or));
                    this._mesh._texCoords[0].push((oy + or) / (2 * or));

                    this._mesh._positions[0].push(ix);
                    this._mesh._positions[0].push(iy);
                    this._mesh._positions[0].push(0.0);

                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(1);
                    this._mesh._texCoords[0].push((ix + or) / (2 * or));
                    this._mesh._texCoords[0].push((iy + or) / (2 * or));
                }

                for (i = 0; i < anzahl * 2; i = i + 2) {
                    if (i == (anzahl * 2) - 2) {
                        this._mesh._indices[0].push(i + 1);
                        this._mesh._indices[0].push(i);
                        this._mesh._indices[0].push(1);

                        this._mesh._indices[0].push(1);
                        this._mesh._indices[0].push(i);
                        this._mesh._indices[0].push(0);
                    } else {
                        this._mesh._indices[0].push(i + 1);
                        this._mesh._indices[0].push(i);
                        this._mesh._indices[0].push(i + 3);

                        this._mesh._indices[0].push(i + 3);
                        this._mesh._indices[0].push(i);
                        this._mesh._indices[0].push(i + 2);
                    }
                }

                this._mesh._numTexComponents = 2;
                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 2;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "innerRadius" || fieldName == "outerRadius" ||
                    fieldName == "subdivision") {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] = [];

                    var ir = this._vf.innerRadius;
                    var or = this._vf.outerRadius;

                    var anzahl = this._vf.subdivision;
                    for (var i = 0; i <= anzahl; i++) {

                        var theta = i * ((2 * Math.PI) / anzahl);

                        var ox = Math.cos(theta) * or;
                        var oy = Math.sin(theta) * or;
                        var ix = Math.cos(theta) * ir;
                        var iy = Math.sin(theta) * ir;
                        this._mesh._positions[0].push(ox);
                        this._mesh._positions[0].push(oy);
                        this._mesh._positions[0].push(0.0);

                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(1);

                        this._mesh._texCoords[0].push((ox + or) / (2 * or));
                        this._mesh._texCoords[0].push((oy + or) / (2 * or));

                        this._mesh._positions[0].push(ix);
                        this._mesh._positions[0].push(iy);
                        this._mesh._positions[0].push(0.0);

                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(1);
                        this._mesh._texCoords[0].push((ix + or) / (2 * or));
                        this._mesh._texCoords[0].push((iy + or) / (2 * or));
                    }

                    for (i = 0; i < anzahl * 2; i = i + 2) {
                        if (i == (anzahl * 2) - 2) {
                            this._mesh._indices[0].push(i + 1);
                            this._mesh._indices[0].push(i);
                            this._mesh._indices[0].push(1);

                            this._mesh._indices[0].push(1);
                            this._mesh._indices[0].push(i);
                            this._mesh._indices[0].push(0);
                        } else {
                            this._mesh._indices[0].push(i + 1);
                            this._mesh._indices[0].push(i);
                            this._mesh._indices[0].push(i + 3);

                            this._mesh._indices[0].push(i + 3);
                            this._mesh._indices[0].push(i);
                            this._mesh._indices[0].push(i + 2);
                        }
                    }

                    this._mesh._numTexComponents = 2;
                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### Polyline2D ### */
x3dom.registerNodeType(
    "Polyline2D",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DPlanarGeometryNode,
        
        /**
         * Constructor for Polyline2D
         * @constructs x3dom.nodeTypes.Polyline2D
         * @x3d 3.3
         * @component Geometry2D
         * @status full
         * @extends x3dom.nodeTypes.X3DPlanarGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Polyline2D node specifies a series of contiguous line segments in the local 2D coordinate
         *  system connecting the specified vertices.
         */
        function (ctx) {
            x3dom.nodeTypes.Polyline2D.superClass.call(this, ctx);


            /**
             * The lineSegments field specifies the vertices to be connected.
             * @var {x3dom.fields.MFVec2f} lineSegments
             * @memberof x3dom.nodeTypes.Polyline2D
             * @initvalue []
             * @range (-inf, inf)
             * @field x3dom
             * @instance
             */
            this.addField_MFVec2f(ctx, 'lineSegments', []);

            this._mesh._primType = 'LINES';

            var x = 0, y = 0;
            if (this._vf.lineSegments.length) {
                x = this._vf.lineSegments[0].x;
                y = this._vf.lineSegments[0].y;
            }

            var geoCacheID = 'Polyline2D_' + x + '-' + y;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Polyline2D from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else {
                for (var i = 0; i < this._vf.lineSegments.length; i++) {
                    x = this._vf.lineSegments[i].x;
                    y = this._vf.lineSegments[i].y;
                    this._mesh._positions[0].push(x);
                    this._mesh._positions[0].push(y);
                    this._mesh._positions[0].push(0.0);
                }
                for (var j = 0; j < this._vf.lineSegments.length - 1; j++) {
                    this._mesh._indices[0].push(j);
                    this._mesh._indices[0].push(j + 1);
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 2;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "lineSegments") {
                    var x, y;
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];
                    for (var i = 0; i < this._vf.lineSegments.length; i++) {
                        x = this._vf.lineSegments[i].x;
                        y = this._vf.lineSegments[i].y;
                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);
                    }
                    for (var j = 0; j < this._vf.lineSegments.length - 1; j++) {
                        this._mesh._indices[0].push(j);
                        this._mesh._indices[0].push(j + 1);
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 2;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node._dirty.indexes = true;
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### Polypoint2D ### */
x3dom.registerNodeType(
    "Polypoint2D",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DPlanarGeometryNode,
        
        /**
         * Constructor for Polypoint2D
         * @constructs x3dom.nodeTypes.Polypoint2D
         * @x3d 3.3
         * @component Geometry2D
         * @status full
         * @extends x3dom.nodeTypes.X3DPlanarGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Polyline2D node specifies a set of vertices in the local 2D coordinate system at each of which
         *  is displayed a point.
         */
        function (ctx) {
            x3dom.nodeTypes.Polypoint2D.superClass.call(this, ctx);


            /**
             * The points field specifies the vertices to be displayed.
             * @var {x3dom.fields.MFVec2f} point
             * @memberof x3dom.nodeTypes.Polypoint2D
             * @initvalue []
             * @range (-inf, inf)
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'point', []);

            this._mesh._primType = 'POINTS';

            var x = 0, y = 0;
            if (this._vf.point.length) {
                x = this._vf.point[0].x;
                y = this._vf.point[0].y;
            }

            var geoCacheID = 'Polypoint2D_' + x + '-' + y;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Polypoint2D from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else {
                for (var i = 0; i < this._vf.point.length; i++) {
                    x = this._vf.point[i].x;
                    y = this._vf.point[i].y;
                    this._mesh._positions[0].push(x);
                    this._mesh._positions[0].push(y);
                    this._mesh._positions[0].push(0.0);
                }

                this._mesh._invalidate = true;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "point") {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];
                    for (var i = 0; i < this._vf.point.length; i++) {
                        var x = this._vf.point[i].x;
                        var y = this._vf.point[i].y;
                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);
                    }

                    this.invalidateVolume();
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### Rectangle2D ### */
x3dom.registerNodeType(
    "Rectangle2D",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DPlanarGeometryNode,
        
        /**
         * Constructor for Rectangle2D
         * @constructs x3dom.nodeTypes.Rectangle2D
         * @x3d 3.3
         * @component Geometry2D
         * @status full
         * @extends x3dom.nodeTypes.X3DPlanarGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Rectangle2D node specifies a rectangle centred at (0, 0) in the current local 2D coordinate
         *  system and aligned with the local coordinate axes. By default, the box measures 2 units in each dimension,
         *  from -1 to +1.
         */
        function (ctx) {
            x3dom.nodeTypes.Rectangle2D.superClass.call(this, ctx);


            /**
             * The size field specifies the extents of the box along the X-, and Y-axes respectively and each component
             *  value shall be greater than zero.
             * @var {x3dom.fields.SFVec2f} size
             * @memberof x3dom.nodeTypes.Rectangle2D
             * @initvalue 2,2
             * @range (0, inf)
             * @field x3d
             * @instance
             */
            this.addField_SFVec2f(ctx, 'size', 2, 2);

            /**
             * Number of segments of the rectangle
             * @var {x3dom.fields.SFVec2f} subdivision
             * @memberof x3dom.nodeTypes.Rectangle2D
             * @initvalue 1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'subdivision', 1, 1);

            var sx = this._vf.size.x, sy = this._vf.size.y;
            var partx = this._vf.subdivision.x, party = this._vf.subdivision.y;

            var geoCacheID = 'Rectangle2D_' + sx + '-' + sy;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using Rectangle2D from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else {
                var xstep = sx / partx;
                var ystep = sy / party;

                sx /= 2;
                sy /= 2;

                for (var i = 0; i <= partx; i++) {
                    for (var j = 0; j <= party; j++) {
                        this._mesh._positions[0].push(i * xstep - sx, j * ystep - sy, 0);
                        this._mesh._normals[0].push(0, 0, 1);
                        this._mesh._texCoords[0].push(i / partx, j / party);
                    }
                }

                for (var i = 1; i <= party; i++) {
                    for (var j = 0; j < partx; j++) {
                        this._mesh._indices[0].push((i - 1) * (partx + 1) + j + 1);
                        this._mesh._indices[0].push((i - 1) * (partx + 1) + j);
                        this._mesh._indices[0].push(i * (partx + 1) + j);

                        this._mesh._indices[0].push((i - 1) * (partx + 1) + j + 1);
                        this._mesh._indices[0].push(i * (partx + 1) + j);
                        this._mesh._indices[0].push(i * (partx + 1) + j + 1);
                    }
                }

                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "size") {
                    this._mesh._positions[0] = [];
                    var size = this._vf.size;
                    var sx = size.x / 2;
                    var sy = size.y / 2;

                    var partx = this._vf.subdivision.x, party = this._vf.subdivision.y;

                    var xstep = sx / partx;
                    var ystep = sy / party;

                    sx /= 2;
                    sy /= 2;

                    for (var i = 0; i <= partx; i++) {
                        for (var j = 0; j <= party; j++) {
                            this._mesh._positions[0].push(i * xstep - sx, j * ystep - sy, 0);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                    });

                } else if (fieldName == "subdivision") {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] = [];

                    var sx = this._vf.size.x / 2;
                    var sy = this._vf.size.y / 2;

                    var partx = this._vf.subdivision.x, party = this._vf.subdivision.y;
                    var xstep = sx / partx;
                    var ystep = sy / party;

                    sx /= 2;
                    sy /= 2;

                    for (var i = 0; i <= partx; i++) {
                        for (var j = 0; j <= party; j++) {
                            this._mesh._positions[0].push(i * xstep - sx, j * ystep - sy, 0);
                            this._mesh._normals[0].push(0, 0, 1);
                            this._mesh._texCoords[0].push(i / partx, j / party);
                        }
                    }

                    for (var i = 1; i <= party; i++) {
                        for (var j = 0; j < partx; j++) {
                            this._mesh._indices[0].push((i - 1) * (partx + 1) + j + 1);
                            this._mesh._indices[0].push((i - 1) * (partx + 1) + j);
                            this._mesh._indices[0].push(i * (partx + 1) + j);

                            this._mesh._indices[0].push((i - 1) * (partx + 1) + j + 1);
                            this._mesh._indices[0].push(i * (partx + 1) + j);
                            this._mesh._indices[0].push(i * (partx + 1) + j + 1);
                        }
                    }

                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

/* ### TriangleSet2D ### */
x3dom.registerNodeType(
    "TriangleSet2D",
    "Geometry2D",
    defineClass(x3dom.nodeTypes.X3DPlanarGeometryNode,
        
        /**
         * Constructor for TriangleSet2D
         * @constructs x3dom.nodeTypes.TriangleSet2D
         * @x3d 3.3
         * @component Geometry2D
         * @status full
         * @extends x3dom.nodeTypes.X3DPlanarGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The TriangleSet2D node specifies a set of triangles in the local 2D coordinate system.
         */
        function (ctx) {
            x3dom.nodeTypes.TriangleSet2D.superClass.call(this, ctx);


            /**
             * The vertices field specifies the triangles to be displayed. The number of vertices provided shall be
             *  evenly divisible by three.
             * @var {x3dom.fields.MFVec2f} vertices
             * @memberof x3dom.nodeTypes.TriangleSet2D
             * @initvalue []
             * @range (-inf, inf)
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'vertices', []);

            
            var x = 0, y = 0;
            if (this._vf.vertices.length) {
                x = this._vf.vertices[0].x;
                y = this._vf.vertices[0].y;
            }

            var geoCacheID = 'TriangleSet2D_' + x + '-' + y;

            if (this._vf.useGeoCache && x3dom.geoCache[geoCacheID] !== undefined) {
                //x3dom.debug.logInfo("Using TriangleSet2D from Cache");
                this._mesh = x3dom.geoCache[geoCacheID];
            }
            else {
                var minx = 0, miny = 0, maxx = 0, maxy = 0;

                if (this._vf.vertices.length) {
                    minx = this._vf.vertices[0].x;
                    miny = this._vf.vertices[0].y;
                    maxx = this._vf.vertices[0].x;
                    maxy = this._vf.vertices[0].y;
                }

                for (var i = 0; i < this._vf.vertices.length; i++) {
                    if (this._vf.vertices[i].x < minx) {
                        minx = this._vf.vertices[i].x
                    }
                    if (this._vf.vertices[i].y < miny) {
                        miny = this._vf.vertices[i].y
                    }
                    if (this._vf.vertices[i].x > maxx) {
                        maxx = this._vf.vertices[i].x
                    }
                    if (this._vf.vertices[i].y > maxy) {
                        maxy = this._vf.vertices[i].y
                    }
                }

                for (var i = 0; i < this._vf.vertices.length; i++) {
                    x = this._vf.vertices[i].x;
                    y = this._vf.vertices[i].y;
                    this._mesh._positions[0].push(x);
                    this._mesh._positions[0].push(y);
                    this._mesh._positions[0].push(0.0);

                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(1);

                    this._mesh._texCoords[0].push((x - minx) / (maxx - minx));
                    this._mesh._texCoords[0].push((y - miny) / (maxy - miny));
                }

                for (var j = 0; j < this._vf.vertices.length; j += 3) {
                    this._mesh._indices[0].push(j);
                    this._mesh._indices[0].push(j + 2);
                    this._mesh._indices[0].push(j + 1);
                }

                this._mesh._numTexComponents = 2;
                this._mesh._invalidate = true;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;

                x3dom.geoCache[geoCacheID] = this._mesh;
            }
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "vertices") {
                    this._mesh._positions[0] = [];
                    this._mesh._indices[0] = [];
                    this._mesh._normals[0] = [];
                    this._mesh._texCoords[0] = [];

                    var minx = this._vf.vertices[0].x;
                    var miny = this._vf.vertices[0].y;
                    var maxx = this._vf.vertices[0].x;
                    var maxy = this._vf.vertices[0].y;

                    for (var i = 0; i < this._vf.vertices.length; i++) {
                        if (this._vf.vertices[i].x < minx) {
                            minx = this._vf.vertices[i].x
                        }
                        if (this._vf.vertices[i].y < miny) {
                            miny = this._vf.vertices[i].y
                        }
                        if (this._vf.vertices[i].x > maxx) {
                            maxx = this._vf.vertices[i].x
                        }
                        if (this._vf.vertices[i].y > maxy) {
                            maxy = this._vf.vertices[i].y
                        }
                    }

                    for (var i = 0; i < this._vf.vertices.length; i++) {
                        var x = this._vf.vertices[i].x;
                        var y = this._vf.vertices[i].y;
                        this._mesh._positions[0].push(x);
                        this._mesh._positions[0].push(y);
                        this._mesh._positions[0].push(0.0);

                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(0);
                        this._mesh._normals[0].push(1);

                        this._mesh._texCoords[0].push((x - minx) / (maxx - minx));
                        this._mesh._texCoords[0].push((y - miny) / (maxy - miny));
                    }

                    for (var j = 0; j < this._vf.vertices.length; j += 3) {
                        this._mesh._indices[0].push(j);
                        this._mesh._indices[0].push(j + 2);
                        this._mesh._indices[0].push(j + 1);
                    }

                    this._mesh._numTexComponents = 2;
                    this.invalidateVolume();
                    this._mesh._numFaces = this._mesh._indices[0].length / 3;
                    this._mesh._numCoords = this._mesh._positions[0].length / 3;

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                    });
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### X3DVolumeDataNode ### */
x3dom.registerNodeType(
    "X3DVolumeDataNode",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DShapeNode,   // changed inheritance!
        
        /**
         * Constructor for X3DVolumeDataNode
         * @constructs x3dom.nodeTypes.X3DVolumeDataNode
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DShapeNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc (Abstract) class for volume data. 
         */
        function (ctx) {
            x3dom.nodeTypes.X3DVolumeDataNode.superClass.call(this, ctx);


            /**
             * Specifies the size of of the bounding box for the volume data.
             * @var {x3dom.fields.SFVec3f} dimensions
             * @memberof x3dom.nodeTypes.X3DVolumeDataNode
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'dimensions', 1, 1, 1);

            /**
             * The voxels field is an ImageTextureAtlas node containing the volume data.
             * @var {x3dom.fields.SFNode} voxels
             * @memberof x3dom.nodeTypes.X3DVolumeDataNode
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('voxels', x3dom.nodeTypes.Texture);
            //this.addField_MFNode('voxels', x3dom.nodeTypes.X3DTexture3DNode);
            //this.addField_SFBool(ctx, 'swapped', false);
            //this.addField_SFVec3f(ctx, 'sliceThickness', 1, 1, 1);

            /**
             * Allow to locate the viewpoint inside the volume.
             * @var {x3dom.fields.SFBool} allowViewpointInside
             * @memberof x3dom.nodeTypes.X3DVolumeDataNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'allowViewpointInside', true)

            //Neccesary for counting the textures which are added on each style, number of textures can be variable
            this._textureID = 0;
            this._first = true;
            this._styleList = [];
            this.surfaceNormalsNeeded = false;
            this.normalTextureProvided = false;
            this.fragmentPreamble = "#ifdef GL_FRAGMENT_PRECISION_HIGH\n" +
                                    "  precision highp float;\n" +
                                    "#else\n" +
                                    "  precision mediump float;\n" +
                                    "#endif\n\n";
        },
        {
            getTextureSize: function(texture) {
                var size = { w: 0, h: 0, valid: false };
                var texBag = this._webgl ? this._webgl.texture : null;
                var t, n = (texture && texBag) ? texBag.length : 0;

                for (t=0; t<n; t++) {
                    if (texture == texBag[t].node && texBag[t].texture) {
                        size.w = texBag[t].texture.width;
                        size.h = texBag[t].texture.height;
                        if (size.w && size.h) {
                            size.valid = true;
                        }
                        break;
                    }
                }

                return size;
            },

            //Common vertex shader text for all volume data nodes
            vertexShaderText: function(needEyePosition){
                var shader = 
                "attribute vec3 position;\n"+
                "uniform vec3 dimensions;\n"+
                "uniform mat4 modelViewProjectionMatrix;\n"+
                "varying vec4 vertexPosition;\n"+
                "varying vec4 pos;\n";
                if(x3dom.nodeTypes.X3DLightNode.lightID>0 || (needEyePosition===true)){
                    shader += "uniform mat4 modelViewMatrix;\n"+
                    "varying vec4 position_eye;\n";
                }
                shader += "\n" +
                "void main()\n"+
                "{\n"+
                "  vertexPosition = modelViewProjectionMatrix * vec4(position, 1.0);\n";
                if(x3dom.nodeTypes.X3DLightNode.lightID>0 || (needEyePosition===true)){
                   shader += "  position_eye = modelViewMatrix * vec4(position, 1.0);\n";
                }
                shader += 
                "  pos = vec4((position/dimensions)+0.5, 1.0);\n"+
                "  gl_Position = vertexPosition;\n"+
                "}";
                return shader;
            },

            defaultUniformsShaderText: function(numberOfSlices, slicesOverX, slicesOverY, needEyePosition){
               var uniformsText = 
                "uniform sampler2D uVolData;\n"+
                "uniform vec3 dimensions;\n"+
                "uniform vec3 offset;\n"+
                "uniform mat4 modelViewMatrix;\n"+
                "uniform mat4 modelViewMatrixInverse;\n"+
                "varying vec4 vertexPosition;\n"+
                "varying vec4 pos;\n";
                if(x3dom.nodeTypes.X3DLightNode.lightID>0 || (needEyePosition===true)){
                    uniformsText += "varying vec4 position_eye;\n";
                }
                //LIGHTS
                for(var l=0; l<x3dom.nodeTypes.X3DLightNode.lightID; l++) {
                    uniformsText +=   "uniform float light"+l+"_On;\n" +
                    "uniform float light"+l+"_Type;\n" +
                    "uniform vec3  light"+l+"_Location;\n" +
                    "uniform vec3  light"+l+"_Direction;\n" +
                    "uniform vec3  light"+l+"_Color;\n" +
                    "uniform vec3  light"+l+"_Attenuation;\n" +
                    "uniform float light"+l+"_Radius;\n" +
                    "uniform float light"+l+"_Intensity;\n" +
                    "uniform float light"+l+"_AmbientIntensity;\n" +
                    "uniform float light"+l+"_BeamWidth;\n" +
                    "uniform float light"+l+"_CutOffAngle;\n" +
                    "uniform float light"+l+"_ShadowIntensity;\n";
                }
                uniformsText +=
                "const float Steps = 60.0;\n"+
                "const float numberOfSlices = "+ numberOfSlices.toPrecision(5)+";\n"+
                "const float slicesOverX = " + slicesOverX.toPrecision(5) +";\n"+
                "const float slicesOverY = " + slicesOverY.toPrecision(5) +";\n";
                return uniformsText;
            },

            texture3DFunctionShaderText: "vec4 cTexture3D(sampler2D vol, vec3 volpos, float nS, float nX, float nY)\n"+
                "{\n"+
                "  float s1,s2;\n"+
                "  float dx1,dy1;\n"+
                "  float dx2,dy2;\n"+
                "  vec2 texpos1,texpos2;\n"+
                "  s1 = floor(volpos.z*nS);\n"+
                "  s2 = s1+1.0;\n"+
                "  dx1 = fract(s1/nX);\n"+
                "  dy1 = floor(s1/nY)/nY;\n"+
                "  dx2 = fract(s2/nX);\n"+
                "  dy2 = floor(s2/nY)/nY;\n"+
                "  texpos1.x = dx1+(volpos.x/nX);\n"+
                "  texpos1.y = dy1+(volpos.y/nY);\n"+
                "  texpos2.x = dx2+(volpos.x/nX);\n"+
                "  texpos2.y = dy2+(volpos.y/nY);\n"+
                "  return mix( texture2D(vol,texpos1), texture2D(vol,texpos2), (volpos.z*nS)-s1);\n"+
                "}\n"+
                "\n",

            normalFunctionShaderText: function(){
                if (this.surfaceNormalsNeeded){
                    return "vec4 getNormalFromTexture(sampler2D sampler, vec3 pos, float nS, float nX, float nY) {\n"+
                    "   vec4 n = (2.0*cTexture3D(sampler, pos, nS, nX, nY)-1.0);\n"+
                    "   return vec4(normalize(n.xyz), length(n.xyz));\n"+
                    "}\n"+
                    "\n"+
                    "vec4 getNormalOnTheFly(sampler2D sampler, vec3 voxPos, float nS, float nX, float nY){\n"+
                    "   float v0 = cTexture3D(sampler, voxPos + vec3(offset.x, 0, 0), nS, nX, nY).r;\n"+
                    "   float v1 = cTexture3D(sampler, voxPos - vec3(offset.x, 0, 0), nS, nX, nY).r;\n"+
                    "   float v2 = cTexture3D(sampler, voxPos + vec3(0, offset.y, 0), nS, nX, nY).r;\n"+
                    "   float v3 = cTexture3D(sampler, voxPos - vec3(0, offset.y, 0), nS, nX, nY).r;\n"+
                    "   float v4 = cTexture3D(sampler, voxPos + vec3(0, 0, offset.z), nS, nX, nY).r;\n"+
                    "   float v5 = cTexture3D(sampler, voxPos - vec3(0, 0, offset.z), nS, nX, nY).r;\n"+
                    "   vec3 grad = vec3(v0-v1, v2-v3, v4-v5)*0.5;\n"+
                    "   return vec4(normalize(grad), length(grad));\n"+
                    "}\n"+
                    "\n";
                }else{
                    return "";
                }
            },

            defaultLoopFragmentShaderText: function(inlineShaderText, inlineLightAssigment, initializeValues){
                initializeValues = typeof initializeValues !== 'undefined' ? initializeValues : ""; //default value, empty string
                var shaderLoop = "void main()\n"+
                "{\n"+
                "  vec3 cam_pos = vec3(modelViewMatrixInverse[3][0], modelViewMatrixInverse[3][1], modelViewMatrixInverse[3][2]);\n"+
                "  vec3 cam_cube = cam_pos/dimensions+0.5;\n"+
                "  vec3 dir = normalize(pos.xyz-cam_cube);\n";
                if(this._vf.allowViewpointInside){
                    shaderLoop +=
                    "  float cam_inside = float(all(bvec2(all(lessThan(cam_cube, vec3(1.0))),all(greaterThan(cam_cube, vec3(0.0))))));\n"+
                    "  vec3 ray_pos = mix(pos.xyz, cam_cube, cam_inside);\n";
                }else{
                    shaderLoop += "  vec3 ray_pos = pos.xyz;\n";
                }
                shaderLoop +=
                "  vec4 accum  = vec4(0.0, 0.0, 0.0, 0.0);\n"+
                "  vec4 sample = vec4(0.0, 0.0, 0.0, 0.0);\n"+
                "  vec4 value  = vec4(0.0, 0.0, 0.0, 0.0);\n"+
                "  float cont = 0.0;\n"+
                "  vec3 step_size = dir/Steps;\n";
                //Light init values
                if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                    shaderLoop +=
                    "  vec3 ambient = vec3(0.0, 0.0, 0.0);\n"+
                    "  vec3 diffuse = vec3(0.0, 0.0, 0.0);\n"+
                    "  vec3 specular = vec3(0.0, 0.0, 0.0);\n"+
                    "  vec4 step_eye = modelViewMatrix * vec4(step_size, 0.0);\n"+
                    "  vec4 positionE = position_eye;\n"+
                    "  float lightFactor = 1.0;\n"; 
                }else{
                    shaderLoop += "  float lightFactor = 1.2;\n";
                }
                shaderLoop += initializeValues+
                "  float opacityFactor = 10.0;\n"+
                "  float t_near;\n"+
                "  float t_far;\n"+
                "  for(float i = 0.0; i < Steps; i+=1.0)\n"+
                "  {\n"+
                "    value = cTexture3D(uVolData, ray_pos, numberOfSlices, slicesOverX, slicesOverY);\n"+
                "    value = value.rgbr;\n";
                if(this.surfaceNormalsNeeded){
                    if(this.normalTextureProvided){
                        shaderLoop += "    vec4 gradEye = getNormalFromTexture(uSurfaceNormals, ray_pos, numberOfSlices, slicesOverX, slicesOverY);\n";
                    }else{
                        shaderLoop += "    vec4 gradEye = getNormalOnTheFly(uVolData, ray_pos, numberOfSlices, slicesOverX, slicesOverY);\n";
                    }
                    shaderLoop += "    vec4 grad = vec4((modelViewMatrix * vec4(gradEye.xyz, 0.0)).xyz, gradEye.a);\n";
                }
                shaderLoop += inlineShaderText;
                if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                    shaderLoop += inlineLightAssigment;
                }
                shaderLoop +=
                //Composite the volume sample
                "    sample.a = value.a * opacityFactor * (1.0/Steps);\n"+
                "    sample.rgb = value.rgb * sample.a * lightFactor;\n"+
                "    accum.rgb += (1.0 - accum.a) * sample.rgb;\n"+
                "    accum.a += (1.0 - accum.a) * sample.a;\n"+
                //Advance the current ray position
                "    ray_pos.xyz += step_size;\n";
                if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                    shaderLoop +="    positionE += step_eye;\n";
                }
                shaderLoop +=
                //Early ray termination and Break if the position is greater than <1, 1, 1>
                "    if(accum.a >= 1.0 || ray_pos.x < 0.0 || ray_pos.y < 0.0 || ray_pos.z < 0.0 || ray_pos.x > 1.0 || ray_pos.y > 1.0 || ray_pos.z > 1.0)\n"+
                "      break;\n"+
                "  }\n"+
                "  gl_FragColor = accum;\n"+
                "}";
                return shaderLoop;
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### X3DVolumeRenderStyleNode ### */
x3dom.registerNodeType(
    "X3DVolumeRenderStyleNode",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DNode,
        
        /**
         * Constructor for X3DVolumeRenderStyleNode
         * @constructs x3dom.nodeTypes.X3DVolumeRenderStyleNode
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc (Abstract) class for volume rendering styles.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DVolumeRenderStyleNode.superClass.call(this, ctx);


            /**
             * Specifies whether the render style is enabled or disabled.
             * @var {x3dom.fields.SFBool} enabled
             * @memberof x3dom.nodeTypes.X3DVolumeRenderStyleNode
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'enabled', true);

            //Private parameters
            this._styleID = 0; //To differentiate between style instances
            this._first = false; //To know if it is the first style being applied
            this._volumeDataParent = null; //Shortcut to the parent volume data
        },
        {
            nodeChanged: function () {
                if(!this._styleID) {
                    this._styleID = ++x3dom.nodeTypes.X3DVolumeRenderStyleNode.styleID;
                }
            },

            updateProperties: function(volumeData) { //FIXME: This property must be called before obtaining the shader pieces from the volume data node.
                //Update the first and parent volume data parameters of the child nodes
                if (this._cf.renderStyle) {
                    if (this._cf.renderStyle.nodes) {
                        for (var i=0; i<this._cf.renderStyle.nodes.length; i++){
                            if(this._cf.renderStyle.nodes[i].updateProperties != undefined){
                                this._cf.renderStyle.nodes[i].updateProperties(volumeData);
                            }
                        }
                    }else if(this._cf.renderStyle.node){
                        this._cf.renderStyle.node.updateProperties(volumeData);
                    }
                }
                
                this._volumeDataParent = volumeData;

                //Update first property, to know it is the first time the style is being applied
                if(this._volumeDataParent._styleList.indexOf(this.typeName()) != -1){
                    this._first = false;
                }else{
                    this._first = true;
                    this._volumeDataParent._styleList.push(this.typeName());
                }
            },

            initializeValues: function(){
                return ""; // overwritten
            },

            styleUniformsShaderText: function(){
                return ""; // overwritten
            },

            styleShaderText: function(){
                return ""; // overwritten
            },

            inlineStyleShaderText: function(){
                return ""; // overwritten
            },

            lightAssigment: function(){
                var shaderText = "";
                for(var l=0; l<x3dom.nodeTypes.X3DLightNode.lightID; l++) {
                    shaderText += "    lighting(light"+l+"_Type, " +
                    "light"+l+"_Location, " +
                    "light"+l+"_Direction, " +
                    "light"+l+"_Color, " + 
                    "light"+l+"_Attenuation, " +
                    "light"+l+"_Radius, " +
                    "light"+l+"_Intensity, " + 
                    "light"+l+"_AmbientIntensity, " +
                    "light"+l+"_BeamWidth, " +
                    "light"+l+"_CutOffAngle, " +
                    "grad.xyz, positionE.xyz, ambient, diffuse, specular);\n";
                }
                shaderText += "    value.rgb = ambient*value.rgb + diffuse*value.rgb + specular;\n"; // overwritten
                return shaderText;
            },

            // default light equation to be overwritten by concrete render style
            lightEquationShaderText: function(){
                if (x3dom.nodeTypes.X3DLightNode.lightID>0){
                    return "void lighting(in float lType, in vec3 lLocation, in vec3 lDirection, in vec3 lColor, in vec3 lAttenuation, " + 
                    "in float lRadius, in float lIntensity, in float lAmbientIntensity, in float lBeamWidth, " +
                    "in float lCutOffAngle, in vec3 N, in vec3 V, inout vec3 ambient, inout vec3 diffuse, " +
                    "inout vec3 specular)\n" +
                    "{\n" +
                    "   vec3 L;\n" +
                    "   float spot = 1.0, attentuation = 0.0;\n" +
                    "   if(lType == 0.0) {\n" +
                    "       L = -normalize(lDirection);\n" +
                    "       V = normalize(V);\n" +
                    "       attentuation = 1.0;\n" +
                    "   } else{\n" +
                    "       L = (lLocation - (-V));\n" +
                    "       float d = length(L);\n" +
                    "       L = normalize(L);\n" +
                    "       V = normalize(V);\n" +
                    "       if(lRadius == 0.0 || d <= lRadius) {\n" +
                    "           attentuation = 1.0 / max(lAttenuation.x + lAttenuation.y * d + lAttenuation.z * (d * d), 1.0);\n" +
                    "       }\n" +
                    "       if(lType == 2.0) {\n" +
                    "           float spotAngle = acos(max(0.0, dot(-L, normalize(lDirection))));\n" +
                    "           if(spotAngle >= lCutOffAngle) spot = 0.0;\n" +
                    "           else if(spotAngle <= lBeamWidth) spot = 1.0;\n" +
                    "           else spot = (spotAngle - lCutOffAngle ) / (lBeamWidth - lCutOffAngle);\n" +
                    "       }\n" +
                    "   }\n" +
                    "   vec3  H = normalize( L + V );\n" +
                    "   float NdotL = max(0.0, dot(L, N));\n" +
                    "   float NdotH = max(0.0, dot(H, N));\n" +   
                    "   float ambientFactor  = lAmbientIntensity;\n" +
                    "   float diffuseFactor  = lIntensity * NdotL;\n" +
                    "   float specularFactor = lIntensity * pow(NdotH,128.0);\n" +
                    "   ambient  += lColor * ambientFactor * attentuation * spot;\n" +
                    "   diffuse  += lColor * diffuseFactor * attentuation * spot;\n" +
                    "   specular += lColor * specularFactor * attentuation * spot;\n" +  
                    "}\n"+
                    "\n";
                }else{
                    return "";
                }
            }
        }
    )
);

/** Static class ID counter (needed to allow duplicate styles) */
x3dom.nodeTypes.X3DVolumeRenderStyleNode.styleID = 0;

/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### X3DComposableVolumeRenderStyleNode ### */
x3dom.registerNodeType(
    "X3DComposableVolumeRenderStyleNode",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DVolumeRenderStyleNode,
        
        /**
         * Constructor for X3DComposableVolumeRenderStyleNode
         * @constructs x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc (Abstract) class for composable volume rendering styles.
         */
        function (ctx) {
            x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode.superClass.call(this, ctx);


            /**
             * The surfaceNormals field allows to provide the normals of the volume data. It takes an ImageTextureAtlas of the same dimensions of the volume data. If it is not provided, it is computed on the fly.
             * @var {x3dom.fields.SFNode} surfaceNormals
             * @memberof x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('surfaceNormals', x3dom.nodeTypes.Texture);
        
        },
        {
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### BlendedVolumeStyle ### */
x3dom.registerNodeType(
    "BlendedVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for BlendedVolumeStyle
         * @constructs x3dom.nodeTypes.BlendedVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The BlendedVolumeStyle node allows to blend the parent volume data with a second specified volume data using a weight function. 
         */
        function (ctx) {
            x3dom.nodeTypes.BlendedVolumeStyle.superClass.call(this, ctx);


            /**
             * Specifies the render style to be applied on the volume data to be blended.
             * @var {x3dom.fields.SFNode} renderStyle
             * @memberof x3dom.nodeTypes.BlendedVolumeStyle
             * @initvalue x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('renderStyle', x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode);

            /**
             * The voxels field is an ImageTextureAtlas node containing the volume data to be blended.
             * @var {x3dom.fields.SFNode} voxels
             * @memberof x3dom.nodeTypes.BlendedVolumeStyle
             * @initvalue x3dom.nodeTypes.X3DVolumeDataNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('voxels', x3dom.nodeTypes.X3DVolumeDataNode);

            /**
             * The weightConstant1 field specifies a constant weight value to be use on the parent volume data.
             * @var {x3dom.fields.SFFloat} weightConstant1
             * @memberof x3dom.nodeTypes.BlendedVolumeStyle
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'weightConstant1', 0.5);

            /**
             * The weightConstant2 field specifies a constant weight value to be use on the volume data to be blended.
             * @var {x3dom.fields.SFFloat} weightConstant2
             * @memberof x3dom.nodeTypes.BlendedVolumeStyle
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'weightConstant2', 0.5);

            /**
             * The weightFunction1 field specifies the type of the weight function to be use on the parent volume data.
             * @var {x3dom.fields.SFString} weightFunction1
             * @memberof x3dom.nodeTypes.BlendedVolumeStyle
             * @initvalue "CONSTANT"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'weightFunction1', "CONSTANT");

            /**
             * The weightFunction2 field specifies the type of weight function to be use on the volume data to be blended.
             * @var {x3dom.fields.SFString} weightFunction2
             * @memberof x3dom.nodeTypes.BlendedVolumeStyle
             * @initvalue "CONSTANT"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'weightFunction2', "CONSTANT");

            /**
             * The weightTransferFunction1 field is a 2D texture that maps each opacity value to a weight value, that will be used on the parent volume data.
             * @var {x3dom.fields.SFNode} weightTransferFunction1
             * @memberof x3dom.nodeTypes.BlendedVolumeStyle
             * @initvalue x3dom.nodeTypes.X3DTexture2DNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('weightTransferFunction1', x3dom.nodeTypes.X3DTexture2DNode);

            /**
             * The weightTransferFunction2 field is a 2D texture that maps each opacity value to a weight value, that will be used on the volume data to be blended.
             * @var {x3dom.fields.SFNode} weightTransferFunction2
             * @memberof x3dom.nodeTypes.BlendedVolumeStyle
             * @initvalue x3dom.nodeTypes.X3DTexture2DNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('weightTransferFunction2', x3dom.nodeTypes.X3DTexture2DNode);

            this.uniformFloatWeightConstant1 = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatWeightConstant2 = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DVoxels = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DWeightTransferFunction1 = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DWeightTransferFunction2 = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'weightConstant1':
                        this.uniformFloatWeightConstant1._vf.value = this._vf.weightConstant1;
                        this.uniformFloatWeightConstant1.fieldChanged("value");
                        break;
                    case 'weightConstant2':
                        this.uniformFloatWeightConstant2._vf.value = this._vf.weightConstant2;
                        this.uniformFloatWeightConstant2.fieldChanged("value");
                        break;
                    case 'weightFunction1':
                        //TODO: Reload node
                        break;
                    case 'weightFunction2':
                        //TODO: Reload node
                        break;
                }
            },

            uniforms: function(){
                var unis = [];
                if (this._cf.voxels.node || this._cf.weightTransferFunction1.node || this._cf.weightTransferFunction2.node) {
                    this.uniformSampler2DVoxels._vf.name = 'uVolBlendData';
                    this.uniformSampler2DVoxels._vf.type = 'SFInt32';
                    this.uniformSampler2DVoxels._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DVoxels);

                    if(this._cf.weightTransferFunction1.node){
                        this.uniformSampler2DWeightTransferFunction1._vf.name = 'uWeightTransferFunctionA';
                        this.uniformSampler2DWeightTransferFunction1._vf.type = 'SFInt32';
                        this.uniformSampler2DWeightTransferFunction1._vf.value = this._volumeDataParent._textureID++;
                        unis.push(this.uniformSampler2DWeightTransferFunction1);
                    }

                    if(this._cf.weightTransferFunction2.node){
                        this.uniformSampler2DWeightTransferFunction2._vf.name = 'uWeightTransferFunctionB';
                        this.uniformSampler2DWeightTransferFunction2._vf.type = 'SFInt32';
                        this.uniformSampler2DWeightTransferFunction2._vf.value = this._volumeDataParent._textureID++;
                        unis.push(this.uniformSampler2DWeightTransferFunction2);
                    }
                }

                this.uniformFloatWeightConstant1._vf.name = 'uWeightConstantA';
                this.uniformFloatWeightConstant1._vf.type = 'SFFloat';
                this.uniformFloatWeightConstant1._vf.value = this._vf.weightConstant1;
                unis.push(this.uniformFloatWeightConstant1);

                this.uniformFloatWeightConstant2._vf.name = 'uWeightConstantB';
                this.uniformFloatWeightConstant2._vf.type = 'SFFloat';
                this.uniformFloatWeightConstant2._vf.value = this._vf.weightConstant2;
                unis.push(this.uniformFloatWeightConstant2);

                //Also add the render style uniforms
                if (this._cf.renderStyle.node) {
                    var renderStyleUniforms = this._cf.renderStyle.node.uniforms();
                    Array.forEach(renderStyleUniforms, function(uni){
                        uni._vf.name = uni._vf.name.replace(/uSurfaceNormals/, "uBlendSurfaceNormals")
                    });
                    unis = unis.concat(renderStyleUniforms);       
                }
                return unis;
            },

            textures: function(){
                var texs = [];
                if (this._cf.voxels.node) {
                    var tex = this._cf.voxels.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }
                if (this._cf.weightTransferFunction1.node) {
                    var tex = this._cf.weightTransferFunction1.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }
                if (this._cf.weightTransferFunction2.node) {
                    var tex = this._cf.weightTransferFunction2.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }
                //Also add the render style textures
                if (this._cf.renderStyle.node) {
                    var renderStyleTextures = this._cf.renderStyle.node.textures();
                    texs = texs.concat(renderStyleTextures);       
                }
                return texs;
            },

            initializeValues: function(){
                var initialValues = "";
                if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                    initialValues += "  vec3 ambientBlend = vec3(0.0, 0.0, 0.0);\n"+
                    "  vec3 diffuseBlend = vec3(0.0, 0.0, 0.0);\n"+
                    "  vec3 specularBlend = vec3(0.0, 0.0, 0.0);\n";
                }
                return initialValues;
            },

            styleUniformsShaderText: function(){
                var uniformsText = "uniform float uWeightConstantA;\n"+
                    "uniform float uWeightConstantB;\n"+
                    "uniform sampler2D uBlendSurfaceNormals;\n";
                    if(this._cf.voxels.node){
                        uniformsText += "uniform sampler2D uVolBlendData;\n";
                    }
                    if(this._cf.weightTransferFunction1.node){
                        uniformsText += "uniform sampler2D uWeightTransferFunctionA;\n";
                    }
                    if(this._cf.weightTransferFunction2.node){
                        uniformsText += "uniform sampler2D uWeightTransferFunctionB;\n";
                    }
                    //Also add the render style uniforms
                    if(this._cf.renderStyle.node) {
                        uniformsText += this._cf.renderStyle.node.styleUniformsShaderText();
                    }
                return uniformsText;
            },

            styleShaderText: function(){
                var styleText = "";
                if(this._cf.renderStyle.node && this._cf.renderStyle.node.styleShaderText!=undefined) {
                    styleText += this._cf.renderStyle.node.styleShaderText();
                }
                return styleText;
            },

            inlineStyleShaderText: function(){
                var nSlices = this._cf.voxels.node._vf.numberOfSlices.toPrecision(5);
                var xSlices = this._cf.voxels.node._vf.slicesOverX.toPrecision(5);
                var ySlices = this._cf.voxels.node._vf.slicesOverY.toPrecision(5);
                var inlineText = "    vec4 blendValue = cTexture3D(uVolBlendData, ray_pos, "+ nSlices +", "+ xSlices +", "+ ySlices +");\n"+
                "    blendValue = vec4(blendValue.rgb,(0.299*blendValue.r)+(0.587*blendValue.g)+(0.114*blendValue.b));\n";
                if(this._cf.renderStyle.node && this._cf.renderStyle.node._cf.surfaceNormals.node){
                    inlineText += "    vec4 blendGradEye = getNormalFromTexture(uBlendSurfaceNormals, ray_pos, "+ nSlices +", "+ xSlices +", "+ ySlices +");\n";
                }else{
                    inlineText += "    vec4 blendGradEye = getNormalOnTheFly(uVolBlendData, ray_pos, "+ nSlices +", "+ xSlices +", "+ ySlices +");\n";
                }
                inlineText += "    vec4 blendGrad = vec4((modelViewMatrix * vec4(blendGradEye.xyz, 0.0)).xyz, blendGradEye.a);\n";
                if(this._cf.renderStyle.node){
                    var tempText = this._cf.renderStyle.node.inlineStyleShaderText().replace(/value/gm, "blendValue").replace(/grad/gm, "blendGrad");
                    inlineText += tempText.replace(/ambient/gm, "ambientBlend").replace(/diffuse/gm, "diffuseBlend").replace(/specular/gm, "specularBlend");
                }
                //obtain the first weight
                switch(this._vf.weightFunction1.toUpperCase()){
                    case "CONSTANT":
                        inlineText += "    float wA = uWeightConstantA;\n";
                        break;
                    case "ALPHA0":
                        inlineText += "    float wA = value.a;\n";
                        break;
                    case "ALPHA1":
                        inlineText += "    float wA = blendValue.a;\n";
                        break;
                    case "ONE_MINUS_ALPHA0":
                        inlineText += "    float wA = 1.0 - value.a;\n";
                        break;
                    case "ONE_MINUS_ALPHA1":
                        inlineText += "    float wA = 1.0 - blendValue.a;\n";
                        break;
                    case "TABLE":
                        if(this._cf.weightTransferFunction1){
                            inlineText += "    float wA = texture2D(uWeightTransferFunctionA, vec2(value.a, blendValue.a));\n";
                        }else{
                            inlineText += "    float wA = value.a;\n";
                            x3dom.debug.logWarning('[VolumeRendering][BlendedVolumeStyle] TABLE specified on weightFunction1 but not weightTrnafer function provided, using ALPHA0.');
                        }
                        break;
                }
                //obtain the second weight
                switch(this._vf.weightFunction2.toUpperCase()){
                    case "CONSTANT":
                        inlineText += "    float wB = uWeightConstantB;\n";
                        break;
                    case "ALPHA0":
                        inlineText += "    float wB = value.a;\n";
                        break;
                    case "ALPHA1":
                        inlineText += "    float wB = blendValue.a;\n";
                        break;
                    case "ONE_MINUS_ALPHA0":
                        inlineText += "    float wB = 1.0 - value.a;\n";
                        break;
                    case "ONE_MINUS_ALPHA1":
                        inlineText += "    float wB = 1.0 - blendValue.a;\n";
                        break;
                    case "TABLE":
                        if(this._cf.weightTransferFunction2){
                            inlineText += "    float wB = texture2D(uWeightTransferFunctionB, vec2(value.a, blendValue.a));\n";
                        }else{
                            inlineText += "    float wB = value.a;\n";
                            x3dom.debug.logWarning('[VolumeRendering][BlendedVolumeStyle] TABLE specified on weightFunction2 but not weightTrasnferFunction provided, using ALPHA0.');
                        }
                        break;
                }
                if(x3dom.nodeTypes.X3DLightNode.lightID == 0){
                    inlineText += "    value = clamp(value * wA + blendValue * wB, 0.0, 1.0);\n";
                }
                return inlineText;
            },

            lightAssigment: function(){
                var inlineText = "";
                if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                    if(this._cf.renderStyle.node){
                        var tempText = this._cf.renderStyle.node.lightAssigment().replace(/value/gm, "blendValue").replace(/grad/gm, "blendGrad");
                        inlineText += tempText.replace(/ambient/gm, "ambientBlend").replace(/diffuse/gm, "diffuseBlend").replace(/specular/gm, "specularBlend");
                    }else{
                        for(var l=0; l<x3dom.nodeTypes.X3DLightNode.lightID; l++) {
                            inlineText += "    lighting(light"+l+"_Type, " +
                            "light"+l+"_Location, " +
                            "light"+l+"_Direction, " +
                            "light"+l+"_Color, " + 
                            "light"+l+"_Attenuation, " +
                            "light"+l+"_Radius, " +
                            "light"+l+"_Intensity, " + 
                            "light"+l+"_AmbientIntensity, " +
                            "light"+l+"_BeamWidth, " +
                            "light"+l+"_CutOffAngle, " +
                            "blendGradEye.xyz, -positionE.xyz, ambientBlend, diffuseBlend, specularBlend);\n";
                        }
                        inlineText += "    blendValue.rgb = ambientBlend*blendValue.rgb + diffuseBlend*blendValue.rgb + specularBlend;\n";
                    }
                }
                inlineText += "    value.rgb = clamp(value.rgb * wA + blendValue.rgb * wB, 0.0, 1.0);\n"+
                "    value.a = clamp(value.a * wA + blendValue.a * wB, 0.0, 1.0);\n";
                return inlineText; //previously computed, empty string
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */
/* ### BoundaryEnhancementVolumeStyle ### */
x3dom.registerNodeType(
    "BoundaryEnhancementVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for BoundaryEnhancementVolumeStyle
         * @constructs x3dom.nodeTypes.BoundaryEnhancementVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The BoundaryEnhancementVolumeStyle node specifies that the boundaries of the volume data shall be enhanced. The rendering is performed based on the gradient magnitude.
         * Areas where density varies are made more visible than areas of constant density.
         */
        function (ctx) {
            x3dom.nodeTypes.BoundaryEnhancementVolumeStyle.superClass.call(this, ctx);


            /**
             * The retainedOpacity field specifies the amount of original opacity to retain.
             * @var {x3dom.fields.SFFloat} retainedOpacity
             * @memberof x3dom.nodeTypes.BoundaryEnhancementVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'retainedOpacity', 1);

            /**
             * The boundaryOpacity field specifies the amount of boundary enhancement to use.
             * @var {x3dom.fields.SFFloat} boundaryOpacity
             * @memberof x3dom.nodeTypes.BoundaryEnhancementVolumeStyle
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'boundaryOpacity', 0);

            /**
             * The opacityFactor field is an exponent factor that specifies the slope of the opacity curve to highlight the boundary.
             * @var {x3dom.fields.SFFloat} opacityFactor
             * @memberof x3dom.nodeTypes.BoundaryEnhancementVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'opacityFactor', 1);

            this.uniformFloatRetainedOpacity = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatBoundaryOpacity = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatOpacityFactor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DSurfaceNormals = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformBoolEnableBoundary = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'retainedOpacity':
                        this.uniformFloatRetainedOpacity._vf.value = this._vf.retainedOpacity;
                        this.uniformFloatRetainedOpacity.fieldChanged("value");
                        break;
                    case 'boundaryOpacity':
                        this.uniformFloatBoundaryOpacity._vf.value = this._vf.boundaryOpacity;
                        this.uniformFloatBoundaryOpacity.fieldChanged("value");
                        break;
                    case 'opacityFactor':
                        this.uniformFloatOpacityFactor._vf.value = this._vf.opacityFactor;
                        this.uniformFloatOpacityFactor.fieldChanged("value");
                        break;
                }
            },

            uniforms: function(){
                var unis = [];
                if (this._cf.surfaceNormals.node) {
                    //Lookup for the parent VolumeData
                    var volumeDataParent = this._parentNodes[0];
                    while(!x3dom.isa(volumeDataParent, x3dom.nodeTypes.X3DVolumeDataNode) || !x3dom.isa(volumeDataParent, x3dom.nodeTypes.X3DNode)){
                        volumeDataParent = volumeDataParent._parentNodes[0];
                    }
                    if(x3dom.isa(volumeDataParent, x3dom.nodeTypes.X3DVolumeDataNode) == false){
                        x3dom.debug.logError("[VolumeRendering][BoundaryEnhancementVolumeStyle] Not VolumeData parent found!");
                        volumeDataParent = null;
                    }
                    this.uniformSampler2DSurfaceNormals._vf.name = 'uSurfaceNormals';
                    this.uniformSampler2DSurfaceNormals._vf.type = 'SFInt32';
                    this.uniformSampler2DSurfaceNormals._vf.value = volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DSurfaceNormals);
                }

                this.uniformFloatRetainedOpacity._vf.name = 'uRetainedOpacity'+this._styleID;
                this.uniformFloatRetainedOpacity._vf.type = 'SFFloat';
                this.uniformFloatRetainedOpacity._vf.value = this._vf.retainedOpacity;
                unis.push(this.uniformFloatRetainedOpacity);

                this.uniformFloatBoundaryOpacity._vf.name = 'uBoundaryOpacity'+this._styleID;
                this.uniformFloatBoundaryOpacity._vf.type = 'SFFloat';
                this.uniformFloatBoundaryOpacity._vf.value = this._vf.boundaryOpacity;
                unis.push(this.uniformFloatBoundaryOpacity);

                this.uniformFloatOpacityFactor._vf.name = 'uOpacityFactor'+this._styleID;
                this.uniformFloatOpacityFactor._vf.type = 'SFFloat';
                this.uniformFloatOpacityFactor._vf.value = this._vf.opacityFactor;
                unis.push(this.uniformFloatOpacityFactor);

                this.uniformBoolEnableBoundary._vf.name = 'uEnableBoundary'+this._styleID;
                this.uniformBoolEnableBoundary._vf.type = 'SFBool';
                this.uniformBoolEnableBoundary._vf.value = this._vf.enabled;
                unis.push(this.uniformBoolEnableBoundary);
                return unis;
            },

            textures: function() {
                var texs = [];
                if (!(this._cf.surfaceNormals.node==null)) {
                    var tex = this._cf.surfaceNormals.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex)
                }
                return texs;
            },

            styleUniformsShaderText: function(){
                return "uniform float uRetainedOpacity"+this._styleID+";\n"+
                    "uniform float uBoundaryOpacity"+this._styleID+";\n"+
                    "uniform float uOpacityFactor"+this._styleID+";\n"+
                    "uniform bool uEnableBoundary"+this._styleID+";\n";
            },

            styleShaderText: function(){
                if (this._first){
                    return "void boundaryEnhancement(inout vec4 original_color, in float gradientMagnitude, in float retainedOpacity, in float boundaryOpacity, in float opacityFactor){\n"+
                    "   original_color.a = original_color.a * (retainedOpacity + (boundaryOpacity * pow(gradientMagnitude, opacityFactor)));\n"+
                    "}\n";
                }else{
                    return "";
                }
            },

            inlineStyleShaderText: function(){
                var inlineText = "    if(uEnableBoundary"+this._styleID+"){\n"+
                "    boundaryEnhancement(value, grad.w, uRetainedOpacity"+this._styleID+", uBoundaryOpacity"+this._styleID+", uOpacityFactor"+this._styleID+");\n"+
                "}\n";
                return inlineText;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### CartoonVolumeStyle ### */
x3dom.registerNodeType(
    "CartoonVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for CartoonVolumeStyle
         * @constructs x3dom.nodeTypes.CartoonVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The CartoonVolumeStyle node specifies that the associated volume data shall be rendered with a cartoon style non-photorealistic rendering.
         * The cartoon styles uses two colors the rendering will depend on the local surface normals and the view direction.
         */
        function (ctx) {
            x3dom.nodeTypes.CartoonVolumeStyle.superClass.call(this, ctx);


            /**
             * The parallelColor field specifies the color to be used when the surface normal is parallel to the view direction.
             * @var {x3dom.fields.SFColor} parallelColor
             * @memberof x3dom.nodeTypes.CartoonVolumeStyle
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'parallelColor', 0, 0, 0);

            /**
             * The orthogonalColor field specifies the color to be used when the surface normal is perpendicular to the view direction.
             * @var {x3dom.fields.SFColor} orthogonalColor
             * @memberof x3dom.nodeTypes.CartoonVolumeStyle
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'orthogonalColor', 1, 1, 1);

            /**
             * The colorSteps field specifies how many distinct colors are taken from the interpolated colors and used to render the object.
             * @var {x3dom.fields.SFInt32} colorSteps
             * @memberof x3dom.nodeTypes.CartoonVolumeStyle
             * @initvalue 4
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'colorSteps', 4);

            this.uniformParallelColor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformOrthogonalColor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformIntColorSteps = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DSurfaceNormals = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformBoolEnableCartoon = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'parallelColor':
                        this.uniformParallelColor._vf.value = this._vf.parallelColor;
                        this.uniformParallelColor.fieldChanged("value");
                        break;
                    case 'orthogonalColor':
                        this.uniformOrthogonalColor._vf.value = this._vf.orthogonalColor;
                        this.uniformOrthogonalColor.fieldChanged("value");
                        break;
                    case 'colorSteps':
                        this.uniformIntColorSteps._vf.value = this._vf.colorSteps;
                        this.uniformIntColorSteps.fieldChanged("value");
                        break;
                }
            },

            uniforms: function(){
                var unis = [];

                if (this._cf.surfaceNormals.node) {
                    this.uniformSampler2DSurfaceNormals._vf.name = 'uSurfaceNormals';
                    this.uniformSampler2DSurfaceNormals._vf.type = 'SFInt32';
                    this.uniformSampler2DSurfaceNormals._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DSurfaceNormals);
                }

                this.uniformParallelColor._vf.name = 'uParallelColor'+this._styleID;
                this.uniformParallelColor._vf.type = 'SFColor';
                this.uniformParallelColor._vf.value = this._vf.parallelColor;
                unis.push(this.uniformParallelColor);

                this.uniformOrthogonalColor._vf.name = 'uOrthogonalColor'+this._styleID;
                this.uniformOrthogonalColor._vf.type = 'SFColor';
                this.uniformOrthogonalColor._vf.value = this._vf.orthogonalColor;
                unis.push(this.uniformOrthogonalColor);

                this.uniformIntColorSteps._vf.name = 'uColorSteps'+this._styleID;
                this.uniformIntColorSteps._vf.type = 'SFInt32';
                this.uniformIntColorSteps._vf.value = this._vf.colorSteps;
                unis.push(this.uniformIntColorSteps);

                this.uniformBoolEnableCartoon._vf.name = 'uEnableCartoon'+this._styleID;
                this.uniformBoolEnableCartoon._vf.type = 'SFBool';
                this.uniformBoolEnableCartoon._vf.value = this._vf.enabled;
                unis.push(this.uniformBoolEnableCartoon);

                return unis;
            },

            textures: function() {
                var texs = [];
                if (this._cf.surfaceNormals.node) {
                    var tex = this._cf.surfaceNormals.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }
                return texs;
            },

            styleShaderText: function(){
                if (this._first){
                    return "//Convert RGBA color to HSVA\n"+
                    "vec4 rgba2hsva(vec4 rgba){\n"+
                    "   float zat, izen;\n"+
                    "   float R = rgba.r, G = rgba.g, B = rgba.b;\n"+
                    "   float minim = min(R, min(G, B)), maxim = max(R, max(G, B));\n"+
                    "   float delta = maxim-minim;\n"+
                    "   if(minim == maxim){\n"+
                    "       return vec4(0.0, 0.0, maxim, rgba.a);\n"+
                    "   }else{\n"+
                    "       zat = (R == maxim) ? G - B : ((G == maxim) ? B - R : R - G);\n"+ 
                    "       izen = (R == maxim) ? ((G<B) ? 6.0 : 0.0) : ((G == maxim) ? 2.0 : 4.0);\n"+ 
                    "        return vec4((zat/delta + izen)/6.0, delta/maxim, maxim, rgba.a);\n"+ 
                    "    }\n"+
                    "}\n"+
                    "\n"+
                    "//Convert RGB color to HSV\n"+
                    "vec3 rgb2hsv(vec3 rgb){\n"+
                    "    return rgba2hsva(vec4(rgb, 1.0)).rgb;\n"+
                    "}\n"+
                    "\n"+
                    "//Convert HSVA color to RGBA\n"+
                    "vec4 hsva2rgba(vec4 hsva){\n"+
                    "   float r, g, b;\n"+
                    "   float h=hsva.x, s=hsva.y, v=hsva.z;\n"+
                    "   float i = floor(h * 6.0);\n"+
                    "   float f = h * 6.0 - i;\n"+
                    "   float p = v * (1.0 - s);\n"+
                    "   float q = v * (1.0 - f * s);\n"+
                    "   float t = v * (1.0 - (1.0 - f) * s);\n"+
                    "   i = mod(i,6.0);\n"+
                    "   if( i == 6.0 || i == 0.0 ) r = v, g = t, b = p;\n"+
                    "   else if( i == 1.0) r = q, g = v, b = p;\n"+
                    "   else if( i == 2.0) r = p, g = v, b = t;\n"+
                    "   else if( i == 3.0) r = p, g = q, b = v;\n"+
                    "   else if( i == 4.0) r = t, g = p, b = v;\n"+
                    "   else if( i == 5.0) r = v, g = p, b = q;\n"+
                    "   return vec4(r,g,b,hsva.w);\n"+
                    "}\n"+
                    "\n"+
                    "//Convert HSV color to RGB\n"+
                    "vec3 hsv2rgb(vec3 hsv){\n"+
                    "   return hsva2rgba(vec4(hsv, 1.0)).rgb;\n"+
                    "}\n"+
                    "void getCartoonStyle(inout vec4 outputColor, vec3 orthogonalColor, vec3 parallelColor, int colorSteps, vec4 surfNormal, vec3 V)\n"+
                    "{\n"+
                    "   float steps = clamp(float(colorSteps), 1.0,64.0);\n"+
                    "   float range_size = pi_half / steps;\n"+
                    "   float cos_angle = abs(dot(surfNormal.xyz, V));\n"+
                    "   float interval = clamp(floor(cos_angle / range_size),0.0,steps);\n"+
                    "   float ang = interval * range_size;\n"+
                    "   outputColor.rgb = hsv2rgb(mix(orthogonalColor, parallelColor, ang));\n"+
                    "}\n"+
                    "\n";
                }else{
                    return "";
                }
            },

            styleUniformsShaderText: function(){
                var styleText = "uniform vec3 uParallelColor"+this._styleID+";\n"+
                "uniform vec3 uOrthogonalColor"+this._styleID+";\n"+
                "uniform int uColorSteps"+this._styleID+";\n"+
                "uniform bool uEnableCartoon"+this._styleID+";\n";
                if (this._first) {
                    styleText += "const float pi_half = "+ (Math.PI/2.0).toPrecision(5) +";\n";
                }
                return styleText;
            },

            inlineStyleShaderText: function(){
                var inlineText = "  if(uEnableCartoon"+this._styleID+"){\n"+
                "      getCartoonStyle(value, rgb2hsv(uOrthogonalColor"+this._styleID+"), rgb2hsv(uParallelColor"+this._styleID+"), uColorSteps"+this._styleID+", gradEye, dir);\n"+
                "  }\n";   
                return inlineText;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### ComposedVolumeStyle ### */
x3dom.registerNodeType(
    "ComposedVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for ComposedVolumeStyle
         * @constructs x3dom.nodeTypes.ComposedVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ComposedVolumeStyle node is used to compose multiple rendering styles into a single-rendering pass.
         * The styles are applied in the same order they are defined on the scene tree.
         */
        function (ctx) {
            x3dom.nodeTypes.ComposedVolumeStyle.superClass.call(this, ctx);


            /**
             * 
             * @var {x3dom.fields.SFBool} ordered
             * @memberof x3dom.nodeTypes.ComposedVolumeStyle
             * @initvalue false
             * @field x3dom
             * @instance
             */
            //this.addField_SFBool(ctx, 'ordered', false);

            /**
             * The renderStyle field contains a list of composable render styles nodes to be used on the associated volume data.
             * @var {x3dom.fields.MFNode} renderStyle
             * @memberof x3dom.nodeTypes.ComposedVolumeStyle
             * @initvalue x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('renderStyle', x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode);
            //Using only one normal texture
            this.normalTextureProvided = false;
        
        },
        {
            uniforms: function(){
                var unis = [];
                var i, n = this._cf.renderStyle.nodes.length;
                for (i=0; i<n; i++){
                    //Not repeat common uniforms, TODO: Allow multiple surface normals
                    var that = this;
                    Array.forEach(this._cf.renderStyle.nodes[i].uniforms(), function(uniform){
                        var contains_uniform = false;
                        Array.forEach(unis, function(accum){
                            if(accum._vf.name == uniform._vf.name){
                                contains_uniform = true;
                            }
                        });
                        if (contains_uniform == false){
                            unis = unis.concat(uniform);
                        }
                    });
                }
                return unis;
            },

            textures: function() {
                var texs = [];
                var i, n = this._cf.renderStyle.nodes.length;
                for (i=0; i<n; i++){
                    //Not repeat same textures, TODO: Allow multiply surface normals textures
                    Array.forEach(this._cf.renderStyle.nodes[i].textures(), function(texture){
                        var contains_texture = false;
                        Array.forEach(texs, function(accum){
                            if(accum._vf.url[0] == texture._vf.url[0]){
                                contains_texture = true;
                            }
                        });
                        if (contains_texture == false){
                            texs = texs.concat(texture);
                        }
                    });
                   
                }
                return texs;
            },

            initializeValues: function() {
                var initialValues ="";
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    if(this._cf.renderStyle.nodes[i].initializeValues != undefined){
                        initialValues += this._cf.renderStyle.nodes[i].initializeValues() + "\n";
                    }
                }
                return initialValues;
            },

            styleUniformsShaderText: function(){
                var styleText = "";
                var n = this._cf.renderStyle.nodes.length;
                if (n == 1 && !x3dom.isa(this._cf.renderStyle.nodes[0], x3dom.nodeTypes.OpacityMapVolumeStyle)){
                    this.surfaceNormalsNeeded = true;
                }
                for (var i=0; i<n; i++){
                    styleText += this._cf.renderStyle.nodes[i].styleUniformsShaderText() + "\n";
                    if(this._cf.renderStyle.nodes[i]._cf.surfaceNormals && this._cf.renderStyle.nodes[i]._cf.surfaceNormals.node != null){
                        this.normalTextureProvided = true;
                        this._cf.surfaceNormals.node = this._cf.renderStyle.nodes[i]._cf.surfaceNormals.node;
                    }
                }
                return styleText;
            },

            styleShaderText: function(){
                var styleText = "";
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    if(this._cf.renderStyle.nodes[i].styleShaderText != undefined){
                        styleText += this._cf.renderStyle.nodes[i].styleShaderText() + "\n";
                    }
                }
                return styleText;
            },

            inlineStyleShaderText: function(){
                var inlineText = "";
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    inlineText += this._cf.renderStyle.nodes[i].inlineStyleShaderText();
                }
                /*if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                    inlineText += this._cf.renderStyle.nodes[0].lightAssigment();
                }*/
                return inlineText;
            },

            lightAssigment: function(){
                var isBlendedStyle = false;
                var blendedLightAssigmentText;
                //Check if there is a blendedStyle, not to use lightAssigment
                Array.forEach(this._cf.renderStyle.nodes, function(style){
                    if(x3dom.isa(style, x3dom.nodeTypes.BlendedVolumeStyle)){
                        isBlendedStyle = true;
                        blendedLightAssigmentText = style.lightAssigment();
                    }
                });
                if(!isBlendedStyle){
                    return this._cf.renderStyle.nodes[0].lightAssigment();
                }else{
                    return this._cf.renderStyle.nodes[0].lightAssigment()+blendedLightAssigmentText;
                }
            },

            lightEquationShaderText: function(){
                return this._cf.renderStyle.nodes[0].lightEquationShaderText();
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### EdgeEnhancementVolumeStyle ### */
x3dom.registerNodeType(
    "EdgeEnhancementVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for EdgeEnhancementVolumeStyle
         * @constructs x3dom.nodeTypes.EdgeEnhancementVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * The EdgeEnhancementVolumeStyle node specifies that the edges of the assocciated volume data shall be enhanced.
         * The edge enhancement is performed by changing the color of the edges to the specified one.
         */
        function (ctx) {
            x3dom.nodeTypes.EdgeEnhancementVolumeStyle.superClass.call(this, ctx);


            /**
             * The edgeColor field specifies the color to be used for the edge enhacement.
             * @var {x3dom.fields.SFColor} edgeColor
             * @memberof x3dom.nodeTypes.EdgeEnhancementVolumeStyle
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'edgeColor', 0, 0, 0);

            /**
             * The gradientThreshold field is used to adjust the edge detection.
             * @var {x3dom.fields.SFFloat} gradientThreshold
             * @memberof x3dom.nodeTypes.EdgeEnhancementVolumeStyle
             * @initvalue 0.4
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'gradientThreshold', 0.4);

            this.uniformColorEdgeColor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatGradientThreshold = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DSurfaceNormals = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformBoolEdgeEnable = new x3dom.nodeTypes.Uniform(ctx);
            
        },
        {
            fieldChanged: function(fieldName){
                if (fieldName == "edgeColor") {
                    this.uniformColorEdgeColor._vf.value = this._vf.edgeColor;
                    this.uniformColorEdgeColor.fieldChanged("value");
                }else if (fieldName == "gradientThreshold") {
                    this.uniformFloatGradientThreshold._vf.value = this._vf.gradientThreshold;
                    this.uniformFloatGradientThreshold.fieldChanged("value");
                }
            },

            uniforms: function(){
                var unis = [];
                
                if (this._cf.surfaceNormals.node) {
                    this.uniformSampler2DSurfaceNormals._vf.name = 'uSurfaceNormals';
                    this.uniformSampler2DSurfaceNormals._vf.type = 'SFInt32';
                    this.uniformSampler2DSurfaceNormals._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DSurfaceNormals);
                }

                this.uniformColorEdgeColor._vf.name = 'uEdgeColor'+this._styleID;
                this.uniformColorEdgeColor._vf.type = 'SFColor';
                this.uniformColorEdgeColor._vf.value = this._vf.edgeColor;
                unis.push(this.uniformColorEdgeColor);

                this.uniformFloatGradientThreshold._vf.name = 'uGradientThreshold'+this._styleID;
                this.uniformFloatGradientThreshold._vf.type = 'SFFloat';
                this.uniformFloatGradientThreshold._vf.value = this._vf.gradientThreshold;
                unis.push(this.uniformFloatGradientThreshold);

                this.uniformBoolEdgeEnable._vf.name = 'uEnableEdge'+this._styleID;
                this.uniformBoolEdgeEnable._vf.type = 'SFBool';
                this.uniformBoolEdgeEnable._vf.value = this._vf.enabled;
                unis.push(this.uniformBoolEdgeEnable);
                return unis;
            },

            textures: function() {
                var texs = [];
                if (this._cf.surfaceNormals.node) {
                    var tex = this._cf.surfaceNormals.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex)
                }
                return texs;
            },

            styleUniformsShaderText: function(){
                return "uniform vec3 uEdgeColor"+this._styleID+";\n"+
                    "uniform float uGradientThreshold"+this._styleID+";\n"+
                    "uniform bool uEnableEdge"+this._styleID+";\n";
            },

            styleShaderText: function(){
                if (this._first){
                    return "void edgeEnhancement(inout vec4 originalColor, in vec4 gradient, in vec3 V, in vec3 edgeColor, in float gradientT)\n"+
                    "{\n"+
                    "   if(gradient.a > 0.05){\n"+
                    "       float angle_dif = abs(dot(gradient.xyz,V));\n"+
                    //"       float br = clamp(sign(cos(gradientT)-angle_dif),0.0,1.0);\n"+
                    "       if(angle_dif > cos(gradientT)){\n"+
                    "           originalColor.rgb = mix(edgeColor, originalColor.rgb, angle_dif);\n"+
                    "       }\n"+
                    "   }\n"+
                    "}\n";
                }else{
                    return "";
                }
            },

            inlineStyleShaderText: function(){
                var inlineText = "   if(uEnableEdge"+this._styleID+"){\n"+
                "       edgeEnhancement(value, gradEye, dir, uEdgeColor"+this._styleID+", uGradientThreshold"+this._styleID+");\n"+
                "   }\n";
                return inlineText;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### IsoSurfaceVolumeData ### */
x3dom.registerNodeType(
    "IsoSurfaceVolumeData",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DVolumeDataNode,
        
        /**
         * Constructor for IsoSurfaceVolumeData
         * @constructs x3dom.nodeTypes.IsoSurfaceVolumeData
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DVolumeDataNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The IsoSurfaceVolumeData node specifies one or more surfaces to be extracted from the volume data.
         */
        function (ctx) {
            x3dom.nodeTypes.IsoSurfaceVolumeData.superClass.call(this, ctx);


            /**
             * The renderStyle field contains a list of volume render style nodes to be used on each isosurface.
             * @var {x3dom.fields.MFNode} renderStyle
             * @memberof x3dom.nodeTypes.IsoSurfaceVolumeData
             * @initvalue x3dom.nodeTypes.X3DVolumeRenderStyleNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('renderStyle', x3dom.nodeTypes.X3DVolumeRenderStyleNode);

            /**
             * The gradients field allows to provide the normals of the volume data. It takes an ImageTextureAtlas of the same dimensions of the volume data. If it is not provided, it is computed on the fly.
             * @var {x3dom.fields.SFNode} gradients
             * @memberof x3dom.nodeTypes.IsoSurfaceVolumeData
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('gradients', x3dom.nodeTypes.Texture);
            //this.addField_SFNode('gradients', x3dom.nodeTypes.X3DTexture3DNode);

            /**
             * The surfaceValues field is a list containing the surface values to be extracted. One or multiple isovalues can be declared.
             * @var {x3dom.fields.MFFloat} surfaceValues
             * @memberof x3dom.nodeTypes.IsoSurfaceVolumeData
             * @initvalue [0.0]
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'surfaceValues', [0.0]);

            /**
             * The countourStepSize field specifies an step size to render isosurfaces that are multiples of an initial isovalue.
             * When this field is non-zero a single isovalue must be defined on the surfaceValues field.
             * @var {x3dom.fields.SFFloat} contourStepSize
             * @memberof x3dom.nodeTypes.IsoSurfaceVolumeData
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'contourStepSize', 0);

            /**
             * The surfaceTolerance field is a threshold to adjust the boundary of the isosurface.
             * @var {x3dom.fields.SFFloat} surfaceTolerance
             * @memberof x3dom.nodeTypes.IsoSurfaceVolumeData
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'surfaceTolerance', 0);

            this.uniformSampler2DGradients = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatContourStepSize = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatSurfaceTolerance = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatArraySurfaceValues = new x3dom.nodeTypes.Uniform(ctx);

            this.vrcMultiTexture = new x3dom.nodeTypes.MultiTexture(ctx);
            this.vrcVolumeTexture = null;

            this.vrcSinglePassShader = new x3dom.nodeTypes.ComposedShader(ctx);
            this.vrcSinglePassShaderVertex = new x3dom.nodeTypes.ShaderPart(ctx);
            this.vrcSinglePassShaderFragment = new x3dom.nodeTypes.ShaderPart(ctx);
            this.vrcSinglePassShaderFieldVolData = new x3dom.nodeTypes.Field(ctx);
            this.vrcSinglePassShaderFieldOffset = new x3dom.nodeTypes.Field(ctx);
            this.vrcSinglePassShaderFieldDimensions = new x3dom.nodeTypes.Field(ctx);
        },
        {
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'surfaceValues':
                        this.uniformFloatArraySurfaceValues._vf.value = this._vf.surfaceValues;
                        this.uniformFloatArraySurfaceValues.fieldChanged("value");
                        //TODO: Reload node
                        break;
                    case 'surfaceTolerance':
                        this.uniformFloatSurfaceTolerance._vf.value = this._vf.surfaceTolerance;
                        this.uniformFloatSurfaceTolerance.fieldChanged("value");
                        break;
                    case 'contourStepSize':
                        //TODO: Reload node
                        break;
                }
            },

            uniforms: function(){
                var unis = [];
                if (this._cf.gradients.node){
                    this.uniformSampler2DGradients._vf.name = 'uSurfaceNormals';
                    this.uniformSampler2DGradients._vf.type = 'SFInt32';
                    this.uniformSampler2DGradients._vf.value = this._textureID++;
                    unis.push(this.uniformSampler2DGradients);
                }

                this.uniformFloatArraySurfaceValues._vf.name = 'uSurfaceValues';
                this.uniformFloatArraySurfaceValues._vf.type = 'MFFloat';
                this.uniformFloatArraySurfaceValues._vf.value = this._vf.surfaceValues;
                unis.push(this.uniformFloatArraySurfaceValues);

                this.uniformFloatSurfaceTolerance._vf.name = 'uSurfaceTolerance';
                this.uniformFloatSurfaceTolerance._vf.type = 'MFFloat';
                this.uniformFloatSurfaceTolerance._vf.value = this._vf.surfaceTolerance;
                unis.push(this.uniformFloatSurfaceTolerance);

                if (this._cf.renderStyle.nodes) {
                    var n = this._cf.renderStyle.nodes.length;
                    for (var i=0; i<n; i++){
                        //Not repeat common uniforms, TODO: Allow multiple surface normals
                        Array.forEach(this._cf.renderStyle.nodes[i].uniforms(), function(uniform){
                            var contains_uniform = false;
                            Array.forEach(unis, function(accum){
                                if(accum._vf.name == uniform._vf.name){
                                    contains_uniform = true;
                                }
                            });
                            if (contains_uniform == false){
                                unis = unis.concat(uniform);
                            }
                        });
                    }    
                }
                return unis;
            },

            textures: function(){
                var texs = [];
                if(this._cf.gradients.node){
                    var tex = this._cf.gradients.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }

                var i, n = this._cf.renderStyle.nodes.length;
                for (i=0; i<n; i++){
                    //Not repeat same textures, TODO: Allow multiply surface normals textures
                    Array.forEach(this._cf.renderStyle.nodes[i].textures(), function(texture){
                        var contains_texture = false;
                        Array.forEach(texs, function(accum){
                            if(accum._vf.url[0] == texture._vf.url[0]){
                                contains_texture = true;
                            }
                        });
                        if (contains_texture == false){
                            texs = texs.concat(texture);
                        }
                    });
                }
                return texs;
            },

            initializeValues: function() {
                var initialValues ="  float previous_value = 0.0;\n";
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    if(this._cf.renderStyle.nodes[i].initializeValues != undefined){
                        initialValues += this._cf.renderStyle.nodes[i].initializeValues() + "\n";
                    }
                }
                return initialValues;
            },

            styleUniformsShaderText: function(){
                var styleText = "uniform float uSurfaceTolerance;\n"+
                "uniform float uSurfaceValues["+this._vf.surfaceValues.length+"];\n";
                if(this._cf.gradients.node){
                    styleText += "uniform sampler2D uSurfaceNormals;\n";
                }
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    styleText += this._cf.renderStyle.nodes[i].styleUniformsShaderText() + "\n";
                    if(this._cf.renderStyle.nodes[i]._cf.surfaceNormals && this._cf.renderStyle.nodes[i]._cf.surfaceNormals.node != null){
                        this.normalTextureProvided = true;
                        this.surfaceNormals = this._cf.renderStyle.nodes[i]._cf.surfaceNormals.node;
                    }
                }
                //Always needed for the isosurface
                this.surfaceNormalsNeeded = true;   
                return styleText;
            },

            inlineStyleShaderText: function(){
                var inlineText = "    sample = value.r;\n";
                if(this._vf.surfaceValues.length == 1) { //Only one surface value
                    if(this._vf.contourStepSize == 0.0){
                        inlineText += "   if(((sample>=uSurfaceValues[0] && previous_value<uSurfaceValues[0])||(sample<uSurfaceValues[0] && previous_value>=uSurfaceValues[0])) && (grad.a>=uSurfaceTolerance)){\n"+
                        "       value = vec4(vec3(uSurfaceValues[0]),1.0);\n";
                        if(this._cf.renderStyle.nodes){
                            inlineText += this._cf.renderStyle.nodes[0].inlineStyleShaderText();
                        }
                        inlineText += "       accum.rgb += (1.0 - accum.a) * (value.rgb * value.a);\n"+
                        "       accum.a += (1.0 - accum.a) * value.a;\n"+
                        "   }\n"; 
                    }else{ //multiple iso values with the contour step size
                        var tmp = this._vf.surfaceValues[0];
                        var positive_range = [tmp];
                        var negative_range = [];
                        var range = [];
                        while(tmp+this._vf.contourStepSize <= 1.0){
                            tmp+=this._vf.contourStepSize;
                            positive_range.push(tmp);
                        }
                        tmp = this._vf.surfaceValues[0];
                        while(tmp-this._vf.contourStepSize >= 0.0){
                            tmp-=this._vf.contourStepSize;
                            negative_range.unshift(tmp);
                        }
                        range = negative_range.concat(positive_range);
                        for (var i = 0; i <= range.length - 1; i++) {
                            var s_value = range[i].toPrecision(3);
                            inlineText += " if(((sample>="+s_value+" && previous_value<"+s_value+")||(sample<"+s_value+" && previous_value>="+s_value+")) && (grad.a>=uSurfaceTolerance)){\n"+
                            "       value = vec4(vec3("+s_value+"),1.0);\n";
                            if(this._cf.renderStyle.nodes){
                                inlineText += this._cf.renderStyle.nodes[0].inlineStyleShaderText();
                            }
                            inlineText += "       accum.rgb += (1.0 - accum.a) * (value.rgb * value.a);\n"+
                            "       accum.a += (1.0 - accum.a) * value.a;\n"+
                            "   }\n"; 
                        };
                    }
                }else{ //Multiple isosurface values had been specified by the user
                    var n_styles = this._cf.renderStyle.nodes.length-1;
                    var s_values = this._vf.surfaceValues.length;
                    for(var i=0; i<s_values; i++){
                        var index = Math.min(i, n_styles);
                        inlineText += "   if(((sample>=uSurfaceValues["+i+"] && previous_value<uSurfaceValues["+i+"])||(sample<uSurfaceValues["+i+"] && previous_value>=uSurfaceValues["+i+"])) && (grad.a>=uSurfaceTolerance)){\n"+
                        "       value = vec4(vec3(uSurfaceValues["+i+"]),1.0);\n";
                        if(this._cf.renderStyle.nodes){
                            inlineText += this._cf.renderStyle.nodes[index].inlineStyleShaderText();
                        }
                        inlineText += "   accum.rgb += (1.0 - accum.a) * (value.rgb * value.a);\n"+
                        "   accum.a += (1.0 - accum.a) * value.a;\n"+
                        "   }\n"; 
                    }
                }
                inlineText += "    previous_value = sample;\n";
                return inlineText;
            },

            styleShaderText: function(){
                var styleText = "";
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    if(this._cf.renderStyle.nodes[i].styleShaderText != undefined){
                        styleText += this._cf.renderStyle.nodes[i].styleShaderText()+"\n";
                    }
                }
                return styleText;
            },

            lightAssigment: function(){
                return this._cf.renderStyle.nodes[0].lightAssigment();
            },

            lightEquationShaderText: function(){ //TODO: ligth equation per isosurface?
                return this._cf.renderStyle.nodes[0].lightEquationShaderText();
            },

            nodeChanged: function()
            {
                if (!this._cf.appearance.node) 
                {
                    var i;

                    this.addChild(new x3dom.nodeTypes.Appearance());
                    
                    // create shortcut to volume data set
                    this.vrcVolumeTexture = this._cf.voxels.node;
                    this.vrcVolumeTexture._vf.repeatS = false;
                    this.vrcVolumeTexture._vf.repeatT = false;

                    this.vrcMultiTexture._nameSpace = this._nameSpace;
                    
                    this.vrcMultiTexture.addChild(this.vrcVolumeTexture, 'texture');
                    this.vrcVolumeTexture.nodeChanged();
                    
                    // textures from styles
                    var styleTextures = this.textures();
                    for (i = 0; i<styleTextures.length; i++)
                    {
                        this.vrcMultiTexture.addChild(styleTextures[i], 'texture');
                        this.vrcVolumeTexture.nodeChanged();
                    }
                    
                    this._cf.appearance.node.addChild(this.vrcMultiTexture);
                    this.vrcMultiTexture.nodeChanged();
                    
                    //Update child node properties
                    for (var i = 0; i < this._cf.renderStyle.nodes.length; i++) {
                        this._cf.renderStyle.nodes[i].updateProperties(this);
                    }
                    
                    // here goes the volume shader
                    this.vrcSinglePassShaderVertex._vf.type = 'vertex';
                    this.vrcSinglePassShaderVertex._vf.url[0] = this.vertexShaderText();;

                    this.vrcSinglePassShaderFragment._vf.type = 'fragment';
                    shaderText =
                    this.fragmentPreamble+
                    this.defaultUniformsShaderText(this.vrcVolumeTexture._vf.numberOfSlices, this.vrcVolumeTexture._vf.slicesOverX, this.vrcVolumeTexture._vf.slicesOverY)+
                    this.styleUniformsShaderText()+
                    this.styleShaderText()+
                    this.texture3DFunctionShaderText+
                    this.normalFunctionShaderText()+
                    this.lightEquationShaderText();
                    shaderText += "void main()\n"+
                    "{\n"+
                    "  vec3 cam_pos = vec3(modelViewMatrixInverse[3][0], modelViewMatrixInverse[3][1], modelViewMatrixInverse[3][2]);\n"+
                    "  vec3 cam_cube = cam_pos/dimensions+0.5;\n"+
                    "  vec3 dir = normalize(pos.xyz-cam_cube);\n";
                    if(this._vf.allowViewpointInside){
                        shaderText +=
                        "  float cam_inside = float(all(bvec2(all(lessThan(cam_cube, vec3(1.0))),all(greaterThan(cam_cube, vec3(0.0))))));\n"+
                        "  vec3 ray_pos = mix(pos.xyz, cam_cube, cam_inside);\n";
                    }else{
                        shaderText += "  vec3 ray_pos = pos.xyz;\n";
                    }
                    shaderText +=
                    "  vec4 accum  = vec4(0.0, 0.0, 0.0, 0.0);\n"+
                    "  float sample = 0.0;\n"+
                    "  vec4 value  = vec4(0.0, 0.0, 0.0, 0.0);\n"+
                    "  float cont = 0.0;\n"+
                    "  vec3 step = dir/Steps;\n";
                    //Light init values
                    if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                        shaderText +=
                        "  vec3 ambient = vec3(0.0, 0.0, 0.0);\n"+
                        "  vec3 diffuse = vec3(0.0, 0.0, 0.0);\n"+
                        "  vec3 specular = vec3(0.0, 0.0, 0.0);\n"+
                        "  vec4 step_eye = modelViewMatrix * vec4(step, 0.0);\n"+
                        "  vec4 positionE = position_eye;\n"+
                        "  float lightFactor = 1.0;\n"; 
                    }else{
                        shaderText += "  float lightFactor = 1.2;\n";
                    }
                    shaderText += this.initializeValues()+
                    "  float opacityFactor = 6.0;\n"+
                    "  float t_near;\n"+
                    "  float t_far;\n"+
                    "  for(float i = 0.0; i < Steps; i+=1.0)\n"+
                    "  {\n"+
                    "    value = cTexture3D(uVolData, ray_pos, numberOfSlices, slicesOverX, slicesOverY);\n"+
                    "    value = value.rgbr;\n";
                    if(this._cf.gradients.node){
                        shaderText += "    vec4 gradEye = getNormalFromTexture(uSurfaceNormals, ray_pos, numberOfSlices, slicesOverX, slicesOverY);\n";
                    }else{
                        shaderText += "    vec4 gradEye = getNormalOnTheFly(uVolData, ray_pos, numberOfSlices, slicesOverX, slicesOverY);\n";
                    }
                    shaderText += "    vec4 grad = vec4((modelViewMatrix * vec4(gradEye.xyz, 0.0)).xyz, gradEye.a);\n";
                    for(var l=0; l<x3dom.nodeTypes.X3DLightNode.lightID; l++) {
                        shaderText += "    lighting(light"+l+"_Type, " +
                        "light"+l+"_Location, " +
                        "light"+l+"_Direction, " +
                        "light"+l+"_Color, " + 
                        "light"+l+"_Attenuation, " +
                        "light"+l+"_Radius, " +
                        "light"+l+"_Intensity, " + 
                        "light"+l+"_AmbientIntensity, " +
                        "light"+l+"_BeamWidth, " +
                        "light"+l+"_CutOffAngle, " +
                        "grad.xyz, positionE.xyz, ambient, diffuse, specular);\n";
                    }
                    shaderText += this.inlineStyleShaderText();
                    if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                        shaderText += this.lightAssigment();
                    }
                    shaderText +=
                    "    //advance the current position\n"+
                    "    ray_pos.xyz += step;\n";
                    if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                        shaderText +="    positionE += step_eye;\n";
                    }
                    shaderText +=
                    "    //break if the position is greater than <1, 1, 1>\n"+
                    "    if(ray_pos.x > 1.0 || ray_pos.y > 1.0 || ray_pos.z > 1.0 || ray_pos.x <= 0.0 || ray_pos.y <= 0.0 || ray_pos.z <= 0.0 || accum.a>=1.0)\n"+
                    "      break;\n"+
                    "  }\n"+
                    "  gl_FragColor = accum;\n"+
                    "}";
                    this.vrcSinglePassShaderFragment._vf.url[0] = shaderText;

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderVertex, 'parts');
                    this.vrcSinglePassShaderVertex.nodeChanged();
                    
                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFragment, 'parts');
                    this.vrcSinglePassShaderFragment.nodeChanged();

                    this.vrcSinglePassShaderFieldVolData._vf.name = 'uVolData';
                    this.vrcSinglePassShaderFieldVolData._vf.type = 'SFInt32';
                    this.vrcSinglePassShaderFieldVolData._vf.value = this._textureID++;

                    this.vrcSinglePassShaderFieldDimensions._vf.name = 'dimensions';
                    this.vrcSinglePassShaderFieldDimensions._vf.type = 'SFVec3f';
                    this.vrcSinglePassShaderFieldDimensions._vf.value = this._vf.dimensions;
                    
                    this.vrcSinglePassShaderFieldOffset._vf.name = 'offset';
                    this.vrcSinglePassShaderFieldOffset._vf.type = 'SFVec3f';
                    this.vrcSinglePassShaderFieldOffset._vf.value = "0.01 0.01 0.01"; //Default initial value
                    
                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldVolData, 'fields');
                    this.vrcSinglePassShaderFieldVolData.nodeChanged();

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldDimensions, 'fields');
                    this.vrcSinglePassShaderFieldDimensions.nodeChanged();

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldOffset, 'fields');
 
                    //Take volume texture size for the ComposableRenderStyles offset parameter
                    this.offsetInterval = window.setInterval((function(aTex, obj) {
                        return function() {
                            x3dom.debug.logInfo('[VolumeRendering][IsoSurfaceVolumeData] Looking for Volume Texture size...');
                            var s = obj.getTextureSize(aTex);
                            if(s.valid){
                                clearInterval(obj.offsetInterval);
                                obj.vrcSinglePassShaderFieldOffset._vf.value = new x3dom.fields.SFVec3f(1.0/(s.w/aTex._vf.slicesOverX), 1.0/(s.h/aTex._vf.slicesOverY), 1.0/aTex._vf.numberOfSlices);
                                obj.vrcSinglePassShader.nodeChanged();
                                x3dom.debug.logInfo('[VolumeRendering][IsoSurfaceVolumeData] Volume Texture size obtained');
                            }
                        }
                    })(this.vrcVolumeTexture, this), 1000);

                    var ShaderUniforms = this.uniforms();
                    for (i = 0; i<ShaderUniforms.length; i++)
                    {
                        this.vrcSinglePassShader.addChild(ShaderUniforms[i], 'fields');
                    }
                
                    this._cf.appearance.node.addChild(this.vrcSinglePassShader);
                    this.vrcSinglePassShader.nodeChanged();
                    
                    this._cf.appearance.node.nodeChanged();
                }

                if (!this._cf.geometry.node) {
                    this.addChild(new x3dom.nodeTypes.Box());

                    this._cf.geometry.node._vf.solid = false;
                    this._cf.geometry.node._vf.hasHelperColors = false;
                    this._cf.geometry.node._vf.size = new x3dom.fields.SFVec3f(
                        this._vf.dimensions.x, this._vf.dimensions.y, this._vf.dimensions.z);

                    // workaround to trigger field change...
                    this._cf.geometry.node.fieldChanged("size");
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### MPRVolumeStyle ### */
x3dom.registerNodeType(
    "MPRVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DVolumeRenderStyleNode,
        
        /**
         * Constructor for MPRVolumeStyle
         * @constructs x3dom.nodeTypes.MPRVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The MPRVolumeStyle node renders a multiplanar reconstruction of the assocciated volume data.
         */
        function (ctx) {
            x3dom.nodeTypes.MPRVolumeStyle.superClass.call(this, ctx);


            /**
             * The originLine field specifies the base line of the slice plane. 
             * @var {x3dom.fields.SFVec3f} originLine
             * @memberof x3dom.nodeTypes.MPRVolumeStyle
             * @initvalue 1.0,1.0,0.0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'originLine', 1.0, 1.0, 0.0);

            /**
             * The finalLine field specifies the second line to calculate the normal plane.
             * @var {x3dom.fields.SFVec3f} finalLine
             * @memberof x3dom.nodeTypes.MPRVolumeStyle
             * @initvalue 0.0,1.0,0.0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'finalLine', 0.0, 1.0, 0.0);

            /**
             * The positionLine field specifies the position along the line where the slice plane is rendered.
             * @var {x3dom.fields.SFFloat} positionLine
             * @memberof x3dom.nodeTypes.MPRVolumeStyle
             * @initvalue 0.2
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'positionLine', 0.2);

            /**
             * The transferFunction field is a texture that is going to be used to map each voxel value to a specific color output.
             * @var {x3dom.fields.SFNode} transferFunction
             * @memberof x3dom.nodeTypes.MPRVolumeStyle
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('transferFunction', x3dom.nodeTypes.Texture);

            this.uniformVec3fOriginLine = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformVec3fFinalLine = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatPosition = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DTransferFunction = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName) {
                 switch(fieldName){
                    case 'positionLine':
                        this.uniformFloatPosition._vf.value = this._vf.positionLine;
                        this.uniformFloatPosition.fieldChanged("value");
                        break;
                    case 'originLine':
                        this.uniformVec3fOriginLine._vf.value = this._vf.originLine;
                        this.uniformVec3fOriginLine.fieldChanged("value");
                        break;
                    case 'finalLine':
                        this.uniformVec3fFinalLine._vf.value = this._vf.finalLine;
                        this.uniformVec3fFinalLine.fieldChanged("value");
                        break;
                }
            },

            uniforms: function() {
                var unis = [];

                this.uniformVec3fOriginLine._vf.name = 'originLine';
                this.uniformVec3fOriginLine._vf.type = 'SFVec3f';
                this.uniformVec3fOriginLine._vf.value = this._vf.originLine;
                unis.push(this.uniformVec3fOriginLine);

                this.uniformVec3fFinalLine._vf.name = 'finalLine';
                this.uniformVec3fFinalLine._vf.type = 'SFVec3f';
                this.uniformVec3fFinalLine._vf.value = this._vf.finalLine;
                unis.push(this.uniformVec3fFinalLine);

                this.uniformFloatPosition._vf.name = 'positionLine';
                this.uniformFloatPosition._vf.type = 'SFFloat';
                this.uniformFloatPosition._vf.value = this._vf.positionLine;
                unis.push(this.uniformFloatPosition);

                if (this._cf.transferFunction.node) {
                    this.uniformSampler2DTransferFunction._vf.name = 'uTransferFunction';
                    this.uniformSampler2DTransferFunction._vf.type = 'SFInt32';
                    this.uniformSampler2DTransferFunction._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DTransferFunction);
                }
  
                return unis;
            },

            textures: function() {
                var texs = [];
                var tex = this._cf.transferFunction.node;
                if (tex) {
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }
                return texs;
            },

            styleUniformsShaderText: function(){
                var uniformShaderText = "uniform vec3 originLine;\nuniform vec3 finalLine;\nuniform float positionLine;\n";
                if (this._cf.transferFunction.node) {
                    uniformShaderText += "uniform sampler2D uTransferFunction;\n";
                }
                return uniformShaderText;
            },

            fragmentShaderText : function (numberOfSlices, slicesOverX, slicesOverY) {
                var shader = 
                this._parentNodes[0].fragmentPreamble+
                this._parentNodes[0].defaultUniformsShaderText(numberOfSlices, slicesOverX, slicesOverY)+
                this.styleUniformsShaderText()+
                this._parentNodes[0].texture3DFunctionShaderText+
                "void main()\n"+
                "{\n"+
                "  vec3 cam_pos = vec3(modelViewMatrixInverse[3][0], modelViewMatrixInverse[3][1], modelViewMatrixInverse[3][2]);\n"+
                "  cam_pos = cam_pos/dimensions+0.5;\n"+
                "  vec3 dir = normalize(pos.xyz-cam_pos);\n"+
                "  vec3 normalPlane = finalLine-originLine;\n"+
                "  vec3 pointLine = normalPlane*positionLine+originLine;\n"+
                "  float d = dot(pointLine-pos.xyz,normalPlane)/dot(dir,normalPlane);\n"+
                "  vec4 color = vec4(0.0,0.0,0.0,0.0);\n"+
                "  vec3 pos = d*dir+pos.rgb;\n"+
                "  if (!(pos.x > 1.0 || pos.y > 1.0 || pos.z > 1.0 || pos.x<0.0 || pos.y<0.0 || pos.z<0.0)){\n"+
                "    vec3 intesity = cTexture3D(uVolData,pos.rgb,numberOfSlices,slicesOverX,slicesOverY).rgb;\n";
                if (this._cf.transferFunction.node){
                    shader += "    color = vec4(texture2D(uTransferFunction, vec2(intesity.r,0.5)).rgb, 1.0);\n";
                }else{
                    shader += "    color = vec4(intesity,1.0);\n";
                }
                shader += "  }\n"+
                "  gl_FragColor = color;\n"+
                "}";
                return shader;
            }
         }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### OpacityMapVolumeStyle ### */
x3dom.registerNodeType(
    "OpacityMapVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for OpacityMapVolumeStyle
         * @constructs x3dom.nodeTypes.OpacityMapVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * The OpacityMapVolumeStyle node specifies that the associated volume data is going to be rendered using a transfer function.
         * The original opacity is mapped to a color with a function stored as a texture (transfer function).
         */
        function (ctx) {
            x3dom.nodeTypes.OpacityMapVolumeStyle.superClass.call(this, ctx);


            /**
             * The transferFunction field is a texture that is going to be used to map each voxel value to a specific color output.
             * @var {x3dom.fields.SFNode} transferFunction
             * @memberof x3dom.nodeTypes.OpacityMapVolumeStyle
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('transferFunction', x3dom.nodeTypes.Texture);

            /**
             * NYI!!
             * @var {x3dom.fields.SFString} type
             * @memberof x3dom.nodeTypes.OpacityMapVolumeStyle
             * @initvalue "simple"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'type', "simple");

            /**
             * The opacityFactor field is a factor to specify the amount of opacity to be considered on each sampled point along the ray traversal.
             * @var {x3dom.fields.SFFloat} opacityFactor
             * @memberof x3dom.nodeTypes.OpacityMapVolumeStyle
             * @initvalue 6.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'opacityFactor', 6.0);

            /**
             * The lightFactor field is a factor to specify the amount of global light to be considered on each sampled point along the ray traversal.
             * @var {x3dom.fields.SFFloat} lightFactor
             * @memberof x3dom.nodeTypes.OpacityMapVolumeStyle
             * @initvalue 1.2
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'lightFactor', 1.2);

            this.uniformFloatOpacityFactor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatLightFactor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DTransferFunction = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformBoolEnableOpacityMap = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'opacityFactor':
                        this.uniformFloatOpacityFactor._vf.value = this._vf.opacityFactor;
                        this.uniformFloatOpacityFactor.fieldChanged("value");
                        break;
                    case 'lightFactor':
                        this.uniformFloatLightFactor._vf.value = this._vf.lightFactor;
                        this.uniformFloatLightFactor.fieldChanged("value");
                        break;
                }
            },

            uniforms: function() {
                var unis = [];
                
                if (this._cf.transferFunction.node) {
                    this.uniformSampler2DTransferFunction._vf.name = 'uTransferFunction'+this._styleID;
                    this.uniformSampler2DTransferFunction._vf.type = 'SFInt32';
                    this.uniformSampler2DTransferFunction._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DTransferFunction);
                }

                this.uniformFloatOpacityFactor._vf.name = 'uOpacityFactor'+this._styleID;
                this.uniformFloatOpacityFactor._vf.type = 'SFFloat';
                this.uniformFloatOpacityFactor._vf.value = this._vf.opacityFactor;
                unis.push(this.uniformFloatOpacityFactor);

                this.uniformFloatLightFactor._vf.name = 'uLightFactor'+this._styleID;
                this.uniformFloatLightFactor._vf.type = 'SFFloat';
                this.uniformFloatLightFactor._vf.value = this._vf.lightFactor;
                unis.push(this.uniformFloatLightFactor);

                this.uniformBoolEnableOpacityMap._vf.name = 'uEnableOpacityMap'+this._styleID;
                this.uniformBoolEnableOpacityMap._vf.type = 'SFBool';
                this.uniformBoolEnableOpacityMap._vf.value = this._vf.enabled;
                unis.push(this.uniformBoolEnableOpacityMap);

                return unis;
            },

            textures: function() {
                var texs = [];
                var tex = this._cf.transferFunction.node;
                if (tex) {
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }
                return texs;
            },

            styleUniformsShaderText: function() {
                var uniformsText = "uniform float uOpacityFactor"+this._styleID+";\n"+
                "uniform float uLightFactor"+this._styleID+";\n"+
                "uniform bool uEnableOpacityMap"+this._styleID+";\n";
                if (this._cf.transferFunction.node) {
                        uniformsText += "uniform sampler2D uTransferFunction"+this._styleID+";\n";
                }
                return uniformsText;
            },

            inlineStyleShaderText: function(){
                var shaderText = "    if(uEnableOpacityMap"+this._styleID+"){\n"+
                "       opacityFactor = uOpacityFactor"+this._styleID+";\n"+
                "       lightFactor = uLightFactor"+this._styleID+";\n";
                if (this._cf.transferFunction.node){
                        shaderText += "       value = texture2D(uTransferFunction"+this._styleID+",vec2(value.r,0.5));\n";
                }
                shaderText += "    }\n";
                return shaderText;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### ProjectionVolumeStyle ### */
x3dom.registerNodeType(
    "ProjectionVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DVolumeRenderStyleNode,
        
        /**
         * Constructor for ProjectionVolumeStyle
         * @constructs x3dom.nodeTypes.ProjectionVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ProjectionVolumeStyle node generates an output color based on the voxel data values traversed by a ray following the view direction. 
         */
        function (ctx) {
            x3dom.nodeTypes.ProjectionVolumeStyle.superClass.call(this, ctx);


            /**
             * The intensityThreshold field is used to define a local maximum or minimum value along the ray traversal. It is ignored on the AVERAGE intensity projection.
             * @var {x3dom.fields.SFFloat} intensityThreshold
             * @memberof x3dom.nodeTypes.ProjectionVolumeStyle
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'intensityThreshold', 0);

            /**
             * The type field specifies the type of intensity projection to be used. It can be MAX, MIN or AVERAGE.
             * @var {x3dom.fields.SFString} type
             * @memberof x3dom.nodeTypes.ProjectionVolumeStyle
             * @initvalue "MAX"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'type', "MAX");

            this.uniformIntensityThreshold = new x3dom.nodeTypes.Uniform(ctx);
            //this.uniformType = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName){
                if (fieldName === 'intensityThreshold') {
                    this.uniformIntensityThreshold._vf.value = this._vf.intensityThreshold;
                    this.uniformIntensityThreshold.fieldChanged("value");
                }else if(fieldName === 'type'){
                    //TODO: Reload node
                }
            },

            uniforms: function(){
                var unis = [];
                //var type_map = {'max':0,'min':1,'average':2};

                this.uniformIntensityThreshold._vf.name = 'uIntensityThreshold';
                this.uniformIntensityThreshold._vf.type = 'SFFloat';
                this.uniformIntensityThreshold._vf.value = this._vf.intensityThreshold;
                unis.push(this.uniformIntensityThreshold);

                /*this.uniformType._vf.name = 'uType';
                this.uniformType._vf.type = 'SFInt32';
                this.uniformType._vf.value = type_map[this._vf.type.toLowerCase()];
                unis.push(this.uniformType);*/

                return unis;
            },

            styleUniformsShaderText: function(){
                return "uniform int uType;\n"+
                "uniform float uIntensityThreshold;\n";
            },

            fragmentShaderText: function(numberOfSlices, slicesOverX, slicesOverY){
                var shader = 
                this._parentNodes[0].fragmentPreamble+
                this._parentNodes[0].defaultUniformsShaderText(numberOfSlices, slicesOverX, slicesOverY)+
                this.styleUniformsShaderText()+
                this._parentNodes[0].texture3DFunctionShaderText+
                "void main()\n"+
                "{\n"+
                "  vec3 cam_pos = vec3(modelViewMatrixInverse[3][0], modelViewMatrixInverse[3][1], modelViewMatrixInverse[3][2]);\n"+
                "  cam_pos = cam_pos/dimensions+0.5;\n"+
                "  vec3 dir = normalize(pos.xyz-cam_pos);\n"+
                "  vec3 ray_pos = pos.xyz;\n"+
                "  vec4 accum  = vec4(0.0, 0.0, 0.0, 0.0);\n"+
                "  vec4 sample = vec4(0.0, 0.0, 0.0, 0.0);\n"+
                "  vec4 value  = vec4(0.0, 0.0, 0.0, 0.0);\n"+
                "  vec4 color  = vec4(0.0);\n";
                if (this._vf.type.toLowerCase() === "max") {
                    shader += "vec2 previous_value = vec2(0.0);\n";
                }else {
                    shader += "vec2 previous_value = vec2(1.0);\n";
                }
                shader +=
                "  float cont = 0.0;\n"+
                "  vec3 step_size = dir/Steps;\n"+
                "  const float lightFactor = 1.3;\n"+
                "  const float opacityFactor = 3.0;\n"+
                "  for(float i = 0.0; i < Steps; i+=1.0)\n"+
                "  {\n"+
                "    value = cTexture3D(uVolData,ray_pos,numberOfSlices,slicesOverX,slicesOverY);\n"+
                "    value = vec4(value.rgb,(0.299*value.r)+(0.587*value.g)+(0.114*value.b));\n"+
                "    //Process the volume sample\n"+
                "    sample.a = value.a * opacityFactor * (1.0/Steps);\n"+
                "    sample.rgb = value.rgb * sample.a * lightFactor;\n"+
                "    accum.a += (1.0-accum.a)*sample.a;\n";
                if(this._vf.enabled){
                    switch (this._vf.type.toLowerCase()) {
                    case "max":
                        shader += "if(value.r > uIntensityThreshold && value.r <= previous_value.x){\n"+
                        "   break;\n"+
                        "}\n"+
                        "color.rgb = vec3(max(value.r, previous_value.x));\n"+
                        "color.a = (value.r > previous_value.x) ? accum.a : previous_value.y;\n";
                        break;
                    case "min":
                        shader += "if(value.r < uIntensityThreshold && value.r >= previous_value.x){\n"+
                        "   break;\n"+
                        "}\n"+
                        "color.rgb = vec3(min(value.r, previous_value.x));\n"+
                        "color.a = (value.r < previous_value.x) ? accum.a : previous_value.y;\n";
                        break;
                    case "average":
                        shader+= "color.rgb += (1.0 - accum.a) * sample.rgb;\n"+
                        "color.a = accum.a;\n";
                        break;
                    }
                }
                shader += 
                "    //update the previous value and keeping the accumulated alpha\n"+
                "    previous_value.x = color.r;\n"+
                "    previous_value.y = accum.a;\n"+
                "    //advance the current position\n"+
                "    ray_pos.xyz += step_size;\n"+
                "    //break if the position is greater than <1, 1, 1>\n"+
                "    if(ray_pos.x > 1.0 || ray_pos.y > 1.0 || ray_pos.z > 1.0 || ray_pos.x <= 0.0 || ray_pos.y <= 0.0 || ray_pos.z <= 0.0 || accum.a>=1.0){\n";
                if(this._vf.type.toLowerCase() == "average" && this._vf.enabled){
                    shader += "     if((i > 0.0) && (i < Steps-1.0)){\n"+
                    "color.rgb = color.rgb/i;\n"+
                    "}\n";
                }
                shader+=
                "      break;\n"+
                "    }\n"+
                " }\n"+
                " gl_FragColor = color;\n"+
                "}";
                return shader;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### SegmentedVolumeData ### */
x3dom.registerNodeType(
    "SegmentedVolumeData",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DVolumeDataNode,
        
        /**
         * Constructor for SegmentedVolumeData
         * @constructs x3dom.nodeTypes.SegmentedVolumeData
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DVolumeDataNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The SegmentedVolumeData node specifies a segmented volume data set. Each segment can be rendered with a different volume rendering style.
         */
        function (ctx) {
            x3dom.nodeTypes.SegmentedVolumeData.superClass.call(this, ctx);


            /**
             * The renderStyle field contains a list of composable render styles nodes to be used on the segmented volume data.
             * @var {x3dom.fields.MFNode} renderStyle
             * @memberof x3dom.nodeTypes.SegmentedVolumeData
             * @initvalue x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('renderStyle', x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode);
            //this.addField_MFBool(ctx, 'segmentEnabled', []);  // MFBool NYI!!!
            //this.addField_SFNode('segmentIdentifiers', x3dom.nodeTypes.X3DVolumeDataNode);

            /**
             * The segmentIdentifiers field is an ImageTextureAtlas node of the same dimensions of the volume data. The segment identifiers are used to map each segment with a volume rendering style. 
             * @var {x3dom.fields.SFNode} segmentIdentifiers
             * @memberof x3dom.nodeTypes.SegmentedVolumeData
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('segmentIdentifiers', x3dom.nodeTypes.Texture);

            /**
             * Specifies the number of segments on the volume data. It is used to correctly match each segment identifier to an index of the renderStyle list.
             * @var {x3dom.fields.SFFloat} numberOfMaxSegments
             * @memberof x3dom.nodeTypes.SegmentedVolumeData
             * @initvalue 10.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'numberOfMaxSegments', 10.0);

            this.uniformSampler2DSegmentIdentifiers = new x3dom.nodeTypes.Uniform(ctx);
            this.normalTextureProvided = false;

            this.vrcMultiTexture = new x3dom.nodeTypes.MultiTexture(ctx);
            this.vrcVolumeTexture = null;

            this.vrcSinglePassShader = new x3dom.nodeTypes.ComposedShader(ctx);
            this.vrcSinglePassShaderVertex = new x3dom.nodeTypes.ShaderPart(ctx);
            this.vrcSinglePassShaderFragment = new x3dom.nodeTypes.ShaderPart(ctx);
            this.vrcSinglePassShaderFieldBackCoord = new x3dom.nodeTypes.Field(ctx);
            this.vrcSinglePassShaderFieldVolData = new x3dom.nodeTypes.Field(ctx);
            this.vrcSinglePassShaderFieldOffset = new x3dom.nodeTypes.Field(ctx);
            this.vrcSinglePassShaderFieldDimensions = new x3dom.nodeTypes.Field(ctx);
        },
        {
            fieldChanged: function(fieldName){
                if (fieldName === "numberOfMaxSegments" || fieldname === "segmentIdentifiers") {
                    //TODO: Reload node   
                }
            },

            uniforms: function(){
                var unis = [];

                if (this._cf.segmentIdentifiers.node) {
                    this.uniformSampler2DSegmentIdentifiers._vf.name = 'uSegmentIdentifiers';
                    this.uniformSampler2DSegmentIdentifiers._vf.type = 'SFInt32';
                    this.uniformSampler2DSegmentIdentifiers._vf.value = this._textureID++;
                    unis.push(this.uniformSampler2DSegmentIdentifiers);
                }

                //Also add the render style uniforms
                if (this._cf.renderStyle.nodes) {
                    var i, n = this._cf.renderStyle.nodes.length;
                    for (i=0; i<n; i++){
                        //Not repeat common uniforms, TODO: Allow multiple surface normals
                        var that = this;
                        Array.forEach(this._cf.renderStyle.nodes[i].uniforms(), function(uniform){
                            var contains_uniform = false;
                            Array.forEach(unis, function(accum){
                                if(accum._vf.name == uniform._vf.name){
                                    contains_uniform = true;
                                }
                            });
                            if (contains_uniform == false){
                                unis = unis.concat(uniform);
                            }
                        });
                    }    
                }
                return unis;
            },

            textures: function(){
                var texs = [];
                if(this._cf.segmentIdentifiers.node){
                    var tex = this._cf.segmentIdentifiers.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }

                //Also add the render style textures
                var i, n = this._cf.renderStyle.nodes.length;
                for (i=0; i<n; i++){
                    //Not repeat same textures, TODO: Allow multiply surface normals textures
                    Array.forEach(this._cf.renderStyle.nodes[i].textures(), function(texture){
                        var contains_texture = false;
                        Array.forEach(texs, function(accum){
                            if(accum._vf.url[0] == texture._vf.url[0]){
                                contains_texture = true;
                            }
                        });
                        if (contains_texture == false){
                            texs = texs.concat(texture);
                        }
                    });
                }
                return texs;
            },

            initializeValues: function() {
                var initialValues = "";
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    if(this._cf.renderStyle.nodes[i].initializeValues != undefined){
                        initialValues += this._cf.renderStyle.nodes[i].initializeValues() + "\n";
                    }
                }
                return initialValues;
            },

            styleUniformsShaderText: function(){
                var styleText = "const float maxSegments = " + this._vf.numberOfMaxSegments.toPrecision(3) + ";\n"+
                "uniform sampler2D uSegmentIdentifiers;\n"; //TODO: Segment enabled
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    styleText += this._cf.renderStyle.nodes[i].styleUniformsShaderText() + "\n";
                    if(this._cf.renderStyle.nodes[i]._cf.surfaceNormals && this._cf.renderStyle.nodes[i]._cf.surfaceNormals.node != null){
                        styleText += "uniform sampler2D uSurfaceNormals;\n"; //Neccessary when gradient is provided
                        this.normalTextureProvided = true;
                        this.surfaceNormals = this._cf.renderStyle.nodes[i]._cf.surfaceNormals.node;
                    }
                    if(!x3dom.isa(this._cf.renderStyle.nodes[i], x3dom.nodeTypes.OpacityMapVolumeStyle)){
                        this.surfaceNormalsNeeded = true;
                    }
                }
                return styleText;
            },

            styleShaderText: function(){
                var styleText = "";
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){
                    if(this._cf.renderStyle.nodes[i].styleShaderText != undefined){
                        styleText += this._cf.renderStyle.nodes[i].styleShaderText()+"\n";
                    }
                }
                return styleText;
            },

            inlineStyleShaderText: function(){
                var inlineText = "";
                if(this._cf.segmentIdentifiers.node){
                    inlineText += "    float t_id = cTexture3D(uSegmentIdentifiers, ray_pos, numberOfSlices, slicesOverX, slicesOverY).r;\n"+
                    "    int s_id = int(clamp(floor(t_id*maxSegments-0.5),0.0,maxSegments));\n"+
                    "    opacityFactor = 10.0;\n";
                    if(x3dom.nodeTypes.X3DLightNode.lightID>0){
                        inlineText += "    lightFactor = 1.0;\n";
                    }else{
                        inlineText += "    lightFactor = 1.2;\n";
                    }
                }else{
                    inlineText += "    int s_id = 0;\n";
                }
                //TODO Check if the segment identifier is going to be rendered or not. NYI!!
                var n = this._cf.renderStyle.nodes.length;
                for (var i=0; i<n; i++){ //TODO Check identifier and add the style
                    inlineText += "    if (s_id == "+i+"){\n"+
                    this._cf.renderStyle.nodes[i].inlineStyleShaderText()+
                    "    }\n";
                }
                return inlineText;
            },

            lightAssigment: function(){
                return this._cf.renderStyle.nodes[0].lightAssigment();
            },

            lightEquationShaderText: function(){ //TODO: ligth equation per segment
                return this._cf.renderStyle.nodes[0].lightEquationShaderText();
            },

            nodeChanged: function()
            {
                if (!this._cf.appearance.node) 
                {
                    var i;

                    this.addChild(new x3dom.nodeTypes.Appearance());
                    
                    // create shortcut to volume data set
                    this.vrcVolumeTexture = this._cf.voxels.node;
                    this.vrcVolumeTexture._vf.repeatS = false;
                    this.vrcVolumeTexture._vf.repeatT = false;

                    this.vrcMultiTexture._nameSpace = this._nameSpace;
                    
                    this.vrcMultiTexture.addChild(this.vrcVolumeTexture, 'texture');
                    this.vrcVolumeTexture.nodeChanged();
                    
                    // textures from styles
                    var styleTextures = this.textures();
                    for (i = 0; i<styleTextures.length; i++)
                    {
                        this.vrcMultiTexture.addChild(styleTextures[i], 'texture');
                        this.vrcVolumeTexture.nodeChanged();
                    }
                    
                    this._cf.appearance.node.addChild(this.vrcMultiTexture);
                    this.vrcMultiTexture.nodeChanged();
                    
                    //Update child node properties
                    for (var i = 0; i < this._cf.renderStyle.nodes.length; i++) {
                        this._cf.renderStyle.nodes[i].updateProperties(this);
                    }

                    // here goes the volume shader
                    this.vrcSinglePassShaderVertex._vf.type = 'vertex';
                    this.vrcSinglePassShaderVertex._vf.url[0] = this.vertexShaderText();

                    this.vrcSinglePassShaderFragment._vf.type = 'fragment';
                    shaderText =
                    this.fragmentPreamble+
                    this.defaultUniformsShaderText(this.vrcVolumeTexture._vf.numberOfSlices, this.vrcVolumeTexture._vf.slicesOverX, this.vrcVolumeTexture._vf.slicesOverY)+
                    this.styleUniformsShaderText()+
                    this.styleShaderText()+
                    this.texture3DFunctionShaderText+
                    this.normalFunctionShaderText()+
                    this.lightEquationShaderText()+
                    this.defaultLoopFragmentShaderText(this.inlineStyleShaderText(), this.lightAssigment(), this.initializeValues());
                    this.vrcSinglePassShaderFragment._vf.url[0] = shaderText;

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderVertex, 'parts');
                    this.vrcSinglePassShaderVertex.nodeChanged();
                    
                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFragment, 'parts');
                    this.vrcSinglePassShaderFragment.nodeChanged();
                    
                    this.vrcSinglePassShaderFieldVolData._vf.name = 'uVolData';
                    this.vrcSinglePassShaderFieldVolData._vf.type = 'SFInt32';
                    this.vrcSinglePassShaderFieldVolData._vf.value = this._textureID++;

                    this.vrcSinglePassShaderFieldDimensions._vf.name = 'dimensions';
                    this.vrcSinglePassShaderFieldDimensions._vf.type = 'SFVec3f';
                    this.vrcSinglePassShaderFieldDimensions._vf.value = this._vf.dimensions;

                    this.vrcSinglePassShaderFieldOffset._vf.name = 'offset';
                    this.vrcSinglePassShaderFieldOffset._vf.type = 'SFVec3f';
                    this.vrcSinglePassShaderFieldOffset._vf.value = "0.01 0.01 0.01"; //Default initial value
                    
                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldVolData, 'fields');
                    this.vrcSinglePassShaderFieldVolData.nodeChanged();

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldDimensions, 'fields');
                    this.vrcSinglePassShaderFieldDimensions.nodeChanged();

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldOffset, 'fields');
 
                    //Take volume texture size for the ComposableRenderStyles offset parameter
                    this.offsetInterval = window.setInterval((function(aTex, obj) {
                        return function() {
                            x3dom.debug.logInfo('[VolumeRendering][SegmentedVolumeData] Looking for Volume Texture size...');
                            var s = obj.getTextureSize(aTex);
                            if(s.valid){
                                clearInterval(obj.offsetInterval);
                                obj.vrcSinglePassShaderFieldOffset._vf.value = new x3dom.fields.SFVec3f(1.0/(s.w/aTex._vf.slicesOverX), 1.0/(s.h/aTex._vf.slicesOverY), 1.0/aTex._vf.numberOfSlices);
                                obj.vrcSinglePassShader.nodeChanged();
                                x3dom.debug.logInfo('[VolumeRendering][SegmentedVolumeData] Volume Texture size obtained');
                            }
                        }
                    })(this.vrcVolumeTexture, this), 1000);
                    
                    var ShaderUniforms = this.uniforms();
                    for (i = 0; i<ShaderUniforms.length; i++)
                    {
                        this.vrcSinglePassShader.addChild(ShaderUniforms[i], 'fields');
                    }
                
                    this._cf.appearance.node.addChild(this.vrcSinglePassShader);
                    this.vrcSinglePassShader.nodeChanged();
                    
                    this._cf.appearance.node.nodeChanged();
                }

                if (!this._cf.geometry.node) {
                    this.addChild(new x3dom.nodeTypes.Box());

                    this._cf.geometry.node._vf.solid = false;
                    this._cf.geometry.node._vf.hasHelperColors = false;
                    this._cf.geometry.node._vf.size = new x3dom.fields.SFVec3f(
                        this._vf.dimensions.x, this._vf.dimensions.y, this._vf.dimensions.z);

                    // workaround to trigger field change...
                    this._cf.geometry.node.fieldChanged("size");
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### ShadedVolumeStyle ### */
x3dom.registerNodeType(
    "ShadedVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for ShadedVolumeStyle
         * @constructs x3dom.nodeTypes.ShadedVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ShadedVolumeStyle node applies the Blinn-Phong illumination model to the assocciated volume data.
         * The light and fog parameters are obtained from the parent Appearence node.
         */
        function (ctx) {
            x3dom.nodeTypes.ShadedVolumeStyle.superClass.call(this, ctx);


            /**
             * The material field allows to specify a Material node to be used on the assocciated volume data. 
             * @var {x3dom.fields.SFNode} material
             * @memberof x3dom.nodeTypes.ShadedVolumeStyle
             * @initvalue x3dom.nodeTypes.X3DMaterialNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('material', x3dom.nodeTypes.X3DMaterialNode);

            /**
             * NYI!!
             * @var {x3dom.fields.SFBool} lighting
             * @memberof x3dom.nodeTypes.ShadedVolumeStyle
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'lighting', false);

            /**
             * NYI!!
             * @var {x3dom.fields.SFBool} shadows
             * @memberof x3dom.nodeTypes.ShadedVolumeStyle
             * @initvalue false
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'shadows', false);

            /**
             * NYI!!
             * @var {x3dom.fields.SFString} phaseFunction
             * @memberof x3dom.nodeTypes.ShadedVolumeStyle
             * @initvalue "Henyey-Greenstein"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'phaseFunction', "Henyey-Greenstein");

            this.uniformBoolLigthning = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformBoolShadows = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DSurfaceNormals = new x3dom.nodeTypes.Uniform(ctx);
            //Material uniforms
            this.uniformColorSpecular = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatAmbientIntensity = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatShininess = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatTransparency = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformColorEmissive = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformColorDiffuse = new x3dom.nodeTypes.Uniform(ctx);
            //Enable/Disable style
            this.uniformBoolEnableShaded = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'lightning':
                        this.uniformBoolLightning._vf.value = this._vf.lightning;
                        this.uniformBoolLightning.fieldChanged("value");
                        break;
                    case 'shadows':
                        this.uniformBoolShadows._vf.value = this._vf.shadows;
                        this.uniformBoolShadows.fieldChanged("value");
                        break;
                    default:
                        //TODO: Reload node
                        break;
                }
            },

            uniforms: function(){
                var unis = [];
                if (this._cf.surfaceNormals.node) {
                    this.uniformSampler2DSurfaceNormals._vf.name = 'uSurfaceNormals';
                    this.uniformSampler2DSurfaceNormals._vf.type = 'SFInt32';
                    this.uniformSampler2DSurfaceNormals._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DSurfaceNormals);
                }

                this.uniformBoolLigthning._vf.name = 'uLightning'+this._styleID;
                this.uniformBoolLigthning._vf.type = 'SFBool';
                this.uniformBoolLigthning._vf.value = this._vf.lighting;
                unis.push(this.uniformBoolLigthning);

                this.uniformBoolShadows._vf.name = 'uShadows'+this._styleID;
                this.uniformBoolShadows._vf.type = 'SFBool';
                this.uniformBoolShadows._vf.value = this._vf.shadows;
                unis.push(this.uniformBoolShadows);

                //Material uniform parameters
                if(this._cf.material.node != null){
                    this.uniformColorSpecular._vf.name = 'specularColor'+this._styleID;
                    this.uniformColorSpecular._vf.type = 'SFColor';
                    this.uniformColorSpecular._vf.value = this._cf.material.node._vf.specularColor;
                    unis.push(this.uniformColorSpecular);

                    this.uniformColorDiffuse._vf.name = 'diffuseColor'+this._styleID;
                    this.uniformColorDiffuse._vf.type = 'SFColor';
                    this.uniformColorDiffuse._vf.value = this._cf.material.node._vf.diffuseColor;
                    unis.push(this.uniformColorDiffuse);

                    this.uniformColorEmissive._vf.name = 'emissiveColor'+this._styleID;
                    this.uniformColorEmissive._vf.type = 'SFColor';
                    this.uniformColorEmissive._vf.value = this._cf.material.node._vf.emissiveColor;
                    unis.push(this.uniformColorEmissive);

                    this.uniformFloatAmbientIntensity._vf.name = 'ambientIntensity'+this._styleID;
                    this.uniformFloatAmbientIntensity._vf.type = 'SFFloat';
                    this.uniformFloatAmbientIntensity._vf.value = this._cf.material.node._vf.ambientIntensity;
                    unis.push(this.uniformFloatAmbientIntensity);

                    this.uniformFloatShininess._vf.name = 'shininess'+this._styleID;
                    this.uniformFloatShininess._vf.type = 'SFFloat';
                    this.uniformFloatShininess._vf.value = this._cf.material.node._vf.shininess;
                    unis.push(this.uniformFloatShininess);

                    this.uniformFloatTransparency._vf.name = 'transparency'+this._styleID;
                    this.uniformFloatTransparency._vf.type = 'SFFloat';
                    this.uniformFloatTransparency._vf.value = this._cf.material.node._vf.transperency;
                    unis.push(this.uniformFloatTransparency);
                }

                this.uniformBoolEnableShaded._vf.name = 'uEnableShaded'+this._styleID;
                this.uniformBoolEnableShaded._vf.type = 'SFBool';
                this.uniformBoolEnableShaded._vf.value = this._vf.enabled;
                unis.push(this.uniformBoolEnableShaded);

                return unis;
            },

            textures: function() {
                var texs = [];
                if (this._cf.surfaceNormals.node) {
                    var tex = this._cf.surfaceNormals.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex)
                }
                return texs;
            },

            styleUniformsShaderText: function(){
                var uniformText = "uniform bool uLightning"+this._styleID+";\n"+
                "uniform bool uShadows"+this._styleID+";\n"+
                //Fog uniforms
                "uniform float fogRange;\n"+
                "uniform vec3 fogColor;\n"+
                "uniform float fogType;\n"+
                "uniform bool uEnableShaded"+this._styleID+";\n";
                //Material uniforms
                if(this._cf.material.node){
                    uniformText += "uniform vec3  diffuseColor"+this._styleID+";\n"+
                    "uniform vec3  specularColor"+this._styleID+";\n"+
                    "uniform vec3  emissiveColor"+this._styleID+";\n"+
                    "uniform float shininess"+this._styleID+";\n"+
                    "uniform float transparency"+this._styleID+";\n"+
                    "uniform float ambientIntensity"+this._styleID+";\n";
                }
                return uniformText;
            },

            styleShaderText: function(){
                if(this._first){
                    return "float computeFogInterpolant(float distanceFromPoint)\n"+
                    "{\n"+
                    "  if (distanceFromPoint > fogRange){\n"+
                    "    return 0.0;\n"+
                    "  }else if (fogType == 0.0){\n"+
                    "    return clamp((fogRange-distanceFromPoint) / fogRange, 0.0, 1.0);\n"+
                    "  }else{\n"+
                    "    return clamp(exp(-distanceFromPoint / (fogRange-distanceFromPoint)), 0.0, 1.0);\n"+
                    "  }\n"+
                    "}\n";
                }else{
                    return "";
                }
            },

            lightEquationShaderText: function(){
                if(this._first){
                    return "void lighting(in float lType, in vec3 lLocation, in vec3 lDirection, in vec3 lColor, in vec3 lAttenuation, " + 
                    "in float lRadius, in float lIntensity, in float lAmbientIntensity, in float lBeamWidth, " +
                    "in float lCutOffAngle, in float ambientIntensity, in float shininess, in vec3 N, in vec3 V, inout vec3 ambient, inout vec3 diffuse, " +
                    "inout vec3 specular)\n" +
                    "{\n" +
                    "   vec3 L;\n" +
                    "   float spot = 1.0, attentuation = 0.0;\n" +
                    "   if(lType == 0.0) {\n" +
                    "       L = -normalize(lDirection);\n" +
                    "       V = normalize(V);\n" +
                    "       attentuation = 1.0;\n" +
                    "   } else{\n" +
                    "       L = (lLocation - (-V));\n" +
                    "       float d = length(L);\n" +
                    "       L = normalize(L);\n" +
                    "       V = normalize(V);\n" +
                    "       if(lRadius == 0.0 || d <= lRadius) {\n" +
                    "           attentuation = 1.0 / max(lAttenuation.x + lAttenuation.y * d + lAttenuation.z * (d * d), 1.0);\n" +
                    "       }\n" +
                    "       if(lType == 2.0) {\n" +
                    "           float spotAngle = acos(max(0.0, dot(-L, normalize(lDirection))));\n" +
                    "           if(spotAngle >= lCutOffAngle) spot = 0.0;\n" +
                    "           else if(spotAngle <= lBeamWidth) spot = 1.0;\n" +
                    "           else spot = (spotAngle - lCutOffAngle ) / (lBeamWidth - lCutOffAngle);\n" +
                    "       }\n" +
                    "   }\n" +
                    "   vec3 H = normalize( L + V );\n" +
                    "   float NdotL = max(0.0, dot(L, N));\n" +
                    "   float NdotH = max(0.0, dot(H, N));\n" +
                    "   float ambientFactor = lAmbientIntensity * ambientIntensity;\n" +
                    "   float diffuseFactor = lIntensity * NdotL;\n" +
                    "   float specularFactor = lIntensity * pow(NdotH, shininess*128.0);\n" +
                    "   ambient += lColor * ambientFactor * attentuation * spot;\n" +
                    "   diffuse += lColor * diffuseFactor * attentuation * spot;\n" +
                    "   specular += lColor * specularFactor * attentuation * spot;\n" +
                    "}\n";
                }else{
                    return "";
                }               
            },

            inlineStyleShaderText: function(){
                if(this._first){
                    return "    float fogFactor = computeFogInterpolant(length(cam_pos-ray_pos));\n";
                }else{
                    return "";
                }
            },

            lightAssigment: function(){
                var shaderText = "    if(uEnableShaded"+this._styleID+"){\n";
                for(var l=0; l<x3dom.nodeTypes.X3DLightNode.lightID; l++) {
                    shaderText += "    lighting(light"+l+"_Type, " +
                    "light"+l+"_Location, " +
                    "light"+l+"_Direction, " +
                    "light"+l+"_Color, " + 
                    "light"+l+"_Attenuation, " +
                    "light"+l+"_Radius, " +
                    "light"+l+"_Intensity, " + 
                    "light"+l+"_AmbientIntensity, " +
                    "light"+l+"_BeamWidth, " +
                    "light"+l+"_CutOffAngle, " +
                    "ambientIntensity"+this._styleID+", "+
                    "shininess"+this._styleID+", "+
                    "grad.xyz, positionE.xyz, ambient, diffuse, specular);\n";
                }
                if(this._vf.lighting == true){
                    if(this._cf.material.node){
                        shaderText += "      value.rgb = (fogColor*(1.0-fogFactor))+fogFactor*(emissiveColor"+this._styleID+" + ambient*value.rgb + diffuse*diffuseColor"+this._styleID+"*value.rgb + specular*specularColor"+this._styleID+");\n"+
                        "      value.a = value.a*(1.0-transparency"+this._styleID+");\n";
                    }else{
                        shaderText += "      value.rgb = (fogColor*(1.0-fogFactor))+fogFactor*(ambient*value.rgb + diffuse*value.rgb + specular);\n";
                    }
                }
                shaderText += "    }\n";
                return shaderText;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### SilhouetteEnhancementVolumeStyle ### */
x3dom.registerNodeType(
    "SilhouetteEnhancementVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for SilhouetteEnhancementVolumeStyle
         * @constructs x3dom.nodeTypes.SilhouetteEnhancementVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The SilhouetteEnhancementVolumeStyle node specifies that silhouettes of the assocciated volume data are going to be enhanced.
         * Voxels opacity are modified based on their normals orientation relative to the view direction. When the normal orientation is perpendicular towards the view direction,
         * voxels are darkened, whereas when it is parallel towards the view direction, the opacity is not enhanced.
         */
        function (ctx) {
            x3dom.nodeTypes.SilhouetteEnhancementVolumeStyle.superClass.call(this, ctx);


            /**
             * The silhouetteBoundaryOpacity field is a factor to specify the amount of silhouette enhancement to use.
             * @var {x3dom.fields.SFFloat} silhouetteBoundaryOpacity
             * @memberof x3dom.nodeTypes.SilhouetteEnhancementVolumeStyle
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'silhouetteBoundaryOpacity', 0);

            /**
             * The silhouetteRetainedOpacity field is a factor to specify the amount of original opacity to retain.
             * @var {x3dom.fields.SFFloat} silhouetteRetainedOpacity
             * @memberof x3dom.nodeTypes.SilhouetteEnhancementVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'silhouetteRetainedOpacity', 1);

            /**
             * The silhouetteSharpness field is an exponent factor to specify the silhouette sharpness.
             * @var {x3dom.fields.SFFloat} silhouetteSharpness
             * @memberof x3dom.nodeTypes.SilhouetteEnhancementVolumeStyle
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'silhouetteSharpness', 0.5);

            this.uniformFloatBoundaryOpacity = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatRetainedOpacity = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatSilhouetteSharpness = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DSurfaceNormals = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformBoolEnableSilhouette = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        { 
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'silhouetteBoundaryOpacity':
                        this.uniformFloatBoundaryOpacity._vf.value = this._vf.silhouetteBoundaryOpacity;
                        this.uniformFloatBoundaryOpacity.fieldChanged("value");
                        break;
                    case 'silhouetteRetainedOpacity':
                        this.uniformFloatRetainedOpacity._vf.value = this._vf.silhouetteRetainedOpacity;
                        this.uniformFloatRetainedOpacity.fieldChanged("value");
                        break;
                    case 'silhouetteSharpness':
                        this.uniformFloatSilhouetteSharpness._vf.value = this._vf.silhouetteSharpness;
                        this.uniformFloatSilhouetteSharpness.fieldChanged("value");
                        break;
                }
            },

            uniforms: function(){
                var unis = [];
                if (this._cf.surfaceNormals.node) {
                    this.uniformSampler2DSurfaceNormals._vf.name = 'uSurfaceNormals';
                    this.uniformSampler2DSurfaceNormals._vf.type = 'SFInt32';
                    this.uniformSampler2DSurfaceNormals._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DSurfaceNormals);
                }

                this.uniformFloatBoundaryOpacity._vf.name = 'uSilhouetteBoundaryOpacity'+this._styleID;
                this.uniformFloatBoundaryOpacity._vf.type = 'SFFloat';
                this.uniformFloatBoundaryOpacity._vf.value = this._vf.silhouetteBoundaryOpacity;
                unis.push(this.uniformFloatBoundaryOpacity);

                this.uniformFloatRetainedOpacity._vf.name = 'uSilhouetteRetainedOpacity'+this._styleID;
                this.uniformFloatRetainedOpacity._vf.type = 'SFFloat';
                this.uniformFloatRetainedOpacity._vf.value = this._vf.silhouetteRetainedOpacity;
                unis.push(this.uniformFloatRetainedOpacity);

                this.uniformFloatSilhouetteSharpness._vf.name = 'uSilhouetteSharpness'+this._styleID;
                this.uniformFloatSilhouetteSharpness._vf.type = 'SFFloat';
                this.uniformFloatSilhouetteSharpness._vf.value = this._vf.silhouetteSharpness;
                unis.push(this.uniformFloatSilhouetteSharpness);

                this.uniformBoolEnableSilhouette._vf.name = 'uEnableSilhouette'+this._styleID;
                this.uniformBoolEnableSilhouette._vf.type = 'SFBool';
                this.uniformBoolEnableSilhouette._vf.value = this._vf.enabled;
                unis.push(this.uniformBoolEnableSilhouette);

                return unis;
            },

            textures: function() {
                var texs = [];
                if (!(this._cf.surfaceNormals.node==null)) {
                    var tex = this._cf.surfaceNormals.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex)
                }
                return texs;
            },

            styleUniformsShaderText: function(){
                return "uniform float uSilhouetteBoundaryOpacity"+this._styleID+";\n"+
                    "uniform float uSilhouetteRetainedOpacity"+this._styleID+";\n"+
                    "uniform float uSilhouetteSharpness"+this._styleID+";\n"+
                    "uniform bool uEnableSilhouette"+this._styleID+";\n";
            },

            styleShaderText: function(){
                if (this._first){
                    return "void silhouetteEnhancement(inout vec4 orig_color, in vec4 normal, in vec3 V, in float sBoundary, in float sRetained, in float sSharpness)\n"+
                    "{\n"+
                    "   if(normal.w > 0.02){\n"+
                    "       orig_color.a = orig_color.a * (sRetained + sBoundary * pow((1.0-abs(dot(normal.xyz, V))), sSharpness));\n"+
                    "   }\n"+
                    "}\n"+
                    "\n";
                }else{
                    return "";
                }                
            },

            inlineStyleShaderText: function(){
                var inlineText = "  if(uEnableSilhouette"+this._styleID+"){\n"+
                "       silhouetteEnhancement(value, gradEye, dir, uSilhouetteBoundaryOpacity"+this._styleID+", uSilhouetteRetainedOpacity"+this._styleID+", uSilhouetteSharpness"+this._styleID+");\n"+
                "   }\n";
                return inlineText;
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### StippleVolumeStyle ### */
x3dom.registerNodeType(
    "StippleVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DVolumeRenderStyleNode,
        
        /**
         * Constructor for StippleVolumeStyle
         * @constructs x3dom.nodeTypes.StippleVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc NYI!!
         */
        function (ctx) {
            x3dom.nodeTypes.StippleVolumeStyle.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFFloat} distanceFactor
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'distanceFactor', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} interiorFactor
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'interiorFactor', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} lightingFactor
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'lightingFactor', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} gradientThreshold
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 0.4
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'gradientThreshold', 0.4);

            /**
             *
             * @var {x3dom.fields.SFFloat} gradientRetainedOpacity
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'gradientRetainedOpacity', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} gradientBoundaryOpacity
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'gradientBoundaryOpacity', 0);

            /**
             *
             * @var {x3dom.fields.SFFloat} gradientOpacityFactor
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'gradientOpacityFactor', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} silhouetteRetainedOpacity
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'silhouetteRetainedOpacity', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} silhouetteBoundaryOpacity
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'silhouetteBoundaryOpacity', 0);

            /**
             *
             * @var {x3dom.fields.SFFloat} silhouetteOpacityFactor
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'silhouetteOpacityFactor', 1);

            /**
             *
             * @var {x3dom.fields.SFFloat} resolutionFactor
             * @memberof x3dom.nodeTypes.StippleVolumeStyle
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'resolutionFactor', 1);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### ToneMappedVolumeStyle ### */
x3dom.registerNodeType(
    "ToneMappedVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for ToneMappedVolumeStyle
         * @constructs x3dom.nodeTypes.ToneMappedVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ToneMappedVolumeStyle node specifies that the associated volume rendering data is going to be rendered following the Gooch et. al. shading model.
         * Two colors are used: warm and cool to shade the volume data based on the light direction. 
         */
        function (ctx) {
            x3dom.nodeTypes.ToneMappedVolumeStyle.superClass.call(this, ctx);


            /**
             * The coolColor field specifies the color to be used for surfaces facing away of the light direction.
             * @var {x3dom.fields.SFColor} coolColor
             * @memberof x3dom.nodeTypes.ToneMappedVolumeStyle
             * @initvalue 0,0,1
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'coolColor', 0, 0, 1);

            /**
             * The warmColor field specifies the color to be used for surfaces facing towards the light direction.
             * @var {x3dom.fields.SFColor} warmColor
             * @memberof x3dom.nodeTypes.ToneMappedVolumeStyle
             * @initvalue 1,1,0
             * @field x3dom
             * @instance
             */
            this.addField_SFColor(ctx, 'warmColor', 1, 1, 0);

            this.uniformCoolColor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformWarmColor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DSurfaceNormals = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformBoolEnableToneMapped = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'coolColor':
                        this.uniformCoolColor._vf.value = this._vf.coolColor;
                        this.uniformCoolColor.fieldChanged("value");
                        break;
                    case 'warmColor':
                        this.uniformWarmColor._vf.value = this._vf.warmColor;
                        this.uniformWarmColor.fieldChanged("value");
                        break;
                }
            },

            uniforms: function(){
                var unis = [];
                if (this._cf.surfaceNormals.node) {
                    this.uniformSampler2DSurfaceNormals._vf.name = 'uSurfaceNormals';
                    this.uniformSampler2DSurfaceNormals._vf.type = 'SFInt32';
                    this.uniformSampler2DSurfaceNormals._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DSurfaceNormals);
                }

                this.uniformCoolColor._vf.name = 'uCoolColor'+this._styleID;
                this.uniformCoolColor._vf.type = 'SFColor';
                this.uniformCoolColor._vf.value = this._vf.coolColor;
                unis.push(this.uniformCoolColor);

                this.uniformWarmColor._vf.name = 'uWarmColor'+this._styleID;
                this.uniformWarmColor._vf.type = 'SFColor';
                this.uniformWarmColor._vf.value = this._vf.warmColor;
                unis.push(this.uniformWarmColor);

                this.uniformBoolEnableToneMapped._vf.name = 'uEnableToneMapped'+this._styleID;
                this.uniformBoolEnableToneMapped._vf.type = 'SFBool';
                this.uniformBoolEnableToneMapped._vf.value = this._vf.enabled;
                unis.push(this.uniformBoolEnableToneMapped);

                return unis;
            },

            textures: function() {
                var texs = [];
                if (this._cf.surfaceNormals.node) {
                    var tex = this._cf.surfaceNormals.node;
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex)
                }
                return texs;
            },

            styleUniformsShaderText: function(){
                return "uniform vec3 uCoolColor"+this._styleID+";\n"+
                "uniform vec3 uWarmColor"+this._styleID+";\n"+
                "uniform bool uEnableToneMapped"+this._styleID+";\n";
            },

            styleShaderText: function(){
                if (this._first){
                    return "void toneMapped(inout vec4 original_color, inout vec3 accum_color, in vec4 surfNormal, in vec3 lightDir, in vec3 cColor, in vec3 wColor)\n"+
                    "{\n"+
                    "   if(surfNormal.a > 0.02){\n"+
                    "       float color_factor = (1.0 + dot(lightDir, surfNormal.xyz))*0.5;\n"+
                    "       accum_color += mix(wColor, cColor, color_factor);\n"+
                    "       original_color.rgb = accum_color;\n"+
                    "   }else{\n"+
                    "       accum_color += mix(wColor, cColor, 0.5);\n"+
                    "       original_color.rgb = accum_color;\n"+
                    "   }\n"+
                    "}\n";
                }else{
                    return "";
                }
            },

            inlineStyleShaderText: function(){
                var shaderText = "    if(uEnableToneMapped"+this._styleID+"){\n"+
                "       vec3 toneColor = vec3(0.0, 0.0, 0.0);\n"+
                "       vec3 L = vec3(0.0, 0.0, 0.0);\n";
                for(var l=0; l<x3dom.nodeTypes.X3DLightNode.lightID; l++) {
                    shaderText += "       L = (light"+l+"_Type == 1.0) ? normalize(light"+l+"_Location - positionE.xyz) : -light"+l+"_Direction;\n"+
                    "       toneMapped(value, toneColor, grad, L, uCoolColor"+this._styleID+", uWarmColor"+this._styleID+");\n";
                }
                shaderText += "    }\n";
                return shaderText;
            },

            lightAssigment: function(){
                //return " value.rgb = ambient*value.rgb + diffuse*value.rgb + specular;\n";
                return "";
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */

/* ### RadarVolumeStyle ### 
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 * 
 * Copyright (C) 2016 Toshiba Corporation
 * Dual licensed under the MIT and GPL.
 */

x3dom.registerNodeType(
    "RadarVolumeStyle",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode,
        
        /**
         * Constructor for RadarVolumeStyle
         * @constructs x3dom.nodeTypes.RadarVolumeStyle
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * The YouCanEnterVolumeStyle node specifies that the associated volume data is going to be rendered using a transfer function.
         * The original opacity is mapped to a color with a function stored as a texture (transfer function).
         */
        function (ctx) {
            x3dom.nodeTypes.RadarVolumeStyle.superClass.call(this, ctx);


            /**
             * The transferFunction field is a texture that is going to be used to map each voxel value to a specific color output.
             * @var {x3dom.fields.SFNode} transferFunction
             * @memberof x3dom.nodeTypes.RadarVolumeStyle
             * @initvalue x3dom.nodeTypes.Texture
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('transferFunction', x3dom.nodeTypes.Texture);

            /**
             * NYI!!
             * @var {x3dom.fields.SFString} type
             * @memberof x3dom.nodeTypes.RadarVolumeStyle
             * @initvalue "simple"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'type', "simple");

            /**
             * The opacityFactor field is a factor to specify the amount of opacity to be considered on each sampled point along the ray traversal.
             * @var {x3dom.fields.SFFloat} opacityFactor
             * @memberof x3dom.nodeTypes.RadarVolumeStyle
             * @initvalue 6.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'opacityFactor', 6.0);

            /**
             * The lightFactor field is a factor to specify the amount of global light to be considered on each sampled point along the ray traversal.
             * @var {x3dom.fields.SFFloat} lightFactor
             * @memberof x3dom.nodeTypes.RadarVolumeStyle
             * @initvalue 1.2
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'lightFactor', 1.2);

            this.uniformFloatOpacityFactor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformFloatLightFactor = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformSampler2DTransferFunction = new x3dom.nodeTypes.Uniform(ctx);
            this.uniformBoolEnableOpacityMap = new x3dom.nodeTypes.Uniform(ctx);
        
        },
        {
            fieldChanged: function(fieldName){
                switch(fieldName){
                    case 'opacityFactor':
                        this.uniformFloatOpacityFactor._vf.value = this._vf.opacityFactor;
                        this.uniformFloatOpacityFactor.fieldChanged("value");
                        break;
                    case 'lightFactor':
                        this.uniformFloatLightFactor._vf.value = this._vf.lightFactor;
                        this.uniformFloatLightFactor.fieldChanged("value");
                        break;
                }
            },

            uniforms: function() {
                var unis = [];
                
                if (this._cf.transferFunction.node) {
                    this.uniformSampler2DTransferFunction._vf.name = 'uTransferFunction'+this._styleID;
                    this.uniformSampler2DTransferFunction._vf.type = 'SFInt32';
                    this.uniformSampler2DTransferFunction._vf.value = this._volumeDataParent._textureID++;
                    unis.push(this.uniformSampler2DTransferFunction);
                }

                this.uniformFloatOpacityFactor._vf.name = 'uOpacityFactor'+this._styleID;
                this.uniformFloatOpacityFactor._vf.type = 'SFFloat';
                this.uniformFloatOpacityFactor._vf.value = this._vf.opacityFactor;
                unis.push(this.uniformFloatOpacityFactor);

                this.uniformFloatLightFactor._vf.name = 'uLightFactor'+this._styleID;
                this.uniformFloatLightFactor._vf.type = 'SFFloat';
                this.uniformFloatLightFactor._vf.value = this._vf.lightFactor;
                unis.push(this.uniformFloatLightFactor);

                this.uniformBoolEnableOpacityMap._vf.name = 'uEnableOpacityMap'+this._styleID;
                this.uniformBoolEnableOpacityMap._vf.type = 'SFBool';
                this.uniformBoolEnableOpacityMap._vf.value = this._vf.enabled;
                unis.push(this.uniformBoolEnableOpacityMap);

                return unis;
            },

            textures: function() {
                var texs = [];
                var tex = this._cf.transferFunction.node;
                if (tex) {
                    tex._vf.repeatS = false;
                    tex._vf.repeatT = false;
                    texs.push(tex);
                }
                return texs;
            },

            styleUniformsShaderText: function() {
                var uniformsText = "uniform float uOpacityFactor"+this._styleID+";\n"+
                "uniform float uLightFactor"+this._styleID+";\n"+
                "uniform bool uEnableOpacityMap"+this._styleID+";\n";
                if (this._cf.transferFunction.node) {
                        uniformsText += "uniform sampler2D uTransferFunction"+this._styleID+";\n";
                }
                return uniformsText;
            },

            inlineStyleShaderText: function(){
/*                 var shaderText = "    if(uEnableOpacityMap"+this._styleID+"){\n"+
                "       opacityFactor = uOpacityFactor"+this._styleID+";\n"+
                "       lightFactor = uLightFactor"+this._styleID+";\n";
                if (this._cf.transferFunction.node){
                        shaderText += "       value = texture2D(uTransferFunction"+this._styleID+",vec2(value.r,0.5));\n";
                }
                shaderText += "    }\n";*/
                var shaderText = "    if(uEnableOpacityMap"+this._styleID+"){\n"+
                "       opacityFactor = uOpacityFactor"+this._styleID+";\n"+
                "       lightFactor = uLightFactor"+this._styleID+";\n";
                if (this._cf.transferFunction.node){
                        shaderText += "       if(value.r > 0.3){\n";
                        shaderText += "         value = texture2D(uTransferFunction"+this._styleID+",vec2(value.r,0.5));\n";
                        shaderText += "       }else{\n"
                        shaderText += "         value.a = 0.0;\n";
                        shaderText += "       }\n"
                }
                shaderText += "    }\n";
                return shaderText;
            }
        }
    )
);





/** @namespace x3dom.nodeTypes */
/*
 * MEDX3DOM JavaScript Library
 * http://medx3dom.org
 *
 * (C)2011 Vicomtech Research Center,
 *         Donostia - San Sebastian
 * Dual licensed under the MIT and GPL.
 *
 * Based on code originally provided by
 * http://www.x3dom.org
 */

/* ### VolumeData ### */
x3dom.registerNodeType(
    "VolumeData",
    "VolumeRendering",
    defineClass(x3dom.nodeTypes.X3DVolumeDataNode,
        
        /**
         * Constructor for VolumeData
         * @constructs x3dom.nodeTypes.VolumeData
         * @x3d x.x
         * @component VolumeRendering
         * @status experimental
         * @extends x3dom.nodeTypes.X3DVolumeDataNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The VolumeData node specifies a non-segmented volume data to be rendered with a volume rendering style.
         */
        function (ctx) {
            x3dom.nodeTypes.VolumeData.superClass.call(this, ctx);


            /**
             * Specifies the render style to be applied on the volume data.
             * @var {x3dom.fields.SFNode} renderStyle
             * @memberof x3dom.nodeTypes.VolumeData
             * @initvalue x3dom.nodeTypes.X3DVolumeRenderStyleNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('renderStyle', x3dom.nodeTypes.X3DVolumeRenderStyleNode);

            this.vrcMultiTexture = new x3dom.nodeTypes.MultiTexture(ctx);
            this.vrcVolumeTexture = null;

            this.vrcSinglePassShader = new x3dom.nodeTypes.ComposedShader(ctx);
            this.vrcSinglePassShaderVertex = new x3dom.nodeTypes.ShaderPart(ctx);
            this.vrcSinglePassShaderFragment = new x3dom.nodeTypes.ShaderPart(ctx);
            this.vrcSinglePassShaderFieldBackCoord = new x3dom.nodeTypes.Field(ctx);
            this.vrcSinglePassShaderFieldVolData = new x3dom.nodeTypes.Field(ctx);
            this.vrcSinglePassShaderFieldOffset = new x3dom.nodeTypes.Field(ctx);
            this.vrcSinglePassShaderFieldDimensions = new x3dom.nodeTypes.Field(ctx);
        
        },
        {
            initializeValues: function() {
                var initialValues ="";
                if(this._cf.renderStyle.node.initializeValues != undefined){
                    initialValues += this._cf.renderStyle.node.initializeValues();
                }
                return initialValues;
            },

            styleUniformsShaderText: function(){
                var styleUniformsText = this._cf.renderStyle.node.styleUniformsShaderText();
                this.surfaceNormalsNeeded = true;
                if(this._cf.renderStyle.node._cf.surfaceNormals && this._cf.renderStyle.node._cf.surfaceNormals.node != null){
                    styleUniformsText += "uniform sampler2D uSurfaceNormals;\n"; //Neccessary when gradient is provided
                    this.normalTextureProvided = true;
                    this.surfaceNormals = this._cf.renderStyle.node._cf.surfaceNormals.node;
                }else if(x3dom.isa(this._cf.renderStyle.node, x3dom.nodeTypes.OpacityMapVolumeStyle)){ //OpacityMap does not use surface normals
                    this.surfaceNormalsNeeded = false;
                    this.normalTextureProvided = false;
                }
                return styleUniformsText;
            },

            styleShaderText: function(){
                var styleText = "";
                if(this._cf.renderStyle.node.styleShaderText != undefined){
                    styleText += this._cf.renderStyle.node.styleShaderText();
                }
                return styleText;
            },

            inlineStyleShaderText: function(){
                return this._cf.renderStyle.node.inlineStyleShaderText();
            },

            lightAssigment: function(){
                return this._cf.renderStyle.node.lightAssigment();
            },

            //Obtain the light equation from the render style child node
            lightEquationShaderText: function(){
                return this._cf.renderStyle.node.lightEquationShaderText();
            },

            // nodeChanged is called after subtree is parsed and attached in DOM
            nodeChanged: function()
            {
                // uhhhh, manually build backend-graph scene-subtree,
                // therefore, try to mimic depth-first parsing scheme
                if (!this._cf.appearance.node) 
                {
                    var i;

                    this.addChild(new x3dom.nodeTypes.Appearance());

                    // create shortcut to volume data set
                    this.vrcVolumeTexture = this._cf.voxels.node;
                    this.vrcVolumeTexture._vf.repeatS = false;
                    this.vrcVolumeTexture._vf.repeatT = false;

                    this.vrcMultiTexture._nameSpace = this._nameSpace;
                    
                    this.vrcMultiTexture.addChild(this.vrcVolumeTexture, 'texture');
                    this.vrcVolumeTexture.nodeChanged();
                    
                    // textures from styles
                    if (this._cf.renderStyle.node.textures) {
                        var styleTextures = this._cf.renderStyle.node.textures();
                        for (i = 0; i<styleTextures.length; i++)
                        {
                            this.vrcMultiTexture.addChild(styleTextures[i], 'texture');
                            this.vrcVolumeTexture.nodeChanged();
                        }
                    }
                    
                    this._cf.appearance.node.addChild(this.vrcMultiTexture);
                    this.vrcMultiTexture.nodeChanged();

                    //Update child node private properties
                    this._cf.renderStyle.node.updateProperties(this);

                    // here goes the volume shader
                    this.vrcSinglePassShaderVertex._vf.type = 'vertex';
                    this.vrcSinglePassShaderVertex._vf.url[0]=this.vertexShaderText(x3dom.isa(this._cf.renderStyle.node, x3dom.nodeTypes.RadarVolumeStyle));

                    this.vrcSinglePassShaderFragment._vf.type = 'fragment';
                    var shaderText = "";
                    if (x3dom.isa(this._cf.renderStyle.node, x3dom.nodeTypes.X3DComposableVolumeRenderStyleNode)) {
                        shaderText += this.fragmentPreamble+
                        this.defaultUniformsShaderText(this.vrcVolumeTexture._vf.numberOfSlices, this.vrcVolumeTexture._vf.slicesOverX, this.vrcVolumeTexture._vf.slicesOverY)+
                        this.styleUniformsShaderText()+
                        this.styleShaderText()+
                        this.texture3DFunctionShaderText+
                        this.normalFunctionShaderText()+
                        this.lightEquationShaderText()+
                        this.defaultLoopFragmentShaderText(this.inlineStyleShaderText(), this.lightAssigment(), this.initializeValues());
                    }else{
                        shaderText += this._cf.renderStyle.node.fragmentShaderText(
                            this.vrcVolumeTexture._vf.numberOfSlices,
                            this.vrcVolumeTexture._vf.slicesOverX, 
                            this.vrcVolumeTexture._vf.slicesOverY);
                    }
                    this.vrcSinglePassShaderFragment._vf.url[0]= shaderText;

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderVertex, 'parts');
                    this.vrcSinglePassShaderVertex.nodeChanged();
                    
                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFragment, 'parts');
                    this.vrcSinglePassShaderFragment.nodeChanged();

                    this.vrcSinglePassShaderFieldVolData._vf.name = 'uVolData';
                    this.vrcSinglePassShaderFieldVolData._vf.type = 'SFInt32';
                    this.vrcSinglePassShaderFieldVolData._vf.value = this._textureID++;

                    this.vrcSinglePassShaderFieldDimensions._vf.name = 'dimensions';
                    this.vrcSinglePassShaderFieldDimensions._vf.type = 'SFVec3f';
                    this.vrcSinglePassShaderFieldDimensions._vf.value = this._vf.dimensions;

                    this.vrcSinglePassShaderFieldOffset._vf.name = 'offset';
                    this.vrcSinglePassShaderFieldOffset._vf.type = 'SFVec3f';
                    this.vrcSinglePassShaderFieldOffset._vf.value = "0.01 0.01 0.01"; //Default initial value
                    
                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldVolData, 'fields');
                    this.vrcSinglePassShaderFieldVolData.nodeChanged();

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldDimensions, 'fields');
                    this.vrcSinglePassShaderFieldDimensions.nodeChanged();

                    this.vrcSinglePassShader.addChild(this.vrcSinglePassShaderFieldOffset, 'fields');
 
                    //Take volume texture size for the ComposableRenderStyles offset parameter
                    this.offsetInterval = window.setInterval((function(aTex, obj) {
                        return function() {
                            x3dom.debug.logInfo('[VolumeRendering][VolumeData] Looking for Volume Texture size...');
                            var s = obj.getTextureSize(aTex);
                            if(s.valid){
                                clearInterval(obj.offsetInterval);
                                obj.vrcSinglePassShaderFieldOffset._vf.value = new x3dom.fields.SFVec3f(1.0/(s.w/aTex._vf.slicesOverX), 1.0/(s.h/aTex._vf.slicesOverY), 1.0/aTex._vf.numberOfSlices);
                                obj.vrcSinglePassShader.nodeChanged();
                                x3dom.debug.logInfo('[VolumeRendering][VolumeData] Volume Texture size obtained');
                            }
                        }
                    })(this.vrcVolumeTexture, this), 1000);
                    
                    var ShaderUniforms = this._cf.renderStyle.node.uniforms();
                    for (i = 0; i<ShaderUniforms.length; i++)
                    {
                        this.vrcSinglePassShader.addChild(ShaderUniforms[i], 'fields');
                    }
                
                    this._cf.appearance.node.addChild(this.vrcSinglePassShader);
                    this.vrcSinglePassShader.nodeChanged();
                    
                    this._cf.appearance.node.nodeChanged();
                }

                if (!this._cf.geometry.node) {
                    this.addChild(new x3dom.nodeTypes.Box());

                    this._cf.geometry.node._vf.solid = false;
                    this._cf.geometry.node._vf.hasHelperColors = false;
                    this._cf.geometry.node._vf.size = new x3dom.fields.SFVec3f(
                        this._vf.dimensions.x, this._vf.dimensions.y, this._vf.dimensions.z);

                    // workaround to trigger field change...
                    this._cf.geometry.node.fieldChanged("size");
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### IndexedQuadSet ###
x3dom.registerNodeType(
    "IndexedQuadSet",
    "CADGeometry",
    defineClass(x3dom.nodeTypes.X3DComposedGeometryNode,
        
        /**
         * Constructor for IndexedQuadSet
         * @constructs x3dom.nodeTypes.IndexedQuadSet
         * @x3d 3.3
         * @component CADGeometry
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposedGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The IndexedQuadSet node represents a 3D shape composed of a collection of individual
         * quadrilaterals (quads).
         */
        function (ctx) {
            x3dom.nodeTypes.IndexedQuadSet.superClass.call(this, ctx);


            /**
             * IndexedQuadSet uses the indices in its index field to specify the vertices of each
             * quad from the coord field.
             * @var {x3dom.fields.MFInt32} index
             * @memberof x3dom.nodeTypes.IndexedQuadSet
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFInt32(ctx, 'index', []);
        
        },
        {
            nodeChanged: function()
            {
                /*
                 This code largely taken from the IndexedTriangleSet code
                 */
                var time0 = new Date().getTime();

                this.handleAttribs();

                var colPerVert = this._vf.colorPerVertex;
                var normPerVert = this._vf.normalPerVertex;

                var indexes = this._vf.index;

                var hasNormal = false, hasTexCoord = false, hasColor = false;
                var positions, normals, texCoords, colors;

                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode);
                positions = coordNode._vf.point;

                var normalNode = this._cf.normal.node;
                if (normalNode) {
                    hasNormal = true;
                    normals = normalNode._vf.vector;
                }
                else {
                    hasNormal = false;
                }

                var texMode = "", numTexComponents = 2;
                var texCoordNode = this._cf.texCoord.node;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }
                if (texCoordNode) {
                    if (texCoordNode._vf.point) {
                        hasTexCoord = true;
                        texCoords = texCoordNode._vf.point;

                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }
                    }
                    else if (texCoordNode._vf.mode) {
                        texMode = texCoordNode._vf.mode;
                    }
                }
                else {
                    hasTexCoord = false;
                }

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                if (colorNode) {
                    hasColor = true;
                    colors = colorNode._vf.color;

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }
                else {
                    hasColor = false;
                }

                this._mesh._indices[0] = [];
                this._mesh._positions[0] = [];
                this._mesh._normals[0] = [];
                this._mesh._texCoords[0] = [];
                this._mesh._colors[0] = [];

                var i, t, cnt, faceCnt, posMax;
                var p0, p1, p2, n0, n1, n2, t0, t1, t2, c0, c1, c2;

                // if positions array too short add degenerate triangle
                while (positions.length % 4 > 0) {
                    positions.push(positions.length-1);
                }
                posMax = positions.length;

                /*
                 Note: A separate section setting the _mesh field members
                 and starting with this test:
                 if (!normPerVert || positions.length > x3dom.Utils.maxIndexableCoords)
                 is in the IndexedTriangleSet code. It has been removed
                 here until it's applicability to the QUadSet case can
                 be evaluated

                 NOTE: !normPerVert or creaseAngle == 0 means per-face normals
                 therefore, the original multi-index structure also can't
                 be retained since this means every face has other vertices
                 with other attribute properties.
                 A similar problem arises if we have more than 2^16 coordinates
                 since WebGL only supports 16-bit indices, why we have to split
                 the mesh here (which is most easiest achieved by using just the
                 same code path previously mentioned)
                 */
                //if (true)
                {
                    faceCnt = 0;
                    for (i=0; i<indexes.length; i++)
                    {
                        if ((i > 0) && (i % 4 === 3 )) {
                            faceCnt++;

                            // then pushe the the 2nd triangle
                            // of the quad on
                            this._mesh._indices[0].push(indexes[i-3]);
                            this._mesh._indices[0].push(indexes[i-1]);
                            this._mesh._indices[0].push(indexes[i]);
                        }
                        else{
                            this._mesh._indices[0].push(indexes[i]);
                        }

                        if(!normPerVert && hasNormal) {
                            this._mesh._normals[0].push(normals[faceCnt].x);
                            this._mesh._normals[0].push(normals[faceCnt].y);
                            this._mesh._normals[0].push(normals[faceCnt].z);
                        }
                        if(!colPerVert && hasColor) {
                            this._mesh._colors[0].push(colors[faceCnt].r);
                            this._mesh._colors[0].push(colors[faceCnt].g);
                            this._mesh._colors[0].push(colors[faceCnt].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(colors[faceCnt].a);
                            }
                        }
                    }

                    this._mesh._positions[0] = positions.toGL();

                    if (hasNormal) {
                        this._mesh._normals[0] = normals.toGL();
                    }
                    else {
                        this._mesh.calcNormals(normPerVert ? Math.PI : 0);
                    }

                    if (hasTexCoord) {
                        this._mesh._texCoords[0] = texCoords.toGL();
                        this._mesh._numTexComponents = numTexComponents;
                    }
                    else {
                        this._mesh.calcTexCoords(texMode);
                    }

                    if (hasColor && colPerVert) {
                        this._mesh._colors[0] = colors.toGL();
                        this._mesh._numColComponents = numColComponents;
                    }
                }

                this.invalidateVolume();
                this._mesh._numFaces = 0;
                this._mesh._numCoords = 0;
                for (i=0; i<this._mesh._indices.length; i++) {
                    this._mesh._numFaces += this._mesh._indices[i].length / 3;
                    this._mesh._numCoords += this._mesh._positions[i].length / 3;
                }

                var time1 = new Date().getTime() - time0;
                //x3dom.debug.logInfo("Mesh load time: " + time1 + " ms");
            },

            fieldChanged: function(fieldName)
            {
                var pnts = this._cf.coord.node._vf.point;

                if ( pnts.length > x3dom.Utils.maxIndexableCoords )  // are there other problematic cases?
                {
                    // TODO; implement
                    x3dom.debug.logWarning("IndexedQuadSet: fieldChanged with " +
                        "too many coordinates not yet implemented!");
                    return;
                }

                if (fieldName == "coord")
                {
                    this._mesh._positions[0] = pnts.toGL();

                    // tells the mesh that its bbox requires update
                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color")
                {
                    pnts = this._cf.color.node._vf.color;

                    if (this._vf.colorPerVertex) {

                        this._mesh._colors[0] = pnts.toGL();

                    } else if (!this._vf.colorPerVertex) {

                        var faceCnt = 0;
                        var numColComponents = 3;
                        if (x3dom.isa(this._cf.color.node, x3dom.nodeTypes.ColorRGBA)) {
                            numColComponents = 4;
                        }

                        this._mesh._colors[0] = [];

                        var indexes = this._vf.index;
                        for (i=0; i < indexes.length; ++i)
                        {
                            if ((i > 0) && (i % 3 === 0 )) {
                                faceCnt++;
                            }

                            this._mesh._colors[0].push(pnts[faceCnt].r);
                            this._mesh._colors[0].push(pnts[faceCnt].g);
                            this._mesh._colors[0].push(pnts[faceCnt].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(pnts[faceCnt].a);
                            }
                        }
                    }
                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
                else if (fieldName == "normal")
                {
                    pnts = this._cf.normal.node._vf.vector;

                    if (this._vf.normalPerVertex) {

                        this._mesh._normals[0] = pnts.toGL();

                    } else if (!this._vf.normalPerVertex) {

                        var indexes = this._vf.index;
                        this._mesh._normals[0] = [];

                        var faceCnt = 0;
                        for (i=0; i < indexes.length; ++i)
                        {
                            if ((i > 0) && (i % 3 === 0 )) {
                                faceCnt++;
                            }

                            this._mesh._normals[0].push(pnts[faceCnt].x);
                            this._mesh._normals[0].push(pnts[faceCnt].y);
                            this._mesh._normals[0].push(pnts[faceCnt].z);
                        }
                    }

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.normals = true;
                    });
                }
                else if (fieldName == "texCoord")
                {
                    var texCoordNode = this._cf.texCoord.node;
                    if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                        if (texCoordNode._cf.texCoord.nodes.length)
                            texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                    }
                    pnts = texCoordNode._vf.point;

                    this._mesh._texCoords[0] = pnts.toGL();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.texcoords = true;
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */


// ### QuadSet ###
x3dom.registerNodeType(
    "QuadSet",
    "CADGeometry",
    defineClass(x3dom.nodeTypes.X3DComposedGeometryNode,
        
        /**
         * Constructor for QuadSet
         * @constructs x3dom.nodeTypes.QuadSet
         * @x3d 3.3
         * @component CADGeometry
         * @status experimental
         * @extends x3dom.nodeTypes.X3DComposedGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The QuadSet node represents a 3D shape that represents a collection of individual planar
         * quadrilaterals.
         */
        function (ctx) {
            x3dom.nodeTypes.QuadSet.superClass.call(this, ctx);
        
        },
        {
            nodeChanged: function()
            {
                /*
                 This code largely taken from the IndexedTriangleSet code
                 */
                var time0 = new Date().getTime();

                this.handleAttribs();

                var colPerVert = this._vf.colorPerVertex;
                var normPerVert = this._vf.normalPerVertex;

                var hasNormal = false, hasTexCoord = false, hasColor = false;
                var positions, normals, texCoords, colors;

                var coordNode = this._cf.coord.node;
                x3dom.debug.assert(coordNode);
                positions = coordNode._vf.point;

                var normalNode = this._cf.normal.node;
                if (normalNode) {
                    hasNormal = true;
                    normals = normalNode._vf.vector;
                }
                else {
                    hasNormal = false;
                }

                var texMode = "", numTexComponents = 2;
                var texCoordNode = this._cf.texCoord.node;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }
                if (texCoordNode) {
                    if (texCoordNode._vf.point) {
                        hasTexCoord = true;
                        texCoords = texCoordNode._vf.point;

                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }
                    }
                    else if (texCoordNode._vf.mode) {
                        texMode = texCoordNode._vf.mode;
                    }
                }
                else {
                    hasTexCoord = false;
                }

                var numColComponents = 3;
                var colorNode = this._cf.color.node;
                if (colorNode) {
                    hasColor = true;
                    colors = colorNode._vf.color;

                    if (x3dom.isa(colorNode, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }
                else {
                    hasColor = false;
                }

                this._mesh._indices[0] = [];
                this._mesh._positions[0] = [];
                this._mesh._normals[0] = [];
                this._mesh._texCoords[0] = [];
                this._mesh._colors[0] = [];

                var i, t, cnt, faceCnt, posMax;
                var p0, p1, p2, n0, n1, n2, t0, t1, t2, c0, c1, c2;

                // if positions array too short add degenerate triangle
                while (positions.length % 4 > 0) {
                    positions.push(positions.length-1);
                }
                posMax = positions.length;

                /*
                 Note: A separate section setting the _mesh field members
                 and starting with this test:
                 if (!normPerVert || positions.length > x3dom.Utils.maxIndexableCoords)
                 is in the IndexedTriangleSet code. It has been removed
                 here until it's applicability to the QUadSet case can
                 be evaluated
                 */
                if (1)
                {
                    faceCnt = 0;
                    for (i=0; i<positions.length; i++)
                    {
                        if ((i > 0) && (i % 4 === 3 )) {
                            faceCnt++;

                            // then pushe the the 2nd triangle
                            // of the quad on
                            this._mesh._indices[0].push(i-3);
                            this._mesh._indices[0].push(i-1);
                            this._mesh._indices[0].push(i);
                        }
                        else{
                            this._mesh._indices[0].push(i);
                        }

                        if(!normPerVert && hasNormal) {
                            this._mesh._normals[0].push(normals[faceCnt].x);
                            this._mesh._normals[0].push(normals[faceCnt].y);
                            this._mesh._normals[0].push(normals[faceCnt].z);
                        }
                        if(!colPerVert && hasColor) {
                            this._mesh._colors[0].push(colors[faceCnt].r);
                            this._mesh._colors[0].push(colors[faceCnt].g);
                            this._mesh._colors[0].push(colors[faceCnt].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(colors[faceCnt].a);
                            }
                        }
                    }

                    this._mesh._positions[0] = positions.toGL();

                    if (hasNormal) {
                        this._mesh._normals[0] = normals.toGL();
                    }
                    else {
                        this._mesh.calcNormals(normPerVert ? Math.PI : 0);
                    }

                    if (hasTexCoord) {
                        this._mesh._texCoords[0] = texCoords.toGL();
                        this._mesh._numTexComponents = numTexComponents;
                    }
                    else {
                        this._mesh.calcTexCoords(texMode);
                    }

                    if (hasColor && colPerVert) {
                        this._mesh._colors[0] = colors.toGL();
                        this._mesh._numColComponents = numColComponents;
                    }
                }

                this.invalidateVolume();
                this._mesh._numFaces = 0;
                this._mesh._numCoords = 0;
                for (i=0; i<this._mesh._indices.length; i++) {
                    this._mesh._numFaces += this._mesh._indices[i].length / 3;
                    this._mesh._numCoords += this._mesh._positions[i].length / 3;
                }

                var time1 = new Date().getTime() - time0;
                //x3dom.debug.logInfo("Mesh load time: " + time1 + " ms");
            },

            fieldChanged: function(fieldName)
            {
                var pnts = this._cf.coord.node._vf.point;

                if ( pnts.length > x3dom.Utils.maxIndexableCoords )  // are there other problematic cases?
                {
                    // TODO; implement
                    x3dom.debug.logWarning("QuadSet: fieldChanged with " +
                        "too many coordinates not yet implemented!");
                    return;
                }

                if (fieldName == "coord")
                {
                    this._mesh._positions[0] = pnts.toGL();

                    // tells the mesh that its bbox requires update
                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color")
                {
                    pnts = this._cf.color.node._vf.color;

                    if (this._vf.colorPerVertex) {

                        this._mesh._colors[0] = pnts.toGL();

                    } else if (!this._vf.colorPerVertex) {

                        var faceCnt = 0;
                        var numColComponents = 3;
                        if (x3dom.isa(this._cf.color.node, x3dom.nodeTypes.ColorRGBA)) {
                            numColComponents = 4;
                        }

                        this._mesh._colors[0] = [];

                        var indexes = this._vf.index;
                        for (i=0; i < indexes.length; ++i)
                        {
                            if ((i > 0) && (i % 3 === 0 )) {
                                faceCnt++;
                            }

                            this._mesh._colors[0].push(pnts[faceCnt].r);
                            this._mesh._colors[0].push(pnts[faceCnt].g);
                            this._mesh._colors[0].push(pnts[faceCnt].b);
                            if (numColComponents === 4) {
                                this._mesh._colors[0].push(pnts[faceCnt].a);
                            }
                        }
                    }
                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
                else if (fieldName == "normal")
                {
                    pnts = this._cf.normal.node._vf.vector;

                    if (this._vf.normalPerVertex) {

                        this._mesh._normals[0] = pnts.toGL();

                    } else if (!this._vf.normalPerVertex) {

                        var indexes = this._vf.index;
                        this._mesh._normals[0] = [];

                        var faceCnt = 0;
                        for (i=0; i < indexes.length; ++i)
                        {
                            if ((i > 0) && (i % 3 === 0 )) {
                                faceCnt++;
                            }

                            this._mesh._normals[0].push(pnts[faceCnt].x);
                            this._mesh._normals[0].push(pnts[faceCnt].y);
                            this._mesh._normals[0].push(pnts[faceCnt].z);
                        }
                    }

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.normals = true;
                    });
                }
                else if (fieldName == "texCoord")
                {
                    var texCoordNode = this._cf.texCoord.node;
                    if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                        if (texCoordNode._cf.texCoord.nodes.length)
                            texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                    }
                    pnts = texCoordNode._vf.point;

                    this._mesh._texCoords[0] = pnts.toGL();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.texcoords = true;
                    });
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### CADLayer ###
x3dom.registerNodeType(
    "CADLayer",
    "CADGeometry",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for CADLayer
         * @constructs x3dom.nodeTypes.CADLayer
         * @x3d 3.3
         * @component CADGeometry
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The CADLayer node defines a hierarchy of nodes used for showing layer structure for the CAD model.
         */
        function (ctx) {
            x3dom.nodeTypes.CADLayer.superClass.call(this, ctx);

            /**
             * The name field describes the content of the layer.
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.CADLayer
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx,'name', "");
            // to be implemented: the 'visible' field
            // there already is a 'render' field defined in base class
            // which basically defines visibility...
            // NOTE: bbox stuff also already defined in a base class!
        
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */


// ### CADAssembly ###
x3dom.registerNodeType(
    "CADAssembly",
    "CADGeometry",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for CADAssembly
         * @constructs x3dom.nodeTypes.CADAssembly
         * @x3d 3.3
         * @component CADGeometry
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The CADAssembly node holds a set of assemblies or parts grouped together.
         */
        function (ctx) {
            x3dom.nodeTypes.CADAssembly.superClass.call(this, ctx);


            /**
             * The name field documents the name of this CAD structure.
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.CADAssembly
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### CADPart ###
// According to the CADGeometry specification,
// the CADPart node has transformation fields identical to
// those used in the Transform node, therefore just inherit it
x3dom.registerNodeType(
    "CADPart",
    "CADGeometry",
    defineClass(x3dom.nodeTypes.Transform,
        
        /**
         * Constructor for CADPart
         * @constructs x3dom.nodeTypes.CADPart
         * @x3d 3.3
         * @component CADGeometry
         * @status full
         * @extends x3dom.nodeTypes.Transform
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The CADPart node is a grouping node that defines a coordinate system for its children that is
         * relative to the coordinate systems of its ancestors.
         */
        function (ctx) {
            x3dom.nodeTypes.CADPart.superClass.call(this, ctx);


            /**
             * The name field describes the content of the part.
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.CADPart
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */


// ### CADFace ###
x3dom.registerNodeType(
    "CADFace",
    "CADGeometry",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for CADFace
         * @constructs x3dom.nodeTypes.CADFace
         * @x3d 3.3
         * @component CADGeometry
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The CADFace node holds the geometry representing a face of a part.
         */
        function (ctx) {
            x3dom.nodeTypes.CADFace.superClass.call(this, ctx);


            /**
             * The name field specifies the name of the CADFace.
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.CADFace
             * @initvalue ""
             * @field x3d
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");

            /**
             * The shape field contains the geometry and appearance for the face or an LOD node containing different
             * detail levels of the shape. If an LOD node is provided, each child of the LOD node shall be a single
             * Shape of varying complexity.
             * @var {x3dom.fields.SFNode} shape
             * @memberof x3dom.nodeTypes.CADFace
             * @initvalue X3DShapeNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('shape', x3dom.nodeTypes.X3DShapeNode);
        
        },
        {
            getVolume: function()
            {
                var vol = this._graph.volume;

                if (!this.volumeValid() && this._vf.render)
                {
                    var child = this._cf.shape.node;
                    var childVol = (child && child._vf.render === true) ? child.getVolume() : null;

                    if (childVol && childVol.isValid())
                        vol.extendBounds(childVol.min, childVol.max);
                }

                return vol;
            },

            collectDrawableObjects: function (transform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes)
            {
                if (singlePath && (this._parentNodes.length > 1))
                    singlePath = false;

                if (singlePath && (invalidateCache = invalidateCache || this.cacheInvalid()))
                    this.invalidateCache();

                if (!this._cf.shape.node ||
                    (planeMask = drawableCollection.cull(transform, this.graphState(), singlePath, planeMask)) < 0) {
                    return;
                }

                var cnode, childTransform;

                if (singlePath) {
                    if (!this._graph.globalMatrix) {
                        this._graph.globalMatrix = this.transformMatrix(transform);
                    }
                    childTransform = this._graph.globalMatrix;
                }
                else {
                    childTransform = this.transformMatrix(transform);
                }

                if ( (cnode = this._cf.shape.node) ) {
                    cnode.collectDrawableObjects(childTransform, drawableCollection, singlePath, invalidateCache, planeMask, clipPlanes);
                }
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */


/* ### Patch ### */
x3dom.registerNodeType(
    "Patch",
    "BVHRefiner",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Patch
         * @constructs x3dom.nodeTypes.Patch
         * @x3d x.x
         * @component BVHRefiner
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Patch node is used by the BVHRefiner.
         */
        function (ctx) {
            x3dom.nodeTypes.Patch.superClass.call(this, ctx);


            /**
             * Size of the entire terrain.
             * @var {x3dom.fields.SFVec2f} size
             * @memberof x3dom.nodeTypes.Patch
             * @initvalue 2,2
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'size', 2, 2);

            /**
             * Subdivision of the regualr geometry. The higher, the finer the geometry.
             * @var {x3dom.fields.SFVec2f} subdivision
             * @memberof x3dom.nodeTypes.Patch
             * @initvalue 1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'subdivision', 1, 1);

            /**
             * Center position of the geometry.
             * @var {x3dom.fields.SFVec3f} center
             * @memberof x3dom.nodeTypes.Patch
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'center', 0, 0, 0);

            /**
             * Render mode of the mesh.
             * @var {x3dom.fields.MFString} primType
             * @memberof x3dom.nodeTypes.Patch
             * @initvalue ['TRIANGLES']
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'primType', ['TRIANGLES']);

            var sx = this._vf.size.x, sy = this._vf.size.y;
            var subx = this._vf.subdivision.x, suby = this._vf.subdivision.y;

            this._indexBufferTriangulationParts = [];

            var x = 0, y = 0;
            var xstep = sx / subx / 2;
            var ystep = sy / suby / 2;

            sx /= 2;
            sy /= 2;
            var countX = subx * 2 + 1;
            var countY = suby * 2 + 1;

            /*************************************************************/
            // VERTEX-INFORMATION
            /*************************************************************/
            for (y = 0; y <= suby * 2; y++) {
                for (x = 0; x <= subx * 2; x++) {
                    this._mesh._positions[0].push(this._vf.center.x + x * xstep - sx);
                    this._mesh._positions[0].push(this._vf.center.y + y * ystep - sy);
                    this._mesh._positions[0].push(this._vf.center.z);
                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(0);
                    this._mesh._normals[0].push(1);
                    this._mesh._texCoords[0].push(x / (subx * 2));
                    this._mesh._texCoords[0].push(y / (suby * 2));
                }
            }

            /*************************************************************/
            // regular triangulation
            for (y = 0; y < countY - 2; y += 2) {
                for (x = 0; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: 0,
                count: subx * suby * 6
            });

            /*************************************************************/
            // finer bottom triangulation
            for (y = 0; y < countY - 2; y += 2) {
                for (x = 0; x < 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 1) * countX);

                    this._mesh._indices[0].push((x) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            for (y = 0; y < countY - 2; y += 2) {
                for (x = 2; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].offset +
                    this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].count * 2,
                count: subx * suby * 6 + suby * 9
            });

            /*************************************************************/
            // finer top triangulation
            for (y = 0; y < countY - 2; y += 2) {
                for (x = countX - 3; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            for (y = 0; y < countY - 2; y += 2) {
                for (x = 0; x < countX - 4; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].offset +
                    this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].count * 2,
                count: subx * suby * 6 + suby * 9
            });

            /*************************************************************/
            // finer right triangulation
            for (y = 2; y < countY - 2; y += 2) {
                for (x = 0; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            for (y = 0; y < 2; y += 2) {
                for (x = 0; x < countX - 2; x += 2) {

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + y * countX);

                    this._mesh._indices[0].push((x + 1) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].offset +
                    this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].count * 2,
                count: subx * suby * 6 + subx * 9
            });

            /*************************************************************/
            // finer left triangulation
            for (y = countY - 3; y < countY - 2; y += 2) {
                for (x = 0; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            for (y = 0; y < countY - 4; y += 2) {
                for (x = 0; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].offset +
                    this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].count * 2,
                count: subx * suby * 6 + subx * 9
            });

            /*************************************************************/
            // finer topLeft triangulation
            for (y = countY - 3; y < countY - 2; y += 2) {
                for (x = countX - 3; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            for (y = 0; y < countY - 4; y += 2) {
                for (x = 0; x < countX - 4; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            for (y = 0; y < countY - 4; y += 2) {
                for (x = countX - 3; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            for (y = countY - 3; y < countY - 2; y += 2) {
                for (x = 0; x < countX - 4; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].offset +
                    this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].count * 2,
                count: subx * suby * 6 + subx * 9 + (suby - 1) * 9 + 3
            });

            /*************************************************************/
            // finer bottomLeft triangulation
            for (y = countY - 3; y < countY - 2; y += 2) {
                for (x = 0; x < 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 1) * countX);

                    this._mesh._indices[0].push((x) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            for (y = 0; y < countY - 4; y += 2) {
                for (x = 2; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            // finer left
            for (y = countY - 3; y < countY - 2; y += 2) {
                for (x = 2; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 1) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            // finer bottom
            for (y = 0; y < countY - 4; y += 2) {
                for (x = 0; x < 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 1) * countX);

                    this._mesh._indices[0].push((x) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].offset +
                    this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].count * 2,
                count: subx * suby * 6 + subx * 9 + (suby - 1) * 9 + 3
            });

            /*************************************************************/
            // finer bottomRight triangulation
            for (y = 0; y < 2; y += 2) {
                for (x = 0; x < 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 1) * countX);

                    this._mesh._indices[0].push((x) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + y * countX);

                    this._mesh._indices[0].push((x + 1) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            for (y = 2; y < countY - 2; y += 2) {
                for (x = 2; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            // finer bottom
            for (y = 2; y < countY - 2; y += 2) {
                for (x = 0; x < 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 1) * countX);

                    this._mesh._indices[0].push((x) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            // finer right
            for (y = 0; y < 2; y += 2) {
                for (x = 2; x < countX - 2; x += 2) {

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + y * countX);

                    this._mesh._indices[0].push((x + 1) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].offset +
                    this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].count * 2,
                count: subx * suby * 6 + subx * 9 + (suby - 1) * 9 + 3
            });

            /*************************************************************/
            // finer topRight triangulation
            // finer right
            for (y = 0; y < 2; y += 2) {
                for (x = countX - 3; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + y * countX);

                    this._mesh._indices[0].push((x + 1) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            for (y = 2; y < countY - 2; y += 2) {
                for (x = 0; x < countX - 4; x += 2) {
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x) + y * countX);

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                }
            }

            //  finer top
            for (y = 2; y < countY - 2; y += 2) {
                for (x = countX - 3; x < countX - 2; x += 2) {
                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            // finer right
            for (y = 0; y < 2; y += 2) {
                for (x = 0; x < countX - 4; x += 2) {

                    this._mesh._indices[0].push((x) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + (y + 2) * countX);

                    this._mesh._indices[0].push((x) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);

                    this._mesh._indices[0].push((x + 2) + (y + 2) * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 2) + y * countX);

                    this._mesh._indices[0].push((x + 2) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x + 1) + y * countX);

                    this._mesh._indices[0].push((x + 1) + y * countX);
                    this._mesh._indices[0].push((x + 1) + (y + 1) * countX);
                    this._mesh._indices[0].push((x) + y * countX);
                }
            }

            this._indexBufferTriangulationParts.push({
                offset: this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].offset +
                    this._indexBufferTriangulationParts[this._indexBufferTriangulationParts.length - 1].count * 2,
                count: subx * suby * 6 + subx * 9 + (suby - 1) * 9 + 3
            });

            this._mesh._invalidate = true;
            this._mesh._numFaces = this._mesh._indices[0].length / 3;
            this._mesh._numCoords = this._mesh._positions[0].length / 3;
        
        },
        {
            hasIndexOffset: function() {
                return true;
            },

            getTriangulationAttributes: function(triangulationIndex){
                return this._indexBufferTriangulationParts[triangulationIndex];
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */


// ### BVHRefiner ###
x3dom.registerNodeType(
    "BVHRefiner",
    "BVHRefiner",
    defineClass(x3dom.nodeTypes.X3DLODNode,
        
        /**
         * Constructor for BVHRefiner
         * @constructs x3dom.nodeTypes.BVHRefiner
         * @x3d x.x
         * @component BVHRefiner
         * @extends x3dom.nodeTypes.X3DLODNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The node handles wmts conform datasets like e.g. terrain-data, city-data, point-clouds etc.
         */
        function (ctx) {
            x3dom.nodeTypes.BVHRefiner.superClass.call(this, ctx);


            /**
             * Parameter to influence refinement behaviour. The higher, the better the performance but quality gets more worse.
             * @var {x3dom.fields.SFFloat} factor
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'factor', 1.0);

            /**
             * Maximum refinement depth of dataset.
             * @var {x3dom.fields.SFInt32} maxDepth
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 3
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'maxDepth', 3);

            /**
             * Minimum depth that should be rendered.
             * @var {x3dom.fields.SFInt32} minDepth
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'minDepth', 0);

            /**
             * Factor to reduce loading speed to get interactive framerates during interaction.
             * @var {x3dom.fields.SFInt32} smoothLoading
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'smoothLoading', 1);

            /**
             * Depth that should be rendered during interaction.
             * @var {x3dom.fields.SFInt32} interactionDepth
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue this._vf.maxDepth
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'interactionDepth', this._vf.maxDepth);

            /**
             * Size of the geometry in case of using terrain datasets.
             * @var {x3dom.fields.SFVec2f} size
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'size', 1, 1);
            // TODO: delete if octree will be deleted

            /**
             * Size of octree dataset.
             * @var {x3dom.fields.SFVec3f} octSize
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 1,1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'octSize', 1, 1, 1);

            /**
             * Subdivision of datasets in case of terrain to define the quality of the rendered dataset.
             * @var {x3dom.fields.SFVec2f} subdivision
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'subdivision', 1, 1);

            /**
             * Url to the dataset.
             * @var {x3dom.fields.SFString} url
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'url', "");

            /**
             * Url to the elevation dataset in case of terrain.
             * @var {x3dom.fields.SFString} elevationUrl
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'elevationUrl', "");

            /**
             * Url to the surface texture dataset in case of terrain.
             * @var {x3dom.fields.SFString} textureUrl
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'textureUrl', "");

            /**
             * Url to the normal dataset in case of terrain.
             * @var {x3dom.fields.SFString} normalUrl
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normalUrl', "");

            /**
             * Defines if 2d dataset or 3d dataset is utilized.
             * @var {x3dom.fields.SFString} mode
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue "3d"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'mode', "3d");

            /**
             * Defines the structure of the dataset that should be exploited.
             * @var {x3dom.fields.SFString} subMode
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue "wmts"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'subMode', "wmts");

            /**
             * Image format of the images in elevation dataset.
             * @var {x3dom.fields.SFString} elevationFormat
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue "png"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'elevationFormat', "png");

            /**
             * Image format of surface texture dataset.
             * @var {x3dom.fields.SFString} textureFormat
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue "png"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'textureFormat', "png");

            /**
             * Image format of normal texture dataset.
             * @var {x3dom.fields.SFString} normalFormat
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue "png"
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'normalFormat', "png");

            /**
             * Maximum elevation that is defined through color white in elevation dataset images.
             * @var {x3dom.fields.SFFloat} maxElevation
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'maxElevation', 1.0);

            /**
             * Should be true if a normal dataset is required.
             * @var {x3dom.fields.SFBool} useNormals
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'useNormals', true);

            /**
             * Specifies whether this geometry should be rendered with or without lighting.
             * @var {x3dom.fields.SFBool} lit
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'lit', true);

            /**
             * Defines the count of divisions that should be produced. Creates dynamic datastructure for pot counts.
             * @var {x3dom.fields.SFInt32} bvhCount
             * @memberof x3dom.nodeTypes.BVHRefiner
             * @initvalue 8
             * @field x3dom
             * @instance
             */
            this.addField_SFInt32(ctx, 'bvhCount', 8);

            this.creationSmooth = 0;
            this.togglePoints = true;
            this.nodeProducer = new NodeProducer();
            // calculation of the array-size for storing the quad-pointers
            var nodeListSize = 0;
            for (var x = 0; x <= this._vf.maxDepth; x++) {
                nodeListSize += Math.pow(4, x);
            }
            this.nodeList = new Array(nodeListSize);

            if (this._vf.mode === "bin") {
                // creating the root-node of the quadtree
                this.rootNode = new QuadtreeNodeBin(ctx, this, 0, 0, 0, null);
            }
            else if (this._vf.mode === "3d" || this._vf.mode === "2d") {
                // 2D-Mesh that will represent the geometry of this node
                var geometry = new x3dom.nodeTypes.Plane(ctx);
                // definition the parameters of the geometry
                geometry._vf.subdivision.setValues(this._vf.subdivision);
                geometry.fieldChanged("subdivision");
                geometry._vf.size.setValues(this._vf.size);
                //geometry._vf.center.setValues(this._vf.center);

                if (this._vf.mode === "2d") {
                    if (this._vf.subMode === "wmts"){
                        // creating the root-node of the quadtree
                        this.rootNode = new QuadtreeNode2dWMTS(ctx, this, 0, 0,
                            x3dom.fields.SFMatrix4f.identity(),
                            0, 0, geometry);
                    }
                    else {
                        // creating the root-node of the quadtree
                        this.rootNode = new QuadtreeNode2D(ctx, this, 0, 0,
                            x3dom.fields.SFMatrix4f.identity(),
                            0, 0, geometry, "/", 1);
                    }
                }
                else {
                    if (this._vf.subMode === "32bit"){
                        this.rootNode = new QuadtreeNode3D_32bit(ctx, this, 0, 0,
                            x3dom.fields.SFMatrix4f.identity(),
                            0, 0, geometry);
                    }
                    else {
                        geometry = new x3dom.nodeTypes.Patch(ctx);
                        this.rootNode = new QuadtreeNode3D(ctx, this, 0, 0,
                            x3dom.fields.SFMatrix4f.identity(),
                            0, 0, geometry);
                    }
                }
            }
            else if (this._vf.mode === "bvh"){
                // creating the root-node of the quadtree
                this.rootNode = new BVHNode(ctx, this, 0, "/", 1, this._vf.bvhCount);
            }
            else {
                x3dom.debug.logError("Error attribute mode. Value: '" + this._vf.mode +
                    "' isn't conform. Please use type 'bin', '2d' or '3d'");
            }
        
        },
        {
            visitChildren: function(transform, drawableCollection, singlePath, invalidateCache, planeMask) {
                var x3dElement = this._nameSpace.doc._x3dElem;

                if (this._vf.mode === "oct") {
                    if (x3dElement.runtime.isReady && this.togglePoints){
                        x3dElement.runtime.togglePoints();
                        this.togglePoints = false;
                        this.view = drawableCollection.viewarea;
                    }
                    this.creationSmooth++;
                    singlePath = false;         // TODO (specify if unique node path or multi-parent)
                    invalidateCache = true;     // TODO (reuse world transform and volume cache)
                    this.rootNode.collectDrawables(transform, drawableCollection, singlePath, invalidateCache, planeMask);

                    if (!this.view.isMovingOrAnimating() && ((this.creationSmooth % this._vf.smoothLoading) === 0)) {
                        this.nodeProducer.CreateNewNode();
                    }
                }
                else {
                    if (x3dElement.runtime.isReady && this.togglePoints){
                        this.view = x3dElement.runtime.canvas.doc._viewarea;
                        this.togglePoints = false;
                    }
                    this.createChildren = 0;
                    this.creationSmooth++;
                    singlePath = false;         // TODO (specify if unique node path or multi-parent)
                    invalidateCache = true;     // TODO (reuse world transform and volume cache)
                    this.rootNode.collectDrawables(transform, drawableCollection, singlePath, invalidateCache, planeMask);
                    if (!this.view.isMovingOrAnimating() && ((this.creationSmooth % this._vf.smoothLoading) === 0)) {
                        this.nodeProducer.CreateNewNode();
                    }
                }
            },

            getVolume: function()
            {
                var vol = this._graph.volume;

                if (!this.volumeValid() && this._vf.render)
                {
                    var childVol = this.rootNode.getVolume();
                    if (childVol && childVol.isValid())
                        vol.extendBounds(childVol.min, childVol.max);
                }

                return vol;
            }
        }
    )
);

/*
 * All bvh-nodes must login at this element if they want to
 * create their children on next frame. This node decides what node
 * has the highest priority and creates its four children on the next
 * frame. On the next frame the same course will happen till all children
 * are created.
 * @returns {NodeProducer}
 */
function NodeProducer()
{
    // Node thats children should be created after current frame is rendered
    var nextNode        = null;
    // Distance of the node that should be created after current frame
    var nearestDistance = 1000000;
    // Depth of the node that should be created after current frame
    var smallestDepth   = 1000000;



    /*
     * Decides if the given node has a smaller or the same depth as the
     * current "nextNode", and if this is true if the distance to camera
     * is less. In this case it will be new "nextNode"
     * @param {Node of BVHRefiner} node node that will create children
     * @param {type} distance distance of the node to camera
     * @returns {null}
     */
    this.AddNewNode = function(node, distance){
        if (node.Level() < smallestDepth) {
            smallestDepth = node.Level();
            nextNode = node;
        }
        if (node.Level() === smallestDepth){
            if (distance < nearestDistance){
                distance = nearestDistance;
                nextNode = node;
            }
        }
    };



    /*
     * Creates the children of the node with highest priority in the last frame
     * @returns {null}
     */
    this.CreateNewNode = function(){
        if (nextNode !== null) {
            nextNode.CreateChildren();
        }
        nextNode = null;
        smallestDepth = 1000;
    };
}


/*******************************************************************************
 *******************************************************************************
 **************************** QuadtreeNode2dWMTS *******************************
 *******************************************************************************
 *******************************************************************************
 *
 * Defines one 2D node (plane) of a quadtree that represents a part
 * (nxn vertices) of the whole mesh.
 * @param {object} ctx context
 * @param {x3dom.nodeTypes.BVHRefiner} bvhRefiner root bvhRefiner node
 * @param {number} level level of the node within the quadtree
 * @param {number} nodeNumber id of the node within the level
 * @param {x3dom.fields.SFMatrix4f} nodeTransformation transformation matrix
 *                                  that defines scale and position
 * @param {number} columnNr column number in the wmts matrix within the level
 * @param {number} rowNr row number in the wmts matrix within the level
 * @param {x3dom.nodeTypes.Plane} geometry plane
 * @returns {QuadtreeNode2dWMTS}
 */
function QuadtreeNode2dWMTS(ctx, bvhRefiner, level, nodeNumber, nodeTransformation,
                            columnNr, rowNr, geometry)
{
    // array with the maximal four child nodes
    var children = [];
    // drawable component of this node
    var shape = new x3dom.nodeTypes.Shape();
    // position of the node in world space
    var position = null;
    // true if this component is available and renderable
    var readyState = false;
    // checks if children are ready
    var childrenReadyState = false;
    // url of the data source
    var url = bvhRefiner._vf.textureUrl + "/" + level + "/" + columnNr +
        "/" + rowNr + "." + (bvhRefiner._vf.textureFormat).toLowerCase();
    // defines the resizing factor
    var resizeFac = (bvhRefiner._vf.size.x + bvhRefiner._vf.size.y) / 2.0;
    // object that stores all information to do a frustum culling
    var cullObject = {};



    /*
     * Initializes all nodeTypes that are needed to create the drawable
     * component for this node
     * @returns {null}
     */
    function initialize() {

        // appearance of the drawable component of this node
        var appearance = new x3dom.nodeTypes.Appearance(ctx);
        // texture that should represent the surface-data of this node
        var texture = new x3dom.nodeTypes.ImageTexture(ctx);

        // definition of the nameSpace of this shape
        shape._nameSpace = bvhRefiner._nameSpace;

        // create height-data
        var texProp = new x3dom.nodeTypes.TextureProperties(ctx);
        texProp._vf.boundaryModeS = "CLAMP_TO_EDGE";
        texProp._vf.boundaryModeT = "CLAMP_TO_EDGE";
        texProp._vf.boundaryModeR = "CLAMP_TO_EDGE";
        texProp._vf.minificationFilter = "LINEAR";
        texProp._vf.magnificationFilter = "LINEAR";
        texture.addChild(texProp, "textureProperties");
        texture.nodeChanged();
        // definition of texture
        texture._nameSpace = bvhRefiner._nameSpace;
        texture._vf.url[0] = url;

        // calculate the average position of the node
        position = nodeTransformation.e3();

        // add textures to the appearence of this node
        appearance.addChild(texture);
        texture.nodeChanged();

        // create shape with geometry and appearance data
        shape.addChild(appearance);
        appearance.nodeChanged();
        shape.addChild(geometry);
        geometry.nodeChanged();

        // add shape to bvhRefiner object
        bvhRefiner.addChild(shape);
        shape.nodeChanged();

        // definition the static properties of cullObject
        cullObject.boundedNode = shape;
        cullObject.volume = shape.getVolume();
    }



    /*
     * Creates the four children
     * @returns {null}
     */
    this.CreateChildren = function () {
        create();
    };



    /*
     * Creates the four children
     * @returns {null}
     */
    function create() {

        // calculation of children number nodeNumber within their level
        var deltaR = Math.sqrt(Math.pow(4, level));
        var deltaR1 = Math.sqrt(Math.pow(4, level + 1));
        var lt = Math.floor(nodeNumber / deltaR) * 4 * deltaR +
            (nodeNumber % deltaR) * 2;
        var rt = lt + 1;
        var lb = lt + deltaR1;
        var rb = lb + 1;

        // calculation of the scaling factor
        var s = (bvhRefiner._vf.size).multiply(0.25);

        // creation of all children
        children.push(new QuadtreeNode2dWMTS(ctx, bvhRefiner, (level + 1), lt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2), geometry));
        children.push(new QuadtreeNode2dWMTS(ctx, bvhRefiner, (level + 1), rt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2), geometry));
        children.push(new QuadtreeNode2dWMTS(ctx, bvhRefiner, (level + 1), lb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2 + 1), geometry));
        children.push(new QuadtreeNode2dWMTS(ctx, bvhRefiner, (level + 1), rb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2 + 1), geometry));
    }



    /*
     * Returns the shape of this node
     * @returns {x3dom.nodeTypes.Shape}
     */
    this.Shape = function () {
        return shape;
    };



    /*
     * Runs only local ready() method. This is needed from parent to ask if
     * all children are ready to render or not
     * @returns {Boolean} true if ready to render, else false
     */
    this.Ready = function(){
        if (shape._webgl !== undefined && shape._webgl.texture !== undefined) {
            return ready();
        }

        return false;
    };



    /*
     * Iterates through all textures of this node and sets readState parameter
     * to true if all textures have been loaded to gpu yet, false if not.
     * @returns {Boolean} true if ready to render, else false
     */
    function ready() {
        readyState = true;
        for (var i = 0; i < shape._webgl.texture.length; i++){
            if (!shape._webgl.texture[i].texture.ready){
                readyState = false;
            }


        }

        return readyState;
    }



    /*
     * Updates the loading state of children and initializes this node
     * if this wasn't done before
     * @param {x3dom.DrawableCollection} drawableCollection
     * @param {x3dom.fields.SFMatrix4f} transform outer transformation matrix
     * @returns {null}
     */
    function updateLoadingState(drawableCollection, transform){

        childrenReadyState = true;
        for (var i = 0; i < children.length; i++){
            if (!children[i].Ready()) {
                childrenReadyState = false;
            }
        }

        if (children.length < 4){
            childrenReadyState = false;
        }
        else if (childrenReadyState) {
            for (var i = 0; i < children.length; i++){
                children[i].Shape()._vf.render = true;
            }
        }

        if (shape._webgl === undefined || shape._webgl.texture === undefined) {
            drawableCollection.context.setupShape(drawableCollection.gl,
                {shape:shape,
                    transform:transform},
                drawableCollection.viewarea);
        }
        else {
            ready();
        }
    }



    /*
     * Decides to create new children and if the node shoud be drawn or not
     * @param {x3dom.fields.SFMatrix4f} transform outer transformation matrix
     * @param {x3dom.DrawableCollection} drawableCollection
     * @param {bool} singlePath
     * @param {bool} invalidateCache
     * @param {number} planeMask
     * @returns {null}
     */
    this.collectDrawables = function (transform, drawableCollection,
                                      singlePath, invalidateCache, planeMask) {

        // definition the actual transformation of the node
        cullObject.localMatrix = nodeTransformation;
        // calculation of new plane mask
        planeMask = drawableCollection.cull(nodeTransformation, cullObject, singlePath, planeMask);

        // Checks the actual loading state of itself and children if something wasn't loaded in last frame
        if (!readyState || !childrenReadyState)
            updateLoadingState(drawableCollection, nodeTransformation);

        if (readyState && planeMask >= 0) {
            var mat_view = drawableCollection.viewMatrix;
            var vPos = mat_view.multMatrixPnt(nodeTransformation.multMatrixPnt(position));
            var distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));

            if ((distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) * resizeFac / bvhRefiner._vf.factor) || level < bvhRefiner._vf.minDepth) {
                if (bvhRefiner.view.isMovingOrAnimating() && children.length === 0 ||
                    bvhRefiner.view.isMovingOrAnimating() && level >= bvhRefiner._vf.interactionDepth) {
                    shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                }
                else {
                    if (children.length === 0) {
                        bvhRefiner.nodeProducer.AddNewNode(that, distanceToCamera);
                        shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                    }
                    else {
                        if (childrenReadyState) {
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                            }
                        }
                        else {
                            shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                                children[i].Shape()._vf.render = false;
                            }
                        }
                    }
                }
            }
            else {
                shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
            }
        }
    };



    /*
     * Returns the volume of this node
     * @returns {x3dom.fields.BoxVolume}
     */
    this.getVolume = function() {
        return shape.getVolume();
    };


    /*
     * Returns the level of this node
     * @returns {number}
     */
    this.Level = function () {
        return level;
    };


    // reference to get access to public methods within this node
    var that = this;
    // initializes this node directly after creating
    initialize();
}



/*******************************************************************************
 *******************************************************************************
 **************************** QuadtreeNode2D ***********************************
 *******************************************************************************
 *******************************************************************************
 *
 * Defines one 2D node (plane) of a quadtree that represents a part
 * (nxn vertices) of the whole mesh.
 * @param {object} ctx context
 * @param {x3dom.nodeTypes.BVHRefiner} bvhRefiner root bvhRefiner node
 * @param {number} level level of the node within the quadtree
 * @param {number} nodeNumber id of the node within the level
 * @param {x3dom.fields.SFMatrix4f} nodeTransformation transformation matrix
 *                                  that defines scale and position
 * @param {number} columnNr column number in the wmts matrix within the level
 * @param {number} rowNr row number in the wmts matrix within the level
 * @param {x3dom.nodeTypes.Plane} geometry plane
 * @param {string} path path to the nodes data
 * @param {type} imgNumber number of the image within the path
 * @returns {QuadtreeNode2D}
 */
function QuadtreeNode2D(ctx, bvhRefiner, level, nodeNumber, nodeTransformation,
                        columnNr, rowNr, geometry, path, imgNumber)
{

    // array with the maximal four child nodes
    var children = [];
    // drawable component of this node
    var shape = new x3dom.nodeTypes.Shape();
    // position of the node in world space
    var position = null;
    // true if this component is available and renderable
    var readyState = false;
    // checks if children are ready
    var childrenReadyState = false;
    // true if components are available and renderable
    var exists = true;
    // url of the data source
    var url = bvhRefiner._vf.textureUrl + path + imgNumber + "." + bvhRefiner._vf.textureFormat;
    // defines the resizing factor
    var resizeFac = (bvhRefiner._vf.size.x + bvhRefiner._vf.size.y) / 2.0;
    // object that stores all information to do a frustum culling
    var cullObject = {};



    /*
     * Initializes all nodeTypes that are needed to create the drawable
     * component for this node
     * @returns {null}
     */
    function initialize() {

        // appearance of the drawable component of this node
        var appearance = new x3dom.nodeTypes.Appearance(ctx);
        // texture that should represent the surface-data of this node
        var texture = new x3dom.nodeTypes.ImageTexture(ctx);

        // definition of the nameSpace of this shape
        shape._nameSpace = bvhRefiner._nameSpace;

        // create height-data
        var texProp = new x3dom.nodeTypes.TextureProperties(ctx);
        texProp._vf.boundaryModeS = "CLAMP_TO_EDGE";
        texProp._vf.boundaryModeT = "CLAMP_TO_EDGE";
        texProp._vf.boundaryModeR = "CLAMP_TO_EDGE";
        texProp._vf.minificationFilter = "LINEAR";
        texProp._vf.magnificationFilter = "LINEAR";
        texture.addChild(texProp, "textureProperties");
        texture.nodeChanged();
        // definition of texture
        texture._nameSpace = bvhRefiner._nameSpace;
        texture._vf.url[0] = url;

        // calculate the average position of the node
        position = nodeTransformation.e3();

        // add textures to the appearence of this node
        appearance.addChild(texture);
        texture.nodeChanged();

        // create shape with geometry and appearance data
        shape.addChild(appearance);
        appearance.nodeChanged();
        shape.addChild(geometry);
        geometry.nodeChanged();

        // add shape to bvhRefiner object
        bvhRefiner.addChild(shape);
        shape.nodeChanged();

        // definition the static properties of cullObject
        cullObject.boundedNode = shape;
        cullObject.volume = shape.getVolume();
    }



    /*
     * Creates the four children
     * @returns {null}
     */
    this.CreateChildren = function () {
        create();
    };



    /*
     * Creates the four children
     * @returns {null}
     */
    function create() {

        // calculation of children number nodeNumber within their level
        var deltaR = Math.sqrt(Math.pow(4, level));
        var deltaR1 = Math.sqrt(Math.pow(4, level + 1));
        var lt = Math.floor(nodeNumber / deltaR) * 4 * deltaR +
            (nodeNumber % deltaR) * 2;
        var rt = lt + 1;
        var lb = lt + deltaR1;
        var rb = lb + 1;

        // calculation of the scaling factor
        var s = (bvhRefiner._vf.size).multiply(0.25);

        // creation of all children
        children.push(new QuadtreeNode2D(ctx, bvhRefiner, (level + 1), lt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2), geometry, path + imgNumber + "/", 1));
        children.push(new QuadtreeNode2D(ctx, bvhRefiner, (level + 1), rt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2), geometry, path + imgNumber + "/", 3));
        children.push(new QuadtreeNode2D(ctx, bvhRefiner, (level + 1), lb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2 + 1), geometry, path + imgNumber + "/", 2));
        children.push(new QuadtreeNode2D(ctx, bvhRefiner, (level + 1), rb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2 + 1), geometry, path + imgNumber + "/", 4));
    }



    /*
     * Returns the shape of this node
     * @returns {x3dom.nodeTypes.Shape}
     */
    this.Shape = function () {
        return shape;
    };



    /*
     * Runs only local ready() method. This is needed from parent to ask if
     * all children are ready to render or not
     * @returns {Boolean} true if ready to render, else false
     */
    this.Ready = function(){
        if (shape._webgl !== undefined && shape._webgl.texture !== undefined) {
            return ready();
        }

        return false;
    };



    /*
     * Iterates through all textures of this node and sets readState parameter
     * to true if all textures have been loaded to gpu yet, false if not.
     * @returns {Boolean} true if ready to render, else false
     */
    function ready() {
        readyState = true;
        for (var i = 0; i < shape._webgl.texture.length; i++){
            if (!shape._webgl.texture[i].texture.ready){
                readyState = false;
            }


        }

        return readyState;
    }



    /*
     * Updates the loading state of children and initializes this node
     * if this wasn't done before
     * @param {x3dom.DrawableCollection} drawableCollection
     * @param {x3dom.fields.SFMatrix4f} transform outer transformation matrix
     * @returns {null}
     */
    function updateLoadingState(drawableCollection, transform){

        childrenReadyState = true;
        for (var i = 0; i < children.length; i++){
            if (!children[i].Ready()) {
                childrenReadyState = false;
            }
        }

        if (children.length < 4){
            childrenReadyState = false;
        }
        else if (childrenReadyState) {
            for (var i = 0; i < children.length; i++){
                children[i].Shape()._vf.render = true;
            }
        }


        if (shape._webgl === undefined || shape._webgl.texture === undefined) {
            drawableCollection.context.setupShape(drawableCollection.gl,
                {shape:shape, transform:transform},
                drawableCollection.viewarea);
        }
        else {
            ready();
        }

    }



    /*
     * Decides to create new children and if the node shoud be drawn or not
     * @param {x3dom.fields.SFMatrix4f} transform outer transformation matrix
     * @param {x3dom.DrawableCollection} drawableCollection
     * @param {bool} singlePath
     * @param {bool} invalidateCache
     * @param {number} planeMask
     * @returns {null}
     */
    this.collectDrawables = function (transform, drawableCollection,
                                      singlePath, invalidateCache, planeMask) {

        // definition the actual transformation of the node
        cullObject.localMatrix = nodeTransformation;
        // Checks the actual loading state of itself and children if something wasn't loaded in last frame
        if (!readyState || !childrenReadyState) { updateLoadingState(drawableCollection, nodeTransformation); }

        if (readyState && (planeMask = drawableCollection.cull(nodeTransformation, cullObject, singlePath, planeMask)) > 0) {
            var mat_view = drawableCollection.viewMatrix;
            var vPos = mat_view.multMatrixPnt(nodeTransformation.multMatrixPnt(position));
            var distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));

            if ((distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) * resizeFac / bvhRefiner._vf.factor) || level < bvhRefiner._vf.minDepth) {
                if (bvhRefiner.view.isMovingOrAnimating() && children.length === 0 ||
                    bvhRefiner.view.isMovingOrAnimating() && level >= bvhRefiner._vf.interactionDepth) {
                    shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                }
                else {
                    if (children.length === 0) {
                        bvhRefiner.nodeProducer.AddNewNode(that, distanceToCamera);
                        shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                    }
                    else {
                        if (childrenReadyState) {
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                            }
                        }
                        else {
                            shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                                children[i].Shape()._vf.render = false;
                            }
                        }
                    }
                }
            }
            else {
                shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
            }
        }
    };



    /*
     * Returns the volume of this node
     * @returns {x3dom.fields.BoxVolume}
     */
    this.getVolume = function() {
        return shape.getVolume();
    };


    /*
     * Returns the level of this node
     * @returns {number}
     */
    this.Level = function () {
        return level;
    };



    // reference to get access to public methods within this node
    var that = this;
    // initializes this node directly after creating
    initialize();
}



/*******************************************************************************
 *******************************************************************************
 **************************** QuadtreeNode3D ***********************************
 *******************************************************************************
 *******************************************************************************
 *
 * Defines one 3D node (plane with displacement) of a quadtree that represents
 * a part (nxn vertices) of the whole mesh.
 * @param {object} ctx context
 * @param {x3dom.nodeTypes.BVHRefiner} bvhRefiner root bvhRefiner node
 * @param {number} level level of the node within the quadtree
 * @param {number} nodeNumber id of the node within the level
 * @param {x3dom.fields.SFMatrix4f} nodeTransformation transformation matrix
 *                                  that defines scale and position
 * @param {number} columnNr column number in the wmts matrix within the level
 * @param {number} rowNr row number in the wmts matrix within the level
 * @param {x3dom.nodeTypes.Plane} geometry plane
 * @returns {QuadtreeNode3D}
 */
function QuadtreeNode3D(ctx, bvhRefiner, level, nodeNumber, nodeTransformation,
                        columnNr, rowNr, geometry)
{
    // array with the maximal four child nodes
    var children = [];
    // neighborhood of the node (0=left, 1=right, 2=bottom, 3=top)
    var neighbors = [];
    // drawable component of this node
    var shape = new x3dom.nodeTypes.Shape();
    // position of the node in world space
    var position = null;
    // address of the image for the bvhRefiner surface
    var imageAddressColor = bvhRefiner._vf.textureUrl + "/" + level + "/" +
        columnNr + "/" + rowNr + "." +
        (bvhRefiner._vf.textureFormat).toLowerCase();
    // address of the image for the bvhRefiner height-data
    var imageAddressHeight = bvhRefiner._vf.elevationUrl + "/" + level + "/" +
        columnNr + "/" + rowNr + "." +
        (bvhRefiner._vf.elevationFormat).toLowerCase();
    if (bvhRefiner._vf.normalUrl !== "")
    // address of the image for the bvhRefiner normal-data
        var imageAddressNormal = bvhRefiner._vf.normalUrl + "/" + level + "/" +
            columnNr + "/" + rowNr + "." +
            (bvhRefiner._vf.normalFormat).toLowerCase();
    // true if this component is available and renderable
    var readyState = false;
    // checks if children are ready
    var childrenReadyState = false;
    // defines the resizing factor
    var resizeFac = (bvhRefiner._vf.size.x + bvhRefiner._vf.size.y) / 2.0;
    // object that stores all information to do a frustum culling
    var cullObject = {};
    // last indice number of mesh
    var lastIndice = 0;
    // triangulation attributes --> offset and count of triangulation buffer
    var triangulationAttributes = null;



    /*
     * Initializes all nodeTypes that are needed to create the drawable
     * component for this node
     */
    function initialize() {

        // appearance of the drawable component of this node
        var appearance = new x3dom.nodeTypes.Appearance(ctx);
        // multiTexture to get heightmap and colormap to gpu
        var textures = new x3dom.nodeTypes.MultiTexture(ctx);
        // texture that should represent the surface-data of this node
        var colorTexture = new x3dom.nodeTypes.ImageTexture(ctx);
        // texture that should represent the height-data of this node
        var heightTexture = new x3dom.nodeTypes.ImageTexture(ctx);
        // texture that should represent the normal-data of this node
        var normalTexture = new x3dom.nodeTypes.ImageTexture(ctx);
        // creating the special shader for these nodes
        var composedShader = new x3dom.nodeTypes.ComposedShader(ctx);

        // definition of the nameSpace of this shape
        shape._nameSpace = bvhRefiner._nameSpace;

        // calculate the average position of the node
        position = nodeTransformation.e3();
        position.z = bvhRefiner._vf.maxElevation / 2;

        // creating the special vertex-shader for bvhRefiner-nodes
        var vertexShader = new x3dom.nodeTypes.ShaderPart(ctx);
        vertexShader._vf.type = 'vertex';
        vertexShader._vf.url[0] = createVertexShader();

        // creating the special fragment-shader for bvhRefiner-nodes
        var fragmentShader = new x3dom.nodeTypes.ShaderPart(ctx);
        fragmentShader._vf.type = 'fragment';
        fragmentShader._vf.url[0] = createFragmentShader();

        // create complete-shader with vertex- and fragment-shader
        composedShader.addChild(vertexShader, 'parts');
        composedShader.addChild(fragmentShader, 'parts');

        var colorTexProp = new x3dom.nodeTypes.TextureProperties(ctx);
        colorTexProp._vf.boundaryModeS = "CLAMP_TO_EDGE";
        colorTexProp._vf.boundaryModeT = "CLAMP_TO_EDGE";
        colorTexProp._vf.boundaryModeR = "CLAMP_TO_EDGE";
        colorTexProp._vf.minificationFilter = "LINEAR";
        colorTexProp._vf.magnificationFilter = "LINEAR";
        colorTexture.addChild(colorTexProp, "textureProperties");
        colorTexture.nodeChanged();
        // create texture-data of this node with url's of the texture data
        colorTexture._nameSpace = bvhRefiner._nameSpace;
        colorTexture._vf.url[0] = imageAddressColor;
        colorTexture._vf.repeatT = false;
        colorTexture._vf.repeatS = false;
        textures.addChild(colorTexture, 'texture');
        colorTexture.nodeChanged();
        var colorTextureField = new x3dom.nodeTypes.Field(ctx);
        colorTextureField._vf.name = 'texColor';
        colorTextureField._vf.type = 'SFInt32';
        colorTextureField._vf.value = 0;
        composedShader.addChild(colorTextureField, 'fields');
        colorTextureField.nodeChanged();

        // create height-data
        var heightTexProp = new x3dom.nodeTypes.TextureProperties(ctx);
        heightTexProp._vf.boundaryModeS = "CLAMP_TO_EDGE";
        heightTexProp._vf.boundaryModeT = "CLAMP_TO_EDGE";
        heightTexProp._vf.boundaryModeR = "CLAMP_TO_EDGE";
        heightTexProp._vf.minificationFilter = "NEAREST";
        heightTexProp._vf.magnificationFilter = "NEAREST";
        heightTexture.addChild(heightTexProp, "textureProperties");
        heightTexture.nodeChanged();
        heightTexture._nameSpace = bvhRefiner._nameSpace;
        heightTexture._vf.url[0] = imageAddressHeight;
        heightTexture._vf.repeatT = false;
        heightTexture._vf.repeatS = false;
        heightTexture._vf.scale = false;
        textures.addChild(heightTexture, 'texture');
        heightTexture.nodeChanged();
        var heightTextureField = new x3dom.nodeTypes.Field(ctx);
        heightTextureField._vf.name = 'texHeight';
        heightTextureField._vf.type = 'SFInt32';
        heightTextureField._vf.value = 1;
        composedShader.addChild(heightTextureField, 'fields');
        heightTextureField.nodeChanged();

        if (bvhRefiner._vf.normalUrl !== "") {
            var normalTexProp = new x3dom.nodeTypes.TextureProperties(ctx);
            normalTexProp._vf.boundaryModeS = "CLAMP_TO_EDGE";
            normalTexProp._vf.boundaryModeT = "CLAMP_TO_EDGE";
            normalTexProp._vf.boundaryModeR = "CLAMP_TO_EDGE";
            normalTexProp._vf.minificationFilter = "LINEAR";
            normalTexProp._vf.magnificationFilter = "LINEAR";
            normalTexture.addChild(normalTexProp, "textureProperties");
            normalTexture.nodeChanged();
            // create normal-data
            normalTexture._nameSpace = bvhRefiner._nameSpace;
            normalTexture._vf.url[0] = imageAddressNormal;
            normalTexture._vf.repeatT = false;
            normalTexture._vf.repeatS = false;
            textures.addChild(normalTexture, 'texture');
            normalTexture.nodeChanged();
            var normalTextureField = new x3dom.nodeTypes.Field(ctx);
            normalTextureField._vf.name = 'texNormal';
            normalTextureField._vf.type = 'SFInt32';
            normalTextureField._vf.value = 2;
            composedShader.addChild(normalTextureField, 'fields');
            normalTextureField.nodeChanged();
        }

        // transmit maximum elevation value to gpu
        var maxHeight = new x3dom.nodeTypes.Field(ctx);
        maxHeight._vf.name = 'maxElevation';
        maxHeight._vf.type = 'SFFloat';
        maxHeight._vf.value = bvhRefiner._vf.maxElevation;
        composedShader.addChild(maxHeight, 'fields');
        maxHeight.nodeChanged();

        // add textures to the appearence of this node
        appearance.addChild(textures);
        textures.nodeChanged();
        appearance.addChild(composedShader);
        composedShader.nodeChanged();

        // create shape with geometry and appearance data
        shape.addChild(appearance);
        appearance.nodeChanged();
        shape.addChild(geometry);

        // add shape to bvhRefiner object
        bvhRefiner.addChild(shape);
        shape.nodeChanged();

        // definition the static properties of cullObject
        cullObject.boundedNode = shape;
        cullObject.volume = shape.getVolume();
        // setting max and min in z-direction to get the complete volume
        cullObject.volume.max.z = bvhRefiner._vf.maxElevation;
        cullObject.volume.min.z = 0;

        cullObject.volume.center = cullObject.volume.min.add(cullObject.volume.max).multiply(0.5);
        cullObject.volume.transform(nodeTransformation);
        //shape._graph.volume = cullObject.volume;

        calculateNeighborhood();
    }



    function calculateNeighborhood() {

        // stores the start-ID of this level in quadList
        var levelStartID = 0;

        // calculate id in quadList where to store this quad
        for (var i = 0; i < level; i++) {
            levelStartID += Math.pow(4, i);
        }
        var sid = levelStartID + nodeNumber;
        bvhRefiner.nodeList[sid] = that;

        var c = Math.sqrt(Math.pow(4, level));
        // calculate neighbor-IDs
        // on the left side of the quad
        neighbors[0] = levelStartID + (Math.ceil(((nodeNumber + 1) / c) - 1) * c + ((nodeNumber + (c - 1)) % c));
        // on the right side of the quad
        neighbors[1] = levelStartID + (Math.ceil(((nodeNumber + 1) / c) - 1) * c + ((nodeNumber + 1) % c));
        // on the top side of the quad
        neighbors[3] = levelStartID + (nodeNumber + (c * (c - 1))) % (Math.pow(4, level));
        // on the bottom side of the quad
        neighbors[2] = levelStartID + (nodeNumber + c) % (Math.pow(4, level));

        if (columnNr === 0) { neighbors[0] = -1; }
        if (rowNr === 0) { neighbors[3] = -1; }
        if (columnNr === c - 1) { neighbors[1] = -1; }
        if (rowNr === c - 1) { neighbors[2] = -1; }
    }



    /*
     * Creates the code for the vertex shader
     * @returns {String} code of the vertex shader
     */
    function createVertexShader() {
        if (bvhRefiner._vf.normalUrl !== "")
            return "attribute vec3 position;\n" +
                "attribute vec3 texcoord;\n" +
                "uniform mat4 modelViewMatrix;\n" +
                "uniform mat4 modelViewProjectionMatrix;\n" +
                "uniform sampler2D texColor;\n" +
                "uniform sampler2D texHeight;\n" +
                "uniform float maxElevation;\n" +
                "uniform sampler2D texNormal;\n" +
                "varying vec2 texC;\n" +
                "varying vec3 vLight;\n" +
                "const float shininess = 32.0;\n" +
                "\n" +
                "void main(void) {\n" +
                "    vec3 uLightPosition = vec3(160.0, -9346.0, 4806.0);\n" +
                "    vec4 colr = texture2D(texColor, vec2(texcoord[0], 1.0-texcoord[1]));\n" +
                "    vec3 uAmbientMaterial = vec3(1.0, 1.0, 0.9);" +
                "    vec3 uAmbientLight = vec3(0.5, 0.5, 0.5);" +
                "    vec3 uDiffuseMaterial = vec3(0.7, 0.7, 0.7);" +
                "    vec3 uDiffuseLight = vec3(1.0, 1.0, 1.0);" +
                "    vec4 vertexPositionEye4 = modelViewMatrix * vec4(position, 1.0);" +
                "    vec3 vertexPositionEye3 = vec3((modelViewMatrix * vec4(vertexPositionEye4.xyz, 1.0)).xyz);" +
                "    vec3 vectorToLightSource = normalize(uLightPosition - vertexPositionEye3);" +
                "    vec4 height = texture2D(texHeight, vec2(texcoord[0], 1.0 - texcoord[1]));\n" +
                "    vec4 normalEye = 2.0 * texture2D(texNormal, vec2(texcoord[0], 1.0-texcoord[1])) - 1.0;\n" +
                "    float diffuseLightWeighting = max(dot(normalEye.xyz, vectorToLightSource), 0.0);" +
                "    texC = vec2(texcoord[0], 1.0-texcoord[1]);\n" +
                "    vec3 diffuseReflectance = uDiffuseMaterial * uDiffuseLight * diffuseLightWeighting;" +
                "    vec3 uSpecularMaterial = vec3(0.0, 0.0, 0.0);" +
                "    vec3 uSpecularLight = vec3(1.0, 1.0, 1.0);" +
                "    vec3 reflectionVector = normalize(reflect(-vectorToLightSource, normalEye.xyz));" +
                "    vec3 viewVectorEye = -normalize(vertexPositionEye3);" +
                "    float rdotv = max(dot(reflectionVector, viewVectorEye), 0.0);" +
                "    float specularLightWeight = pow(rdotv, shininess);" +
                "    vec3 specularReflection = uSpecularMaterial * uSpecularLight * specularLightWeight;" +
                "    vLight = vec4(uAmbientMaterial * uAmbientLight + diffuseReflectance + specularReflection, 1.0).xyz;" +
                "    gl_Position = modelViewProjectionMatrix * vec4(position.xy, height.x * maxElevation, 1.0);\n" +
                "}\n";
        else
            return "attribute vec3 position;\n" +
                "attribute vec3 texcoord;\n" +
                "uniform mat4 modelViewProjectionMatrix;\n" +
                "uniform sampler2D texHeight;\n" +
                "uniform float maxElevation;\n" +
                "varying vec2 texC;\n" +
                "\n" +
                "void main(void) {\n" +
                "    vec4 height = texture2D(texHeight, vec2(texcoord[0], 1.0 - texcoord[1]));\n" +
                "    texC = vec2(texcoord[0], 1.0-texcoord[1]);\n" +
                "    gl_Position = modelViewProjectionMatrix * vec4(position.xy, height.x * maxElevation, 1.0);\n" +
                "}\n";
    }



    /*
     * Creates the code for the fragment shader
     * @returns {String} code of the fragment shader
     */
    function createFragmentShader() {
        if (bvhRefiner._vf.normalUrl !== "")
            return "#ifdef GL_ES\n" +
                "precision highp float;\n" +
                "#endif\n" +
                "uniform sampler2D texColor;\n" +
                "uniform sampler2D texNormal;\n" +
                "varying vec2 texC;\n" +
                "varying vec3 vLight;\n" +
                "\n" +
                "\n" +
                "void main(void) {\n" +
                "    vec4 normal = 2.0 * texture2D(texNormal, texC) - 1.0;\n" +
                "    vec4 colr = texture2D(texColor, texC);\n" +
                "    gl_FragColor = vec4(colr.xyz * vLight, colr.w);\n" +
                "}\n";
        else
            return "#ifdef GL_ES\n" +
                "precision highp float;\n" +
                "#endif\n" +
                "uniform sampler2D texColor;\n" +
                "varying vec2 texC;\n" +
                "\n" +
                "\n" +
                "void main(void) {\n" +
                "    gl_FragColor = texture2D(texColor, texC);\n" +
                "}\n";
    }



    this.CreateChildren = function() {
        create();
    };



    /*
     * Creates the four children
     */
    function create() {

        // calculation of children number nodeNumber within their level
        var deltaR = Math.sqrt(Math.pow(4, level));
        var deltaR1 = Math.sqrt(Math.pow(4, level + 1));
        var lt = Math.floor(nodeNumber / deltaR) * 4 * deltaR +
            (nodeNumber % deltaR) * 2;
        var rt = lt + 1;
        var lb = lt + deltaR1;
        var rb = lb + 1;

        // calculation of the scaling factor
        var s = (bvhRefiner._vf.size).multiply(0.25);

        // creation of all children
        children.push(new QuadtreeNode3D(ctx, bvhRefiner, (level + 1), lt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2), geometry));
        children.push(new QuadtreeNode3D(ctx, bvhRefiner, (level + 1), rt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2), geometry));
        children.push(new QuadtreeNode3D(ctx, bvhRefiner, (level + 1), lb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2 + 1), geometry));
        children.push(new QuadtreeNode3D(ctx, bvhRefiner, (level + 1), rb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2 + 1), geometry));
    }



    this.Shape = function(){
        return shape;
    };



    /*
     * Returns if the children of this node exist and are ready to render
     */
    this.ChildrenReady = function(){
        return childrenReadyState;
    };



    /*
     * Runs only local ready() method. This is needed from parent to ask if
     * all children are ready to render or not
     */
    this.Ready = function(){
        if (shape._webgl !== undefined && shape._webgl.texture !== undefined) {
            return ready();
        }

        return false;
    };



    /*
     * Iterates through all textures of this node and sets readState parameter
     * to true if all textures have been loaded to gpu yet, false if not.
     */
    function ready() {
        readyState = true;
        for (var i = 0; i < shape._webgl.texture.length; i++){
            if (!shape._webgl.texture[i].texture.ready){
                readyState = false;
            }
        }

        return readyState;
    }



    /*
     * Updates the loading state of children and initializes this node
     * if this wasn't done before
     */
    function updateLoadingState(drawableCollection, transform){

        childrenReadyState = true;
        for (var i = 0; i < children.length; i++){
            if (!children[i].Ready()) {
                childrenReadyState = false;
            }
        }

        if (children.length < 4){
            childrenReadyState = false;
        }
        else if (childrenReadyState) {
            for (var i = 0; i < children.length; i++){
                children[i].Shape()._vf.render = true;
            }
        }

        if (shape._webgl === undefined || shape._webgl.texture === undefined) {
            drawableCollection.context.setupShape(drawableCollection.gl,
                {shape:shape, transform:transform},
                drawableCollection.viewarea);
        }
        else {
            ready();
        }

    }




    /*
     * Decides to create new children and if the node shoud be drawn or not
     * @param {x3dom.fields.SFMatrix4f} transform outer transformation matrix
     * @param {x3dom.DrawableCollection} drawableCollection
     * @param {bool} singlePath
     * @param {bool} invalidateCache
     * @param {number} planeMask
     * @returns {null}
     */
    this.collectDrawables = function (transform, drawableCollection, singlePath, invalidateCache, planeMask) {

        // TODO: IMPLEMENT RIGHT
        drawableCollection.frustumCulling = false;


        // definition the actual transformation of the node
        cullObject.localMatrix = nodeTransformation;
        // Checks the actual loading state of itself and children if something wasn't loaded in last frame
        if (!readyState || !childrenReadyState) {
            updateLoadingState(drawableCollection, nodeTransformation);
        }
        var mat_view = drawableCollection.viewMatrix;
        var vPos = mat_view.multMatrixPnt(nodeTransformation.multMatrixPnt(position));
        var distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));

        //if (readyState && (planeMask = drawableCollection.cull(nodeTransformation, shape.graphState(), singlePath, planeMask)) > 0) {
        if (readyState && vPos.z - (cullObject.volume.diameter / 2) < 0) {
            if ((distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) * resizeFac / bvhRefiner._vf.factor) ||
                level < bvhRefiner._vf.minDepth) {
                if (bvhRefiner.view.isMovingOrAnimating() && (children.length == 0 || level >= bvhRefiner._vf.interactionDepth)){
                    render(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                }
                else {
                    if (children.length === 0) {
                        bvhRefiner.nodeProducer.AddNewNode(that, distanceToCamera);
                        render(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                    }
                    else {
                        if (childrenReadyState){
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                            }
                        }
                        else {
                            render(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                                children[i].Shape()._vf.render = false;
                            }
                        }
                    }
                }
            }
            else {
                render(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
            }
        }
    };



    /*
     * Decides if this node should be rendered or the children of this node
     * @param {x3dom.DrawableCollection} drawableCollection
     * @returns {Boolean}
     */
    this.hasHigherRenderLevel = function(drawableCollection){

        var mat_view = drawableCollection.viewMatrix;
        var vPos = mat_view.multMatrixPnt(nodeTransformation.multMatrixPnt(position));
        var distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));

        if (distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) * resizeFac / bvhRefiner._vf.factor){
            return true;
        }


        return false;
    };



    /*
     * Renders the object with the required patch version
     * @param {x3dom.fields.SFMatrix4f} transform outer transformation matrix
     * @param {x3dom.DrawableCollection} drawableCollection
     * @param {bool} singlePath
     * @param {bool} invalidateCache
     * @param {number} planeMask
     * @returns {null}
     */
    function render(transform, drawableCollection, singlePath, invalidateCache, planeMask){

        var hasNeighborHigherResolution = [];
        // Calculation if neighbors levels
        for (var i = 0; i < neighbors.length; i++){
            if (bvhRefiner.nodeList[neighbors[i]] !== undefined) {
                if (bvhRefiner.nodeList[neighbors[i]].ChildrenReady() &&
                    bvhRefiner.nodeList[neighbors[i]].hasHigherRenderLevel(drawableCollection))
                    hasNeighborHigherResolution.push(true);
                else
                    hasNeighborHigherResolution.push(false);
            }
            else {
                hasNeighborHigherResolution.push(false);
            }
        }

        var indiceNumber = 0;
        //hasNeighborHigherResolution --> 0=left, 1=right, 2=bottom, 3=top
        if (hasNeighborHigherResolution[3]) {
            if (hasNeighborHigherResolution[1]) {
                indiceNumber = 5;
            }
            else if (hasNeighborHigherResolution[0]) {
                indiceNumber = 6;
            }
            else {
                indiceNumber = 4;
            }
        }
        else if (hasNeighborHigherResolution[2]) {
            if (hasNeighborHigherResolution[1]) {
                indiceNumber = 8;
            }
            else if (hasNeighborHigherResolution[0]) {
                indiceNumber = 7;
            }
            else {
                indiceNumber = 3;
            }
        }
        else if (hasNeighborHigherResolution[0]) {
            indiceNumber = 1;
        }
        else if (hasNeighborHigherResolution[1]) {
            indiceNumber = 2;
        }

        if (lastIndice !== indiceNumber || triangulationAttributes === null){
            triangulationAttributes = shape._cf.geometry.node.getTriangulationAttributes(indiceNumber);
            lastIndice = indiceNumber;
        }
        shape._tessellationProperties = [ triangulationAttributes ];
        shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
    }



    /*
     * Returns the volume of this node
     */
    this.getVolume = function() {
        // TODO; implement correctly, for now just use first shape as workaround
        return shape.getVolume();
    };



    this.Level = function() {
        return level;
    };


    var that = this;
    // initializes this node directly after creating
    initialize();
}




/*****************************************************************************************************************************
 *****************************************************************************************************************************
 ************************************************ QuadtreeNodeBin ************************************************************
 *****************************************************************************************************************************
 ****************************************************************************************************************************/

/*
 * Defines one node of a quadtree that represents a part (nxn vertices) of
 * the whole mesh, that represents a binary geometry object.
 * @param {object} ctx context
 * @param {x3dom.nodeTypes.BVHRefiner} bvhRefiner root bvhRefiner node
 * @param {number} level level of the node within the quadtree
 * @param {number} columnNr column number in the wmts matrix within the level
 * @param {number} rowNr row number in the wmts matrix within the level
 * @param {number} resizeFac defines the resizing factor. Can be null on init
 * @returns {QuadtreeNodeBin}
 */
function QuadtreeNodeBin(ctx, bvhRefiner, level, columnNr, rowNr, resizeFac)
{
    // object that stores all information to do a frustum culling
    var cullObject = {};
    // temporary variable to store the view matrix
    var mat_view;
    // temporary position of this node in view space
    var vPos;
    // temporary distance to camera in view space
    var distanceToCamera;
    // factor redefinition to get a view about the whole scene on level three
    var fac = ((1 / 4 * Math.pow(level, 2) + 1.5) * 0.1) * bvhRefiner._vf.factor;
    // array with the maximal four child nodes
    var children = [];
    // true if a file for the children is available
    var childrenExist = false;
    // true if this component is available and renderable
    var readyState = false;
    // checks if children are ready
    var childrenReadyState = false;
    // path to x3d-file that should be loaded
    var path = bvhRefiner._vf.url + "/" + level + "/" + columnNr + "/";
    // address of the image for the bvhRefiner height-data
    var file = path + rowNr + ".x3d";
    // position of the node in world space
    var position = new x3dom.fields.SFVec3f(0.0, 0.0, 0.0);
    // stores if file has been loaded
    var exists = false;
    // drawable component of this node
    var shape = new x3dom.nodeTypes.Shape(ctx);


    // loader for binary geometry files
    var xhr = new XMLHttpRequest();
    xhr.open("GET", file, false);
    // Try to load the binary geometry files
    try {
        //xhr.send();
        x3dom.RequestManager.addRequest(xhr);

        var xmlDoc = xhr.responseXML;
        if (xmlDoc !== null) {
            var replacer = new RegExp("\"", "g");
            createGeometry(shape);
            initialize();
            exists = true;
        }
    }
    catch (exp) {
        x3dom.debug.logException("Error loading file '" + file + "': " + exp);
    }


    this.Exists = function () {
        return exists;
    };


    this.Shape = function () {
        return shape;
    };



    /*
     * creates the geometry of this node
     */
    function createGeometry(parent) {
        // definition of nameSpace
        this._nameSpace = new x3dom.NodeNameSpace("", bvhRefiner._nameSpace.doc);
        this._nameSpace.setBaseURL(bvhRefiner._nameSpace.baseURL + path);
        var tempShape = xmlDoc.getElementsByTagName("Shape")[0];
        shape = this._nameSpace.setupTree(tempShape);
        if (!bvhRefiner._vf.useNormals) {
            var appearance = new x3dom.nodeTypes.Appearance(ctx);
            var material = new x3dom.nodeTypes.Material(ctx);
            appearance.addChild(material);
            shape._cf.appearance = appearance;
        }
        position = x3dom.fields.SFVec3f.copy(shape._cf.geometry.node._vf.position);
    }



    /*
     * creates the appearance for this node and add it to the dom tree
     */
    function initialize() {

        // bind static cull-properties to cullObject
        cullObject.boundedNode = shape;
        cullObject.volume = shape.getVolume();
    }



    this.CreateChildren = function () {
        create();
    };



    /*
     * creates the four child-nodes
     */
    function create() {
        children.push(new QuadtreeNodeBin(ctx, bvhRefiner, (level + 1), (columnNr * 2), (rowNr * 2), resizeFac));
        children.push(new QuadtreeNodeBin(ctx, bvhRefiner, (level + 1), (columnNr * 2 + 1), (rowNr * 2), resizeFac));
        children.push(new QuadtreeNodeBin(ctx, bvhRefiner, (level + 1), (columnNr * 2), (rowNr * 2 + 1), resizeFac));
        children.push(new QuadtreeNodeBin(ctx, bvhRefiner, (level + 1), (columnNr * 2 + 1), (rowNr * 2 + 1), resizeFac));
    }



    /*
     * Runs only local ready() method. This is needed from parent to ask if
     * all children are ready to render or not
     */
    this.Ready = function () {
        if (shape._webgl !== undefined && shape._webgl.internalDownloadCount !== undefined) {
            return ready();
        }

        return false;
    };



    /*
     * Iterates through all textures of this node and sets readState parameter
     * to true if all textures have been loaded to gpu yet, false if not.
     */
    function ready() {
        readyState = true;

        if (shape._webgl.internalDownloadCount > 0) {
            readyState = false;
        }

        return readyState;
    }



    /*
     * Updates the loading state of children and initializes this node
     * if this wasn't done before
     */
    function updateLoadingState(drawableCollection, transform) {

        childrenReadyState = true;
        for (var i = 0; i < children.length; i++) {
            if (!children[i].Ready()) {
                childrenReadyState = false;
            }
        }

        if (childrenReadyState){
            for (var i = 0; i < children.length; i++){
                children[i].Shape()._vf.render = true;
            }
        }

        if (shape._cf.geometry.node !== null) {
            if (shape._webgl === undefined || shape._webgl.internalDownloadCount === undefined) {
                drawableCollection.context.setupShape(drawableCollection.gl,
                    { shape: shape, transform: transform },
                    drawableCollection.viewarea);
            }
            else {
                ready();
            }
        }
    }



    /*
     * Decides to create new children and if the node shoud be drawn or not
     */
    this.collectDrawables = function (transform, drawableCollection, singlePath, invalidateCache, planeMask) {

        // THIS CALC IS ONLY FOR THE DEMO AND HAS TO BE DELETED AFTER IT
        fac = ((1 / 4 * Math.pow(level, 2) + 1.5) * 0.1) * bvhRefiner._vf.factor;

        // definition the actual transformation of the node
        cullObject.localMatrix = transform;

        // Checks the actual loading state of itself and children if something wasn't loaded in last frame
        if (!readyState || !childrenReadyState) { updateLoadingState(drawableCollection, transform); }

        if (readyState && exists && (planeMask = drawableCollection.cull(transform, cullObject, singlePath, planeMask)) > 0) {
            mat_view = drawableCollection.viewMatrix;
            vPos = mat_view.multMatrixPnt(transform.multMatrixPnt(position));
            distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));

            if ((distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) / fac * 1000) || level < bvhRefiner._vf.minDepth) {
                if (bvhRefiner.view.isMovingOrAnimating() && level >= bvhRefiner._vf.interactionDepth) {
                    shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
                }
                else {
                    if (children.length === 0) {
                        bvhRefiner.nodeProducer.AddNewNode(that, distanceToCamera);
                        shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
                    }
                    else if (children.length === 0 && bvhRefiner.createChildren > 0) {
                        shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
                    }
                    else {
                        if (!childrenExist) {
                            for (var i = 0; i < children.length; i++) {
                                if (children[i].Exists()) {
                                    childrenExist = true;
                                    break;
                                }
                            }
                        }
                        if (childrenExist && childrenReadyState) {
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(transform, drawableCollection, singlePath, invalidateCache, planeMask);
                            }
                        }
                        else {
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(transform, drawableCollection, singlePath, invalidateCache, planeMask);
                                children[i].Shape()._vf.render = false;
                            }
                            shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
                        }
                    }
                }
            }
            else {
                shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
            }
        }
    };



    /*
     * Returns the volume of this node
     */
    this.getVolume = function () {
        // TODO; implement correctly, for now just use first shape as workaround
        return shape.getVolume();
    };



    this.Level = function () {
        return level;
    };


    var that = this;
    // initializes this node directly after creating
    initialize();
}




/*****************************************************************************************************************************
 *****************************************************************************************************************************
 ***************************************************** BVHNode ***************************************************************
 *****************************************************************************************************************************
 ****************************************************************************************************************************/

/*
 * Defines one node of an arbitrary tree that represents a part (nxn vertices)
 * of the entire point cloud
 * @param {object} ctx context
 * @param {x3dom.nodeTypes.BVHRefiner} bvhRefiner root bvhRefiner node
 * @param {number} level level of the node within the quadtree
 * @param {number} columnNr column number in the wmts matrix within the level
 * @param {number} rowNr row number in the wmts matrix within the level
 * @param {number} resizeFac defines the resizing factor. Can be null on init
 * @returns {OctreeNode}
 */
function BVHNode(ctx, bvhRefiner, level, path, imgNumber, count)
{
    // object that stores all information to do a frustum culling
    var cullObject = {};
    // temporary variable to store the view matrix
    var mat_view;
    // temporary position of this node in view space
    var vPos;
    // temporary distance to camera in view space
    var distanceToCamera;
    // factor redefinition to get a view about the whole scene on level three
    var fac = ((1/4 * Math.pow(level, 2) + 1.5) * 0.1) * bvhRefiner._vf.factor;
    // array with the maximal four child nodes
    var children = [];
    // true if a file for the children is available
    var childrenExist = false;
    // true if this component is available and renderable
    var readyState = false;
    // checks if children are ready
    var childrenReadyState = false;
    // address of the image for the bvhRefiner height-data
    var file = bvhRefiner._vf.url + path + imgNumber + ".x3d";
    // position of the node in world space
    var position = new x3dom.fields.SFVec3f(0.0, 0.0, 0.0);
    // stores if file has been loaded
    var exists = false;
    // drawable component of this node
    var shape = new x3dom.nodeTypes.Shape(ctx);


    this.RecalcFactor = function() {
        fac = ((1/4 * Math.pow(level, 2) + 1.5) * 0.1) * bvhRefiner._vf.factor;
        for (var i = 0; i < children.length; i++){
            children[i].RecalcFactor();
        }
    };



    // loader for binary geometry files
    var xhr = new XMLHttpRequest();
    xhr.open("GET", file, false);
    // Try to load the binary geometry files
    try {
        //xhr.send();
        x3dom.RequestManager.addRequest(xhr);

        var xmlDoc = xhr.responseXML;
        if (xmlDoc !== null) {
            var replacer = new RegExp("\"", "g");
            createGeometry(shape);
            initialize();
            exists = true;
        }
    }
    catch (exp) {
        x3dom.debug.logException("Error loading file '" + file + "': " + exp);
    }


    this.Exists = function()
    {
        return exists;
    };


    this.Shape = function(){
        return shape;
    };



    /*
     * creates the geometry of this node
     */
    function createGeometry(parent) {
        // definition of nameSpace
        this._nameSpace = new x3dom.NodeNameSpace("", bvhRefiner._nameSpace.doc);
        this._nameSpace.setBaseURL(bvhRefiner._nameSpace.baseURL + bvhRefiner._vf.url + path);
        var tempShape = xmlDoc.getElementsByTagName("Shape")[0];
        shape = this._nameSpace.setupTree(tempShape);
        if (!bvhRefiner._vf.useNormals){
            var appearance = new x3dom.nodeTypes.Appearance(ctx);
            var material = new x3dom.nodeTypes.Material(ctx);
            appearance.addChild(material);
            shape._cf.appearance = appearance;
        }
        position = x3dom.fields.SFVec3f.copy(shape._cf.geometry.node._vf.position);
    }



    /*
     * creates the appearance for this node and add it to the dom tree
     */
    function initialize() {

        // bind static cull-properties to cullObject
        cullObject.boundedNode = shape;
        cullObject.volume = shape.getVolume();
    }



    this.CreateChildren = function() {
        create();
    };



    /*
     * creates the four child-nodes
     */
    function create() {
        for (var i = 0; i < count; i++){
            children.push(new BVHNode(ctx, bvhRefiner, (level + 1),
                    path + imgNumber + "/",
                    i + 1, count));
        }
    }



    /*
     * Runs only local ready() method. This is needed from parent to ask if
     * all children are ready to render or not
     */
    this.Ready = function(){
        if (shape._webgl !== undefined && shape._webgl.internalDownloadCount !== undefined) {
            return ready();
        }

        return false;
    };



    /*
     * Iterates through all textures of this node and sets readState parameter
     * to true if all textures have been loaded to gpu yet, false if not.
     */
    function ready() {
        return (shape._webgl.internalDownloadCount <= 0);
    }



    /*
     * Updates the loading state of children and initializes this node
     * if this wasn't done before
     */
    function updateLoadingState(drawableCollection, transform){

        for (var i = 0; i < children.length; i++) {
            childrenReadyState = true;
            if (!children[i].Ready()) {
                childrenReadyState = false;
            }
            else {
                children[i].Shape()._vf.render = true;
            }
        }

        if (shape._cf.geometry.node !== null) {
            if (shape._webgl === undefined || shape._webgl.internalDownloadCount === undefined) {
                drawableCollection.context.setupShape(drawableCollection.gl,
                    {shape:shape, transform:transform},
                    drawableCollection.viewarea);
            }
            else {
                ready();
            }
        }
    }



    /*
     * Decides to create new children and if the node shoud be drawn or not
     */
    this.collectDrawables = function (transform, drawableCollection, singlePath, invalidateCache, planeMask) {

        // THIS CALC IS ONLY FOR THE DEMO AND HAS TO BE DELETED AFTER IT
        fac = ((1/4 * Math.pow(level, 2) + 1.5) * 0.1) * bvhRefiner._vf.factor;

        // definition the actual transformation of the node
        cullObject.localMatrix = transform;

        // Checks the actual loading state of itself and children if something wasn't loaded in last frame
        if (!readyState || !childrenReadyState) { updateLoadingState(drawableCollection, transform); }

        if (readyState && exists && (planeMask = drawableCollection.cull(transform, cullObject, singlePath, planeMask)) > 0) {
            mat_view = drawableCollection.viewMatrix;
            vPos = mat_view.multMatrixPnt(transform.multMatrixPnt(position));
            distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));

            if ((distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) / fac) || level < bvhRefiner._vf.minDepth) {
                if (bvhRefiner.view.isMovingOrAnimating() && level >= bvhRefiner._vf.interactionDepth) {
                    shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
                }
                else {
                    if (children.length === 0) {
                        bvhRefiner.nodeProducer.AddNewNode(that, distanceToCamera);
                        shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
                    }
                    else if (children.length === 0 && bvhRefiner.createChildren > 0) {
                        shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
                    }
                    else {
                        if (!childrenExist){
                            for (var i = 0; i < children.length; i++) {
                                if (children[i].Exists()) {
                                    childrenExist = true;
                                    break;
                                }
                            }
                        }
                        if (childrenExist && childrenReadyState){
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(transform, drawableCollection, singlePath, invalidateCache, planeMask);
                            }
                        }
                        else {
                            for (var i = 0; i < children.length; i++) {
                                children[i].collectDrawables(transform, drawableCollection, singlePath, invalidateCache, planeMask);
                                children[i].Shape()._vf.render = false;
                            }
                            shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
                        }
                    }
                }
            }
            else {
                shape.collectDrawableObjects(transform, drawableCollection, singlePath, invalidateCache, planeMask, []);
            }
        }
    };



    /*
     * Returns the volume of this node
     */
    this.getVolume = function() {
        // TODO; implement correctly, for now just use first shape as workaround
        return shape.getVolume();
    };



    this.Level = function() {
        return level;
    };


    var that = this;
    // initializes this node directly after creating
    initialize();
}





/*
 * Defines one 3D node (plane with displacement) of a quadtree that represents
 * a part (nxn vertices) of the whole mesh.
 * @param {object} ctx context
 * @param {x3dom.nodeTypes.BVHRefiner} bvhRefiner root bvhRefiner node
 * @param {number} level level of the node within the quadtree
 * @param {number} nodeNumber id of the node within the level
 * @param {x3dom.fields.SFMatrix4f} nodeTransformation transformation matrix that defines scale and position
 * @param {number} columnNr column number in the wmts matrix within the level
 * @param {number} rowNr row number in the wmts matrix within the level
 * @param {x3dom.nodeTypes.Plane} geometry plane
 * @returns {QuadtreeNode3D}
 */
function QuadtreeNode3D_NEW(ctx, bvhRefiner, level, nodeNumber, nodeTransformation,
                            columnNr, rowNr, geometry)
{
    // array with the maximal four child nodes
    var children = [];
    // drawable component of this node
    var shape = new x3dom.nodeTypes.Shape();
    // position of the node in world space
    var position = null;
    // address of the image for the bvhRefiner surface
    var imageAddressColor = bvhRefiner._vf.textureUrl + "/" + level + "/" +
        columnNr + "/" + rowNr + "." +
        (bvhRefiner._vf.textureFormat).toLowerCase();
    // address of the image for the bvhRefiner height-data
    var imageAddressHeight = bvhRefiner._vf.elevationUrl + "/" + level + "/" +
        columnNr + "/" + rowNr + "." +
        (bvhRefiner._vf.elevationFormat).toLowerCase();
    // address of the image for the bvhRefiner normal-data
    var imageAddressNormal = bvhRefiner._vf.normalUrl + "/" + level + "/" +
        columnNr + "/" + rowNr + "." +
        (bvhRefiner._vf.normalFormat).toLowerCase();
    // true if components are available and renderable
    var exists = true;
    // defines the resizing factor
    var resizeFac = (bvhRefiner._vf.size.x + bvhRefiner._vf.size.y) / 2.0;
    // object that stores all information to do a frustum culling
    var cullObject = {};



    /*
     * Initializes all nodeTypes that are needed to create the drawable
     * component for this node
     */
    function initialize() {

        // appearance of the drawable component of this node
        var appearance = new x3dom.nodeTypes.Appearance(ctx);
        // multiTexture to get heightmap and colormap to gpu
        var shader = new x3dom.nodeTypes.CommonSurfaceShader(ctx);
        var ssTexColor = new x3dom.nodeTypes.SurfaceShaderTexture(ctx);
        // texture that should represent the surface-data of this node
        var colorTexture = new x3dom.nodeTypes.ImageTexture(ctx);
        var ssTexDisplace = new x3dom.nodeTypes.SurfaceShaderTexture(ctx);
        // texture that should represent the height-data of this node
        var heightTexture = new x3dom.nodeTypes.ImageTexture(ctx);

        // definition of the nameSpace of this shape
        shape._nameSpace = bvhRefiner._nameSpace;

        // calculate the average position of the node
        position = nodeTransformation.e3();

        shader._vf.displacementFactor = bvhRefiner._vf.maxElevation;

        // create texture-data of this node with url's of the texture data
        colorTexture._nameSpace = bvhRefiner._nameSpace;
        colorTexture._vf.url[0] = imageAddressColor;
        colorTexture._vf.repeatT = false;
        colorTexture._vf.repeatS = false;
        ssTexColor.addChild(colorTexture, 'texture');
        colorTexture.nodeChanged();
        shader.addChild(ssTexColor, 'diffuseTexture');
        ssTexColor.nodeChanged();

        // create height-data
        heightTexture._nameSpace = bvhRefiner._nameSpace;
        heightTexture._vf.url[0] = imageAddressHeight;
        heightTexture._vf.repeatT = false;
        heightTexture._vf.repeatS = false;
        ssTexDisplace.addChild(heightTexture, 'texture');
        heightTexture.nodeChanged();
        shader.addChild(ssTexDisplace, 'displacementTexture');
        heightTexture.nodeChanged();

        appearance.addChild(shader, 'shaders');
        shader.nodeChanged();

        // create shape with geometry and appearance data
        shape.addChild(appearance);
        appearance.nodeChanged();
        shape.addChild(geometry);
        geometry.nodeChanged();

        // add shape to bvhRefiner object
        bvhRefiner.addChild(shape);
        shape.nodeChanged();

        // definition the static properties of cullObject
        cullObject.boundedNode = shape;
        cullObject.volume = shape.getVolume();
        // setting max and min in z-direction to get the complete volume
        cullObject.volume.max.z = Math.round(bvhRefiner._vf.maxElevation / 2);
        cullObject.volume.min.z = -cullObject.volume.max.z;
    }



    /*
     * Creates the code for the vertex shader
     * @returns {String} code of the vertex shader
     */
    function createVertexShader() {
        return "attribute vec3 position;\n" +
            "attribute vec3 texcoord;\n" +
            "uniform mat4 modelViewMatrix;\n" +
            "uniform mat4 modelViewProjectionMatrix;\n" +
            "uniform sampler2D texColor;\n" +
            "uniform sampler2D texHeight;\n" +
            "uniform float maxElevation;\n" +
            "uniform sampler2D texNormal;\n" +
            "varying vec2 texC;\n" +
            "varying vec3 vLight;\n" +
            "const float shininess = 32.0;\n" +
            "\n" +
            "void main(void) {\n" +
            "    vec3 uLightPosition = vec3(160.0, -9346.0, 4806.0);\n" +
            "    vec4 colr = texture2D(texColor, vec2(texcoord[0], 1.0-texcoord[1]));\n" +
            "    vec3 uAmbientMaterial = vec3(1.0, 1.0, 0.9);" +
            "    vec3 uAmbientLight = vec3(0.5, 0.5, 0.5);" +
            "    vec3 uDiffuseMaterial = vec3(0.7, 0.7, 0.7);" +
            "    vec3 uDiffuseLight = vec3(1.0, 1.0, 1.0);" +
            "    vec4 vertexPositionEye4 = modelViewMatrix * vec4(position, 1.0);" +
            "    vec3 vertexPositionEye3 = vec3((modelViewMatrix * vec4(vertexPositionEye4.xyz, 1.0)).xyz);" +
            "    vec3 vectorToLightSource = normalize(uLightPosition - vertexPositionEye3);" +
            "    vec4 height = texture2D(texHeight, vec2(texcoord[0], 1.0 - texcoord[1]));\n" +
            "    vec4 normalEye = 2.0 * texture2D(texNormal, vec2(texcoord[0], 1.0-texcoord[1])) - 1.0;\n" +
            "    float diffuseLightWeighting = max(dot(normalEye.xyz, vectorToLightSource), 0.0);" +
            "    texC = vec2(texcoord[0], 1.0-texcoord[1]);\n" +
            "    vec3 diffuseReflectance = uDiffuseMaterial * uDiffuseLight * diffuseLightWeighting;" +
            "    vec3 uSpecularMaterial = vec3(0.0, 0.0, 0.0);" +
            "    vec3 uSpecularLight = vec3(1.0, 1.0, 1.0);" +
            "    vec3 reflectionVector = normalize(reflect(-vectorToLightSource, normalEye.xyz));" +
            "    vec3 viewVectorEye = -normalize(vertexPositionEye3);" +
            "    float rdotv = max(dot(reflectionVector, viewVectorEye), 0.0);" +
            "    float specularLightWeight = pow(rdotv, shininess);" +
            "    vec3 specularReflection = uSpecularMaterial * uSpecularLight * specularLightWeight;" +
            "    vLight = vec4(uAmbientMaterial * uAmbientLight + diffuseReflectance + specularReflection, 1.0).xyz;" +
            "    gl_Position = modelViewProjectionMatrix * vec4(position.xy, height.x * maxElevation, 1.0);\n" +
            "}\n";
    }



    /*
     * Creates the code for the fragment shader
     * @returns {String} code of the fragment shader
     */
    function createFragmentShader() {
        return "#ifdef GL_ES\n" +
            "precision highp float;\n" +
            "#endif\n" +
            "uniform sampler2D texColor;\n" +
            "uniform sampler2D texNormal;\n" +
            "varying vec2 texC;\n" +
            "varying vec3 vLight;\n" +
            "\n" +
            "\n" +
            "void main(void) {\n" +
            "    vec4 normal = 2.0 * texture2D(texNormal, texC) - 1.0;\n" +
            "    vec4 colr = texture2D(texColor, texC);\n" +
            "    gl_FragColor = vec4(colr.xyz * vLight, colr.w);\n" +
            "}\n";
    }



    /*
     * Creates the four children
     */
    function create() {

        // calculation of children number nodeNumber within their level
        var deltaR = Math.sqrt(Math.pow(4, level));
        var deltaR1 = Math.sqrt(Math.pow(4, level + 1));
        var lt = Math.floor(nodeNumber / deltaR) * 4 * deltaR +
            (nodeNumber % deltaR) * 2;
        var rt = lt + 1;
        var lb = lt + deltaR1;
        var rb = lb + 1;

        // calculation of the scaling factor
        var s = (bvhRefiner._vf.size).multiply(0.25);

        // creation of all children
        children.push(new QuadtreeNode3D_NEW(ctx, bvhRefiner, (level + 1), lt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2), geometry));
        children.push(new QuadtreeNode3D_NEW(ctx, bvhRefiner, (level + 1), rt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2), geometry));
        children.push(new QuadtreeNode3D_NEW(ctx, bvhRefiner, (level + 1), lb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2 + 1), geometry));
        children.push(new QuadtreeNode3D_NEW(ctx, bvhRefiner, (level + 1), rb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2 + 1), geometry));
    }



    /*
     * Decides to create new children and if the node shoud be drawn or not
     */
    this.collectDrawables = function (transform, drawableCollection, singlePath, invalidateCache, planeMask) {

        // definition the actual transformation of the node
        cullObject.localMatrix = nodeTransformation;

        if (exists && (planeMask = drawableCollection.cull(transform, cullObject, singlePath, planeMask)) > 0) {
            var mat_view = drawableCollection.viewMatrix;
            var vPos = mat_view.multMatrixPnt(transform.multMatrixPnt(position));
            var distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));
            if ((distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) * resizeFac / bvhRefiner._vf.factor)) {
                if (children.length === 0 && bvhRefiner.createChildren === 0) {
                    bvhRefiner.createChildren++;
                    create();
                    shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                }
                else if (children.length === 0 && bvhRefiner.createChildren > 0) {
                    shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                }
                else {
                    for (var i = 0; i < children.length; i++) {
                        children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                    }
                }
            }
            else {
                shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
            }
        }
    };



    /*
     * Returns the volume of this node
     */
    this.getVolume = function() {
        // TODO; implement correctly, for now just use first shape as workaround
        return shape.getVolume();
    };



    // initializes this node directly after creating
    initialize();
}




/*
 * Defines one node of an octree that represents a part (nxn vertices) of
 * the whole point cloud
 * @param {object} ctx context
 * @param {x3dom.nodeTypes.BVHRefiner} bvhRefiner root bvhRefiner node
 * @param {number} level level of the node within the quadtree
 * @param {number} columnNr column number in the wmts matrix within the level
 * @param {number} rowNr row number in the wmts matrix within the level
 * @param {number} resizeFac defines the resizing factor. Can be null on init
 * @returns {QuadtreeNodeBin}
 */
function OctreeNode(ctx, bvhRefiner, level, nodeTransformation)
{
    // array with the maximal four child nodes
    var children = [];
    // position of the node in world space
    var position = nodeTransformation.e3();
    // drawable component of this node
    var shape = new x3dom.nodeTypes.Shape(ctx);
    // object that stores all information to do a frustum culling
    var cullObject = {};
    // defines the resizing factor
    var resizeFac = (bvhRefiner._vf.octSize.x + bvhRefiner._vf.octSize.y + bvhRefiner._vf.octSize.z) / 3.0;



    /*
     * creates the appearance for this node and add it to the dom tree
     */
    function initialize() {

        // appearance of the drawable component of this node
        var appearance = new x3dom.nodeTypes.Appearance(ctx);
        var geometry = new x3dom.nodeTypes.Box(ctx);

        geometry._vf.size = bvhRefiner._vf.octSize;
        geometry.fieldChanged('size');

        // definition of nameSpace
        shape._nameSpace = new x3dom.NodeNameSpace("", bvhRefiner._nameSpace.doc);
        shape._nameSpace.setBaseURL(bvhRefiner._nameSpace.baseURL);

        shape.addChild(appearance);
        appearance.nodeChanged();
        shape.addChild(geometry);
        geometry.nodeChanged();

        //bvhRefiner.addChild(shape);
        shape.nodeChanged();

        // bind static cull-properties to cullObject
        cullObject.boundedNode = shape;
        cullObject.volume = shape.getVolume();
    }



    /*
     * creates the four child-nodes
     */
    function create() {
        // calculation of the scaling factor
        var s = bvhRefiner._vf.octSize.multiply(0.25);

        // creation of all children
        children.push(new OctreeNode(ctx, bvhRefiner, (level + 1),
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, s.y, s.z))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 0.5)))));
        children.push(new OctreeNode(ctx, bvhRefiner, (level + 1),
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, s.y, s.z))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 0.5)))));
        children.push(new OctreeNode(ctx, bvhRefiner, (level + 1),
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, -s.y, s.z))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 0.5)))));
        children.push(new OctreeNode(ctx, bvhRefiner, (level + 1),
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, -s.y, s.z))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 0.5)))));
        children.push(new OctreeNode(ctx, bvhRefiner, (level + 1),
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, s.y, -s.z))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 0.5)))));
        children.push(new OctreeNode(ctx, bvhRefiner, (level + 1),
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, s.y, -s.z))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 0.5)))));
        children.push(new OctreeNode(ctx, bvhRefiner, (level + 1),
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, -s.y, -s.z))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 0.5)))));
        children.push(new OctreeNode(ctx, bvhRefiner, (level + 1),
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, -s.y, -s.z))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 0.5)))));
    }



    /*
     * Decides to create new children and if the node shoud be drawn or not
     */
    this.collectDrawables = function (transform, drawableCollection, singlePath, invalidateCache, planeMask) {

        // definition the actual transformation of the node
        cullObject.localMatrix = nodeTransformation;

        if ((planeMask = drawableCollection.cull(transform, cullObject, singlePath, planeMask)) > 0) {

            var mat_view = drawableCollection.viewMatrix;
            var vPos = mat_view.multMatrixPnt(transform.multMatrixPnt(position));
            var distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));

            // bvhRefiner._vf.factor instead (level * 16)
            if ((distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) * resizeFac / bvhRefiner._vf.factor)) {
                if (children.length === 0){
                    create();
                }
                else {
                    for (var i = 0; i < children.length; i++) {
                        children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                    }
                }
            }
            else {
                shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
            }
        }
    };



    /*
     * Returns the volume of this node
     */
    this.getVolume = function() {
        // TODO; implement correctly, for now just use first box as workaround
        return shape.getVolume();
    };



    // initializes this node directly after creating
    initialize();
}



/*
 * Defines one 3D node (plane with displacement) of a quadtree that represents
 * a part (nxn vertices) of the whole mesh.
 * @param {object} ctx context
 * @param {x3dom.nodeTypes.BVHRefiner} bvhRefiner root bvhRefiner node
 * @param {number} level level of the node within the quadtree
 * @param {number} nodeNumber id of the node within the level
 * @param {x3dom.fields.SFMatrix4f} nodeTransformation transformation matrix that defines scale and position
 * @param {number} columnNr column number in the wmts matrix within the level
 * @param {number} rowNr row number in the wmts matrix within the level
 * @param {x3dom.nodeTypes.Plane} geometry plane
 * @returns {QuadtreeNode3D}
 */
function QuadtreeNode3D_32bit(ctx, bvhRefiner, level, nodeNumber, nodeTransformation,
                              columnNr, rowNr, geometry)
{
    // array with the maximal four child nodes
    var children = [];
    // drawable component of this node
    var shape = new x3dom.nodeTypes.Shape();
    // position of the node in world space
    var position = null;
    // address of the image for the bvhRefiner surface
    var imageAddressColor = bvhRefiner._vf.textureUrl + "/" + level + "/" +
        columnNr + "/" + rowNr + "." +
        (bvhRefiner._vf.textureFormat).toLowerCase();
    // address of the image for the bvhRefiner height-data
    var imageAddressHeight = bvhRefiner._vf.elevationUrl + "/" + level + "/" +
        columnNr + "/" + rowNr + "." +
        (bvhRefiner._vf.elevationFormat).toLowerCase();
    // address of the image for the bvhRefiner normal-data
    var imageAddressNormal = bvhRefiner._vf.normalUrl + "/" + level + "/" +
        columnNr + "/" + rowNr + "." +
        (bvhRefiner._vf.normalFormat).toLowerCase();
    // true if components are available and renderable
    var exists = true;
    // defines the resizing factor
    var resizeFac = (bvhRefiner._vf.size.x + bvhRefiner._vf.size.y) / 2.0;
    // object that stores all information to do a frustum culling
    var cullObject = {};



    /*
     * Initializes all nodeTypes that are needed to create the drawable
     * component for this node
     */
    function initialize() {

        // appearance of the drawable component of this node
        var appearance = new x3dom.nodeTypes.Appearance(ctx);
        // multiTexture to get heightmap and colormap to gpu
        var textures = new x3dom.nodeTypes.MultiTexture(ctx);
        // texture that should represent the surface-data of this node
        var colorTexture = new x3dom.nodeTypes.ImageTexture(ctx);
        // texture that should represent the height-data of this node
        var heightTexture = new x3dom.nodeTypes.ImageTexture(ctx);
        // texture that should represent the normal-data of this node
        var normalTexture = new x3dom.nodeTypes.ImageTexture(ctx);
        // creating the special shader for these nodes
        var composedShader = new x3dom.nodeTypes.ComposedShader(ctx);

        // definition of the nameSpace of this shape
        shape._nameSpace = bvhRefiner._nameSpace;

        // calculate the average position of the node
        position = nodeTransformation.e3();

        // creating the special vertex-shader for bvhRefiner-nodes
        var vertexShader = new x3dom.nodeTypes.ShaderPart(ctx);
        vertexShader._vf.type = 'vertex';
        vertexShader._vf.url[0] = createVertexShader();

        // creating the special fragment-shader for bvhRefiner-nodes
        var fragmentShader = new x3dom.nodeTypes.ShaderPart(ctx);
        fragmentShader._vf.type = 'fragment';
        fragmentShader._vf.url[0] = createFragmentShader();

        // create complete-shader with vertex- and fragment-shader
        composedShader.addChild(vertexShader, 'parts');
        composedShader.addChild(fragmentShader, 'parts');

        // create texture-data of this node with url's of the texture data
        colorTexture._nameSpace = bvhRefiner._nameSpace;
        colorTexture._vf.url[0] = imageAddressColor;
        colorTexture._vf.repeatT = false;
        colorTexture._vf.repeatS = false;
        colorTexture._vf.generateMipMaps = false;
        textures.addChild(colorTexture, 'texture');
        colorTexture.nodeChanged();
        var colorTextureField = new x3dom.nodeTypes.Field(ctx);
        colorTextureField._vf.name = 'texColor';
        colorTextureField._vf.type = 'SFInt32';
        colorTextureField._vf.value = 0;
        composedShader.addChild(colorTextureField, 'fields');
        colorTextureField.nodeChanged();

        // create height-data
        heightTexture._nameSpace = bvhRefiner._nameSpace;
        heightTexture._vf.url[0] = imageAddressHeight;
        heightTexture._vf.repeatT = false;
        heightTexture._vf.repeatS = false;

        /*heightTexture._cf.textureProperties.node = new x3dom.nodeTypes.TextureProperties(ctx);
         heightTexture._cf.textureProperties.node._vf.minificationFilter = 'NEAREST';
         heightTexture._cf.textureProperties.node._vf.magnificationFilter = 'NEAREST';
         heightTexture._cf.textureProperties.node._vf.generateMipMaps = false;
         heightTexture._cf.textureProperties.node._vf.boundaryModeS = 'MIRRORED_REPEAT';
         heightTexture._cf.textureProperties.node._vf.boundaryModeT = 'MIRRORED_REPEAT';
         heightTexture._cf.textureProperties.node._vf.boundaryModeR = 'MIRRORED_REPEAT';*/

        textures.addChild(heightTexture, 'texture');
        heightTexture.nodeChanged();
        var heightTextureField = new x3dom.nodeTypes.Field(ctx);
        heightTextureField._vf.name = 'texHeight';
        heightTextureField._vf.type = 'SFInt32';
        heightTextureField._vf.value = 1;
        composedShader.addChild(heightTextureField, 'fields');
        heightTextureField.nodeChanged();

        // create normal-data
        normalTexture._nameSpace = bvhRefiner._nameSpace;
        normalTexture._vf.url[0] = imageAddressNormal;
        normalTexture._vf.repeatT = false;
        normalTexture._vf.repeatS = false;
        textures.addChild(normalTexture, 'texture');
        normalTexture.nodeChanged();
        var normalTextureField = new x3dom.nodeTypes.Field(ctx);
        normalTextureField._vf.name = 'texNormal';
        normalTextureField._vf.type = 'SFInt32';
        normalTextureField._vf.value = 2;
        composedShader.addChild(normalTextureField, 'fields');
        normalTextureField.nodeChanged();

        // transmit maximum elevation value to gpu
        var maxHeight = new x3dom.nodeTypes.Field(ctx);
        maxHeight._vf.name = 'maxElevation';
        maxHeight._vf.type = 'SFFloat';
        maxHeight._vf.value = bvhRefiner._vf.maxElevation;
        composedShader.addChild(maxHeight, 'fields');
        maxHeight.nodeChanged();

        // add textures to the appearence of this node
        appearance.addChild(textures);
        textures.nodeChanged();
        appearance.addChild(composedShader);
        composedShader.nodeChanged();

        // create shape with geometry and appearance data
        shape.addChild(appearance);
        appearance.nodeChanged();
        shape.addChild(geometry);
        geometry.nodeChanged();

        // add shape to bvhRefiner object
        bvhRefiner.addChild(shape);
        shape.nodeChanged();

        // definition the static properties of cullObject
        cullObject.boundedNode = shape;
        cullObject.volume = shape.getVolume();
        // setting max and min in z-direction to get the complete volume
        cullObject.volume.max.z = Math.round(bvhRefiner._vf.maxElevation);
        cullObject.volume.min.z = -cullObject.volume.max.z;
    }



    /*
     * Creates the code for the vertex shader
     * @returns {String} code of the vertex shader
     */
    function createVertexShader() {
        return "attribute vec3 position;\n" +
            "attribute vec3 texcoord;\n" +
            "uniform mat4 modelViewMatrix;\n" +
            "uniform mat4 modelViewProjectionMatrix;\n" +
            "uniform sampler2D texColor;\n" +
            "uniform sampler2D texHeight;\n" +
            "uniform float maxElevation;\n" +
            "uniform sampler2D texNormal;\n" +
            "varying vec2 texC;\n" +
            "varying vec3 vLight;\n" +
            "const float shininess = 32.0;\n" +
            "\n" +
            "void main(void) {\n" +
            "    vec3 uLightPosition = vec3(160.0, -9346.0, 4806.0);\n" +
            "    vec4 colr = texture2D(texColor, vec2(texcoord[0], 1.0-texcoord[1]));\n" +
            "    vec3 uAmbientMaterial = vec3(1.0, 1.0, 0.9);" +
            "    vec3 uAmbientLight = vec3(0.5, 0.5, 0.5);" +
            "    vec3 uDiffuseMaterial = vec3(0.7, 0.7, 0.7);" +
            "    vec3 uDiffuseLight = vec3(1.0, 1.0, 1.0);" +
            "    vec4 vertexPositionEye4 = modelViewMatrix * vec4(position, 1.0);" +
            "    vec3 vertexPositionEye3 = vec3((modelViewMatrix * vec4(vertexPositionEye4.xyz, 1.0)).xyz);" +
            "    vec3 vectorToLightSource = normalize(uLightPosition - vertexPositionEye3);" +
            "    vec4 height = texture2D(texHeight, vec2(texcoord[0], 1.0 - texcoord[1]));\n" +
            "    vec4 normalEye = 2.0 * texture2D(texNormal, vec2(texcoord[0], 1.0-texcoord[1])) - 1.0;\n" +
            "    float diffuseLightWeighting = max(dot(normalEye.xyz, vectorToLightSource), 0.0);" +
            "    texC = vec2(texcoord[0], 1.0-texcoord[1]);\n" +
            "    vec3 diffuseReflectance = uDiffuseMaterial * uDiffuseLight * diffuseLightWeighting;" +
            "    vec3 uSpecularMaterial = vec3(0.0, 0.0, 0.0);" +
            "    vec3 uSpecularLight = vec3(1.0, 1.0, 1.0);" +
            "    vec3 reflectionVector = normalize(reflect(-vectorToLightSource, normalEye.xyz));" +
            "    vec3 viewVectorEye = -normalize(vertexPositionEye3);" +
            "    float rdotv = max(dot(reflectionVector, viewVectorEye), 0.0);" +
            "    float specularLightWeight = pow(rdotv, shininess);" +
            "    vec3 specularReflection = uSpecularMaterial * uSpecularLight * specularLightWeight;" +
            "    vLight = vec4(uAmbientMaterial * uAmbientLight + diffuseReflectance + specularReflection, 1.0).xyz;" +
            "    gl_Position = modelViewProjectionMatrix * vec4(position.xy, ((height.g * 256.0)+height.b) * maxElevation, 1.0);\n" +
            "}\n";
    }



    /*
     * Creates the code for the fragment shader
     * @returns {String} code of the fragment shader
     */
    function createFragmentShader() {
        return "#ifdef GL_ES\n" +
            "precision highp float;\n" +
            "#endif\n" +
            "uniform sampler2D texColor;\n" +
            "uniform sampler2D texNormal;\n" +
            "varying vec2 texC;\n" +
            "varying vec3 vLight;\n" +
            "\n" +
            "\n" +
            "void main(void) {\n" +
            "    vec4 normal = 2.0 * texture2D(texNormal, texC) - 1.0;\n" +
            "    vec4 colr = texture2D(texColor, texC);\n" +
            "    float coler = ((colr.g * 256.0)+colr.b);" +
            "    gl_FragColor = vec4(vLight * coler, 1.0);\n" +
            "}\n";
    }



    /*
     * Creates the four children
     */
    function create() {

        // calculation of children number nodeNumber within their level
        var deltaR = Math.sqrt(Math.pow(4, level));
        var deltaR1 = Math.sqrt(Math.pow(4, level + 1));
        var lt = Math.floor(nodeNumber / deltaR) * 4 * deltaR +
            (nodeNumber % deltaR) * 2;
        var rt = lt + 1;
        var lb = lt + deltaR1;
        var rb = lb + 1;

        // calculation of the scaling factor
        var s = (bvhRefiner._vf.size).multiply(0.25);

        // creation of all children
        children.push(new QuadtreeNode3D_32bit(ctx, bvhRefiner, (level + 1), lt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2), geometry));
        children.push(new QuadtreeNode3D_32bit(ctx, bvhRefiner, (level + 1), rt,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2), geometry));
        children.push(new QuadtreeNode3D_32bit(ctx, bvhRefiner, (level + 1), lb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(-s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2), (rowNr * 2 + 1), geometry));
        children.push(new QuadtreeNode3D_32bit(ctx, bvhRefiner, (level + 1), rb,
            nodeTransformation.mult(x3dom.fields.SFMatrix4f.translation(
                new x3dom.fields.SFVec3f(s.x, -s.y, 0.0))).mult(x3dom.fields.SFMatrix4f.scale(
                new x3dom.fields.SFVec3f(0.5, 0.5, 1.0))), (columnNr * 2 + 1), (rowNr * 2 + 1), geometry));
    }



    /*
     * Decides to create new children and if the node shoud be drawn or not
     */
    this.collectDrawables = function (transform, drawableCollection, singlePath, invalidateCache, planeMask) {

        // definition the actual transformation of the node
        cullObject.localMatrix = nodeTransformation;

        if (exists && (planeMask = drawableCollection.cull(transform, cullObject, singlePath, planeMask)) > 0) {
            var mat_view = drawableCollection.viewMatrix;
            var vPos = mat_view.multMatrixPnt(transform.multMatrixPnt(position));
            var distanceToCamera = Math.sqrt(Math.pow(vPos.x, 2) + Math.pow(vPos.y, 2) + Math.pow(vPos.z, 2));
            if ((distanceToCamera < Math.pow((bvhRefiner._vf.maxDepth - level), 2) * resizeFac / bvhRefiner._vf.factor)) {
                if (children.length === 0 && bvhRefiner.createChildren === 0) {
                    bvhRefiner.createChildren++;
                    create();
                    shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                }
                else if (children.length === 0 && bvhRefiner.createChildren > 0) {
                    shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
                }
                else {
                    for (var i = 0; i < children.length; i++) {
                        children[i].collectDrawables(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask);
                    }
                }
            }
            else {
                shape.collectDrawableObjects(nodeTransformation, drawableCollection, singlePath, invalidateCache, planeMask, []);
            }
        }
    };


    /*
     * Returns the volume of this node
     */
    this.getVolume = function() {
        // TODO; implement correctly, for now just use first shape as workaround
        return shape.getVolume();
    };


    // initializes this node directly after creating
    initialize();
}

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Snout ### */
x3dom.registerNodeType(
    "Snout",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Snout
         * @constructs x3dom.nodeTypes.Snout
         * @x3d x.x
         * @component Geometry3DExt
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Describes a snout shape
         */
        function (ctx) {
            x3dom.nodeTypes.Snout.superClass.call(this, ctx);


            /**
             * Defines the diameter of the bottom surface.
             * @var {x3dom.fields.SFFloat} dbottom
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Snout
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'dbottom', 1.0);

            /**
             * Defines the diameter of the top surface.
             * @var {x3dom.fields.SFFloat} dtop
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Snout
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'dtop', 0.5);

            /**
             * Defines the perpendicular distance between surfaces.
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Snout
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 1.0);

            /**
             * Defines the displacement in x direction.
             * @var {x3dom.fields.SFFloat} xoff
             * @memberof x3dom.nodeTypes.Snout
             * @initvalue 0.25
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'xoff', 0.25);   // Displacement of axes along X-axis

            /**
             * Defines the displacement in y direction.
             * @var {x3dom.fields.SFFloat} yoff
             * @memberof x3dom.nodeTypes.Snout
             * @initvalue 0.25
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'yoff', 0.25);   // Displacement of axes along Y-axis

            /**
             * Specifies whether the bottom exists.
             * @var {x3dom.fields.SFBool} bottom
             * @memberof x3dom.nodeTypes.Snout
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'bottom', true);

            /**
             * Specifies whether the bottom exists.
             * @var {x3dom.fields.SFBool} top
             * @memberof x3dom.nodeTypes.Snout
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'top', true);

            /**
             * Specifies the number of faces that are generated to approximate the snout.
             * @var {x3dom.fields.SFFloat} subdivision
             * @memberof x3dom.nodeTypes.Snout
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            this.rebuildGeometry();
        
        },
        {
            rebuildGeometry: function()
            {
                this._mesh._positions[0] = [];
                this._mesh._normals[0]   = [];
                this._mesh._texCoords[0] = [];
                this._mesh._indices[0]   = [];

                var bottomRadius = this._vf.dbottom / 2, height = this._vf.height;
                var topRadius = this._vf.dtop / 2, sides = this._vf.subdivision;

                var beta, x, z;
                var delta = 2.0 * Math.PI / sides;

                var incl = (bottomRadius - topRadius) / height;
                var nlen = 1.0 / Math.sqrt(1.0 + incl * incl);

                var j = 0, k = 0;
                var h, base;

                if (height > 0) {
                    var px = 0, pz = 0;

                    for (j = 0, k = 0; j <= sides; j++) {
                        beta = j * delta;
                        x = Math.sin(beta);
                        z = -Math.cos(beta);

                        px = x * topRadius + this._vf.xoff;
                        pz = z * topRadius + this._vf.yoff;

                        this._mesh._positions[0].push(px, height / 2, pz);
                        this._mesh._normals[0].push(x / nlen, incl / nlen, z / nlen);
                        this._mesh._texCoords[0].push(1.0 - j / sides, 1);

                        this._mesh._positions[0].push(x * bottomRadius, -height / 2, z * bottomRadius);
                        this._mesh._normals[0].push(x / nlen, incl / nlen, z / nlen);
                        this._mesh._texCoords[0].push(1.0 - j / sides, 0);

                        if (j > 0) {
                            this._mesh._indices[0].push(k    );
                            this._mesh._indices[0].push(k + 2);
                            this._mesh._indices[0].push(k + 1);

                            this._mesh._indices[0].push(k + 1);
                            this._mesh._indices[0].push(k + 2);
                            this._mesh._indices[0].push(k + 3);

                            k += 2;
                        }
                    }
                }

                if (bottomRadius > 0 && this._vf.bottom) {
                    base = this._mesh._positions[0].length / 3;

                    for (j = sides - 1; j >= 0; j--) {
                        beta = j * delta;
                        x = bottomRadius * Math.sin(beta);
                        z = -bottomRadius * Math.cos(beta);

                        this._mesh._positions[0].push(x, -height / 2, z);
                        this._mesh._normals[0].push(0, -1, 0);
                        this._mesh._texCoords[0].push(x / bottomRadius / 2 + 0.5, z / bottomRadius / 2 + 0.5);
                    }

                    h = base + 1;

                    for (j = 2; j < sides; j++) {
                        this._mesh._indices[0].push(h);
                        this._mesh._indices[0].push(base);

                        h = base + j;
                        this._mesh._indices[0].push(h);
                    }
                }

                if (topRadius > x3dom.fields.Eps && this._vf.top) {
                    base = this._mesh._positions[0].length / 3;

                    for (j = sides - 1; j >= 0; j--) {
                        beta = j * delta;
                        x =  topRadius * Math.sin(beta);
                        z = -topRadius * Math.cos(beta);

                        this._mesh._positions[0].push(x + this._vf.xoff, height / 2, z + this._vf.yoff);
                        this._mesh._normals[0].push(0, 1, 0);
                        this._mesh._texCoords[0].push(x / topRadius / 2 + 0.5, 1.0 - z / topRadius / 2 + 0.5);
                    }

                    h = base + 1;

                    for (j = 2; j < sides; j++) {
                        this._mesh._indices[0].push(base);
                        this._mesh._indices[0].push(h);

                        h = base + j;
                        this._mesh._indices[0].push(h);
                    }
                }

                this.invalidateVolume();
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function (fieldName)
            {
                if (fieldName == "dtop" || fieldName == "dbottom" ||
                    fieldName == "height" || fieldName == "subdivision" ||
                    fieldName == "xoff" || fieldName == "yoff" ||
                    fieldName == "bottom" || fieldName == "top")
                {
                    this.rebuildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Dish ### */
x3dom.registerNodeType(
    "Dish",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Dish
         * @constructs x3dom.nodeTypes.Dish
         * @x3d x.x
         * @component Geometry3DExt
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         */
        function (ctx) {
            x3dom.nodeTypes.Dish.superClass.call(this, ctx);


            /**
             * Specifies the diameter of the base.
             * @var {x3dom.fields.SFFloat} diameter
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Dish
             * @initvalue 2
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'diameter', 2);

            /**
             * Defines the maximum height of the dished surface above the base
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Dish
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 1);

            /**
             * Defines the radius of the third semi-principal axes of the ellipsoid
             * @var {x3dom.fields.SFFloat} radius
             * @memberof x3dom.nodeTypes.Dish
             * @initvalue this._vf.diameter/2
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', this._vf.diameter / 2);

            /**
             * The bottom field specifies whether the bottom cap of the dish is created.
             * @var {x3dom.fields.SFBool} bottom
             * @memberof x3dom.nodeTypes.Dish
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'bottom', true);

            /**
             * Specifies the number of faces that are generated to approximate the sides of the dish.
             * The first component specifies the number of rings and the second component the number of subdivisions of each ring.
             * @var {x3dom.fields.SFVec2f} subdivision
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Dish
             * @initvalue 24,24
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'subdivision', 24, 24);

            this.rebuildGeometry();
        
        },
        {
            rebuildGeometry: function()
            {
                this._mesh._positions[0] = [];
                this._mesh._normals[0]   = [];
                this._mesh._texCoords[0] = [];
                this._mesh._indices[0]   = [];

                var twoPi = Math.PI * 2, halfPi = Math.PI / 2;
                var halfDia = this._vf.diameter / 2;

                // If r is 0 or half of diameter, treat as section of sphere, else half of ellipsoid
                var r = this._vf.radius;
                r = (r == 0 || Math.abs(halfDia - r) <= x3dom.fields.Eps) ? halfDia : r;

                // height defines sectional part taken from half of ellipsoid (sphere if r==halfDia)
                var h = Math.min(this._vf.height, r);
                var offset = r - h;

                var a = halfDia;    // 1st semi-principal axes along x
                var b = r;          // 2nd semi-principal axes along y
                var c = halfDia;    // 3rd semi-principal axes along z

                var latitudeBands = this._vf.subdivision.x, longitudeBands = this._vf.subdivision.y;
                var latNumber, longNumber;

                var segTheta = halfPi - Math.asin(1 - h / r);
                var segL = Math.ceil(latitudeBands / halfPi * segTheta);

                var theta, sinTheta, cosTheta;
                var phi, sinPhi, cosPhi;
                var x, y, z, u, v;
                var tmpPosArr = [], tmpTcArr = [];

                for (latNumber = 0; latNumber <= latitudeBands; latNumber++) {
                    if (segL == latNumber) {
                        theta = segTheta;
                    }
                    else {
                        theta = (latNumber * halfPi) / latitudeBands;
                    }
                    sinTheta = Math.sin(theta);
                    cosTheta = Math.cos(theta);

                    for (longNumber = 0; longNumber <= longitudeBands; longNumber++) {
                        phi = (longNumber * twoPi) / longitudeBands;
                        sinPhi = Math.sin(phi);
                        cosPhi = Math.cos(phi);

                        x = a * (-cosPhi * sinTheta);
                        y = b *            cosTheta;
                        z = c * (-sinPhi * sinTheta);

                        u = 0.25 - (longNumber / longitudeBands);
                        v = latNumber / latitudeBands;

                        this._mesh._positions[0].push(x, y - offset, z);
                        this._mesh._texCoords[0].push(u, v);
                        this._mesh._normals[0].push(x/(a*a), y/(b*b), z/(c*c));

                        if ((latNumber == latitudeBands) || (segL == latNumber)) {
                            tmpPosArr.push(x, y - offset, z);
                            tmpTcArr.push(u, v);
                        }
                    }

                    if (segL == latNumber)
                        break;
                }

                for (latNumber = 0; latNumber < latitudeBands; latNumber++) {
                    if (segL == latNumber)
                        break;

                    for (longNumber = 0; longNumber < longitudeBands; longNumber++) {
                        var first = (latNumber * (longitudeBands + 1)) + longNumber;
                        var second = first + longitudeBands + 1;

                        this._mesh._indices[0].push(first + 1);
                        this._mesh._indices[0].push(second);
                        this._mesh._indices[0].push(first);

                        this._mesh._indices[0].push(first + 1);
                        this._mesh._indices[0].push(second + 1);
                        this._mesh._indices[0].push(second);
                    }
                }

                if (this._vf.bottom)
                {
                    var origPos = this._mesh._positions[0].length / 3;
                    var t = origPos + 1;

                    for (var i=0, m=tmpPosArr.length/3; i<m; i++) {
                        var j = 3 * i;
                        this._mesh._positions[0].push(tmpPosArr[j  ]);
                        this._mesh._positions[0].push(tmpPosArr[j+1]);
                        this._mesh._positions[0].push(tmpPosArr[j+2]);
                        j = 2 * i;
                        this._mesh._texCoords[0].push(tmpTcArr[j  ]);
                        this._mesh._texCoords[0].push(tmpTcArr[j+1]);
                        this._mesh._normals[0].push(0, -1, 0);

                        if (i >= 2) {
                            this._mesh._indices[0].push(origPos);
                            this._mesh._indices[0].push(t);

                            t = origPos + i;
                            this._mesh._indices[0].push(t);
                        }
                    }
                }

                this.invalidateVolume();
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "radius" || fieldName == "height" || fieldName == "diameter" ||
                    fieldName == "subdivision" || fieldName == "bottom")
                {
                    this.rebuildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Pyramid ### */
x3dom.registerNodeType(
    "Pyramid",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Pyramid
         * @constructs x3dom.nodeTypes.Pyramid
         * @x3d x.x
         * @component Geometry3DExt
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Describes a pyramid shape.
         */
        function (ctx) {
            x3dom.nodeTypes.Pyramid.superClass.call(this, ctx);


            /**
             * Defines the bottom length in x direction.
             * @var {x3dom.fields.SFFloat} xbottom
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Pyramid
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'xbottom', 1);

            /**
             * Defines the bottom length in y direction.
             * @var {x3dom.fields.SFFloat} ybottom
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Pyramid
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'ybottom', 1);

            /**
             * Defines the top length in x direction.
             * @var {x3dom.fields.SFFloat} xtop
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Pyramid
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'xtop', 0.5);

            /**
             * Defines the top length in y direction.
             * @var {x3dom.fields.SFFloat} ytop
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Pyramid
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'ytop', 0.5);

            /**
             * Defines the Distance between the bottom and the top faces.
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Pyramid
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 1);

            /**
             * Defines the displacement along the x axis.
             * @var {x3dom.fields.SFFloat} xoff
             * @memberof x3dom.nodeTypes.Pyramid
             * @initvalue 0.25
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'xoff', 0.25);

            /**
             * Defines the displacement along the y axis.
             * @var {x3dom.fields.SFFloat} yoff
             * @memberof x3dom.nodeTypes.Pyramid
             * @initvalue 0.25
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'yoff', 0.25);

            var xTop = this._vf.xtop / 2;
            var yTop = this._vf.ytop / 2;
            var xBot = this._vf.xbottom / 2;
            var yBot = this._vf.ybottom / 2;
            var xOff = this._vf.xoff;
            var yOff = this._vf.yoff;
            var sy = this._vf.height / 2;

            this._mesh._positions[0] = [
                -xBot,       -sy, -yBot,        -xTop + xOff, sy, -yTop + yOff,  xTop + xOff, sy, -yTop + yOff,  xBot,       -sy, -yBot,
                -xBot,       -sy,  yBot,        -xTop + xOff, sy,  yTop + yOff,  xTop + xOff, sy,  yTop + yOff,  xBot,       -sy,  yBot,
                -xBot,       -sy, -yBot,        -xBot,       -sy,  yBot,        -xTop + xOff, sy,  yTop + yOff, -xTop + xOff, sy, -yTop + yOff,
                xBot,       -sy, -yBot,         xBot,       -sy,  yBot,         xTop + xOff, sy,  yTop + yOff,  xTop + xOff, sy, -yTop + yOff,
                    -xTop + xOff, sy, -yTop + yOff, -xTop + xOff, sy,  yTop + yOff,  xTop + xOff, sy,  yTop + yOff,  xTop + xOff, sy, -yTop + yOff,
                -xBot,       -sy, -yBot,        -xBot,       -sy,  yBot,         xBot,       -sy,  yBot,         xBot,       -sy, -yBot
            ];
            this._mesh._texCoords[0] = [
                1, 0, 1, 1, 0, 1, 0, 0,
                0, 0, 0, 1, 1, 1, 1, 0,
                0, 0, 1, 0, 1, 1, 0, 1,
                1, 0, 0, 0, 0, 1, 1, 1,
                0, 1, 0, 0, 1, 0, 1, 1,
                0, 0, 0, 1, 1, 1, 1, 0
            ];
            this._mesh._indices[0] = [
                0, 1, 2, 2, 3, 0,
                6, 5, 4, 4, 7, 6,
                8, 9, 10, 10, 11, 8,
                12, 15, 14, 14, 13, 12,
                16, 17, 18, 18, 19, 16,
                20, 23, 22, 22, 21, 20
            ];

            // attention, we share per side, therefore creaseAngle > 0
            this._mesh.calcNormals(Math.PI, this._vf.ccw);

            this._mesh._invalidate = true;
            this._mesh._numFaces = 12;
            this._mesh._numCoords = 24;
        
        },
        {
            fieldChanged: function(fieldName)
            {
                if (fieldName == "xbottom" || fieldName == "ybottom" ||
                    fieldName == "xtop" || fieldName == "ytop" ||
                    fieldName == "xoff" || fieldName == "yoff" || fieldName == "height")
                {
                    var xTop = this._vf.xtop / 2;
                    var yTop = this._vf.ytop / 2;
                    var xBot = this._vf.xbottom / 2;
                    var yBot = this._vf.ybottom / 2;
                    var xOff = this._vf.xoff;
                    var yOff = this._vf.yoff;
                    var sy = this._vf.height / 2;

                    this._mesh._positions[0] = [
                        -xBot,       -sy, -yBot,        -xTop + xOff, sy, -yTop + yOff,  xTop + xOff, sy, -yTop + yOff,  xBot,       -sy, -yBot,
                        -xBot,       -sy,  yBot,        -xTop + xOff, sy,  yTop + yOff,  xTop + xOff, sy,  yTop + yOff,  xBot,       -sy,  yBot,
                        -xBot,       -sy, -yBot,        -xBot,       -sy,  yBot,        -xTop + xOff, sy,  yTop + yOff, -xTop + xOff, sy, -yTop + yOff,
                        xBot,       -sy, -yBot,         xBot,       -sy,  yBot,         xTop + xOff, sy,  yTop + yOff,  xTop + xOff, sy, -yTop + yOff,
                            -xTop + xOff, sy, -yTop + yOff, -xTop + xOff, sy,  yTop + yOff,  xTop + xOff, sy,  yTop + yOff,  xTop + xOff, sy, -yTop + yOff,
                        -xBot,       -sy, -yBot,        -xBot,       -sy,  yBot,         xBot,       -sy,  yBot,         xBot,       -sy, -yBot
                    ];

                    this._mesh._normals[0] = [];
                    this._mesh.calcNormals(Math.PI, this._vf.ccw);

                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### RectangularTorus ### */
x3dom.registerNodeType(
    "RectangularTorus",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for RectangularTorus
         * @constructs x3dom.nodeTypes.RectangularTorus
         * @x3d x.x
         * @component Geometry3DExt
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Describes a rectangular torus shape.
         */
        function (ctx) {
            x3dom.nodeTypes.RectangularTorus.superClass.call(this, ctx);


            /**
             * Defines the inner radius of the torus.
             * @var {x3dom.fields.SFFloat} innerRadius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.RectangularTorus
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'innerRadius', 0.5); //Inside radius

            /**
             * Defines the outer radius of the torus.
             * @var {x3dom.fields.SFFloat} outerRadius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.RectangularTorus
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'outerRadius', 1);	//Outside radius

            /**
             * Defines the height of the torus.
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.RectangularTorus
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 1);	    //Height of rectangular section

            /**
             * Defines the length of the torus as angle. 2 * PI is a full torus.
             * @var {x3dom.fields.SFFloat} angle
             * @range [0, 2*pi]
             * @memberof x3dom.nodeTypes.RectangularTorus
             * @initvalue 2*Math.PI
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'angle', 2 * Math.PI);	//Subtended angle

            /**
             * Specifies whether the side caps exist.
             * @var {x3dom.fields.SFBool} caps
             * @memberof x3dom.nodeTypes.RectangularTorus
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'caps', true);        //Show side caps

            /**
             * Specifies the number of rings that are generated to approximate the torus.
             * @var {x3dom.fields.SFFloat} subdivision
             * @memberof x3dom.nodeTypes.RectangularTorus
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            this._origCCW = this._vf.ccw;

            this.rebuildGeometry();
        
        },
        {
            rebuildGeometry: function()
            {
                this._mesh._positions[0] = [];
                this._mesh._normals[0]   = [];
                this._mesh._texCoords[0] = [];
                this._mesh._indices[0]   = [];

                var twoPi = 2.0 * Math.PI;

                this._vf.ccw = !this._origCCW;

                // assure that angle in [0, 2 * PI]
                if (this._vf.angle < 0)
                    this._vf.angle = 0;
                else if (this._vf.angle > twoPi)
                    this._vf.angle = twoPi;

                // assure that innerRadius < outerRadius
                if (this._vf.innerRadius > this._vf.outerRadius)
                {
                    var tmp = this._vf.innerRadius;
                    this._vf.innerRadius = this._vf.outerRadius;
                    this._vf.outerRadius = tmp;
                }

                var innerRadius = this._vf.innerRadius;
                var outerRadius = this._vf.outerRadius;
                var height = this._vf.height / 2;
                var angle = this._vf.angle;
                var sides = this._vf.subdivision;

                var beta, x, z, k, j, nx, nz;
                var delta = angle / sides;

                //Outer Side
                for (j=0, k=0; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.cos(beta);
                    nz = -Math.sin(beta);

                    x = outerRadius * nx;
                    z = outerRadius * nz;

                    this._mesh._positions[0].push(x, -height, z);
                    this._mesh._normals[0].push(nx, 0, nz);

                    this._mesh._positions[0].push(x, height, z);
                    this._mesh._normals[0].push(nx, 0, nz);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k    );
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 3);

                        k += 2;
                    }
                }

                //Inner Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.cos(beta);
                    nz = -Math.sin(beta);

                    x = innerRadius * nx;
                    z = innerRadius * nz;

                    this._mesh._positions[0].push(x, -height, z);
                    this._mesh._normals[0].push(-nx, 0, -nz);

                    this._mesh._positions[0].push(x, height, z);
                    this._mesh._normals[0].push(-nx, 0, -nz);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k    );

                        this._mesh._indices[0].push(k + 3);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        k += 2;
                    }
                }

                //Top Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.cos(beta);
                    nz = -Math.sin(beta);

                    x = outerRadius * nx;
                    z = outerRadius * nz;

                    this._mesh._positions[0].push(x, height, z);
                    this._mesh._normals[0].push(0, 1, 0);

                    x = innerRadius * nx;
                    z = innerRadius * nz;

                    this._mesh._positions[0].push(x, height, z);
                    this._mesh._normals[0].push(0, 1, 0);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k    );
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 3);

                        k += 2;
                    }
                }

                //Bottom Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.cos(beta);
                    nz = -Math.sin(beta);

                    x = outerRadius * nx;
                    z = outerRadius * nz;

                    this._mesh._positions[0].push(x, -height, z);
                    this._mesh._normals[0].push(0, -1, 0);

                    x = innerRadius * nx;
                    z = innerRadius * nz;

                    this._mesh._positions[0].push(x, -height, z);
                    this._mesh._normals[0].push(0, -1, 0);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k    );

                        this._mesh._indices[0].push(k + 3);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        k += 2;
                    }
                }

                //Create Caps
                if (angle < twoPi && this._vf.caps == true)
                {
                    //First Cap
                    k += 2;

                    x = outerRadius;
                    z = 0;

                    this._mesh._positions[0].push(x, height, z);
                    this._mesh._normals[0].push(0, 0, 1);
                    this._mesh._positions[0].push(x, -height, z);
                    this._mesh._normals[0].push(0, 0, 1);

                    x = innerRadius;
                    z = 0;

                    this._mesh._positions[0].push(x, height, z);
                    this._mesh._normals[0].push(0, 0, 1);
                    this._mesh._positions[0].push(x, -height, z);
                    this._mesh._normals[0].push(0, 0, 1);

                    this._mesh._indices[0].push(k    );
                    this._mesh._indices[0].push(k + 1);
                    this._mesh._indices[0].push(k + 2);

                    this._mesh._indices[0].push(k + 2);
                    this._mesh._indices[0].push(k + 1);
                    this._mesh._indices[0].push(k + 3);

                    //Second Cap
                    k+=4;

                    nx =  Math.cos(angle);
                    nz = -Math.sin(angle);

                    x = outerRadius * nx;
                    z = outerRadius * nz;

                    this._mesh._positions[0].push(x, height, z);
                    this._mesh._normals[0].push(nz, 0, -nx);
                    this._mesh._positions[0].push(x, -height, z);
                    this._mesh._normals[0].push(nz, 0, -nx);

                    x = innerRadius * nx;
                    z = innerRadius * nz;

                    this._mesh._positions[0].push(x, height, z);
                    this._mesh._normals[0].push(nz, 0, -nx);
                    this._mesh._positions[0].push(x, -height, z);
                    this._mesh._normals[0].push(nz, 0, -nx);

                    this._mesh._indices[0].push(k + 2);
                    this._mesh._indices[0].push(k + 1);
                    this._mesh._indices[0].push(k    );

                    this._mesh._indices[0].push(k + 3);
                    this._mesh._indices[0].push(k + 1);
                    this._mesh._indices[0].push(k + 2);
                }

                this.invalidateVolume();
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "innerRadius" || fieldName == "outerRadius" ||
                    fieldName == "height" || fieldName == "angle" ||
                    fieldName == "subdivision" || fieldName == "caps")
                {
                    this.rebuildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### SlopedCylinder ### */
x3dom.registerNodeType(
    "SlopedCylinder",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for SlopedCylinder
         * @constructs x3dom.nodeTypes.SlopedCylinder
         * @x3d x.x
         * @component Geometry3DExt
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Describes a sloped cylinder shape
         */
        function (ctx) {
            x3dom.nodeTypes.SlopedCylinder.superClass.call(this, ctx);


            /**
             * Defines the radius of the sloped cylinder.
             * @var {x3dom.fields.SFFloat} radius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', 1.0);

            /**
             * Defines the height of the sloped cylinder
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue 2.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 2.0);

            /**
             * Defines whether the bottom exists.
             * @var {x3dom.fields.SFBool} bottom
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'bottom', true);

            /**
             * Defines whether the top exists.
             * @var {x3dom.fields.SFBool} top
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'top', true);

            /**
             * Defines the shear for the top in x direction.
             * @var {x3dom.fields.SFFloat} xtshear
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue 0.26179
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'xtshear', 0.26179);

            /**
             * Defines the shear for the top in y direction.
             * @var {x3dom.fields.SFFloat} ytshear
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue 0.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'ytshear', 0.0);

            /**
             * Defines the shear for the bottom in x direction.
             * @var {x3dom.fields.SFFloat} xbshear
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue 0.26179
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'xbshear', 0.26179);

            /**
             * Defines the shear for the bottom in y direction.
             * @var {x3dom.fields.SFFloat} ybshear
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue 0.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'ybshear', 0.0);

            /**
             * Specifies the number of faces that are generated to approximate the cylinder.
             * @var {x3dom.fields.SFFloat} subdivision
             * @memberof x3dom.nodeTypes.SlopedCylinder
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            this.rebuildGeometry();
        
        },
        {
            rebuildGeometry: function()
            {
                this._mesh._positions[0] = [];
                this._mesh._normals[0]   = [];
                this._mesh._texCoords[0] = [];
                this._mesh._indices[0]   = [];

                var topSlopeX = this._vf.xtshear;
                var topSlopeY = this._vf.ytshear;
                var botSlopeX = this._vf.xbshear;
                var botSlopeY = this._vf.ybshear;
                var sides = this._vf.subdivision;

                var radius = this._vf.radius;
                var height = this._vf.height / 2;

                var delta = 2.0 * Math.PI / sides;
                var beta, x, y, z;
                var j, k;

                for (j=0, k=0; j<=sides; j++)
                {
                    beta = j * delta;
                    x =  Math.sin(beta);
                    z = -Math.cos(beta);

                    this._mesh._positions[0].push(x * radius, -height + x * botSlopeX + z * botSlopeY, z * radius);
                    this._mesh._texCoords[0].push(1.0 - j / sides, 0);

                    this._mesh._positions[0].push(x * radius,  height + x * topSlopeX + z * topSlopeY, z * radius);
                    this._mesh._texCoords[0].push(1.0 - j / sides, 1);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k    );
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 3);

                        k += 2;
                    }
                }

                var h, base;

                if (this._vf.top && radius > 0)
                {
                    base = this._mesh._positions[0].length / 3;

                    for (j=sides-1; j>=0; j--)
                    {
                        k = 6 * j;
                        x = this._mesh._positions[0][k+3];
                        y = this._mesh._positions[0][k+4];
                        z = this._mesh._positions[0][k+5];

                        this._mesh._positions[0].push(x, y, z);
                        this._mesh._texCoords[0].push(x / 2 + 0.5, -z / 2 + 0.5);
                    }

                    h = base + 1;

                    for (j=2; j<sides; j++)
                    {
                        this._mesh._indices[0].push(base);
                        this._mesh._indices[0].push(h);

                        h = base + j;
                        this._mesh._indices[0].push(h);
                    }
                }

                if (this._vf.bottom && radius > 0)
                {
                    base = this._mesh._positions[0].length / 3;

                    for (j=sides-1; j>=0; j--)
                    {
                        k = 6 * j;
                        x = this._mesh._positions[0][k  ];
                        y = this._mesh._positions[0][k+1];
                        z = this._mesh._positions[0][k+2];

                        this._mesh._positions[0].push(x, y, z);
                        this._mesh._texCoords[0].push(x / 2 + 0.5, z / 2 + 0.5);
                    }

                    h = base + 1;

                    for (j=2; j<sides; j++)
                    {
                        this._mesh._indices[0].push(h);
                        this._mesh._indices[0].push(base);

                        h = base + j;
                        this._mesh._indices[0].push(h);
                    }
                }

                // calculate normals and adjust them at seam
                this._mesh.calcNormals(Math.PI, this._vf.ccw);

                var n0b = new x3dom.fields.SFVec3f(this._mesh._normals[0][0],
                    this._mesh._normals[0][1],
                    this._mesh._normals[0][2]);
                var n0t = new x3dom.fields.SFVec3f(this._mesh._normals[0][3],
                    this._mesh._normals[0][4],
                    this._mesh._normals[0][5]);
                k = 6 * sides;
                var n1b = new x3dom.fields.SFVec3f(this._mesh._normals[0][k  ],
                    this._mesh._normals[0][k+1],
                    this._mesh._normals[0][k+2]);
                var n1t = new x3dom.fields.SFVec3f(this._mesh._normals[0][k+3],
                    this._mesh._normals[0][k+4],
                    this._mesh._normals[0][k+5]);

                var nb = n0b.add(n1b).normalize();
                var nt = n0t.add(n1t).normalize();

                this._mesh._normals[0][0] = nb.x;
                this._mesh._normals[0][1] = nb.y;
                this._mesh._normals[0][2] = nb.z;
                this._mesh._normals[0][3] = nt.x;
                this._mesh._normals[0][4] = nt.y;
                this._mesh._normals[0][5] = nt.z;

                this._mesh._normals[0][k  ] = nb.x;
                this._mesh._normals[0][k+1] = nb.y;
                this._mesh._normals[0][k+2] = nb.z;
                this._mesh._normals[0][k+3] = nt.x;
                this._mesh._normals[0][k+4] = nt.y;
                this._mesh._normals[0][k+5] = nt.z;

                this.invalidateVolume();
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "xtshear" || fieldName == "ytshear" ||
                    fieldName == "xbshear" || fieldName == "ybshear" ||
                    fieldName == "radius" || fieldName == "height" ||
                    fieldName == "bottom" || fieldName == "top" || fieldName == "subdivision")
                {
                    this.rebuildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Nozzle ### */
x3dom.registerNodeType(
    "Nozzle",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for Nozzle
         * @constructs x3dom.nodeTypes.Nozzle
         * @x3d x.x
         * @component Geometry3DExt
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc This node describes a nozzle shape for a pipe.
         */
        function (ctx) {
            x3dom.nodeTypes.Nozzle.superClass.call(this, ctx);


            /**
             * Defines the height of the nozzle.
             * @var {x3dom.fields.SFFloat} nozzleHeight
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Nozzle
             * @initvalue 0.1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'nozzleHeight', 0.1);

            /**
             * Defines the radius of the nozzle.
             * @var {x3dom.fields.SFFloat} nozzleRadius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Nozzle
             * @initvalue 0.6
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'nozzleRadius', 0.6);

            /**
             * Defines the height of the pipe.
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Nozzle
             * @initvalue 1.0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 1.0);

            /**
             * Defines the outer radius of the pipe.
             * @var {x3dom.fields.SFFloat} outerRadius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Nozzle
             * @initvalue 0.5
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'outerRadius', 0.5);

            /**
             * Defines the inner radius of the pipe.
             * @var {x3dom.fields.SFFloat} innerRadius
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Nozzle
             * @initvalue 0.4
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'innerRadius', 0.4);

            /**
             * Specifies the number of faces that are generated to approximate the sides of the pipe and nozzle.
             * @var {x3dom.fields.SFFloat} subdivision
             * @range [2, inf]
             * @memberof x3dom.nodeTypes.Nozzle
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            this.rebuildGeometry();
        
        },
        {
            rebuildGeometry: function()
            {
                this._mesh._positions[0] = [];
                this._mesh._normals[0]   = [];
                this._mesh._texCoords[0] = [];
                this._mesh._indices[0]   = [];

                var twoPi = 2.0 * Math.PI;
                var sides = this._vf.subdivision;

                var height = this._vf.height;
                var center = height / 2;

                if (this._vf.innerRadius > this._vf.outerRadius)
                {
                    var tmp = this._vf.innerRadius;
                    this._vf.innerRadius = this._vf.outerRadius;
                    this._vf.outerRadius = tmp;
                }
                var innerRadius = this._vf.innerRadius;
                var outerRadius = this._vf.outerRadius;

                if (this._vf.nozzleRadius < outerRadius)
                {
                    this._vf.nozzleRadius = outerRadius;
                }
                var nozzleRadius = this._vf.nozzleRadius;

                if (this._vf.nozzleHeight > height)
                {
                    this._vf.nozzleHeight = height;
                }
                var nozzleHeight = this._vf.nozzleHeight;

                var beta, delta, x, z, k, j, nx, nz;
                delta = twoPi / sides;

                //Outer Stem Side
                for (j=0, k=0; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.sin(beta);
                    nz = -Math.cos(beta);

                    x = outerRadius * nx;
                    z = outerRadius * nz;

                    this._mesh._positions[0].push(x, -center, z);
                    this._mesh._normals[0].push(nx, 0, nz);

                    this._mesh._positions[0].push(x, (height-nozzleHeight)-center, z);
                    this._mesh._normals[0].push(nx, 0, nz);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k    );
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 3);

                        k += 2;
                    }
                }

                //Inner Stem Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.sin(beta);
                    nz = -Math.cos(beta);

                    x = innerRadius * nx;
                    z = innerRadius * nz;

                    this._mesh._positions[0].push(x, -center, z);
                    this._mesh._normals[0].push(-nx, 0, -nz);

                    this._mesh._positions[0].push(x, (height-nozzleHeight)-center, z);
                    this._mesh._normals[0].push(-nx, 0, -nz);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k    );

                        this._mesh._indices[0].push(k + 3);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        k += 2;
                    }
                }

                //Stem Bottom Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.sin(beta);
                    nz = -Math.cos(beta);

                    x = outerRadius * nx;
                    z = outerRadius * nz;

                    this._mesh._positions[0].push(x, -center, z);
                    this._mesh._normals[0].push(0, -1, 0);

                    x = innerRadius * nx;
                    z = innerRadius * nz;

                    this._mesh._positions[0].push(x, -center, z);
                    this._mesh._normals[0].push(0, -1, 0);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k    );

                        this._mesh._indices[0].push(k + 3);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        k += 2;
                    }
                }

                //Outer Nozzle Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.sin(beta);
                    nz = -Math.cos(beta);

                    x = nozzleRadius * nx;
                    z = nozzleRadius * nz;

                    this._mesh._positions[0].push(x, (height-nozzleHeight)-center, z);
                    this._mesh._normals[0].push(nx, 0, nz);

                    this._mesh._positions[0].push(x, center, z);
                    this._mesh._normals[0].push(nx, 0, nz);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k    );
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 3);

                        k += 2;
                    }
                }

                //Inner Nozzle Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.sin(beta);
                    nz = -Math.cos(beta);

                    x = innerRadius * nx;
                    z = innerRadius * nz;

                    this._mesh._positions[0].push(x, (height-nozzleHeight)-center, z);
                    this._mesh._normals[0].push(-nx, 0, -nz);

                    this._mesh._positions[0].push(x, center, z);
                    this._mesh._normals[0].push(-nx, 0, -nz);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k    );

                        this._mesh._indices[0].push(k + 3);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        k += 2;
                    }
                }

                //Nozzle Bottom Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.sin(beta);
                    nz = -Math.cos(beta);

                    x = nozzleRadius * nx;
                    z = nozzleRadius * nz;

                    this._mesh._positions[0].push(x, (height-nozzleHeight)-center, z);
                    this._mesh._normals[0].push(0, -1, 0);

                    x = outerRadius * nx;
                    z = outerRadius * nz;

                    this._mesh._positions[0].push(x, (height-nozzleHeight)-center, z);
                    this._mesh._normals[0].push(0, -1, 0);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k    );

                        this._mesh._indices[0].push(k + 3);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        k += 2;
                    }
                }

                //Nozzle Top Side
                for (j=0, k=k+2; j<=sides; j++)
                {
                    beta = j * delta;
                    nx =  Math.sin(beta);
                    nz = -Math.cos(beta);

                    x = nozzleRadius * nx;
                    z = nozzleRadius * nz;

                    this._mesh._positions[0].push(x, center, z);
                    this._mesh._normals[0].push(0, 1, 0);

                    x = innerRadius * nx;
                    z = innerRadius * nz;

                    this._mesh._positions[0].push(x, center, z);
                    this._mesh._normals[0].push(0, 1, 0);

                    if (j > 0)
                    {
                        this._mesh._indices[0].push(k    );
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 2);

                        this._mesh._indices[0].push(k + 2);
                        this._mesh._indices[0].push(k + 1);
                        this._mesh._indices[0].push(k + 3);

                        k += 2;
                    }
                }

                this.invalidateVolume();
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "nozzleHeight" || fieldName == "nozzleRadius" || fieldName == "height" ||
                    fieldName == "outerRadius" || fieldName == "innerRadius" || fieldName == "subdivision")
                {
                    this.rebuildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### SolidOfRevolution ### */
x3dom.registerNodeType(
    "SolidOfRevolution",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for SolidOfRevolution
         * @constructs x3dom.nodeTypes.SolidOfRevolution
         * @x3d x.x
         * @component Geometry3DExt
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Describes a SolidOfRevolution shape.
         */
        function (ctx) {
            x3dom.nodeTypes.SolidOfRevolution.superClass.call(this, ctx);


            /**
             * The creaseAngle field affects how default normals are generated.
             * If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced.
             * Crease angles shall be greater than or equal to 0.0 angle base units.
             * @var {x3dom.fields.SFFloat} creaseAngle
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.SolidOfRevolution
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'creaseAngle', 0);

            /**
             * Defines the cross section that is swept around the axis. The cross section is described as an array of 2D vertices.
             * @var {x3dom.fields.MFVec2f} crossSection
             * @memberof x3dom.nodeTypes.SolidOfRevolution
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFVec2f(ctx, 'crossSection', []);

            /**
             * The subtended angle through which the 2D loop is swept around the x axis.
             * @var {x3dom.fields.SFFloat} angle
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.SolidOfRevolution
             * @initvalue 2*Math.PI
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'angle', 2*Math.PI);

            /**
             *Specifies whether the caps exist.
             * @var {x3dom.fields.SFBool} caps
             * @memberof x3dom.nodeTypes.SolidOfRevolution
             * @initvalue true
             * @field x3dom
             * @instance
             */
            this.addField_SFBool(ctx, 'caps', true);

            /**
             * Specifies the number of steps that are generated to approximate the shape.
             * @var {x3dom.fields.SFFloat} subdivision
             * @memberof x3dom.nodeTypes.SolidOfRevolution
             * @initvalue 32
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'subdivision', 32);

            this._origCCW = this._vf.ccw;

            this.rebuildGeometry();
        
        },
        {
            rebuildGeometry: function()
            {
                this._mesh._positions[0] = [];
                this._mesh._normals[0]   = [];
                this._mesh._texCoords[0] = [];
                this._mesh._indices[0]   = [];

                // assure that angle in [-2.Pi, 2.PI]
                var twoPi = 2.0 * Math.PI;
                if (this._vf.angle < -twoPi)
                    this._vf.angle = -twoPi;
                else if (this._vf.angle > twoPi)
                    this._vf.angle = twoPi;

                var crossSection = this._vf.crossSection, angle = this._vf.angle, steps = this._vf.subdivision;
                var i, j, k, l, m, n = crossSection.length;

                if (n < 1) {
                    x3dom.debug.logWarning("SolidOfRevolution requires crossSection curve.");
                    return;
                }

                var loop = (n > 2) ? crossSection[0].equals(crossSection[n-1], x3dom.fields.Eps) : false;
                var fullRevolution = (twoPi - Math.abs(angle) <= x3dom.fields.Eps);

                var alpha, delta = angle / steps;
                var positions = [], baseCurve = [];

                // fix wrong face orientation in case of clockwise rotation
                this._vf.ccw = (angle < 0) ? this._origCCW : !this._origCCW;

                // check if side caps are required
                if (!loop)
                {
                    if (Math.abs(crossSection[n-1].y) > x3dom.fields.Eps) {
                        crossSection.push(new x3dom.fields.SFVec2f(crossSection[n-1].x, 0));
                    }
                    if (Math.abs(crossSection[0].y) > x3dom.fields.Eps) {
                        crossSection.unshift(new x3dom.fields.SFVec2f(crossSection[0].x, 0));
                    }
                    n = crossSection.length;
                }

                // check curvature, starting from 2nd segment, and adjust base curve
                var pos = null, lastPos = null, penultimatePos = null;
                var duplicate = [];    // to be able to sort out duplicates for caps

                for (j=0; j<n; j++)
                {
                    if (pos) {
                        if (lastPos) {
                            penultimatePos = lastPos;
                        }
                        lastPos = pos;
                    }

                    pos = new x3dom.fields.SFVec3f(crossSection[j].x, 0, crossSection[j].y);

                    if (j >= 2)
                    {
                        alpha = pos.subtract(lastPos).normalize();
                        alpha = alpha.dot(lastPos.subtract(penultimatePos).normalize());
                        alpha = Math.abs(Math.cos(alpha));

                        if (alpha > this._vf.creaseAngle)
                        {
                            baseCurve.push(x3dom.fields.SFVec3f.copy(lastPos));
                            duplicate.push(true);
                        }
                        // TODO; handle case that curve is loop and angle smaller creaseAngle
                    }

                    baseCurve.push(pos);
                    duplicate.push(false);
                }

                n = baseCurve.length;

                // generate body of revolution (with rotation around x-axis)
                for (i=0, alpha=0; i<=steps; i++, alpha+=delta)
                {
                    var mat = x3dom.fields.SFMatrix4f.rotationX(alpha);

                    for (j=0; j<n; j++)
                    {
                        pos = mat.multMatrixPnt(baseCurve[j]);
                        positions.push(pos);

                        this._mesh._positions[0].push(pos.x, pos.y, pos.z);

                        if (i > 0 && j > 0)
                        {
                            this._mesh._indices[0].push((i-1)*n+(j-1), (i-1)*n+ j   ,  i   *n+ j   );
                            this._mesh._indices[0].push( i   *n+ j   ,  i   *n+(j-1), (i-1)*n+(j-1));
                        }
                    }
                }

                if (!fullRevolution && this._vf.caps == true)
                {
                    // add first cap
                    var linklist = new x3dom.DoublyLinkedList();
                    m = this._mesh._positions[0].length / 3;

                    for (j=0, i=0; j<n; j++)
                    {
                        if (!duplicate[j])
                        {
                            // Tessellation leads to errors with duplicated vertices if polygon not convex
                            linklist.appendNode(new x3dom.DoublyLinkedList.ListNode(positions[j], i++));

                            pos = positions[j];
                            this._mesh._positions[0].push(pos.x, pos.y, pos.z);
                        }
                    }

                    var linklist_indices = x3dom.EarClipping.getIndexes(linklist);

                    for (j=linklist_indices.length-1; j>=0; j--)
                    {
                        this._mesh._indices[0].push(m + linklist_indices[j]);
                    }

                    // second cap
                    m = this._mesh._positions[0].length / 3;

                    for (j=0; j<n; j++)
                    {
                        if (!duplicate[j])
                        {
                            pos = positions[n * steps + j];
                            this._mesh._positions[0].push(pos.x, pos.y, pos.z);
                        }
                    }

                    for (j=0; j<linklist_indices.length; j++)
                    {
                        this._mesh._indices[0].push(m + linklist_indices[j]);
                    }
                }

                // calculate and readjust normals if full revolution
                this._mesh.calcNormals(Math.PI, this._vf.ccw);

                if (fullRevolution)
                {
                    m = 3 * n * steps;

                    for (j=0; j<n; j++)
                    {
                        k = 3 * j;
                        this._mesh._normals[0][m+k  ] = this._mesh._normals[0][k  ];
                        this._mesh._normals[0][m+k+1] = this._mesh._normals[0][k+1];
                        this._mesh._normals[0][m+k+2] = this._mesh._normals[0][k+2];
                    }
                }

                this._mesh.calcTexCoords("");

                this.invalidateVolume();
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "crossSection" || fieldName == "angle" || fieldName == "caps" ||
                    fieldName == "subdivision" || fieldName == "creaseAngle")
                {
                    this.rebuildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### SphereSegment ### */
x3dom.registerNodeType(
    "SphereSegment",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DSpatialGeometryNode,
        
        /**
         * Constructor for SphereSegment
         * @constructs x3dom.nodeTypes.SphereSegment
         * @x3d x.x
         * @component Geometry3DExt
         * @extends x3dom.nodeTypes.X3DSpatialGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Describes a sphere segment shape.
         */
        function (ctx) {
            x3dom.nodeTypes.SphereSegment.superClass.call(this, ctx);


            /**
             * Defines the radius of the sphere.
             * @var {x3dom.fields.SFFloat} radius
             * @memberof x3dom.nodeTypes.SphereSegment
             * @initvalue 1
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'radius', 1);

            /**
             * Defines an array of longitude values.
             * @var {x3dom.fields.MFFloat} longitude
             * @memberof x3dom.nodeTypes.SphereSegment
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'longitude', []);

            /**
             * Defines an array of latitude values.
             * @var {x3dom.fields.MFFloat} latitude
             * @memberof x3dom.nodeTypes.SphereSegment
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'latitude', []);

            /**
             * Defines an array of stepSizes.
             * @var {x3dom.fields.SFVec2f} stepSize
             * @memberof x3dom.nodeTypes.SphereSegment
             * @initvalue 1,1
             * @field x3dom
             * @instance
             */
            this.addField_SFVec2f(ctx, 'stepSize', 1, 1);

            var r = this._vf.radius;
            var longs = this._vf.longitude;
            var lats = this._vf.latitude;

            var subx = longs.length, suby = lats.length;
            var first, second;

            var latNumber, longNumber;
            var latitudeBands = suby;
            var longitudeBands = subx;

            var theta, sinTheta, cosTheta;
            var phi, sinPhi, cosPhi;
            var x, y, z, u, v;

            for (latNumber = 0; latNumber <= latitudeBands; latNumber++) {
                theta = ((lats[latNumber]+90) * Math.PI) / 180;
                sinTheta = Math.sin(theta);
                cosTheta = Math.cos(theta);

                for (longNumber = 0; longNumber <= longitudeBands; longNumber++) {
                    phi = ((longs[longNumber]) * Math.PI) / 180;

                    sinPhi = Math.sin(phi);
                    cosPhi = Math.cos(phi);

                    x = -cosPhi * sinTheta;
                    y = -cosTheta;
                    z = -sinPhi * sinTheta;

                    u = longNumber / (longitudeBands-1);
                    v = latNumber / (latitudeBands-1);

                    this._mesh._positions[0].push(r * x, r * y, r * z);
                    this._mesh._normals[0].push(x, y, z);
                    this._mesh._texCoords[0].push(u, v);
                }
            }

            for (latNumber = 0; latNumber < latitudeBands; latNumber++) {
                for (longNumber = 0; longNumber < longitudeBands; longNumber++) {
                    first = (latNumber * (longitudeBands + 1)) + longNumber;
                    second = first + longitudeBands + 1;

                    this._mesh._indices[0].push(first);
                    this._mesh._indices[0].push(second);
                    this._mesh._indices[0].push(first + 1);

                    this._mesh._indices[0].push(second);
                    this._mesh._indices[0].push(second + 1);
                    this._mesh._indices[0].push(first + 1);
                }
            }

            this._mesh._invalidate = true;
            this._mesh._numFaces = this._mesh._indices[0].length / 3;
            this._mesh._numCoords = this._mesh._positions[0].length / 3;
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 *
 */

 /* Added support for changes of the "color" field
  * (C) 2014 Toshiba Corporation
  * Dual licensed under the MIT and GPL
  */

/* ### ElevationGrid ### */
x3dom.registerNodeType(
    "ElevationGrid",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,

        /**
         * Constructor for ElevationGrid
         * @constructs x3dom.nodeTypes.ElevationGrid
         * @x3d 3.3
         * @component Geometry3DExt
         * @status experimental
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The ElevationGrid node specifies a uniform rectangular grid of varying height in the Y=0 plane of the local coordinate system.
         * The geometry is described by a scalar array of height values that specify the height of a surface above each point of the grid.
         * The xDimension and zDimension fields indicate the number of elements of the grid height array in the X and Z directions.
         * Both xDimension and zDimension shall be greater than or equal to zero.
         * If either the xDimension or the zDimension is less than two, the ElevationGrid contains no quadrilaterals.
         * The vertex locations for the rectangles are defined by the height field and the xSpacing and zSpacing fields
         */
        function (ctx) {
            x3dom.nodeTypes.ElevationGrid.superClass.call(this, ctx);


            /**
             * The colorPerVertex field determines whether colours specified in the color field are applied to each vertex or each quadrilateral of the ElevationGrid node.
             * If colorPerVertex is FALSE and the color field is not NULL, the color field shall specify a node derived from X3DColorNode containing at least (xDimension-1)×(zDimension-1) colours; one for each quadrilateral.
             * If colorPerVertex is TRUE and the color field is not NULL, the color field shall specify a node derived from X3DColorNode containing at least xDimension × zDimension colours, one for each vertex.
             * @var {x3dom.fields.SFBool} colorPerVertex
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'colorPerVertex', true);

            /**
             * The normalPerVertex field determines whether normals are applied to each vertex or each quadrilateral of the ElevationGrid node depending on the value of normalPerVertex.
             * If normalPerVertex is FALSE and the normal node is not NULL, the normal field shall specify a node derived from X3DNormalNode containing at least (xDimension−1)×(zDimension−1) normals; one for each quadrilateral.
             * If normalPerVertex is TRUE and the normal field is not NULL, the normal field shall specify a node derived from X3DNormalNode containing at least xDimension × zDimension normals; one for each vertex.
             * @var {x3dom.fields.SFBool} normalPerVertex
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'normalPerVertex', true);

            /**
             * The creaseAngle field affects how default normals are generated.
             * If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced.
             * Crease angles shall be greater than or equal to 0.0 angle base units.
             * @var {x3dom.fields.SFFloat} creaseAngle
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'creaseAngle', 0);


            /**
             *
             * @var {x3dom.fields.MFNode} attrib
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue x3dom.nodeTypes.X3DVertexAttributeNode
             * @field x3d
             * @instance
             */
            this.addField_MFNode('attrib', x3dom.nodeTypes.X3DVertexAttributeNode);

            /**
             * The normal field specifies per-vertex or per-quadrilateral normals for the ElevationGrid node.
             * If the normal field is NULL, the browser shall automatically generate normals, using the creaseAngle field to determine if and how normals are smoothed across the surface.
             * @var {x3dom.fields.SFNode} normal
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue x3dom.nodeTypes.Normal
             * @field x3d
             * @instance
             */
            this.addField_SFNode('normal', x3dom.nodeTypes.Normal);

            /**
             * The color field specifies per-vertex or per-quadrilateral colours for the ElevationGrid node depending on the value of colorPerVertex.
             * If the color field is NULL, the ElevationGrid node is rendered with the overall attributes of the Shape node enclosing the ElevationGrid node.
             * @var {x3dom.fields.SFNode} color
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue x3dom.nodeTypes.X3DColorNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('color', x3dom.nodeTypes.X3DColorNode);

            /**
             * The texCoord field specifies per-vertex texture coordinates for the ElevationGrid node. If texCoord is NULL, default texture coordinates are applied to the geometry.
             * @var {x3dom.fields.SFNode} texCoord
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue x3dom.nodeTypes.X3DTextureCoordinateNode
             * @field x3d
             * @instance
             */
            this.addField_SFNode('texCoord', x3dom.nodeTypes.X3DTextureCoordinateNode);


            /**
             * The height field is an xDimension by zDimension array of scalar values representing the height above the grid for each vertex.
             * @var {x3dom.fields.MFFloat} height
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue []
             * @field x3d
             * @instance
             */
            this.addField_MFFloat(ctx, 'height', []);

            /**
             * Defines the grid size in x.
             * @var {x3dom.fields.SFInt32} xDimension
             * @range [0, inf]
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'xDimension', 0);

            /**
             * @var {x3dom.fields.SFDouble} xSpacing
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'xSpacing', 1.0);

            /**
             * Defines the grid size in z.
             * @var {x3dom.fields.SFInt32} zDimension
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFInt32(ctx, 'zDimension', 0);

            /**
             * @var {x3dom.fields.SFDouble} zSpacing
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.ElevationGrid
             * @initvalue 1.0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'zSpacing', 1.0);

        },
        {
            nodeChanged: function()
            {
                this._mesh._indices[0] = [];
                this._mesh._positions[0] = [];
                this._mesh._normals[0] = [];
                this._mesh._texCoords[0] = [];
                this._mesh._colors[0] = [];

                var x = 0, y = 0;
                var subx = this._vf.xDimension-1;
                var suby = this._vf.zDimension-1;

                var h = this._vf.height;

                x3dom.debug.assert((h.length >= this._vf.xDimension*this._vf.zDimension),
                    "Too few height values for given x/zDimension!");

                var normals = null, texCoords = null, colors = null;

                if (this._cf.normal.node) {
                    normals = this._cf.normal.node._vf.vector;
                }

                var numTexComponents = 2;
                var texMode;
                
                var texCoordNode = this._cf.texCoord.node;
                if (x3dom.isa(texCoordNode, x3dom.nodeTypes.MultiTextureCoordinate)) {
                    if (texCoordNode._cf.texCoord.nodes.length)
                        texCoordNode = texCoordNode._cf.texCoord.nodes[0];
                }

                if (texCoordNode) {
                    if (texCoordNode._vf.point) {
                        texCoords = texCoordNode._vf.point;
                        if (x3dom.isa(texCoordNode, x3dom.nodeTypes.TextureCoordinate3D)) {
                            numTexComponents = 3;
                        }
                    }
                    
                    else if (texCoordNode._vf.mode) {
                        texMode = texCoordNode._vf.mode;
                    }
                }

                var numColComponents = 3;
                if (this._cf.color.node) {
                    colors = this._cf.color.node._vf.color;
                    if (x3dom.isa(this._cf.color.node, x3dom.nodeTypes.ColorRGBA)) {
                        numColComponents = 4;
                    }
                }

                var c = 0;
                var faceCnt = 0;

                for (y = 0; y <= suby; y++)
                {
                    for (x = 0; x <= subx; x++)
                    {
                        this._mesh._positions[0].push(x * this._vf.xSpacing);
                        this._mesh._positions[0].push(h[c]);
                        this._mesh._positions[0].push(y * this._vf.zSpacing);

                        if (normals) {
                            if(this._vf.normalPerVertex) {
                                this._mesh._normals[0].push(normals[c].x);
                                this._mesh._normals[0].push(normals[c].y);
                                this._mesh._normals[0].push(normals[c].z);
                            }
                        }

                        if (texCoords) {
                            this._mesh._texCoords[0].push(texCoords[c].x);
                            this._mesh._texCoords[0].push(texCoords[c].y);
                            if (numTexComponents === 3) {
                                this._mesh._texCoords[0].push(texCoords[c].z);
                            }
                        }
                        
                        else {
                            this._mesh._texCoords[0].push(x / subx);
                            this._mesh._texCoords[0].push(y / suby);
                        }

                        if (colors) {
                            if(this._vf.colorPerVertex) {
                                this._mesh._colors[0].push(colors[c].r);
                                this._mesh._colors[0].push(colors[c].g);
                                this._mesh._colors[0].push(colors[c].b);
                                if (numColComponents === 4) {
                                    this._mesh._colors[0].push(colors[c].a);
                                }
                            }
                        }

                        c++;
                    }
                }
                
                for (y = 1; y <= suby; y++) {
                    for (x = 0; x < subx; x++) {
                        this._mesh._indices[0].push((y - 1) * (subx + 1) + x);
                        this._mesh._indices[0].push(y * (subx + 1) + x);
                        this._mesh._indices[0].push((y - 1) * (subx + 1) + x + 1);

                        this._mesh._indices[0].push(y * (subx + 1) + x);
                        this._mesh._indices[0].push(y * (subx + 1) + x + 1);
                        this._mesh._indices[0].push((y - 1) * (subx + 1) + x + 1);
                    }
                }

                // TODO; handle at least per quad normals
                //       (corresponds to creaseAngle = 0)
                //this._mesh.calcNormals(this._vf.creaseAngle, this._vf.ccw);
                if (!normals)
                    this._mesh.calcNormals(Math.PI, this._vf.ccw);

                if (texMode) {this._mesh.calcTexCoords(texMode);}
                
                this.invalidateVolume();
                this._mesh._numTexComponents = numTexComponents;
                this._mesh._numColComponents = numColComponents;
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function(fieldName)
            {
                var i, n;
                var normals = null;

                if (this._cf.normal.node) {
                    normals = this._cf.normal.node._vf.vector;
                }

                if (fieldName == "height")
                {
                    n = this._mesh._positions[0].length / 3;
                    var h = this._vf.height;

                    for (i=0; i<n; i++) {
                        this._mesh._positions[0][3*i+1] = h[i];
                    }

                    if (!normals) {
                        this._mesh._normals[0] = [];
                        this._mesh.calcNormals(Math.PI, this._vf.ccw);
                    }

                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        if (!normals)
                            node._dirty.normals = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "xSpacing" || fieldName == "zSpacing")
                {
                    for (var y = 0; y < this._vf.zDimension; y++) {
                        for (var x = 0; x < this._vf.xDimension; x++) {
                            var j = 3 * (y * this._vf.xDimension + x);
                            this._mesh._positions[0][j  ] = x * this._vf.xSpacing;
                            this._mesh._positions[0][j+2] = y * this._vf.zSpacing;
                        }
                    }

                    if (!normals) {
                        this._mesh._normals[0] = [];
                        this._mesh.calcNormals(Math.PI, this._vf.ccw);
                    }

                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.positions = true;
                        if (!normals)
                            node._dirty.normals = true;
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "xDimension" || fieldName == "zDimension")
                {
                    this.nodeChanged();     // re-init whole geo, changed too much

                    Array.forEach(this._parentNodes, function (node) {
                        node.setGeoDirty();
                        node.invalidateVolume();
                    });
                }
                else if (fieldName == "color")
                {
                    // TODO; FIXME: this code assumes that size has not change and color node exists.
                    n = this._mesh._colors[0].length / 3; // 3 stands for RGB. RGBA not supported yet.
                    var c = this._cf.color.node._vf.color;

                    for (i=0; i<n; i++) {
                        this._mesh._colors[0][i*3]   = c[i].r;
                        this._mesh._colors[0][i*3+1] = c[i].g;
                        this._mesh._colors[0][i*3+2] = c[i].b;
                    }

                    Array.forEach(this._parentNodes, function (node) {
                        node._dirty.colors = true;
                    });
                }
                // TODO: handle other cases!
            }
        }
    )
);

/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

/* ### Extrusion ### */
x3dom.registerNodeType(
    "Extrusion",
    "Geometry3DExt",
    defineClass(x3dom.nodeTypes.X3DGeometryNode,
        
        /**
         * Constructor for Extrusion
         * @constructs x3dom.nodeTypes.Extrusion
         * @x3d 3.3
         * @component Geometry3DExt
         * @status full
         * @extends x3dom.nodeTypes.X3DGeometryNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The Extrusion node specifies geometric shapes based on a two dimensional cross-section extruded along a three dimensional spine in the local coordinate system. The cross-section can be scaled and rotated at each spine point to produce a wide variety of shapes.
         */
        function (ctx) {
            x3dom.nodeTypes.Extrusion.superClass.call(this, ctx);


            /**
             * Specifies whether the beginCap should exist.
             * @var {x3dom.fields.SFBool} beginCap
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'beginCap', true);

            /**
             * Specifies whether the endCap should exist.
             * @var {x3dom.fields.SFBool} endCap
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'endCap', true);

            /**
             * The convex field indicates whether all polygons in the shape are convex.
             * @var {x3dom.fields.SFBool} convex
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue true
             * @field x3d
             * @instance
             */
            this.addField_SFBool(ctx, 'convex', true);

            /**
             * The creaseAngle field affects how default normals are generated.
             * If the angle between the geometric normals of two adjacent faces is less than the crease angle, normals shall be calculated so that the faces are shaded smoothly across the edge; otherwise, normals shall be calculated so that a lighting discontinuity across the edge is produced.
             * Crease angles shall be greater than or equal to 0.0 angle base units.
             * @var {x3dom.fields.SFFloat} creaseAngle
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue 0
             * @field x3d
             * @instance
             */
            this.addField_SFFloat(ctx, 'creaseAngle', 0);

            /**
             * Describes the cross section as a 2D piecewise linear curve (series of connected vertices).
             * @var {x3dom.fields.MFVec2f} crossSection
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue [(1,1), (1, -1), (-1, -1), (-1, 1), (1, 1)]
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'crossSection', [
                new x3dom.fields.SFVec2f(1, 1),
                new x3dom.fields.SFVec2f(1, -1),
                new x3dom.fields.SFVec2f(-1, -1),
                new x3dom.fields.SFVec2f(-1, 1),
                new x3dom.fields.SFVec2f(1, 1)
            ]);

            /**
             * Defines an array of orientations for each extrusion step.
             * @var {x3dom.fields.MFRotation} orientation
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue [(0,0,0,1)]
             * @field x3d
             * @instance
             */
            this.addField_MFRotation(ctx, 'orientation', [ new x3dom.fields.Quaternion(0, 0, 0, 1) ]);

            /**
             * Defines an array of 2D scale values for each extrusion step.
             * @var {x3dom.fields.MFVec2f} scale
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue [(1,1)]
             * @field x3d
             * @instance
             */
            this.addField_MFVec2f(ctx, 'scale', [ new x3dom.fields.SFVec2f(1, 1) ]);

            /**
             * Describes the spine as a 3D piecewise linear curve (series of conntected vertices).
             * @var {x3dom.fields.MFVec3f} spine
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue [(0,0,0)]
             * @field x3d
             * @instance
             */
            this.addField_MFVec3f(ctx, 'spine', [ new x3dom.fields.SFVec3f(0, 0, 0),
                new x3dom.fields.SFVec3f(0, 1, 0)
            ]);

            /**
             * Convenience field for setting default spine.
             * @var {x3dom.fields.SFFloat} height
             * @range [0, inf]
             * @memberof x3dom.nodeTypes.Extrusion
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'height', 0);

            // http://www.web3d.org/files/specifications/19775-1/V3.3/Part01/components/geometry3D.html#Extrusion
            // http://accad.osu.edu/~pgerstma/class/vnv/resources/info/AnnotatedVrmlRef/ch3-318.htm
            this.rebuildGeometry();
        
        },
        {
            rebuildGeometry: function()
            {
                this._mesh._positions[0] = [];
                this._mesh._normals[0]   = [];
                this._mesh._texCoords[0] = [];
                this._mesh._indices[0]   = [];

                var i, j, n, m, len, sx = 1, sy = 1;
                var spine = this._vf.spine,
                    scale = this._vf.scale,
                    orientation = this._vf.orientation,
                    crossSection = this._vf.crossSection;
                var positions = [], index = 0;

                m = spine.length;
                n = crossSection.length;

                if (/*m == 0 &&*/ this._vf.height > 0) {
                    spine[0] = new x3dom.fields.SFVec3f(0, 0, 0);
                    spine[1] = new x3dom.fields.SFVec3f(0, this._vf.height, 0);
                    m = 2;
                }

                var x, y, z;
                var last_z = new x3dom.fields.SFVec3f(0, 0, 1);

                if (m > 2) {
                    for (i = 1; i < m - 1; i++) {
                        var last_z_candidate = spine[i + 1].subtract(spine[i]).cross(spine[i - 1].subtract(spine[i]));
                        if (last_z_candidate.length() > x3dom.fields.Eps) {
                            last_z = x3dom.fields.SFVec3f.copy(last_z_candidate.normalize());
                            break;
                        }
                    }
                }

                var texCoordV = 0;
                var texCoordsV = [ 0 ];

                for (i=1; i<m; i++) {
                    var v = spine[i].subtract(spine[i-1]).length();
                    texCoordV = texCoordV + v;
                    texCoordsV[i] = texCoordV;
                }

                var texCoordU = 0;
                var texCoordsU = [ 0 ];

                var maxTexU_x = 0, minTexU_x = 0;
                var maxTexU_z = 0, minTexU_z = 0;

                for (j=1; j<n; j++) {
                    var u = crossSection[j].subtract(crossSection[j-1]).length();
                    texCoordU = texCoordU + u;
                    texCoordsU[j] = texCoordU;

                    if (j==1) {
                        maxTexU_x = minTexU_x = crossSection[j-1].x;
                        maxTexU_z = minTexU_z = crossSection[j-1].y;
                    }

                    if (maxTexU_x < crossSection[j].x) {
                        maxTexU_x = crossSection[j].x;
                    }
                    if (minTexU_x > crossSection[j].x) {
                        minTexU_x = crossSection[j].x;
                    }
                    if (maxTexU_z < crossSection[j].y) {
                        maxTexU_z = crossSection[j].y;
                    }
                    if (minTexU_z > crossSection[j].y) {
                        minTexU_z = crossSection[j].y;
                    }
                }

                if (Math.abs(maxTexU_x - minTexU_x) < Math.abs(maxTexU_z - minTexU_z)) {
                    var helpMax = maxTexU_x;
                    var helpMin = minTexU_x;
                    maxTexU_x = maxTexU_z;
                    minTexU_x = minTexU_z;
                    maxTexU_z = helpMax;
                    minTexU_z = helpMin;
                }

                var diffTexU_x = Math.abs(maxTexU_x - minTexU_x);
                var diffTexU_z = Math.abs(maxTexU_z - minTexU_z);

                var spineClosed = (m > 2) ? spine[0].equals(spine[spine.length-1], x3dom.fields.Eps) : false;

                for (i=0; i<m; i++) {
                    if ((len = scale.length) > 0) {
                        if (i < len) {
                            sx = scale[i].x;
                            sy = scale[i].y;
                        }
                        else {
                            sx = scale[len-1].x;
                            sy = scale[len-1].y;
                        }
                    }

                    for (j=0; j<n; j++) {
                        var pos = new x3dom.fields.SFVec3f(
                                crossSection[j].x * sx + spine[i].x,
                            spine[i].y,
                                crossSection[j].y * sy + spine[i].z);

                        if (m > 2) {
                            if (i == 0) {
                                if (spineClosed) {
                                    y = spine[1].subtract(spine[m-2]);
                                    z = spine[1].subtract(spine[0]).cross(spine[m-2].subtract(spine[0]));
                                }
                                else {
                                    y = spine[1].subtract(spine[0]);
                                    z = spine[2].subtract(spine[1]).cross(spine[0].subtract(spine[1]));
                                }
                                if (z.length() > x3dom.fields.Eps) {
                                    last_z = x3dom.fields.SFVec3f.copy(z);
                                }
                            }
                            else if (i == m-1) {
                                if (spineClosed) {
                                    y = spine[1].subtract(spine[m-2]);
                                    z = spine[1].subtract(spine[0]).cross(spine[m-2].subtract(spine[0]));
                                }
                                else {
                                    y = spine[m-1].subtract(spine[m-2]);
                                    z = x3dom.fields.SFVec3f.copy(last_z);
                                }
                            }
                            else {
                                y = spine[i+1].subtract(spine[i-1]);
                                z = y.cross(spine[i-1].subtract(spine[i]));
                            }
                            if (z.dot(last_z) < 0) {
                                z = z.negate();
                            }

                            y = y.normalize();
                            z = z.normalize();

                            if (z.length() <= x3dom.fields.Eps)	{
                                z = x3dom.fields.SFVec3f.copy(last_z);
                            }

                            if (i != 0) {
                                last_z = x3dom.fields.SFVec3f.copy(z);
                            }
                            x = y.cross(z).normalize();

                            var baseMat = x3dom.fields.SFMatrix4f.identity();
                            baseMat.setValue(x, y, z);
                            var rotMat = (i < orientation.length) ? orientation[i].toMatrix() :
                                ( (orientation.length > 0) ? orientation[orientation.length-1].toMatrix() :
                                    x3dom.fields.SFMatrix4f.identity() );

                            pos = pos.subtract(spine[i]);
                            pos = baseMat.multMatrixPnt(rotMat.multMatrixPnt(pos));
                            pos = pos.add(spine[i]);
                        }
                        pos.crossSection = crossSection[j];
                        positions.push(pos);

                        if (this._vf.creaseAngle <= x3dom.fields.Eps) {
                            if (i > 0 && j > 0) {
                                var iPos = (i-1)*n+(j-1);
                                this._mesh._positions[0].push(positions[iPos].x, positions[iPos].y, positions[iPos].z);
                                this._mesh._texCoords[0].push(texCoordsU[j-1]/texCoordU, texCoordsV[i-1]/texCoordV);
                                iPos = (i-1)*n+j;
                                this._mesh._positions[0].push(positions[iPos].x, positions[iPos].y, positions[iPos].z);
                                this._mesh._texCoords[0].push(texCoordsU[j]/texCoordU, texCoordsV[i-1]/texCoordV);
                                iPos = i*n+j;
                                this._mesh._positions[0].push(positions[iPos].x, positions[iPos].y, positions[iPos].z);
                                this._mesh._texCoords[0].push(texCoordsU[j]/texCoordU, texCoordsV[i]/texCoordV);

                                this._mesh._indices[0].push(index++, index++, index++);

                                this._mesh._positions[0].push(positions[iPos].x, positions[iPos].y, positions[iPos].z);
                                this._mesh._texCoords[0].push(texCoordsU[j]/texCoordU, texCoordsV[i]/texCoordV);
                                iPos = i*n+(j-1);
                                this._mesh._positions[0].push(positions[iPos].x, positions[iPos].y, positions[iPos].z);
                                this._mesh._texCoords[0].push(texCoordsU[j-1]/texCoordU, texCoordsV[i]/texCoordV);
                                iPos = (i-1)*n+(j-1);
                                this._mesh._positions[0].push(positions[iPos].x, positions[iPos].y, positions[iPos].z);
                                this._mesh._texCoords[0].push(texCoordsU[j-1]/texCoordU, texCoordsV[i-1]/texCoordV);

                                this._mesh._indices[0].push(index++, index++, index++);
                            }
                        }
                        else {
                            this._mesh._positions[0].push(pos.x, pos.y, pos.z);
                            this._mesh._texCoords[0].push(texCoordsU[j]/texCoordU, texCoordsV[i]/texCoordV);

                            if (i > 0 && j > 0) {
                                this._mesh._indices[0].push((i-1)*n+(j-1), (i-1)*n+ j   ,  i   *n+ j   );
                                this._mesh._indices[0].push( i   *n+ j   ,  i   *n+(j-1), (i-1)*n+(j-1));
                            }
                        }
                    }

                    if (i == m-1) {
                        var p0, l, startPos;
                        var linklist, linklist_indices;

                        // add bottom (1st cross-section)
                        if (this._vf.beginCap) {
                            linklist = new x3dom.DoublyLinkedList();
                            l = this._mesh._positions[0].length / 3;

                            for (j=0; j<n; j++) {
                                linklist.appendNode(new x3dom.DoublyLinkedList.ListNode(positions[j], j));

                                if (this._vf.creaseAngle > x3dom.fields.Eps) {
                                    p0 = positions[j];
                                    this._mesh._positions[0].push(p0.x, p0.y, p0.z);
                                    this._mesh._texCoords[0].push((p0.crossSection.x - minTexU_x)/diffTexU_x,
                                            (p0.crossSection.y - minTexU_z)/diffTexU_z);

                                }
                            }
                            if(this._vf.ccw == false)
                                linklist.invert();

                            linklist_indices = x3dom.EarClipping.getIndexes(linklist);

                            for (j=linklist_indices.length-1; j>=0; j--) {
                                if (this._vf.creaseAngle > x3dom.fields.Eps) {
                                    this._mesh._indices[0].push(l + linklist_indices[j]);
                                }
                                else {
                                    p0 = positions[linklist_indices[j]];
                                    this._mesh._positions[0].push(p0.x, p0.y, p0.z);
                                    this._mesh._texCoords[0].push((p0.crossSection.x - minTexU_x)/diffTexU_x,
                                            (p0.crossSection.y - minTexU_z)/diffTexU_z);
                                    this._mesh._indices[0].push(index++);
                                }
                            }
                        }

                        // add top (last cross-section)
                        if (this._vf.endCap) {
                            linklist = new x3dom.DoublyLinkedList();
                            startPos = (m - 1) * n;
                            l = this._mesh._positions[0].length / 3;

                            for (j=0; j<n; j++) {
                                linklist.appendNode(new x3dom.DoublyLinkedList.ListNode(positions[startPos+j], startPos+j));

                                if (this._vf.creaseAngle > x3dom.fields.Eps) {
                                    p0 = positions[startPos+j];
                                    this._mesh._positions[0].push(p0.x, p0.y, p0.z);
                                    this._mesh._texCoords[0].push((p0.crossSection.x - minTexU_x)/diffTexU_x,
                                            (p0.crossSection.y - minTexU_z)/diffTexU_z);
                                }
                            }

                            if(this._vf.ccw == false)
                                linklist.invert();

                            linklist_indices = x3dom.EarClipping.getIndexes(linklist);

                            for (j=0; j<linklist_indices.length; j++) {
                                if (this._vf.creaseAngle > x3dom.fields.Eps) {
                                    this._mesh._indices[0].push(l + (linklist_indices[j] - startPos));
                                }
                                else {
                                    p0 = positions[linklist_indices[j]];
                                    this._mesh._positions[0].push(p0.x, p0.y, p0.z);
                                    this._mesh._texCoords[0].push((p0.crossSection.x - minTexU_x)/diffTexU_x,
                                            (p0.crossSection.y - minTexU_z)/diffTexU_z);
                                    this._mesh._indices[0].push(index++);
                                }
                            }
                        }
                    }
                }

                this._mesh.calcNormals(this._vf.creaseAngle, this._vf.ccw);

                this.invalidateVolume();
                this._mesh._numFaces = this._mesh._indices[0].length / 3;
                this._mesh._numCoords = this._mesh._positions[0].length / 3;
            },

            fieldChanged: function(fieldName)
            {
                if (fieldName == "beginCap" || fieldName == "endCap" ||
                    fieldName == "crossSection" || fieldName == "orientation" ||
                    fieldName == "scale" || fieldName == "spine" ||
                    fieldName == "height" || fieldName == "creaseAngle")
                {
                    this.rebuildGeometry();

                    Array.forEach(this._parentNodes, function (node) {
                        node.setAllDirty();
                        node.invalidateVolume();
                    });
                }
            }
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### HAnimDisplacer ###
x3dom.registerNodeType(
    "HAnimDisplacer",
    "H-Anim",
    defineClass(x3dom.nodeTypes.X3DGeometricPropertyNode,
        
        /**
         * Constructor for HAnimDisplacer
         * @constructs x3dom.nodeTypes.HAnimDisplacer
         * @x3d 3.3
         * @component H-Anim
         * @status full
         * @extends x3dom.nodeTypes.X3DGeometricPropertyNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc In some cases, the application may need to be able to identify specific groups of vertices within an HAnimSegment.
         * It may also require "hints" as to the direction in which each vertex should move. That information is stored in a node called an HAnimDisplacer.
         * The HAnimDisplacers for a particular HAnimSegment are stored in the displacers field of that HAnimSegment.
         */
        function (ctx) {
            x3dom.nodeTypes.HAnimDisplacer.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.HAnimDisplacer
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx,'name', "");

            /**
             *
             * @var {x3dom.fields.SFFloat} weight
             * @memberof x3dom.nodeTypes.HAnimDisplacer
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'weight', 0);

            /**
             *
             * @var {x3dom.fields.MFInt32} coordIndex
             * @memberof x3dom.nodeTypes.HAnimDisplacer
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'coordIndex', []);

            /**
             *
             * @var {x3dom.fields.MFVec3f} displacements
             * @memberof x3dom.nodeTypes.HAnimDisplacer
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFVec3f(ctx, 'displacements', []);

            // TODO displacement (add functionality e.g. via matrix palette skinning in shader)
            x3dom.debug.logWarning("HAnimDisplacer NYI!");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### HAnimJoint ###
x3dom.registerNodeType(
    "HAnimJoint",
    "H-Anim",
    defineClass(x3dom.nodeTypes.Transform,
        
        /**
         * Constructor for HAnimJoint
         * @constructs x3dom.nodeTypes.HAnimJoint
         * @x3d 3.3
         * @component H-Anim
         * @status experimental
         * @extends x3dom.nodeTypes.Transform
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Each joint in the body is represented by an HAnimJoint node, which is used to define the relationship of each body segment to its immediate parent.
         * An HAnimJoint may only be a child of another HAnimJoint node or a child within the skeleton field in the case of the HAnimJoint used as a humanoid root (i.e., an HAnimJoint may not be a child of an HAnimSegment).
         * The HAnimJoint node is also used to store other joint-specific information. In particular, a joint name is provided so that applications can identify each HAnimJoint node at run-time.
         * The HAnimJoint node may contain hints for inverse-kinematics systems that wish to control the H-Anim figure.
         * These hints include the upper and lower joint limits, the orientation of the joint limits, and a stiffness/resistance value.
         */
        function (ctx) {
            x3dom.nodeTypes.HAnimJoint.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.HAnimJoint
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");

            /**
             *
             * @var {x3dom.fields.MFNode} displacers
             * @memberof x3dom.nodeTypes.HAnimJoint
             * @initvalue x3dom.nodeTypes.HAnimDisplacer
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('displacers', x3dom.nodeTypes.HAnimDisplacer);


            /**
             *
             * @var {x3dom.fields.SFRotation} limitOrientation
             * @memberof x3dom.nodeTypes.HAnimJoint
             * @initvalue 0,0,1,0
             * @field x3dom
             * @instance
             */
            this.addField_SFRotation(ctx, 'limitOrientation', 0, 0, 1, 0);

            /**
             *
             * @var {x3dom.fields.MFFloat} llimit
             * @memberof x3dom.nodeTypes.HAnimJoint
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'llimit', []);

            /**
             *
             * @var {x3dom.fields.MFFloat} ulimit
             * @memberof x3dom.nodeTypes.HAnimJoint
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'ulimit', []);

            /**
             *
             * @var {x3dom.fields.MFInt32} skinCoordIndex
             * @memberof x3dom.nodeTypes.HAnimJoint
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFInt32(ctx, 'skinCoordIndex', []);

            /**
             *
             * @var {x3dom.fields.MFFloat} skinCoordWeight
             * @memberof x3dom.nodeTypes.HAnimJoint
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'skinCoordWeight', []);
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### HAnimSegment ###
x3dom.registerNodeType(
    "HAnimSegment",
    "H-Anim",
    defineClass(x3dom.nodeTypes.X3DGroupingNode,
        
        /**
         * Constructor for HAnimSegment
         * @constructs x3dom.nodeTypes.HAnimSegment
         * @x3d 3.3
         * @component H-Anim
         * @status full
         * @extends x3dom.nodeTypes.X3DGroupingNode
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc Each body segment is stored in an HAnimSegment node.
         * The HAnimSegment node is a grouping node that will typically contain either a number of Shape nodes or perhaps Transform nodes that position the body part within its coordinate system.
         */
        function (ctx) {
            x3dom.nodeTypes.HAnimSegment.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.HAnimSegment
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx,'name', "");

            /**
             *
             * @var {x3dom.fields.SFVec3f} centerOfMass
             * @memberof x3dom.nodeTypes.HAnimSegment
             * @initvalue 0,0,0
             * @field x3dom
             * @instance
             */
            this.addField_SFVec3f(ctx, 'centerOfMass', 0, 0, 0);

            /**
             *
             * @var {x3dom.fields.SFFloat} mass
             * @memberof x3dom.nodeTypes.HAnimSegment
             * @initvalue 0
             * @field x3dom
             * @instance
             */
            this.addField_SFFloat(ctx, 'mass', 0);

            /**
             *
             * @var {x3dom.fields.MFFloat} momentsOfInertia
             * @memberof x3dom.nodeTypes.HAnimSegment
             * @initvalue [0,0,0,0,0,0,0,0,0]
             * @field x3dom
             * @instance
             */
            this.addField_MFFloat(ctx, 'momentsOfInertia', [0, 0, 0, 0, 0, 0, 0, 0, 0]);


            /**
             *
             * @var {x3dom.fields.SFNode} coord
             * @memberof x3dom.nodeTypes.HAnimSegment
             * @initvalue x3dom.nodeTypes.X3DCoordinateNode
             * @field x3dom
             * @instance
             */
            this.addField_SFNode('coord', x3dom.nodeTypes.X3DCoordinateNode);

            /**
             *
             * @var {x3dom.fields.MFNode} displacers
             * @memberof x3dom.nodeTypes.HAnimSegment
             * @initvalue x3dom.nodeTypes.HAnimDisplacer
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('displacers', x3dom.nodeTypes.HAnimDisplacer);
        
        },
        {
            // TODO coord      add functionality
            // TODO displacers add functionality
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### HAnimSite ###
x3dom.registerNodeType(
    "HAnimSite",
    "H-Anim",
    defineClass(x3dom.nodeTypes.Transform,
        
        /**
         * Constructor for HAnimSite
         * @constructs x3dom.nodeTypes.HAnimSite
         * @x3d 3.3
         * @component H-Anim
         * @status full
         * @extends x3dom.nodeTypes.Transform
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc An HAnimSite node serves three purposes. The first is to define an "end effecter" location that can be used by an inverse kinematics system.
         * The second is to define an attachment point for accessories such as jewelry and clothing.
         * The third is to define a location for a virtual camera in the reference frame of an HAnimSegment (such as a view "through the eyes" of the humanoid for use in multi-user worlds).
         */
        function (ctx) {
            x3dom.nodeTypes.HAnimSite.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.HAnimSite
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");
        
        }
    )
);
/** @namespace x3dom.nodeTypes */
/*
 * X3DOM JavaScript Library
 * http://www.x3dom.org
 *
 * (C)2009 Fraunhofer IGD, Darmstadt, Germany
 * Dual licensed under the MIT and GPL
 */

// ### HAnimHumanoid ###
x3dom.registerNodeType(
    "HAnimHumanoid",
    "H-Anim",
    defineClass(x3dom.nodeTypes.Transform,
        
        /**
         * Constructor for HAnimHumanoid
         * @constructs x3dom.nodeTypes.HAnimHumanoid
         * @x3d 3.3
         * @component H-Anim
         * @status full
         * @extends x3dom.nodeTypes.Transform
         * @param {Object} [ctx=null] - context object, containing initial settings like namespace
         * @classdesc The HAnimHumanoid node is used to store human-readable data such as author and copyright information, as well as to store references to the HAnimJoint, HAnimSegment, and HAnimSite nodes in addition to serving as a container for the entire humanoid.
         * Thus, it serves as a central node for moving the humanoid through its environment.
         */
        function (ctx) {
            x3dom.nodeTypes.HAnimHumanoid.superClass.call(this, ctx);


            /**
             *
             * @var {x3dom.fields.SFString} name
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'name', "");

            /**
             *
             * @var {x3dom.fields.SFString} version
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'version', "");

            /**
             *
             * @var {x3dom.fields.MFString} info
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue []
             * @field x3dom
             * @instance
             */
            this.addField_MFString(ctx, 'info', []);


            /**
             *
             * @var {x3dom.fields.MFNode} joints
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue x3dom.nodeTypes.HAnimJoint
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('joints', x3dom.nodeTypes.HAnimJoint);

            /**
             *
             * @var {x3dom.fields.MFNode} segments
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue x3dom.nodeTypes.HAnimSegment
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('segments', x3dom.nodeTypes.HAnimSegment);

            /**
             *
             * @var {x3dom.fields.MFNode} sites
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue x3dom.nodeTypes.HAnimSite
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('sites', x3dom.nodeTypes.HAnimSite);

            /**
             *
             * @var {x3dom.fields.MFNode} skeleton
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue x3dom.nodeTypes.HAnimJoint
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('skeleton', x3dom.nodeTypes.HAnimJoint);

            /**
             *
             * @var {x3dom.fields.MFNode} skin
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue x3dom.nodeTypes.X3DChildNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('skin', x3dom.nodeTypes.X3DChildNode);

            /**
             *
             * @var {x3dom.fields.MFNode} skinCoord
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue x3dom.nodeTypes.X3DCoordinateNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('skinCoord', x3dom.nodeTypes.X3DCoordinateNode);

            /**
             *
             * @var {x3dom.fields.MFNode} skinNormal
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue x3dom.nodeTypes.X3DNormalNode
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('skinNormal', x3dom.nodeTypes.X3DNormalNode);

            /**
             *
             * @var {x3dom.fields.MFNode} viewpoints
             * @memberof x3dom.nodeTypes.HAnimHumanoid
             * @initvalue x3dom.nodeTypes.HAnimSite
             * @field x3dom
             * @instance
             */
            this.addField_MFNode('viewpoints', x3dom.nodeTypes.HAnimSite);
        
        },
        {
            // TODO skeleton   contains the HumanoidRoot Joint object functionality: map similar to children of Group
            // TODO skeleton   add functionality for HAnimSite also (unaffected by internal transformations)
            // TODO joints     add functionality
            // TODO segments   add functionality
            // TODO sites      add functionality
            // TODO skin...    add functionality
            // TODO viewpoints add functionality
        }
    )
);

x3dom.versionInfo = {
    version:  '1.7.2',
    revision: '61a235203deb34329fe615cbbf21314db6ebf49f',
    date:     'Mon Dec 19 19:17:05 2016 +0100'
};


x3dom.versionInfo = {
    version:  '1.7.2',
    revision: '61a235203deb34329fe615cbbf21314db6ebf49f',
    date:     'Mon Dec 19 19:17:05 2016 +0100'
};

