/** X3DOM Runtime, http://www.x3dom.org/ 1.6.2 - 8f5655cec1951042e852ee9def292c9e0194186b - Sat Dec 20 00:03:52 2014 +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
var 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();
	};
};


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 Request(url, onloadCallback, priority);
        
        if (this.requests[priority]) {
          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();    
  }
}
	
};

/**
 * 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(x, y, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.colorMap.setPixel(x, y, 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(x, y, pixelFront);
                    parts.colorMap.setPixel(x, y, 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(x, y, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.emissiveMap.setPixel(x, y, 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(x, y, pixelFront);
                    parts.emissiveMap.setPixel(x, y, 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(x, y, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.r = color.r;
                    pixelBack.g = color.g;
                    pixelBack.b = color.b;

                    parts.specularMap.setPixel(x, y, 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(x, y, pixelFront);
                    parts.specularMap.setPixel(x, y, 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(x, y, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.a = 1.0 - transparency;

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

                    parts.colorMap.setPixel(x, y, pixelFront);
                    parts.colorMap.setPixel(x, y, 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(x, y, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.a = shininess;

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

                    parts.specularMap.setPixel(x, y, pixelFront);
                    parts.specularMap.setPixel(x, y, 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(x, y, pixelFront);
                }
                else if(side == "back")
                {
                    pixelBack.a = ambientIntensity;

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

                    parts.emissiveMap.setPixel(x, y, pixelFront);
                    parts.emissiveMap.setPixel(x, y, 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  = partID + this.widthTwo;

                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 = {
	
	reversePointDirection: function (linklist, plane) {
			var l, k;
			var count = 0;
			var z = 0;
			var nodei, nodel, nodek;
			
			if (linklist.length < 3) {
				return false;
			}
			
			for (var i = 0; i < linklist.length; i++) {
				l = (i + 1) % linklist.length;
				k = (i + 2) % linklist.length;
				
				nodei = linklist.getNode(i);
				nodel = linklist.getNode(l);
				nodek = linklist.getNode(k); 
							
				if(plane == 'YZ') {
					z  = (nodel.point.y - nodei.point.y) * (nodek.point.z - nodel.point.z);
					z -= (nodel.point.z - nodei.point.z) * (nodek.point.y - nodel.point.y);
				} else if(plane == 'XZ') {
					z  = (nodel.point.z - nodei.point.z) * (nodek.point.x - nodel.point.x);
					z -= (nodel.point.x - nodei.point.x) * (nodek.point.z - nodel.point.z);
				} else {
					z  = (nodel.point.x - nodei.point.x) * (nodek.point.y - nodel.point.y);
					z -= (nodel.point.y - nodei.point.y) * (nodek.point.x - nodel.point.x);
				}
				
				if (z < 0) {
					count--;
				} else {
					count++;
				}
			}
			
			if (count < 0) {
				linklist.invert();
				return true;
			}	
			return false;
	}, 

	getIndexes: function (linklist) {
		var node = linklist.first.next;
		var plane = this.identifyPlane(node.prev.point, node.point, node.next.point);
		
		var invers = this.reversePointDirection(linklist, plane);
		var indexes = [];
		node = linklist.first.next;
		var next = null;
		var count = 0;	
			
		var isEar = true;
		
		while(linklist.length >= 3 && count < 15) {
			next = node.next;
			for(var i = 0; i < linklist.length; i++) {
				if(this.isNotEar(linklist.getNode(i).point, node.prev.point, node.point, node.next.point, plane)) {
					isEar = false;
				}
			}
			
			if(isEar) {
				if(this.isKonvex(node.prev.point, node.point, node.next.point, plane)) {
					indexes.push(node.prev.point_index, node.point_index, node.next.point_index);
					linklist.deleteNode(node);
				} else {
					count++;
				}
			}

			node = next;
			isEar = true;
		}
		if(invers){
			return indexes.reverse();
		} else {
			return indexes;
		}
	},

	getMultiIndexes: function (linklist) {
		var node = linklist.first.next;
		var plane = this.identifyPlane(node.prev.point, node.point, node.next.point);
		var invers = this.reversePointDirection(linklist, plane);
		
		var data = {};
		data.indices = [];
		data.point = [];
		data.normals = [];
		data.colors = [];
		data.texCoords = [];
		node = linklist.first.next;
		var next = null;
		var count = 0;
			
		var isEar = true;
		while(linklist.length >= 3  && count < 15) {
			
			next = node.next;
			for(var i = 0; i < linklist.length; i++) {
				
			if(this.isNotEar(linklist.getNode(i).point, node.prev.point, node.point, node.next.point, plane)) {
					isEar = false;
				}
			}
			if(isEar) {
				
				if(this.isKonvex(node.prev.point, node.point, node.next.point, plane)) {				
					data.indices.push(node.prev.point_index, node.point_index, node.next.point_index);
					data.point.push(node.prev.point,
									node.point,
									node.next.point);
					if(node.normals) {					
						data.normals.push(node.prev.normals,
										  node.normals,
										  node.next.normals);
					
					}
					if(node.colors){
						data.colors.push(node.prev.colors,
										node.colors,
										node.next.colors);
					}
					if(node.texCoords){
						data.texCoords.push(node.prev.texCoords,
											node.texCoords,
											node.next.texCoords); 
					}
					linklist.deleteNode(node);
				}  else {
					count++;
				}
			}

			node = next;
			isEar = true;
		}
		
		if(invers){	
			data.indices = data.indices.reverse();
			data.point = data.point.reverse();
			data.normals = data.normals.reverse();
			data.colors = data.colors.reverse();
			data.texCoords = data.texCoords.reverse();
		}

		return data;
	}, 
	
	isNotEar: function (ap1, tp1, tp2, tp3, plane) {
		var b0, b1, b2, b3;
		var ap1a, ap1b, tp1a, tp1b, tp2a, tp2b, tp3a, tp3b;
		
		if(plane == 'YZ') {
			ap1a = ap1.y; ap1b = ap1.z;
			tp1a = tp1.y; tp1b = tp1.z;
			tp2a = tp2.y; tp2b = tp2.z;
			tp3a = tp3.y; tp3b = tp3.z;
		} else if(plane == 'XZ') {
			ap1a = ap1.z; ap1b = ap1.x;
			tp1a = tp1.z; tp1b = tp1.x;
			tp2a = tp2.z; tp2b = tp2.x;
			tp3a = tp3.z; tp3b = tp3.x;
		} else {
			ap1a = ap1.x; ap1b = ap1.y;
			tp1a = tp1.x; tp1b = tp1.y;
			tp2a = tp2.x; tp2b = tp2.y;
			tp3a = tp3.x; tp3b = tp3.y;
		}

        b0 = ((tp2a - tp1a) * (tp3b - tp1b) - (tp3a - tp1a) * (tp2b - tp1b));
        if (b0 != 0) {
            b1 = (((tp2a - ap1a) * (tp3b - ap1b) - (tp3a - ap1a) * (tp2b - ap1b)) / b0);
            b2 = (((tp3a - ap1a) * (tp1b - ap1b) - (tp1a - ap1a) * (tp3b - ap1b)) / b0);
            b3 = 1 - b1 - b2;

            return ((b1 > 0) && (b2 > 0) && (b3 > 0));
        }
        else {
            return false;
        }
    },

	isKonvex: function (p, p1, p2, plane) {
		var pa, pb, p1a, p1b, p2a, p2b;
		if(plane == 'YZ') {
			pa = p.y; pb = p.z;
			p1a = p1.y; p1b = p1.z;
			p2a = p2.y; p2b = p2.z;
		} else if(plane == 'XZ') {
			pa = p.z; pb = p.x;
			p1a = p1.z; p1b = p1.x;
			p2a = p2.z; p2b = p2.x;
		} else {
			pa = p.x; pb = p.y;
			p1a = p1.x; p1b = p1.y;
			p2a = p2.x; p2b = p2.y;
		}
		
		var l = ((p1a - pa) * (p2b - pb) - (p1b - pb) * (p2a - pa));
        return (l >= 0);
	},
	
	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
		}
	}
};

/*
 * 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() {
        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() {
		x3dom.debug.logError("[Utils|createTexture2D] Can't load Image: " + src);
		doc.downloadCount--;
	};
	
	return texture;
};

/*****************************************************************************
* 
*****************************************************************************/
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, needRenderBuf, 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 rb = null;

    if (needRenderBuf) {
        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);
        }
    }
    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, 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.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;
    }
};

/*****************************************************************************
* 
*****************************************************************************/
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.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 && geometry._vf.idsPerVertex) ? 1 : 0;
        property.IS_PARTICLE      = (x3dom.isa(geometry, x3dom.nodeTypes.ParticleSet)) ? 1 : 0;

        property.APPMAT           = (appearance && (material || property.CSSHADER) ) ? 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.LIGHTS           = (!property.POINTLINE2D && appearance && shape.isLit() && (material || property.CSSHADER)) ?
                                     viewarea.getLights().length + (viewarea._scene.getNavigationInfo()._vf.headlight) : 0;
        property.TEXTURED         = (texture || property.TEXT) ? 1 : 0;
        property.PIXELTEX         = (texture && x3dom.isa(texture, x3dom.nodeTypes.PixelTexture)) ? 1 : 0;
        property.TEXTRAFO         = (appearance && appearance._cf.textureTransform.node) ? 1 : 0;
        property.DIFFUSEMAP       = (property.CSSHADER && appearance._shader.getDiffuseMap()) ? 1 : 0;
        property.NORMALMAP        = (property.CSSHADER && appearance._shader.getNormalMap()) ? 1 : 0;
        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.CUBEMAP          = (texture && x3dom.isa(texture, x3dom.nodeTypes.X3DEnvironmentTextureNode)) ? 1 : 0;
        property.BLENDING         = (property.TEXT || property.CUBEMAP || (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.GAMMACORRECTION  = environment._vf.gammaCorrectionDefault;
	}
	
	property.toIdentifier = function() { 
		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.viewer.appendChild(title);
    this.viewer.appendChild(this.renderMode);
    this.viewer.appendChild(this.measureList);
    this.viewer.appendChild(this.infoList);

    /**
     * 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);
    };

    /**
     * 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) {
            infoItem = document.createElement('li');
            infoItem.className = 'x3dom-states-item';

            infoTitle = document.createElement('div');
            infoTitle.className = 'x3dom-states-item-title';
            infoTitle.appendChild(document.createTextNode(m));

            infoValue = document.createElement('div');
            infoValue.className = 'x3dom-states-item-value';
            infoValue.appendChild(document.createTextNode(this.toFixed(measurements[m])));

            infoItem.appendChild(infoTitle);
            infoItem.appendChild(infoValue);

            this.measureList.appendChild(infoItem);
        }

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

        //Create list items
        for (var i in infos) {
            var infoItem = document.createElement('li');
            infoItem.className = 'x3dom-states-item';

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

            var infoValue = document.createElement('div');
            infoValue.className = 'x3dom-states-item-value';
            infoValue.appendChild(document.createTextNode(this.thousandSeperator(infos[i])));

            infoItem.appendChild(infoTitle);
            infoItem.appendChild(infoValue);

            this.infoList.appendChild(infoItem);
        }
    };

    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)
    {
        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);

        xmlhttp0.onload = function()
        {
            if (!shape._webgl)
                return;

            var XHR_buffer = 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();
            shape._webgl.buffers[0] = indicesBuffer;

            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);

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

            shape._webgl.binaryGeometry = 1;    // indexed BG

            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;

            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;
            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");
        };
    }

    // 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);

        xmlhttp.onload = function()
        {
            if (!shape._webgl)
                return;

            var XHR_buffer = 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();

            shape._webgl.buffers[1] = buffer;

            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)
            {
                console.log("YUPPIE");

                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

            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;
            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");
        };
    }

    // 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);

        xmlhttp1.onload = function()
        {
            if (!shape._webgl)
                return;

            var XHR_buffer = 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();
            shape._webgl.buffers[1] = positionBuffer;
            gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

            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);

            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;

            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;
            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");
        };
    }

    // 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);

        xmlhttp2.onload = function()
        {
            if (!shape._webgl)
                return;

            var XHR_buffer = 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();
            shape._webgl.buffers[2] = normalBuffer;

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

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

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

            normals = null;

            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;
            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");
        };
    }

    // 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);

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

            if (!shape._webgl)
                return;

            var XHR_buffer = 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();

                shape._webgl.buffers[5] = idBuffer;

                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.vertexAttribPointer(sp.id,
                    1,
                    gl.FLOAT, false,
                    4, 0);
                gl.enableVertexAttribArray(sp.id);
            }
            else
            {
                var texcBuffer = gl.createBuffer();
                shape._webgl.buffers[3] = texcBuffer;

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

                gl.vertexAttribPointer(sp.texcoord,
                    binGeo._mesh._numTexComponents,
                    shape._webgl.texCoordType, false,
                    shape._texCoordStrideOffset[0], shape._texCoordStrideOffset[1]);
                gl.enableVertexAttribArray(sp.texcoord);
            }
            // Test reading Data
            //x3dom.debug.logWarning("arraybuffer[0].tx="+texCoords[0]);

            texCoords = null;

            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;
            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);

        xmlhttp4.onload = function()
        {
            if (!shape._webgl)
                return;

            var XHR_buffer = 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();
            shape._webgl.buffers[4] = colorBuffer;

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

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

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

            colors = null;

            shape._nameSpace.doc.downloadCount -= 1;
            shape._webgl.internalDownloadCount -= 1;
            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");
        };
    }
    // 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;
};

/**
 *  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 0;   // <0 outside, >0 inside, but can't tell in this case
    }

    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);
        if (planeMask <= 0) {
            return -1;      // if culled return -1; 0 should never happen
        }
    }
    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 0;   // differentiate between outside and this case
        }
    }

    // not culled, incr node cnt
    this.numberOfNodes++;
    
    return planeMask;   // >0, inside
};

/**
 * 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();
};

/*
 * 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;

    /**
     * Flag if flash is ready - needed for WebKit Browser
     * @member {Boolean} isFlashReady
     */
    this.isFlashReady = false;

    /**
     * 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.doc = null;

    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);

        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');
    if (this.backend)
        this.backend = this.backend.toLowerCase();
    else
        this.backend = 'none';

    if (this.backend == 'flash') {
        this.backend = 'flash';
        this.canvas = this._createFlashObject(x3dElem);
        if (this.canvas != null) {
            this.canvas.parent = this;
            this.gl = this._initFlashContext(this.canvas, this.flash_renderType);
        } else {
            this._createInitFailedDiv(x3dElem);
            return;
        }
    } else {
        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)
        {
            x3dom.debug.logInfo("Fallback to Flash Renderer");
            this.backend = 'flash';
            this.canvas = this._createFlashObject(x3dElem);
            if (this.canvas != null) {
                this.canvas.parent = this;
                this.gl = this._initFlashContext(this.canvas, this.flash_renderType);
            } else {
                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;
    }

    if (this.gl === null) {
        this.hasRuntime = false;
    }

    //States only needed for the webgl backend. flash has his own.
    if (this.backend != "flash") {
        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.toLowerCase() == "false") : true;
    //this.showTouchpoints = this.showTouchpoints ? (this.showTouchpoints.toLowerCase() == "true") : false;

    // disable touch events
    this.disableTouch = x3dElem.getAttribute("disableTouch");
    this.disableTouch = this.disableTouch ? (this.disableTouch.toLowerCase() == "true") : false;


    if (this.canvas !== null && this.gl !== null && this.hasRuntime && this.backend !== "flash") {
        // 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', 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;
            }
        }, false);

        this.canvas.addEventListener('mouseup', 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;
            }
        }, false);

        this.canvas.addEventListener('mouseover', 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;
            }
        }, false);

        this.canvas.addEventListener('mouseout', 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;
            }
        }, false);

        this.canvas.addEventListener('dblclick', 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;
            }
        }, false);

        this.canvas.addEventListener('mousemove', 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) {
                        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();
                }
            }
        }, false);

        this.canvas.addEventListener('DOMMouseScroll', 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();
            }
        }, false);

        this.canvas.addEventListener('mousewheel', 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();
            }
        }, false);


        // Key Events
        this.canvas.addEventListener('keypress', function (evt) {
            var keysEnabled = this.parent.x3dElem.getAttribute("keysEnabled");
            if (!keysEnabled || keysEnabled.toLowerCase() == "true") {
                this.parent.doc.onKeyPress(evt.charCode);
            }
            this.parent.doc.needRender = true;
        }, true);

        // in webkit special keys are only handled on key-up
        this.canvas.addEventListener('keyup', function (evt) {
            var keysEnabled = this.parent.x3dElem.getAttribute("keysEnabled");
            if (!keysEnabled || keysEnabled.toLowerCase() == "true") {
                this.parent.doc.onKeyUp(evt.keyCode);
            }
            this.parent.doc.needRender = true;
        }, true);

        this.canvas.addEventListener('keydown', function (evt) {
            var keysEnabled = this.parent.x3dElem.getAttribute("keysEnabled");
            if (!keysEnabled || keysEnabled.toLowerCase() == "true") {
                this.parent.doc.onKeyDown(evt.keyCode);
            }
            this.parent.doc.needRender = true;
        }, 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);
                    }
                }
            }
        };

        // Mozilla Touches (seems obsolete now...)
        var mozilla_ids = [];

        var mozilla_touches =
        {
            touches : [],
            preventDefault : function() {}
        };

        // === 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;
        };

        var touchStartHandlerMoz = function(evt)
        {
            this.isMulti = true;
            evt.preventDefault();

            var new_id = true;
            for(var i=0; i<mozilla_ids.length; ++i)
                if(mozilla_ids[i] == evt.streamId)
                    new_id = false;

            if(new_id == true) {
                evt.identifier = evt.streamId;
                mozilla_ids.push(evt.streamId);
                mozilla_touches.touches.push(evt);
            }
            touchStartHandler(mozilla_touches, this.parent.doc);
        };

        // === 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, 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, 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;
        };

        var touchMoveHandlerMoz = function(evt)
        {
            evt.preventDefault();

            for(var i=0; i<mozilla_ids.length; ++i)
                if(mozilla_ids[i] == evt.streamId)
                    mozilla_touches.touches[i] = evt;

            touchMoveHandler(mozilla_touches, this.parent.doc);
        };

        // === 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;
        };

        var touchEndHandlerMoz = function(evt)
        {
            this.isMulti = false;
            evt.preventDefault();

            var remove_index = -1;
            for(var i=0; i<mozilla_ids.length; ++i)
                if(mozilla_ids[i] == evt.streamId)
                    remove_index = i;

            if(remove_index != -1)
            {
                mozilla_ids.splice(remove_index, 1);
                mozilla_touches.touches.splice(remove_index, 1);
            }

            touchEndHandler(mozilla_touches, this.parent.doc);
        };

        if (!this.disableTouch)
        {
            // mozilla touch events (TODO: seem to be obsolete now, completely remove all code if no one complains!)
            // However, touch in general seems to be broken if this flag is not set: dom.w3c_touch_events.enabled;10
            //this.canvas.addEventListener('MozTouchDown',  touchStartHandlerMoz, true);
            //this.canvas.addEventListener('MozTouchMove',  touchMoveHandlerMoz,  true);
            //this.canvas.addEventListener('MozTouchUp',    touchEndHandlerMoz,   true);

            // 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, forceFlashForIE, 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) {
            console.log(forceFlashForIE);
            if (forceFlashForIE) {
                x3dom.debug.logError("No valid 3D context found...");
                this.x3dElem.removeChild(canvas);
                return null;
            } else {
                x3dom.debug.logError("WebGL version " + x3dom.caps.VERSION +
                    " lacks important WebGL/GLSL features needed for shadows, special vertex attribute types, etc.!");
            }
        }
    }

    return gl;
};

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

/**
 * Creates the WebGL context and returns it
 * @returns {WebGLContext} gl
 * @param {HTMLCanvas} canvas - the HTMLCanvas
 * @param {Object} renderType - the renderType for the Flash backend
 */
x3dom.X3DCanvas.prototype._initFlashContext = function(canvas, renderType) {
    x3dom.debug.logInfo("Initializing X3DObject for [" + canvas.id + "]");
    return x3dom.gfx_flash(canvas, renderType);
};

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

/**
 * 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 );
};

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

/**
 * Tests if a file exists
 * @returns {Boolean}
 * @param {String} url - the url to be tested
 */
x3dom.X3DCanvas.prototype._fileExists = function(url) {
    var xhr = new XMLHttpRequest();
    try {
        xhr.open("HEAD", url, false);
        xhr.send(null);
        return (xhr.status != 404);
    } catch(e) { return true; }
};

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

/**
 * Detects if flash is available
 * @returns {Boolean}
 * @param {String} required - required version
 * @param {String} max - maximal compatible version
 */
x3dom.X3DCanvas.prototype._detectFlash = function(required, max)
{
    var required_version = required;
    var max_version = max;
    var available_version = 0;

    /* this section is for NS, Mozilla, Firefox and similar Browsers */
    if(typeof(navigator.plugins["Shockwave Flash"]) == "object")
    {
        var description = navigator.plugins["Shockwave Flash"].description;
        available_version = description.substr(16, (description.indexOf(".", 16) - 16));
    }
    else if(typeof(ActiveXObject) == "function") {
        for(var i = 10; i < (max_version + 1); i ++) {
            try {
                if(typeof(new ActiveXObject("ShockwaveFlash.ShockwaveFlash." + i)) == "object") {
                    available_version = i+1;
                }
            }
            catch(error){}
        }
    }

    return [available_version, required_version];
};

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

/**
 * 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 flash object used as render target
 * @returns {Object} - the flash object
 * @param {HTMLNode} x3dElem - the X3D root node
 */
x3dom.X3DCanvas.prototype._createFlashObject = function (x3dElem) {

    var result = this._detectFlash(11, 11);

    if (!result[0] || result[0] < result[1]) {
        return null;
    } else {

        x3dom.debug.logInfo("Creating FlashObject for (X)3D element...");

        //Get X3D-Element ID
        var id = this.x3dElem.getAttribute("id");
        if (id !== null) {
            id = "x3dom-" + id + "-object";
        } else {
            var index = new Date().getTime();
            id = "x3dom-" + index + "-object";
        }

        //Get SWFPath
        var swf_path = this.x3dElem.getAttribute("swfpath");
        if (swf_path === null) {
            swf_path = "x3dom.swf";
        }

        if (!this._fileExists(swf_path)) {
            var version;

            //No version info or a dev string?
            if (x3dom.versionInfo === undefined || x3dom.versionInfo.version.indexOf('dev') != -1) //use dev version
            {
                version = "dev";
            }
            //Stable version?
            else
            {
                version = x3dom.versionInfo.version;

                //If version ends with ".0" (modification number), remove this part from path to download folder
                var modification = version.substr(version.length-1);
                if(modification == 0) {
                    version = version.substr(0, 3);
                }
            }

            swf_path = "http://www.x3dom.org/download/" + version + "/x3dom.swf";

            x3dom.debug.logWarning("Can't find local x3dom.swf (" + version + "). X3DOM now using the online version from x3dom.org." +
                "The online version needs a <a href='http://examples.x3dom.org/crossdomain.xml'>crossdomain.xml</a> " +
                "file in the root directory of your domain to access textures");
        }

        //Get width from x3d-Element or set default
        var width = this.x3dElem.getAttribute("width");
        var idx = -1;
        if (width == null) {
            width = 550;
        } else {
            idx = width.indexOf("px");
            if (idx != -1) {
                width = width.substr(0, idx);
            }
        }
        //Get height from x3d-Element or set default
        var height = this.x3dElem.getAttribute("height");
        if (height == null) {
            height = 400;
        } else {
            idx = height.indexOf("px");
            if (idx != -1) {
                height = height.substr(0, idx);
            }
        }

        //Get flash render type
        var renderType = this.x3dElem.getAttribute("flashrenderer");
        if (renderType == null) {
            this.flash_renderType = "forward";
        } else {
            this.flash_renderType = "deferred";
        }

        var obj = document.createElement('object');
        obj.setAttribute('width', '100%');
        obj.setAttribute('height', '100%');
        obj.setAttribute('id', id);

        //Check for xhtml
        if (!document.doctype || document.doctype && document.doctype.publicId && document.doctype.publicId.search(/DTD XHTML/i) != -1) {
            x3dom.debug.logWarning("Flash backend doesn't like XHTML, please use HTML5!");
            obj.setAttribute('style', 'width:' + width + 'px; height:' + height + 'px;');
        } else {
            if (x3dElem.getAttribute('style') == null) {
                x3dElem.setAttribute('style', 'width:' + width + 'px; height:' + height + 'px;');
            }
        }

        this.appendParam(obj, 'menu', 'false');
        this.appendParam(obj, 'quality', 'high');
        this.appendParam(obj, 'wmode', 'direct');
        this.appendParam(obj, 'allowScriptAccess', 'always');
        this.appendParam(obj, 'flashvars', 'canvasIdx=' + this._canvasIdx + '&renderType=' + this.flash_renderType);
        this.appendParam(obj, 'movie', swf_path);

        if (navigator.appName == "Microsoft Internet Explorer") {
            x3dElem.appendChild(obj);
            obj.setAttribute('classid', 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000');
        } else {
            obj.setAttribute('type', 'application/x-shockwave-flash');
            obj.setAttribute('data', swf_path);
            x3dElem.appendChild(obj);
        }

        return obj;
    }
};

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

/**
 * 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",

        // mozilla touch
        //"onMozTouchDown",
        //"onMozTouchMove",
        //"onMozTouchUp",

        // 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);
            }
        };
    }

    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 x = 0, y = 0;

    if ( "getBoundingClientRect" in document.documentElement ) {
        var elem = evt.target.offsetParent;    // should be x3dElem
        var box = elem.getBoundingClientRect();

        var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
        var scrollTop  = window.pageYOffset || document.documentElement.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'));

        x = Math.round(evt.pageX - (box.left + paddingLeft + borderLeftWidth + scrollLeft));
        y = Math.round(evt.pageY - (box.top + paddingTop + borderTopWidth + scrollTop));
    }
    else {
        x3dom.debug.logError('NO getBoundingClientRect');
    }

    return new x3dom.fields.SFVec2f(x, y);
};

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

/** Is called in the main loop after every frame
 */
x3dom.X3DCanvas.prototype.tick = function()
{
    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();

        if (this.backend == 'flash') {
            if (this.isFlashReady) {
                this.canvas.setFPS({fps: fps});

                this.doc.needRender = false;
                this.doc.render(this.gl);
            }
        }
        else {
            // 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();
    }

    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';
        }
    }
};

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

/** 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(){
                if (x3dCanvas.doc && x3dCanvas.x3dElem.runtime) {
                    x3dCanvas._watchForResize();
                    x3dCanvas.tick();
                    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
 *
 */
x3dom.Runtime.prototype.showAll = function(axis) {
    this.canvas.doc._viewarea.showAll(axis);
};

/**
 * 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
 */
x3dom.Runtime.prototype.showObject = function(obj)
{
    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 = new x3dom.fields.SFVec3f(0, 0, 1);    // facingDir
        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;
 };

/**
 * 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: 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;
};

/*
 * 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.detectActiveX = function() {
    var isInstalled = false;  
    
    if (window.ActiveXObject)  {  
        var control = null;  

        try  {  
            control = new ActiveXObject('AVALONATX.InstantPluginATXCtrl.1');  
        } catch (e) {
        }  
        
        if (control) {
            isInstalled = true;  
        }
    }
    
    return isInstalled;
};

x3dom.rerouteSetAttribute = function(node, browser) {
    // save old setAttribute method
    node._setAttribute = node.setAttribute;
    node.setAttribute = function(name, value) {
        var id = node.getAttribute("_x3domNode");
        var anode = browser.findNode(id);
        
        if (anode)
            return anode.parseField(name, value);
        else
            return 0;
    };

    for(var i=0; i < node.childNodes.length; i++) {
        var child = node.childNodes[i];
        x3dom.rerouteSetAttribute(child, browser);
    }
};

x3dom.insertActiveX = function(x3d) {
    
    if (typeof x3dom.atxCtrlCounter == 'undefined') {
        x3dom.atxCtrlCounter = 0;
    }
 
    var height = x3d.getAttribute("height");
    var width  = x3d.getAttribute("width");

    var parent = x3d.parentNode;
    
    var divelem = document.createElement("div");
    divelem.setAttribute("id", "x3dplaceholder");

    var inserted = parent.insertBefore(divelem, x3d);
    
    // hide x3d div
    var hiddenx3d = document.createElement("div");
    hiddenx3d.style.display = "none";
    parent.appendChild(hiddenx3d);
    parent.removeChild(x3d);
    hiddenx3d.appendChild(x3d);
     
    var atx = document.createElement("object");
    
    var containerName = "Avalon" + x3dom.atxCtrlCounter;
    x3dom.atxCtrlCounter++;
    
    atx.setAttribute("id", containerName);
    atx.setAttribute("classid", "CLSID:F3254BA0-99FF-4D14-BD81-EDA9873A471E");
    atx.setAttribute("width",   width   ? width     : "500");
    atx.setAttribute("height",  height  ? height    : "500");
    
    inserted.appendChild(atx);
    
    var atxctrl = document.getElementById(containerName);
    var browser = atxctrl.getBrowser();
    var scene   = browser.importDocument(x3d);
    browser.replaceWorld(scene);
        
    // add backtrack method to get browser from x3d node instead of the ctrl
    x3d.getBrowser = function() {
        return atxctrl.getBrowser();
    };
    
    x3dom.rerouteSetAttribute(x3d, browser);
};

// 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;
        });

        var w3sg = document.getElementsByTagName('webSG');	// THINKABOUTME: shall we still support exp. WebSG?!

        for (i=0; i<w3sg.length; i++) {
            w3sg[i].hasRuntime = false;
            x3ds.push(w3sg[i]);
        }

        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 - w3sg.length) + " X3D and " + 
                            w3sg.length + " (experimental) WebSG 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];

            // http://www.howtocreate.co.uk/wrongWithIE/?chapter=navigator.plugins
            if (x3dom.detectActiveX()) {
                x3dom.insertActiveX(x3d_element);
                continue;
            }

            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();
    };
	
    /* FIX PROBLEM IN CHROME - HACK - searching for better solution !!! */
	if (navigator.userAgent.indexOf("Chrome") != -1) {
		document.__getElementsByTagName = document.getElementsByTagName;
		
		document.getElementsByTagName = function(tag) {
			var obj = [];
			var elems = this.__getElementsByTagName("*");

			if(tag =="*"){
				obj = elems;
			} else {
				tag = tag.toUpperCase();
				for (var i = 0; i < elems.length; i++) {
					var tagName = elems[i].tagName.toUpperCase();		
					if (tagName === tag) {
						obj.push(elems[i]);
					}
				}
			}
			
            return obj;
        };

		document.__getElementById = document.getElementById;
        document.getElementById = function(id) {
            var obj = this.__getElementById(id);
            
            if (!obj) {
                var elems = this.__getElementsByTagName("*");
                for (var i=0; i<elems.length && !obj; i++) {
                    if (elems[i].getAttribute("id") === id) {
                        obj = elems[i];
                    }
                }
            }
            return obj;
        };
		
	} else { /* END OF HACK */
        document.__getElementById = document.getElementById;
        document.getElementById = function(id) {
            var obj = this.__getElementById(id);
            
            if (!obj) {
                var elems = this.getElementsByTagName("*");
                for (var i=0; i<elems.length && !obj; i++) {
                    if (elems[i].getAttribute("id") === id) {
                        obj = elems[i];
                    }
                }
            }
            return obj;
        };
	}
    
    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;
        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) {

    //Get shaderID
    var shaderID = properties.id;

    if(pickMode != undefined || pickMode != null) {
        shaderID += pickMode;
    }

    if (this.shaders[shaderID] === undefined)
    {
        var program;
        if (properties.CSHADER != -1) {
            program = new x3dom.shader.ComposedShader(gl, shape);
        } else if(pickMode != undefined || pickMode != null) {
            program = new x3dom.shader.DynamicShaderPicking(gl, properties, pickMode);
        } 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.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) {
		    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;
	
	var fontStyleNode = this.node._cf.fontStyle.node;

    var font_family = 'serif';
    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 = "";

    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;
		
		// TODO: make it possible to use multiple values
		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; // not clear what to do with this one
			case 'MIDDLE': 	font_justify = 'center'; 	break;
			default: 		font_justify = 'left'; 		break;
		}

		font_size 		= fontStyleNode._vf.size;
		font_spacing 	= fontStyleNode._vf.spacing;
		font_horizontal = fontStyleNode._vf.horizontal;
		font_language 	= fontStyleNode._vf.language;

        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 text_canvas = document.createElement('canvas');
	text_canvas.dir = leftToRight;
	var textHeight = font_size * 42; // 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');
	
	// calculate font font_size in px
	text_ctx.font = font_style + " " + textHeight + "px " + font_family;

	var maxWidth = text_ctx.measureText(paragraph[0]).width;
    var i;

	// calculate maxWidth
	for(i = 1; i < paragraph.length; i++) {
		if(text_ctx.measureText(paragraph[i]).width > maxWidth)
			maxWidth = text_ctx.measureText(paragraph[i]).width;
	}
	var canvas_scale = 1.1; //needed for some fonts that are higher than the textHeight
	text_canvas.width = maxWidth * canvas_scale;
	text_canvas.height = textHeight * paragraph.length * canvas_scale;

	switch(textAlignment) {
		case "left": 	textX = 0; 						break;
		case "center": 	textX = text_canvas.width/2; 	break;
		case "right": 	textX = text_canvas.width;		break;
	}

	var txtW =  text_canvas.width;
	var txtH = text_canvas.height;

	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.lineWidth = 2.5;
	text_ctx.strokeStyle = 'grey';
	text_ctx.textBaseline = 'top';

	text_ctx.font = font_style + " " + textHeight + "px " + font_family;
	text_ctx.textAlign = textAlignment;

	// create the multiline text
	for(i = 0; i < paragraph.length; i++) {
		textY = i*textHeight;          
		text_ctx.fillText(paragraph[i], textX,  textY);
	}
	
	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);
	
	var w = txtW / 100.0;
    	var h = txtH / 100.0;
	
	this.node._mesh._positions[0] = [-w,-h+.4,0, w,-h+.4,0, w,h+.4,0, -w,h+.4,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

    // 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'))
            {
                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, translation, rotation) {
    if (!ctx || !this._viewarea) {
        return;
    }

    this._viewarea.onMoveView(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 27: /* ESC */
            window.history.back(); // emulate good old ESC key
            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 */
            this._viewarea._points = ++this._viewarea._points % 3;
            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) {
    if (arguments.length === 0) {
        this._beginTime = 0;
        this._endTime = 1;
    }
    else {
        this._beginTime = beginTime;
        this._endTime = endTime;
    }

    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();
};

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.setBeginMatrix = function(mat) {
    this._beginMat.setValues(mat);
    this._beginInvMat = mat.inverse();
    this._beginLogMat = x3dom.fields.SFMatrix4f.zeroMatrix();  // mat.log();
};

x3dom.MatrixMixer.prototype.setEndMatrix = function(mat) {
    this._endMat.setValues(mat);
    this._endLogMat = mat.mult(this._beginInvMat).log();
    this._logDiffMat = this._endLogMat.addScaled(this._beginLogMat, -1);
};

x3dom.MatrixMixer.prototype.mix = function(time) {
    var mat = null;

    if (time <= this._beginTime)
    {
        mat = x3dom.fields.SFMatrix4f.copy(this._beginLogMat);
    }
    else
    {
        if (time >= this._endTime)
        {
            mat = x3dom.fields.SFMatrix4f.copy(this._endLogMat);
        }
        else
        {
            var fraction = this.calcFraction(time);
            mat = this._logDiffMat.multiply(fraction).add(this._beginLogMat);
        }
    }

    return mat.exp().mult(this._beginMat);
};

/*
 * 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.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._beginTime > 0)
    {
        needMixAnim = true;

        if (timeStamp >= this._mixer._beginTime)
        {
            if (timeStamp <= this._mixer._endTime)
            {
                var mat = this._mixer.mix(timeStamp);

                this._scene.getViewpoint().setView(mat);
            }
            else {
                this._mixer._beginTime = 0;
                this._mixer._endTime = 0;

                this._scene.getViewpoint().setView(this._mixer._endMat);
            }
        }
        else {
            this._mixer._beginTime = 0;
            this._mixer._endTime = 0;
            
            this._scene.getViewpoint().setView(this._mixer._beginMat);
        }
    }

    var needNavAnim = this.navigateTo(timeStamp);
    var lastIsAnimating = this._isAnimating;

    this._lastTS = timeStamp;
    this._isAnimating = (needMixAnim || 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();
    var navType = navi.getType();
    
    var needNavAnim = (this._currentInputType == x3dom.InputTypes.NAVIGATION) &&
                      ( navType === "game" ||
                        (this._lastButton > 0 &&
                        (navType.indexOf("fly") >= 0 ||
                         navType === "walk" ||
                         navType === "helicopter" ||
                         navType.substr(0, 5) === "looka")) );
    
    this._deltaT = timeStamp - this._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)
    {
        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 = this.getViewMatrix();
        var dist = 0;
        
        // estimate one screen size for motion puposes so navigation behaviour
        // is less dependent on screen geometry. This makes no sense for very
        // anisotropic cases, so it should probably be configurable.
        var screenSize = Math.min(this._width, this._height);
        var rdeltaX = removeZeroMargin((this._pressX - this._lastX) / screenSize, 0.01);
        var rdeltaY = removeZeroMargin((this._pressY - this._lastY) / screenSize, 0.01);
        
        var userXdiff = humanizeDiff(1, rdeltaX);
        var userYdiff = humanizeDiff(1, rdeltaY);

        // check if forwards or backwards (on right button)
        var step = (this._lastButton & 2) ? -1 : 1;
        step *= (this._deltaT * navi._vf.speed);
        
        // factor in delta time and the nav speed setting
        var userXstep = this._deltaT * navi._vf.speed * userXdiff;
        var userYstep = this._deltaT * navi._vf.speed * userYdiff;
        
        var phi = Math.PI * this._deltaT * userXdiff;
        var theta = Math.PI * this._deltaT * userYdiff;
        
        if (this._needNavigationMatrixUpdate === true)
        {
            this._needNavigationMatrixUpdate = false;
          
            // reset examine matrices to identity
            this._rotMat = x3dom.fields.SFMatrix4f.identity();
            this._transMat = x3dom.fields.SFMatrix4f.identity();
            this._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
            this._flyMat = currViewMat.inverse();
            
            this._from = this._flyMat.e3();
            this._at = this._from.subtract(this._flyMat.e2());

            if (navType === "helicopter")
                this._at.y = this._from.y;

            //lookat, lookaround
            if (navType.substr(0, 5) === "looka")
            {
                this._up = this._flyMat.e1();
            }
            //all other modes
            else
            {
                //initially read up-vector from current orientation and keep it
                if (typeof this._up == 'undefined')
                {
                    this._up = this._flyMat.e1();
                }
            }

            this._pitch = angleX * 180 / Math.PI;
            this._yaw = angleY * 180 / Math.PI;
            this._eyePos = this._from.negate();
        }

        var tmpAt = null, tmpUp = null, tmpMat = null;
        var q, temp, fin;
        var lv, sv, up;

        if (navType === "game")
        {
            this._pitch += this._dy;
            this._yaw   += this._dx;

            if (this._pitch >=  89) this._pitch = 89;
            if (this._pitch <= -89) this._pitch = -89;
            if (this._yaw >=  360) this._yaw -= 360;
            if (this._yaw < 0) this._yaw = 360 + this._yaw;
            
            this._dx = 0;
            this._dy = 0;

            var xMat = x3dom.fields.SFMatrix4f.rotationX(this._pitch / 180 * Math.PI);
            var yMat = x3dom.fields.SFMatrix4f.rotationY(this._yaw / 180 * Math.PI);

            var fPos = x3dom.fields.SFMatrix4f.translation(this._eyePos);

            this._flyMat = xMat.mult(yMat).mult(fPos);

            // Finally check floor for terrain following (TODO: optimize!)
            var flyMat = this._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();

            this._scene._nameSpace.doc.ctx.pickValue(this, this._width/2, this._height/2,
                        this._lastButton, tmpMat, this.getProjectionMatrix().mult(tmpMat));

            if (this._pickingInfo.pickObj)
            {
                dist = this._pickingInfo.pickPos.subtract(tmpFrom).length();
                //x3dom.debug.logWarning("Floor collision at dist=" + dist.toFixed(4));

                tmpFrom.y += (avatarHeight - dist);
                flyMat.setTranslate(tmpFrom);

                this._eyePos = flyMat.e3().negate();
                this._flyMat = flyMat.inverse();

                this._pickingInfo.pickObj = null;
            }

            this._scene.getViewpoint().setView(this._flyMat);

            return needNavAnim;
        }   // game
        else if (navType === "helicopter") {
            var typeParams = navi.getTypeParams();

            

            if (this._lastButton & 2) // up/down levelling
            {
                var stepUp = 200 * userYstep;
                typeParams[1] += stepUp;
                navi.setTypeParams(typeParams);
            }

            if (this._lastButton & 1) {  // forward/backward motion
                step = 300 * userYstep;
            }
            else {
                step = 0;
            }
            
            theta = typeParams[0];
            this._from.y = typeParams[1];
            this._at.y = this._from.y;

            // rotate around the up vector
            q = x3dom.fields.Quaternion.axisAngle(this._up, phi);
            temp = q.toMatrix();

            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);

            // rotate around the side vector
            lv = this._at.subtract(this._from).normalize();
            sv = lv.cross(this._up).normalize();
            up = sv.cross(lv).normalize();

            lv = lv.multiply(step);

            this._from = this._from.add(lv);
            this._at = this._at.add(lv);

            // rotate around the side vector
            q = x3dom.fields.Quaternion.axisAngle(sv, theta);
            temp = q.toMatrix();

            fin = x3dom.fields.SFMatrix4f.translation(this._from);
            fin = fin.mult(temp);

            temp = x3dom.fields.SFMatrix4f.translation(this._from.negate());
            fin = fin.mult(temp);

            var at = fin.multMatrixPnt(this._at);

            this._flyMat = x3dom.fields.SFMatrix4f.lookAt(this._from, at, up);

            this._scene.getViewpoint().setView(this._flyMat.inverse());

            return needNavAnim;
        }   // helicopter

        // rotate around the up vector
        q = x3dom.fields.Quaternion.axisAngle(this._up, phi);
        temp = q.toMatrix();

        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);

        // rotate around the side vector
        lv = this._at.subtract(this._from).normalize();
        sv = lv.cross(this._up).normalize();
        up = sv.cross(lv).normalize();
        //this._up = up;

        q = x3dom.fields.Quaternion.axisAngle(sv, theta);
        temp = q.toMatrix();

        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);

        // forward along view vector
        if (navType.substr(0, 5) !== "looka")
        {
            var currProjMat = this.getProjectionMatrix();

            if (navType !== "freefly") {
                if (step < 0) {
                    // backwards: negate viewing direction
                    tmpMat = new x3dom.fields.SFMatrix4f();
                    tmpMat.setValue(this._last_mat_view.e0(), this._last_mat_view.e1(),
                                    this._last_mat_view.e2().negate(), this._last_mat_view.e3());

                    this._scene._nameSpace.doc.ctx.pickValue(this, this._width/2, this._height/2,
                                this._lastButton, tmpMat, currProjMat.mult(tmpMat));
                }
                else {
                    this._scene._nameSpace.doc.ctx.pickValue(this, this._width/2, this._height/2, this._lastButton);
                }
                if (this._pickingInfo.pickObj)
                {
                    dist = this._pickingInfo.pickPos.subtract(this._from).length();

                    if (dist <= avatarRadius) {
                        step = 0;
                    }
                }
            }

            lv = this._at.subtract(this._from).normalize().multiply(step);

            this._at = this._at.add(lv);
            this._from = this._from.add(lv);

            // finally attach to ground when walking
            if (navType === "walk")
            {
                tmpAt = this._from.addScaled(up, -1.0);
                tmpUp = sv.cross(up.negate()).normalize();  // lv

                tmpMat = x3dom.fields.SFMatrix4f.lookAt(this._from, tmpAt, tmpUp);
                tmpMat = tmpMat.inverse();

                this._scene._nameSpace.doc.ctx.pickValue(this, this._width/2, this._height/2,
                            this._lastButton, tmpMat, currProjMat.mult(tmpMat));

                if (this._pickingInfo.pickObj)
                {
                    dist = this._pickingInfo.pickPos.subtract(this._from).length();

                    this._at = this._at.add(up.multiply(avatarHeight - dist));
                    this._from = this._from.add(up.multiply(avatarHeight - dist));
                }
            }
            this._pickingInfo.pickObj = null;
        }
        
        this._flyMat = x3dom.fields.SFMatrix4f.lookAt(this._from, this._at, up);

        this._scene.getViewpoint().setView(this._flyMat.inverse());
    }

    return needNavAnim;
};

x3dom.Viewarea.prototype.moveFwd = function()
{
    var navi = this._scene.getNavigationInfo();

    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 * this._deltaT * navi._vf.speed;
        var yRotRad = (this._yaw / 180 * Math.PI);
        var xRotRad = (this._pitch / 180 * Math.PI);

        var dist = 0;
        var fMat = this._flyMat.inverse();

        // check front for collisions
        this._scene._nameSpace.doc.ctx.pickValue(this, this._width/2, this._height/2, this._lastButton);

        if (this._pickingInfo.pickObj)
        {
            dist = this._pickingInfo.pickPos.subtract(fMat.e3()).length();

            if (dist <= 2 * avatarRadius) {
                //x3dom.debug.logWarning("Collision at dist=" + dist.toFixed(4));
            }
            else {
                this._eyePos.x -= Math.sin(yRotRad) * speed;
                this._eyePos.z += Math.cos(yRotRad) * speed;
                this._eyePos.y += Math.sin(xRotRad) * speed;
            }
        }
    }
};

x3dom.Viewarea.prototype.moveBwd = function()
{
    var navi = this._scene.getNavigationInfo();

    if (navi.getType() === "game")
    {
        var speed = 5 * this._deltaT * navi._vf.speed;
        var yRotRad = (this._yaw / 180 * Math.PI);
        var xRotRad = (this._pitch / 180 * Math.PI);

        this._eyePos.x += Math.sin(yRotRad) * speed;
        this._eyePos.z -= Math.cos(yRotRad) * speed;
        this._eyePos.y -= Math.sin(xRotRad) * speed;
    }
};

x3dom.Viewarea.prototype.strafeRight = function()
{
    var navi = this._scene.getNavigationInfo();

    if (navi.getType() === "game")
    {
        var speed = 5 * this._deltaT * navi._vf.speed;
        var yRotRad = (this._yaw / 180 * Math.PI);

        this._eyePos.x -= Math.cos(yRotRad) * speed;
        this._eyePos.z -= Math.sin(yRotRad) * speed;
    }
};

x3dom.Viewarea.prototype.strafeLeft = function()
{
    var navi = this._scene.getNavigationInfo();

    if (navi.getType() === "game")
    {
        var speed = 5 * this._deltaT * navi._vf.speed;
        var yRotRad = (this._yaw / 180 * Math.PI);

        this._eyePos.x += Math.cos(yRotRad) * speed;
        this._eyePos.z += Math.sin(yRotRad) * speed;
    }
};

x3dom.Viewarea.prototype.animateTo = function(target, prev, dur)
{
    var navi = this._scene.getNavigationInfo();

    if (x3dom.isa(target, x3dom.nodeTypes.X3DViewpointNode)) {
        target = target.getViewMatrix().mult(target.getCurrentTransform().inverse());
    }

    if (navi._vf.transitionType[0].toLowerCase() !== "teleport" && navi.getType() !== "game")
    {
        if (prev && x3dom.isa(prev, x3dom.nodeTypes.X3DViewpointNode)) {
            prev = prev.getViewMatrix().mult(prev.getCurrentTransform().inverse()).
                         mult(this._transMat).mult(this._rotMat);

            this._mixer._beginTime = this._lastTS;

            if (arguments.length >= 3) {
                // for lookAt to assure travel speed of 1 m/s
                this._mixer._endTime = this._lastTS + dur;
            }
            else {
                this._mixer._endTime = this._lastTS + navi._vf.transitionTime;
            }

            this._mixer.setBeginMatrix (prev);
            this._mixer.setEndMatrix (target);
            
            this._scene.getViewpoint().setView(prev);
        }
        else {
            this._scene.getViewpoint().setView(target);
        }
    }
    else
    {
        this._scene.getViewpoint().setView(target);
    }

    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.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)
{
    if (axis === undefined)
        axis = "negZ";

    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 dia = max.subtract(min);

    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));

    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()));

    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))
    {
        viewpoint._vf.fieldOfView[0] = -dist;
        viewpoint._vf.fieldOfView[1] = -dist;
        viewpoint._vf.fieldOfView[2] = dist;
        viewpoint._vf.fieldOfView[3] = dist;
        viewpoint._projMatrix = null;
        this.animateTo(viewmat, viewpoint, 0);
    }
    else
    {
        this.animateTo(viewmat, viewpoint);
    }
};

x3dom.Viewarea.prototype.resetView = function()
{
    var navi = this._scene.getNavigationInfo();

    if (navi._vf.transitionType[0].toLowerCase() !== "teleport" && navi.getType() !== "game")
    {
        this._mixer._beginTime = this._lastTS;
        this._mixer._endTime = this._lastTS + navi._vf.transitionTime;

        this._mixer.setBeginMatrix(this.getViewMatrix());

        var target = this._scene.getViewpoint();
        target.resetView();

        target = target.getViewMatrix().mult(target.getCurrentTransform().inverse());

        this._mixer.setEndMatrix(target);
    }
    else
    {
        this._scene.getViewpoint().resetView();
    }

    this.resetNavHelpers();
    navi._heliUpdated = false;
};

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.initTurnTable = function(navi, flyTo)
{
    flyTo = (flyTo == undefined) ? true : flyTo;

    var currViewMat = this.getViewMatrix();

    var viewpoint = this._scene.getViewpoint();
    var center = x3dom.fields.SFVec3f.copy(viewpoint.getCenterOfRotation());

    this._flyMat = currViewMat.inverse();

    this._from = this._flyMat.e3();
    //this._at = this._from.subtract(this._flyMat.e2());
    this._at = center;
    this._up = this._flyMat.e1();

    this._flyMat = x3dom.fields.SFMatrix4f.lookAt(this._from, this._at, this._up);
    this._flyMat = this.calcOrbit(0, 0, navi);

    var dur = 0.0;

    if (flyTo) {
        dur = 0.2 / navi._vf.speed;   // fly to pivot point
    }

    this.animateTo(this._flyMat.inverse(), viewpoint, dur);

    this.resetNavHelpers();
};

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();

        if (navi.getType() === "turntable") {
            this.initTurnTable(navi, false);
        }
    }
};

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();
    
    if (navi.getType() == "none") {
        return;
    }

    var pickMode = this._scene._vf.pickMode.toLowerCase();

    if ((pickMode == "color" || pickMode == "texcoord")) {
         return;
    }

    var viewpoint = this._scene.getViewpoint();

    viewpoint.setCenterOfRotation(this._pick);
    x3dom.debug.logInfo("New center of Rotation:  " + this._pick);

    var mat = this.getViewMatrix().inverse();

    var from = mat.e3();
    var at = this._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(this._pick.subtract(from));
    
    from = at.addScaled(norm, -dist);
    mat = x3dom.fields.SFMatrix4f.lookAt(from, at, up);
    
    x3dom.debug.logInfo("New camera position:  " + from);
    this.animateTo(mat.inverse(), viewpoint);
};

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)
    {
        var navi = this._scene.getNavigationInfo();

        var navType = navi.getType();
        var navRestrict = navi.getExplorationMode();

        if (navType === "none" || navRestrict == 0) {
            return;
        }

        var viewpoint = this._scene.getViewpoint();

        var dx = x - this._lastX;
        var dy = y - this._lastY;
        var d, vec, cor, mat = null;
        var alpha, beta;

        buttonState = (!navRestrict || (navRestrict != 7 && buttonState == 1)) ? navRestrict : buttonState;

        if (navType === "examine")
        {
            if (buttonState & 1) //left
            {
                alpha = (dy * 2 * Math.PI) / this._width;
                beta = (dx * 2 * Math.PI) / this._height;
                mat = this.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));

                this._rotMat = this._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 = (this._scene._lastMax.subtract(this._scene._lastMin)).length();
                d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed;

                vec = new x3dom.fields.SFVec3f(d*dx/this._width, d*(-dy)/this._height, 0);
                this._movement = this._movement.add(vec);

                mat = this.getViewpointMatrix().mult(this._transMat);
                //TODO; move real distance along viewing plane
                this._transMat = mat.inverse().
                                 mult(x3dom.fields.SFMatrix4f.translation(this._movement)).
                                 mult(mat);
            }
            if (buttonState & 2) //right
            {
                    d = (this._scene._lastMax.subtract(this._scene._lastMin)).length();
                    d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed;

                vec = new x3dom.fields.SFVec3f(0, 0, d*(dx+dy)/this._height);

                if (x3dom.isa(viewpoint, x3dom.nodeTypes.OrthoViewpoint))
                {
                    viewpoint._vf.fieldOfView[0] += vec.z;
                    viewpoint._vf.fieldOfView[1] += vec.z;
                    viewpoint._vf.fieldOfView[2] -= vec.z;
                    viewpoint._vf.fieldOfView[3] -= vec.z;
                    viewpoint._projMatrix = null;
                }
                else
                {
                    this._movement = this._movement.add(vec);
                    mat = this.getViewpointMatrix().mult(this._transMat);
                    //TODO; move real distance along viewing ray
                    this._transMat = mat.inverse().
                                     mult(x3dom.fields.SFMatrix4f.translation(this._movement)).
                                     mult(mat);
                }
            }

            this._isMoving = true;
        }
        else if (navType === "turntable")   // requires that y is up vector in world coords
        {
            if (!this._flyMat)
                this.initTurnTable(navi, false);

            if (buttonState & 1) //left
            {
                alpha = (dy * 2 * Math.PI) / this._height;
                beta = (dx * 2 * Math.PI) / this._width;

                this._flyMat = this.calcOrbit(alpha, beta, navi);
                viewpoint.setView(this._flyMat.inverse());
            }
            else if (buttonState & 2) //right
            {
                d = (this._scene._lastMax.subtract(this._scene._lastMin)).length();
                d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed;

                this._up   = this._flyMat.e1();
                this._from = this._flyMat.e3(); // eye

                // zoom in/out
                cor = viewpoint.getCenterOfRotation();

                var lastDir  = cor.subtract(this._from);
                var lastDirL = lastDir.length();
                lastDir = lastDir.normalize();
                
                var zoomAmount = d * (dx + dy) / this._height;
                
                // FIXME: very experimental HACK to switch between both versions (clamp to CoR and CoR translation)
                if (navi._vf.typeParams.length >= 5 && navi._vf.typeParams[4] > 0)
                {
                    // maintain minimum distance (value given in typeParams[4]) to prevent orientation flips
                    var newDist = Math.min(zoomAmount, lastDirL - navi._vf.typeParams[4]);

                    // move along viewing ray, scaled with zoom factor
                    this._from = this._from.addScaled(lastDir, newDist);
                }
                else
                {
                    // add z offset to look-at position, alternatively clamp
                    var diff = zoomAmount - lastDirL + 0.01;
                    if (diff >= 0) {
                        cor = cor.addScaled(lastDir, diff);
                        viewpoint.setCenterOfRotation(cor);
                    }

                    // move along viewing ray, scaled with zoom factor
                    this._from = this._from.addScaled(lastDir, zoomAmount);
                }

                // move along viewing ray, scaled with zoom factor
                this._from = this._from.addScaled(lastDir, zoomAmount);

                // update camera matrix with lookAt() and invert again
                this._flyMat = x3dom.fields.SFMatrix4f.lookAt(this._from, cor, this._up);
                viewpoint.setView(this._flyMat.inverse());
            }
            else if (buttonState & 4) //middle
            {
                d = (this._scene._lastMax.subtract(this._scene._lastMin)).length();
                d = ((d < x3dom.fields.Eps) ? 1 : d) * navi._vf.speed * 0.75;

                var tx = -d * dx / this._width;
                var ty =  d * dy / this._height;

                this._up   = this._flyMat.e1();
                this._from = this._flyMat.e3(); // eye
                var s = this._flyMat.e0();

                // add xy offset to camera position for pan
                this._from = this._from.addScaled(this._up, ty);
                this._from = this._from.addScaled(s, tx);

                // add xy offset to look-at position
                cor = viewpoint.getCenterOfRotation();
                cor = cor.addScaled(this._up, ty);
                cor = cor.addScaled(s, tx);
                viewpoint.setCenterOfRotation(cor);

                // update camera matrix with lookAt() and invert
                this._flyMat = x3dom.fields.SFMatrix4f.lookAt(this._from, cor, this._up);
                viewpoint.setView(this._flyMat.inverse());
            }

            this._isMoving = true;
        }
    }

    this._dx = dx;
    this._dy = dy;

    this._lastX = x;
    this._lastY = y;
};

x3dom.Viewarea.prototype.calcOrbit = function (alpha, beta, navi)
{
    this._up   = this._flyMat.e1();
    this._from = this._flyMat.e3();

    var offset = this._from.subtract(this._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
    var typeParams = navi.getTypeParams();
    theta = Math.max(typeParams[2], Math.min(typeParams[3], theta));

    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 = this._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, this._at, up);
};

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.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(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;
    destination.setPixels(that.array);
    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([]);

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(/\\([\\"])/, "$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)
{
    return new x3dom.fields.BoxVolume(other.min, other.max);
};

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);
};

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. */
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.SHADOW = "shadow";
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 = map * 255./127. - 128./127.;\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) {
		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 || properties.CSSHADER) {
		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 && !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.MULTIDIFFALPMAP || properties.MULTIVISMAP) {
            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 || properties.CSSHADER) {
			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 {
				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.CSSHADER) && !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
        {
            shader += "fragNormal = (normalMatrix * vec4(vertNormal, 0.0)).xyz;\n";
        }
	}
    
	//Textures
	if(properties.TEXTURED || properties.CSSHADER){
		if(properties.CUBEMAP) {
			shader += "fragViewDir = (viewMatrix[3].xyz);\n";
		} else 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  && !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";
    }
	
	//Textures
	if(properties.TEXTURED || properties.CSSHADER) {
		shader += "varying vec2 fragTexcoord;\n";
		if((properties.TEXTURED || properties.DIFFUSEMAP) && !properties.CUBEMAP) {
			shader += "uniform sampler2D diffuseMap;\n";
		} else if(properties.CUBEMAP) {
			shader += "uniform samplerCube cubeMap;\n";
			shader += "varying vec3 fragViewDir;\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.MULTIDIFFALPMAP || properties.MULTIVISMAP) {
            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";
        }
        if(properties.NORMALMAP){
            shader += "uniform sampler2D normalMap;\n";

            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";
            }
        }
	}
	
	//Fog
	if(properties.FOG) {
		shader += x3dom.shader.fog();
	}

    if(properties.LIGHTS || properties.CLIPPLANES)
    {
        shader += "varying vec4 fragPosition;\n";
    }

	//Lights
	if(properties.LIGHTS) {
		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";

    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 += "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 normal 	  = normalize(fragNormal);\n";
		shader += "vec3 eye 	  = -fragPosition.xyz;\n";


		
		//Normalmap
		if(properties.NORMALMAP){
			shader += "vec3 n = normalize( fragNormal );\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";
            }
		}

        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 += "    _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){        
			if(properties.CUBEMAP) {
				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 = " + x3dom.shader.decodeGamma(properties, "textureCube(cubeMap, reflected)") + ";\n";
				shader += "color.a *= texColor.a;\n";
			}
            else if (properties.DIFFPLACEMENTMAP)
            {
                shader += "vec2 texCoord = vec2(fragTexcoord.x, 1.0-fragTexcoord.y);\n";
                shader += "vec4 texColor = texture2D(diffuseDisplacementMap, texCoord);\n";
            }
            else
            {
                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.CUBEMAP) {
					shader += "color.rgb = mix(color.rgb, texColor.rgb, vec3(0.75));\n";
				} else {
					shader += "color.rgb *= texColor.rgb;\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) {
			shader += "color = vec4(0.0, 0.0, 0.0, 1.0 - transparency);\n";
		}
		
		if(properties.TEXTURED || properties.DIFFUSEMAP){
            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 && !properties.MULTIDIFFALPMAP){
			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 <= 0.1) 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 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" +
					"\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("[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 ShadowShader program
 */
x3dom.shader.ShadowShader = 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.ShadowShader.prototype.generateVertexShader = function(gl)
{
	var shader = "";
	if (!x3dom.caps.MOBILE) {
	
		shader +=   "attribute vec3 position;\n" +
					"uniform mat4 modelViewProjectionMatrix;\n" +
					"varying vec4 projCoords;\n" +
					//image geometry 
					"uniform float imageGeometry;\n" +
					"uniform vec3 IG_bboxMin;\n" +
					"uniform vec3 IG_bboxMax;\n" +
					"uniform float IG_coordTextureWidth;\n" +
					"uniform float IG_coordTextureHeight;\n" +
					"uniform float IG_indexTextureWidth;\n" +
					"uniform float IG_indexTextureHeight;\n" +
					"uniform sampler2D IG_indexTexture;\n" +
					"uniform sampler2D IG_coordinateTexture;\n" +
					"uniform vec2 IG_implicitMeshSize;\n" +
                    //pop geometry
                    "uniform vec3 bgCenter;\n" +
                    "uniform vec3 bgSize;\n" +
                    "uniform float bgPrecisionMax;\n" +
                    "uniform float popGeometry;\n" +
                    "uniform float PG_precisionLevel;\n" +
                    "uniform float PG_powPrecision;\n" +
                    "uniform vec3 PG_maxBBSize;\n" +
                    "uniform vec3 PG_bbMin;\n" +
                    "uniform vec3 PG_bbMaxModF;\n" +
                    "uniform vec3 PG_bboxShiftVec;\n" +

					//MAIN
					"void main(void) {\n" +
					"	vec3 pos;\n" +
					"	if (imageGeometry != 0.0) {\n" +
						//IG
					"		vec2 IG_texCoord;\n" +
					"		if(imageGeometry == 1.0) {\n" +
					"			vec2 halfPixel = vec2(0.5/IG_indexTextureWidth,0.5/IG_indexTextureHeight);\n" +
					"			IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_indexTextureWidth), position.y*(IG_implicitMeshSize.y/IG_indexTextureHeight)) + halfPixel;\n" +
					"			vec2 IG_index = texture2D( IG_indexTexture, IG_texCoord ).rg;\n" + 
					"			IG_texCoord = IG_index * 0.996108948;\n" +
					"		} else {\n" +
					"			vec2 halfPixel = vec2(0.5/IG_coordTextureWidth, 0.5/IG_coordTextureHeight);\n" +
					"			IG_texCoord = vec2(position.x*(IG_implicitMeshSize.x/IG_coordTextureWidth), position.y*(IG_implicitMeshSize.y/IG_coordTextureHeight)) + halfPixel;\n" +
					"		}\n" +
					"		pos = texture2D( IG_coordinateTexture, IG_texCoord ).rgb;\n" +
					"	 	pos = pos * (IG_bboxMax - IG_bboxMin) + IG_bboxMin;\n" +
					"	} else if (popGeometry != 0.0){\n" +
						//PG
                    "		pos = position;\n" +
                    "		vec3 offsetVec = step(pos / bgPrecisionMax, PG_bbMaxModF) * PG_bboxShiftVec;\n" +
                    "		if (PG_precisionLevel <= 2.0) {\n" +
                    "   		pos = floor(pos / PG_powPrecision) * PG_powPrecision;\n" +
                    "   		pos /= (65536.0 - PG_powPrecision);\n" +
                    "		}\n" +
                    "		else {\n" +
                    "   		pos /= bgPrecisionMax;\n" +
                    "		}\n" +
                    "		pos = (pos + offsetVec + PG_bbMin) * PG_maxBBSize;\n" +
					"	} else {\n" +
						//BG
					"		pos = bgCenter + bgSize * position / bgPrecisionMax;\n" +
					"	}\n" +
					"   projCoords = modelViewProjectionMatrix * vec4(pos, 1.0);\n" +
					"   gl_Position = projCoords;\n" +
					"}\n";
	} else {
		shader = 	"attribute vec3 position;\n" +
                    "uniform vec3 bgCenter;\n" +
                    "uniform vec3 bgSize;\n" +
                    "uniform float bgPrecisionMax;\n" +
                    "uniform mat4 modelViewProjectionMatrix;\n" +
					"varying vec4 projCoords;\n" +
                    
                    "void main(void) {\n" +
                    "	vec3 pos = bgCenter + bgSize * position / bgPrecisionMax;\n" +
					"	projCoords = modelViewProjectionMatrix * vec4(pos, 1.0);\n" +					
                    "	gl_Position = projCoords;\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("[ShadowShader] VertexShader " + gl.getShaderInfoLog(vertexShader));		
	}
	
	return vertexShader;
};

/**
 * Generate the fragment shader
 */
x3dom.shader.ShadowShader.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 vec4 projCoords;\n" +
				"uniform float offset;\n" +
				"uniform bool cameraView;\n";
	if(!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE) 
		shader += 	x3dom.shader.rgbaPacking();
	
	shader +=	"void main(void) {\n" +
				"    vec3 proj = (projCoords.xyz / projCoords.w);\n";

 	if(!x3dom.caps.FP_TEXTURES || x3dom.caps.MOBILE) {
		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" +
					"		proj.z = (proj.z + 1.0)*0.5;\n" +
					"		proj.y = proj.z * proj.z;\n" +
					"	}\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.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;
};

/**
 * 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.DEBUGRENDERINFO = ctx.getExtension("WEBGL_debug_renderer_info");
                        x3dom.caps.EXTENSIONS = ctx.getSupportedExtensions();

                        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)) &&
                  (!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.updateRenderData(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, true, 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);
                }

                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 sp = scene._webgl.shadowShader;

        this.stateManager.bindFramebuffer(gl.FRAMEBUFFER, targetFbo.fbo);
        this.stateManager.viewport(0, 0, targetFbo.width, targetFbo.height);

        this.stateManager.useProgram(sp);

        sp.cameraView = isCameraView;
        sp.offset = camOffset;

        // workaround for old graphics cards/ drivers
        {
            sp.PG_precisionLevel = 1.0;
            sp.PG_powPrecision = 1.0;
            sp.PG_maxBBSize = [0, 0, 0];
            sp.PG_bbMin = [0, 0, 0];
            sp.PG_bbMaxModF = [0, 0, 0];
            sp.PG_bboxShiftVec = [0, 0, 0];
        }

        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_msh = s_geo._mesh;

            sp.modelViewProjectionMatrix = mat_scene.mult(trafo).toGL();

            //Set ImageGeometry switch
            sp.imageGeometry = s_gl.imageGeometry;
            sp.popGeometry = s_gl.popGeometry;

            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 = bgCenter;
                sp.bgSize = bgSize;
                sp.bgPrecisionMax = 1;
            }

            if (s_gl.colorType != gl.FLOAT) {
                sp.bgPrecisionColMax = s_geo.getPrecisionMax('colorType');
            }

            if (s_gl.texCoordType != gl.FLOAT) {
                sp.bgPrecisionTexMax = s_geo.getPrecisionMax('texCoordType');
            }

            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++;
                    }
                }
            }

            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);
            }

            //PopGeometry: adapt LOD and set shader variables
            if (s_gl.popGeometry) {
                var model_view = mat_view.mult(trafo);
                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 = s_gl.primType.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 ( !(sp.position !== undefined && s_gl.buffers[q6 + 1] && (s_gl.indexes[q] || s_gl.externalGeometry != 0)) )
                    continue;

                // set buffers
                if (s_gl.buffers[q6]) {
                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[q6]);
                }

                gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 1]);

                gl.vertexAttribPointer(sp.position,
                    s_msh._numPosComponents, s_gl.coordType, false,
                    shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
                gl.enableVertexAttribArray(sp.position);

                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];
                    }
                }
                //ExternalGeometry: indexed rendering (shadow pass)
                else if (s_gl.externalGeometry == 1)
                {
                    gl.drawElements(s_gl.primType[q], s_gl.drawCount[q], s_gl.indexType, s_gl.indexOffset[q]);
                }
                //ExternalGeometry: non-indexed rendering (shadow pass)
                else if (s_gl.externalGeometry == -1)
                {
                    gl.drawArrays(s_gl.primType[q], 0, s_gl.drawCount[q]);
                }
                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);
            }

            //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);
                }
            }
        }

        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;

        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_geo._vf.idsPerVertex) {
                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_geo._vf.idsPerVertex) { //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 = s_gl.primType.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 ( !(sp.position !== undefined && s_gl.buffers[q6 + 1] && (s_gl.indexes[q] || s_gl.externalGeometry != 0)) )
                    continue;

                // set buffers
                if (s_gl.buffers[q6]) {
                    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, s_gl.buffers[q6]);
                }

                gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 1]);

                gl.vertexAttribPointer(sp.position,
                    s_msh._numPosComponents, s_gl.coordType, false,
                    shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
                gl.enableVertexAttribArray(sp.position);

                if (pickMode == 1 && sp.color !== undefined && s_gl.buffers[q6 + 4]) {
                    gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 4]);

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

                if (pickMode == 2 && sp.texcoord !== undefined && s_gl.buffers[q6 + 3]) {
                    gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 3]);

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

                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_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 (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];
                    }
                }
                //ExternalGeometry: indexed rendering (picking pass)
                else if (s_gl.externalGeometry == 1)
                {
                    gl.drawElements(s_gl.primType[q], s_gl.drawCount[q], s_gl.indexType, s_gl.indexOffset[q]);
                }
                //ExternalGeometry: non-indexed rendering (picking pass)
                else if (s_gl.externalGeometry == -1)
                {
                    gl.drawArrays(s_gl.primType[q], 0, s_gl.drawCount[q]);
                }
                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)
    {
        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;

            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;
            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) {
                            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.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)
        if (isUserDefinedShader) {
            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 = s_gl.primType.length;
        }
        else
        {
            q_n = s_gl.positions.length;
        }

        for (var q = 0; q < q_n; q++) {
            var q6 = 6 * q;

            if ( !(sp.position !== undefined && s_gl.buffers[q6 + 1] && (s_gl.indexes[q] || s_gl.externalGeometry != 0)) )
                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]);
            }

            gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 1]);

            gl.vertexAttribPointer(sp.position,
                s_msh._numPosComponents, s_gl.coordType, false,
                shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
            gl.enableVertexAttribArray(sp.position);

            if (sp.normal !== undefined && s_gl.buffers[q6 + 2]) {
                gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 2]);

                gl.vertexAttribPointer(sp.normal,
                    s_msh._numNormComponents, s_gl.normalType, false,
                    shape._normalStrideOffset[0], shape._normalStrideOffset[1]);
                gl.enableVertexAttribArray(sp.normal);
            }
            if (sp.texcoord !== undefined && s_gl.buffers[q6 + 3]) {
                gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 3]);

                gl.vertexAttribPointer(sp.texcoord,
                    s_msh._numTexComponents, s_gl.texCoordType, false,
                    shape._texCoordStrideOffset[0], shape._texCoordStrideOffset[1]);
                gl.enableVertexAttribArray(sp.texcoord);
            }
            if (sp.color !== undefined && s_gl.buffers[q6 + 4]) {
                gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 4]);

                gl.vertexAttribPointer(sp.color,
                    s_msh._numColComponents, s_gl.colorType, false,
                    shape._colorStrideOffset[0], shape._colorStrideOffset[1]);
                gl.enableVertexAttribArray(sp.color);
            }
            if ((sp.id !== undefined || sp.particleSize !== 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_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 (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];
                    }
                }
                //ExternalGeometry: indexed rendering (standard pass, POINTS or LINES)
                else if (s_gl.externalGeometry == 1)
                {
                    gl.drawElements(polyMode, s_gl.drawCount[q], s_gl.indexType, s_gl.indexOffset[q]);
                }
                //ExternalGeometry: non-indexed rendering (standard pass, POINTS or LINES)
                else if (s_gl.externalGeometry == -1)
                {
                    gl.drawArrays(polyMode, 0, s_gl.drawCount[q]);
                }
                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 (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];
                    }
                }
                //ExternalGeometry: indexed rendering (standard pass)
                else if (s_gl.externalGeometry == 1)
                {
                    gl.drawElements(s_gl.primType[q], s_gl.drawCount[q], s_gl.indexType, s_gl.indexOffset[q]);
                }
                //ExternalGeometry: non-indexed rendering (standard pass)
                else if (s_gl.externalGeometry == -1)
                {
                    gl.drawArrays(s_gl.primType[q], 0, s_gl.drawCount[q]);
                }
                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 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;

                    //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) {
                            hitObject = new x3dom.Parts(multiPart, [objId], colorMap, emissiveMap, specularMap, visibilityMap);
                            pickedNodes.push(hitObject);
                        }
                    }
                }

            }
            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.requirePingPong());

                rt_tex._cleanupGLObjects = function(retainTex) {
                    if (!retainTex)
                        gl.deleteTexture(this._webgl.fbo.tex);
                    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;
                };

                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.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.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 ( !(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]);
                }

                gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 1]);

                gl.vertexAttribPointer(sp.position,
                    s_msh._numPosComponents, s_gl.coordType, false,
                    shape._coordStrideOffset[0], shape._coordStrideOffset[1]);
                gl.enableVertexAttribArray(sp.position);

                if (sp.normal !== undefined && s_gl.buffers[q6 + 2]) {
                    gl.bindBuffer(gl.ARRAY_BUFFER, s_gl.buffers[q6 + 2]);

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

                // 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);
	};
	
    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
 */

x3dom.bridge = {

    setFlashReady: function (driver, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.isFlashReady = true;
        x3dom.debug.logInfo('Flash is ready for rendering (' + driver + ')');
    },

    onMouseDown: function (x, y, button, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.doc.onMousePress(x3dCanvas.gl, x, y, button);
        x3dCanvas.doc.needRender = true;
    },

    onMouseUp: function (x, y, button, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.doc.onMouseRelease(x3dCanvas.gl, x, y, button);
        x3dCanvas.doc.needRender = true;
    },

    onMouseOver: function (x, y, button, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.doc.onMouseOver(x3dCanvas.gl, x, y, button);
        x3dCanvas.doc.needRender = true;
    },

    onMouseOut: function (x, y, button, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.doc.onMouseOut(x3dCanvas.gl, x, y, button);
        x3dCanvas.doc.needRender = true;
    },

    onDoubleClick: function (x, y, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.doc.onDoubleClick(x3dCanvas.gl, x, y);
        x3dCanvas.doc.needRender = true;
        x3dom.debug.logInfo("dblClick");
    },

    onMouseDrag: function (x, y, button, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.doc.onDrag(x3dCanvas.gl, x, y, button);
        x3dCanvas.doc.needRender = true;
    },

    onMouseMove: function (x, y, button, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.doc.onMove(x3dCanvas.gl, x, y, button);
        x3dCanvas.doc.needRender = true;
    },

    onMouseWheel: function (x, y, button, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        x3dCanvas.doc.onDrag(x3dCanvas.gl, x, y, button);
        x3dCanvas.doc.needRender = true;
    },

    onKeyDown: function (charCode, canvas) {
        var x3dCanvas = x3dom.canvases[canvas];
        var keysEnabled = x3dCanvas.x3dElem.getAttribute("keysEnabled");
        if (!keysEnabled || keysEnabled.toLowerCase() === "true") {
            x3dCanvas.doc.onKeyPress(charCode);
        }
        x3dCanvas.doc.needRender = true;
    },

    setBBox: function (id, center, size) {
        var shape = x3dom.nodeTypes.Shape.idMap.nodeID[id];
        //shape._vf.bboxCenter.setValues( new x3dom.fields.SFVec3f(center.x,center.y,center.z) );
        //shape._vf.bboxSize.setValues( new x3dom.fields.SFVec3f(size.x,size.y,size.z) );
    },

    setShapeDirty: function (id) {
        var shape = x3dom.nodeTypes.Shape.idMap.nodeID[id];
        shape.setAllDirty();
    }
};


x3dom.gfx_flash = (function () {

    /** Context
     *
     */
    function Context(object, name, renderType) {
        this.object = object;
        this.name = name;
        this.isAlreadySet = false;
        this.renderType = renderType;
    }

    /** setup context
     *
     */
    function setupContext(object, renderType) {

        //Set max indexable coords
        x3dom.Utils.maxIndexableCoords = 65535;

        //Return new Context
        return new Context(object, 'flash', renderType);
    }

    /** get context name
     *
     */
    Context.prototype.getName = function () {
        return this.name;
    };

    /** render scene
     *
     */
    Context.prototype.renderScene = function (viewarea) {
        //Get Scene from Viewarea
        var scene = viewarea._scene;

        var min = x3dom.fields.SFVec3f.MAX();
        var max = x3dom.fields.SFVec3f.MIN();

        var vol = scene.getVolume();
        vol.getBounds(min, max);

        scene._lastMin = min;
        scene._lastMax = max;

        viewarea._last_mat_view = x3dom.fields.SFMatrix4f.identity();
        viewarea._last_mat_proj = x3dom.fields.SFMatrix4f.identity();
        viewarea._last_mat_scene = x3dom.fields.SFMatrix4f.identity();

        //Dirty HACK
        var viewpoint = scene.getViewpoint();
        if (viewpoint._vf.zNear == -1 || viewpoint._vf.zFar == -1) {
            viewpoint._vf.zFar = 20000;
            viewpoint._vf.zNear = 0.1;
        }

        var mat_view = viewarea.getViewMatrix();
        var mat_proj = viewarea.getProjectionMatrix();
        var mat_scene = mat_proj.mult(mat_view);

        //Setup the flash scene
        this.setupScene(scene, viewarea);

        //Get background node
        var background = scene.getBackground();

        //Setup the background
        this.setupBackground(background);
		
		// Get the fog node
		var fog = scene.getFog();
		
		// Setup the fog
		this.setupFog(fog);

        //Collect all drawableObjects
        scene.drawableCollection = null;
        var env = scene.getEnvironment();

        var drawableCollectionConfig = {
            viewArea: viewarea,
            sortTrans: env._vf.sortTrans,
            viewMatrix: mat_view,
            projMatrix: mat_proj,
            sceneMatrix: mat_scene,
            frustumCulling: false,
            smallFeatureThreshold: false,
            context: null,
            gl: null
        };

        scene.drawableCollection = new x3dom.DrawableCollection(drawableCollectionConfig);
        scene.collectDrawableObjects(x3dom.fields.SFMatrix4f.identity(), scene.drawableCollection, true, false, 0, []);

        scene.drawableCollection.concat();

        //Get Number of drawableObjects
        var numDrawableObjects = scene.drawableCollection.length;

        if (numDrawableObjects > 0) {
            var RefList = [];

            //Iterate over all Objects for setup
            for (var i = 0; i < numDrawableObjects; i++) {
                //Get object and transformation
                var drawable = scene.drawableCollection.get(i);
                var trafo = drawable.transform;
                var obj3d = drawable.shape;

                //Count shape references for DEF/USE
                if (RefList[obj3d._objectID] != undefined) {
                    RefList[obj3d._objectID]++;
                } else {
                    RefList[obj3d._objectID] = 0;
                }

                // TODO; move to addDrawable()
                this.setupShape(obj3d, trafo, RefList[obj3d._objectID]);
            }
        }

        //Render the flash scene
        this.object.renderScene();
    };

    /** setup scene
     *
     */
    Context.prototype.setupScene = function (scene, viewarea) {

        //Set View-Matrix
        var mat_view = viewarea.getViewMatrix();

        // fire viewpointChanged event
        if (!viewarea._last_mat_view.equals(mat_view)) {
            var e_viewpoint = viewarea._scene.getViewpoint();
            var e_eventType = "viewpointChanged";
            /*TEST*/
            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;
                        }
                    };

                    e_viewpoint.callEvtHandler(e_eventType, e_event);
                }
            }
            catch (e_e) {
                x3dom.debug.logException(e_e);
            }
        }

        viewarea._last_mat_view = mat_view;

        //Dirty HACK
        var viewpoint = scene.getViewpoint();
        //viewpoint._vf.zFar = 100;
        //viewpoint._vf.zNear = 0.1;

        var mat_proj = viewarea.getProjectionMatrix();

        this.object.setViewpoint({ fov: viewpoint._vf.fov,
            zFar: viewpoint._vf.zFar,
            zNear: viewpoint._vf.zNear,
            viewMatrix: mat_view.toGL(),
            projectionMatrix: mat_proj.toGL() });

        //Set HeadLight
        var nav = scene.getNavigationInfo();
        if (nav._vf.headlight) {
            /*this.object.setLights( { idx: 0,
             type: 0,
             on: 1.0,
             color: [1.0, 1.0, 1.0],
             intensity: 1.0,
             ambientIntensity: 0.0,
             direction: [0.0, 0.0, 1.0],
             attenuation: [1.0, 1.0, 1.0],
             location: [1.0, 1.0, 1.0],
             radius: 0.0,
             beamWidth: 0.0,
             cutOffAngle: 0.0 } );*/

            this.object.setHeadLight({ id: -1,
                on: 1.0,
                color: [1.0, 1.0, 1.0],
                intensity: 1.0,
                ambientIntensity: 0.0,
                direction: [0.0, 0.0, -1.0] });
        }

        //TODO Set Lights
        if (this.renderType == "deferred") {
            var lights = viewarea.getLights();
            for (var i = 0; i < lights.length; i++) {
                if (lights[i]._dirty) {

                    if (x3dom.isa(lights[i], x3dom.nodeTypes.DirectionalLight)) {
                        this.object.setDirectionalLight({ id: lights[i]._lightID,
                            on: lights[i]._vf.on,
                            color: lights[i]._vf.color.toGL(),
                            intensity: lights[i]._vf.intensity,
                            ambientIntensity: lights[i]._vf.ambientIntensity,
                            direction: lights[i]._vf.direction.toGL() });
                    }
                    else if (x3dom.isa(lights[i], x3dom.nodeTypes.PointLight)) {
                        var light_transform = mat_view.mult(lights[i].getCurrentTransform());

                        this.object.setPointLight({ id: lights[i]._lightID,
                            on: lights[i]._vf.on,
                            color: lights[i]._vf.color.toGL(),
                            intensity: lights[i]._vf.intensity,
                            ambientIntensity: lights[i]._vf.ambientIntensity,
                            attenuation: lights[i]._vf.attenuation.toGL(),
                            location: lights[i]._vf.location.toGL(),
                            radius: lights[i]._vf.radius });
                    }
                    else if (x3dom.isa(lights[i], x3dom.nodeTypes.SpotLight)) {
                        /*this.object.setSpotLight( { id: lights[i]._lightID,
                         on: lights[i]._vf.on,
                         color: lights[i]._vf.color.toGL(),
                         intensity: lights[i]._vf.color.toGL(),
                         ambientIntensity: lights[i]._vf.ambientIntensity,
                         direction: lights[i]._vf.direction.toGL(),
                         attenuation: lights[i]._vf.attenuation.toGL(),
                         location: lights[i]._vf.location.toGL(),
                         radius: lights[i]._vf.radius,
                         beamWidth: lights[i]._vf.beamWidth,
                         cutOffAngle: lights[i]._vf.cutOffAngle } );*/
                    }
                    lights[i]._dirty = false;
                }
            }
        }
    };

    /** setup Background
     *
     */
    Context.prototype.setupBackground = function (background) {
        //If background dirty -> update
        if (background._dirty) {
            this.object.setBackground({ texURLs: background.getTexUrl(),
                skyAngle: background._vf.skyAngle,
                skyColor: background.getSkyColor().toGL(),
                groundAngle: background._vf.groundAngle,
                groundColor: background.getGroundColor().toGL(),
                transparency: background.getTransparency() });
            background._dirty = false;
        }
    };
	
	/** setup Fog
     *
     */
    Context.prototype.setupFog = function (fog) {
		if (!fog || !fog._vf || fog._vf.visibilityRange <= 0.0) {
			this.object.setFog({
				color: null,
				visibilityRange: -1.0,
				fogType: -1.0
			});
			return;
		};		
		
		this.object.setFog({
			color: fog._vf.color.toGL(),
			visibilityRange: fog._vf.visibilityRange,
			fogType: (fog._vf.fogType === "LINEAR") ? 0.0 : 1.0
		});
    };

    /** setup Shape
     *
     */
    Context.prototype.setupShape = function (shape, trafo, refID) {

        //Check shape geometry type
        if (x3dom.isa(shape._cf.geometry.node, x3dom.nodeTypes.PointSet)) {
            x3dom.debug.logError("Flash backend doesn't support PointSets yet");
        } else if (x3dom.isa(shape._cf.geometry.node, x3dom.nodeTypes.IndexedLineSet)) {
            x3dom.debug.logError("Flash backend doesn't support LineSets yet");
        } else if (x3dom.isa(shape._cf.geometry.node, x3dom.nodeTypes.Text)) {
            this.setupText(shape, trafo, refID);
        } else {
            this.setupIndexedFaceSet(shape, trafo, refID);
        }
    };

    Context.prototype.setupIndexedFaceSet = function (shape, trafo, refID) {
        //Set modelMatrix
        this.object.setMeshTransform({ id: shape._objectID,
            refID: refID,
            transform: trafo.toGL() });
        if (refID == 0) {
            //Check if is ImageGeometry or BinaryGeometry
            var isImageGeometry = x3dom.isa(shape._cf.geometry.node, x3dom.nodeTypes.ImageGeometry);
            var isBinaryGeometry = x3dom.isa(shape._cf.geometry.node, x3dom.nodeTypes.BinaryGeometry);

            //Check if Appearance is available
            var appearance = shape._cf.appearance.node;
            var sortType = (appearance) ? shape._cf.appearance.node._vf.sortType : "auto";
            var sortKey = (appearance) ? shape._cf.appearance.node._vf.sortKey : 0

            //Set Mesh Properties
            if (isImageGeometry) {
                this.object.setMeshProperties({ id: shape._objectID,
                    type: "ImageGeometry",
                    sortType: sortType,
                    sortKey: sortKey,
                    solid: shape.isSolid(),
                    bboxMin: shape._cf.geometry.node.getMin().toGL(),
                    bboxMax: shape._cf.geometry.node.getMax().toGL(),
                    bboxCenter: shape._cf.geometry.node.getCenter().toGL(),
                    primType: shape._cf.geometry.node._vf.primType,
                    vertexCount: shape._cf.geometry.node._vf.vertexCount });
            } else if (isBinaryGeometry) {
                this.object.setMeshProperties({ id: shape._objectID,
                    type: "BinaryGeometry",
                    sortType: sortType,
                    sortKey: sortKey,
                    solid: shape.isSolid(),
                    bgCenter: shape._cf.geometry.node._vf.position.toGL(),
                    bgSize: shape._cf.geometry.node._vf.size.toGL(),
                    bboxCenter: shape._cf.geometry.node.getCenter().toGL(),
                    primType: shape._cf.geometry.node._vf.primType,
                    vertexCount: shape._cf.geometry.node._vf.vertexCount });
            } else {
                this.object.setMeshProperties({ id: shape._objectID,
                    type: "Default",
                    sortType: sortType,
                    sortKey: sortKey,
                    solid: shape.isSolid() });
            }

            //Set indices
            if (shape._dirty.indexes === true) {
                if (isImageGeometry) {
                    //TODO new flash IG implementation
                    /*this.object.setMeshIndices( { id: shape._objectID,
                     idx: 0,
                     indices: shape._cf.geometry.node.getIndexTextureURL() } );*/
                } else if (isBinaryGeometry) {
                    this.object.setMeshIndices({ id: shape._objectID,
                        idx: 0,
                        indices: shape._nameSpace.getURL(shape._cf.geometry.node._vf.index) });
                } else {
                    //If Mesh is multi indexed we have to split it in Flash
                    if (shape._cf.geometry.node._mesh._multiIndIndices && shape._cf.geometry.node._mesh._multiIndIndices.length)
                    {
                        shape._cf.geometry.node._mesh.splitMesh(3, true);
                    }

                    for (var i = 0; i < shape._cf.geometry.node._mesh._indices.length; i++) {
                        this.object.setMeshIndices({ id: shape._objectID,
                            idx: i,
                            indices: shape._cf.geometry.node._mesh._indices[i] });
                    }
                }
                shape._dirty.indexes = false;
            }

            //Set vertices
            if (shape._dirty.positions === true) {
                if (isImageGeometry) {
                    this.object.setMeshVertices({ id: shape._objectID,
                        idx: 0,
                        //TODO new flash IG implementation coords: shape._cf.geometry.node.getCoordinateTextureURLs(),
                        coordinateTexture0: shape._cf.geometry.node.getCoordinateTextureURL(0),
                        coordinateTexture1: shape._cf.geometry.node.getCoordinateTextureURL(1) });
                } else if (isBinaryGeometry) {
                    this.object.setMeshVertices({ id: shape._objectID,
                        idx: 0,
                        interleaved: shape._cf.geometry.node._hasStrideOffset,
                        vertices: shape._nameSpace.getURL(shape._cf.geometry.node._vf.coord),
                        normals: shape._nameSpace.getURL(shape._cf.geometry.node._vf.normal),
                        texCoords: shape._nameSpace.getURL(shape._cf.geometry.node._vf.texCoord),
                        colors: shape._nameSpace.getURL(shape._cf.geometry.node._vf.color),
                        numColorComponents: shape._cf.geometry.node._mesh._numColComponents,
                        numNormalComponents: shape._cf.geometry.node._mesh._numNormComponents,
                        vertexType: shape._cf.geometry.node._vf.coordType,
                        normalType: shape._cf.geometry.node._vf.normalType,
                        texCoordType: shape._cf.geometry.node._vf.texCoordType,
                        colorType: shape._cf.geometry.node._vf.colorType,
                        vertexStrideOffset: shape._coordStrideOffset,
                        normalStrideOffset: shape._normalStrideOffset,
                        texCoordStrideOffset: shape._texCoordStrideOffset,
                        colorStrideOffset: shape._colorStrideOffset });
                } else {
                    for (var i = 0; i < shape._cf.geometry.node._mesh._positions.length; i++) {
                        this.object.setMeshVertices({ id: shape._objectID,
                            idx: i,
                            vertices: shape._cf.geometry.node._mesh._positions[i] });
                    }
                }
                shape._dirty.positions = false;
            }

            //Set normals
            if (shape._dirty.normals === true) {
                if (isImageGeometry) {
                    this.object.setMeshNormals({ id: shape._objectID,
                        idx: 0,
                        //TODO new flash IG implementation normals: shape._cf.geometry.node.getNormalTextureURLs(),
                        normalTexture: shape._cf.geometry.node.getNormalTextureURL() });
                } else if (isBinaryGeometry) {
                    if (!shape._cf.geometry.node._hasStrideOffset) {
                        this.object.setMeshNormals({ id: shape._objectID,
                            idx: 0,
                            normals: shape._nameSpace.getURL(shape._cf.geometry.node._vf.normal) });
                    }
                } else {
                    if (shape._cf.geometry.node._mesh._normals[0].length) {
                        for (var i = 0; i < shape._cf.geometry.node._mesh._normals.length; i++) {
                            this.object.setMeshNormals({ id: shape._objectID,
                                idx: i,
                                normals: shape._cf.geometry.node._mesh._normals[i] });
                        }
                    }
                }
                shape._dirty.normals = false;
            }

            //Set colors
            if (shape._dirty.colors === true) {
                if (isImageGeometry) {
                    this.object.setMeshColors({ id: shape._objectID,
                        idx: 0,
                        colorTexture: shape._cf.geometry.node.getColorTextureURL(),
                        components: shape._cf.geometry.node._mesh._numColComponents });
                } else if (isBinaryGeometry) {
                    if (!shape._cf.geometry.node._hasStrideOffset) {
                        this.object.setMeshColors({ id: shape._objectID,
                            idx: 0,
                            colors: shape._nameSpace.getURL(shape._cf.geometry.node._vf.color),
                            components: shape._cf.geometry.node._mesh._numColComponents });
                    }
                } else {
                    if (shape._cf.geometry.node._mesh._colors[0].length) {
                        for (var i = 0; i < shape._cf.geometry.node._mesh._colors.length; i++) {
                            this.object.setMeshColors({ id: shape._objectID,
                                idx: i,
                                colors: shape._cf.geometry.node._mesh._colors[i],
                                components: shape._cf.geometry.node._mesh._numColComponents });
                        }
                    }
                }
                shape._dirty.colors = false;
            }

            //Set texture coordinates
            if (shape._dirty.texcoords === true) {
                if (isImageGeometry) {
                    this.object.setMeshTexCoords({ id: shape._objectID,
                        idx: 0,
                        texCoordTexture: shape._cf.geometry.node.getTexCoordTextureURL() });
                } else if (isBinaryGeometry) {
                    if (!shape._cf.geometry.node._hasStrideOffset) {
                        this.object.setMeshTexCoords({ id: shape._objectID,
                            idx: 0,
                            texCoords: shape._nameSpace.getURL(shape._cf.geometry.node._vf.texCoord) });
                    }
                } else {
                    if (shape._cf.geometry.node._mesh._texCoords[0].length) {
                        for (var i = 0; i < shape._cf.geometry.node._mesh._texCoords.length; i++) {
                            this.object.setMeshTexCoords({ id: shape._objectID,
                                idx: i,
                                texCoords: shape._cf.geometry.node._mesh._texCoords[i] });
                        }
                    }
                }
                shape._dirty.texcoords = false;
            }

            //Set material
            if (shape._dirty.material === true) {
                if (appearance) {
                    var material = shape._cf.appearance.node._cf.material.node;
                    if (material) {
                        this.object.setMeshMaterial({ id: shape._objectID,
                            ambientIntensity: material._vf.ambientIntensity,
                            diffuseColor: material._vf.diffuseColor.toGL(),
                            emissiveColor: material._vf.emissiveColor.toGL(),
                            shininess: material._vf.shininess,
                            specularColor: material._vf.specularColor.toGL(),
                            transparency: material._vf.transparency });
                    }
                }
                shape._dirty.material = false;
            }

            //Set Texture
            if (shape._dirty.texture === true) {
                if (appearance) {
                    var texTrafo = null;
                    if (appearance._cf.textureTransform.node) {
                        texTrafo = appearance.texTransformMatrix().toGL();
                    }

                    var texture = shape._cf.appearance.node._cf.texture.node;

                    if (texture) {
                        if (x3dom.isa(texture, x3dom.nodeTypes.PixelTexture)) {
                            this.object.setPixelTexture({ id: shape._objectID,
                                width: texture._vf.image.width,
                                height: texture._vf.image.height,
                                comp: texture._vf.image.comp,
                                pixels: texture._vf.image.toGL() });
                        } else if (x3dom.isa(texture, x3dom.nodeTypes.ComposedCubeMapTexture)) {
                            this.object.setCubeTexture({ id: shape._objectID,
                                texURLs: texture.getTexUrl() });
                        } else if (texture._isCanvas && texture._canvas) {
                            this.object.setCanvasTexture({ id: shape._objectID,
                                width: texture._canvas.width,
                                height: texture._canvas.height,
                                dataURL: texture._canvas.toDataURL() });
                        } else if (x3dom.isa(texture, x3dom.nodeTypes.MultiTexture)) {
                            x3dom.debug.logError("Flash backend doesn't support MultiTextures yet");
                        } else if (x3dom.isa(texture, x3dom.nodeTypes.MovieTexture)) {
                            x3dom.debug.logError("Flash backend doesn't support MovieTextures yet");
                        } else {
                            this.object.setMeshTexture({ id: shape._objectID,
                                origChannelCount: texture._vf.origChannelCount,
                                repeatS: texture._vf.repeatS,
                                repeatT: texture._vf.repeatT,
                                url: texture._vf.url[0],
                                transform: texTrafo });
                        }
                    } else {
                        this.object.removeTexture({ id: shape._objectID });
                    }
                }
                shape._dirty.texture = false;
            }

            //Set sphere mapping
            if (shape._cf.geometry.node._cf.texCoord !== undefined &&
                shape._cf.geometry.node._cf.texCoord.node !== null &&
                !x3dom.isa(shape._cf.geometry.node._cf.texCoord.node, x3dom.nodeTypes.X3DTextureNode) &&
                shape._cf.geometry.node._cf.texCoord.node._vf.mode) {
                var texMode = shape._cf.geometry.node._cf.texCoord.node._vf.mode;
                if (texMode.toLowerCase() == "sphere") {
                    this.object.setSphereMapping({ id: shape._objectID,
                        sphereMapping: 1 });
                }
                else {
                    this.object.setSphereMapping({ id: shape._objectID,
                        sphereMapping: 0 });
                }
            }
            else {
                this.object.setSphereMapping({ id: shape._objectID,
                    sphereMapping: 0 });
            }
        }
    };

    Context.prototype.setupText = function (shape, trafo, refID) {
        //Set modelMatrix
        this.object.setMeshTransform({ id: shape._objectID,
            refID: refID,
            transform: trafo.toGL() });

        if (refID == 0) {

            /*this.object.setMeshProperties( { id: shape._objectID,
             type: "Text",
             solid: shape.isSolid() } );*/

            //Check if Appearance is available
            var appearance = shape._cf.appearance.node;
            var sortType = (appearance) ? shape._cf.appearance.node._vf.sortType : "auto";
            var sortKey = (appearance) ? shape._cf.appearance.node._vf.sortKey : 0

            if (shape._dirty.text === true) {
                var fontStyleNode = shape._cf.geometry.node._cf.fontStyle.node;
                if (fontStyleNode === null) {
                    this.object.setMeshProperties({ id: shape._objectID,
                        type: "Text",
                        sortType: sortType,
                        sortKey: sortKey,
                        solid: shape.isSolid(),
                        text: shape._cf.geometry.node._vf.string,
                        fontFamily: ['SERIF'],
                        fontStyle: "PLAIN",
                        fontAlign: "BEGIN",
                        fontSize: 32,
                        fontSpacing: 1.0,
                        fontHorizontal: true,
                        fontLanguage: "",
                        fontLeftToRight: true,
                        fontTopToBottom: true });
                } else {
                    this.object.setMeshProperties({ id: shape._objectID,
                        type: "Text",
                        sortType: sortType,
                        sortKey: sortKey,
                        solid: shape.isSolid(),
                        text: shape._cf.geometry.node._vf.string,
                        fontFamily: fontStyleNode._vf.family.toString(),
                        fontStyle: fontStyleNode._vf.style.toString(),
                        fontAlign: fontStyleNode._vf.justify.toString(),
                        fontSize: fontStyleNode._vf.size,
                        fontSpacing: fontStyleNode._vf.spacing,
                        fontHorizontal: fontStyleNode._vf.horizontal,
                        fontLanguage: fontStyleNode._vf.language,
                        fontLeftToRight: fontStyleNode._vf.leftToRight,
                        fontTopToBottom: fontStyleNode._vf.topToBottom });
                }
                shape._dirty.text = false;
            }

            if (shape._dirty.material === true) {
                if (appearance) {
                    var material = shape._cf.appearance.node._cf.material.node;
                    if (material) {
                        this.object.setMeshMaterial({ id: shape._objectID,
                            ambientIntensity: material._vf.ambientIntensity,
                            diffuseColor: material._vf.diffuseColor.toGL(),
                            emissiveColor: material._vf.emissiveColor.toGL(),
                            shininess: material._vf.shininess,
                            specularColor: material._vf.specularColor.toGL(),
                            transparency: material._vf.transparency });
                    }
                }
                shape._dirty.material = false;
            }
        }
    };


    /** pick Value
     *
     */
    Context.prototype.pickValue = function (viewarea, x, y, viewMat, sceneMat) {
        var scene = viewarea._scene;

        // method requires that scene has already been rendered at least once
        if (this.object === null || scene === null || scene.drawableCollection === undefined || !scene.drawableCollection || scene._vf.pickMode.toLowerCase() === "box") {
            return false;
        }

        var pickMode = (scene._vf.pickMode.toLowerCase() === "color") ? 1 :
            ((scene._vf.pickMode.toLowerCase() === "texcoord") ? 2 : 0);

        var data = this.object.pickValue({ pickMode: pickMode });

        if (data.objID > 0) {
            viewarea._pickingInfo.pickPos = new x3dom.fields.SFVec3f(data.pickPosX, data.pickPosY, data.pickPosZ);
            viewarea._pickingInfo.pickObj = x3dom.nodeTypes.Shape.idMap.nodeID[data.objID];
        } else {
            viewarea._pickingInfo.pickObj = null;
            viewarea._pickingInfo.lastClickObj = null;
        }

        return true;
    };

    /** shutdown
     *
     */
    Context.prototype.shutdown = function (viewarea) {
        // TODO?
    };

    //Return the setup context function
    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]) {
        return x3dNode._vf[fieldName].copy();
    }

    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]) {

        // 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) {
    var n = 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);
                    if (c) {
                        n.addChild(c, childDomNode.getAttribute("containerField"));
                    }
                } );

                n.nodeChanged();
                return n;
            }
        }
    }
    else if (domNode.localName) {
        // 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;
        
        },
        {
            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
                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);
                    }
                }

                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.volumeValid())
                        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();

                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, colours 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;
        
        },
        {
            nodeChanged: 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 > 0)
                {
                    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)
                    {
                        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");
            },

            fieldChanged: function(fieldName)
            {
                var pnts = null;

                if (fieldName == "coord")
                {
                    pnts = this._cf.coord.node._vf.point;

                    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;
                    });
                }
                else if (fieldName == "coordIndex") {
                    this._mesh._indices[0] = [];

                    var indexes = this._vf.coordIndex;
                    var p0, p1, t = 0;

                    for (var i=0, n=indexes.length; 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; this._mesh._indices[0].push(p0, p1); break;
                                case 2: p0 = p1; p1 = +indexes[i]; this._mesh._indices[0].push(p0, p1); break;
                            }
                        }
                    }

                    this.invalidateVolume();

                    Array.forEach(this._parentNodes, function (node) {
                        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
 */

/* ### 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 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;

                if (!normPerVert || 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);
                    }
                    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);
                    }

                    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;
                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
 */

/* ### 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);

            // shortcut to shader program
            this._shader = null;
        
        },
        {
            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
            };

            // 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._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 */
/*
 * 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 === 0) {
                    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);
                        }
                    }
                }
            }
        }
    )
);

/** 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") {
                    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);
                    }
                    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);
                    }
                }
                
                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);
                }
            },

            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 + " " + 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 + " " + 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": 1.0 - transparency,
                            "backAmbientIntensity": backAmbientIntensity,
                            "backDiffuseColor": x3dom.fields.SFColor.parse(backDiffuseColor),
                            "backEmissiveColor": x3dom.fields.SFColor.parse(backEmissiveColor),
                            "backShininess": backShininess,
                            "backSpecularColor": x3dom.fields.SFColor.parse(backSpecularColor),
                            "backTransparency": 1.0 - 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 {
                        for (i=0; i<selector.length; i++) {
                            if (multiPart._identifierToPartId[selector[i]]) {
                                selection = selection.concat(multiPart._identifierToPartId[selector[i]]);
                            }
                        }
                    }

                    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);
                    }
                    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', []);
        
        },
        {
            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 "none"
             * @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) {
                    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;
            }
        }
    )
);
/** @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._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();
            },

            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', 0.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', 10000);

            this._viewMatrix = null;
            this._projMatrix = null;
            this._lastAspect = 1.0;

            this.resetView();
        
        },
        {
            fieldChanged: function (fieldName) {
                if (fieldName == "position" || fieldName == "orientation") {
                    this.resetView();
                }
                else if (fieldName == "fieldOfView" ||
                    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._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.mult(offset).inverse();
            },

            getNear: function() {
                return this._vf.zNear;
            },

            getFar: function() {
                return this._vf.zFar;
            },

            getProjectionMatrix: function(aspect)
            {
                if (this._projMatrix == null || this._lastAspect != aspect)
                {
                    var near = this.getNear();
                    var far = this.getFar();

                    var left = this._vf.fieldOfView[0];
                    var bottom = this._vf.fieldOfView[1];
                    var right = this._vf.fieldOfView[2];
                    var top = this._vf.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._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._heliUpdated = false;

            var type = this.checkType(this.getType());
            x3dom.debug.logInfo("NavType: " + type);
        
        },
        {
            fieldChanged: function(fieldName) {
                if (fieldName == "typeParams") {
                    this._heliUpdated = false;
                }
                else if (fieldName == "type") {
                    var type = this.checkType(this.getType());

                    switch (type) {
                        case 'game':
                            this._nameSpace.doc._viewarea.initMouseState();
                            break;
                        case 'helicopter':
                            this._heliUpdated = false;
                            break;
                        case "turntable":
                            this._nameSpace.doc._viewarea.initMouseState();
                            this._nameSpace.doc._viewarea.initTurnTable(this);
                            break;
                        default:
                            break;
                    }

                    this._vf.type[0] = type;
                    x3dom.debug.logInfo("Switch to " + type + " mode.");
                }
            },

            setType: function(type, viewarea) {
                var navType = this.checkType(type.toLowerCase());
                var oldType = this.checkType(this.getType());

                switch (navType) {
                    case 'game':
                        if (oldType !== navType) {
                            if (viewarea)
                                viewarea.initMouseState();
                            else
                                this._nameSpace.doc._viewarea.initMouseState();
                        }
                        break;
                    case 'helicopter':
                        if (oldType !== navType) {
                            this._heliUpdated = false;
                        }
                        break;
                    case "turntable":
                        if (oldType !== navType) {
                            if (viewarea) {
                                viewarea.initMouseState();
                                viewarea.initTurnTable(this);
                            }
                            else {
                                this._nameSpace.doc._viewarea.initMouseState();
                                this._nameSpace.doc._viewarea.initTurnTable(this);
                            }
                        }
                        break;
                    default:
                        break;
                }

                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)
               {
                   params.push(this._vf.typeParams[4]);
               }
               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;
            }
        }
    )
);
/** @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.
             * @var {x3dom.fields.MFString} justify
             * @range ["BEGIN","END","FIRST","MIDDLE",""]
             * @memberof x3dom.nodeTypes.FontStyle
             * @initvalue ['BEGIN']
             * @field x3d
             * @instance
             */
            this.addField_MFString(ctx, 'justify', ['BEGIN']);

            /**
             * 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);
        
        },
        {
            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) {
                        node.fieldChanged(fieldName);
                    });
                }
            }
        }
    )
);

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

            /**
             * 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);

            this.hScreenSize = (this._vf.oculusRiftVersion == 1) ? 0.14976 : 0.12576;
            this.vScreenSize = (this._vf.oculusRiftVersion == 1) ? 0.09356 : 0.07074;
            this.vScreenCenter = this.vScreenSize / 2;
            this.eyeToScreenDistance = 0.041;
            this.lensSeparationDistance = 0.0635;
            this.distortionK = [1.0, 0.22, 0.24, 0.0];
            //hRes, vRes = 1280 x 800  // DK2:  1920 x 1080
            //this.lensCenter = 1 - 2 * this.lensSeparationDistance / this.hScreenSize;
            this.lensCenter = 0.151976495726;   // TODO: DK2 ?

            x3dom.debug.assert(this._vf.dimensions.length >= 3,
                "RenderedTexture.dimensions requires at least 3 entries.");
            this._clearParents = true;
            this._needRenderUpdate = true;
        
        },
        {
            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;
                    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.vScreenSize / (2 * this.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 hp = this.lensCenter;
                    if (stereoMode == "RIGHT_EYE") {
                        hp = -hp;
                    }
                    var modifier = new x3dom.fields.SFMatrix4f(
                        1, 0, 0, hp,
                        0, 1, 0, 0,
                        0, 0, 1, 0,
                        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 = "cubeMap";
        
        },
        {
            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. Currently only "TANGENT" (a default tangent space
             *  normal map) is supported.
             * @var {x3dom.fields.SFString} normalSpace
             * @memberof x3dom.nodeTypes.CommonSurfaceShader
             * @initvalue 'TANGENT'
             * @range [TANGENT]
             * @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;
                }
            },

            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 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;
            }
        }
    )
);
/** @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);
                    }
                    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 Shape Resource Container. A suffix with a leading # can be used to reference single meshes inside a SRC: "path/to/data.src#mesh0".
             * @var {x3dom.fields.SFString} url
             * @memberof x3dom.nodeTypes.Geometry3D
             * @initvalue ""
             * @field x3dom
             * @instance
             */
            this.addField_SFString(ctx, 'url',  "");


            //initialization of rendering-related X3DOM structures
            this._mesh._invalidate = false;
            this._mesh._numCoords  = 0;
            this._mesh._numFaces   = 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
             */
            updateRenderData: function(shape, shaderProgram, gl, viewarea, context) {
                var that = this;
                var xhr;

                if (this._vf['url'] == "") {
                    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;

                //TODO: check this object - when is it called, where is it really needed?
                //shape._webgl.makeSeparateTris = {...};


                //post request
                xhr = new XMLHttpRequest();

                xhr.open("GET", this._vf['url'], true);

                xhr.responseType = "arraybuffer";

                xhr.send(null);

                xhr.onerror = function() {
                    x3dom.debug.logError("Unable to load SRC data from URL \"" + that._vf['url'] + "\"");
                };

                //TODO: currently, we assume that the referenced file is always an SRC file
                xhr.onload = function() {
                    shape._webgl.internalDownloadCount  = 0;
                    shape._nameSpace.doc.downloadCount  = 0;

                    var responseBeginUint32 = new Uint32Array(xhr.response, 0, 12);

                    var srcHeaderSize, srcBodySize, srcBodyOffset;
                    var srcHeaderView, srcBodyView;

                    var srcHeaderObj;

                    if ((xhr.status == 200 || xhr.status == 0) && responseBeginUint32.length >= 3) {

                        srcHeaderSize = responseBeginUint32[2];
                        srcBodyOffset = srcHeaderSize + 12;
                        srcBodySize   = xhr.response.byteLength - srcBodyOffset;

                        if (srcHeaderSize > 0 &&  srcBodySize >= 0)
                        {
                            srcHeaderView = new Uint8Array(xhr.response, 12,            srcHeaderSize);
                            srcBodyView   = new Uint8Array(xhr.response, srcBodyOffset, srcBodySize  );

                            //decode SRC header
                            //currently, we assume ASCII JSON encoding
                            try
                            {
                                srcHeaderObj = JSON.parse(String.fromCharCode.apply(null, srcHeaderView));
                            }
                            catch (exc)
                            {
                                x3dom.debug.logError("Unable to parse SRC header: " + exc);
                                return;
                            }

                            that._updateRenderDataFromSRC(shape, shaderProgram, gl, srcHeaderObj, srcBodyView);
                        }
                        else
                        {
                            x3dom.debug.logError("Invalid SRC data, loaded from URL \"" +
                                                 that._vf['url'] + "\"");
                            return;
                        }
                    }
                    else
                    {
                        x3dom.debug.logError("Unable to load SRC data from URL \"" + that._vf['url'] + "\"");
                    }
                };
            } ,

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

            //----------------------------------------------------------------------------------------------------------
            // PRIVATE FUNCTIONS
            //----------------------------------------------------------------------------------------------------------

            //TODO: we currently assume that we always read data from exactly one SRC (i.e., no Source nodes)
            /**
             * Helper function, updating the render data, stored in the given objects,
             * with data read from the given SRC.
             *
             * @param {Object} shape - x3dom shape node
             * @param {Object} shaderProgram - x3dom shader program
             * @param {Object} gl - WebGL context
             * @param {Object} srcHeaderObj - the JS object which was created from the SRC header
             * @param {Uint8Array} srcBodyView - a typed array view on the body of the SRC file
             * @private
             */
            _updateRenderDataFromSRC: function(shape, shaderProgram, gl, srcHeaderObj, srcBodyView)
            {
                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 ID_BUFFER_IDX       = 5;

                var MAX_NUM_BUFFERS_PER_DRAW = 6;

                var indexViews = srcHeaderObj["accessors"]["indexViews"];
                var indexViewID, indexView;

                var attributeViews = srcHeaderObj["accessors"]["attributeViews"];
                var attributes;
                var attributeID, attributeView;
                var x3domTypeID, x3domShortTypeID, numComponents;

                var meshes = srcHeaderObj["meshes"];
                var mesh, meshID;
                var meshIdx, bufferOffset;


                //the meta data object is currently unused
                //var metadataObj = srcHeaderObj["meta"];


                //1. create GL buffers for bufferChunks / bufferViews

                //create buffers and GL buffer views, and store their identifiers in a map
                var viewIDsToGLBufferIDs = {};

                //due to the differentiation between targets ARRAY and ELEMENT_ARRAY, we need to check the usage
                //of the buffer view objects here first, before uploading them for the matching target
                var indexViewBufferIDs = {};
                for (indexViewID in indexViews)
                {
                    indexView = indexViews[indexViewID];
                    indexViewBufferIDs[indexView["bufferView"]] = true;
                }

                this._createGLBuffersFromSRCChunks(gl,
                                                   srcHeaderObj["bufferChunks"], srcHeaderObj["bufferViews"],
                                                   srcBodyView, indexViewBufferIDs, viewIDsToGLBufferIDs);


                //2. remember GL index buffer properties, if any

                for (indexViewID in indexViews)
                {
                    indexView = indexViews[indexViewID];

                    //we currently assume 16 bit index data
                    if (indexView["componentType"] != gl.UNSIGNED_SHORT)
                    {
                        x3dom.debug.logWarning("SRC index componentType " + indexView["componentType"] +
                                               " is not UNSIGNED_SHORT. " +
                                               "Ignoring given value and assuming UNSIGNED_SHORT indices.");
                    }
                    shape._webgl.indexType = gl.UNSIGNED_SHORT;
                }


                //3. remember necessary information to setup GL draw parameters and attribute pointers

                meshIdx      = 0;
                bufferOffset = 0;

                shape._webgl.primType    = [];
                shape._webgl.indexOffset = [];
                shape._webgl.drawCount   = [];

                //hints for stats display
                this._mesh._numCoords   = 0;
                this._mesh._numFaces    = 0;

                for (meshID in meshes)
                {
                    mesh = meshes[meshID];

                    //setup indices, if any
                    indexViewID = mesh["indices"];
                    //TODO: allow the renderer to switch between indexed and non-indexed rendering, for one extGeo
                    if (indexViewID != "")
                    {
                        shape._webgl.externalGeometry =  1; //indexed EG

                        indexView = indexViews[indexViewID];

                        shape._webgl.indexOffset[meshIdx] = indexView["byteOffset"];
                        shape._webgl.drawCount[meshIdx]   = indexView["count"];

                        shape._webgl.buffers[INDEX_BUFFER_IDX + bufferOffset] =
                            viewIDsToGLBufferIDs[indexView["bufferView"]];

                        //TODO: add support for LINES and POINTS
                        this._mesh._numFaces += indexView["count"] / 3;
                    }
                    else
                    {
                        shape._webgl.externalGeometry = -1; //non-indexed EG
                    }

                    //setup primType
                    shape._webgl.primType[meshIdx] = mesh["primitive"];

                    //setup attributes
                    attributes = mesh["attributes"];

                    for (attributeID in attributes)
                    {
                        attributeView = attributeViews[attributes[attributeID]];

                        //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] =
                                    viewIDsToGLBufferIDs[attributeView["bufferView"]];
                                //for non-indexed rendering, we assume that all attributes have the same count
                                if (mesh["indices"] == "")
                                {
                                    shape._webgl.drawCount[meshIdx] = attributeView["count"];
                                    //TODO: add support for LINES and POINTS
                                    this._mesh._numFaces += attributeView["count"] / 3;
                                }
                                this._mesh._numCoords += attributeView["count"];
                                break;

                            case "normal":
                                x3domTypeID      = "normal";
                                x3domShortTypeID = "Norm";
                                shape._webgl.buffers[NORMAL_BUFFER_IDX + bufferOffset] =
                                    viewIDsToGLBufferIDs[attributeView["bufferView"]];
                                break;

                            case "texcoord":
                                x3domTypeID      = "texCoord";
                                x3domShortTypeID = "Tex";
                                shape._webgl.buffers[TEXCOORD_BUFFER_IDX + bufferOffset] =
                                    viewIDsToGLBufferIDs[attributeView["bufferView"]];
                                break;

                            case "color":
                                x3domTypeID      = "color";
                                x3domShortTypeID = "Col";
                                shape._webgl.buffers[COLOR_BUFFER_IDX + bufferOffset] =
                                    viewIDsToGLBufferIDs[attributeView["bufferView"]];
                                break;

                            case "id":
                                x3domTypeID      = "id";
                                x3domShortTypeID = "Id";
                                shape._webgl.buffers[ID_BUFFER_IDX + bufferOffset] =
                                    viewIDsToGLBufferIDs[attributeView["bufferView"]];
                                break;
                        }

                        shape["_" + x3domTypeID + "StrideOffset"][0] = attributeView["byteStride"];
                        shape["_" + x3domTypeID + "StrideOffset"][1] = attributeView["byteOffset"];
                        shape._webgl[x3domTypeID + "Type"]           = attributeView["componentType"];

                        numComponents = x3dom.nodeTypes.ExternalGeometry._findNumComponentsForSRCAccessorType(attributeView["type"]);
                        this._mesh["_num" + x3domShortTypeID + "Components"] = numComponents;
                    }

                    ++meshIdx;
                    bufferOffset += MAX_NUM_BUFFERS_PER_DRAW;
                }


                //4. notify renderer

                shape._nameSpace.doc.needRender = true;

                x3dom.BinaryContainerLoader.checkError(gl);
            },

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

            /**
             * Helper function, creating WebGL buffers for the given SRC data structures.
             * The result is stored in the given map from bufferView IDs to GL buffer IDs.
             *
             * @param {Object} gl - WebGL context
             * @param {Object} bufferChunksObj - the SRC header's bufferChunks object
             * @param {Object} bufferViewsObj - the SRC header's bufferViews object
             * @param {Uint8Array} srcBodyView - a typed array view on the body of the SRC file
             * @param {Object} indexViewBufferIDs - an object which holds the IDs of all index data bufferViews
             * @param {Object} viewIDsToGLBufferIDs - map that will be filled with a GL buffer ID for each bufferView ID
             * @private
             */
            _createGLBuffersFromSRCChunks: function(gl, bufferChunksObj, bufferViewsObj, srcBodyView,
                                                    indexViewBufferIDs, viewIDsToGLBufferIDs)
            {
                var i;
                var bufferView;
                var chunkIDList;
                var bufferType;

                var chunk;
                var newBuffer;
                var chunkDataView;
                var currentChunkDataOffset;

                //for each buffer view object, create and fill a GL buffer from its buffer chunks
                for (var bufferViewID in bufferViewsObj)
                {
                    bufferType = (typeof indexViewBufferIDs[bufferViewID] !== 'undefined') ? gl.ELEMENT_ARRAY_BUFFER :
                                                                                             gl.ARRAY_BUFFER;

                    bufferView = bufferViewsObj[bufferViewID];

                    chunkIDList = bufferView["chunks"];

                    //case 1: single chunk
                    if (chunkIDList.length == 1)
                    {
                        chunk = bufferChunksObj[chunkIDList[0]];

                        chunkDataView = new Uint8Array(srcBodyView.buffer,
                                                       srcBodyView.byteOffset + chunk["byteOffset"],
                                                       chunk["byteLength"]);

                        newBuffer = gl.createBuffer();

                        gl.bindBuffer(bufferType, newBuffer);

                        //upload all chunk data to GPU
                        gl.bufferData(bufferType, chunkDataView, gl.STATIC_DRAW);

                        viewIDsToGLBufferIDs[bufferViewID] = newBuffer;
                    }
                    //case 2: multiple chunks
                    else
                    {
                        newBuffer = gl.createBuffer();

                        gl.bindBuffer(bufferType, newBuffer);

                        //reserve GPU memory for all chunks
                        gl.bufferData(bufferType, bufferView["byteLength"], gl.STATIC_DRAW);

                        currentChunkDataOffset = 0;

                        for (i = 0; i < chunkIDList.length; ++i)
                        {
                            chunk = bufferChunksObj[chunkIDList[i]];

                            chunkDataView = new Uint8Array(srcBodyView.buffer,
                                                           srcBodyView.byteOffset + chunk["byteOffset"],
                                                           chunk["byteLength"]);

                            //upload chunk data to GPU
                            gl.bufferSubData(bufferType, currentChunkDataOffset, chunkDataView);

                            currentChunkDataOffset += chunk["byteLength"];
                        }

                        viewIDsToGLBufferIDs[bufferViewID] = newBuffer;
                    }
                }
            },

            /**
             * 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;
            }

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

            /*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();
                }
            }*/

        }
    )
);


//----------------------------------------------------------------------------------------------------------------------
// PUBLIC STATIC FUNCTIONS
//----------------------------------------------------------------------------------------------------------------------



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

//----------------------------------------------------------------------------------------------------------------------
// PRIVATE STATIC FUNCTIONS
//----------------------------------------------------------------------------------------------------------------------

/**
 *
 * @param {STRING} type - accessor type, must be "SCALAR", "VEC2", "VEC3" or "VEC4"
 * @private
 */
x3dom.nodeTypes.ExternalGeometry._findNumComponentsForSRCAccessorType = function(type)
{
    switch (type)
    {
        case "SCALAR": return 1;
        case "VEC2":   return 2;
        case "VEC3":   return 3;
        case "VEC4":   return 4;
        default:       return 0;
    }
};

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

/** @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);

            // 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;

                if (indexes.length && indexes[indexes.length-1] != -1)
                {
                    indexes.push(-1);
                    x3dom.debug.logWarning('Last index value should be -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);
                }
            }

            //----------------------------------------------------------------------------------------------------------------------
        }
    )
);

x3dom.versionInfo = {
    version:  '1.6.2',
    revision: '8f5655cec1951042e852ee9def292c9e0194186b',
    date:     'Sat Dec 20 00:03:52 2014 +0100'
};


x3dom.versionInfo = {
    version:  '1.6.2',
    revision: '8f5655cec1951042e852ee9def292c9e0194186b',
    date:     'Sat Dec 20 00:03:52 2014 +0100'
};

