").append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+ // Otherwise use the full result
+ responseText );
+
+ }).complete( callback && function( jqXHR, status ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ });
+ }
+
+ return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
+ jQuery.fn[ type ] = function( fn ){
+ return this.on( type, fn );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ });
+ };
+});
+
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ type: "GET",
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ processData: true,
+ async: true,
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ url: true,
+ context: true
+ }
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ return settings ?
+
+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var transport,
+ // URL without anti-cache param
+ cacheURL,
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events is callbackContext if it is a DOM node or jQuery collection
+ globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+ jQuery( callbackContext ) :
+ jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks("once memory"),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+ readyState: 0,
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while ( (match = rheaders.exec( responseHeadersString )) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match == null ? null : match;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ var lname = name.toLowerCase();
+ if ( !state ) {
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Status-dependent callbacks
+ statusCode: function( map ) {
+ var code;
+ if ( map ) {
+ if ( state < 2 ) {
+ for ( code in map ) {
+ // Lazy-add the new callback in a way that preserves old ones
+ statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+ }
+ } else {
+ // Execute the appropriate callbacks
+ jqXHR.always( map[ jqXHR.status ] );
+ }
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
+
+ // Attach deferreds
+ deferred.promise( jqXHR ).complete = completeDeferred.add;
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // Handle falsy url in the settings object (#10093: consistency with old signature)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
+
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger("ajaxStart");
+ }
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ cacheURL = s.url;
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+ s.url = rts.test( cacheURL ) ?
+
+ // If there is already a '_' parameter, set its value
+ cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
+
+ // Otherwise add one to the end
+ cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
+ }
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+ }
+ if ( jQuery.etag[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout(function() {
+ jqXHR.abort("timeout");
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch ( e ) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ cacheURL ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("etag");
+ if ( modified ) {
+ jQuery.etag[ cacheURL ] = modified;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+ isSuccess = true;
+ statusText = "notmodified";
+
+ // If we have data
+ } else {
+ isSuccess = ajaxConvert( s, response );
+ statusText = isSuccess.state;
+ success = isSuccess.data;
+ error = isSuccess.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( status || !statusText ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger("ajaxStop");
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ }
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields;
+
+ // Fill responseXXX fields
+ for ( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ var conv, conv2, current, tmp,
+ converters = {},
+ i = 0,
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice(),
+ prev = dataTypes[ 0 ];
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ // Convert to each sequential dataType, tolerating list modification
+ for ( ; (current = dataTypes[++i]); ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current !== "*" ) {
+
+ // Convert response if prev dataType is non-auto and differs from current
+ if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split(" ");
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.splice( i--, 0, current );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s["throws"] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+
+ // Update prev for next iteration
+ prev = current;
+ }
+ }
+
+ return { state: "success", data: response };
+}
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /(?:java|ecma)script/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || jQuery("head")[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement("script");
+
+ script.async = true;
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( script.parentNode ) {
+ script.parentNode.removeChild( script );
+ }
+
+ // Dereference the script
+ script = null;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+
+ // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
+ // Use native DOM manipulation to avoid our domManip AJAX trickery
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( undefined, true );
+ }
+ }
+ };
+ }
+});
+var oldCallbacks = [],
+ rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var callbackName, overwritten, responseContainer,
+ jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+ "url" :
+ typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
+ );
+
+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
+
+ // Insert callback into url or form data
+ if ( jsonProp ) {
+ s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+ } else if ( s.jsonp !== false ) {
+ s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Install callback
+ overwritten = window[ callbackName ];
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
+
+ // Clean-up function (fires after converters)
+ jqXHR.always(function() {
+ // Restore preexisting value
+ window[ callbackName ] = overwritten;
+
+ // Save back as free
+ if ( s[ callbackName ] ) {
+ // make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
+
+ // save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
+
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
+
+ responseContainer = overwritten = undefined;
+ });
+
+ // Delegate to script
+ return "script";
+ }
+});
+var xhrCallbacks, xhrSupported,
+ xhrId = 0,
+ // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject && function() {
+ // Abort all pending requests
+ var key;
+ for ( key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( undefined, true );
+ }
+ };
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject("Microsoft.XMLHTTP");
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+xhrSupported = jQuery.ajaxSettings.xhr();
+jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+xhrSupported = jQuery.support.ajax = !!xhrSupported;
+
+// Create transport if the browser can provide an xhr
+if ( xhrSupported ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var handle, i,
+ xhr = s.xhr();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers["X-Requested-With"] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( err ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occurred
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ responses = {};
+ status = xhr.status;
+ xml = xhr.responseXML;
+ responseHeaders = xhr.getAllResponseHeaders();
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+
+ // When requesting binary data, IE6-9 will throw an exception
+ // on any attempt to access responseText (#11426)
+ if ( typeof xhr.responseText === "string" ) {
+ responses.text = xhr.responseText;
+ }
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ if ( !s.async ) {
+ // if we're in sync mode we fire the callback
+ callback();
+ } else if ( xhr.readyState === 4 ) {
+ // (IE6 & IE7) if it's in cache and has been
+ // retrieved directly we need to fire the callback
+ setTimeout( callback );
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback( undefined, true );
+ }
+ }
+ };
+ }
+ });
+}
+var fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [function( prop, value ) {
+ var end, unit,
+ tween = this.createTween( prop, value ),
+ parts = rfxnum.exec( value ),
+ target = tween.cur(),
+ start = +target || 0,
+ scale = 1,
+ maxIterations = 20;
+
+ if ( parts ) {
+ end = +parts[2];
+ unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" && start ) {
+ // Iteratively approximate from a nonzero starting point
+ // Prefer the current property, because this process will be trivial if it uses the same units
+ // Fallback to end or a simple constant
+ start = jQuery.css( tween.elem, prop, true ) || end || 1;
+
+ do {
+ // If previous iteration zeroed out, double until we get *something*
+ // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+ }
+
+ tween.unit = unit;
+ tween.start = start;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
+ }
+ return tween;
+ }]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ });
+ return ( fxNow = jQuery.now() );
+}
+
+function createTweens( animation, props ) {
+ jQuery.each( props, function( prop, value ) {
+ var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( collection[ index ].call( animation, prop, value ) ) {
+
+ // we're done with this property
+ return;
+ }
+ }
+ });
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ stopped,
+ index = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ if ( stopped ) {
+ return false;
+ }
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
+ temp = remaining / animation.duration || 0,
+ percent = 1 - temp,
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // if we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+ if ( stopped ) {
+ return this;
+ }
+ stopped = true;
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
+ }
+
+ createTweens( animation, props );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ elem: elem,
+ anim: animation,
+ queue: animation.opts.queue
+ })
+ );
+
+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'index' from above because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+function defaultPrefilter( elem, props, opts ) {
+ /*jshint validthis:true */
+ var index, prop, value, length, dataShow, toggle, tween, hooks, oldfire,
+ anim = this,
+ style = elem.style,
+ orig = {},
+ handled = [],
+ hidden = elem.nodeType && isHidden( elem );
+
+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always(function() {
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
+ }
+
+ // height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( elem, "display" ) === "inline" &&
+ jQuery.css( elem, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
+ style.display = "inline-block";
+
+ } else {
+ style.zoom = 1;
+ }
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ if ( !jQuery.support.shrinkWrapBlocks ) {
+ anim.done(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
+ }
+ }
+
+
+ // show/hide pass
+ for ( index in props ) {
+ value = props[ index ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ index ];
+ toggle = toggle || value === "toggle";
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+ continue;
+ }
+ handled.push( index );
+ }
+ }
+
+ length = handled.length;
+ if ( length ) {
+ dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+
+ // store state if its toggle - enables .stop().toggle() to "reverse"
+ if ( toggle ) {
+ dataShow.hidden = !hidden;
+ }
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
+ jQuery._removeData( elem, "fxshow" );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( index = 0 ; index < length ; index++ ) {
+ prop = handled[ index ];
+ tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+ orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+ }
+}
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // passing a non empty string as a 3rd parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails
+ // so, simple values such as "10px" are parsed to Float.
+ // complex values such as "rotate(1rad)" are returned as is.
+ result = jQuery.css( tween.elem, tween.prop, "auto" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // use step hook for back compat - use cssHook if its there - use .style if its
+ // available and use plain properties where available
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Remove in 2.0 - this supports IE8's panic based approach
+// to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+});
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+ doAnimation.finish = function() {
+ anim.stop( true );
+ };
+ // Empty animations, or finishing resolves immediately
+ if ( empty || jQuery._data( this, "finish" ) ) {
+ anim.stop( true );
+ }
+ };
+ doAnimation.finish = doAnimation;
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ finish: function( type ) {
+ if ( type !== false ) {
+ type = type || "fx";
+ }
+ return this.each(function() {
+ var index,
+ data = jQuery._data( this ),
+ queue = data[ type + "queue" ],
+ hooks = data[ type + "queueHooks" ],
+ timers = jQuery.timers,
+ length = queue ? queue.length : 0;
+
+ // enable finishing flag on private data
+ data.finish = true;
+
+ // empty the queue first
+ jQuery.queue( this, type, [] );
+
+ if ( hooks && hooks.cur && hooks.cur.finish ) {
+ hooks.cur.finish.call( this );
+ }
+
+ // look for any active animations, and finish them
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+ timers[ index ].anim.stop( true );
+ timers.splice( index, 1 );
+ }
+ }
+
+ // look for any animations in the old queue and finish them
+ for ( index = 0; index < length; index++ ) {
+ if ( queue[ index ] && queue[ index ].finish ) {
+ queue[ index ].finish.call( this );
+ }
+ }
+
+ // turn off finishing flag
+ delete data.finish;
+ });
+ }
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ attrs = { height: type },
+ i = 0;
+
+ // if we include width, step value is 1 to do all cssExpand values,
+ // if we don't include width, step value is 2 to skip over Left and Right
+ includeWidth = includeWidth? 1 : 0;
+ for( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p*Math.PI ) / 2;
+ }
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ fxNow = jQuery.now();
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+ if ( timer() && jQuery.timers.push( timer ) ) {
+ jQuery.fx.start();
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+ if ( !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var docElem, win,
+ box = { top: 0, left: 0 },
+ elem = this[ 0 ],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
+
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== "undefined" ) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow( doc );
+ return {
+ top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),
+ left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
+ };
+};
+
+jQuery.offset = {
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
+
+ var offsetParent, offset,
+ parentOffset = { top: 0, left: 0 },
+ elem = this[ 0 ];
+
+ // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
+ if ( jQuery.css( elem, "position" ) === "fixed" ) {
+ // we assume that getBoundingClientRect is available when computed position is fixed
+ offset = elem.getBoundingClientRect();
+ } else {
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent();
+
+ // Get correct offsets
+ offset = this.offset();
+ if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+ parentOffset = offsetParent.offset();
+ }
+
+ // Add offsetParent borders
+ parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+ parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+ }
+
+ // Subtract parent offsets and element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ return {
+ top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+ left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.documentElement;
+ while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || document.documentElement;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ win.document.documentElement[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+ // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable, null );
+ };
+ });
+});
+// Limit scope pollution from any deprecated API
+// (function() {
+
+// })();
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+ define( "jquery", [], function () { return jQuery; } );
+}
+
+})( window );
diff --git a/papi/plugin/dpp/Human/js/stats.min.js b/papi/plugin/dpp/Human/js/stats.min.js
new file mode 100644
index 00000000..73744ef7
--- /dev/null
+++ b/papi/plugin/dpp/Human/js/stats.min.js
@@ -0,0 +1,6 @@
+// stats.js - http://github.com/mrdoob/stats.js
+var Stats=function(){var l=Date.now(),m=l,g=0,n=Infinity,o=0,h=0,p=Infinity,q=0,r=0,s=0,f=document.createElement("div");f.id="stats";f.addEventListener("mousedown",function(b){b.preventDefault();t(++s%2)},!1);f.style.cssText="width:80px;opacity:0.9;cursor:pointer";var a=document.createElement("div");a.id="fps";a.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#002";f.appendChild(a);var i=document.createElement("div");i.id="fpsText";i.style.cssText="color:#0ff;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";
+i.innerHTML="FPS";a.appendChild(i);var c=document.createElement("div");c.id="fpsGraph";c.style.cssText="position:relative;width:74px;height:30px;background-color:#0ff";for(a.appendChild(c);74>c.children.length;){var j=document.createElement("span");j.style.cssText="width:1px;height:30px;float:left;background-color:#113";c.appendChild(j)}var d=document.createElement("div");d.id="ms";d.style.cssText="padding:0 0 3px 3px;text-align:left;background-color:#020;display:none";f.appendChild(d);var k=document.createElement("div");
+k.id="msText";k.style.cssText="color:#0f0;font-family:Helvetica,Arial,sans-serif;font-size:9px;font-weight:bold;line-height:15px";k.innerHTML="MS";d.appendChild(k);var e=document.createElement("div");e.id="msGraph";e.style.cssText="position:relative;width:74px;height:30px;background-color:#0f0";for(d.appendChild(e);74>e.children.length;)j=document.createElement("span"),j.style.cssText="width:1px;height:30px;float:left;background-color:#131",e.appendChild(j);var t=function(b){s=b;switch(s){case 0:a.style.display=
+"block";d.style.display="none";break;case 1:a.style.display="none",d.style.display="block"}};return{REVISION:11,domElement:f,setMode:t,begin:function(){l=Date.now()},end:function(){var b=Date.now();g=b-l;n=Math.min(n,g);o=Math.max(o,g);k.textContent=g+" MS ("+n+"-"+o+")";var a=Math.min(30,30-30*(g/200));e.appendChild(e.firstChild).style.height=a+"px";r++;b>m+1E3&&(h=Math.round(1E3*r/(b-m)),p=Math.min(p,h),q=Math.max(q,h),i.textContent=h+" FPS ("+p+"-"+q+")",a=Math.min(30,30-30*(h/100)),c.appendChild(c.firstChild).style.height=
+a+"px",m=b,r=0);return b},update:function(){l=this.end()}}};
diff --git a/papi/plugin/dpp/Human/js/three.js b/papi/plugin/dpp/Human/js/three.js
new file mode 100755
index 00000000..b6c74723
--- /dev/null
+++ b/papi/plugin/dpp/Human/js/three.js
@@ -0,0 +1,36949 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author Larry Battle / http://bateru.com/news
+ * @author bhouston / http://exocortex.com
+ */
+
+var THREE = THREE || { REVISION: '61' };
+
+self.console = self.console || {
+
+ info: function () {},
+ log: function () {},
+ debug: function () {},
+ warn: function () {},
+ error: function () {}
+
+};
+
+String.prototype.trim = String.prototype.trim || function () {
+
+ return this.replace( /^\s+|\s+$/g, '' );
+
+};
+
+// based on https://github.com/documentcloud/underscore/blob/bf657be243a075b5e72acc8a83e6f12a564d8f55/underscore.js#L767
+THREE.extend = function ( obj, source ) {
+
+ // ECMAScript5 compatibility based on: http://www.nczonline.net/blog/2012/12/11/are-your-mixins-ecmascript-5-compatible/
+ if ( Object.keys ) {
+
+ var keys = Object.keys( source );
+
+ for (var i = 0, il = keys.length; i < il; i++) {
+
+ var prop = keys[i];
+ Object.defineProperty( obj, prop, Object.getOwnPropertyDescriptor( source, prop ) );
+
+ }
+
+ } else {
+
+ var safeHasOwnProperty = {}.hasOwnProperty;
+
+ for ( var prop in source ) {
+
+ if ( safeHasOwnProperty.call( source, prop ) ) {
+
+ obj[prop] = source[prop];
+
+ }
+
+ }
+
+ }
+
+ return obj;
+
+};
+
+// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
+
+// requestAnimationFrame polyfill by Erik Möller
+// fixes from Paul Irish and Tino Zijdel
+// using 'self' instead of 'window' for compatibility with both NodeJS and IE10.
+( function () {
+
+ var lastTime = 0;
+ var vendors = [ 'ms', 'moz', 'webkit', 'o' ];
+
+ for ( var x = 0; x < vendors.length && !self.requestAnimationFrame; ++ x ) {
+
+ self.requestAnimationFrame = self[ vendors[ x ] + 'RequestAnimationFrame' ];
+ self.cancelAnimationFrame = self[ vendors[ x ] + 'CancelAnimationFrame' ] || self[ vendors[ x ] + 'CancelRequestAnimationFrame' ];
+
+ }
+
+ if ( self.requestAnimationFrame === undefined && self['setTimeout'] !== undefined ) {
+
+ self.requestAnimationFrame = function ( callback ) {
+
+ var currTime = Date.now(), timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
+ var id = self.setTimeout( function() { callback( currTime + timeToCall ); }, timeToCall );
+ lastTime = currTime + timeToCall;
+ return id;
+
+ };
+
+ }
+
+ if( self.cancelAnimationFrame === undefined && self['clearTimeout'] !== undefined ) {
+
+ self.cancelAnimationFrame = function ( id ) { self.clearTimeout( id ) };
+
+ }
+
+}() );
+
+// GL STATE CONSTANTS
+
+THREE.CullFaceNone = 0;
+THREE.CullFaceBack = 1;
+THREE.CullFaceFront = 2;
+THREE.CullFaceFrontBack = 3;
+
+THREE.FrontFaceDirectionCW = 0;
+THREE.FrontFaceDirectionCCW = 1;
+
+// SHADOWING TYPES
+
+THREE.BasicShadowMap = 0;
+THREE.PCFShadowMap = 1;
+THREE.PCFSoftShadowMap = 2;
+
+// MATERIAL CONSTANTS
+
+// side
+
+THREE.FrontSide = 0;
+THREE.BackSide = 1;
+THREE.DoubleSide = 2;
+
+// shading
+
+THREE.NoShading = 0;
+THREE.FlatShading = 1;
+THREE.SmoothShading = 2;
+
+// colors
+
+THREE.NoColors = 0;
+THREE.FaceColors = 1;
+THREE.VertexColors = 2;
+
+// blending modes
+
+THREE.NoBlending = 0;
+THREE.NormalBlending = 1;
+THREE.AdditiveBlending = 2;
+THREE.SubtractiveBlending = 3;
+THREE.MultiplyBlending = 4;
+THREE.CustomBlending = 5;
+
+// custom blending equations
+// (numbers start from 100 not to clash with other
+// mappings to OpenGL constants defined in Texture.js)
+
+THREE.AddEquation = 100;
+THREE.SubtractEquation = 101;
+THREE.ReverseSubtractEquation = 102;
+
+// custom blending destination factors
+
+THREE.ZeroFactor = 200;
+THREE.OneFactor = 201;
+THREE.SrcColorFactor = 202;
+THREE.OneMinusSrcColorFactor = 203;
+THREE.SrcAlphaFactor = 204;
+THREE.OneMinusSrcAlphaFactor = 205;
+THREE.DstAlphaFactor = 206;
+THREE.OneMinusDstAlphaFactor = 207;
+
+// custom blending source factors
+
+//THREE.ZeroFactor = 200;
+//THREE.OneFactor = 201;
+//THREE.SrcAlphaFactor = 204;
+//THREE.OneMinusSrcAlphaFactor = 205;
+//THREE.DstAlphaFactor = 206;
+//THREE.OneMinusDstAlphaFactor = 207;
+THREE.DstColorFactor = 208;
+THREE.OneMinusDstColorFactor = 209;
+THREE.SrcAlphaSaturateFactor = 210;
+
+
+// TEXTURE CONSTANTS
+
+THREE.MultiplyOperation = 0;
+THREE.MixOperation = 1;
+THREE.AddOperation = 2;
+
+// Mapping modes
+
+THREE.UVMapping = function () {};
+
+THREE.CubeReflectionMapping = function () {};
+THREE.CubeRefractionMapping = function () {};
+
+THREE.SphericalReflectionMapping = function () {};
+THREE.SphericalRefractionMapping = function () {};
+
+// Wrapping modes
+
+THREE.RepeatWrapping = 1000;
+THREE.ClampToEdgeWrapping = 1001;
+THREE.MirroredRepeatWrapping = 1002;
+
+// Filters
+
+THREE.NearestFilter = 1003;
+THREE.NearestMipMapNearestFilter = 1004;
+THREE.NearestMipMapLinearFilter = 1005;
+THREE.LinearFilter = 1006;
+THREE.LinearMipMapNearestFilter = 1007;
+THREE.LinearMipMapLinearFilter = 1008;
+
+// Data types
+
+THREE.UnsignedByteType = 1009;
+THREE.ByteType = 1010;
+THREE.ShortType = 1011;
+THREE.UnsignedShortType = 1012;
+THREE.IntType = 1013;
+THREE.UnsignedIntType = 1014;
+THREE.FloatType = 1015;
+
+// Pixel types
+
+//THREE.UnsignedByteType = 1009;
+THREE.UnsignedShort4444Type = 1016;
+THREE.UnsignedShort5551Type = 1017;
+THREE.UnsignedShort565Type = 1018;
+
+// Pixel formats
+
+THREE.AlphaFormat = 1019;
+THREE.RGBFormat = 1020;
+THREE.RGBAFormat = 1021;
+THREE.LuminanceFormat = 1022;
+THREE.LuminanceAlphaFormat = 1023;
+
+// Compressed texture formats
+
+THREE.RGB_S3TC_DXT1_Format = 2001;
+THREE.RGBA_S3TC_DXT1_Format = 2002;
+THREE.RGBA_S3TC_DXT3_Format = 2003;
+THREE.RGBA_S3TC_DXT5_Format = 2004;
+
+/*
+// Potential future PVRTC compressed texture formats
+THREE.RGB_PVRTC_4BPPV1_Format = 2100;
+THREE.RGB_PVRTC_2BPPV1_Format = 2101;
+THREE.RGBA_PVRTC_4BPPV1_Format = 2102;
+THREE.RGBA_PVRTC_2BPPV1_Format = 2103;
+*/
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Color = function ( value ) {
+
+ if ( value !== undefined ) this.set( value );
+
+ return this;
+
+};
+
+THREE.Color.prototype = {
+
+ constructor: THREE.Color,
+
+ r: 1, g: 1, b: 1,
+
+ set: function ( value ) {
+
+ if ( value instanceof THREE.Color ) {
+
+ this.copy( value );
+
+ } else if ( typeof value === 'number' ) {
+
+ this.setHex( value );
+
+ } else if ( typeof value === 'string' ) {
+
+ this.setStyle( value );
+
+ }
+
+ return this;
+
+ },
+
+ setHex: function ( hex ) {
+
+ hex = Math.floor( hex );
+
+ this.r = ( hex >> 16 & 255 ) / 255;
+ this.g = ( hex >> 8 & 255 ) / 255;
+ this.b = ( hex & 255 ) / 255;
+
+ return this;
+
+ },
+
+ setRGB: function ( r, g, b ) {
+
+ this.r = r;
+ this.g = g;
+ this.b = b;
+
+ return this;
+
+ },
+
+ setHSL: function ( h, s, l ) {
+
+ // h,s,l ranges are in 0.0 - 1.0
+
+ if ( s === 0 ) {
+
+ this.r = this.g = this.b = l;
+
+ } else {
+
+ var hue2rgb = function ( p, q, t ) {
+
+ if ( t < 0 ) t += 1;
+ if ( t > 1 ) t -= 1;
+ if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;
+ if ( t < 1 / 2 ) return q;
+ if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );
+ return p;
+
+ };
+
+ var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );
+ var q = ( 2 * l ) - p;
+
+ this.r = hue2rgb( q, p, h + 1 / 3 );
+ this.g = hue2rgb( q, p, h );
+ this.b = hue2rgb( q, p, h - 1 / 3 );
+
+ }
+
+ return this;
+
+ },
+
+ setStyle: function ( style ) {
+
+ // rgb(255,0,0)
+
+ if ( /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test( style ) ) {
+
+ var color = /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec( style );
+
+ this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;
+ this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;
+ this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;
+
+ return this;
+
+ }
+
+ // rgb(100%,0%,0%)
+
+ if ( /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test( style ) ) {
+
+ var color = /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.exec( style );
+
+ this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;
+ this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;
+ this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;
+
+ return this;
+
+ }
+
+ // #ff0000
+
+ if ( /^\#([0-9a-f]{6})$/i.test( style ) ) {
+
+ var color = /^\#([0-9a-f]{6})$/i.exec( style );
+
+ this.setHex( parseInt( color[ 1 ], 16 ) );
+
+ return this;
+
+ }
+
+ // #f00
+
+ if ( /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test( style ) ) {
+
+ var color = /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec( style );
+
+ this.setHex( parseInt( color[ 1 ] + color[ 1 ] + color[ 2 ] + color[ 2 ] + color[ 3 ] + color[ 3 ], 16 ) );
+
+ return this;
+
+ }
+
+ // red
+
+ if ( /^(\w+)$/i.test( style ) ) {
+
+ this.setHex( THREE.ColorKeywords[ style ] );
+
+ return this;
+
+ }
+
+
+ },
+
+ copy: function ( color ) {
+
+ this.r = color.r;
+ this.g = color.g;
+ this.b = color.b;
+
+ return this;
+
+ },
+
+ copyGammaToLinear: function ( color ) {
+
+ this.r = color.r * color.r;
+ this.g = color.g * color.g;
+ this.b = color.b * color.b;
+
+ return this;
+
+ },
+
+ copyLinearToGamma: function ( color ) {
+
+ this.r = Math.sqrt( color.r );
+ this.g = Math.sqrt( color.g );
+ this.b = Math.sqrt( color.b );
+
+ return this;
+
+ },
+
+ convertGammaToLinear: function () {
+
+ var r = this.r, g = this.g, b = this.b;
+
+ this.r = r * r;
+ this.g = g * g;
+ this.b = b * b;
+
+ return this;
+
+ },
+
+ convertLinearToGamma: function () {
+
+ this.r = Math.sqrt( this.r );
+ this.g = Math.sqrt( this.g );
+ this.b = Math.sqrt( this.b );
+
+ return this;
+
+ },
+
+ getHex: function () {
+
+ return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;
+
+ },
+
+ getHexString: function () {
+
+ return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
+
+ },
+
+ getHSL: function () {
+
+ var hsl = { h: 0, s: 0, l: 0 };
+
+ return function () {
+
+ // h,s,l ranges are in 0.0 - 1.0
+
+ var r = this.r, g = this.g, b = this.b;
+
+ var max = Math.max( r, g, b );
+ var min = Math.min( r, g, b );
+
+ var hue, saturation;
+ var lightness = ( min + max ) / 2.0;
+
+ if ( min === max ) {
+
+ hue = 0;
+ saturation = 0;
+
+ } else {
+
+ var delta = max - min;
+
+ saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );
+
+ switch ( max ) {
+
+ case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;
+ case g: hue = ( b - r ) / delta + 2; break;
+ case b: hue = ( r - g ) / delta + 4; break;
+
+ }
+
+ hue /= 6;
+
+ }
+
+ hsl.h = hue;
+ hsl.s = saturation;
+ hsl.l = lightness;
+
+ return hsl;
+
+ };
+
+ }(),
+
+ getStyle: function () {
+
+ return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';
+
+ },
+
+ offsetHSL: function ( h, s, l ) {
+
+ var hsl = this.getHSL();
+
+ hsl.h += h; hsl.s += s; hsl.l += l;
+
+ this.setHSL( hsl.h, hsl.s, hsl.l );
+
+ return this;
+
+ },
+
+ add: function ( color ) {
+
+ this.r += color.r;
+ this.g += color.g;
+ this.b += color.b;
+
+ return this;
+
+ },
+
+ addColors: function ( color1, color2 ) {
+
+ this.r = color1.r + color2.r;
+ this.g = color1.g + color2.g;
+ this.b = color1.b + color2.b;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.r += s;
+ this.g += s;
+ this.b += s;
+
+ return this;
+
+ },
+
+ multiply: function ( color ) {
+
+ this.r *= color.r;
+ this.g *= color.g;
+ this.b *= color.b;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( s ) {
+
+ this.r *= s;
+ this.g *= s;
+ this.b *= s;
+
+ return this;
+
+ },
+
+ lerp: function ( color, alpha ) {
+
+ this.r += ( color.r - this.r ) * alpha;
+ this.g += ( color.g - this.g ) * alpha;
+ this.b += ( color.b - this.b ) * alpha;
+
+ return this;
+
+ },
+
+ equals: function ( c ) {
+
+ return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.r = array[ 0 ];
+ this.g = array[ 1 ];
+ this.b = array[ 2 ];
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this.r, this.g, this.b ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Color().setRGB( this.r, this.g, this.b );
+
+ }
+
+};
+
+THREE.ColorKeywords = { "aliceblue": 0xF0F8FF, "antiquewhite": 0xFAEBD7, "aqua": 0x00FFFF, "aquamarine": 0x7FFFD4, "azure": 0xF0FFFF,
+"beige": 0xF5F5DC, "bisque": 0xFFE4C4, "black": 0x000000, "blanchedalmond": 0xFFEBCD, "blue": 0x0000FF, "blueviolet": 0x8A2BE2,
+"brown": 0xA52A2A, "burlywood": 0xDEB887, "cadetblue": 0x5F9EA0, "chartreuse": 0x7FFF00, "chocolate": 0xD2691E, "coral": 0xFF7F50,
+"cornflowerblue": 0x6495ED, "cornsilk": 0xFFF8DC, "crimson": 0xDC143C, "cyan": 0x00FFFF, "darkblue": 0x00008B, "darkcyan": 0x008B8B,
+"darkgoldenrod": 0xB8860B, "darkgray": 0xA9A9A9, "darkgreen": 0x006400, "darkgrey": 0xA9A9A9, "darkkhaki": 0xBDB76B, "darkmagenta": 0x8B008B,
+"darkolivegreen": 0x556B2F, "darkorange": 0xFF8C00, "darkorchid": 0x9932CC, "darkred": 0x8B0000, "darksalmon": 0xE9967A, "darkseagreen": 0x8FBC8F,
+"darkslateblue": 0x483D8B, "darkslategray": 0x2F4F4F, "darkslategrey": 0x2F4F4F, "darkturquoise": 0x00CED1, "darkviolet": 0x9400D3,
+"deeppink": 0xFF1493, "deepskyblue": 0x00BFFF, "dimgray": 0x696969, "dimgrey": 0x696969, "dodgerblue": 0x1E90FF, "firebrick": 0xB22222,
+"floralwhite": 0xFFFAF0, "forestgreen": 0x228B22, "fuchsia": 0xFF00FF, "gainsboro": 0xDCDCDC, "ghostwhite": 0xF8F8FF, "gold": 0xFFD700,
+"goldenrod": 0xDAA520, "gray": 0x808080, "green": 0x008000, "greenyellow": 0xADFF2F, "grey": 0x808080, "honeydew": 0xF0FFF0, "hotpink": 0xFF69B4,
+"indianred": 0xCD5C5C, "indigo": 0x4B0082, "ivory": 0xFFFFF0, "khaki": 0xF0E68C, "lavender": 0xE6E6FA, "lavenderblush": 0xFFF0F5, "lawngreen": 0x7CFC00,
+"lemonchiffon": 0xFFFACD, "lightblue": 0xADD8E6, "lightcoral": 0xF08080, "lightcyan": 0xE0FFFF, "lightgoldenrodyellow": 0xFAFAD2, "lightgray": 0xD3D3D3,
+"lightgreen": 0x90EE90, "lightgrey": 0xD3D3D3, "lightpink": 0xFFB6C1, "lightsalmon": 0xFFA07A, "lightseagreen": 0x20B2AA, "lightskyblue": 0x87CEFA,
+"lightslategray": 0x778899, "lightslategrey": 0x778899, "lightsteelblue": 0xB0C4DE, "lightyellow": 0xFFFFE0, "lime": 0x00FF00, "limegreen": 0x32CD32,
+"linen": 0xFAF0E6, "magenta": 0xFF00FF, "maroon": 0x800000, "mediumaquamarine": 0x66CDAA, "mediumblue": 0x0000CD, "mediumorchid": 0xBA55D3,
+"mediumpurple": 0x9370DB, "mediumseagreen": 0x3CB371, "mediumslateblue": 0x7B68EE, "mediumspringgreen": 0x00FA9A, "mediumturquoise": 0x48D1CC,
+"mediumvioletred": 0xC71585, "midnightblue": 0x191970, "mintcream": 0xF5FFFA, "mistyrose": 0xFFE4E1, "moccasin": 0xFFE4B5, "navajowhite": 0xFFDEAD,
+"navy": 0x000080, "oldlace": 0xFDF5E6, "olive": 0x808000, "olivedrab": 0x6B8E23, "orange": 0xFFA500, "orangered": 0xFF4500, "orchid": 0xDA70D6,
+"palegoldenrod": 0xEEE8AA, "palegreen": 0x98FB98, "paleturquoise": 0xAFEEEE, "palevioletred": 0xDB7093, "papayawhip": 0xFFEFD5, "peachpuff": 0xFFDAB9,
+"peru": 0xCD853F, "pink": 0xFFC0CB, "plum": 0xDDA0DD, "powderblue": 0xB0E0E6, "purple": 0x800080, "red": 0xFF0000, "rosybrown": 0xBC8F8F,
+"royalblue": 0x4169E1, "saddlebrown": 0x8B4513, "salmon": 0xFA8072, "sandybrown": 0xF4A460, "seagreen": 0x2E8B57, "seashell": 0xFFF5EE,
+"sienna": 0xA0522D, "silver": 0xC0C0C0, "skyblue": 0x87CEEB, "slateblue": 0x6A5ACD, "slategray": 0x708090, "slategrey": 0x708090, "snow": 0xFFFAFA,
+"springgreen": 0x00FF7F, "steelblue": 0x4682B4, "tan": 0xD2B48C, "teal": 0x008080, "thistle": 0xD8BFD8, "tomato": 0xFF6347, "turquoise": 0x40E0D0,
+"violet": 0xEE82EE, "wheat": 0xF5DEB3, "white": 0xFFFFFF, "whitesmoke": 0xF5F5F5, "yellow": 0xFFFF00, "yellowgreen": 0x9ACD32 };
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Quaternion = function ( x, y, z, w ) {
+
+ this._x = x || 0;
+ this._y = y || 0;
+ this._z = z || 0;
+ this._w = ( w !== undefined ) ? w : 1;
+
+};
+
+THREE.Quaternion.prototype = {
+
+ constructor: THREE.Quaternion,
+
+ _x: 0,_y: 0, _z: 0, _w: 0,
+
+ _euler: undefined,
+
+ _updateEuler: function ( callback ) {
+
+ if ( this._euler !== undefined ) {
+
+ this._euler.setFromQuaternion( this, undefined, false );
+
+ }
+
+ },
+
+ get x () {
+
+ return this._x;
+
+ },
+
+ set x ( value ) {
+
+ this._x = value;
+ this._updateEuler();
+
+ },
+
+ get y () {
+
+ return this._y;
+
+ },
+
+ set y ( value ) {
+
+ this._y = value;
+ this._updateEuler();
+
+ },
+
+ get z () {
+
+ return this._z;
+
+ },
+
+ set z ( value ) {
+
+ this._z = value;
+ this._updateEuler();
+
+ },
+
+ get w () {
+
+ return this._w;
+
+ },
+
+ set w ( value ) {
+
+ this._w = value;
+ this._updateEuler();
+
+ },
+
+ set: function ( x, y, z, w ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._w = w;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ copy: function ( quaternion ) {
+
+ this._x = quaternion._x;
+ this._y = quaternion._y;
+ this._z = quaternion._z;
+ this._w = quaternion._w;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromEuler: function ( euler, update ) {
+
+ if ( euler instanceof THREE.Euler === false ) {
+
+ throw new Error( 'ERROR: Quaternion\'s .setFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' );
+ }
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/
+ // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
+ // content/SpinCalc.m
+
+ var c1 = Math.cos( euler._x / 2 );
+ var c2 = Math.cos( euler._y / 2 );
+ var c3 = Math.cos( euler._z / 2 );
+ var s1 = Math.sin( euler._x / 2 );
+ var s2 = Math.sin( euler._y / 2 );
+ var s3 = Math.sin( euler._z / 2 );
+
+ if ( euler.order === 'XYZ' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( euler.order === 'YXZ' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ } else if ( euler.order === 'ZXY' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( euler.order === 'ZYX' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ } else if ( euler.order === 'YZX' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( euler.order === 'XZY' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ }
+
+ if ( update !== false ) this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromAxisAngle: function ( axis, angle ) {
+
+ // from http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
+ // axis have to be normalized
+
+ var halfAngle = angle / 2, s = Math.sin( halfAngle );
+
+ this._x = axis.x * s;
+ this._y = axis.y * s;
+ this._z = axis.z * s;
+ this._w = Math.cos( halfAngle );
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromRotationMatrix: function ( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ var te = m.elements,
+
+ m11 = te[0], m12 = te[4], m13 = te[8],
+ m21 = te[1], m22 = te[5], m23 = te[9],
+ m31 = te[2], m32 = te[6], m33 = te[10],
+
+ trace = m11 + m22 + m33,
+ s;
+
+ if ( trace > 0 ) {
+
+ s = 0.5 / Math.sqrt( trace + 1.0 );
+
+ this._w = 0.25 / s;
+ this._x = ( m32 - m23 ) * s;
+ this._y = ( m13 - m31 ) * s;
+ this._z = ( m21 - m12 ) * s;
+
+ } else if ( m11 > m22 && m11 > m33 ) {
+
+ s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
+
+ this._w = (m32 - m23 ) / s;
+ this._x = 0.25 * s;
+ this._y = (m12 + m21 ) / s;
+ this._z = (m13 + m31 ) / s;
+
+ } else if ( m22 > m33 ) {
+
+ s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
+
+ this._w = (m13 - m31 ) / s;
+ this._x = (m12 + m21 ) / s;
+ this._y = 0.25 * s;
+ this._z = (m23 + m32 ) / s;
+
+ } else {
+
+ s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
+
+ this._w = ( m21 - m12 ) / s;
+ this._x = ( m13 + m31 ) / s;
+ this._y = ( m23 + m32 ) / s;
+ this._z = 0.25 * s;
+
+ }
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ inverse: function () {
+
+ this.conjugate().normalize();
+
+ return this;
+
+ },
+
+ conjugate: function () {
+
+ this._x *= -1;
+ this._y *= -1;
+ this._z *= -1;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ lengthSq: function () {
+
+ return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
+
+ },
+
+ normalize: function () {
+
+ var l = this.length();
+
+ if ( l === 0 ) {
+
+ this._x = 0;
+ this._y = 0;
+ this._z = 0;
+ this._w = 1;
+
+ } else {
+
+ l = 1 / l;
+
+ this._x = this._x * l;
+ this._y = this._y * l;
+ this._z = this._z * l;
+ this._w = this._w * l;
+
+ }
+
+ return this;
+
+ },
+
+ multiply: function ( q, p ) {
+
+ if ( p !== undefined ) {
+
+ console.warn( 'DEPRECATED: Quaternion\'s .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
+ return this.multiplyQuaternions( q, p );
+
+ }
+
+ return this.multiplyQuaternions( this, q );
+
+ },
+
+ multiplyQuaternions: function ( a, b ) {
+
+ // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
+
+ var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
+ var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
+
+ this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
+ this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
+ this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
+ this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Quaternion\'s .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
+ return vector.applyQuaternion( this );
+
+ },
+
+ slerp: function ( qb, t ) {
+
+ var x = this._x, y = this._y, z = this._z, w = this._w;
+
+ // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
+
+ var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
+
+ if ( cosHalfTheta < 0 ) {
+
+ this._w = -qb._w;
+ this._x = -qb._x;
+ this._y = -qb._y;
+ this._z = -qb._z;
+
+ cosHalfTheta = -cosHalfTheta;
+
+ } else {
+
+ this.copy( qb );
+
+ }
+
+ if ( cosHalfTheta >= 1.0 ) {
+
+ this._w = w;
+ this._x = x;
+ this._y = y;
+ this._z = z;
+
+ return this;
+
+ }
+
+ var halfTheta = Math.acos( cosHalfTheta );
+ var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );
+
+ if ( Math.abs( sinHalfTheta ) < 0.001 ) {
+
+ this._w = 0.5 * ( w + this._w );
+ this._x = 0.5 * ( x + this._x );
+ this._y = 0.5 * ( y + this._y );
+ this._z = 0.5 * ( z + this._z );
+
+ return this;
+
+ }
+
+ var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
+ ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
+
+ this._w = ( w * ratioA + this._w * ratioB );
+ this._x = ( x * ratioA + this._x * ratioB );
+ this._y = ( y * ratioA + this._y * ratioB );
+ this._z = ( z * ratioA + this._z * ratioB );
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ equals: function ( quaternion ) {
+
+ return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this._x = array[ 0 ];
+ this._y = array[ 1 ];
+ this._z = array[ 2 ];
+ this._w = array[ 3 ];
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this._x, this._y, this._z, this._w ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Quaternion( this._x, this._y, this._z, this._w );
+
+ }
+
+};
+
+THREE.Quaternion.slerp = function ( qa, qb, qm, t ) {
+
+ return qm.copy( qa ).slerp( qb, t );
+
+}
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author philogb / http://blog.thejit.org/
+ * @author egraether / http://egraether.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ */
+
+THREE.Vector2 = function ( x, y ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+
+};
+
+THREE.Vector2.prototype = {
+
+ constructor: THREE.Vector2,
+
+ set: function ( x, y ) {
+
+ this.x = x;
+ this.y = y;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector2\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector2\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( s ) {
+
+ this.x *= s;
+ this.y *= s;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ if ( scalar !== 0 ) {
+
+ var invScalar = 1 / scalar;
+
+ this.x *= invScalar;
+ this.y *= invScalar;
+
+ } else {
+
+ this.x = 0;
+ this.y = 0;
+
+ }
+
+ return this;
+
+ },
+
+ min: function ( v ) {
+
+ if ( this.x > v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y > v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ if ( this.x < v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y < v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+ if ( this.x < min.x ) {
+
+ this.x = min.x;
+
+ } else if ( this.x > max.x ) {
+
+ this.x = max.x;
+
+ }
+
+ if ( this.y < min.y ) {
+
+ this.y = min.y;
+
+ } else if ( this.y > max.y ) {
+
+ this.y = max.y;
+
+ }
+
+ return this;
+
+ },
+
+ negate: function() {
+
+ return this.multiplyScalar( - 1 );
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y;
+
+ },
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() );
+
+ },
+
+ distanceTo: function ( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ },
+
+ distanceToSquared: function ( v ) {
+
+ var dx = this.x - v.x, dy = this.y - v.y;
+ return dx * dx + dy * dy;
+
+ },
+
+ setLength: function ( l ) {
+
+ var oldLength = this.length();
+
+ if ( oldLength !== 0 && l !== oldLength ) {
+
+ this.multiplyScalar( l / oldLength );
+ }
+
+ return this;
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+
+ return this;
+
+ },
+
+ equals: function( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.x = array[ 0 ];
+ this.y = array[ 1 ];
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this.x, this.y ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Vector2( this.x, this.y );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author *kile / http://kile.stravaganza.org/
+ * @author philogb / http://blog.thejit.org/
+ * @author mikael emtinger / http://gomo.se/
+ * @author egraether / http://egraether.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Vector3 = function ( x, y, z ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+ this.z = z || 0;
+
+};
+
+THREE.Vector3.prototype = {
+
+ constructor: THREE.Vector3,
+
+ set: function ( x, y, z ) {
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+ setZ: function ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ },
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ case 2: return this.z;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector3\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector3\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+
+ return this;
+
+ },
+
+ multiply: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector3\'s .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );
+ return this.multiplyVectors( v, w );
+
+ }
+
+ this.x *= v.x;
+ this.y *= v.y;
+ this.z *= v.z;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+
+ return this;
+
+ },
+
+ multiplyVectors: function ( a, b ) {
+
+ this.x = a.x * b.x;
+ this.y = a.y * b.y;
+ this.z = a.z * b.z;
+
+ return this;
+
+ },
+
+ applyMatrix3: function ( m ) {
+
+ var x = this.x;
+ var y = this.y;
+ var z = this.z;
+
+ var e = m.elements;
+
+ this.x = e[0] * x + e[3] * y + e[6] * z;
+ this.y = e[1] * x + e[4] * y + e[7] * z;
+ this.z = e[2] * x + e[5] * y + e[8] * z;
+
+ return this;
+
+ },
+
+ applyMatrix4: function ( m ) {
+
+ // input: THREE.Matrix4 affine matrix
+
+ var x = this.x, y = this.y, z = this.z;
+
+ var e = m.elements;
+
+ this.x = e[0] * x + e[4] * y + e[8] * z + e[12];
+ this.y = e[1] * x + e[5] * y + e[9] * z + e[13];
+ this.z = e[2] * x + e[6] * y + e[10] * z + e[14];
+
+ return this;
+
+ },
+
+ applyProjection: function ( m ) {
+
+ // input: THREE.Matrix4 projection matrix
+
+ var x = this.x, y = this.y, z = this.z;
+
+ var e = m.elements;
+ var d = 1 / ( e[3] * x + e[7] * y + e[11] * z + e[15] ); // perspective divide
+
+ this.x = ( e[0] * x + e[4] * y + e[8] * z + e[12] ) * d;
+ this.y = ( e[1] * x + e[5] * y + e[9] * z + e[13] ) * d;
+ this.z = ( e[2] * x + e[6] * y + e[10] * z + e[14] ) * d;
+
+ return this;
+
+ },
+
+ applyQuaternion: function ( q ) {
+
+ var x = this.x;
+ var y = this.y;
+ var z = this.z;
+
+ var qx = q.x;
+ var qy = q.y;
+ var qz = q.z;
+ var qw = q.w;
+
+ // calculate quat * vector
+
+ var ix = qw * x + qy * z - qz * y;
+ var iy = qw * y + qz * x - qx * z;
+ var iz = qw * z + qx * y - qy * x;
+ var iw = -qx * x - qy * y - qz * z;
+
+ // calculate result * inverse quat
+
+ this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+ this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+ this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+
+ return this;
+
+ },
+
+ transformDirection: function ( m ) {
+
+ // input: THREE.Matrix4 affine matrix
+ // vector interpreted as a direction
+
+ var x = this.x, y = this.y, z = this.z;
+
+ var e = m.elements;
+
+ this.x = e[0] * x + e[4] * y + e[8] * z;
+ this.y = e[1] * x + e[5] * y + e[9] * z;
+ this.z = e[2] * x + e[6] * y + e[10] * z;
+
+ this.normalize();
+
+ return this;
+
+ },
+
+ divide: function ( v ) {
+
+ this.x /= v.x;
+ this.y /= v.y;
+ this.z /= v.z;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ if ( scalar !== 0 ) {
+
+ var invScalar = 1 / scalar;
+
+ this.x *= invScalar;
+ this.y *= invScalar;
+ this.z *= invScalar;
+
+ } else {
+
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+
+ }
+
+ return this;
+
+ },
+
+ min: function ( v ) {
+
+ if ( this.x > v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y > v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ if ( this.z > v.z ) {
+
+ this.z = v.z;
+
+ }
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ if ( this.x < v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y < v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ if ( this.z < v.z ) {
+
+ this.z = v.z;
+
+ }
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+ if ( this.x < min.x ) {
+
+ this.x = min.x;
+
+ } else if ( this.x > max.x ) {
+
+ this.x = max.x;
+
+ }
+
+ if ( this.y < min.y ) {
+
+ this.y = min.y;
+
+ } else if ( this.y > max.y ) {
+
+ this.y = max.y;
+
+ }
+
+ if ( this.z < min.z ) {
+
+ this.z = min.z;
+
+ } else if ( this.z > max.z ) {
+
+ this.z = max.z;
+
+ }
+
+ return this;
+
+ },
+
+ negate: function () {
+
+ return this.multiplyScalar( - 1 );
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+
+ },
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+ },
+
+ lengthManhattan: function () {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() );
+
+ },
+
+ setLength: function ( l ) {
+
+ var oldLength = this.length();
+
+ if ( oldLength !== 0 && l !== oldLength ) {
+
+ this.multiplyScalar( l / oldLength );
+ }
+
+ return this;
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+
+ return this;
+
+ },
+
+ cross: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector3\'s .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
+ return this.crossVectors( v, w );
+
+ }
+
+ var x = this.x, y = this.y, z = this.z;
+
+ this.x = y * v.z - z * v.y;
+ this.y = z * v.x - x * v.z;
+ this.z = x * v.y - y * v.x;
+
+ return this;
+
+ },
+
+ crossVectors: function ( a, b ) {
+
+ var ax = a.x, ay = a.y, az = a.z;
+ var bx = b.x, by = b.y, bz = b.z;
+
+ this.x = ay * bz - az * by;
+ this.y = az * bx - ax * bz;
+ this.z = ax * by - ay * bx;
+
+ return this;
+
+ },
+
+ angleTo: function ( v ) {
+
+ var theta = this.dot( v ) / ( this.length() * v.length() );
+
+ // clamp, to handle numerical problems
+
+ return Math.acos( THREE.Math.clamp( theta, -1, 1 ) );
+
+ },
+
+ distanceTo: function ( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ },
+
+ distanceToSquared: function ( v ) {
+
+ var dx = this.x - v.x;
+ var dy = this.y - v.y;
+ var dz = this.z - v.z;
+
+ return dx * dx + dy * dy + dz * dz;
+
+ },
+
+ setEulerFromRotationMatrix: function ( m, order ) {
+
+ console.error( "REMOVED: Vector3\'s setEulerFromRotationMatrix has been removed in favor of Euler.setFromRotationMatrix(), please update your code.");
+
+ },
+
+ setEulerFromQuaternion: function ( q, order ) {
+
+ console.error( "REMOVED: Vector3\'s setEulerFromQuaternion: has been removed in favor of Euler.setFromQuaternion(), please update your code.");
+
+ },
+
+ getPositionFromMatrix: function ( m ) {
+
+ this.x = m.elements[12];
+ this.y = m.elements[13];
+ this.z = m.elements[14];
+
+ return this;
+
+ },
+
+ getScaleFromMatrix: function ( m ) {
+
+ var sx = this.set( m.elements[0], m.elements[1], m.elements[2] ).length();
+ var sy = this.set( m.elements[4], m.elements[5], m.elements[6] ).length();
+ var sz = this.set( m.elements[8], m.elements[9], m.elements[10] ).length();
+
+ this.x = sx;
+ this.y = sy;
+ this.z = sz;
+
+ return this;
+ },
+
+ getColumnFromMatrix: function ( index, matrix ) {
+
+ var offset = index * 4;
+
+ var me = matrix.elements;
+
+ this.x = me[ offset ];
+ this.y = me[ offset + 1 ];
+ this.z = me[ offset + 2 ];
+
+ return this;
+
+ },
+
+ equals: function ( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.x = array[ 0 ];
+ this.y = array[ 1 ];
+ this.z = array[ 2 ];
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this.x, this.y, this.z ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Vector3( this.x, this.y, this.z );
+
+ }
+
+};
+
+THREE.extend( THREE.Vector3.prototype, {
+
+ applyEuler: function () {
+
+ var quaternion = new THREE.Quaternion();
+
+ return function ( euler ) {
+
+ if ( euler instanceof THREE.Euler === false ) {
+
+ console.error( 'ERROR: Vector3\'s .applyEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' );
+
+ }
+
+ this.applyQuaternion( quaternion.setFromEuler( euler ) );
+
+ return this;
+
+ };
+
+ }(),
+
+ applyAxisAngle: function () {
+
+ var quaternion = new THREE.Quaternion();
+
+ return function ( axis, angle ) {
+
+ this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );
+
+ return this;
+
+ };
+
+ }(),
+
+ projectOnVector: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( vector ) {
+
+ v1.copy( vector ).normalize();
+ var d = this.dot( v1 );
+ return this.copy( v1 ).multiplyScalar( d );
+
+ };
+
+ }(),
+
+ projectOnPlane: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( planeNormal ) {
+
+ v1.copy( this ).projectOnVector( planeNormal );
+
+ return this.sub( v1 );
+
+ }
+
+ }(),
+
+ reflect: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( vector ) {
+
+ v1.copy( this ).projectOnVector( vector ).multiplyScalar( 2 );
+
+ return this.subVectors( v1, this );
+
+ }
+
+ }()
+
+} );
+
+/**
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author philogb / http://blog.thejit.org/
+ * @author mikael emtinger / http://gomo.se/
+ * @author egraether / http://egraether.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Vector4 = function ( x, y, z, w ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+ this.z = z || 0;
+ this.w = ( w !== undefined ) ? w : 1;
+
+};
+
+THREE.Vector4.prototype = {
+
+ constructor: THREE.Vector4,
+
+ set: function ( x, y, z, w ) {
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+ setZ: function ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ },
+
+ setW: function ( w ) {
+
+ this.w = w;
+
+ return this;
+
+ },
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ case 3: this.w = value; break;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ case 2: return this.z;
+ case 3: return this.w;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ this.w = ( v.w !== undefined ) ? v.w : 1;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector4\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+ this.w += v.w;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+ this.w += s;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+ this.w = a.w + b.w;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector4\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+ this.w -= v.w;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+ this.w = a.w - b.w;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ this.w *= scalar;
+
+ return this;
+
+ },
+
+ applyMatrix4: function ( m ) {
+
+ var x = this.x;
+ var y = this.y;
+ var z = this.z;
+ var w = this.w;
+
+ var e = m.elements;
+
+ this.x = e[0] * x + e[4] * y + e[8] * z + e[12] * w;
+ this.y = e[1] * x + e[5] * y + e[9] * z + e[13] * w;
+ this.z = e[2] * x + e[6] * y + e[10] * z + e[14] * w;
+ this.w = e[3] * x + e[7] * y + e[11] * z + e[15] * w;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ if ( scalar !== 0 ) {
+
+ var invScalar = 1 / scalar;
+
+ this.x *= invScalar;
+ this.y *= invScalar;
+ this.z *= invScalar;
+ this.w *= invScalar;
+
+ } else {
+
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+ this.w = 1;
+
+ }
+
+ return this;
+
+ },
+
+ setAxisAngleFromQuaternion: function ( q ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
+
+ // q is assumed to be normalized
+
+ this.w = 2 * Math.acos( q.w );
+
+ var s = Math.sqrt( 1 - q.w * q.w );
+
+ if ( s < 0.0001 ) {
+
+ this.x = 1;
+ this.y = 0;
+ this.z = 0;
+
+ } else {
+
+ this.x = q.x / s;
+ this.y = q.y / s;
+ this.z = q.z / s;
+
+ }
+
+ return this;
+
+ },
+
+ setAxisAngleFromRotationMatrix: function ( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ var angle, x, y, z, // variables for result
+ epsilon = 0.01, // margin to allow for rounding errors
+ epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees
+
+ te = m.elements,
+
+ m11 = te[0], m12 = te[4], m13 = te[8],
+ m21 = te[1], m22 = te[5], m23 = te[9],
+ m31 = te[2], m32 = te[6], m33 = te[10];
+
+ if ( ( Math.abs( m12 - m21 ) < epsilon )
+ && ( Math.abs( m13 - m31 ) < epsilon )
+ && ( Math.abs( m23 - m32 ) < epsilon ) ) {
+
+ // singularity found
+ // first check for identity matrix which must have +1 for all terms
+ // in leading diagonal and zero in other terms
+
+ if ( ( Math.abs( m12 + m21 ) < epsilon2 )
+ && ( Math.abs( m13 + m31 ) < epsilon2 )
+ && ( Math.abs( m23 + m32 ) < epsilon2 )
+ && ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
+
+ // this singularity is identity matrix so angle = 0
+
+ this.set( 1, 0, 0, 0 );
+
+ return this; // zero angle, arbitrary axis
+
+ }
+
+ // otherwise this singularity is angle = 180
+
+ angle = Math.PI;
+
+ var xx = ( m11 + 1 ) / 2;
+ var yy = ( m22 + 1 ) / 2;
+ var zz = ( m33 + 1 ) / 2;
+ var xy = ( m12 + m21 ) / 4;
+ var xz = ( m13 + m31 ) / 4;
+ var yz = ( m23 + m32 ) / 4;
+
+ if ( ( xx > yy ) && ( xx > zz ) ) { // m11 is the largest diagonal term
+
+ if ( xx < epsilon ) {
+
+ x = 0;
+ y = 0.707106781;
+ z = 0.707106781;
+
+ } else {
+
+ x = Math.sqrt( xx );
+ y = xy / x;
+ z = xz / x;
+
+ }
+
+ } else if ( yy > zz ) { // m22 is the largest diagonal term
+
+ if ( yy < epsilon ) {
+
+ x = 0.707106781;
+ y = 0;
+ z = 0.707106781;
+
+ } else {
+
+ y = Math.sqrt( yy );
+ x = xy / y;
+ z = yz / y;
+
+ }
+
+ } else { // m33 is the largest diagonal term so base result on this
+
+ if ( zz < epsilon ) {
+
+ x = 0.707106781;
+ y = 0.707106781;
+ z = 0;
+
+ } else {
+
+ z = Math.sqrt( zz );
+ x = xz / z;
+ y = yz / z;
+
+ }
+
+ }
+
+ this.set( x, y, z, angle );
+
+ return this; // return 180 deg rotation
+
+ }
+
+ // as we have reached here there are no singularities so we can handle normally
+
+ var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 )
+ + ( m13 - m31 ) * ( m13 - m31 )
+ + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize
+
+ if ( Math.abs( s ) < 0.001 ) s = 1;
+
+ // prevent divide by zero, should not happen if matrix is orthogonal and should be
+ // caught by singularity test above, but I've left it in just in case
+
+ this.x = ( m32 - m23 ) / s;
+ this.y = ( m13 - m31 ) / s;
+ this.z = ( m21 - m12 ) / s;
+ this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );
+
+ return this;
+
+ },
+
+ min: function ( v ) {
+
+ if ( this.x > v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y > v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ if ( this.z > v.z ) {
+
+ this.z = v.z;
+
+ }
+
+ if ( this.w > v.w ) {
+
+ this.w = v.w;
+
+ }
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ if ( this.x < v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y < v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ if ( this.z < v.z ) {
+
+ this.z = v.z;
+
+ }
+
+ if ( this.w < v.w ) {
+
+ this.w = v.w;
+
+ }
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+ if ( this.x < min.x ) {
+
+ this.x = min.x;
+
+ } else if ( this.x > max.x ) {
+
+ this.x = max.x;
+
+ }
+
+ if ( this.y < min.y ) {
+
+ this.y = min.y;
+
+ } else if ( this.y > max.y ) {
+
+ this.y = max.y;
+
+ }
+
+ if ( this.z < min.z ) {
+
+ this.z = min.z;
+
+ } else if ( this.z > max.z ) {
+
+ this.z = max.z;
+
+ }
+
+ if ( this.w < min.w ) {
+
+ this.w = min.w;
+
+ } else if ( this.w > max.w ) {
+
+ this.w = max.w;
+
+ }
+
+ return this;
+
+ },
+
+ negate: function() {
+
+ return this.multiplyScalar( -1 );
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
+
+ },
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+
+ },
+
+ lengthManhattan: function () {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() );
+
+ },
+
+ setLength: function ( l ) {
+
+ var oldLength = this.length();
+
+ if ( oldLength !== 0 && l !== oldLength ) {
+
+ this.multiplyScalar( l / oldLength );
+
+ }
+
+ return this;
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+ this.w += ( v.w - this.w ) * alpha;
+
+ return this;
+
+ },
+
+ equals: function ( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.x = array[ 0 ];
+ this.y = array[ 1 ];
+ this.z = array[ 2 ];
+ this.w = array[ 3 ];
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this.x, this.y, this.z, this.w ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Vector4( this.x, this.y, this.z, this.w );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Euler = function ( x, y, z, order ) {
+
+ this._x = x || 0;
+ this._y = y || 0;
+ this._z = z || 0;
+ this._order = order || THREE.Euler.DefaultOrder;
+
+};
+
+THREE.Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];
+
+THREE.Euler.DefaultOrder = 'XYZ';
+
+THREE.Euler.prototype = {
+
+ constructor: THREE.Euler,
+
+ _x: 0, _y: 0, _z: 0, _order: THREE.Euler.DefaultOrder,
+
+ _quaternion: undefined,
+
+ _updateQuaternion: function () {
+
+ if ( this._quaternion !== undefined ) {
+
+ this._quaternion.setFromEuler( this, false );
+
+ }
+
+ },
+
+ get x () {
+
+ return this._x;
+
+ },
+
+ set x ( value ) {
+
+ this._x = value;
+ this._updateQuaternion();
+
+ },
+
+ get y () {
+
+ return this._y;
+
+ },
+
+ set y ( value ) {
+
+ this._y = value;
+ this._updateQuaternion();
+
+ },
+
+ get z () {
+
+ return this._z;
+
+ },
+
+ set z ( value ) {
+
+ this._z = value;
+ this._updateQuaternion();
+
+ },
+
+ get order () {
+
+ return this._order;
+
+ },
+
+ set order ( value ) {
+
+ this._order = value;
+ this._updateQuaternion();
+
+ },
+
+ set: function ( x, y, z, order ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._order = order || this._order;
+
+ this._updateQuaternion();
+
+ return this;
+
+ },
+
+ copy: function ( euler ) {
+
+ this._x = euler._x;
+ this._y = euler._y;
+ this._z = euler._z;
+ this._order = euler._order;
+
+ this._updateQuaternion();
+
+ return this;
+
+ },
+
+ setFromRotationMatrix: function ( m, order ) {
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ // clamp, to handle numerical problems
+
+ function clamp( x ) {
+
+ return Math.min( Math.max( x, -1 ), 1 );
+
+ }
+
+ var te = m.elements;
+ var m11 = te[0], m12 = te[4], m13 = te[8];
+ var m21 = te[1], m22 = te[5], m23 = te[9];
+ var m31 = te[2], m32 = te[6], m33 = te[10];
+
+ order = order || this._order;
+
+ if ( order === 'XYZ' ) {
+
+ this._y = Math.asin( clamp( m13 ) );
+
+ if ( Math.abs( m13 ) < 0.99999 ) {
+
+ this._x = Math.atan2( - m23, m33 );
+ this._z = Math.atan2( - m12, m11 );
+
+ } else {
+
+ this._x = Math.atan2( m32, m22 );
+ this._z = 0;
+
+ }
+
+ } else if ( order === 'YXZ' ) {
+
+ this._x = Math.asin( - clamp( m23 ) );
+
+ if ( Math.abs( m23 ) < 0.99999 ) {
+
+ this._y = Math.atan2( m13, m33 );
+ this._z = Math.atan2( m21, m22 );
+
+ } else {
+
+ this._y = Math.atan2( - m31, m11 );
+ this._z = 0;
+
+ }
+
+ } else if ( order === 'ZXY' ) {
+
+ this._x = Math.asin( clamp( m32 ) );
+
+ if ( Math.abs( m32 ) < 0.99999 ) {
+
+ this._y = Math.atan2( - m31, m33 );
+ this._z = Math.atan2( - m12, m22 );
+
+ } else {
+
+ this._y = 0;
+ this._z = Math.atan2( m21, m11 );
+
+ }
+
+ } else if ( order === 'ZYX' ) {
+
+ this._y = Math.asin( - clamp( m31 ) );
+
+ if ( Math.abs( m31 ) < 0.99999 ) {
+
+ this._x = Math.atan2( m32, m33 );
+ this._z = Math.atan2( m21, m11 );
+
+ } else {
+
+ this._x = 0;
+ this._z = Math.atan2( - m12, m22 );
+
+ }
+
+ } else if ( order === 'YZX' ) {
+
+ this._z = Math.asin( clamp( m21 ) );
+
+ if ( Math.abs( m21 ) < 0.99999 ) {
+
+ this._x = Math.atan2( - m23, m22 );
+ this._y = Math.atan2( - m31, m11 );
+
+ } else {
+
+ this._x = 0;
+ this._y = Math.atan2( m13, m33 );
+
+ }
+
+ } else if ( order === 'XZY' ) {
+
+ this._z = Math.asin( - clamp( m12 ) );
+
+ if ( Math.abs( m12 ) < 0.99999 ) {
+
+ this._x = Math.atan2( m32, m22 );
+ this._y = Math.atan2( m13, m11 );
+
+ } else {
+
+ this._x = Math.atan2( - m23, m33 );
+ this._y = 0;
+
+ }
+
+ } else {
+
+ console.warn( 'WARNING: Euler.setFromRotationMatrix() given unsupported order: ' + order )
+
+ }
+
+ this._order = order;
+
+ this._updateQuaternion();
+
+ return this;
+
+ },
+
+ setFromQuaternion: function ( q, order, update ) {
+
+ // q is assumed to be normalized
+
+ // clamp, to handle numerical problems
+
+ function clamp( x ) {
+
+ return Math.min( Math.max( x, -1 ), 1 );
+
+ }
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m
+
+ var sqx = q.x * q.x;
+ var sqy = q.y * q.y;
+ var sqz = q.z * q.z;
+ var sqw = q.w * q.w;
+
+ order = order || this._order;
+
+ if ( order === 'XYZ' ) {
+
+ this._x = Math.atan2( 2 * ( q.x * q.w - q.y * q.z ), ( sqw - sqx - sqy + sqz ) );
+ this._y = Math.asin( clamp( 2 * ( q.x * q.z + q.y * q.w ) ) );
+ this._z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw + sqx - sqy - sqz ) );
+
+ } else if ( order === 'YXZ' ) {
+
+ this._x = Math.asin( clamp( 2 * ( q.x * q.w - q.y * q.z ) ) );
+ this._y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw - sqx - sqy + sqz ) );
+ this._z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw - sqx + sqy - sqz ) );
+
+ } else if ( order === 'ZXY' ) {
+
+ this._x = Math.asin( clamp( 2 * ( q.x * q.w + q.y * q.z ) ) );
+ this._y = Math.atan2( 2 * ( q.y * q.w - q.z * q.x ), ( sqw - sqx - sqy + sqz ) );
+ this._z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw - sqx + sqy - sqz ) );
+
+ } else if ( order === 'ZYX' ) {
+
+ this._x = Math.atan2( 2 * ( q.x * q.w + q.z * q.y ), ( sqw - sqx - sqy + sqz ) );
+ this._y = Math.asin( clamp( 2 * ( q.y * q.w - q.x * q.z ) ) );
+ this._z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw + sqx - sqy - sqz ) );
+
+ } else if ( order === 'YZX' ) {
+
+ this._x = Math.atan2( 2 * ( q.x * q.w - q.z * q.y ), ( sqw - sqx + sqy - sqz ) );
+ this._y = Math.atan2( 2 * ( q.y * q.w - q.x * q.z ), ( sqw + sqx - sqy - sqz ) );
+ this._z = Math.asin( clamp( 2 * ( q.x * q.y + q.z * q.w ) ) );
+
+ } else if ( order === 'XZY' ) {
+
+ this._x = Math.atan2( 2 * ( q.x * q.w + q.y * q.z ), ( sqw - sqx + sqy - sqz ) );
+ this._y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw + sqx - sqy - sqz ) );
+ this._z = Math.asin( clamp( 2 * ( q.z * q.w - q.x * q.y ) ) );
+
+ } else {
+
+ console.warn( 'WARNING: Euler.setFromQuaternion() given unsupported order: ' + order )
+
+ }
+
+ this._order = order;
+
+ if ( update !== false ) this._updateQuaternion();
+
+ return this;
+
+ },
+
+ reorder: function () {
+
+ // WARNING: this discards revolution information -bhouston
+
+ var q = new THREE.Quaternion();
+
+ return function ( newOrder ) {
+
+ q.setFromEuler( this );
+ this.setFromQuaternion( q, newOrder );
+
+ };
+
+
+ }(),
+
+ fromArray: function ( array ) {
+
+ this._x = array[ 0 ];
+ this._y = array[ 1 ];
+ this._z = array[ 2 ];
+ if ( array[ 3 ] !== undefined ) this._order = array[ 3 ];
+
+ this._updateQuaternion();
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this._x, this._y, this._z, this._order ];
+
+ },
+
+ equals: function ( euler ) {
+
+ return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Euler( this._x, this._y, this._z, this._order );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Line3 = function ( start, end ) {
+
+ this.start = ( start !== undefined ) ? start : new THREE.Vector3();
+ this.end = ( end !== undefined ) ? end : new THREE.Vector3();
+
+};
+
+THREE.Line3.prototype = {
+
+ constructor: THREE.Line3,
+
+ set: function ( start, end ) {
+
+ this.start.copy( start );
+ this.end.copy( end );
+
+ return this;
+
+ },
+
+ copy: function ( line ) {
+
+ this.start.copy( line.start );
+ this.end.copy( line.end );
+
+ return this;
+
+ },
+
+ center: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 );
+
+ },
+
+ delta: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.subVectors( this.end, this.start );
+
+ },
+
+ distanceSq: function () {
+
+ return this.start.distanceToSquared( this.end );
+
+ },
+
+ distance: function () {
+
+ return this.start.distanceTo( this.end );
+
+ },
+
+ at: function ( t, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ return this.delta( result ).multiplyScalar( t ).add( this.start );
+
+ },
+
+ closestPointToPointParameter: function() {
+
+ var startP = new THREE.Vector3();
+ var startEnd = new THREE.Vector3();
+
+ return function ( point, clampToLine ) {
+
+ startP.subVectors( point, this.start );
+ startEnd.subVectors( this.end, this.start );
+
+ var startEnd2 = startEnd.dot( startEnd );
+ var startEnd_startP = startEnd.dot( startP );
+
+ var t = startEnd_startP / startEnd2;
+
+ if ( clampToLine ) {
+
+ t = THREE.Math.clamp( t, 0, 1 );
+
+ }
+
+ return t;
+
+ };
+
+ }(),
+
+ closestPointToPoint: function ( point, clampToLine, optionalTarget ) {
+
+ var t = this.closestPointToPointParameter( point, clampToLine );
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ return this.delta( result ).multiplyScalar( t ).add( this.start );
+
+ },
+
+ applyMatrix4: function ( matrix ) {
+
+ this.start.applyMatrix4( matrix );
+ this.end.applyMatrix4( matrix );
+
+ return this;
+
+ },
+
+ equals: function ( line ) {
+
+ return line.start.equals( this.start ) && line.end.equals( this.end );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Line3().copy( this );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Box2 = function ( min, max ) {
+
+ this.min = ( min !== undefined ) ? min : new THREE.Vector2( Infinity, Infinity );
+ this.max = ( max !== undefined ) ? max : new THREE.Vector2( -Infinity, -Infinity );
+
+};
+
+THREE.Box2.prototype = {
+
+ constructor: THREE.Box2,
+
+ set: function ( min, max ) {
+
+ this.min.copy( min );
+ this.max.copy( max );
+
+ return this;
+
+ },
+
+ setFromPoints: function ( points ) {
+
+ if ( points.length > 0 ) {
+
+ var point = points[ 0 ];
+
+ this.min.copy( point );
+ this.max.copy( point );
+
+ for ( var i = 1, il = points.length; i < il; i ++ ) {
+
+ point = points[ i ];
+
+ if ( point.x < this.min.x ) {
+
+ this.min.x = point.x;
+
+ } else if ( point.x > this.max.x ) {
+
+ this.max.x = point.x;
+
+ }
+
+ if ( point.y < this.min.y ) {
+
+ this.min.y = point.y;
+
+ } else if ( point.y > this.max.y ) {
+
+ this.max.y = point.y;
+
+ }
+
+ }
+
+ } else {
+
+ this.makeEmpty();
+
+ }
+
+ return this;
+
+ },
+
+ setFromCenterAndSize: function () {
+
+ var v1 = new THREE.Vector2();
+
+ return function ( center, size ) {
+
+ var halfSize = v1.copy( size ).multiplyScalar( 0.5 );
+ this.min.copy( center ).sub( halfSize );
+ this.max.copy( center ).add( halfSize );
+
+ return this;
+
+ };
+
+ }(),
+
+ copy: function ( box ) {
+
+ this.min.copy( box.min );
+ this.max.copy( box.max );
+
+ return this;
+
+ },
+
+ makeEmpty: function () {
+
+ this.min.x = this.min.y = Infinity;
+ this.max.x = this.max.y = -Infinity;
+
+ return this;
+
+ },
+
+ empty: function () {
+
+ // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+ return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y );
+
+ },
+
+ center: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector2();
+ return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+ },
+
+ size: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector2();
+ return result.subVectors( this.max, this.min );
+
+ },
+
+ expandByPoint: function ( point ) {
+
+ this.min.min( point );
+ this.max.max( point );
+
+ return this;
+ },
+
+ expandByVector: function ( vector ) {
+
+ this.min.sub( vector );
+ this.max.add( vector );
+
+ return this;
+ },
+
+ expandByScalar: function ( scalar ) {
+
+ this.min.addScalar( -scalar );
+ this.max.addScalar( scalar );
+
+ return this;
+ },
+
+ containsPoint: function ( point ) {
+
+ if ( point.x < this.min.x || point.x > this.max.x ||
+ point.y < this.min.y || point.y > this.max.y ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ },
+
+ containsBox: function ( box ) {
+
+ if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) &&
+ ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) ) {
+
+ return true;
+
+ }
+
+ return false;
+
+ },
+
+ getParameter: function ( point ) {
+
+ // This can potentially have a divide by zero if the box
+ // has a size dimension of 0.
+
+ return new THREE.Vector2(
+ ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+ ( point.y - this.min.y ) / ( this.max.y - this.min.y )
+ );
+
+ },
+
+ isIntersectionBox: function ( box ) {
+
+ // using 6 splitting planes to rule out intersections.
+
+ if ( box.max.x < this.min.x || box.min.x > this.max.x ||
+ box.max.y < this.min.y || box.min.y > this.max.y ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ },
+
+ clampPoint: function ( point, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector2();
+ return result.copy( point ).clamp( this.min, this.max );
+
+ },
+
+ distanceToPoint: function () {
+
+ var v1 = new THREE.Vector2();
+
+ return function ( point ) {
+
+ var clampedPoint = v1.copy( point ).clamp( this.min, this.max );
+ return clampedPoint.sub( point ).length();
+
+ };
+
+ }(),
+
+ intersect: function ( box ) {
+
+ this.min.max( box.min );
+ this.max.min( box.max );
+
+ return this;
+
+ },
+
+ union: function ( box ) {
+
+ this.min.min( box.min );
+ this.max.max( box.max );
+
+ return this;
+
+ },
+
+ translate: function ( offset ) {
+
+ this.min.add( offset );
+ this.max.add( offset );
+
+ return this;
+
+ },
+
+ equals: function ( box ) {
+
+ return box.min.equals( this.min ) && box.max.equals( this.max );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Box2().copy( this );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Box3 = function ( min, max ) {
+
+ this.min = ( min !== undefined ) ? min : new THREE.Vector3( Infinity, Infinity, Infinity );
+ this.max = ( max !== undefined ) ? max : new THREE.Vector3( -Infinity, -Infinity, -Infinity );
+
+};
+
+THREE.Box3.prototype = {
+
+ constructor: THREE.Box3,
+
+ set: function ( min, max ) {
+
+ this.min.copy( min );
+ this.max.copy( max );
+
+ return this;
+
+ },
+
+ addPoint: function ( point ) {
+
+ if ( point.x < this.min.x ) {
+
+ this.min.x = point.x;
+
+ } else if ( point.x > this.max.x ) {
+
+ this.max.x = point.x;
+
+ }
+
+ if ( point.y < this.min.y ) {
+
+ this.min.y = point.y;
+
+ } else if ( point.y > this.max.y ) {
+
+ this.max.y = point.y;
+
+ }
+
+ if ( point.z < this.min.z ) {
+
+ this.min.z = point.z;
+
+ } else if ( point.z > this.max.z ) {
+
+ this.max.z = point.z;
+
+ }
+
+ },
+
+ setFromPoints: function ( points ) {
+
+ if ( points.length > 0 ) {
+
+ var point = points[ 0 ];
+
+ this.min.copy( point );
+ this.max.copy( point );
+
+ for ( var i = 1, il = points.length; i < il; i ++ ) {
+
+ this.addPoint( points[ i ] )
+
+ }
+
+ } else {
+
+ this.makeEmpty();
+
+ }
+
+ return this;
+
+ },
+
+ setFromCenterAndSize: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( center, size ) {
+
+ var halfSize = v1.copy( size ).multiplyScalar( 0.5 );
+
+ this.min.copy( center ).sub( halfSize );
+ this.max.copy( center ).add( halfSize );
+
+ return this;
+
+ };
+
+ }(),
+
+ setFromObject: function() {
+
+ // Computes the world-axis-aligned bounding box of an object (including its children),
+ // accounting for both the object's, and childrens', world transforms
+
+ var v1 = new THREE.Vector3();
+
+ return function( object ) {
+
+ var scope = this;
+
+ object.updateMatrixWorld( true );
+
+ this.makeEmpty();
+
+ object.traverse( function ( node ) {
+
+ if ( node.geometry !== undefined && node.geometry.vertices !== undefined ) {
+
+ var vertices = node.geometry.vertices;
+
+ for ( var i = 0, il = vertices.length; i < il; i++ ) {
+
+ v1.copy( vertices[ i ] );
+
+ v1.applyMatrix4( node.matrixWorld );
+
+ scope.expandByPoint( v1 );
+
+ }
+
+ }
+
+ } );
+
+ return this;
+
+ };
+
+ }(),
+
+ copy: function ( box ) {
+
+ this.min.copy( box.min );
+ this.max.copy( box.max );
+
+ return this;
+
+ },
+
+ makeEmpty: function () {
+
+ this.min.x = this.min.y = this.min.z = Infinity;
+ this.max.x = this.max.y = this.max.z = -Infinity;
+
+ return this;
+
+ },
+
+ empty: function () {
+
+ // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+ return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );
+
+ },
+
+ center: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+ },
+
+ size: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.subVectors( this.max, this.min );
+
+ },
+
+ expandByPoint: function ( point ) {
+
+ this.min.min( point );
+ this.max.max( point );
+
+ return this;
+
+ },
+
+ expandByVector: function ( vector ) {
+
+ this.min.sub( vector );
+ this.max.add( vector );
+
+ return this;
+
+ },
+
+ expandByScalar: function ( scalar ) {
+
+ this.min.addScalar( -scalar );
+ this.max.addScalar( scalar );
+
+ return this;
+
+ },
+
+ containsPoint: function ( point ) {
+
+ if ( point.x < this.min.x || point.x > this.max.x ||
+ point.y < this.min.y || point.y > this.max.y ||
+ point.z < this.min.z || point.z > this.max.z ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ },
+
+ containsBox: function ( box ) {
+
+ if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) &&
+ ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) &&
+ ( this.min.z <= box.min.z ) && ( box.max.z <= this.max.z ) ) {
+
+ return true;
+
+ }
+
+ return false;
+
+ },
+
+ getParameter: function ( point ) {
+
+ // This can potentially have a divide by zero if the box
+ // has a size dimension of 0.
+
+ return new THREE.Vector3(
+ ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+ ( point.y - this.min.y ) / ( this.max.y - this.min.y ),
+ ( point.z - this.min.z ) / ( this.max.z - this.min.z )
+ );
+
+ },
+
+ isIntersectionBox: function ( box ) {
+
+ // using 6 splitting planes to rule out intersections.
+
+ if ( box.max.x < this.min.x || box.min.x > this.max.x ||
+ box.max.y < this.min.y || box.min.y > this.max.y ||
+ box.max.z < this.min.z || box.min.z > this.max.z ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ },
+
+ clampPoint: function ( point, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.copy( point ).clamp( this.min, this.max );
+
+ },
+
+ distanceToPoint: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( point ) {
+
+ var clampedPoint = v1.copy( point ).clamp( this.min, this.max );
+ return clampedPoint.sub( point ).length();
+
+ };
+
+ }(),
+
+ getBoundingSphere: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Sphere();
+
+ result.center = this.center();
+ result.radius = this.size( v1 ).length() * 0.5;
+
+ return result;
+
+ };
+
+ }(),
+
+ intersect: function ( box ) {
+
+ this.min.max( box.min );
+ this.max.min( box.max );
+
+ return this;
+
+ },
+
+ union: function ( box ) {
+
+ this.min.min( box.min );
+ this.max.max( box.max );
+
+ return this;
+
+ },
+
+ applyMatrix4: function() {
+
+ var points = [
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3()
+ ];
+
+ return function ( matrix ) {
+
+ // NOTE: I am using a binary pattern to specify all 2^3 combinations below
+ points[0].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000
+ points[1].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001
+ points[2].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010
+ points[3].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011
+ points[4].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100
+ points[5].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101
+ points[6].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110
+ points[7].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111
+
+ this.makeEmpty();
+ this.setFromPoints( points );
+
+ return this;
+
+ };
+
+ }(),
+
+ translate: function ( offset ) {
+
+ this.min.add( offset );
+ this.max.add( offset );
+
+ return this;
+
+ },
+
+ equals: function ( box ) {
+
+ return box.min.equals( this.min ) && box.max.equals( this.max );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Box3().copy( this );
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Matrix3 = function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+ this.elements = new Float32Array(9);
+
+ this.set(
+
+ ( n11 !== undefined ) ? n11 : 1, n12 || 0, n13 || 0,
+ n21 || 0, ( n22 !== undefined ) ? n22 : 1, n23 || 0,
+ n31 || 0, n32 || 0, ( n33 !== undefined ) ? n33 : 1
+
+ );
+};
+
+THREE.Matrix3.prototype = {
+
+ constructor: THREE.Matrix3,
+
+ set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+ var te = this.elements;
+
+ te[0] = n11; te[3] = n12; te[6] = n13;
+ te[1] = n21; te[4] = n22; te[7] = n23;
+ te[2] = n31; te[5] = n32; te[8] = n33;
+
+ return this;
+
+ },
+
+ identity: function () {
+
+ this.set(
+
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ copy: function ( m ) {
+
+ var me = m.elements;
+
+ this.set(
+
+ me[0], me[3], me[6],
+ me[1], me[4], me[7],
+ me[2], me[5], me[8]
+
+ );
+
+ return this;
+
+ },
+
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Matrix3\'s .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
+ return vector.applyMatrix3( this );
+
+ },
+
+ multiplyVector3Array: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( a ) {
+
+ for ( var i = 0, il = a.length; i < il; i += 3 ) {
+
+ v1.x = a[ i ];
+ v1.y = a[ i + 1 ];
+ v1.z = a[ i + 2 ];
+
+ v1.applyMatrix3(this);
+
+ a[ i ] = v1.x;
+ a[ i + 1 ] = v1.y;
+ a[ i + 2 ] = v1.z;
+
+ }
+
+ return a;
+
+ };
+
+ }(),
+
+ multiplyScalar: function ( s ) {
+
+ var te = this.elements;
+
+ te[0] *= s; te[3] *= s; te[6] *= s;
+ te[1] *= s; te[4] *= s; te[7] *= s;
+ te[2] *= s; te[5] *= s; te[8] *= s;
+
+ return this;
+
+ },
+
+ determinant: function () {
+
+ var te = this.elements;
+
+ var a = te[0], b = te[1], c = te[2],
+ d = te[3], e = te[4], f = te[5],
+ g = te[6], h = te[7], i = te[8];
+
+ return a*e*i - a*f*h - b*d*i + b*f*g + c*d*h - c*e*g;
+
+ },
+
+ getInverse: function ( matrix, throwOnInvertible ) {
+
+ // input: THREE.Matrix4
+ // ( based on http://code.google.com/p/webgl-mjs/ )
+
+ var me = matrix.elements;
+ var te = this.elements;
+
+ te[ 0 ] = me[10] * me[5] - me[6] * me[9];
+ te[ 1 ] = - me[10] * me[1] + me[2] * me[9];
+ te[ 2 ] = me[6] * me[1] - me[2] * me[5];
+ te[ 3 ] = - me[10] * me[4] + me[6] * me[8];
+ te[ 4 ] = me[10] * me[0] - me[2] * me[8];
+ te[ 5 ] = - me[6] * me[0] + me[2] * me[4];
+ te[ 6 ] = me[9] * me[4] - me[5] * me[8];
+ te[ 7 ] = - me[9] * me[0] + me[1] * me[8];
+ te[ 8 ] = me[5] * me[0] - me[1] * me[4];
+
+ var det = me[ 0 ] * te[ 0 ] + me[ 1 ] * te[ 3 ] + me[ 2 ] * te[ 6 ];
+
+ // no inverse
+
+ if ( det === 0 ) {
+
+ var msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0";
+
+ if ( throwOnInvertible || false ) {
+
+ throw new Error( msg );
+
+ } else {
+
+ console.warn( msg );
+
+ }
+
+ this.identity();
+
+ return this;
+
+ }
+
+ this.multiplyScalar( 1.0 / det );
+
+ return this;
+
+ },
+
+ transpose: function () {
+
+ var tmp, m = this.elements;
+
+ tmp = m[1]; m[1] = m[3]; m[3] = tmp;
+ tmp = m[2]; m[2] = m[6]; m[6] = tmp;
+ tmp = m[5]; m[5] = m[7]; m[7] = tmp;
+
+ return this;
+
+ },
+
+ getNormalMatrix: function ( m ) {
+
+ // input: THREE.Matrix4
+
+ this.getInverse( m ).transpose();
+
+ return this;
+
+ },
+
+ transposeIntoArray: function ( r ) {
+
+ var m = this.elements;
+
+ r[ 0 ] = m[ 0 ];
+ r[ 1 ] = m[ 3 ];
+ r[ 2 ] = m[ 6 ];
+ r[ 3 ] = m[ 1 ];
+ r[ 4 ] = m[ 4 ];
+ r[ 5 ] = m[ 7 ];
+ r[ 6 ] = m[ 2 ];
+ r[ 7 ] = m[ 5 ];
+ r[ 8 ] = m[ 8 ];
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ var te = this.elements;
+
+ return new THREE.Matrix3(
+
+ te[0], te[3], te[6],
+ te[1], te[4], te[7],
+ te[2], te[5], te[8]
+
+ );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author philogb / http://blog.thejit.org/
+ * @author jordi_ros / http://plattsoft.com
+ * @author D1plo1d / http://github.com/D1plo1d
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author timknip / http://www.floorplanner.com/
+ * @author bhouston / http://exocortex.com
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+
+THREE.Matrix4 = function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+ this.elements = new Float32Array( 16 );
+
+ // TODO: if n11 is undefined, then just set to identity, otherwise copy all other values into matrix
+ // we should not support semi specification of Matrix4, it is just weird.
+
+ var te = this.elements;
+
+ te[0] = ( n11 !== undefined ) ? n11 : 1; te[4] = n12 || 0; te[8] = n13 || 0; te[12] = n14 || 0;
+ te[1] = n21 || 0; te[5] = ( n22 !== undefined ) ? n22 : 1; te[9] = n23 || 0; te[13] = n24 || 0;
+ te[2] = n31 || 0; te[6] = n32 || 0; te[10] = ( n33 !== undefined ) ? n33 : 1; te[14] = n34 || 0;
+ te[3] = n41 || 0; te[7] = n42 || 0; te[11] = n43 || 0; te[15] = ( n44 !== undefined ) ? n44 : 1;
+
+};
+
+THREE.Matrix4.prototype = {
+
+ constructor: THREE.Matrix4,
+
+ set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+ var te = this.elements;
+
+ te[0] = n11; te[4] = n12; te[8] = n13; te[12] = n14;
+ te[1] = n21; te[5] = n22; te[9] = n23; te[13] = n24;
+ te[2] = n31; te[6] = n32; te[10] = n33; te[14] = n34;
+ te[3] = n41; te[7] = n42; te[11] = n43; te[15] = n44;
+
+ return this;
+
+ },
+
+ identity: function () {
+
+ this.set(
+
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ copy: function ( m ) {
+
+ this.elements.set( m.elements );
+
+ return this;
+
+ },
+
+ extractPosition: function ( m ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .extractPosition() has been renamed to .copyPosition().' );
+ return this.copyPosition( m );
+
+ },
+
+ copyPosition: function ( m ) {
+
+ var te = this.elements;
+ var me = m.elements;
+
+ te[12] = me[12];
+ te[13] = me[13];
+ te[14] = me[14];
+
+ return this;
+
+ },
+
+ extractRotation: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( m ) {
+
+ var te = this.elements;
+ var me = m.elements;
+
+ var scaleX = 1 / v1.set( me[0], me[1], me[2] ).length();
+ var scaleY = 1 / v1.set( me[4], me[5], me[6] ).length();
+ var scaleZ = 1 / v1.set( me[8], me[9], me[10] ).length();
+
+ te[0] = me[0] * scaleX;
+ te[1] = me[1] * scaleX;
+ te[2] = me[2] * scaleX;
+
+ te[4] = me[4] * scaleY;
+ te[5] = me[5] * scaleY;
+ te[6] = me[6] * scaleY;
+
+ te[8] = me[8] * scaleZ;
+ te[9] = me[9] * scaleZ;
+ te[10] = me[10] * scaleZ;
+
+ return this;
+
+ };
+
+ }(),
+
+ makeRotationFromEuler: function ( euler ) {
+
+ if ( euler instanceof THREE.Euler === false ) {
+
+ console.error( 'ERROR: Matrix\'s .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' );
+
+ }
+
+ var te = this.elements;
+
+ var x = euler.x, y = euler.y, z = euler.z;
+ var a = Math.cos( x ), b = Math.sin( x );
+ var c = Math.cos( y ), d = Math.sin( y );
+ var e = Math.cos( z ), f = Math.sin( z );
+
+ if ( euler.order === 'XYZ' ) {
+
+ var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+ te[0] = c * e;
+ te[4] = - c * f;
+ te[8] = d;
+
+ te[1] = af + be * d;
+ te[5] = ae - bf * d;
+ te[9] = - b * c;
+
+ te[2] = bf - ae * d;
+ te[6] = be + af * d;
+ te[10] = a * c;
+
+ } else if ( euler.order === 'YXZ' ) {
+
+ var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+ te[0] = ce + df * b;
+ te[4] = de * b - cf;
+ te[8] = a * d;
+
+ te[1] = a * f;
+ te[5] = a * e;
+ te[9] = - b;
+
+ te[2] = cf * b - de;
+ te[6] = df + ce * b;
+ te[10] = a * c;
+
+ } else if ( euler.order === 'ZXY' ) {
+
+ var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+ te[0] = ce - df * b;
+ te[4] = - a * f;
+ te[8] = de + cf * b;
+
+ te[1] = cf + de * b;
+ te[5] = a * e;
+ te[9] = df - ce * b;
+
+ te[2] = - a * d;
+ te[6] = b;
+ te[10] = a * c;
+
+ } else if ( euler.order === 'ZYX' ) {
+
+ var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+ te[0] = c * e;
+ te[4] = be * d - af;
+ te[8] = ae * d + bf;
+
+ te[1] = c * f;
+ te[5] = bf * d + ae;
+ te[9] = af * d - be;
+
+ te[2] = - d;
+ te[6] = b * c;
+ te[10] = a * c;
+
+ } else if ( euler.order === 'YZX' ) {
+
+ var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+ te[0] = c * e;
+ te[4] = bd - ac * f;
+ te[8] = bc * f + ad;
+
+ te[1] = f;
+ te[5] = a * e;
+ te[9] = - b * e;
+
+ te[2] = - d * e;
+ te[6] = ad * f + bc;
+ te[10] = ac - bd * f;
+
+ } else if ( euler.order === 'XZY' ) {
+
+ var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+ te[0] = c * e;
+ te[4] = - f;
+ te[8] = d * e;
+
+ te[1] = ac * f + bd;
+ te[5] = a * e;
+ te[9] = ad * f - bc;
+
+ te[2] = bc * f - ad;
+ te[6] = b * e;
+ te[10] = bd * f + ac;
+
+ }
+
+ // last column
+ te[3] = 0;
+ te[7] = 0;
+ te[11] = 0;
+
+ // bottom row
+ te[12] = 0;
+ te[13] = 0;
+ te[14] = 0;
+ te[15] = 1;
+
+ return this;
+
+ },
+
+ setRotationFromQuaternion: function ( q ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .setRotationFromQuaternion() has been deprecated in favor of makeRotationFromQuaternion. Please update your code.' );
+
+ return this.makeRotationFromQuaternion( q );
+
+ },
+
+ makeRotationFromQuaternion: function ( q ) {
+
+ var te = this.elements;
+
+ var x = q.x, y = q.y, z = q.z, w = q.w;
+ var x2 = x + x, y2 = y + y, z2 = z + z;
+ var xx = x * x2, xy = x * y2, xz = x * z2;
+ var yy = y * y2, yz = y * z2, zz = z * z2;
+ var wx = w * x2, wy = w * y2, wz = w * z2;
+
+ te[0] = 1 - ( yy + zz );
+ te[4] = xy - wz;
+ te[8] = xz + wy;
+
+ te[1] = xy + wz;
+ te[5] = 1 - ( xx + zz );
+ te[9] = yz - wx;
+
+ te[2] = xz - wy;
+ te[6] = yz + wx;
+ te[10] = 1 - ( xx + yy );
+
+ // last column
+ te[3] = 0;
+ te[7] = 0;
+ te[11] = 0;
+
+ // bottom row
+ te[12] = 0;
+ te[13] = 0;
+ te[14] = 0;
+ te[15] = 1;
+
+ return this;
+
+ },
+
+ lookAt: function() {
+
+ var x = new THREE.Vector3();
+ var y = new THREE.Vector3();
+ var z = new THREE.Vector3();
+
+ return function ( eye, target, up ) {
+
+ var te = this.elements;
+
+ z.subVectors( eye, target ).normalize();
+
+ if ( z.length() === 0 ) {
+
+ z.z = 1;
+
+ }
+
+ x.crossVectors( up, z ).normalize();
+
+ if ( x.length() === 0 ) {
+
+ z.x += 0.0001;
+ x.crossVectors( up, z ).normalize();
+
+ }
+
+ y.crossVectors( z, x );
+
+
+ te[0] = x.x; te[4] = y.x; te[8] = z.x;
+ te[1] = x.y; te[5] = y.y; te[9] = z.y;
+ te[2] = x.z; te[6] = y.z; te[10] = z.z;
+
+ return this;
+
+ };
+
+ }(),
+
+ multiply: function ( m, n ) {
+
+ if ( n !== undefined ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
+ return this.multiplyMatrices( m, n );
+
+ }
+
+ return this.multiplyMatrices( this, m );
+
+ },
+
+ multiplyMatrices: function ( a, b ) {
+
+ var ae = a.elements;
+ var be = b.elements;
+ var te = this.elements;
+
+ var a11 = ae[0], a12 = ae[4], a13 = ae[8], a14 = ae[12];
+ var a21 = ae[1], a22 = ae[5], a23 = ae[9], a24 = ae[13];
+ var a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14];
+ var a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15];
+
+ var b11 = be[0], b12 = be[4], b13 = be[8], b14 = be[12];
+ var b21 = be[1], b22 = be[5], b23 = be[9], b24 = be[13];
+ var b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14];
+ var b41 = be[3], b42 = be[7], b43 = be[11], b44 = be[15];
+
+ te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
+ te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
+ te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
+ te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
+
+ te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
+ te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
+ te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
+ te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
+
+ te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
+ te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
+ te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
+ te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
+
+ te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
+ te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
+ te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
+ te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
+
+ return this;
+
+ },
+
+ multiplyToArray: function ( a, b, r ) {
+
+ var te = this.elements;
+
+ this.multiplyMatrices( a, b );
+
+ r[ 0 ] = te[0]; r[ 1 ] = te[1]; r[ 2 ] = te[2]; r[ 3 ] = te[3];
+ r[ 4 ] = te[4]; r[ 5 ] = te[5]; r[ 6 ] = te[6]; r[ 7 ] = te[7];
+ r[ 8 ] = te[8]; r[ 9 ] = te[9]; r[ 10 ] = te[10]; r[ 11 ] = te[11];
+ r[ 12 ] = te[12]; r[ 13 ] = te[13]; r[ 14 ] = te[14]; r[ 15 ] = te[15];
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( s ) {
+
+ var te = this.elements;
+
+ te[0] *= s; te[4] *= s; te[8] *= s; te[12] *= s;
+ te[1] *= s; te[5] *= s; te[9] *= s; te[13] *= s;
+ te[2] *= s; te[6] *= s; te[10] *= s; te[14] *= s;
+ te[3] *= s; te[7] *= s; te[11] *= s; te[15] *= s;
+
+ return this;
+
+ },
+
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.' );
+ return vector.applyProjection( this );
+
+ },
+
+ multiplyVector4: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+ return vector.applyMatrix4( this );
+
+ },
+
+ multiplyVector3Array: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( a ) {
+
+ for ( var i = 0, il = a.length; i < il; i += 3 ) {
+
+ v1.x = a[ i ];
+ v1.y = a[ i + 1 ];
+ v1.z = a[ i + 2 ];
+
+ v1.applyProjection( this );
+
+ a[ i ] = v1.x;
+ a[ i + 1 ] = v1.y;
+ a[ i + 2 ] = v1.z;
+
+ }
+
+ return a;
+
+ };
+
+ }(),
+
+ rotateAxis: function ( v ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );
+
+ v.transformDirection( this );
+
+ },
+
+ crossVector: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+ return vector.applyMatrix4( this );
+
+ },
+
+ determinant: function () {
+
+ var te = this.elements;
+
+ var n11 = te[0], n12 = te[4], n13 = te[8], n14 = te[12];
+ var n21 = te[1], n22 = te[5], n23 = te[9], n24 = te[13];
+ var n31 = te[2], n32 = te[6], n33 = te[10], n34 = te[14];
+ var n41 = te[3], n42 = te[7], n43 = te[11], n44 = te[15];
+
+ //TODO: make this more efficient
+ //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
+
+ return (
+ n41 * (
+ +n14 * n23 * n32
+ -n13 * n24 * n32
+ -n14 * n22 * n33
+ +n12 * n24 * n33
+ +n13 * n22 * n34
+ -n12 * n23 * n34
+ ) +
+ n42 * (
+ +n11 * n23 * n34
+ -n11 * n24 * n33
+ +n14 * n21 * n33
+ -n13 * n21 * n34
+ +n13 * n24 * n31
+ -n14 * n23 * n31
+ ) +
+ n43 * (
+ +n11 * n24 * n32
+ -n11 * n22 * n34
+ -n14 * n21 * n32
+ +n12 * n21 * n34
+ +n14 * n22 * n31
+ -n12 * n24 * n31
+ ) +
+ n44 * (
+ -n13 * n22 * n31
+ -n11 * n23 * n32
+ +n11 * n22 * n33
+ +n13 * n21 * n32
+ -n12 * n21 * n33
+ +n12 * n23 * n31
+ )
+
+ );
+
+ },
+
+ transpose: function () {
+
+ var te = this.elements;
+ var tmp;
+
+ tmp = te[1]; te[1] = te[4]; te[4] = tmp;
+ tmp = te[2]; te[2] = te[8]; te[8] = tmp;
+ tmp = te[6]; te[6] = te[9]; te[9] = tmp;
+
+ tmp = te[3]; te[3] = te[12]; te[12] = tmp;
+ tmp = te[7]; te[7] = te[13]; te[13] = tmp;
+ tmp = te[11]; te[11] = te[14]; te[14] = tmp;
+
+ return this;
+
+ },
+
+ flattenToArray: function ( flat ) {
+
+ var te = this.elements;
+ flat[ 0 ] = te[0]; flat[ 1 ] = te[1]; flat[ 2 ] = te[2]; flat[ 3 ] = te[3];
+ flat[ 4 ] = te[4]; flat[ 5 ] = te[5]; flat[ 6 ] = te[6]; flat[ 7 ] = te[7];
+ flat[ 8 ] = te[8]; flat[ 9 ] = te[9]; flat[ 10 ] = te[10]; flat[ 11 ] = te[11];
+ flat[ 12 ] = te[12]; flat[ 13 ] = te[13]; flat[ 14 ] = te[14]; flat[ 15 ] = te[15];
+
+ return flat;
+
+ },
+
+ flattenToArrayOffset: function( flat, offset ) {
+
+ var te = this.elements;
+ flat[ offset ] = te[0];
+ flat[ offset + 1 ] = te[1];
+ flat[ offset + 2 ] = te[2];
+ flat[ offset + 3 ] = te[3];
+
+ flat[ offset + 4 ] = te[4];
+ flat[ offset + 5 ] = te[5];
+ flat[ offset + 6 ] = te[6];
+ flat[ offset + 7 ] = te[7];
+
+ flat[ offset + 8 ] = te[8];
+ flat[ offset + 9 ] = te[9];
+ flat[ offset + 10 ] = te[10];
+ flat[ offset + 11 ] = te[11];
+
+ flat[ offset + 12 ] = te[12];
+ flat[ offset + 13 ] = te[13];
+ flat[ offset + 14 ] = te[14];
+ flat[ offset + 15 ] = te[15];
+
+ return flat;
+
+ },
+
+ getPosition: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function () {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .getPosition() has been removed. Use Vector3.getPositionFromMatrix( matrix ) instead.' );
+
+ var te = this.elements;
+ return v1.set( te[12], te[13], te[14] );
+
+ };
+
+ }(),
+
+ setPosition: function ( v ) {
+
+ var te = this.elements;
+
+ te[12] = v.x;
+ te[13] = v.y;
+ te[14] = v.z;
+
+ return this;
+
+ },
+
+ getInverse: function ( m, throwOnInvertible ) {
+
+ // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
+ var te = this.elements;
+ var me = m.elements;
+
+ var n11 = me[0], n12 = me[4], n13 = me[8], n14 = me[12];
+ var n21 = me[1], n22 = me[5], n23 = me[9], n24 = me[13];
+ var n31 = me[2], n32 = me[6], n33 = me[10], n34 = me[14];
+ var n41 = me[3], n42 = me[7], n43 = me[11], n44 = me[15];
+
+ te[0] = n23*n34*n42 - n24*n33*n42 + n24*n32*n43 - n22*n34*n43 - n23*n32*n44 + n22*n33*n44;
+ te[4] = n14*n33*n42 - n13*n34*n42 - n14*n32*n43 + n12*n34*n43 + n13*n32*n44 - n12*n33*n44;
+ te[8] = n13*n24*n42 - n14*n23*n42 + n14*n22*n43 - n12*n24*n43 - n13*n22*n44 + n12*n23*n44;
+ te[12] = n14*n23*n32 - n13*n24*n32 - n14*n22*n33 + n12*n24*n33 + n13*n22*n34 - n12*n23*n34;
+ te[1] = n24*n33*n41 - n23*n34*n41 - n24*n31*n43 + n21*n34*n43 + n23*n31*n44 - n21*n33*n44;
+ te[5] = n13*n34*n41 - n14*n33*n41 + n14*n31*n43 - n11*n34*n43 - n13*n31*n44 + n11*n33*n44;
+ te[9] = n14*n23*n41 - n13*n24*n41 - n14*n21*n43 + n11*n24*n43 + n13*n21*n44 - n11*n23*n44;
+ te[13] = n13*n24*n31 - n14*n23*n31 + n14*n21*n33 - n11*n24*n33 - n13*n21*n34 + n11*n23*n34;
+ te[2] = n22*n34*n41 - n24*n32*n41 + n24*n31*n42 - n21*n34*n42 - n22*n31*n44 + n21*n32*n44;
+ te[6] = n14*n32*n41 - n12*n34*n41 - n14*n31*n42 + n11*n34*n42 + n12*n31*n44 - n11*n32*n44;
+ te[10] = n12*n24*n41 - n14*n22*n41 + n14*n21*n42 - n11*n24*n42 - n12*n21*n44 + n11*n22*n44;
+ te[14] = n14*n22*n31 - n12*n24*n31 - n14*n21*n32 + n11*n24*n32 + n12*n21*n34 - n11*n22*n34;
+ te[3] = n23*n32*n41 - n22*n33*n41 - n23*n31*n42 + n21*n33*n42 + n22*n31*n43 - n21*n32*n43;
+ te[7] = n12*n33*n41 - n13*n32*n41 + n13*n31*n42 - n11*n33*n42 - n12*n31*n43 + n11*n32*n43;
+ te[11] = n13*n22*n41 - n12*n23*n41 - n13*n21*n42 + n11*n23*n42 + n12*n21*n43 - n11*n22*n43;
+ te[15] = n12*n23*n31 - n13*n22*n31 + n13*n21*n32 - n11*n23*n32 - n12*n21*n33 + n11*n22*n33;
+
+ var det = n11 * te[ 0 ] + n21 * te[ 4 ] + n31 * te[ 8 ] + n41 * te[ 12 ];
+
+ if ( det == 0 ) {
+
+ var msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0";
+
+ if ( throwOnInvertible || false ) {
+
+ throw new Error( msg );
+
+ } else {
+
+ console.warn( msg );
+
+ }
+
+ this.identity();
+
+ return this;
+ }
+
+ this.multiplyScalar( 1 / det );
+
+ return this;
+
+ },
+
+ translate: function ( v ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .translate() has been removed.');
+
+ },
+
+ rotateX: function ( angle ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateX() has been removed.');
+
+ },
+
+ rotateY: function ( angle ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateY() has been removed.');
+
+ },
+
+ rotateZ: function ( angle ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateZ() has been removed.');
+
+ },
+
+ rotateByAxis: function ( axis, angle ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateByAxis() has been removed.');
+
+ },
+
+ scale: function ( v ) {
+
+ var te = this.elements;
+ var x = v.x, y = v.y, z = v.z;
+
+ te[0] *= x; te[4] *= y; te[8] *= z;
+ te[1] *= x; te[5] *= y; te[9] *= z;
+ te[2] *= x; te[6] *= y; te[10] *= z;
+ te[3] *= x; te[7] *= y; te[11] *= z;
+
+ return this;
+
+ },
+
+ getMaxScaleOnAxis: function () {
+
+ var te = this.elements;
+
+ var scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2];
+ var scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6];
+ var scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10];
+
+ return Math.sqrt( Math.max( scaleXSq, Math.max( scaleYSq, scaleZSq ) ) );
+
+ },
+
+ makeTranslation: function ( x, y, z ) {
+
+ this.set(
+
+ 1, 0, 0, x,
+ 0, 1, 0, y,
+ 0, 0, 1, z,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationX: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ 1, 0, 0, 0,
+ 0, c, -s, 0,
+ 0, s, c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationY: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ c, 0, s, 0,
+ 0, 1, 0, 0,
+ -s, 0, c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationZ: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ c, -s, 0, 0,
+ s, c, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationAxis: function ( axis, angle ) {
+
+ // Based on http://www.gamedev.net/reference/articles/article1199.asp
+
+ var c = Math.cos( angle );
+ var s = Math.sin( angle );
+ var t = 1 - c;
+ var x = axis.x, y = axis.y, z = axis.z;
+ var tx = t * x, ty = t * y;
+
+ this.set(
+
+ tx * x + c, tx * y - s * z, tx * z + s * y, 0,
+ tx * y + s * z, ty * y + c, ty * z - s * x, 0,
+ tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeScale: function ( x, y, z ) {
+
+ this.set(
+
+ x, 0, 0, 0,
+ 0, y, 0, 0,
+ 0, 0, z, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ compose: function ( position, quaternion, scale ) {
+
+ this.makeRotationFromQuaternion( quaternion );
+ this.scale( scale );
+ this.setPosition( position );
+
+ return this;
+
+ },
+
+ decompose: function () {
+
+ var vector = new THREE.Vector3();
+ var matrix = new THREE.Matrix4();
+
+ return function ( position, quaternion, scale ) {
+
+ var te = this.elements;
+
+ var sx = vector.set( te[0], te[1], te[2] ).length();
+ var sy = vector.set( te[4], te[5], te[6] ).length();
+ var sz = vector.set( te[8], te[9], te[10] ).length();
+
+ position.x = te[12];
+ position.y = te[13];
+ position.z = te[14];
+
+ // scale the rotation part
+
+ matrix.elements.set( this.elements ); // at this point matrix is incomplete so we can't use .copy()
+
+ var invSX = 1 / sx;
+ var invSY = 1 / sy;
+ var invSZ = 1 / sz;
+
+ matrix.elements[0] *= invSX;
+ matrix.elements[1] *= invSX;
+ matrix.elements[2] *= invSX;
+
+ matrix.elements[4] *= invSY;
+ matrix.elements[5] *= invSY;
+ matrix.elements[6] *= invSY;
+
+ matrix.elements[8] *= invSZ;
+ matrix.elements[9] *= invSZ;
+ matrix.elements[10] *= invSZ;
+
+ quaternion.setFromRotationMatrix( matrix );
+
+ scale.x = sx;
+ scale.y = sy;
+ scale.z = sz;
+
+ return this;
+
+ };
+
+ }(),
+
+ makeFrustum: function ( left, right, bottom, top, near, far ) {
+
+ var te = this.elements;
+ var x = 2 * near / ( right - left );
+ var y = 2 * near / ( top - bottom );
+
+ var a = ( right + left ) / ( right - left );
+ var b = ( top + bottom ) / ( top - bottom );
+ var c = - ( far + near ) / ( far - near );
+ var d = - 2 * far * near / ( far - near );
+
+ te[0] = x; te[4] = 0; te[8] = a; te[12] = 0;
+ te[1] = 0; te[5] = y; te[9] = b; te[13] = 0;
+ te[2] = 0; te[6] = 0; te[10] = c; te[14] = d;
+ te[3] = 0; te[7] = 0; te[11] = - 1; te[15] = 0;
+
+ return this;
+
+ },
+
+ makePerspective: function ( fov, aspect, near, far ) {
+
+ var ymax = near * Math.tan( THREE.Math.degToRad( fov * 0.5 ) );
+ var ymin = - ymax;
+ var xmin = ymin * aspect;
+ var xmax = ymax * aspect;
+
+ return this.makeFrustum( xmin, xmax, ymin, ymax, near, far );
+
+ },
+
+ makeOrthographic: function ( left, right, top, bottom, near, far ) {
+
+ var te = this.elements;
+ var w = right - left;
+ var h = top - bottom;
+ var p = far - near;
+
+ var x = ( right + left ) / w;
+ var y = ( top + bottom ) / h;
+ var z = ( far + near ) / p;
+
+ te[0] = 2 / w; te[4] = 0; te[8] = 0; te[12] = -x;
+ te[1] = 0; te[5] = 2 / h; te[9] = 0; te[13] = -y;
+ te[2] = 0; te[6] = 0; te[10] = -2/p; te[14] = -z;
+ te[3] = 0; te[7] = 0; te[11] = 0; te[15] = 1;
+
+ return this;
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.elements.set( array );
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ var te = this.elements;
+
+ return [
+ te[ 0 ], te[ 1 ], te[ 2 ], te[ 3 ],
+ te[ 4 ], te[ 5 ], te[ 6 ], te[ 7 ],
+ te[ 8 ], te[ 9 ], te[ 10 ], te[ 11 ],
+ te[ 12 ], te[ 13 ], te[ 14 ], te[ 15 ]
+ ];
+
+ },
+
+ clone: function () {
+
+ var te = this.elements;
+
+ return new THREE.Matrix4(
+
+ te[0], te[4], te[8], te[12],
+ te[1], te[5], te[9], te[13],
+ te[2], te[6], te[10], te[14],
+ te[3], te[7], te[11], te[15]
+
+ );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Ray = function ( origin, direction ) {
+
+ this.origin = ( origin !== undefined ) ? origin : new THREE.Vector3();
+ this.direction = ( direction !== undefined ) ? direction : new THREE.Vector3();
+
+};
+
+THREE.Ray.prototype = {
+
+ constructor: THREE.Ray,
+
+ set: function ( origin, direction ) {
+
+ this.origin.copy( origin );
+ this.direction.copy( direction );
+
+ return this;
+
+ },
+
+ copy: function ( ray ) {
+
+ this.origin.copy( ray.origin );
+ this.direction.copy( ray.direction );
+
+ return this;
+
+ },
+
+ at: function ( t, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ return result.copy( this.direction ).multiplyScalar( t ).add( this.origin );
+
+ },
+
+ recast: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( t ) {
+
+ this.origin.copy( this.at( t, v1 ) );
+
+ return this;
+
+ };
+
+ }(),
+
+ closestPointToPoint: function ( point, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ result.subVectors( point, this.origin );
+ var directionDistance = result.dot( this.direction );
+
+ if ( directionDistance < 0 ) {
+
+ return result.copy( this.origin );
+
+ }
+
+ return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+ },
+
+ distanceToPoint: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( point ) {
+
+ var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction );
+
+ // point behind the ray
+
+ if ( directionDistance < 0 ) {
+
+ return this.origin.distanceTo( point );
+
+ }
+
+ v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+ return v1.distanceTo( point );
+
+ };
+
+ }(),
+
+ distanceSqToSegment: function( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
+
+ // from http://www.geometrictools.com/LibMathematics/Distance/Wm5DistRay3Segment3.cpp
+ // It returns the min distance between the ray and the segment
+ // defined by v0 and v1
+ // It can also set two optional targets :
+ // - The closest point on the ray
+ // - The closest point on the segment
+
+ var segCenter = v0.clone().add( v1 ).multiplyScalar( 0.5 );
+ var segDir = v1.clone().sub( v0 ).normalize();
+ var segExtent = v0.distanceTo( v1 ) * 0.5;
+ var diff = this.origin.clone().sub( segCenter );
+ var a01 = - this.direction.dot( segDir );
+ var b0 = diff.dot( this.direction );
+ var b1 = - diff.dot( segDir );
+ var c = diff.lengthSq();
+ var det = Math.abs( 1 - a01 * a01 );
+ var s0, s1, sqrDist, extDet;
+
+ if ( det >= 0 ) {
+
+ // The ray and segment are not parallel.
+
+ s0 = a01 * b1 - b0;
+ s1 = a01 * b0 - b1;
+ extDet = segExtent * det;
+
+ if ( s0 >= 0 ) {
+
+ if ( s1 >= - extDet ) {
+
+ if ( s1 <= extDet ) {
+
+ // region 0
+ // Minimum at interior points of ray and segment.
+
+ var invDet = 1 / det;
+ s0 *= invDet;
+ s1 *= invDet;
+ sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
+
+ } else {
+
+ // region 1
+
+ s1 = segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ } else {
+
+ // region 5
+
+ s1 = - segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ } else {
+
+ if ( s1 <= - extDet) {
+
+ // region 4
+
+ s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
+ s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ } else if ( s1 <= extDet ) {
+
+ // region 3
+
+ s0 = 0;
+ s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = s1 * ( s1 + 2 * b1 ) + c;
+
+ } else {
+
+ // region 2
+
+ s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
+ s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ }
+
+ } else {
+
+ // Ray and segment are parallel.
+
+ s1 = ( a01 > 0 ) ? - segExtent : segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ if ( optionalPointOnRay ) {
+
+ optionalPointOnRay.copy( this.direction.clone().multiplyScalar( s0 ).add( this.origin ) );
+
+ }
+
+ if ( optionalPointOnSegment ) {
+
+ optionalPointOnSegment.copy( segDir.clone().multiplyScalar( s1 ).add( segCenter ) );
+
+ }
+
+ return sqrDist;
+
+ },
+
+ isIntersectionSphere: function ( sphere ) {
+
+ return this.distanceToPoint( sphere.center ) <= sphere.radius;
+
+ },
+
+ isIntersectionPlane: function ( plane ) {
+
+ // check if the ray lies on the plane first
+
+ var distToPoint = plane.distanceToPoint( this.origin );
+
+ if ( distToPoint === 0 ) {
+
+ return true;
+
+ }
+
+ var denominator = plane.normal.dot( this.direction );
+
+ if ( denominator * distToPoint < 0 ) {
+
+ return true
+
+ }
+
+ // ray origin is behind the plane (and is pointing behind it)
+
+ return false;
+
+ },
+
+ distanceToPlane: function ( plane ) {
+
+ var denominator = plane.normal.dot( this.direction );
+ if ( denominator == 0 ) {
+
+ // line is coplanar, return origin
+ if( plane.distanceToPoint( this.origin ) == 0 ) {
+
+ return 0;
+
+ }
+
+ // Null is preferable to undefined since undefined means.... it is undefined
+
+ return null;
+
+ }
+
+ var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
+
+ // Return if the ray never intersects the plane
+
+ return t >= 0 ? t : null;
+
+ },
+
+ intersectPlane: function ( plane, optionalTarget ) {
+
+ var t = this.distanceToPlane( plane );
+
+ if ( t === null ) {
+
+ return null;
+ }
+
+ return this.at( t, optionalTarget );
+
+ },
+
+ isIntersectionBox: function () {
+
+ var v = new THREE.Vector3();
+
+ return function ( box ) {
+
+ return this.intersectBox( box, v ) !== null;
+
+ }
+
+ }(),
+
+ intersectBox: function ( box , optionalTarget ) {
+
+ // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/
+
+ var tmin,tmax,tymin,tymax,tzmin,tzmax;
+
+ var invdirx = 1/this.direction.x,
+ invdiry = 1/this.direction.y,
+ invdirz = 1/this.direction.z;
+
+ var origin = this.origin;
+
+ if (invdirx >= 0) {
+
+ tmin = (box.min.x - origin.x) * invdirx;
+ tmax = (box.max.x - origin.x) * invdirx;
+
+ } else {
+
+ tmin = (box.max.x - origin.x) * invdirx;
+ tmax = (box.min.x - origin.x) * invdirx;
+ }
+
+ if (invdiry >= 0) {
+
+ tymin = (box.min.y - origin.y) * invdiry;
+ tymax = (box.max.y - origin.y) * invdiry;
+
+ } else {
+
+ tymin = (box.max.y - origin.y) * invdiry;
+ tymax = (box.min.y - origin.y) * invdiry;
+ }
+
+ if ((tmin > tymax) || (tymin > tmax)) return null;
+
+ // These lines also handle the case where tmin or tmax is NaN
+ // (result of 0 * Infinity). x !== x returns true if x is NaN
+
+ if (tymin > tmin || tmin !== tmin ) tmin = tymin;
+
+ if (tymax < tmax || tmax !== tmax ) tmax = tymax;
+
+ if (invdirz >= 0) {
+
+ tzmin = (box.min.z - origin.z) * invdirz;
+ tzmax = (box.max.z - origin.z) * invdirz;
+
+ } else {
+
+ tzmin = (box.max.z - origin.z) * invdirz;
+ tzmax = (box.min.z - origin.z) * invdirz;
+ }
+
+ if ((tmin > tzmax) || (tzmin > tmax)) return null;
+
+ if (tzmin > tmin || tmin !== tmin ) tmin = tzmin;
+
+ if (tzmax < tmax || tmax !== tmax ) tmax = tzmax;
+
+ //return point closest to the ray (positive side)
+
+ if ( tmax < 0 ) return null;
+
+ return this.at( tmin >= 0 ? tmin : tmax, optionalTarget );
+
+ },
+
+ intersectTriangle: function() {
+
+ // Compute the offset origin, edges, and normal.
+ var diff = new THREE.Vector3();
+ var edge1 = new THREE.Vector3();
+ var edge2 = new THREE.Vector3();
+ var normal = new THREE.Vector3();
+
+ return function ( a, b, c, backfaceCulling, optionalTarget ) {
+
+ // from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp
+
+ edge1.subVectors( b, a );
+ edge2.subVectors( c, a );
+ normal.crossVectors( edge1, edge2 );
+
+ // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
+ // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
+ // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
+ // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
+ // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
+ var DdN = this.direction.dot( normal );
+ var sign;
+
+ if ( DdN > 0 ) {
+
+ if ( backfaceCulling ) return null;
+ sign = 1;
+
+ } else if ( DdN < 0 ) {
+
+ sign = - 1;
+ DdN = - DdN;
+
+ } else {
+
+ return null;
+
+ }
+
+ diff.subVectors( this.origin, a );
+ var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) );
+
+ // b1 < 0, no intersection
+ if ( DdQxE2 < 0 ) {
+
+ return null;
+
+ }
+
+ var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) );
+
+ // b2 < 0, no intersection
+ if ( DdE1xQ < 0 ) {
+
+ return null;
+
+ }
+
+ // b1+b2 > 1, no intersection
+ if ( DdQxE2 + DdE1xQ > DdN ) {
+
+ return null;
+
+ }
+
+ // Line intersects triangle, check if ray does.
+ var QdN = - sign * diff.dot( normal );
+
+ // t < 0, no intersection
+ if ( QdN < 0 ) {
+
+ return null;
+
+ }
+
+ // Ray intersects triangle.
+ return this.at( QdN / DdN, optionalTarget );
+
+ }
+
+ }(),
+
+ applyMatrix4: function ( matrix4 ) {
+
+ this.direction.add( this.origin ).applyMatrix4( matrix4 );
+ this.origin.applyMatrix4( matrix4 );
+ this.direction.sub( this.origin );
+ this.direction.normalize();
+
+ return this;
+ },
+
+ equals: function ( ray ) {
+
+ return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Ray().copy( this );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Sphere = function ( center, radius ) {
+
+ this.center = ( center !== undefined ) ? center : new THREE.Vector3();
+ this.radius = ( radius !== undefined ) ? radius : 0;
+
+};
+
+THREE.Sphere.prototype = {
+
+ constructor: THREE.Sphere,
+
+ set: function ( center, radius ) {
+
+ this.center.copy( center );
+ this.radius = radius;
+
+ return this;
+ },
+
+
+ setFromPoints: function () {
+
+ var box = new THREE.Box3();
+
+ return function ( points, optionalCenter ) {
+
+ var center = this.center;
+
+ if ( optionalCenter !== undefined ) {
+
+ center.copy( optionalCenter );
+
+ } else {
+
+ box.setFromPoints( points ).center( center );
+
+ }
+
+ var maxRadiusSq = 0;
+
+ for ( var i = 0, il = points.length; i < il; i ++ ) {
+
+ maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) );
+
+ }
+
+ this.radius = Math.sqrt( maxRadiusSq );
+
+ return this;
+
+ };
+
+ }(),
+
+ copy: function ( sphere ) {
+
+ this.center.copy( sphere.center );
+ this.radius = sphere.radius;
+
+ return this;
+
+ },
+
+ empty: function () {
+
+ return ( this.radius <= 0 );
+
+ },
+
+ containsPoint: function ( point ) {
+
+ return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );
+
+ },
+
+ distanceToPoint: function ( point ) {
+
+ return ( point.distanceTo( this.center ) - this.radius );
+
+ },
+
+ intersectsSphere: function ( sphere ) {
+
+ var radiusSum = this.radius + sphere.radius;
+
+ return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );
+
+ },
+
+ clampPoint: function ( point, optionalTarget ) {
+
+ var deltaLengthSq = this.center.distanceToSquared( point );
+
+ var result = optionalTarget || new THREE.Vector3();
+ result.copy( point );
+
+ if ( deltaLengthSq > ( this.radius * this.radius ) ) {
+
+ result.sub( this.center ).normalize();
+ result.multiplyScalar( this.radius ).add( this.center );
+
+ }
+
+ return result;
+
+ },
+
+ getBoundingBox: function ( optionalTarget ) {
+
+ var box = optionalTarget || new THREE.Box3();
+
+ box.set( this.center, this.center );
+ box.expandByScalar( this.radius );
+
+ return box;
+
+ },
+
+ applyMatrix4: function ( matrix ) {
+
+ this.center.applyMatrix4( matrix );
+ this.radius = this.radius * matrix.getMaxScaleOnAxis();
+
+ return this;
+
+ },
+
+ translate: function ( offset ) {
+
+ this.center.add( offset );
+
+ return this;
+
+ },
+
+ equals: function ( sphere ) {
+
+ return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Sphere().copy( this );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Frustum = function ( p0, p1, p2, p3, p4, p5 ) {
+
+ this.planes = [
+
+ ( p0 !== undefined ) ? p0 : new THREE.Plane(),
+ ( p1 !== undefined ) ? p1 : new THREE.Plane(),
+ ( p2 !== undefined ) ? p2 : new THREE.Plane(),
+ ( p3 !== undefined ) ? p3 : new THREE.Plane(),
+ ( p4 !== undefined ) ? p4 : new THREE.Plane(),
+ ( p5 !== undefined ) ? p5 : new THREE.Plane()
+
+ ];
+
+};
+
+THREE.Frustum.prototype = {
+
+ constructor: THREE.Frustum,
+
+ set: function ( p0, p1, p2, p3, p4, p5 ) {
+
+ var planes = this.planes;
+
+ planes[0].copy( p0 );
+ planes[1].copy( p1 );
+ planes[2].copy( p2 );
+ planes[3].copy( p3 );
+ planes[4].copy( p4 );
+ planes[5].copy( p5 );
+
+ return this;
+
+ },
+
+ copy: function ( frustum ) {
+
+ var planes = this.planes;
+
+ for( var i = 0; i < 6; i ++ ) {
+
+ planes[i].copy( frustum.planes[i] );
+
+ }
+
+ return this;
+
+ },
+
+ setFromMatrix: function ( m ) {
+
+ var planes = this.planes;
+ var me = m.elements;
+ var me0 = me[0], me1 = me[1], me2 = me[2], me3 = me[3];
+ var me4 = me[4], me5 = me[5], me6 = me[6], me7 = me[7];
+ var me8 = me[8], me9 = me[9], me10 = me[10], me11 = me[11];
+ var me12 = me[12], me13 = me[13], me14 = me[14], me15 = me[15];
+
+ planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();
+ planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();
+ planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();
+ planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();
+ planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();
+ planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();
+
+ return this;
+
+ },
+
+ intersectsObject: function () {
+
+ var sphere = new THREE.Sphere();
+
+ return function ( object ) {
+
+ var geometry = object.geometry;
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ sphere.copy( geometry.boundingSphere );
+ sphere.applyMatrix4( object.matrixWorld );
+
+ return this.intersectsSphere( sphere );
+
+ };
+
+ }(),
+
+ intersectsSphere: function ( sphere ) {
+
+ var planes = this.planes;
+ var center = sphere.center;
+ var negRadius = -sphere.radius;
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ var distance = planes[ i ].distanceToPoint( center );
+
+ if ( distance < negRadius ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ },
+
+ intersectsBox : function() {
+
+ var p1 = new THREE.Vector3(),
+ p2 = new THREE.Vector3();
+
+ return function( box ) {
+
+ var planes = this.planes;
+
+ for ( var i = 0; i < 6 ; i ++ ) {
+
+ var plane = planes[i];
+
+ p1.x = plane.normal.x > 0 ? box.min.x : box.max.x;
+ p2.x = plane.normal.x > 0 ? box.max.x : box.min.x;
+ p1.y = plane.normal.y > 0 ? box.min.y : box.max.y;
+ p2.y = plane.normal.y > 0 ? box.max.y : box.min.y;
+ p1.z = plane.normal.z > 0 ? box.min.z : box.max.z;
+ p2.z = plane.normal.z > 0 ? box.max.z : box.min.z;
+
+ var d1 = plane.distanceToPoint( p1 );
+ var d2 = plane.distanceToPoint( p2 );
+
+ // if both outside plane, no intersection
+
+ if ( d1 < 0 && d2 < 0 ) {
+
+ return false;
+
+ }
+ }
+
+ return true;
+ };
+
+ }(),
+
+
+ containsPoint: function ( point ) {
+
+ var planes = this.planes;
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ if ( planes[ i ].distanceToPoint( point ) < 0 ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ },
+
+ clone: function () {
+
+ return new THREE.Frustum().copy( this );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Plane = function ( normal, constant ) {
+
+ this.normal = ( normal !== undefined ) ? normal : new THREE.Vector3( 1, 0, 0 );
+ this.constant = ( constant !== undefined ) ? constant : 0;
+
+};
+
+THREE.Plane.prototype = {
+
+ constructor: THREE.Plane,
+
+ set: function ( normal, constant ) {
+
+ this.normal.copy( normal );
+ this.constant = constant;
+
+ return this;
+
+ },
+
+ setComponents: function ( x, y, z, w ) {
+
+ this.normal.set( x, y, z );
+ this.constant = w;
+
+ return this;
+
+ },
+
+ setFromNormalAndCoplanarPoint: function ( normal, point ) {
+
+ this.normal.copy( normal );
+ this.constant = - point.dot( this.normal ); // must be this.normal, not normal, as this.normal is normalized
+
+ return this;
+
+ },
+
+ setFromCoplanarPoints: function() {
+
+ var v1 = new THREE.Vector3();
+ var v2 = new THREE.Vector3();
+
+ return function ( a, b, c ) {
+
+ var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize();
+
+ // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
+
+ this.setFromNormalAndCoplanarPoint( normal, a );
+
+ return this;
+
+ };
+
+ }(),
+
+
+ copy: function ( plane ) {
+
+ this.normal.copy( plane.normal );
+ this.constant = plane.constant;
+
+ return this;
+
+ },
+
+ normalize: function () {
+
+ // Note: will lead to a divide by zero if the plane is invalid.
+
+ var inverseNormalLength = 1.0 / this.normal.length();
+ this.normal.multiplyScalar( inverseNormalLength );
+ this.constant *= inverseNormalLength;
+
+ return this;
+
+ },
+
+ negate: function () {
+
+ this.constant *= -1;
+ this.normal.negate();
+
+ return this;
+
+ },
+
+ distanceToPoint: function ( point ) {
+
+ return this.normal.dot( point ) + this.constant;
+
+ },
+
+ distanceToSphere: function ( sphere ) {
+
+ return this.distanceToPoint( sphere.center ) - sphere.radius;
+
+ },
+
+ projectPoint: function ( point, optionalTarget ) {
+
+ return this.orthoPoint( point, optionalTarget ).sub( point ).negate();
+
+ },
+
+ orthoPoint: function ( point, optionalTarget ) {
+
+ var perpendicularMagnitude = this.distanceToPoint( point );
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.copy( this.normal ).multiplyScalar( perpendicularMagnitude );
+
+ },
+
+ isIntersectionLine: function ( line ) {
+
+ // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
+
+ var startSign = this.distanceToPoint( line.start );
+ var endSign = this.distanceToPoint( line.end );
+
+ return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );
+
+ },
+
+ intersectLine: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( line, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ var direction = line.delta( v1 );
+
+ var denominator = this.normal.dot( direction );
+
+ if ( denominator == 0 ) {
+
+ // line is coplanar, return origin
+ if( this.distanceToPoint( line.start ) == 0 ) {
+
+ return result.copy( line.start );
+
+ }
+
+ // Unsure if this is the correct method to handle this case.
+ return undefined;
+
+ }
+
+ var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;
+
+ if( t < 0 || t > 1 ) {
+
+ return undefined;
+
+ }
+
+ return result.copy( direction ).multiplyScalar( t ).add( line.start );
+
+ };
+
+ }(),
+
+
+ coplanarPoint: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.copy( this.normal ).multiplyScalar( - this.constant );
+
+ },
+
+ applyMatrix4: function() {
+
+ var v1 = new THREE.Vector3();
+ var v2 = new THREE.Vector3();
+
+ return function ( matrix, optionalNormalMatrix ) {
+
+ // compute new normal based on theory here:
+ // http://www.songho.ca/opengl/gl_normaltransform.html
+ optionalNormalMatrix = optionalNormalMatrix || new THREE.Matrix3().getNormalMatrix( matrix );
+ var newNormal = v1.copy( this.normal ).applyMatrix3( optionalNormalMatrix );
+
+ var newCoplanarPoint = this.coplanarPoint( v2 );
+ newCoplanarPoint.applyMatrix4( matrix );
+
+ this.setFromNormalAndCoplanarPoint( newNormal, newCoplanarPoint );
+
+ return this;
+
+ };
+
+ }(),
+
+ translate: function ( offset ) {
+
+ this.constant = this.constant - offset.dot( this.normal );
+
+ return this;
+
+ },
+
+ equals: function ( plane ) {
+
+ return plane.normal.equals( this.normal ) && ( plane.constant == this.constant );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Plane().copy( this );
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Math = {
+
+ PI2: Math.PI * 2,
+
+ generateUUID: function () {
+
+ // http://www.broofa.com/Tools/Math.uuid.htm
+
+ var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+ var uuid = new Array(36);
+ var rnd = 0, r;
+
+ return function () {
+
+ for ( var i = 0; i < 36; i ++ ) {
+
+ if ( i == 8 || i == 13 || i == 18 || i == 23 ) {
+
+ uuid[ i ] = '-';
+
+ } else if ( i == 14 ) {
+
+ uuid[ i ] = '4';
+
+ } else {
+
+ if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0;
+ r = rnd & 0xf;
+ rnd = rnd >> 4;
+ uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
+
+ }
+ }
+
+ return uuid.join('');
+
+ };
+
+ }(),
+
+ // Clamp value to range
+
+ clamp: function ( x, a, b ) {
+
+ return ( x < a ) ? a : ( ( x > b ) ? b : x );
+
+ },
+
+ // Clamp value to range to range
+
+ mapLinear: function ( x, a1, a2, b1, b2 ) {
+
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+
+ },
+
+ // http://en.wikipedia.org/wiki/Smoothstep
+
+ smoothstep: function ( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min )/( max - min );
+
+ return x*x*(3 - 2*x);
+
+ },
+
+ smootherstep: function ( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min )/( max - min );
+
+ return x*x*x*(x*(x*6 - 15) + 10);
+
+ },
+
+ // Random float from <0, 1> with 16 bits of randomness
+ // (standard Math.random() creates repetitive patterns when applied over larger space)
+
+ random16: function () {
+
+ return ( 65280 * Math.random() + 255 * Math.random() ) / 65535;
+
+ },
+
+ // Random integer from interval
+
+ randInt: function ( low, high ) {
+
+ return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+ },
+
+ // Random float from interval
+
+ randFloat: function ( low, high ) {
+
+ return low + Math.random() * ( high - low );
+
+ },
+
+ // Random float from <-range/2, range/2> interval
+
+ randFloatSpread: function ( range ) {
+
+ return range * ( 0.5 - Math.random() );
+
+ },
+
+ sign: function ( x ) {
+
+ return ( x < 0 ) ? -1 : ( ( x > 0 ) ? 1 : 0 );
+
+ },
+
+ degToRad: function() {
+
+ var degreeToRadiansFactor = Math.PI / 180;
+
+ return function ( degrees ) {
+
+ return degrees * degreeToRadiansFactor;
+
+ };
+
+ }(),
+
+ radToDeg: function() {
+
+ var radianToDegreesFactor = 180 / Math.PI;
+
+ return function ( radians ) {
+
+ return radians * radianToDegreesFactor;
+
+ };
+
+ }()
+
+};
+
+/**
+ * Spline from Tween.js, slightly optimized (and trashed)
+ * http://sole.github.com/tween.js/examples/05_spline.html
+ *
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Spline = function ( points ) {
+
+ this.points = points;
+
+ var c = [], v3 = { x: 0, y: 0, z: 0 },
+ point, intPoint, weight, w2, w3,
+ pa, pb, pc, pd;
+
+ this.initFromArray = function( a ) {
+
+ this.points = [];
+
+ for ( var i = 0; i < a.length; i++ ) {
+
+ this.points[ i ] = { x: a[ i ][ 0 ], y: a[ i ][ 1 ], z: a[ i ][ 2 ] };
+
+ }
+
+ };
+
+ this.getPoint = function ( k ) {
+
+ point = ( this.points.length - 1 ) * k;
+ intPoint = Math.floor( point );
+ weight = point - intPoint;
+
+ c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1;
+ c[ 1 ] = intPoint;
+ c[ 2 ] = intPoint > this.points.length - 2 ? this.points.length - 1 : intPoint + 1;
+ c[ 3 ] = intPoint > this.points.length - 3 ? this.points.length - 1 : intPoint + 2;
+
+ pa = this.points[ c[ 0 ] ];
+ pb = this.points[ c[ 1 ] ];
+ pc = this.points[ c[ 2 ] ];
+ pd = this.points[ c[ 3 ] ];
+
+ w2 = weight * weight;
+ w3 = weight * w2;
+
+ v3.x = interpolate( pa.x, pb.x, pc.x, pd.x, weight, w2, w3 );
+ v3.y = interpolate( pa.y, pb.y, pc.y, pd.y, weight, w2, w3 );
+ v3.z = interpolate( pa.z, pb.z, pc.z, pd.z, weight, w2, w3 );
+
+ return v3;
+
+ };
+
+ this.getControlPointsArray = function () {
+
+ var i, p, l = this.points.length,
+ coords = [];
+
+ for ( i = 0; i < l; i ++ ) {
+
+ p = this.points[ i ];
+ coords[ i ] = [ p.x, p.y, p.z ];
+
+ }
+
+ return coords;
+
+ };
+
+ // approximate length by summing linear segments
+
+ this.getLength = function ( nSubDivisions ) {
+
+ var i, index, nSamples, position,
+ point = 0, intPoint = 0, oldIntPoint = 0,
+ oldPosition = new THREE.Vector3(),
+ tmpVec = new THREE.Vector3(),
+ chunkLengths = [],
+ totalLength = 0;
+
+ // first point has 0 length
+
+ chunkLengths[ 0 ] = 0;
+
+ if ( !nSubDivisions ) nSubDivisions = 100;
+
+ nSamples = this.points.length * nSubDivisions;
+
+ oldPosition.copy( this.points[ 0 ] );
+
+ for ( i = 1; i < nSamples; i ++ ) {
+
+ index = i / nSamples;
+
+ position = this.getPoint( index );
+ tmpVec.copy( position );
+
+ totalLength += tmpVec.distanceTo( oldPosition );
+
+ oldPosition.copy( position );
+
+ point = ( this.points.length - 1 ) * index;
+ intPoint = Math.floor( point );
+
+ if ( intPoint != oldIntPoint ) {
+
+ chunkLengths[ intPoint ] = totalLength;
+ oldIntPoint = intPoint;
+
+ }
+
+ }
+
+ // last point ends with total length
+
+ chunkLengths[ chunkLengths.length ] = totalLength;
+
+ return { chunks: chunkLengths, total: totalLength };
+
+ };
+
+ this.reparametrizeByArcLength = function ( samplingCoef ) {
+
+ var i, j,
+ index, indexCurrent, indexNext,
+ linearDistance, realDistance,
+ sampling, position,
+ newpoints = [],
+ tmpVec = new THREE.Vector3(),
+ sl = this.getLength();
+
+ newpoints.push( tmpVec.copy( this.points[ 0 ] ).clone() );
+
+ for ( i = 1; i < this.points.length; i++ ) {
+
+ //tmpVec.copy( this.points[ i - 1 ] );
+ //linearDistance = tmpVec.distanceTo( this.points[ i ] );
+
+ realDistance = sl.chunks[ i ] - sl.chunks[ i - 1 ];
+
+ sampling = Math.ceil( samplingCoef * realDistance / sl.total );
+
+ indexCurrent = ( i - 1 ) / ( this.points.length - 1 );
+ indexNext = i / ( this.points.length - 1 );
+
+ for ( j = 1; j < sampling - 1; j++ ) {
+
+ index = indexCurrent + j * ( 1 / sampling ) * ( indexNext - indexCurrent );
+
+ position = this.getPoint( index );
+ newpoints.push( tmpVec.copy( position ).clone() );
+
+ }
+
+ newpoints.push( tmpVec.copy( this.points[ i ] ).clone() );
+
+ }
+
+ this.points = newpoints;
+
+ };
+
+ // Catmull-Rom
+
+ function interpolate( p0, p1, p2, p3, t, t2, t3 ) {
+
+ var v0 = ( p2 - p0 ) * 0.5,
+ v1 = ( p3 - p1 ) * 0.5;
+
+ return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+ };
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Triangle = function ( a, b, c ) {
+
+ this.a = ( a !== undefined ) ? a : new THREE.Vector3();
+ this.b = ( b !== undefined ) ? b : new THREE.Vector3();
+ this.c = ( c !== undefined ) ? c : new THREE.Vector3();
+
+};
+
+THREE.Triangle.normal = function() {
+
+ var v0 = new THREE.Vector3();
+
+ return function ( a, b, c, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ result.subVectors( c, b );
+ v0.subVectors( a, b );
+ result.cross( v0 );
+
+ var resultLengthSq = result.lengthSq();
+ if( resultLengthSq > 0 ) {
+
+ return result.multiplyScalar( 1 / Math.sqrt( resultLengthSq ) );
+
+ }
+
+ return result.set( 0, 0, 0 );
+
+ };
+
+}();
+
+// static/instance method to calculate barycoordinates
+// based on: http://www.blackpawn.com/texts/pointinpoly/default.html
+THREE.Triangle.barycoordFromPoint = function() {
+
+ var v0 = new THREE.Vector3();
+ var v1 = new THREE.Vector3();
+ var v2 = new THREE.Vector3();
+
+ return function ( point, a, b, c, optionalTarget ) {
+
+ v0.subVectors( c, a );
+ v1.subVectors( b, a );
+ v2.subVectors( point, a );
+
+ var dot00 = v0.dot( v0 );
+ var dot01 = v0.dot( v1 );
+ var dot02 = v0.dot( v2 );
+ var dot11 = v1.dot( v1 );
+ var dot12 = v1.dot( v2 );
+
+ var denom = ( dot00 * dot11 - dot01 * dot01 );
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ // colinear or singular triangle
+ if( denom == 0 ) {
+ // arbitrary location outside of triangle?
+ // not sure if this is the best idea, maybe should be returning undefined
+ return result.set( -2, -1, -1 );
+ }
+
+ var invDenom = 1 / denom;
+ var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
+ var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
+
+ // barycoordinates must always sum to 1
+ return result.set( 1 - u - v, v, u );
+
+ };
+
+}();
+
+THREE.Triangle.containsPoint = function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( point, a, b, c ) {
+
+ var result = THREE.Triangle.barycoordFromPoint( point, a, b, c, v1 );
+
+ return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 );
+
+ };
+
+}();
+
+THREE.Triangle.prototype = {
+
+ constructor: THREE.Triangle,
+
+ set: function ( a, b, c ) {
+
+ this.a.copy( a );
+ this.b.copy( b );
+ this.c.copy( c );
+
+ return this;
+
+ },
+
+ setFromPointsAndIndices: function ( points, i0, i1, i2 ) {
+
+ this.a.copy( points[i0] );
+ this.b.copy( points[i1] );
+ this.c.copy( points[i2] );
+
+ return this;
+
+ },
+
+ copy: function ( triangle ) {
+
+ this.a.copy( triangle.a );
+ this.b.copy( triangle.b );
+ this.c.copy( triangle.c );
+
+ return this;
+
+ },
+
+ area: function() {
+
+ var v0 = new THREE.Vector3();
+ var v1 = new THREE.Vector3();
+
+ return function () {
+
+ v0.subVectors( this.c, this.b );
+ v1.subVectors( this.a, this.b );
+
+ return v0.cross( v1 ).length() * 0.5;
+
+ };
+
+ }(),
+
+ midpoint: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 );
+
+ },
+
+ normal: function ( optionalTarget ) {
+
+ return THREE.Triangle.normal( this.a, this.b, this.c, optionalTarget );
+
+ },
+
+ plane: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Plane();
+
+ return result.setFromCoplanarPoints( this.a, this.b, this.c );
+
+ },
+
+ barycoordFromPoint: function ( point, optionalTarget ) {
+
+ return THREE.Triangle.barycoordFromPoint( point, this.a, this.b, this.c, optionalTarget );
+
+ },
+
+ containsPoint: function ( point ) {
+
+ return THREE.Triangle.containsPoint( point, this.a, this.b, this.c );
+
+ },
+
+ equals: function ( triangle ) {
+
+ return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Triangle().copy( this );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Vertex = function ( v ) {
+
+ console.warn( 'THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.')
+ return v;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.UV = function ( u, v ) {
+
+ console.warn( 'THREE.UV has been DEPRECATED. Use THREE.Vector2 instead.')
+ return new THREE.Vector2( u, v );
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Clock = function ( autoStart ) {
+
+ this.autoStart = ( autoStart !== undefined ) ? autoStart : true;
+
+ this.startTime = 0;
+ this.oldTime = 0;
+ this.elapsedTime = 0;
+
+ this.running = false;
+
+};
+
+THREE.Clock.prototype = {
+
+ constructor: THREE.Clock,
+
+ start: function () {
+
+ this.startTime = self.performance !== undefined && self.performance.now !== undefined
+ ? self.performance.now()
+ : Date.now();
+
+ this.oldTime = this.startTime;
+ this.running = true;
+ },
+
+ stop: function () {
+
+ this.getElapsedTime();
+ this.running = false;
+
+ },
+
+ getElapsedTime: function () {
+
+ this.getDelta();
+ return this.elapsedTime;
+
+ },
+
+ getDelta: function () {
+
+ var diff = 0;
+
+ if ( this.autoStart && ! this.running ) {
+
+ this.start();
+
+ }
+
+ if ( this.running ) {
+
+ var newTime = self.performance !== undefined && self.performance.now !== undefined
+ ? self.performance.now()
+ : Date.now();
+
+ diff = 0.001 * ( newTime - this.oldTime );
+ this.oldTime = newTime;
+
+ this.elapsedTime += diff;
+
+ }
+
+ return diff;
+
+ }
+
+};
+
+/**
+ * https://github.com/mrdoob/eventdispatcher.js/
+ */
+
+THREE.EventDispatcher = function () {}
+
+THREE.EventDispatcher.prototype = {
+
+ constructor: THREE.EventDispatcher,
+
+ apply: function ( object ) {
+
+ object.addEventListener = THREE.EventDispatcher.prototype.addEventListener;
+ object.hasEventListener = THREE.EventDispatcher.prototype.hasEventListener;
+ object.removeEventListener = THREE.EventDispatcher.prototype.removeEventListener;
+ object.dispatchEvent = THREE.EventDispatcher.prototype.dispatchEvent;
+
+ },
+
+ addEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) this._listeners = {};
+
+ var listeners = this._listeners;
+
+ if ( listeners[ type ] === undefined ) {
+
+ listeners[ type ] = [];
+
+ }
+
+ if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+
+ listeners[ type ].push( listener );
+
+ }
+
+ },
+
+ hasEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) return false;
+
+ var listeners = this._listeners;
+
+ if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) {
+
+ return true;
+
+ }
+
+ return false;
+
+ },
+
+ removeEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) return;
+
+ var listeners = this._listeners;
+ var index = listeners[ type ].indexOf( listener );
+
+ if ( index !== - 1 ) {
+
+ listeners[ type ].splice( index, 1 );
+
+ }
+
+ },
+
+ dispatchEvent: function ( event ) {
+
+ if ( this._listeners === undefined ) return;
+
+ var listeners = this._listeners;
+ var listenerArray = listeners[ event.type ];
+
+ if ( listenerArray !== undefined ) {
+
+ event.target = this;
+
+ for ( var i = 0, l = listenerArray.length; i < l; i ++ ) {
+
+ listenerArray[ i ].call( this, event );
+
+ }
+
+ }
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author bhouston / http://exocortex.com/
+ * @author stephomi / http://stephaneginier.com/
+ */
+
+( function ( THREE ) {
+
+ THREE.Raycaster = function ( origin, direction, near, far ) {
+
+ this.ray = new THREE.Ray( origin, direction );
+ // direction is assumed to be normalized (for accurate distance calculations)
+
+ this.near = near || 0;
+ this.far = far || Infinity;
+
+ };
+
+ var sphere = new THREE.Sphere();
+ var localRay = new THREE.Ray();
+ var facePlane = new THREE.Plane();
+ var intersectPoint = new THREE.Vector3();
+ var matrixPosition = new THREE.Vector3();
+
+ var inverseMatrix = new THREE.Matrix4();
+
+ var descSort = function ( a, b ) {
+
+ return a.distance - b.distance;
+
+ };
+
+ var vA = new THREE.Vector3();
+ var vB = new THREE.Vector3();
+ var vC = new THREE.Vector3();
+
+ var intersectObject = function ( object, raycaster, intersects ) {
+
+ if ( object instanceof THREE.Particle ) {
+
+ matrixPosition.getPositionFromMatrix( object.matrixWorld );
+ var distance = raycaster.ray.distanceToPoint( matrixPosition );
+
+ if ( distance > object.scale.x ) {
+
+ return intersects;
+
+ }
+
+ intersects.push( {
+
+ distance: distance,
+ point: object.position,
+ face: null,
+ object: object
+
+ } );
+
+ } else if ( object instanceof THREE.LOD ) {
+
+ matrixPosition.getPositionFromMatrix( object.matrixWorld );
+ var distance = raycaster.ray.origin.distanceTo( matrixPosition );
+
+ intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );
+
+ } else if ( object instanceof THREE.Mesh ) {
+
+ var geometry = object.geometry;
+
+ // Checking boundingSphere distance to ray
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ sphere.copy( geometry.boundingSphere );
+ sphere.applyMatrix4( object.matrixWorld );
+
+ if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {
+
+ return intersects;
+
+ }
+
+ // Check boundingBox before continuing
+
+ inverseMatrix.getInverse( object.matrixWorld );
+ localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );
+
+ if ( geometry.boundingBox !== null ) {
+
+ if ( localRay.isIntersectionBox( geometry.boundingBox ) === false ) {
+
+ return intersects;
+
+ }
+
+ }
+
+ if ( geometry instanceof THREE.BufferGeometry ) {
+
+ var material = object.material;
+
+ if ( material === undefined ) return intersects;
+ if ( geometry.dynamic === false ) return intersects;
+
+ var a, b, c;
+ var precision = raycaster.precision;
+
+ if ( geometry.attributes.index !== undefined ) {
+
+ var offsets = geometry.offsets;
+ var indices = geometry.attributes.index.array;
+ var positions = geometry.attributes.position.array;
+ var offLength = geometry.offsets.length;
+
+ var fl = geometry.attributes.index.array.length / 3;
+
+ for ( var oi = 0; oi < offLength; ++oi ) {
+
+ var start = offsets[ oi ].start;
+ var count = offsets[ oi ].count;
+ var index = offsets[ oi ].index;
+
+ for ( var i = start, il = start + count; i < il; i += 3 ) {
+
+ a = index + indices[ i ];
+ b = index + indices[ i + 1 ];
+ c = index + indices[ i + 2 ];
+
+ vA.set(
+ positions[ a * 3 ],
+ positions[ a * 3 + 1 ],
+ positions[ a * 3 + 2 ]
+ );
+ vB.set(
+ positions[ b * 3 ],
+ positions[ b * 3 + 1 ],
+ positions[ b * 3 + 2 ]
+ );
+ vC.set(
+ positions[ c * 3 ],
+ positions[ c * 3 + 1 ],
+ positions[ c * 3 + 2 ]
+ );
+
+ var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide );
+
+ if ( intersectionPoint === null ) continue;
+
+ intersectionPoint.applyMatrix4( object.matrixWorld );
+
+ var distance = raycaster.ray.origin.distanceTo( intersectionPoint );
+
+ if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ point: intersectionPoint,
+ face: null,
+ faceIndex: null,
+ object: object
+
+ } );
+
+ }
+
+ }
+
+ } else {
+
+ var offsets = geometry.offsets;
+ var positions = geometry.attributes.position.array;
+ var offLength = geometry.offsets.length;
+
+ var fl = geometry.attributes.position.array.length;
+
+ for ( var i = 0; i < fl; i += 3 ) {
+
+ a = i;
+ b = i + 1;
+ c = i + 2;
+
+ vA.set(
+ positions[ a * 3 ],
+ positions[ a * 3 + 1 ],
+ positions[ a * 3 + 2 ]
+ );
+ vB.set(
+ positions[ b * 3 ],
+ positions[ b * 3 + 1 ],
+ positions[ b * 3 + 2 ]
+ );
+ vC.set(
+ positions[ c * 3 ],
+ positions[ c * 3 + 1 ],
+ positions[ c * 3 + 2 ]
+ );
+
+ var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide );
+
+ if ( intersectionPoint === null ) continue;
+
+ intersectionPoint.applyMatrix4( object.matrixWorld );
+
+ var distance = raycaster.ray.origin.distanceTo( intersectionPoint );
+
+ if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ point: intersectionPoint,
+ face: null,
+ faceIndex: null,
+ object: object
+
+ } );
+
+ }
+
+ }
+
+ } else if ( geometry instanceof THREE.Geometry ) {
+
+ var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
+ var objectMaterials = isFaceMaterial === true ? object.material.materials : null;
+
+ var a, b, c, d;
+ var precision = raycaster.precision;
+
+ var vertices = geometry.vertices;
+
+ for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
+
+ var face = geometry.faces[ f ];
+
+ var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;
+
+ if ( material === undefined ) continue;
+
+ a = vertices[ face.a ];
+ b = vertices[ face.b ];
+ c = vertices[ face.c ];
+
+ var intersectionPoint = localRay.intersectTriangle( a, b, c, material.side !== THREE.DoubleSide );
+
+ if ( intersectionPoint === null ) continue;
+
+ intersectionPoint.applyMatrix4( object.matrixWorld );
+
+ var distance = raycaster.ray.origin.distanceTo( intersectionPoint );
+
+ if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ point: intersectionPoint,
+ face: face,
+ faceIndex: f,
+ object: object
+
+ } );
+
+ }
+
+ }
+
+ } else if ( object instanceof THREE.Line ) {
+
+ var precision = raycaster.linePrecision;
+ var precisionSq = precision * precision;
+
+ var geometry = object.geometry;
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ // Checking boundingSphere distance to ray
+
+ sphere.copy( geometry.boundingSphere );
+ sphere.applyMatrix4( object.matrixWorld );
+
+ if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {
+
+ return intersects;
+
+ }
+
+ inverseMatrix.getInverse( object.matrixWorld );
+ localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );
+
+ /* if ( geometry instanceof THREE.BufferGeometry ) {
+
+ } else */ if ( geometry instanceof THREE.Geometry ) {
+
+ var vertices = geometry.vertices;
+ var nbVertices = vertices.length;
+ var interSegment = new THREE.Vector3();
+ var interRay = new THREE.Vector3();
+ var step = object.type === THREE.LineStrip ? 1 : 2;
+
+ for ( var i = 0; i < nbVertices - 1; i = i + step ) {
+
+ var distSq = localRay.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );
+
+ if ( distSq > precisionSq ) continue;
+
+ var distance = localRay.origin.distanceTo( interRay );
+
+ if ( distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ // What do we want? intersection point on the ray or on the segment??
+ // point: raycaster.ray.at( distance ),
+ point: interSegment.clone().applyMatrix4( object.matrixWorld ),
+ face: null,
+ faceIndex: null,
+ object: object
+
+ } );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ var intersectDescendants = function ( object, raycaster, intersects ) {
+
+ var descendants = object.getDescendants();
+
+ for ( var i = 0, l = descendants.length; i < l; i ++ ) {
+
+ intersectObject( descendants[ i ], raycaster, intersects );
+
+ }
+ };
+
+ //
+
+ THREE.Raycaster.prototype.precision = 0.0001;
+ THREE.Raycaster.prototype.linePrecision = 1;
+
+ THREE.Raycaster.prototype.set = function ( origin, direction ) {
+
+ this.ray.set( origin, direction );
+ // direction is assumed to be normalized (for accurate distance calculations)
+
+ };
+
+ THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {
+
+ var intersects = [];
+
+ if ( recursive === true ) {
+
+ intersectDescendants( object, this, intersects );
+
+ }
+
+ intersectObject( object, this, intersects );
+
+ intersects.sort( descSort );
+
+ return intersects;
+
+ };
+
+ THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {
+
+ var intersects = [];
+
+ for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+ intersectObject( objects[ i ], this, intersects );
+
+ if ( recursive === true ) {
+
+ intersectDescendants( objects[ i ], this, intersects );
+
+ }
+
+ }
+
+ intersects.sort( descSort );
+
+ return intersects;
+
+ };
+
+}( THREE ) );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Object3D = function () {
+
+ this.id = THREE.Object3DIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ this.parent = undefined;
+ this.children = [];
+
+ this.up = new THREE.Vector3( 0, 1, 0 );
+
+ this.position = new THREE.Vector3();
+ this.rotation = new THREE.Euler();
+ this.quaternion = new THREE.Quaternion();
+ this.scale = new THREE.Vector3( 1, 1, 1 );
+
+ // keep rotation and quaternion in sync
+
+ this.rotation._quaternion = this.quaternion;
+ this.quaternion._euler = this.rotation;
+
+ this.renderDepth = null;
+
+ this.rotationAutoUpdate = true;
+
+ this.matrix = new THREE.Matrix4();
+ this.matrixWorld = new THREE.Matrix4();
+
+ this.matrixAutoUpdate = true;
+ this.matrixWorldNeedsUpdate = true;
+
+ this.visible = true;
+
+ this.castShadow = false;
+ this.receiveShadow = false;
+
+ this.frustumCulled = true;
+
+ this.userData = {};
+
+};
+
+
+THREE.Object3D.prototype = {
+
+ constructor: THREE.Object3D,
+
+ get eulerOrder () {
+
+ console.warn( 'DEPRECATED: Object3D\'s .eulerOrder has been moved to Object3D\'s .rotation.order.' );
+
+ return this.rotation.order;
+
+ },
+
+ set eulerOrder ( value ) {
+
+ console.warn( 'DEPRECATED: Object3D\'s .eulerOrder has been moved to Object3D\'s .rotation.order.' );
+
+ this.rotation.order = value;
+
+ },
+
+ get useQuaternion () {
+
+ console.warn( 'DEPRECATED: Object3D\'s .useQuaternion has been removed. The library now uses quaternions by default.' );
+
+ },
+
+ set useQuaternion ( value ) {
+
+ console.warn( 'DEPRECATED: Object3D\'s .useQuaternion has been removed. The library now uses quaternions by default.' );
+
+ },
+
+ applyMatrix: function () {
+
+ var m1 = new THREE.Matrix4();
+
+ return function ( matrix ) {
+
+ this.matrix.multiplyMatrices( matrix, this.matrix );
+
+ this.position.getPositionFromMatrix( this.matrix );
+
+ this.scale.getScaleFromMatrix( this.matrix );
+
+ m1.extractRotation( this.matrix );
+
+ this.quaternion.setFromRotationMatrix( m1 );
+
+ }
+
+ }(),
+
+ setRotationFromAxisAngle: function ( axis, angle ) {
+
+ // assumes axis is normalized
+
+ this.quaternion.setFromAxisAngle( axis, angle );
+
+ },
+
+ setRotationFromEuler: function ( euler ) {
+
+ this.quaternion.setFromEuler( euler, true );
+
+ },
+
+ setRotationFromMatrix: function ( m ) {
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ this.quaternion.setFromRotationMatrix( m );
+
+ },
+
+ setRotationFromQuaternion: function ( q ) {
+
+ // assumes q is normalized
+
+ this.quaternion.copy( q );
+
+ },
+
+ rotateOnAxis: function() {
+
+ // rotate object on axis in object space
+ // axis is assumed to be normalized
+
+ var q1 = new THREE.Quaternion();
+
+ return function ( axis, angle ) {
+
+ q1.setFromAxisAngle( axis, angle );
+
+ this.quaternion.multiply( q1 );
+
+ return this;
+
+ }
+
+ }(),
+
+ rotateX: function () {
+
+ var v1 = new THREE.Vector3( 1, 0, 0 );
+
+ return function ( angle ) {
+
+ return this.rotateOnAxis( v1, angle );
+
+ };
+
+ }(),
+
+ rotateY: function () {
+
+ var v1 = new THREE.Vector3( 0, 1, 0 );
+
+ return function ( angle ) {
+
+ return this.rotateOnAxis( v1, angle );
+
+ };
+
+ }(),
+
+ rotateZ: function () {
+
+ var v1 = new THREE.Vector3( 0, 0, 1 );
+
+ return function ( angle ) {
+
+ return this.rotateOnAxis( v1, angle );
+
+ };
+
+ }(),
+
+ translateOnAxis: function () {
+
+ // translate object by distance along axis in object space
+ // axis is assumed to be normalized
+
+ var v1 = new THREE.Vector3();
+
+ return function ( axis, distance ) {
+
+ v1.copy( axis );
+
+ v1.applyQuaternion( this.quaternion );
+
+ this.position.add( v1.multiplyScalar( distance ) );
+
+ return this;
+
+ }
+
+ }(),
+
+ translate: function ( distance, axis ) {
+
+ console.warn( 'DEPRECATED: Object3D\'s .translate() has been removed. Use .translateOnAxis( axis, distance ) instead. Note args have been changed.' );
+ return this.translateOnAxis( axis, distance );
+
+ },
+
+ translateX: function () {
+
+ var v1 = new THREE.Vector3( 1, 0, 0 );
+
+ return function ( distance ) {
+
+ return this.translateOnAxis( v1, distance );
+
+ };
+
+ }(),
+
+ translateY: function () {
+
+ var v1 = new THREE.Vector3( 0, 1, 0 );
+
+ return function ( distance ) {
+
+ return this.translateOnAxis( v1, distance );
+
+ };
+
+ }(),
+
+ translateZ: function () {
+
+ var v1 = new THREE.Vector3( 0, 0, 1 );
+
+ return function ( distance ) {
+
+ return this.translateOnAxis( v1, distance );
+
+ };
+
+ }(),
+
+ localToWorld: function ( vector ) {
+
+ return vector.applyMatrix4( this.matrixWorld );
+
+ },
+
+ worldToLocal: function () {
+
+ var m1 = new THREE.Matrix4();
+
+ return function ( vector ) {
+
+ return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) );
+
+ };
+
+ }(),
+
+ lookAt: function () {
+
+ // This routine does not support objects with rotated and/or translated parent(s)
+
+ var m1 = new THREE.Matrix4();
+
+ return function ( vector ) {
+
+ m1.lookAt( vector, this.position, this.up );
+
+ this.quaternion.setFromRotationMatrix( m1 );
+
+ };
+
+ }(),
+
+ add: function ( object ) {
+
+ if ( object === this ) {
+
+ console.warn( 'THREE.Object3D.add: An object can\'t be added as a child of itself.' );
+ return;
+
+ }
+
+ if ( object instanceof THREE.Object3D ) {
+
+ if ( object.parent !== undefined ) {
+
+ object.parent.remove( object );
+
+ }
+
+ object.parent = this;
+ object.dispatchEvent( { type: 'added' } );
+
+ this.children.push( object );
+
+ // add to scene
+
+ var scene = this;
+
+ while ( scene.parent !== undefined ) {
+
+ scene = scene.parent;
+
+ }
+
+ if ( scene !== undefined && scene instanceof THREE.Scene ) {
+
+ scene.__addObject( object );
+
+ }
+
+ }
+
+ },
+
+ remove: function ( object ) {
+
+ var index = this.children.indexOf( object );
+
+ if ( index !== - 1 ) {
+
+ object.parent = undefined;
+ object.dispatchEvent( { type: 'removed' } );
+
+ this.children.splice( index, 1 );
+
+ // remove from scene
+
+ var scene = this;
+
+ while ( scene.parent !== undefined ) {
+
+ scene = scene.parent;
+
+ }
+
+ if ( scene !== undefined && scene instanceof THREE.Scene ) {
+
+ scene.__removeObject( object );
+
+ }
+
+ }
+
+ },
+
+ traverse: function ( callback ) {
+
+ callback( this );
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ this.children[ i ].traverse( callback );
+
+ }
+
+ },
+
+ getObjectById: function ( id, recursive ) {
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ var child = this.children[ i ];
+
+ if ( child.id === id ) {
+
+ return child;
+
+ }
+
+ if ( recursive === true ) {
+
+ child = child.getObjectById( id, recursive );
+
+ if ( child !== undefined ) {
+
+ return child;
+
+ }
+
+ }
+
+ }
+
+ return undefined;
+
+ },
+
+ getObjectByName: function ( name, recursive ) {
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ var child = this.children[ i ];
+
+ if ( child.name === name ) {
+
+ return child;
+
+ }
+
+ if ( recursive === true ) {
+
+ child = child.getObjectByName( name, recursive );
+
+ if ( child !== undefined ) {
+
+ return child;
+
+ }
+
+ }
+
+ }
+
+ return undefined;
+
+ },
+
+ getChildByName: function ( name, recursive ) {
+
+ console.warn( 'DEPRECATED: Object3D\'s .getChildByName() has been renamed to .getObjectByName().' );
+ return this.getObjectByName( name, recursive );
+
+ },
+
+ getDescendants: function ( array ) {
+
+ if ( array === undefined ) array = [];
+
+ Array.prototype.push.apply( array, this.children );
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ this.children[ i ].getDescendants( array );
+
+ }
+
+ return array;
+
+ },
+
+ updateMatrix: function () {
+
+ this.matrix.compose( this.position, this.quaternion, this.scale );
+
+ this.matrixWorldNeedsUpdate = true;
+
+ },
+
+ updateMatrixWorld: function ( force ) {
+
+ if ( this.matrixAutoUpdate === true ) this.updateMatrix();
+
+ if ( this.matrixWorldNeedsUpdate === true || force === true ) {
+
+ if ( this.parent === undefined ) {
+
+ this.matrixWorld.copy( this.matrix );
+
+ } else {
+
+ this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+ }
+
+ this.matrixWorldNeedsUpdate = false;
+
+ force = true;
+
+ }
+
+ // update children
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ this.children[ i ].updateMatrixWorld( force );
+
+ }
+
+ },
+
+ clone: function ( object, recursive ) {
+
+ if ( object === undefined ) object = new THREE.Object3D();
+ if ( recursive === undefined ) recursive = true;
+
+ object.name = this.name;
+
+ object.up.copy( this.up );
+
+ object.position.copy( this.position );
+ object.quaternion.copy( this.quaternion );
+ object.scale.copy( this.scale );
+
+ object.renderDepth = this.renderDepth;
+
+ object.rotationAutoUpdate = this.rotationAutoUpdate;
+
+ object.matrix.copy( this.matrix );
+ object.matrixWorld.copy( this.matrixWorld );
+
+ object.matrixAutoUpdate = this.matrixAutoUpdate;
+ object.matrixWorldNeedsUpdate = this.matrixWorldNeedsUpdate;
+
+ object.visible = this.visible;
+
+ object.castShadow = this.castShadow;
+ object.receiveShadow = this.receiveShadow;
+
+ object.frustumCulled = this.frustumCulled;
+
+ object.userData = JSON.parse( JSON.stringify( this.userData ) );
+
+ if ( recursive === true ) {
+
+ for ( var i = 0; i < this.children.length; i ++ ) {
+
+ var child = this.children[ i ];
+ object.add( child.clone() );
+
+ }
+
+ }
+
+ return object;
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Object3D.prototype );
+
+THREE.Object3DIdCount = 0;
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author julianwa / https://github.com/julianwa
+ */
+
+THREE.Projector = function () {
+
+ var _object, _objectCount, _objectPool = [], _objectPoolLength = 0,
+ _vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0,
+ _face, _face3Count, _face3Pool = [], _face3PoolLength = 0,
+ _line, _lineCount, _linePool = [], _linePoolLength = 0,
+ _particle, _particleCount, _particlePool = [], _particlePoolLength = 0,
+
+ _renderData = { objects: [], sprites: [], lights: [], elements: [] },
+
+ _vector3 = new THREE.Vector3(),
+ _vector4 = new THREE.Vector4(),
+
+ _clipBox = new THREE.Box3( new THREE.Vector3( -1, -1, -1 ), new THREE.Vector3( 1, 1, 1 ) ),
+ _boundingBox = new THREE.Box3(),
+ _points3 = new Array( 3 ),
+ _points4 = new Array( 4 ),
+
+ _viewMatrix = new THREE.Matrix4(),
+ _viewProjectionMatrix = new THREE.Matrix4(),
+
+ _modelMatrix,
+ _modelViewProjectionMatrix = new THREE.Matrix4(),
+
+ _normalMatrix = new THREE.Matrix3(),
+ _normalViewMatrix = new THREE.Matrix3(),
+
+ _centroid = new THREE.Vector3(),
+
+ _frustum = new THREE.Frustum(),
+
+ _clippedVertex1PositionScreen = new THREE.Vector4(),
+ _clippedVertex2PositionScreen = new THREE.Vector4();
+
+ this.projectVector = function ( vector, camera ) {
+
+ camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+
+ _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+
+ return vector.applyProjection( _viewProjectionMatrix );
+
+ };
+
+ this.unprojectVector = function ( vector, camera ) {
+
+ camera.projectionMatrixInverse.getInverse( camera.projectionMatrix );
+
+ _viewProjectionMatrix.multiplyMatrices( camera.matrixWorld, camera.projectionMatrixInverse );
+
+ return vector.applyProjection( _viewProjectionMatrix );
+
+ };
+
+ this.pickingRay = function ( vector, camera ) {
+
+ // set two vectors with opposing z values
+ vector.z = -1.0;
+ var end = new THREE.Vector3( vector.x, vector.y, 1.0 );
+
+ this.unprojectVector( vector, camera );
+ this.unprojectVector( end, camera );
+
+ // find direction from vector to end
+ end.sub( vector ).normalize();
+
+ return new THREE.Raycaster( vector, end );
+
+ };
+
+ var getObject = function ( object ) {
+
+ _object = getNextObjectInPool();
+ _object.id = object.id;
+ _object.object = object;
+
+ if ( object.renderDepth !== null ) {
+
+ _object.z = object.renderDepth;
+
+ } else {
+
+ _vector3.getPositionFromMatrix( object.matrixWorld );
+ _vector3.applyProjection( _viewProjectionMatrix );
+ _object.z = _vector3.z;
+
+ }
+
+ return _object;
+
+ };
+
+ var projectObject = function ( object ) {
+
+ if ( object.visible === false ) return;
+
+ if ( object instanceof THREE.Light ) {
+
+ _renderData.lights.push( object );
+
+ } else if ( object instanceof THREE.Mesh || object instanceof THREE.Line ) {
+
+ if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) {
+
+ _renderData.objects.push( getObject( object ) );
+
+ }
+
+ } else if ( object instanceof THREE.Sprite || object instanceof THREE.Particle ) {
+
+ _renderData.sprites.push( getObject( object ) );
+
+ }
+
+ for ( var i = 0, l = object.children.length; i < l; i ++ ) {
+
+ projectObject( object.children[ i ] );
+
+ }
+
+ };
+
+ var projectGraph = function ( root, sortObjects ) {
+
+ _objectCount = 0;
+
+ _renderData.objects.length = 0;
+ _renderData.sprites.length = 0;
+ _renderData.lights.length = 0;
+
+ projectObject( root );
+
+ if ( sortObjects === true ) {
+
+ _renderData.objects.sort( painterSort );
+
+ }
+
+ };
+
+ this.projectScene = function ( scene, camera, sortObjects, sortElements ) {
+
+ var visible = false,
+ o, ol, v, vl, f, fl, n, nl, c, cl, u, ul, object,
+ geometry, vertices, faces, face, faceVertexNormals, faceVertexUvs, uvs,
+ v1, v2, v3, v4, isFaceMaterial, objectMaterials;
+
+ _face3Count = 0;
+ _lineCount = 0;
+ _particleCount = 0;
+
+ _renderData.elements.length = 0;
+
+ if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+ if ( camera.parent === undefined ) camera.updateMatrixWorld();
+
+ _viewMatrix.copy( camera.matrixWorldInverse.getInverse( camera.matrixWorld ) );
+ _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
+
+ _normalViewMatrix.getNormalMatrix( _viewMatrix );
+
+ _frustum.setFromMatrix( _viewProjectionMatrix );
+
+ projectGraph( scene, sortObjects );
+
+ for ( o = 0, ol = _renderData.objects.length; o < ol; o ++ ) {
+
+ object = _renderData.objects[ o ].object;
+
+ _modelMatrix = object.matrixWorld;
+
+ _vertexCount = 0;
+
+ if ( object instanceof THREE.Mesh ) {
+
+ geometry = object.geometry;
+
+ vertices = geometry.vertices;
+ faces = geometry.faces;
+ faceVertexUvs = geometry.faceVertexUvs;
+
+ _normalMatrix.getNormalMatrix( _modelMatrix );
+
+ isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
+ objectMaterials = isFaceMaterial === true ? object.material : null;
+
+ for ( v = 0, vl = vertices.length; v < vl; v ++ ) {
+
+ _vertex = getNextVertexInPool();
+
+ _vertex.positionWorld.copy( vertices[ v ] ).applyMatrix4( _modelMatrix );
+ _vertex.positionScreen.copy( _vertex.positionWorld ).applyMatrix4( _viewProjectionMatrix );
+
+ var invW = 1 / _vertex.positionScreen.w;
+
+ _vertex.positionScreen.x *= invW;
+ _vertex.positionScreen.y *= invW;
+ _vertex.positionScreen.z *= invW;
+
+ _vertex.visible = ! ( _vertex.positionScreen.x < -1 || _vertex.positionScreen.x > 1 ||
+ _vertex.positionScreen.y < -1 || _vertex.positionScreen.y > 1 ||
+ _vertex.positionScreen.z < -1 || _vertex.positionScreen.z > 1 );
+
+ }
+
+ for ( f = 0, fl = faces.length; f < fl; f ++ ) {
+
+ face = faces[ f ];
+
+ var material = isFaceMaterial === true
+ ? objectMaterials.materials[ face.materialIndex ]
+ : object.material;
+
+ if ( material === undefined ) continue;
+
+ var side = material.side;
+
+ v1 = _vertexPool[ face.a ];
+ v2 = _vertexPool[ face.b ];
+ v3 = _vertexPool[ face.c ];
+
+ _points3[ 0 ] = v1.positionScreen;
+ _points3[ 1 ] = v2.positionScreen;
+ _points3[ 2 ] = v3.positionScreen;
+
+ if ( v1.visible === true || v2.visible === true || v3.visible === true ||
+ _clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points3 ) ) ) {
+
+ visible = ( ( v3.positionScreen.x - v1.positionScreen.x ) *
+ ( v2.positionScreen.y - v1.positionScreen.y ) -
+ ( v3.positionScreen.y - v1.positionScreen.y ) *
+ ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0;
+
+ if ( side === THREE.DoubleSide || visible === ( side === THREE.FrontSide ) ) {
+
+ _face = getNextFace3InPool();
+
+ _face.id = object.id;
+ _face.v1.copy( v1 );
+ _face.v2.copy( v2 );
+ _face.v3.copy( v3 );
+
+ } else {
+
+ continue;
+
+ }
+
+ } else {
+
+ continue;
+
+ }
+
+ _face.normalModel.copy( face.normal );
+
+ if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) {
+
+ _face.normalModel.negate();
+
+ }
+
+ _face.normalModel.applyMatrix3( _normalMatrix ).normalize();
+
+ _face.normalModelView.copy( _face.normalModel ).applyMatrix3( _normalViewMatrix );
+
+ _face.centroidModel.copy( face.centroid ).applyMatrix4( _modelMatrix );
+
+ faceVertexNormals = face.vertexNormals;
+
+ for ( n = 0, nl = Math.min( faceVertexNormals.length, 3 ); n < nl; n ++ ) {
+
+ var normalModel = _face.vertexNormalsModel[ n ];
+ normalModel.copy( faceVertexNormals[ n ] );
+
+ if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) {
+
+ normalModel.negate();
+
+ }
+
+ normalModel.applyMatrix3( _normalMatrix ).normalize();
+
+ var normalModelView = _face.vertexNormalsModelView[ n ];
+ normalModelView.copy( normalModel ).applyMatrix3( _normalViewMatrix );
+
+ }
+
+ _face.vertexNormalsLength = faceVertexNormals.length;
+
+ for ( c = 0, cl = Math.min( faceVertexUvs.length, 3 ); c < cl; c ++ ) {
+
+ uvs = faceVertexUvs[ c ][ f ];
+
+ if ( uvs === undefined ) continue;
+
+ for ( u = 0, ul = uvs.length; u < ul; u ++ ) {
+
+ _face.uvs[ c ][ u ] = uvs[ u ];
+
+ }
+
+ }
+
+ _face.color = face.color;
+ _face.material = material;
+
+ _centroid.copy( _face.centroidModel ).applyProjection( _viewProjectionMatrix );
+
+ _face.z = _centroid.z;
+
+ _renderData.elements.push( _face );
+
+ }
+
+ } else if ( object instanceof THREE.Line ) {
+
+ _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix );
+
+ vertices = object.geometry.vertices;
+
+ v1 = getNextVertexInPool();
+ v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix );
+
+ // Handle LineStrip and LinePieces
+ var step = object.type === THREE.LinePieces ? 2 : 1;
+
+ for ( v = 1, vl = vertices.length; v < vl; v ++ ) {
+
+ v1 = getNextVertexInPool();
+ v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix );
+
+ if ( ( v + 1 ) % step > 0 ) continue;
+
+ v2 = _vertexPool[ _vertexCount - 2 ];
+
+ _clippedVertex1PositionScreen.copy( v1.positionScreen );
+ _clippedVertex2PositionScreen.copy( v2.positionScreen );
+
+ if ( clipLine( _clippedVertex1PositionScreen, _clippedVertex2PositionScreen ) === true ) {
+
+ // Perform the perspective divide
+ _clippedVertex1PositionScreen.multiplyScalar( 1 / _clippedVertex1PositionScreen.w );
+ _clippedVertex2PositionScreen.multiplyScalar( 1 / _clippedVertex2PositionScreen.w );
+
+ _line = getNextLineInPool();
+
+ _line.id = object.id;
+ _line.v1.positionScreen.copy( _clippedVertex1PositionScreen );
+ _line.v2.positionScreen.copy( _clippedVertex2PositionScreen );
+
+ _line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z );
+
+ _line.material = object.material;
+
+ if ( object.material.vertexColors === THREE.VertexColors ) {
+
+ _line.vertexColors[ 0 ].copy( object.geometry.colors[ v ] );
+ _line.vertexColors[ 1 ].copy( object.geometry.colors[ v - 1 ] );
+
+ }
+
+ _renderData.elements.push( _line );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ for ( o = 0, ol = _renderData.sprites.length; o < ol; o++ ) {
+
+ object = _renderData.sprites[ o ].object;
+
+ _modelMatrix = object.matrixWorld;
+
+ if ( object instanceof THREE.Particle ) {
+
+ _vector4.set( _modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1 );
+ _vector4.applyMatrix4( _viewProjectionMatrix );
+
+ var invW = 1 / _vector4.w;
+
+ _vector4.z *= invW;
+
+ if ( _vector4.z > 0 && _vector4.z < 1 ) {
+
+ _particle = getNextParticleInPool();
+ _particle.id = object.id;
+ _particle.x = _vector4.x * invW;
+ _particle.y = _vector4.y * invW;
+ _particle.z = _vector4.z;
+ _particle.object = object;
+
+ _particle.rotation = object.rotation.z;
+
+ _particle.scale.x = object.scale.x * Math.abs( _particle.x - ( _vector4.x + camera.projectionMatrix.elements[0] ) / ( _vector4.w + camera.projectionMatrix.elements[12] ) );
+ _particle.scale.y = object.scale.y * Math.abs( _particle.y - ( _vector4.y + camera.projectionMatrix.elements[5] ) / ( _vector4.w + camera.projectionMatrix.elements[13] ) );
+
+ _particle.material = object.material;
+
+ _renderData.elements.push( _particle );
+
+ }
+
+ }
+
+ }
+
+ if ( sortElements === true ) _renderData.elements.sort( painterSort );
+
+ return _renderData;
+
+ };
+
+ // Pools
+
+ function getNextObjectInPool() {
+
+ if ( _objectCount === _objectPoolLength ) {
+
+ var object = new THREE.RenderableObject();
+ _objectPool.push( object );
+ _objectPoolLength ++;
+ _objectCount ++;
+ return object;
+
+ }
+
+ return _objectPool[ _objectCount ++ ];
+
+ }
+
+ function getNextVertexInPool() {
+
+ if ( _vertexCount === _vertexPoolLength ) {
+
+ var vertex = new THREE.RenderableVertex();
+ _vertexPool.push( vertex );
+ _vertexPoolLength ++;
+ _vertexCount ++;
+ return vertex;
+
+ }
+
+ return _vertexPool[ _vertexCount ++ ];
+
+ }
+
+ function getNextFace3InPool() {
+
+ if ( _face3Count === _face3PoolLength ) {
+
+ var face = new THREE.RenderableFace3();
+ _face3Pool.push( face );
+ _face3PoolLength ++;
+ _face3Count ++;
+ return face;
+
+ }
+
+ return _face3Pool[ _face3Count ++ ];
+
+
+ }
+
+ function getNextLineInPool() {
+
+ if ( _lineCount === _linePoolLength ) {
+
+ var line = new THREE.RenderableLine();
+ _linePool.push( line );
+ _linePoolLength ++;
+ _lineCount ++
+ return line;
+
+ }
+
+ return _linePool[ _lineCount ++ ];
+
+ }
+
+ function getNextParticleInPool() {
+
+ if ( _particleCount === _particlePoolLength ) {
+
+ var particle = new THREE.RenderableParticle();
+ _particlePool.push( particle );
+ _particlePoolLength ++;
+ _particleCount ++
+ return particle;
+
+ }
+
+ return _particlePool[ _particleCount ++ ];
+
+ }
+
+ //
+
+ function painterSort( a, b ) {
+
+ if ( a.z !== b.z ) {
+
+ return b.z - a.z;
+
+ } else if ( a.id !== b.id ) {
+
+ return a.id - b.id;
+
+ } else {
+
+ return 0;
+
+ }
+
+ }
+
+ function clipLine( s1, s2 ) {
+
+ var alpha1 = 0, alpha2 = 1,
+
+ // Calculate the boundary coordinate of each vertex for the near and far clip planes,
+ // Z = -1 and Z = +1, respectively.
+ bc1near = s1.z + s1.w,
+ bc2near = s2.z + s2.w,
+ bc1far = - s1.z + s1.w,
+ bc2far = - s2.z + s2.w;
+
+ if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) {
+
+ // Both vertices lie entirely within all clip planes.
+ return true;
+
+ } else if ( ( bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0 ) ) {
+
+ // Both vertices lie entirely outside one of the clip planes.
+ return false;
+
+ } else {
+
+ // The line segment spans at least one clip plane.
+
+ if ( bc1near < 0 ) {
+
+ // v1 lies outside the near plane, v2 inside
+ alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) );
+
+ } else if ( bc2near < 0 ) {
+
+ // v2 lies outside the near plane, v1 inside
+ alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) );
+
+ }
+
+ if ( bc1far < 0 ) {
+
+ // v1 lies outside the far plane, v2 inside
+ alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) );
+
+ } else if ( bc2far < 0 ) {
+
+ // v2 lies outside the far plane, v2 inside
+ alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) );
+
+ }
+
+ if ( alpha2 < alpha1 ) {
+
+ // The line segment spans two boundaries, but is outside both of them.
+ // (This can't happen when we're only clipping against just near/far but good
+ // to leave the check here for future usage if other clip planes are added.)
+ return false;
+
+ } else {
+
+ // Update the s1 and s2 vertices to match the clipped line segment.
+ s1.lerp( s2, alpha1 );
+ s2.lerp( s1, 1 - alpha2 );
+
+ return true;
+
+ }
+
+ }
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Face3 = function ( a, b, c, normal, color, materialIndex ) {
+
+ this.a = a;
+ this.b = b;
+ this.c = c;
+
+ this.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3();
+ this.vertexNormals = normal instanceof Array ? normal : [ ];
+
+ this.color = color instanceof THREE.Color ? color : new THREE.Color();
+ this.vertexColors = color instanceof Array ? color : [];
+
+ this.vertexTangents = [];
+
+ this.materialIndex = materialIndex !== undefined ? materialIndex : 0;
+
+ this.centroid = new THREE.Vector3();
+
+};
+
+THREE.Face3.prototype = {
+
+ constructor: THREE.Face3,
+
+ clone: function () {
+
+ var face = new THREE.Face3( this.a, this.b, this.c );
+
+ face.normal.copy( this.normal );
+ face.color.copy( this.color );
+ face.centroid.copy( this.centroid );
+
+ face.materialIndex = this.materialIndex;
+
+ var i, il;
+ for ( i = 0, il = this.vertexNormals.length; i < il; i ++ ) face.vertexNormals[ i ] = this.vertexNormals[ i ].clone();
+ for ( i = 0, il = this.vertexColors.length; i < il; i ++ ) face.vertexColors[ i ] = this.vertexColors[ i ].clone();
+ for ( i = 0, il = this.vertexTangents.length; i < il; i ++ ) face.vertexTangents[ i ] = this.vertexTangents[ i ].clone();
+
+ return face;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Face4 = function ( a, b, c, d, normal, color, materialIndex ) {
+
+ console.warn( 'THREE.Face4 has been removed. A THREE.Face3 will be created instead.')
+
+ return new THREE.Face3( a, b, c, normal, color, materialIndex );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author kile / http://kile.stravaganza.org/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Geometry = function () {
+
+ this.id = THREE.GeometryIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ this.vertices = [];
+ this.colors = []; // one-to-one vertex colors, used in ParticleSystem and Line
+
+ this.faces = [];
+
+ this.faceVertexUvs = [[]];
+
+ this.morphTargets = [];
+ this.morphColors = [];
+ this.morphNormals = [];
+
+ this.skinWeights = [];
+ this.skinIndices = [];
+
+ this.lineDistances = [];
+
+ this.boundingBox = null;
+ this.boundingSphere = null;
+
+ this.hasTangents = false;
+
+ this.dynamic = true; // the intermediate typed arrays will be deleted when set to false
+
+ // update flags
+
+ this.verticesNeedUpdate = false;
+ this.elementsNeedUpdate = false;
+ this.uvsNeedUpdate = false;
+ this.normalsNeedUpdate = false;
+ this.tangentsNeedUpdate = false;
+ this.colorsNeedUpdate = false;
+ this.lineDistancesNeedUpdate = false;
+
+ this.buffersNeedUpdate = false;
+
+};
+
+THREE.Geometry.prototype = {
+
+ constructor: THREE.Geometry,
+
+ applyMatrix: function ( matrix ) {
+
+ var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+ for ( var i = 0, il = this.vertices.length; i < il; i ++ ) {
+
+ var vertex = this.vertices[ i ];
+ vertex.applyMatrix4( matrix );
+
+ }
+
+ for ( var i = 0, il = this.faces.length; i < il; i ++ ) {
+
+ var face = this.faces[ i ];
+ face.normal.applyMatrix3( normalMatrix ).normalize();
+
+ for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
+
+ face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize();
+
+ }
+
+ face.centroid.applyMatrix4( matrix );
+
+ }
+
+ if ( this.boundingBox instanceof THREE.Box3 ) {
+
+ this.computeBoundingBox();
+
+ }
+
+ if ( this.boundingSphere instanceof THREE.Sphere ) {
+
+ this.computeBoundingSphere();
+
+ }
+
+ },
+
+ computeCentroids: function () {
+
+ var f, fl, face;
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+ face.centroid.set( 0, 0, 0 );
+
+ face.centroid.add( this.vertices[ face.a ] );
+ face.centroid.add( this.vertices[ face.b ] );
+ face.centroid.add( this.vertices[ face.c ] );
+ face.centroid.divideScalar( 3 );
+
+ }
+
+ },
+
+ computeFaceNormals: function () {
+
+ var cb = new THREE.Vector3(), ab = new THREE.Vector3();
+
+ for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ var face = this.faces[ f ];
+
+ var vA = this.vertices[ face.a ];
+ var vB = this.vertices[ face.b ];
+ var vC = this.vertices[ face.c ];
+
+ cb.subVectors( vC, vB );
+ ab.subVectors( vA, vB );
+ cb.cross( ab );
+
+ cb.normalize();
+
+ face.normal.copy( cb );
+
+ }
+
+ },
+
+ computeVertexNormals: function ( areaWeighted ) {
+
+ var v, vl, f, fl, face, vertices;
+
+ // create internal buffers for reuse when calling this method repeatedly
+ // (otherwise memory allocation / deallocation every frame is big resource hog)
+
+ if ( this.__tmpVertices === undefined ) {
+
+ this.__tmpVertices = new Array( this.vertices.length );
+ vertices = this.__tmpVertices;
+
+ for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+ vertices[ v ] = new THREE.Vector3();
+
+ }
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+ face.vertexNormals = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+
+ }
+
+ } else {
+
+ vertices = this.__tmpVertices;
+
+ for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+ vertices[ v ].set( 0, 0, 0 );
+
+ }
+
+ }
+
+ if ( areaWeighted ) {
+
+ // vertex normals weighted by triangle areas
+ // http://www.iquilezles.org/www/articles/normals/normals.htm
+
+ var vA, vB, vC, vD;
+ var cb = new THREE.Vector3(), ab = new THREE.Vector3(),
+ db = new THREE.Vector3(), dc = new THREE.Vector3(), bc = new THREE.Vector3();
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ vA = this.vertices[ face.a ];
+ vB = this.vertices[ face.b ];
+ vC = this.vertices[ face.c ];
+
+ cb.subVectors( vC, vB );
+ ab.subVectors( vA, vB );
+ cb.cross( ab );
+
+ vertices[ face.a ].add( cb );
+ vertices[ face.b ].add( cb );
+ vertices[ face.c ].add( cb );
+
+ }
+
+ } else {
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ vertices[ face.a ].add( face.normal );
+ vertices[ face.b ].add( face.normal );
+ vertices[ face.c ].add( face.normal );
+
+ }
+
+ }
+
+ for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+ vertices[ v ].normalize();
+
+ }
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ face.vertexNormals[ 0 ].copy( vertices[ face.a ] );
+ face.vertexNormals[ 1 ].copy( vertices[ face.b ] );
+ face.vertexNormals[ 2 ].copy( vertices[ face.c ] );
+
+ }
+
+ },
+
+ computeMorphNormals: function () {
+
+ var i, il, f, fl, face;
+
+ // save original normals
+ // - create temp variables on first access
+ // otherwise just copy (for faster repeated calls)
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ if ( ! face.__originalFaceNormal ) {
+
+ face.__originalFaceNormal = face.normal.clone();
+
+ } else {
+
+ face.__originalFaceNormal.copy( face.normal );
+
+ }
+
+ if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = [];
+
+ for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) {
+
+ if ( ! face.__originalVertexNormals[ i ] ) {
+
+ face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone();
+
+ } else {
+
+ face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] );
+
+ }
+
+ }
+
+ }
+
+ // use temp geometry to compute face and vertex normals for each morph
+
+ var tmpGeo = new THREE.Geometry();
+ tmpGeo.faces = this.faces;
+
+ for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) {
+
+ // create on first access
+
+ if ( ! this.morphNormals[ i ] ) {
+
+ this.morphNormals[ i ] = {};
+ this.morphNormals[ i ].faceNormals = [];
+ this.morphNormals[ i ].vertexNormals = [];
+
+ var dstNormalsFace = this.morphNormals[ i ].faceNormals;
+ var dstNormalsVertex = this.morphNormals[ i ].vertexNormals;
+
+ var faceNormal, vertexNormals;
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ faceNormal = new THREE.Vector3();
+ vertexNormals = { a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3() };
+
+ dstNormalsFace.push( faceNormal );
+ dstNormalsVertex.push( vertexNormals );
+
+ }
+
+ }
+
+ var morphNormals = this.morphNormals[ i ];
+
+ // set vertices to morph target
+
+ tmpGeo.vertices = this.morphTargets[ i ].vertices;
+
+ // compute morph normals
+
+ tmpGeo.computeFaceNormals();
+ tmpGeo.computeVertexNormals();
+
+ // store morph normals
+
+ var faceNormal, vertexNormals;
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ faceNormal = morphNormals.faceNormals[ f ];
+ vertexNormals = morphNormals.vertexNormals[ f ];
+
+ faceNormal.copy( face.normal );
+
+ vertexNormals.a.copy( face.vertexNormals[ 0 ] );
+ vertexNormals.b.copy( face.vertexNormals[ 1 ] );
+ vertexNormals.c.copy( face.vertexNormals[ 2 ] );
+
+ }
+
+ }
+
+ // restore original normals
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ face.normal = face.__originalFaceNormal;
+ face.vertexNormals = face.__originalVertexNormals;
+
+ }
+
+ },
+
+ computeTangents: function () {
+
+ // based on http://www.terathon.com/code/tangent.html
+ // tangents go to vertices
+
+ var f, fl, v, vl, i, il, vertexIndex,
+ face, uv, vA, vB, vC, uvA, uvB, uvC,
+ x1, x2, y1, y2, z1, z2,
+ s1, s2, t1, t2, r, t, test,
+ tan1 = [], tan2 = [],
+ sdir = new THREE.Vector3(), tdir = new THREE.Vector3(),
+ tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3(),
+ n = new THREE.Vector3(), w;
+
+ for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+ tan1[ v ] = new THREE.Vector3();
+ tan2[ v ] = new THREE.Vector3();
+
+ }
+
+ function handleTriangle( context, a, b, c, ua, ub, uc ) {
+
+ vA = context.vertices[ a ];
+ vB = context.vertices[ b ];
+ vC = context.vertices[ c ];
+
+ uvA = uv[ ua ];
+ uvB = uv[ ub ];
+ uvC = uv[ uc ];
+
+ x1 = vB.x - vA.x;
+ x2 = vC.x - vA.x;
+ y1 = vB.y - vA.y;
+ y2 = vC.y - vA.y;
+ z1 = vB.z - vA.z;
+ z2 = vC.z - vA.z;
+
+ s1 = uvB.x - uvA.x;
+ s2 = uvC.x - uvA.x;
+ t1 = uvB.y - uvA.y;
+ t2 = uvC.y - uvA.y;
+
+ r = 1.0 / ( s1 * t2 - s2 * t1 );
+ sdir.set( ( t2 * x1 - t1 * x2 ) * r,
+ ( t2 * y1 - t1 * y2 ) * r,
+ ( t2 * z1 - t1 * z2 ) * r );
+ tdir.set( ( s1 * x2 - s2 * x1 ) * r,
+ ( s1 * y2 - s2 * y1 ) * r,
+ ( s1 * z2 - s2 * z1 ) * r );
+
+ tan1[ a ].add( sdir );
+ tan1[ b ].add( sdir );
+ tan1[ c ].add( sdir );
+
+ tan2[ a ].add( tdir );
+ tan2[ b ].add( tdir );
+ tan2[ c ].add( tdir );
+
+ }
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+ uv = this.faceVertexUvs[ 0 ][ f ]; // use UV layer 0 for tangents
+
+ handleTriangle( this, face.a, face.b, face.c, 0, 1, 2 );
+
+ }
+
+ var faceIndex = [ 'a', 'b', 'c', 'd' ];
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ for ( i = 0; i < Math.min( face.vertexNormals.length, 3 ); i++ ) {
+
+ n.copy( face.vertexNormals[ i ] );
+
+ vertexIndex = face[ faceIndex[ i ] ];
+
+ t = tan1[ vertexIndex ];
+
+ // Gram-Schmidt orthogonalize
+
+ tmp.copy( t );
+ tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+ // Calculate handedness
+
+ tmp2.crossVectors( face.vertexNormals[ i ], t );
+ test = tmp2.dot( tan2[ vertexIndex ] );
+ w = (test < 0.0) ? -1.0 : 1.0;
+
+ face.vertexTangents[ i ] = new THREE.Vector4( tmp.x, tmp.y, tmp.z, w );
+
+ }
+
+ }
+
+ this.hasTangents = true;
+
+ },
+
+ computeLineDistances: function ( ) {
+
+ var d = 0;
+ var vertices = this.vertices;
+
+ for ( var i = 0, il = vertices.length; i < il; i ++ ) {
+
+ if ( i > 0 ) {
+
+ d += vertices[ i ].distanceTo( vertices[ i - 1 ] );
+
+ }
+
+ this.lineDistances[ i ] = d;
+
+ }
+
+ },
+
+ computeBoundingBox: function () {
+
+ if ( this.boundingBox === null ) {
+
+ this.boundingBox = new THREE.Box3();
+
+ }
+
+ this.boundingBox.setFromPoints( this.vertices );
+
+ },
+
+ computeBoundingSphere: function () {
+
+ if ( this.boundingSphere === null ) {
+
+ this.boundingSphere = new THREE.Sphere();
+
+ }
+
+ this.boundingSphere.setFromPoints( this.vertices );
+
+ },
+
+ /*
+ * Checks for duplicate vertices with hashmap.
+ * Duplicated vertices are removed
+ * and faces' vertices are updated.
+ */
+
+ mergeVertices: function () {
+
+ var verticesMap = {}; // Hashmap for looking up vertice by position coordinates (and making sure they are unique)
+ var unique = [], changes = [];
+
+ var v, key;
+ var precisionPoints = 4; // number of decimal points, eg. 4 for epsilon of 0.0001
+ var precision = Math.pow( 10, precisionPoints );
+ var i,il, face;
+ var indices, k, j, jl, u;
+
+ // reset cache of vertices as it now will be changing.
+ this.__tmpVertices = undefined;
+
+ for ( i = 0, il = this.vertices.length; i < il; i ++ ) {
+
+ v = this.vertices[ i ];
+ key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision );
+
+ if ( verticesMap[ key ] === undefined ) {
+
+ verticesMap[ key ] = i;
+ unique.push( this.vertices[ i ] );
+ changes[ i ] = unique.length - 1;
+
+ } else {
+
+ //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);
+ changes[ i ] = changes[ verticesMap[ key ] ];
+
+ }
+
+ };
+
+
+ // if faces are completely degenerate after merging vertices, we
+ // have to remove them from the geometry.
+ var faceIndicesToRemove = [];
+
+ for( i = 0, il = this.faces.length; i < il; i ++ ) {
+
+ face = this.faces[ i ];
+
+ face.a = changes[ face.a ];
+ face.b = changes[ face.b ];
+ face.c = changes[ face.c ];
+
+ indices = [ face.a, face.b, face.c ];
+
+ var dupIndex = -1;
+
+ // if any duplicate vertices are found in a Face3
+ // we have to remove the face as nothing can be saved
+ for ( var n = 0; n < 3; n ++ ) {
+ if ( indices[ n ] == indices[ ( n + 1 ) % 3 ] ) {
+
+ dupIndex = n;
+ faceIndicesToRemove.push( i );
+ break;
+
+ }
+ }
+
+ }
+
+ for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) {
+ var idx = faceIndicesToRemove[ i ];
+
+ this.faces.splice( idx, 1 );
+
+ for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
+
+ this.faceVertexUvs[ j ].splice( idx, 1 );
+
+ }
+
+ }
+
+ // Use unique set of vertices
+
+ var diff = this.vertices.length - unique.length;
+ this.vertices = unique;
+ return diff;
+
+ },
+
+ clone: function () {
+
+ var geometry = new THREE.Geometry();
+
+ var vertices = this.vertices;
+
+ for ( var i = 0, il = vertices.length; i < il; i ++ ) {
+
+ geometry.vertices.push( vertices[ i ].clone() );
+
+ }
+
+ var faces = this.faces;
+
+ for ( var i = 0, il = faces.length; i < il; i ++ ) {
+
+ geometry.faces.push( faces[ i ].clone() );
+
+ }
+
+ var uvs = this.faceVertexUvs[ 0 ];
+
+ for ( var i = 0, il = uvs.length; i < il; i ++ ) {
+
+ var uv = uvs[ i ], uvCopy = [];
+
+ for ( var j = 0, jl = uv.length; j < jl; j ++ ) {
+
+ uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) );
+
+ }
+
+ geometry.faceVertexUvs[ 0 ].push( uvCopy );
+
+ }
+
+ return geometry;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Geometry.prototype );
+
+THREE.GeometryIdCount = 0;
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.BufferGeometry = function () {
+
+ this.id = THREE.GeometryIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ // attributes
+
+ this.attributes = {};
+
+ // attributes typed arrays are kept only if dynamic flag is set
+
+ this.dynamic = true;
+
+ // offsets for chunks when using indexed elements
+
+ this.offsets = [];
+
+ // boundings
+
+ this.boundingBox = null;
+ this.boundingSphere = null;
+
+ this.hasTangents = false;
+
+ // for compatibility
+
+ this.morphTargets = [];
+
+};
+
+THREE.BufferGeometry.prototype = {
+
+ constructor: THREE.BufferGeometry,
+
+ applyMatrix: function ( matrix ) {
+
+ var positionArray;
+ var normalArray;
+
+ if ( this.attributes[ "position" ] ) positionArray = this.attributes[ "position" ].array;
+ if ( this.attributes[ "normal" ] ) normalArray = this.attributes[ "normal" ].array;
+
+ if ( positionArray !== undefined ) {
+
+ matrix.multiplyVector3Array( positionArray );
+ this.verticesNeedUpdate = true;
+
+ }
+
+ if ( normalArray !== undefined ) {
+
+ var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+ normalMatrix.multiplyVector3Array( normalArray );
+
+ this.normalizeNormals();
+
+ this.normalsNeedUpdate = true;
+
+ }
+
+ },
+
+ computeBoundingBox: function () {
+
+ if ( this.boundingBox === null ) {
+
+ this.boundingBox = new THREE.Box3();
+
+ }
+
+ var positions = this.attributes[ "position" ].array;
+
+ if ( positions ) {
+
+ var bb = this.boundingBox;
+ var x, y, z;
+
+ if( positions.length >= 3 ) {
+ bb.min.x = bb.max.x = positions[ 0 ];
+ bb.min.y = bb.max.y = positions[ 1 ];
+ bb.min.z = bb.max.z = positions[ 2 ];
+ }
+
+ for ( var i = 3, il = positions.length; i < il; i += 3 ) {
+
+ x = positions[ i ];
+ y = positions[ i + 1 ];
+ z = positions[ i + 2 ];
+
+ // bounding box
+
+ if ( x < bb.min.x ) {
+
+ bb.min.x = x;
+
+ } else if ( x > bb.max.x ) {
+
+ bb.max.x = x;
+
+ }
+
+ if ( y < bb.min.y ) {
+
+ bb.min.y = y;
+
+ } else if ( y > bb.max.y ) {
+
+ bb.max.y = y;
+
+ }
+
+ if ( z < bb.min.z ) {
+
+ bb.min.z = z;
+
+ } else if ( z > bb.max.z ) {
+
+ bb.max.z = z;
+
+ }
+
+ }
+
+ }
+
+ if ( positions === undefined || positions.length === 0 ) {
+
+ this.boundingBox.min.set( 0, 0, 0 );
+ this.boundingBox.max.set( 0, 0, 0 );
+
+ }
+
+ },
+
+ computeBoundingSphere: function () {
+
+ var box = new THREE.Box3();
+ var vector = new THREE.Vector3();
+
+ return function () {
+
+ if ( this.boundingSphere === null ) {
+
+ this.boundingSphere = new THREE.Sphere();
+
+ }
+
+ var positions = this.attributes[ "position" ].array;
+
+ if ( positions ) {
+
+ var center = this.boundingSphere.center;
+
+ for ( var i = 0, il = positions.length; i < il; i += 3 ) {
+
+ vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] );
+ box.addPoint( vector );
+
+ }
+
+ box.center( center );
+
+ var maxRadiusSq = 0;
+
+ for ( var i = 0, il = positions.length; i < il; i += 3 ) {
+
+ vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] );
+ maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+
+ }
+
+ this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
+
+ }
+
+ }
+
+ }(),
+
+ computeVertexNormals: function () {
+
+ if ( this.attributes[ "position" ] ) {
+
+ var i, il;
+ var j, jl;
+
+ var nVertexElements = this.attributes[ "position" ].array.length;
+
+ if ( this.attributes[ "normal" ] === undefined ) {
+
+ this.attributes[ "normal" ] = {
+
+ itemSize: 3,
+ array: new Float32Array( nVertexElements )
+
+ };
+
+ } else {
+
+ // reset existing normals to zero
+
+ for ( i = 0, il = this.attributes[ "normal" ].array.length; i < il; i ++ ) {
+
+ this.attributes[ "normal" ].array[ i ] = 0;
+
+ }
+
+ }
+
+ var positions = this.attributes[ "position" ].array;
+ var normals = this.attributes[ "normal" ].array;
+
+ var vA, vB, vC, x, y, z,
+
+ pA = new THREE.Vector3(),
+ pB = new THREE.Vector3(),
+ pC = new THREE.Vector3(),
+
+ cb = new THREE.Vector3(),
+ ab = new THREE.Vector3();
+
+ // indexed elements
+
+ if ( this.attributes[ "index" ] ) {
+
+ var indices = this.attributes[ "index" ].array;
+
+ var offsets = this.offsets;
+
+ for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+ var start = offsets[ j ].start;
+ var count = offsets[ j ].count;
+ var index = offsets[ j ].index;
+
+ for ( i = start, il = start + count; i < il; i += 3 ) {
+
+ vA = index + indices[ i ];
+ vB = index + indices[ i + 1 ];
+ vC = index + indices[ i + 2 ];
+
+ x = positions[ vA * 3 ];
+ y = positions[ vA * 3 + 1 ];
+ z = positions[ vA * 3 + 2 ];
+ pA.set( x, y, z );
+
+ x = positions[ vB * 3 ];
+ y = positions[ vB * 3 + 1 ];
+ z = positions[ vB * 3 + 2 ];
+ pB.set( x, y, z );
+
+ x = positions[ vC * 3 ];
+ y = positions[ vC * 3 + 1 ];
+ z = positions[ vC * 3 + 2 ];
+ pC.set( x, y, z );
+
+ cb.subVectors( pC, pB );
+ ab.subVectors( pA, pB );
+ cb.cross( ab );
+
+ normals[ vA * 3 ] += cb.x;
+ normals[ vA * 3 + 1 ] += cb.y;
+ normals[ vA * 3 + 2 ] += cb.z;
+
+ normals[ vB * 3 ] += cb.x;
+ normals[ vB * 3 + 1 ] += cb.y;
+ normals[ vB * 3 + 2 ] += cb.z;
+
+ normals[ vC * 3 ] += cb.x;
+ normals[ vC * 3 + 1 ] += cb.y;
+ normals[ vC * 3 + 2 ] += cb.z;
+
+ }
+
+ }
+
+ // non-indexed elements (unconnected triangle soup)
+
+ } else {
+
+ for ( i = 0, il = positions.length; i < il; i += 9 ) {
+
+ x = positions[ i ];
+ y = positions[ i + 1 ];
+ z = positions[ i + 2 ];
+ pA.set( x, y, z );
+
+ x = positions[ i + 3 ];
+ y = positions[ i + 4 ];
+ z = positions[ i + 5 ];
+ pB.set( x, y, z );
+
+ x = positions[ i + 6 ];
+ y = positions[ i + 7 ];
+ z = positions[ i + 8 ];
+ pC.set( x, y, z );
+
+ cb.subVectors( pC, pB );
+ ab.subVectors( pA, pB );
+ cb.cross( ab );
+
+ normals[ i ] = cb.x;
+ normals[ i + 1 ] = cb.y;
+ normals[ i + 2 ] = cb.z;
+
+ normals[ i + 3 ] = cb.x;
+ normals[ i + 4 ] = cb.y;
+ normals[ i + 5 ] = cb.z;
+
+ normals[ i + 6 ] = cb.x;
+ normals[ i + 7 ] = cb.y;
+ normals[ i + 8 ] = cb.z;
+
+ }
+
+ }
+
+ this.normalizeNormals();
+
+ this.normalsNeedUpdate = true;
+
+ }
+
+ },
+
+ normalizeNormals: function () {
+
+ var normals = this.attributes[ "normal" ].array;
+
+ var x, y, z, n;
+
+ for ( var i = 0, il = normals.length; i < il; i += 3 ) {
+
+ x = normals[ i ];
+ y = normals[ i + 1 ];
+ z = normals[ i + 2 ];
+
+ n = 1.0 / Math.sqrt( x * x + y * y + z * z );
+
+ normals[ i ] *= n;
+ normals[ i + 1 ] *= n;
+ normals[ i + 2 ] *= n;
+
+ }
+
+ },
+
+ computeTangents: function () {
+
+ // based on http://www.terathon.com/code/tangent.html
+ // (per vertex tangents)
+
+ if ( this.attributes[ "index" ] === undefined ||
+ this.attributes[ "position" ] === undefined ||
+ this.attributes[ "normal" ] === undefined ||
+ this.attributes[ "uv" ] === undefined ) {
+
+ console.warn( "Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()" );
+ return;
+
+ }
+
+ var indices = this.attributes[ "index" ].array;
+ var positions = this.attributes[ "position" ].array;
+ var normals = this.attributes[ "normal" ].array;
+ var uvs = this.attributes[ "uv" ].array;
+
+ var nVertices = positions.length / 3;
+
+ if ( this.attributes[ "tangent" ] === undefined ) {
+
+ var nTangentElements = 4 * nVertices;
+
+ this.attributes[ "tangent" ] = {
+
+ itemSize: 4,
+ array: new Float32Array( nTangentElements )
+
+ };
+
+ }
+
+ var tangents = this.attributes[ "tangent" ].array;
+
+ var tan1 = [], tan2 = [];
+
+ for ( var k = 0; k < nVertices; k ++ ) {
+
+ tan1[ k ] = new THREE.Vector3();
+ tan2[ k ] = new THREE.Vector3();
+
+ }
+
+ var xA, yA, zA,
+ xB, yB, zB,
+ xC, yC, zC,
+
+ uA, vA,
+ uB, vB,
+ uC, vC,
+
+ x1, x2, y1, y2, z1, z2,
+ s1, s2, t1, t2, r;
+
+ var sdir = new THREE.Vector3(), tdir = new THREE.Vector3();
+
+ function handleTriangle( a, b, c ) {
+
+ xA = positions[ a * 3 ];
+ yA = positions[ a * 3 + 1 ];
+ zA = positions[ a * 3 + 2 ];
+
+ xB = positions[ b * 3 ];
+ yB = positions[ b * 3 + 1 ];
+ zB = positions[ b * 3 + 2 ];
+
+ xC = positions[ c * 3 ];
+ yC = positions[ c * 3 + 1 ];
+ zC = positions[ c * 3 + 2 ];
+
+ uA = uvs[ a * 2 ];
+ vA = uvs[ a * 2 + 1 ];
+
+ uB = uvs[ b * 2 ];
+ vB = uvs[ b * 2 + 1 ];
+
+ uC = uvs[ c * 2 ];
+ vC = uvs[ c * 2 + 1 ];
+
+ x1 = xB - xA;
+ x2 = xC - xA;
+
+ y1 = yB - yA;
+ y2 = yC - yA;
+
+ z1 = zB - zA;
+ z2 = zC - zA;
+
+ s1 = uB - uA;
+ s2 = uC - uA;
+
+ t1 = vB - vA;
+ t2 = vC - vA;
+
+ r = 1.0 / ( s1 * t2 - s2 * t1 );
+
+ sdir.set(
+ ( t2 * x1 - t1 * x2 ) * r,
+ ( t2 * y1 - t1 * y2 ) * r,
+ ( t2 * z1 - t1 * z2 ) * r
+ );
+
+ tdir.set(
+ ( s1 * x2 - s2 * x1 ) * r,
+ ( s1 * y2 - s2 * y1 ) * r,
+ ( s1 * z2 - s2 * z1 ) * r
+ );
+
+ tan1[ a ].add( sdir );
+ tan1[ b ].add( sdir );
+ tan1[ c ].add( sdir );
+
+ tan2[ a ].add( tdir );
+ tan2[ b ].add( tdir );
+ tan2[ c ].add( tdir );
+
+ }
+
+ var i, il;
+ var j, jl;
+ var iA, iB, iC;
+
+ var offsets = this.offsets;
+
+ for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+ var start = offsets[ j ].start;
+ var count = offsets[ j ].count;
+ var index = offsets[ j ].index;
+
+ for ( i = start, il = start + count; i < il; i += 3 ) {
+
+ iA = index + indices[ i ];
+ iB = index + indices[ i + 1 ];
+ iC = index + indices[ i + 2 ];
+
+ handleTriangle( iA, iB, iC );
+
+ }
+
+ }
+
+ var tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3();
+ var n = new THREE.Vector3(), n2 = new THREE.Vector3();
+ var w, t, test;
+
+ function handleVertex( v ) {
+
+ n.x = normals[ v * 3 ];
+ n.y = normals[ v * 3 + 1 ];
+ n.z = normals[ v * 3 + 2 ];
+
+ n2.copy( n );
+
+ t = tan1[ v ];
+
+ // Gram-Schmidt orthogonalize
+
+ tmp.copy( t );
+ tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+ // Calculate handedness
+
+ tmp2.crossVectors( n2, t );
+ test = tmp2.dot( tan2[ v ] );
+ w = ( test < 0.0 ) ? -1.0 : 1.0;
+
+ tangents[ v * 4 ] = tmp.x;
+ tangents[ v * 4 + 1 ] = tmp.y;
+ tangents[ v * 4 + 2 ] = tmp.z;
+ tangents[ v * 4 + 3 ] = w;
+
+ }
+
+ for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+ var start = offsets[ j ].start;
+ var count = offsets[ j ].count;
+ var index = offsets[ j ].index;
+
+ for ( i = start, il = start + count; i < il; i += 3 ) {
+
+ iA = index + indices[ i ];
+ iB = index + indices[ i + 1 ];
+ iC = index + indices[ i + 2 ];
+
+ handleVertex( iA );
+ handleVertex( iB );
+ handleVertex( iC );
+
+ }
+
+ }
+
+ this.hasTangents = true;
+ this.tangentsNeedUpdate = true;
+
+ },
+
+ clone: function () {
+
+ var geometry = new THREE.BufferGeometry();
+
+ var types = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array ];
+
+ for ( var attr in this.attributes ) {
+
+ var sourceAttr = this.attributes[ attr ];
+ var sourceArray = sourceAttr.array;
+
+ var attribute = {
+
+ itemSize: sourceAttr.itemSize,
+ numItems: sourceAttr.numItems,
+ array: null
+
+ };
+
+ for ( var i = 0, il = types.length; i < il; i ++ ) {
+
+ var type = types[ i ];
+
+ if ( sourceArray instanceof type ) {
+
+ attribute.array = new type( sourceArray );
+ break;
+
+ }
+
+ }
+
+ geometry.attributes[ attr ] = attribute;
+
+ }
+
+ for ( var i = 0, il = this.offsets.length; i < il; i ++ ) {
+
+ var offset = this.offsets[ i ];
+
+ geometry.offsets.push( {
+
+ start: offset.start,
+ index: offset.index,
+ count: offset.count
+
+ } );
+
+ }
+
+ return geometry;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.BufferGeometry.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.Camera = function () {
+
+ THREE.Object3D.call( this );
+
+ this.matrixWorldInverse = new THREE.Matrix4();
+
+ this.projectionMatrix = new THREE.Matrix4();
+ this.projectionMatrixInverse = new THREE.Matrix4();
+
+};
+
+THREE.Camera.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Camera.prototype.lookAt = function () {
+
+ // This routine does not support cameras with rotated and/or translated parent(s)
+
+ var m1 = new THREE.Matrix4();
+
+ return function ( vector ) {
+
+ m1.lookAt( this.position, vector, this.up );
+
+ this.quaternion.setFromRotationMatrix( m1 );
+
+ };
+
+}();
+
+THREE.Camera.prototype.clone = function (camera) {
+
+ if ( camera === undefined ) camera = new THREE.Camera();
+
+ THREE.Object3D.prototype.clone.call( this, camera );
+
+ camera.matrixWorldInverse.copy( this.matrixWorldInverse );
+ camera.projectionMatrix.copy( this.projectionMatrix );
+ camera.projectionMatrixInverse.copy( this.projectionMatrixInverse );
+
+ return camera;
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.OrthographicCamera = function ( left, right, top, bottom, near, far ) {
+
+ THREE.Camera.call( this );
+
+ this.left = left;
+ this.right = right;
+ this.top = top;
+ this.bottom = bottom;
+
+ this.near = ( near !== undefined ) ? near : 0.1;
+ this.far = ( far !== undefined ) ? far : 2000;
+
+ this.updateProjectionMatrix();
+
+};
+
+THREE.OrthographicCamera.prototype = Object.create( THREE.Camera.prototype );
+
+THREE.OrthographicCamera.prototype.updateProjectionMatrix = function () {
+
+ this.projectionMatrix.makeOrthographic( this.left, this.right, this.top, this.bottom, this.near, this.far );
+
+};
+
+THREE.OrthographicCamera.prototype.clone = function () {
+
+ var camera = new THREE.OrthographicCamera();
+
+ THREE.Camera.prototype.clone.call( this, camera );
+
+ camera.left = this.left;
+ camera.right = this.right;
+ camera.top = this.top;
+ camera.bottom = this.bottom;
+
+ camera.near = this.near;
+ camera.far = this.far;
+
+ return camera;
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author greggman / http://games.greggman.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ */
+
+THREE.PerspectiveCamera = function ( fov, aspect, near, far ) {
+
+ THREE.Camera.call( this );
+
+ this.fov = fov !== undefined ? fov : 50;
+ this.aspect = aspect !== undefined ? aspect : 1;
+ this.near = near !== undefined ? near : 0.1;
+ this.far = far !== undefined ? far : 2000;
+
+ this.updateProjectionMatrix();
+
+};
+
+THREE.PerspectiveCamera.prototype = Object.create( THREE.Camera.prototype );
+
+
+/**
+ * Uses Focal Length (in mm) to estimate and set FOV
+ * 35mm (fullframe) camera is used if frame size is not specified;
+ * Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html
+ */
+
+THREE.PerspectiveCamera.prototype.setLens = function ( focalLength, frameHeight ) {
+
+ if ( frameHeight === undefined ) frameHeight = 24;
+
+ this.fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) );
+ this.updateProjectionMatrix();
+
+}
+
+
+/**
+ * Sets an offset in a larger frustum. This is useful for multi-window or
+ * multi-monitor/multi-machine setups.
+ *
+ * For example, if you have 3x2 monitors and each monitor is 1920x1080 and
+ * the monitors are in grid like this
+ *
+ * +---+---+---+
+ * | A | B | C |
+ * +---+---+---+
+ * | D | E | F |
+ * +---+---+---+
+ *
+ * then for each monitor you would call it like this
+ *
+ * var w = 1920;
+ * var h = 1080;
+ * var fullWidth = w * 3;
+ * var fullHeight = h * 2;
+ *
+ * --A--
+ * camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
+ * --B--
+ * camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
+ * --C--
+ * camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
+ * --D--
+ * camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
+ * --E--
+ * camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
+ * --F--
+ * camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
+ *
+ * Note there is no reason monitors have to be the same size or in a grid.
+ */
+
+THREE.PerspectiveCamera.prototype.setViewOffset = function ( fullWidth, fullHeight, x, y, width, height ) {
+
+ this.fullWidth = fullWidth;
+ this.fullHeight = fullHeight;
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+
+ this.updateProjectionMatrix();
+
+};
+
+
+THREE.PerspectiveCamera.prototype.updateProjectionMatrix = function () {
+
+ if ( this.fullWidth ) {
+
+ var aspect = this.fullWidth / this.fullHeight;
+ var top = Math.tan( THREE.Math.degToRad( this.fov * 0.5 ) ) * this.near;
+ var bottom = -top;
+ var left = aspect * bottom;
+ var right = aspect * top;
+ var width = Math.abs( right - left );
+ var height = Math.abs( top - bottom );
+
+ this.projectionMatrix.makeFrustum(
+ left + this.x * width / this.fullWidth,
+ left + ( this.x + this.width ) * width / this.fullWidth,
+ top - ( this.y + this.height ) * height / this.fullHeight,
+ top - this.y * height / this.fullHeight,
+ this.near,
+ this.far
+ );
+
+ } else {
+
+ this.projectionMatrix.makePerspective( this.fov, this.aspect, this.near, this.far );
+
+ }
+
+};
+
+THREE.PerspectiveCamera.prototype.clone = function () {
+
+ var camera = new THREE.PerspectiveCamera();
+
+ THREE.Camera.prototype.clone.call( this, camera );
+
+ camera.fov = this.fov;
+ camera.aspect = this.aspect;
+ camera.near = this.near;
+ camera.far = this.far;
+
+ return camera;
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Light = function ( hex ) {
+
+ THREE.Object3D.call( this );
+
+ this.color = new THREE.Color( hex );
+
+};
+
+THREE.Light.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Light.prototype.clone = function ( light ) {
+
+ if ( light === undefined ) light = new THREE.Light();
+
+ THREE.Object3D.prototype.clone.call( this, light );
+
+ light.color.copy( this.color );
+
+ return light;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.AmbientLight = function ( hex ) {
+
+ THREE.Light.call( this, hex );
+
+};
+
+THREE.AmbientLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.AmbientLight.prototype.clone = function () {
+
+ var light = new THREE.AmbientLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ return light;
+
+};
+
+/**
+ * @author MPanknin / http://www.redplant.de/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.AreaLight = function ( hex, intensity ) {
+
+ THREE.Light.call( this, hex );
+
+ this.normal = new THREE.Vector3( 0, -1, 0 );
+ this.right = new THREE.Vector3( 1, 0, 0 );
+
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+ this.width = 1.0;
+ this.height = 1.0;
+
+ this.constantAttenuation = 1.5;
+ this.linearAttenuation = 0.5;
+ this.quadraticAttenuation = 0.1;
+
+};
+
+THREE.AreaLight.prototype = Object.create( THREE.Light.prototype );
+
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DirectionalLight = function ( hex, intensity ) {
+
+ THREE.Light.call( this, hex );
+
+ this.position.set( 0, 1, 0 );
+ this.target = new THREE.Object3D();
+
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+ this.castShadow = false;
+ this.onlyShadow = false;
+
+ //
+
+ this.shadowCameraNear = 50;
+ this.shadowCameraFar = 5000;
+
+ this.shadowCameraLeft = -500;
+ this.shadowCameraRight = 500;
+ this.shadowCameraTop = 500;
+ this.shadowCameraBottom = -500;
+
+ this.shadowCameraVisible = false;
+
+ this.shadowBias = 0;
+ this.shadowDarkness = 0.5;
+
+ this.shadowMapWidth = 512;
+ this.shadowMapHeight = 512;
+
+ //
+
+ this.shadowCascade = false;
+
+ this.shadowCascadeOffset = new THREE.Vector3( 0, 0, -1000 );
+ this.shadowCascadeCount = 2;
+
+ this.shadowCascadeBias = [ 0, 0, 0 ];
+ this.shadowCascadeWidth = [ 512, 512, 512 ];
+ this.shadowCascadeHeight = [ 512, 512, 512 ];
+
+ this.shadowCascadeNearZ = [ -1.000, 0.990, 0.998 ];
+ this.shadowCascadeFarZ = [ 0.990, 0.998, 1.000 ];
+
+ this.shadowCascadeArray = [];
+
+ //
+
+ this.shadowMap = null;
+ this.shadowMapSize = null;
+ this.shadowCamera = null;
+ this.shadowMatrix = null;
+
+};
+
+THREE.DirectionalLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.DirectionalLight.prototype.clone = function () {
+
+ var light = new THREE.DirectionalLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ light.target = this.target.clone();
+
+ light.intensity = this.intensity;
+
+ light.castShadow = this.castShadow;
+ light.onlyShadow = this.onlyShadow;
+
+ return light;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.HemisphereLight = function ( skyColorHex, groundColorHex, intensity ) {
+
+ THREE.Light.call( this, skyColorHex );
+
+ this.position.set( 0, 100, 0 );
+
+ this.groundColor = new THREE.Color( groundColorHex );
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+};
+
+THREE.HemisphereLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.HemisphereLight.prototype.clone = function () {
+
+ var light = new THREE.HemisphereLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ light.groundColor.copy( this.groundColor );
+ light.intensity = this.intensity;
+
+ return light;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.PointLight = function ( hex, intensity, distance ) {
+
+ THREE.Light.call( this, hex );
+
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+ this.distance = ( distance !== undefined ) ? distance : 0;
+
+};
+
+THREE.PointLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.PointLight.prototype.clone = function () {
+
+ var light = new THREE.PointLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ light.intensity = this.intensity;
+ light.distance = this.distance;
+
+ return light;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SpotLight = function ( hex, intensity, distance, angle, exponent ) {
+
+ THREE.Light.call( this, hex );
+
+ this.position.set( 0, 1, 0 );
+ this.target = new THREE.Object3D();
+
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+ this.distance = ( distance !== undefined ) ? distance : 0;
+ this.angle = ( angle !== undefined ) ? angle : Math.PI / 3;
+ this.exponent = ( exponent !== undefined ) ? exponent : 10;
+
+ this.castShadow = false;
+ this.onlyShadow = false;
+
+ //
+
+ this.shadowCameraNear = 50;
+ this.shadowCameraFar = 5000;
+ this.shadowCameraFov = 50;
+
+ this.shadowCameraVisible = false;
+
+ this.shadowBias = 0;
+ this.shadowDarkness = 0.5;
+
+ this.shadowMapWidth = 512;
+ this.shadowMapHeight = 512;
+
+ //
+
+ this.shadowMap = null;
+ this.shadowMapSize = null;
+ this.shadowCamera = null;
+ this.shadowMatrix = null;
+
+};
+
+THREE.SpotLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.SpotLight.prototype.clone = function () {
+
+ var light = new THREE.SpotLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ light.target = this.target.clone();
+
+ light.intensity = this.intensity;
+ light.distance = this.distance;
+ light.angle = this.angle;
+ light.exponent = this.exponent;
+
+ light.castShadow = this.castShadow;
+ light.onlyShadow = this.onlyShadow;
+
+ return light;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Loader = function ( showStatus ) {
+
+ this.showStatus = showStatus;
+ this.statusDomElement = showStatus ? THREE.Loader.prototype.addStatusElement() : null;
+
+ this.onLoadStart = function () {};
+ this.onLoadProgress = function () {};
+ this.onLoadComplete = function () {};
+
+};
+
+THREE.Loader.prototype = {
+
+ constructor: THREE.Loader,
+
+ crossOrigin: 'anonymous',
+
+ addStatusElement: function () {
+
+ var e = document.createElement( "div" );
+
+ e.style.position = "absolute";
+ e.style.right = "0px";
+ e.style.top = "0px";
+ e.style.fontSize = "0.8em";
+ e.style.textAlign = "left";
+ e.style.background = "rgba(0,0,0,0.25)";
+ e.style.color = "#fff";
+ e.style.width = "120px";
+ e.style.padding = "0.5em 0.5em 0.5em 0.5em";
+ e.style.zIndex = 1000;
+
+ e.innerHTML = "Loading ...";
+
+ return e;
+
+ },
+
+ updateProgress: function ( progress ) {
+
+ var message = "Loaded ";
+
+ if ( progress.total ) {
+
+ message += ( 100 * progress.loaded / progress.total ).toFixed(0) + "%";
+
+
+ } else {
+
+ message += ( progress.loaded / 1000 ).toFixed(2) + " KB";
+
+ }
+
+ this.statusDomElement.innerHTML = message;
+
+ },
+
+ extractUrlBase: function ( url ) {
+
+ var parts = url.split( '/' );
+ parts.pop();
+ return ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/';
+
+ },
+
+ initMaterials: function ( materials, texturePath ) {
+
+ var array = [];
+
+ for ( var i = 0; i < materials.length; ++ i ) {
+
+ array[ i ] = THREE.Loader.prototype.createMaterial( materials[ i ], texturePath );
+
+ }
+
+ return array;
+
+ },
+
+ needsTangents: function ( materials ) {
+
+ for( var i = 0, il = materials.length; i < il; i ++ ) {
+
+ var m = materials[ i ];
+
+ if ( m instanceof THREE.ShaderMaterial ) return true;
+
+ }
+
+ return false;
+
+ },
+
+ createMaterial: function ( m, texturePath ) {
+
+ var _this = this;
+
+ function is_pow2( n ) {
+
+ var l = Math.log( n ) / Math.LN2;
+ return Math.floor( l ) == l;
+
+ }
+
+ function nearest_pow2( n ) {
+
+ var l = Math.log( n ) / Math.LN2;
+ return Math.pow( 2, Math.round( l ) );
+
+ }
+
+ function load_image( where, url ) {
+
+ var image = new Image();
+
+ image.onload = function () {
+
+ if ( !is_pow2( this.width ) || !is_pow2( this.height ) ) {
+
+ var width = nearest_pow2( this.width );
+ var height = nearest_pow2( this.height );
+
+ where.image.width = width;
+ where.image.height = height;
+ where.image.getContext( '2d' ).drawImage( this, 0, 0, width, height );
+
+ } else {
+
+ where.image = this;
+
+ }
+
+ where.needsUpdate = true;
+
+ };
+
+ image.crossOrigin = _this.crossOrigin;
+ image.src = url;
+
+ }
+
+ function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) {
+
+ var isCompressed = /\.dds$/i.test( sourceFile );
+ var fullPath = texturePath + "/" + sourceFile;
+
+ if ( isCompressed ) {
+
+ var texture = THREE.ImageUtils.loadCompressedTexture( fullPath );
+
+ where[ name ] = texture;
+
+ } else {
+
+ var texture = document.createElement( 'canvas' );
+
+ where[ name ] = new THREE.Texture( texture );
+
+ }
+
+ where[ name ].sourceFile = sourceFile;
+
+ if( repeat ) {
+
+ where[ name ].repeat.set( repeat[ 0 ], repeat[ 1 ] );
+
+ if ( repeat[ 0 ] !== 1 ) where[ name ].wrapS = THREE.RepeatWrapping;
+ if ( repeat[ 1 ] !== 1 ) where[ name ].wrapT = THREE.RepeatWrapping;
+
+ }
+
+ if ( offset ) {
+
+ where[ name ].offset.set( offset[ 0 ], offset[ 1 ] );
+
+ }
+
+ if ( wrap ) {
+
+ var wrapMap = {
+ "repeat": THREE.RepeatWrapping,
+ "mirror": THREE.MirroredRepeatWrapping
+ }
+
+ if ( wrapMap[ wrap[ 0 ] ] !== undefined ) where[ name ].wrapS = wrapMap[ wrap[ 0 ] ];
+ if ( wrapMap[ wrap[ 1 ] ] !== undefined ) where[ name ].wrapT = wrapMap[ wrap[ 1 ] ];
+
+ }
+
+ if ( anisotropy ) {
+
+ where[ name ].anisotropy = anisotropy;
+
+ }
+
+ if ( ! isCompressed ) {
+
+ load_image( where[ name ], fullPath );
+
+ }
+
+ }
+
+ function rgb2hex( rgb ) {
+
+ return ( rgb[ 0 ] * 255 << 16 ) + ( rgb[ 1 ] * 255 << 8 ) + rgb[ 2 ] * 255;
+
+ }
+
+ // defaults
+
+ var mtype = "MeshLambertMaterial";
+ var mpars = { color: 0xeeeeee, opacity: 1.0, map: null, lightMap: null, normalMap: null, bumpMap: null, wireframe: false };
+
+ // parameters from model file
+
+ if ( m.shading ) {
+
+ var shading = m.shading.toLowerCase();
+
+ if ( shading === "phong" ) mtype = "MeshPhongMaterial";
+ else if ( shading === "basic" ) mtype = "MeshBasicMaterial";
+
+ }
+
+ if ( m.blending !== undefined && THREE[ m.blending ] !== undefined ) {
+
+ mpars.blending = THREE[ m.blending ];
+
+ }
+
+ if ( m.transparent !== undefined || m.opacity < 1.0 ) {
+
+ mpars.transparent = m.transparent;
+
+ }
+
+ if ( m.depthTest !== undefined ) {
+
+ mpars.depthTest = m.depthTest;
+
+ }
+
+ if ( m.depthWrite !== undefined ) {
+
+ mpars.depthWrite = m.depthWrite;
+
+ }
+
+ if ( m.visible !== undefined ) {
+
+ mpars.visible = m.visible;
+
+ }
+
+ if ( m.flipSided !== undefined ) {
+
+ mpars.side = THREE.BackSide;
+
+ }
+
+ if ( m.doubleSided !== undefined ) {
+
+ mpars.side = THREE.DoubleSide;
+
+ }
+
+ if ( m.wireframe !== undefined ) {
+
+ mpars.wireframe = m.wireframe;
+
+ }
+
+ if ( m.vertexColors !== undefined ) {
+
+ if ( m.vertexColors === "face" ) {
+
+ mpars.vertexColors = THREE.FaceColors;
+
+ } else if ( m.vertexColors ) {
+
+ mpars.vertexColors = THREE.VertexColors;
+
+ }
+
+ }
+
+ // colors
+
+ if ( m.colorDiffuse ) {
+
+ mpars.color = rgb2hex( m.colorDiffuse );
+
+ } else if ( m.DbgColor ) {
+
+ mpars.color = m.DbgColor;
+
+ }
+
+ if ( m.colorSpecular ) {
+
+ mpars.specular = rgb2hex( m.colorSpecular );
+
+ }
+
+ if ( m.colorAmbient ) {
+
+ mpars.ambient = rgb2hex( m.colorAmbient );
+
+ }
+
+ // modifiers
+
+ if ( m.transparency ) {
+
+ mpars.opacity = m.transparency;
+
+ }
+
+ if ( m.specularCoef ) {
+
+ mpars.shininess = m.specularCoef;
+
+ }
+
+ // textures
+
+ if ( m.mapDiffuse && texturePath ) {
+
+ create_texture( mpars, "map", m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy );
+
+ }
+
+ if ( m.mapLight && texturePath ) {
+
+ create_texture( mpars, "lightMap", m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy );
+
+ }
+
+ if ( m.mapBump && texturePath ) {
+
+ create_texture( mpars, "bumpMap", m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy );
+
+ }
+
+ if ( m.mapNormal && texturePath ) {
+
+ create_texture( mpars, "normalMap", m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy );
+
+ }
+
+ if ( m.mapSpecular && texturePath ) {
+
+ create_texture( mpars, "specularMap", m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy );
+
+ }
+
+ //
+
+ if ( m.mapBumpScale ) {
+
+ mpars.bumpScale = m.mapBumpScale;
+
+ }
+
+ // special case for normal mapped material
+
+ if ( m.mapNormal ) {
+
+ var shader = THREE.ShaderLib[ "normalmap" ];
+ var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+ uniforms[ "tNormal" ].value = mpars.normalMap;
+
+ if ( m.mapNormalFactor ) {
+
+ uniforms[ "uNormalScale" ].value.set( m.mapNormalFactor, m.mapNormalFactor );
+
+ }
+
+ if ( mpars.map ) {
+
+ uniforms[ "tDiffuse" ].value = mpars.map;
+ uniforms[ "enableDiffuse" ].value = true;
+
+ }
+
+ if ( mpars.specularMap ) {
+
+ uniforms[ "tSpecular" ].value = mpars.specularMap;
+ uniforms[ "enableSpecular" ].value = true;
+
+ }
+
+ if ( mpars.lightMap ) {
+
+ uniforms[ "tAO" ].value = mpars.lightMap;
+ uniforms[ "enableAO" ].value = true;
+
+ }
+
+ // for the moment don't handle displacement texture
+
+ uniforms[ "uDiffuseColor" ].value.setHex( mpars.color );
+ uniforms[ "uSpecularColor" ].value.setHex( mpars.specular );
+ uniforms[ "uAmbientColor" ].value.setHex( mpars.ambient );
+
+ uniforms[ "uShininess" ].value = mpars.shininess;
+
+ if ( mpars.opacity !== undefined ) {
+
+ uniforms[ "uOpacity" ].value = mpars.opacity;
+
+ }
+
+ var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true };
+ var material = new THREE.ShaderMaterial( parameters );
+
+ if ( mpars.transparent ) {
+
+ material.transparent = true;
+
+ }
+
+ } else {
+
+ var material = new THREE[ mtype ]( mpars );
+
+ }
+
+ if ( m.DbgName !== undefined ) material.name = m.DbgName;
+
+ return material;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.XHRLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.XHRLoader.prototype = {
+
+ constructor: THREE.XHRLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+ var request = new XMLHttpRequest();
+
+ if ( onLoad !== undefined ) {
+
+ request.addEventListener( 'load', function ( event ) {
+
+ onLoad( event.target.responseText );
+ scope.manager.itemEnd( url );
+
+ }, false );
+
+ }
+
+ if ( onProgress !== undefined ) {
+
+ request.addEventListener( 'progress', function ( event ) {
+
+ onProgress( event );
+
+ }, false );
+
+ }
+
+ if ( onError !== undefined ) {
+
+ request.addEventListener( 'error', function ( event ) {
+
+ onError( event );
+
+ }, false );
+
+ }
+
+ if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin;
+
+ request.open( 'GET', url, true );
+ request.send( null );
+
+ scope.manager.itemStart( url );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ImageLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.ImageLoader.prototype = {
+
+ constructor: THREE.ImageLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+ var image = document.createElement( 'img' );
+
+ if ( onLoad !== undefined ) {
+
+ image.addEventListener( 'load', function ( event ) {
+
+ scope.manager.itemEnd( url );
+ onLoad( this );
+
+ }, false );
+
+ }
+
+ if ( onProgress !== undefined ) {
+
+ image.addEventListener( 'progress', function ( event ) {
+
+ onProgress( event );
+
+ }, false );
+
+ }
+
+ if ( onError !== undefined ) {
+
+ image.addEventListener( 'error', function ( event ) {
+
+ onError( event );
+
+ }, false );
+
+ }
+
+ if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
+
+ image.src = url;
+
+ scope.manager.itemStart( url );
+
+ return image;
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ }
+
+}
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.JSONLoader = function ( showStatus ) {
+
+ THREE.Loader.call( this, showStatus );
+
+ this.withCredentials = false;
+
+};
+
+THREE.JSONLoader.prototype = Object.create( THREE.Loader.prototype );
+
+THREE.JSONLoader.prototype.load = function ( url, callback, texturePath ) {
+
+ var scope = this;
+
+ // todo: unify load API to for easier SceneLoader use
+
+ texturePath = texturePath && ( typeof texturePath === "string" ) ? texturePath : this.extractUrlBase( url );
+
+ this.onLoadStart();
+ this.loadAjaxJSON( this, url, callback, texturePath );
+
+};
+
+THREE.JSONLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, callbackProgress ) {
+
+ var xhr = new XMLHttpRequest();
+
+ var length = 0;
+
+ xhr.onreadystatechange = function () {
+
+ if ( xhr.readyState === xhr.DONE ) {
+
+ if ( xhr.status === 200 || xhr.status === 0 ) {
+
+ if ( xhr.responseText ) {
+
+ var json = JSON.parse( xhr.responseText );
+ var result = context.parse( json, texturePath );
+ callback( result.geometry, result.materials );
+
+ } else {
+
+ console.warn( "THREE.JSONLoader: [" + url + "] seems to be unreachable or file there is empty" );
+
+ }
+
+ // in context of more complex asset initialization
+ // do not block on single failed file
+ // maybe should go even one more level up
+
+ context.onLoadComplete();
+
+ } else {
+
+ console.error( "THREE.JSONLoader: Couldn't load [" + url + "] [" + xhr.status + "]" );
+
+ }
+
+ } else if ( xhr.readyState === xhr.LOADING ) {
+
+ if ( callbackProgress ) {
+
+ if ( length === 0 ) {
+
+ length = xhr.getResponseHeader( "Content-Length" );
+
+ }
+
+ callbackProgress( { total: length, loaded: xhr.responseText.length } );
+
+ }
+
+ } else if ( xhr.readyState === xhr.HEADERS_RECEIVED ) {
+
+ if ( callbackProgress !== undefined ) {
+
+ length = xhr.getResponseHeader( "Content-Length" );
+
+ }
+
+ }
+
+ };
+
+ xhr.open( "GET", url, true );
+ xhr.withCredentials = this.withCredentials;
+ xhr.send( null );
+
+};
+
+THREE.JSONLoader.prototype.parse = function ( json, texturePath ) {
+
+ var scope = this,
+ geometry = new THREE.Geometry(),
+ scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0;
+
+ parseModel( scale );
+
+ parseSkin();
+ parseMorphing( scale );
+
+ geometry.computeCentroids();
+ geometry.computeFaceNormals();
+ geometry.computeBoundingSphere();
+
+ function parseModel( scale ) {
+
+ function isBitSet( value, position ) {
+
+ return value & ( 1 << position );
+
+ }
+
+ var i, j, fi,
+
+ offset, zLength,
+
+ colorIndex, normalIndex, uvIndex, materialIndex,
+
+ type,
+ isQuad,
+ hasMaterial,
+ hasFaceVertexUv,
+ hasFaceNormal, hasFaceVertexNormal,
+ hasFaceColor, hasFaceVertexColor,
+
+ vertex, face, faceA, faceB, color, hex, normal,
+
+ uvLayer, uv, u, v,
+
+ faces = json.faces,
+ vertices = json.vertices,
+ normals = json.normals,
+ colors = json.colors,
+
+ nUvLayers = 0;
+
+ if ( json.uvs !== undefined ) {
+
+ // disregard empty arrays
+
+ for ( i = 0; i < json.uvs.length; i++ ) {
+
+ if ( json.uvs[ i ].length ) nUvLayers ++;
+
+ }
+
+ for ( i = 0; i < nUvLayers; i++ ) {
+
+ geometry.faceVertexUvs[ i ] = [];
+
+ }
+
+ }
+
+ offset = 0;
+ zLength = vertices.length;
+
+ while ( offset < zLength ) {
+
+ vertex = new THREE.Vector3();
+
+ vertex.x = vertices[ offset ++ ] * scale;
+ vertex.y = vertices[ offset ++ ] * scale;
+ vertex.z = vertices[ offset ++ ] * scale;
+
+ geometry.vertices.push( vertex );
+
+ }
+
+ offset = 0;
+ zLength = faces.length;
+
+ while ( offset < zLength ) {
+
+ type = faces[ offset ++ ];
+
+
+ isQuad = isBitSet( type, 0 );
+ hasMaterial = isBitSet( type, 1 );
+ hasFaceVertexUv = isBitSet( type, 3 );
+ hasFaceNormal = isBitSet( type, 4 );
+ hasFaceVertexNormal = isBitSet( type, 5 );
+ hasFaceColor = isBitSet( type, 6 );
+ hasFaceVertexColor = isBitSet( type, 7 );
+
+ // console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);
+
+ if ( isQuad ) {
+
+ faceA = new THREE.Face3();
+ faceA.a = faces[ offset ];
+ faceA.b = faces[ offset + 1 ];
+ faceA.c = faces[ offset + 3 ];
+
+ faceB = new THREE.Face3();
+ faceB.a = faces[ offset + 1 ];
+ faceB.b = faces[ offset + 2 ];
+ faceB.c = faces[ offset + 3 ];
+
+ offset += 4;
+
+ if ( hasMaterial ) {
+
+ materialIndex = faces[ offset ++ ];
+ faceA.materialIndex = materialIndex;
+ faceB.materialIndex = materialIndex;
+
+ }
+
+ // to get face <=> uv index correspondence
+
+ fi = geometry.faces.length;
+
+ if ( hasFaceVertexUv ) {
+
+ for ( i = 0; i < nUvLayers; i++ ) {
+
+ uvLayer = json.uvs[ i ];
+
+ geometry.faceVertexUvs[ i ][ fi ] = [];
+ geometry.faceVertexUvs[ i ][ fi + 1 ] = []
+
+ for ( j = 0; j < 4; j ++ ) {
+
+ uvIndex = faces[ offset ++ ];
+
+ u = uvLayer[ uvIndex * 2 ];
+ v = uvLayer[ uvIndex * 2 + 1 ];
+
+ uv = new THREE.Vector2( u, v );
+
+ if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv );
+ if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv );
+
+ }
+
+ }
+
+ }
+
+ if ( hasFaceNormal ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ faceA.normal.set(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ faceB.normal.copy( faceA.normal );
+
+ }
+
+ if ( hasFaceVertexNormal ) {
+
+ for ( i = 0; i < 4; i++ ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ normal = new THREE.Vector3(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+
+ if ( i !== 2 ) faceA.vertexNormals.push( normal );
+ if ( i !== 0 ) faceB.vertexNormals.push( normal );
+
+ }
+
+ }
+
+
+ if ( hasFaceColor ) {
+
+ colorIndex = faces[ offset ++ ];
+ hex = colors[ colorIndex ];
+
+ faceA.color.setHex( hex );
+ faceB.color.setHex( hex );
+
+ }
+
+
+ if ( hasFaceVertexColor ) {
+
+ for ( i = 0; i < 4; i++ ) {
+
+ colorIndex = faces[ offset ++ ];
+ hex = colors[ colorIndex ];
+
+ if ( i !== 2 ) faceA.vertexColors.push( new THREE.Color( hex ) );
+ if ( i !== 0 ) faceB.vertexColors.push( new THREE.Color( hex ) );
+
+ }
+
+ }
+
+ geometry.faces.push( faceA );
+ geometry.faces.push( faceB );
+
+ } else {
+
+ face = new THREE.Face3();
+ face.a = faces[ offset ++ ];
+ face.b = faces[ offset ++ ];
+ face.c = faces[ offset ++ ];
+
+ if ( hasMaterial ) {
+
+ materialIndex = faces[ offset ++ ];
+ face.materialIndex = materialIndex;
+
+ }
+
+ // to get face <=> uv index correspondence
+
+ fi = geometry.faces.length;
+
+ if ( hasFaceVertexUv ) {
+
+ for ( i = 0; i < nUvLayers; i++ ) {
+
+ uvLayer = json.uvs[ i ];
+
+ geometry.faceVertexUvs[ i ][ fi ] = [];
+
+ for ( j = 0; j < 3; j ++ ) {
+
+ uvIndex = faces[ offset ++ ];
+
+ u = uvLayer[ uvIndex * 2 ];
+ v = uvLayer[ uvIndex * 2 + 1 ];
+
+ uv = new THREE.Vector2( u, v );
+
+ geometry.faceVertexUvs[ i ][ fi ].push( uv );
+
+ }
+
+ }
+
+ }
+
+ if ( hasFaceNormal ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ face.normal.set(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ }
+
+ if ( hasFaceVertexNormal ) {
+
+ for ( i = 0; i < 3; i++ ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ normal = new THREE.Vector3(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ face.vertexNormals.push( normal );
+
+ }
+
+ }
+
+
+ if ( hasFaceColor ) {
+
+ colorIndex = faces[ offset ++ ];
+ face.color.setHex( colors[ colorIndex ] );
+
+ }
+
+
+ if ( hasFaceVertexColor ) {
+
+ for ( i = 0; i < 3; i++ ) {
+
+ colorIndex = faces[ offset ++ ];
+ face.vertexColors.push( new THREE.Color( colors[ colorIndex ] ) );
+
+ }
+
+ }
+
+ geometry.faces.push( face );
+
+ }
+
+ }
+
+ };
+
+ function parseSkin() {
+
+ var i, l, x, y, z, w, a, b, c, d;
+
+ if ( json.skinWeights ) {
+
+ for ( i = 0, l = json.skinWeights.length; i < l; i += 2 ) {
+
+ x = json.skinWeights[ i ];
+ y = json.skinWeights[ i + 1 ];
+ z = 0;
+ w = 0;
+
+ geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) );
+
+ }
+
+ }
+
+ if ( json.skinIndices ) {
+
+ for ( i = 0, l = json.skinIndices.length; i < l; i += 2 ) {
+
+ a = json.skinIndices[ i ];
+ b = json.skinIndices[ i + 1 ];
+ c = 0;
+ d = 0;
+
+ geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) );
+
+ }
+
+ }
+
+ geometry.bones = json.bones;
+ geometry.animation = json.animation;
+
+ };
+
+ function parseMorphing( scale ) {
+
+ if ( json.morphTargets !== undefined ) {
+
+ var i, l, v, vl, dstVertices, srcVertices;
+
+ for ( i = 0, l = json.morphTargets.length; i < l; i ++ ) {
+
+ geometry.morphTargets[ i ] = {};
+ geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;
+ geometry.morphTargets[ i ].vertices = [];
+
+ dstVertices = geometry.morphTargets[ i ].vertices;
+ srcVertices = json.morphTargets [ i ].vertices;
+
+ for( v = 0, vl = srcVertices.length; v < vl; v += 3 ) {
+
+ var vertex = new THREE.Vector3();
+ vertex.x = srcVertices[ v ] * scale;
+ vertex.y = srcVertices[ v + 1 ] * scale;
+ vertex.z = srcVertices[ v + 2 ] * scale;
+
+ dstVertices.push( vertex );
+
+ }
+
+ }
+
+ }
+
+ if ( json.morphColors !== undefined ) {
+
+ var i, l, c, cl, dstColors, srcColors, color;
+
+ for ( i = 0, l = json.morphColors.length; i < l; i++ ) {
+
+ geometry.morphColors[ i ] = {};
+ geometry.morphColors[ i ].name = json.morphColors[ i ].name;
+ geometry.morphColors[ i ].colors = [];
+
+ dstColors = geometry.morphColors[ i ].colors;
+ srcColors = json.morphColors [ i ].colors;
+
+ for ( c = 0, cl = srcColors.length; c < cl; c += 3 ) {
+
+ color = new THREE.Color( 0xffaa00 );
+ color.setRGB( srcColors[ c ], srcColors[ c + 1 ], srcColors[ c + 2 ] );
+ dstColors.push( color );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ if ( json.materials === undefined ) {
+
+ return { geometry: geometry };
+
+ } else {
+
+ var materials = this.initMaterials( json.materials, texturePath );
+
+ if ( this.needsTangents( materials ) ) {
+
+ geometry.computeTangents();
+
+ }
+
+ return { geometry: geometry, materials: materials };
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.LoadingManager = function ( onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loaded = 0, total = 0;
+
+ this.onLoad = onLoad;
+ this.onProgress = onProgress;
+ this.onError = onError;
+
+ this.itemStart = function ( url ) {
+
+ total ++;
+
+ };
+
+ this.itemEnd = function ( url ) {
+
+ loaded ++;
+
+ if ( scope.onProgress !== undefined ) {
+
+ scope.onProgress( url, loaded, total );
+
+ }
+
+ if ( loaded === total && scope.onLoad !== undefined ) {
+
+ scope.onLoad();
+
+ }
+
+ };
+
+};
+
+THREE.DefaultLoadingManager = new THREE.LoadingManager();
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.BufferGeometryLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.BufferGeometryLoader.prototype = {
+
+ constructor: THREE.BufferGeometryLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader();
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ parse: function ( json ) {
+
+ var geometry = new THREE.BufferGeometry();
+
+ var attributes = json.attributes;
+ var offsets = json.offsets;
+ var boundingSphere = json.boundingSphere;
+
+ for ( var key in attributes ) {
+
+ var attribute = attributes[ key ];
+
+ geometry.attributes[ key ] = {
+ itemSize: attribute.itemSize,
+ array: new self[ attribute.type ]( attribute.array )
+ }
+
+ }
+
+ if ( offsets !== undefined ) {
+
+ geometry.offsets = JSON.parse( JSON.stringify( offsets ) );
+
+ }
+
+ if ( boundingSphere !== undefined ) {
+
+ geometry.boundingSphere = new THREE.Sphere(
+ new THREE.Vector3().fromArray( boundingSphere.center !== undefined ? boundingSphere.center : [ 0, 0, 0 ] ),
+ boundingSphere.radius
+ );
+
+ }
+
+ return geometry;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.GeometryLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.GeometryLoader.prototype = {
+
+ constructor: THREE.GeometryLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader();
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ parse: function ( json ) {
+
+
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.MaterialLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.MaterialLoader.prototype = {
+
+ constructor: THREE.MaterialLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader();
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ parse: function ( json ) {
+
+ var material = new THREE[ json.type ];
+
+ if ( json.color !== undefined ) material.color.setHex( json.color );
+ if ( json.ambient !== undefined ) material.ambient.setHex( json.ambient );
+ if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive );
+ if ( json.specular !== undefined ) material.specular.setHex( json.specular );
+ if ( json.shininess !== undefined ) material.shininess = json.shininess;
+ if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors;
+ if ( json.blending !== undefined ) material.blending = json.blending;
+ if ( json.opacity !== undefined ) material.opacity = json.opacity;
+ if ( json.transparent !== undefined ) material.transparent = json.transparent;
+ if ( json.wireframe !== undefined ) material.wireframe = json.wireframe;
+
+ if ( json.materials !== undefined ) {
+
+ for ( var i = 0, l = json.materials.length; i < l; i ++ ) {
+
+ material.materials.push( this.parse( json.materials[ i ] ) );
+
+ }
+
+ }
+
+ return material;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ObjectLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.ObjectLoader.prototype = {
+
+ constructor: THREE.ObjectLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader( scope.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ parse: function ( json ) {
+
+ var geometries = this.parseGeometries( json.geometries );
+ var materials = this.parseMaterials( json.materials );
+ var object = this.parseObject( json.object, geometries, materials );
+
+ return object;
+
+ },
+
+ parseGeometries: function ( json ) {
+
+ var geometries = {};
+
+ if ( json !== undefined ) {
+
+ var geometryLoader = new THREE.JSONLoader();
+ var bufferGeometryLoader = new THREE.BufferGeometryLoader();
+
+ for ( var i = 0, l = json.length; i < l; i ++ ) {
+
+ var geometry;
+ var data = json[ i ];
+
+ switch ( data.type ) {
+
+ case 'PlaneGeometry':
+
+ geometry = new THREE.PlaneGeometry(
+ data.width,
+ data.height,
+ data.widthSegments,
+ data.heightSegments
+ );
+
+ break;
+
+ case 'CircleGeometry':
+
+ geometry = new THREE.CircleGeometry(
+ data.radius,
+ data.segments
+ );
+
+ break;
+
+ case 'CubeGeometry':
+
+ geometry = new THREE.CubeGeometry(
+ data.width,
+ data.height,
+ data.depth,
+ data.widthSegments,
+ data.heightSegments,
+ data.depthSegments
+ );
+
+ break;
+
+ case 'CylinderGeometry':
+
+ geometry = new THREE.CylinderGeometry(
+ data.radiusTop,
+ data.radiusBottom,
+ data.height,
+ data.radiusSegments,
+ data.heightSegments,
+ data.openEnded
+ );
+
+ break;
+
+ case 'SphereGeometry':
+
+ geometry = new THREE.SphereGeometry(
+ data.radius,
+ data.widthSegments,
+ data.heightSegments,
+ data.phiStart,
+ data.phiLength,
+ data.thetaStart,
+ data.thetaLength
+ );
+
+ break;
+
+ case 'IcosahedronGeometry':
+
+ geometry = new THREE.IcosahedronGeometry(
+ data.radius,
+ data.detail
+ );
+
+ break;
+
+ case 'TorusGeometry':
+
+ geometry = new THREE.TorusGeometry(
+ data.radius,
+ data.tube,
+ data.radialSegments,
+ data.tubularSegments,
+ data.arc
+ );
+
+ break;
+
+ case 'TorusKnotGeometry':
+
+ geometry = new THREE.TorusKnotGeometry(
+ data.radius,
+ data.tube,
+ data.radialSegments,
+ data.tubularSegments,
+ data.p,
+ data.q,
+ data.heightScale
+ );
+
+ break;
+
+ case 'BufferGeometry':
+
+ geometry = bufferGeometryLoader.parse( data.data );
+
+ break;
+
+ case 'Geometry':
+
+ geometry = geometryLoader.parse( data.data ).geometry;
+
+ break;
+
+ }
+
+ geometry.uuid = data.uuid;
+
+ if ( data.name !== undefined ) geometry.name = data.name;
+
+ geometries[ data.uuid ] = geometry;
+
+ }
+
+ }
+
+ return geometries;
+
+ },
+
+ parseMaterials: function ( json ) {
+
+ var materials = {};
+
+ if ( json !== undefined ) {
+
+ var loader = new THREE.MaterialLoader();
+
+ for ( var i = 0, l = json.length; i < l; i ++ ) {
+
+ var data = json[ i ];
+ var material = loader.parse( data );
+
+ material.uuid = data.uuid;
+
+ if ( data.name !== undefined ) material.name = data.name;
+
+ materials[ data.uuid ] = material;
+
+ }
+
+ }
+
+ return materials;
+
+ },
+
+ parseObject: function () {
+
+ var matrix = new THREE.Matrix4();
+
+ return function ( data, geometries, materials ) {
+
+ var object;
+
+ switch ( data.type ) {
+
+ case 'Scene':
+
+ object = new THREE.Scene();
+
+ break;
+
+ case 'PerspectiveCamera':
+
+ object = new THREE.PerspectiveCamera( data.fov, data.aspect, data.near, data.far );
+
+ break;
+
+ case 'OrthographicCamera':
+
+ object = new THREE.OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far );
+
+ break;
+
+ case 'AmbientLight':
+
+ object = new THREE.AmbientLight( data.color );
+
+ break;
+
+ case 'DirectionalLight':
+
+ object = new THREE.DirectionalLight( data.color, data.intensity );
+
+ break;
+
+ case 'PointLight':
+
+ object = new THREE.PointLight( data.color, data.intensity, data.distance );
+
+ break;
+
+ case 'SpotLight':
+
+ object = new THREE.SpotLight( data.color, data.intensity, data.distance, data.angle, data.exponent );
+
+ break;
+
+ case 'HemisphereLight':
+
+ object = new THREE.HemisphereLight( data.color, data.groundColor, data.intensity );
+
+ break;
+
+ case 'Mesh':
+
+ var geometry = geometries[ data.geometry ];
+ var material = materials[ data.material ];
+
+ if ( geometry === undefined ) {
+
+ console.error( 'THREE.ObjectLoader: Undefined geometry ' + data.geometry );
+
+ }
+
+ if ( material === undefined ) {
+
+ console.error( 'THREE.ObjectLoader: Undefined material ' + data.material );
+
+ }
+
+ object = new THREE.Mesh( geometry, material );
+
+ break;
+
+ default:
+
+ object = new THREE.Object3D();
+
+ }
+
+ object.uuid = data.uuid;
+
+ if ( data.name !== undefined ) object.name = data.name;
+ if ( data.matrix !== undefined ) {
+
+ matrix.fromArray( data.matrix );
+ matrix.decompose( object.position, object.quaternion, object.scale );
+
+ } else {
+
+ if ( data.position !== undefined ) object.position.fromArray( data.position );
+ if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation );
+ if ( data.scale !== undefined ) object.scale.fromArray( data.scale );
+
+ }
+
+ if ( data.visible !== undefined ) object.visible = data.visible;
+ if ( data.userData !== undefined ) object.userData = data.userData;
+
+ if ( data.children !== undefined ) {
+
+ for ( var child in data.children ) {
+
+ object.add( this.parseObject( data.children[ child ], geometries, materials ) );
+
+ }
+
+ }
+
+ return object;
+
+ }
+
+ }()
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SceneLoader = function () {
+
+ this.onLoadStart = function () {};
+ this.onLoadProgress = function() {};
+ this.onLoadComplete = function () {};
+
+ this.callbackSync = function () {};
+ this.callbackProgress = function () {};
+
+ this.geometryHandlers = {};
+ this.hierarchyHandlers = {};
+
+ this.addGeometryHandler( "ascii", THREE.JSONLoader );
+
+};
+
+THREE.SceneLoader.prototype = {
+
+ constructor: THREE.SceneLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader( scope.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ scope.parse( JSON.parse( text ), onLoad, url );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ addGeometryHandler: function ( typeID, loaderClass ) {
+
+ this.geometryHandlers[ typeID ] = { "loaderClass": loaderClass };
+
+ },
+
+ addHierarchyHandler: function ( typeID, loaderClass ) {
+
+ this.hierarchyHandlers[ typeID ] = { "loaderClass": loaderClass };
+
+ },
+
+ parse: function ( json, callbackFinished, url ) {
+
+ var scope = this;
+
+ var urlBase = THREE.Loader.prototype.extractUrlBase( url );
+
+ var geometry, material, camera, fog,
+ texture, images, color,
+ light, hex, intensity,
+ counter_models, counter_textures,
+ total_models, total_textures,
+ result;
+
+ var target_array = [];
+
+ var data = json;
+
+ // async geometry loaders
+
+ for ( var typeID in this.geometryHandlers ) {
+
+ var loaderClass = this.geometryHandlers[ typeID ][ "loaderClass" ];
+ this.geometryHandlers[ typeID ][ "loaderObject" ] = new loaderClass();
+
+ }
+
+ // async hierachy loaders
+
+ for ( var typeID in this.hierarchyHandlers ) {
+
+ var loaderClass = this.hierarchyHandlers[ typeID ][ "loaderClass" ];
+ this.hierarchyHandlers[ typeID ][ "loaderObject" ] = new loaderClass();
+
+ }
+
+ counter_models = 0;
+ counter_textures = 0;
+
+ result = {
+
+ scene: new THREE.Scene(),
+ geometries: {},
+ face_materials: {},
+ materials: {},
+ textures: {},
+ objects: {},
+ cameras: {},
+ lights: {},
+ fogs: {},
+ empties: {},
+ groups: {}
+
+ };
+
+ if ( data.transform ) {
+
+ var position = data.transform.position,
+ rotation = data.transform.rotation,
+ scale = data.transform.scale;
+
+ if ( position ) {
+
+ result.scene.position.fromArray( position );
+
+ }
+
+ if ( rotation ) {
+
+ result.scene.rotation.fromArray( rotation );
+
+ }
+
+ if ( scale ) {
+
+ result.scene.scale.fromArray( scale );
+
+ }
+
+ if ( position || rotation || scale ) {
+
+ result.scene.updateMatrix();
+ result.scene.updateMatrixWorld();
+
+ }
+
+ }
+
+ function get_url( source_url, url_type ) {
+
+ if ( url_type == "relativeToHTML" ) {
+
+ return source_url;
+
+ } else {
+
+ return urlBase + "/" + source_url;
+
+ }
+
+ };
+
+ // toplevel loader function, delegates to handle_children
+
+ function handle_objects() {
+
+ handle_children( result.scene, data.objects );
+
+ }
+
+ // handle all the children from the loaded json and attach them to given parent
+
+ function handle_children( parent, children ) {
+
+ var mat, dst, pos, rot, scl, quat;
+
+ for ( var objID in children ) {
+
+ // check by id if child has already been handled,
+ // if not, create new object
+
+ var object = result.objects[ objID ];
+ var objJSON = children[ objID ];
+
+ if ( object === undefined ) {
+
+ // meshes
+
+ if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlers ) ) {
+
+ if ( objJSON.loading === undefined ) {
+
+ var reservedTypes = {
+ "type": 1, "url": 1, "material": 1,
+ "position": 1, "rotation": 1, "scale" : 1,
+ "visible": 1, "children": 1, "userData": 1,
+ "skin": 1, "morph": 1, "mirroredLoop": 1, "duration": 1
+ };
+
+ var loaderParameters = {};
+
+ for ( var parType in objJSON ) {
+
+ if ( ! ( parType in reservedTypes ) ) {
+
+ loaderParameters[ parType ] = objJSON[ parType ];
+
+ }
+
+ }
+
+ material = result.materials[ objJSON.material ];
+
+ objJSON.loading = true;
+
+ var loader = scope.hierarchyHandlers[ objJSON.type ][ "loaderObject" ];
+
+ // ColladaLoader
+
+ if ( loader.options ) {
+
+ loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) );
+
+ // UTF8Loader
+ // OBJLoader
+
+ } else {
+
+ loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters );
+
+ }
+
+ }
+
+ } else if ( objJSON.geometry !== undefined ) {
+
+ geometry = result.geometries[ objJSON.geometry ];
+
+ // geometry already loaded
+
+ if ( geometry ) {
+
+ var needsTangents = false;
+
+ material = result.materials[ objJSON.material ];
+ needsTangents = material instanceof THREE.ShaderMaterial;
+
+ pos = objJSON.position;
+ rot = objJSON.rotation;
+ scl = objJSON.scale;
+ mat = objJSON.matrix;
+ quat = objJSON.quaternion;
+
+ // use materials from the model file
+ // if there is no material specified in the object
+
+ if ( ! objJSON.material ) {
+
+ material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] );
+
+ }
+
+ // use materials from the model file
+ // if there is just empty face material
+ // (must create new material as each model has its own face material)
+
+ if ( ( material instanceof THREE.MeshFaceMaterial ) && material.materials.length === 0 ) {
+
+ material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] );
+
+ }
+
+ if ( material instanceof THREE.MeshFaceMaterial ) {
+
+ for ( var i = 0; i < material.materials.length; i ++ ) {
+
+ needsTangents = needsTangents || ( material.materials[ i ] instanceof THREE.ShaderMaterial );
+
+ }
+
+ }
+
+ if ( needsTangents ) {
+
+ geometry.computeTangents();
+
+ }
+
+ if ( objJSON.skin ) {
+
+ object = new THREE.SkinnedMesh( geometry, material );
+
+ } else if ( objJSON.morph ) {
+
+ object = new THREE.MorphAnimMesh( geometry, material );
+
+ if ( objJSON.duration !== undefined ) {
+
+ object.duration = objJSON.duration;
+
+ }
+
+ if ( objJSON.time !== undefined ) {
+
+ object.time = objJSON.time;
+
+ }
+
+ if ( objJSON.mirroredLoop !== undefined ) {
+
+ object.mirroredLoop = objJSON.mirroredLoop;
+
+ }
+
+ if ( material.morphNormals ) {
+
+ geometry.computeMorphNormals();
+
+ }
+
+ } else {
+
+ object = new THREE.Mesh( geometry, material );
+
+ }
+
+ object.name = objID;
+
+ if ( mat ) {
+
+ object.matrixAutoUpdate = false;
+ object.matrix.set(
+ mat[0], mat[1], mat[2], mat[3],
+ mat[4], mat[5], mat[6], mat[7],
+ mat[8], mat[9], mat[10], mat[11],
+ mat[12], mat[13], mat[14], mat[15]
+ );
+
+ } else {
+
+ object.position.fromArray( pos );
+
+ if ( quat ) {
+
+ object.quaternion.fromArray( quat );
+
+ } else {
+
+ object.rotation.fromArray( rot );
+
+ }
+
+ object.scale.fromArray( scl );
+
+ }
+
+ object.visible = objJSON.visible;
+ object.castShadow = objJSON.castShadow;
+ object.receiveShadow = objJSON.receiveShadow;
+
+ parent.add( object );
+
+ result.objects[ objID ] = object;
+
+ }
+
+ // lights
+
+ } else if ( objJSON.type === "DirectionalLight" || objJSON.type === "PointLight" || objJSON.type === "AmbientLight" ) {
+
+ hex = ( objJSON.color !== undefined ) ? objJSON.color : 0xffffff;
+ intensity = ( objJSON.intensity !== undefined ) ? objJSON.intensity : 1;
+
+ if ( objJSON.type === "DirectionalLight" ) {
+
+ pos = objJSON.direction;
+
+ light = new THREE.DirectionalLight( hex, intensity );
+ light.position.fromArray( pos );
+
+ if ( objJSON.target ) {
+
+ target_array.push( { "object": light, "targetName" : objJSON.target } );
+
+ // kill existing default target
+ // otherwise it gets added to scene when parent gets added
+
+ light.target = null;
+
+ }
+
+ } else if ( objJSON.type === "PointLight" ) {
+
+ pos = objJSON.position;
+ dst = objJSON.distance;
+
+ light = new THREE.PointLight( hex, intensity, dst );
+ light.position.fromArray( pos );
+
+ } else if ( objJSON.type === "AmbientLight" ) {
+
+ light = new THREE.AmbientLight( hex );
+
+ }
+
+ parent.add( light );
+
+ light.name = objID;
+ result.lights[ objID ] = light;
+ result.objects[ objID ] = light;
+
+ // cameras
+
+ } else if ( objJSON.type === "PerspectiveCamera" || objJSON.type === "OrthographicCamera" ) {
+
+ pos = objJSON.position;
+ rot = objJSON.rotation;
+ quat = objJSON.quaternion;
+
+ if ( objJSON.type === "PerspectiveCamera" ) {
+
+ camera = new THREE.PerspectiveCamera( objJSON.fov, objJSON.aspect, objJSON.near, objJSON.far );
+
+ } else if ( objJSON.type === "OrthographicCamera" ) {
+
+ camera = new THREE.OrthographicCamera( objJSON.left, objJSON.right, objJSON.top, objJSON.bottom, objJSON.near, objJSON.far );
+
+ }
+
+ camera.name = objID;
+ camera.position.fromArray( pos );
+
+ if ( quat !== undefined ) {
+
+ camera.quaternion.fromArray( quat );
+
+ } else if ( rot !== undefined ) {
+
+ camera.rotation.fromArray( rot );
+
+ }
+
+ parent.add( camera );
+
+ result.cameras[ objID ] = camera;
+ result.objects[ objID ] = camera;
+
+ // pure Object3D
+
+ } else {
+
+ pos = objJSON.position;
+ rot = objJSON.rotation;
+ scl = objJSON.scale;
+ quat = objJSON.quaternion;
+
+ object = new THREE.Object3D();
+ object.name = objID;
+ object.position.fromArray( pos );
+
+ if ( quat ) {
+
+ object.quaternion.fromArray( quat );
+
+ } else {
+
+ object.rotation.fromArray( rot );
+
+ }
+
+ object.scale.fromArray( scl );
+ object.visible = ( objJSON.visible !== undefined ) ? objJSON.visible : false;
+
+ parent.add( object );
+
+ result.objects[ objID ] = object;
+ result.empties[ objID ] = object;
+
+ }
+
+ if ( object ) {
+
+ if ( objJSON.userData !== undefined ) {
+
+ for ( var key in objJSON.userData ) {
+
+ var value = objJSON.userData[ key ];
+ object.userData[ key ] = value;
+
+ }
+
+ }
+
+ if ( objJSON.groups !== undefined ) {
+
+ for ( var i = 0; i < objJSON.groups.length; i ++ ) {
+
+ var groupID = objJSON.groups[ i ];
+
+ if ( result.groups[ groupID ] === undefined ) {
+
+ result.groups[ groupID ] = [];
+
+ }
+
+ result.groups[ groupID ].push( objID );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( object !== undefined && objJSON.children !== undefined ) {
+
+ handle_children( object, objJSON.children );
+
+ }
+
+ }
+
+ };
+
+ function handle_mesh( geo, mat, id ) {
+
+ result.geometries[ id ] = geo;
+ result.face_materials[ id ] = mat;
+ handle_objects();
+
+ };
+
+ function handle_hierarchy( node, id, parent, material, obj ) {
+
+ var p = obj.position;
+ var r = obj.rotation;
+ var q = obj.quaternion;
+ var s = obj.scale;
+
+ node.position.fromArray( p );
+
+ if ( q ) {
+
+ node.quaternion.fromArray( q );
+
+ } else {
+
+ node.rotation.fromArray( r );
+
+ }
+
+ node.scale.fromArray( s );
+
+ // override children materials
+ // if object material was specified in JSON explicitly
+
+ if ( material ) {
+
+ node.traverse( function ( child ) {
+
+ child.material = material;
+
+ } );
+
+ }
+
+ // override children visibility
+ // with root node visibility as specified in JSON
+
+ var visible = ( obj.visible !== undefined ) ? obj.visible : true;
+
+ node.traverse( function ( child ) {
+
+ child.visible = visible;
+
+ } );
+
+ parent.add( node );
+
+ node.name = id;
+
+ result.objects[ id ] = node;
+ handle_objects();
+
+ };
+
+ function create_callback_geometry( id ) {
+
+ return function ( geo, mat ) {
+
+ geo.name = id;
+
+ handle_mesh( geo, mat, id );
+
+ counter_models -= 1;
+
+ scope.onLoadComplete();
+
+ async_callback_gate();
+
+ }
+
+ };
+
+ function create_callback_hierachy( id, parent, material, obj ) {
+
+ return function ( event ) {
+
+ var result;
+
+ // loaders which use EventDispatcher
+
+ if ( event.content ) {
+
+ result = event.content;
+
+ // ColladaLoader
+
+ } else if ( event.dae ) {
+
+ result = event.scene;
+
+
+ // UTF8Loader
+
+ } else {
+
+ result = event;
+
+ }
+
+ handle_hierarchy( result, id, parent, material, obj );
+
+ counter_models -= 1;
+
+ scope.onLoadComplete();
+
+ async_callback_gate();
+
+ }
+
+ };
+
+ function create_callback_embed( id ) {
+
+ return function ( geo, mat ) {
+
+ geo.name = id;
+
+ result.geometries[ id ] = geo;
+ result.face_materials[ id ] = mat;
+
+ }
+
+ };
+
+ function async_callback_gate() {
+
+ var progress = {
+
+ totalModels : total_models,
+ totalTextures : total_textures,
+ loadedModels : total_models - counter_models,
+ loadedTextures : total_textures - counter_textures
+
+ };
+
+ scope.callbackProgress( progress, result );
+
+ scope.onLoadProgress();
+
+ if ( counter_models === 0 && counter_textures === 0 ) {
+
+ finalize();
+ callbackFinished( result );
+
+ }
+
+ };
+
+ function finalize() {
+
+ // take care of targets which could be asynchronously loaded objects
+
+ for ( var i = 0; i < target_array.length; i ++ ) {
+
+ var ta = target_array[ i ];
+
+ var target = result.objects[ ta.targetName ];
+
+ if ( target ) {
+
+ ta.object.target = target;
+
+ } else {
+
+ // if there was error and target of specified name doesn't exist in the scene file
+ // create instead dummy target
+ // (target must be added to scene explicitly as parent is already added)
+
+ ta.object.target = new THREE.Object3D();
+ result.scene.add( ta.object.target );
+
+ }
+
+ ta.object.target.userData.targetInverse = ta.object;
+
+ }
+
+ };
+
+ var callbackTexture = function ( count ) {
+
+ counter_textures -= count;
+ async_callback_gate();
+
+ scope.onLoadComplete();
+
+ };
+
+ // must use this instead of just directly calling callbackTexture
+ // because of closure in the calling context loop
+
+ var generateTextureCallback = function ( count ) {
+
+ return function () {
+
+ callbackTexture( count );
+
+ };
+
+ };
+
+ function traverse_json_hierarchy( objJSON, callback ) {
+
+ callback( objJSON );
+
+ if ( objJSON.children !== undefined ) {
+
+ for ( var objChildID in objJSON.children ) {
+
+ traverse_json_hierarchy( objJSON.children[ objChildID ], callback );
+
+ }
+
+ }
+
+ };
+
+ // first go synchronous elements
+
+ // fogs
+
+ var fogID, fogJSON;
+
+ for ( fogID in data.fogs ) {
+
+ fogJSON = data.fogs[ fogID ];
+
+ if ( fogJSON.type === "linear" ) {
+
+ fog = new THREE.Fog( 0x000000, fogJSON.near, fogJSON.far );
+
+ } else if ( fogJSON.type === "exp2" ) {
+
+ fog = new THREE.FogExp2( 0x000000, fogJSON.density );
+
+ }
+
+ color = fogJSON.color;
+ fog.color.setRGB( color[0], color[1], color[2] );
+
+ result.fogs[ fogID ] = fog;
+
+ }
+
+ // now come potentially asynchronous elements
+
+ // geometries
+
+ // count how many geometries will be loaded asynchronously
+
+ var geoID, geoJSON;
+
+ for ( geoID in data.geometries ) {
+
+ geoJSON = data.geometries[ geoID ];
+
+ if ( geoJSON.type in this.geometryHandlers ) {
+
+ counter_models += 1;
+
+ scope.onLoadStart();
+
+ }
+
+ }
+
+ // count how many hierarchies will be loaded asynchronously
+
+ for ( var objID in data.objects ) {
+
+ traverse_json_hierarchy( data.objects[ objID ], function ( objJSON ) {
+
+ if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlers ) ) {
+
+ counter_models += 1;
+
+ scope.onLoadStart();
+
+ }
+
+ });
+
+ }
+
+ total_models = counter_models;
+
+ for ( geoID in data.geometries ) {
+
+ geoJSON = data.geometries[ geoID ];
+
+ if ( geoJSON.type === "cube" ) {
+
+ geometry = new THREE.CubeGeometry( geoJSON.width, geoJSON.height, geoJSON.depth, geoJSON.widthSegments, geoJSON.heightSegments, geoJSON.depthSegments );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "plane" ) {
+
+ geometry = new THREE.PlaneGeometry( geoJSON.width, geoJSON.height, geoJSON.widthSegments, geoJSON.heightSegments );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "sphere" ) {
+
+ geometry = new THREE.SphereGeometry( geoJSON.radius, geoJSON.widthSegments, geoJSON.heightSegments );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "cylinder" ) {
+
+ geometry = new THREE.CylinderGeometry( geoJSON.topRad, geoJSON.botRad, geoJSON.height, geoJSON.radSegs, geoJSON.heightSegs );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "torus" ) {
+
+ geometry = new THREE.TorusGeometry( geoJSON.radius, geoJSON.tube, geoJSON.segmentsR, geoJSON.segmentsT );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "icosahedron" ) {
+
+ geometry = new THREE.IcosahedronGeometry( geoJSON.radius, geoJSON.subdivisions );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type in this.geometryHandlers ) {
+
+ var loaderParameters = {};
+
+ for ( var parType in geoJSON ) {
+
+ if ( parType !== "type" && parType !== "url" ) {
+
+ loaderParameters[ parType ] = geoJSON[ parType ];
+
+ }
+
+ }
+
+ var loader = this.geometryHandlers[ geoJSON.type ][ "loaderObject" ];
+ loader.load( get_url( geoJSON.url, data.urlBaseType ), create_callback_geometry( geoID ), loaderParameters );
+
+ } else if ( geoJSON.type === "embedded" ) {
+
+ var modelJson = data.embeds[ geoJSON.id ],
+ texture_path = "";
+
+ // pass metadata along to jsonLoader so it knows the format version
+
+ modelJson.metadata = data.metadata;
+
+ if ( modelJson ) {
+
+ var jsonLoader = this.geometryHandlers[ "ascii" ][ "loaderObject" ];
+ var model = jsonLoader.parse( modelJson, texture_path );
+ create_callback_embed( geoID )( model.geometry, model.materials );
+
+ }
+
+ }
+
+ }
+
+ // textures
+
+ // count how many textures will be loaded asynchronously
+
+ var textureID, textureJSON;
+
+ for ( textureID in data.textures ) {
+
+ textureJSON = data.textures[ textureID ];
+
+ if ( textureJSON.url instanceof Array ) {
+
+ counter_textures += textureJSON.url.length;
+
+ for( var n = 0; n < textureJSON.url.length; n ++ ) {
+
+ scope.onLoadStart();
+
+ }
+
+ } else {
+
+ counter_textures += 1;
+
+ scope.onLoadStart();
+
+ }
+
+ }
+
+ total_textures = counter_textures;
+
+ for ( textureID in data.textures ) {
+
+ textureJSON = data.textures[ textureID ];
+
+ if ( textureJSON.mapping !== undefined && THREE[ textureJSON.mapping ] !== undefined ) {
+
+ textureJSON.mapping = new THREE[ textureJSON.mapping ]();
+
+ }
+
+ if ( textureJSON.url instanceof Array ) {
+
+ var count = textureJSON.url.length;
+ var url_array = [];
+
+ for( var i = 0; i < count; i ++ ) {
+
+ url_array[ i ] = get_url( textureJSON.url[ i ], data.urlBaseType );
+
+ }
+
+ var isCompressed = /\.dds$/i.test( url_array[ 0 ] );
+
+ if ( isCompressed ) {
+
+ texture = THREE.ImageUtils.loadCompressedTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) );
+
+ } else {
+
+ texture = THREE.ImageUtils.loadTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) );
+
+ }
+
+ } else {
+
+ var isCompressed = /\.dds$/i.test( textureJSON.url );
+ var fullUrl = get_url( textureJSON.url, data.urlBaseType );
+ var textureCallback = generateTextureCallback( 1 );
+
+ if ( isCompressed ) {
+
+ texture = THREE.ImageUtils.loadCompressedTexture( fullUrl, textureJSON.mapping, textureCallback );
+
+ } else {
+
+ texture = THREE.ImageUtils.loadTexture( fullUrl, textureJSON.mapping, textureCallback );
+
+ }
+
+ if ( THREE[ textureJSON.minFilter ] !== undefined )
+ texture.minFilter = THREE[ textureJSON.minFilter ];
+
+ if ( THREE[ textureJSON.magFilter ] !== undefined )
+ texture.magFilter = THREE[ textureJSON.magFilter ];
+
+ if ( textureJSON.anisotropy ) texture.anisotropy = textureJSON.anisotropy;
+
+ if ( textureJSON.repeat ) {
+
+ texture.repeat.set( textureJSON.repeat[ 0 ], textureJSON.repeat[ 1 ] );
+
+ if ( textureJSON.repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping;
+ if ( textureJSON.repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping;
+
+ }
+
+ if ( textureJSON.offset ) {
+
+ texture.offset.set( textureJSON.offset[ 0 ], textureJSON.offset[ 1 ] );
+
+ }
+
+ // handle wrap after repeat so that default repeat can be overriden
+
+ if ( textureJSON.wrap ) {
+
+ var wrapMap = {
+ "repeat": THREE.RepeatWrapping,
+ "mirror": THREE.MirroredRepeatWrapping
+ }
+
+ if ( wrapMap[ textureJSON.wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ textureJSON.wrap[ 0 ] ];
+ if ( wrapMap[ textureJSON.wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ textureJSON.wrap[ 1 ] ];
+
+ }
+
+ }
+
+ result.textures[ textureID ] = texture;
+
+ }
+
+ // materials
+
+ var matID, matJSON;
+ var parID;
+
+ for ( matID in data.materials ) {
+
+ matJSON = data.materials[ matID ];
+
+ for ( parID in matJSON.parameters ) {
+
+ if ( parID === "envMap" || parID === "map" || parID === "lightMap" || parID === "bumpMap" ) {
+
+ matJSON.parameters[ parID ] = result.textures[ matJSON.parameters[ parID ] ];
+
+ } else if ( parID === "shading" ) {
+
+ matJSON.parameters[ parID ] = ( matJSON.parameters[ parID ] === "flat" ) ? THREE.FlatShading : THREE.SmoothShading;
+
+ } else if ( parID === "side" ) {
+
+ if ( matJSON.parameters[ parID ] == "double" ) {
+
+ matJSON.parameters[ parID ] = THREE.DoubleSide;
+
+ } else if ( matJSON.parameters[ parID ] == "back" ) {
+
+ matJSON.parameters[ parID ] = THREE.BackSide;
+
+ } else {
+
+ matJSON.parameters[ parID ] = THREE.FrontSide;
+
+ }
+
+ } else if ( parID === "blending" ) {
+
+ matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.NormalBlending;
+
+ } else if ( parID === "combine" ) {
+
+ matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.MultiplyOperation;
+
+ } else if ( parID === "vertexColors" ) {
+
+ if ( matJSON.parameters[ parID ] == "face" ) {
+
+ matJSON.parameters[ parID ] = THREE.FaceColors;
+
+ // default to vertex colors if "vertexColors" is anything else face colors or 0 / null / false
+
+ } else if ( matJSON.parameters[ parID ] ) {
+
+ matJSON.parameters[ parID ] = THREE.VertexColors;
+
+ }
+
+ } else if ( parID === "wrapRGB" ) {
+
+ var v3 = matJSON.parameters[ parID ];
+ matJSON.parameters[ parID ] = new THREE.Vector3( v3[ 0 ], v3[ 1 ], v3[ 2 ] );
+
+ }
+
+ }
+
+ if ( matJSON.parameters.opacity !== undefined && matJSON.parameters.opacity < 1.0 ) {
+
+ matJSON.parameters.transparent = true;
+
+ }
+
+ if ( matJSON.parameters.normalMap ) {
+
+ var shader = THREE.ShaderLib[ "normalmap" ];
+ var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+ var diffuse = matJSON.parameters.color;
+ var specular = matJSON.parameters.specular;
+ var ambient = matJSON.parameters.ambient;
+ var shininess = matJSON.parameters.shininess;
+
+ uniforms[ "tNormal" ].value = result.textures[ matJSON.parameters.normalMap ];
+
+ if ( matJSON.parameters.normalScale ) {
+
+ uniforms[ "uNormalScale" ].value.set( matJSON.parameters.normalScale[ 0 ], matJSON.parameters.normalScale[ 1 ] );
+
+ }
+
+ if ( matJSON.parameters.map ) {
+
+ uniforms[ "tDiffuse" ].value = matJSON.parameters.map;
+ uniforms[ "enableDiffuse" ].value = true;
+
+ }
+
+ if ( matJSON.parameters.envMap ) {
+
+ uniforms[ "tCube" ].value = matJSON.parameters.envMap;
+ uniforms[ "enableReflection" ].value = true;
+ uniforms[ "uReflectivity" ].value = matJSON.parameters.reflectivity;
+
+ }
+
+ if ( matJSON.parameters.lightMap ) {
+
+ uniforms[ "tAO" ].value = matJSON.parameters.lightMap;
+ uniforms[ "enableAO" ].value = true;
+
+ }
+
+ if ( matJSON.parameters.specularMap ) {
+
+ uniforms[ "tSpecular" ].value = result.textures[ matJSON.parameters.specularMap ];
+ uniforms[ "enableSpecular" ].value = true;
+
+ }
+
+ if ( matJSON.parameters.displacementMap ) {
+
+ uniforms[ "tDisplacement" ].value = result.textures[ matJSON.parameters.displacementMap ];
+ uniforms[ "enableDisplacement" ].value = true;
+
+ uniforms[ "uDisplacementBias" ].value = matJSON.parameters.displacementBias;
+ uniforms[ "uDisplacementScale" ].value = matJSON.parameters.displacementScale;
+
+ }
+
+ uniforms[ "uDiffuseColor" ].value.setHex( diffuse );
+ uniforms[ "uSpecularColor" ].value.setHex( specular );
+ uniforms[ "uAmbientColor" ].value.setHex( ambient );
+
+ uniforms[ "uShininess" ].value = shininess;
+
+ if ( matJSON.parameters.opacity ) {
+
+ uniforms[ "uOpacity" ].value = matJSON.parameters.opacity;
+
+ }
+
+ var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true };
+
+ material = new THREE.ShaderMaterial( parameters );
+
+ } else {
+
+ material = new THREE[ matJSON.type ]( matJSON.parameters );
+
+ }
+
+ material.name = matID;
+
+ result.materials[ matID ] = material;
+
+ }
+
+ // second pass through all materials to initialize MeshFaceMaterials
+ // that could be referring to other materials out of order
+
+ for ( matID in data.materials ) {
+
+ matJSON = data.materials[ matID ];
+
+ if ( matJSON.parameters.materials ) {
+
+ var materialArray = [];
+
+ for ( var i = 0; i < matJSON.parameters.materials.length; i ++ ) {
+
+ var label = matJSON.parameters.materials[ i ];
+ materialArray.push( result.materials[ label ] );
+
+ }
+
+ result.materials[ matID ].materials = materialArray;
+
+ }
+
+ }
+
+ // objects ( synchronous init of procedural primitives )
+
+ handle_objects();
+
+ // defaults
+
+ if ( result.cameras && data.defaults.camera ) {
+
+ result.currentCamera = result.cameras[ data.defaults.camera ];
+
+ }
+
+ if ( result.fogs && data.defaults.fog ) {
+
+ result.scene.fog = result.fogs[ data.defaults.fog ];
+
+ }
+
+ // synchronous callback
+
+ scope.callbackSync( result );
+
+ // just in case there are no async elements
+
+ async_callback_gate();
+
+ }
+
+}
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.TextureLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.TextureLoader.prototype = {
+
+ constructor: THREE.TextureLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.ImageLoader( scope.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( image ) {
+
+ var texture = new THREE.Texture( image );
+ texture.needsUpdate = true;
+
+ if ( onLoad !== undefined ) {
+
+ onLoad( texture );
+
+ }
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Material = function () {
+
+ this.id = THREE.MaterialIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ this.side = THREE.FrontSide;
+
+ this.opacity = 1;
+ this.transparent = false;
+
+ this.blending = THREE.NormalBlending;
+
+ this.blendSrc = THREE.SrcAlphaFactor;
+ this.blendDst = THREE.OneMinusSrcAlphaFactor;
+ this.blendEquation = THREE.AddEquation;
+
+ this.depthTest = true;
+ this.depthWrite = true;
+
+ this.polygonOffset = false;
+ this.polygonOffsetFactor = 0;
+ this.polygonOffsetUnits = 0;
+
+ this.alphaTest = 0;
+
+ this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer
+
+ this.visible = true;
+
+ this.needsUpdate = true;
+
+};
+
+THREE.Material.prototype = {
+
+ constructor: THREE.Material,
+
+ setValues: function ( values ) {
+
+ if ( values === undefined ) return;
+
+ for ( var key in values ) {
+
+ var newValue = values[ key ];
+
+ if ( newValue === undefined ) {
+
+ console.warn( 'THREE.Material: \'' + key + '\' parameter is undefined.' );
+ continue;
+
+ }
+
+ if ( key in this ) {
+
+ var currentValue = this[ key ];
+
+ if ( currentValue instanceof THREE.Color ) {
+
+ currentValue.set( newValue );
+
+ } else if ( currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3 ) {
+
+ currentValue.copy( newValue );
+
+ } else if ( key == 'overdraw') {
+
+ // ensure overdraw is backwards-compatable with legacy boolean type
+ this[ key ] = Number(newValue);
+
+ } else {
+
+ this[ key ] = newValue;
+
+ }
+
+ }
+
+ }
+
+ },
+
+ clone: function ( material ) {
+
+ if ( material === undefined ) material = new THREE.Material();
+
+ material.name = this.name;
+
+ material.side = this.side;
+
+ material.opacity = this.opacity;
+ material.transparent = this.transparent;
+
+ material.blending = this.blending;
+
+ material.blendSrc = this.blendSrc;
+ material.blendDst = this.blendDst;
+ material.blendEquation = this.blendEquation;
+
+ material.depthTest = this.depthTest;
+ material.depthWrite = this.depthWrite;
+
+ material.polygonOffset = this.polygonOffset;
+ material.polygonOffsetFactor = this.polygonOffsetFactor;
+ material.polygonOffsetUnits = this.polygonOffsetUnits;
+
+ material.alphaTest = this.alphaTest;
+
+ material.overdraw = this.overdraw;
+
+ material.visible = this.visible;
+
+ return material;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Material.prototype );
+
+THREE.MaterialIdCount = 0;
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * linewidth: ,
+ * linecap: "round",
+ * linejoin: "round",
+ *
+ * vertexColors:
+ *
+ * fog:
+ * }
+ */
+
+THREE.LineBasicMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff );
+
+ this.linewidth = 1;
+ this.linecap = 'round';
+ this.linejoin = 'round';
+
+ this.vertexColors = false;
+
+ this.fog = true;
+
+ this.setValues( parameters );
+
+};
+
+THREE.LineBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.LineBasicMaterial.prototype.clone = function () {
+
+ var material = new THREE.LineBasicMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+
+ material.linewidth = this.linewidth;
+ material.linecap = this.linecap;
+ material.linejoin = this.linejoin;
+
+ material.vertexColors = this.vertexColors;
+
+ material.fog = this.fog;
+
+ return material;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * linewidth: ,
+ *
+ * scale: ,
+ * dashSize: ,
+ * gapSize: ,
+ *
+ * vertexColors:
+ *
+ * fog:
+ * }
+ */
+
+THREE.LineDashedMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff );
+
+ this.linewidth = 1;
+
+ this.scale = 1;
+ this.dashSize = 3;
+ this.gapSize = 1;
+
+ this.vertexColors = false;
+
+ this.fog = true;
+
+ this.setValues( parameters );
+
+};
+
+THREE.LineDashedMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.LineDashedMaterial.prototype.clone = function () {
+
+ var material = new THREE.LineDashedMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+
+ material.linewidth = this.linewidth;
+
+ material.scale = this.scale;
+ material.dashSize = this.dashSize;
+ material.gapSize = this.gapSize;
+
+ material.vertexColors = this.vertexColors;
+
+ material.fog = this.fog;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ * map: new THREE.Texture( ),
+ *
+ * lightMap: new THREE.Texture( ),
+ *
+ * specularMap: new THREE.Texture( ),
+ *
+ * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.Multiply,
+ * reflectivity: ,
+ * refractionRatio: ,
+ *
+ * shading: THREE.SmoothShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth: ,
+ *
+ * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ * skinning: ,
+ * morphTargets: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.MeshBasicMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff ); // emissive
+
+ this.map = null;
+
+ this.lightMap = null;
+
+ this.specularMap = null;
+
+ this.envMap = null;
+ this.combine = THREE.MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.fog = true;
+
+ this.shading = THREE.SmoothShading;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.vertexColors = THREE.NoColors;
+
+ this.skinning = false;
+ this.morphTargets = false;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshBasicMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshBasicMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+
+ material.map = this.map;
+
+ material.lightMap = this.lightMap;
+
+ material.specularMap = this.specularMap;
+
+ material.envMap = this.envMap;
+ material.combine = this.combine;
+ material.reflectivity = this.reflectivity;
+ material.refractionRatio = this.refractionRatio;
+
+ material.fog = this.fog;
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+ material.wireframeLinecap = this.wireframeLinecap;
+ material.wireframeLinejoin = this.wireframeLinejoin;
+
+ material.vertexColors = this.vertexColors;
+
+ material.skinning = this.skinning;
+ material.morphTargets = this.morphTargets;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * ambient: ,
+ * emissive: ,
+ * opacity: ,
+ *
+ * map: new THREE.Texture( ),
+ *
+ * lightMap: new THREE.Texture( ),
+ *
+ * specularMap: new THREE.Texture( ),
+ *
+ * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.Multiply,
+ * reflectivity: ,
+ * refractionRatio: ,
+ *
+ * shading: THREE.SmoothShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth: ,
+ *
+ * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ * skinning: ,
+ * morphTargets: ,
+ * morphNormals: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.MeshLambertMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff ); // diffuse
+ this.ambient = new THREE.Color( 0xffffff );
+ this.emissive = new THREE.Color( 0x000000 );
+
+ this.wrapAround = false;
+ this.wrapRGB = new THREE.Vector3( 1, 1, 1 );
+
+ this.map = null;
+
+ this.lightMap = null;
+
+ this.specularMap = null;
+
+ this.envMap = null;
+ this.combine = THREE.MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.fog = true;
+
+ this.shading = THREE.SmoothShading;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.vertexColors = THREE.NoColors;
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshLambertMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshLambertMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshLambertMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+ material.ambient.copy( this.ambient );
+ material.emissive.copy( this.emissive );
+
+ material.wrapAround = this.wrapAround;
+ material.wrapRGB.copy( this.wrapRGB );
+
+ material.map = this.map;
+
+ material.lightMap = this.lightMap;
+
+ material.specularMap = this.specularMap;
+
+ material.envMap = this.envMap;
+ material.combine = this.combine;
+ material.reflectivity = this.reflectivity;
+ material.refractionRatio = this.refractionRatio;
+
+ material.fog = this.fog;
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+ material.wireframeLinecap = this.wireframeLinecap;
+ material.wireframeLinejoin = this.wireframeLinejoin;
+
+ material.vertexColors = this.vertexColors;
+
+ material.skinning = this.skinning;
+ material.morphTargets = this.morphTargets;
+ material.morphNormals = this.morphNormals;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * ambient: ,
+ * emissive: ,
+ * specular: ,
+ * shininess: ,
+ * opacity: ,
+ *
+ * map: new THREE.Texture( ),
+ *
+ * lightMap: new THREE.Texture( ),
+ *
+ * bumpMap: new THREE.Texture( ),
+ * bumpScale: ,
+ *
+ * normalMap: new THREE.Texture( ),
+ * normalScale: ,
+ *
+ * specularMap: new THREE.Texture( ),
+ *
+ * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.Multiply,
+ * reflectivity: ,
+ * refractionRatio: ,
+ *
+ * shading: THREE.SmoothShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth: ,
+ *
+ * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ * skinning: ,
+ * morphTargets: ,
+ * morphNormals: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.MeshPhongMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff ); // diffuse
+ this.ambient = new THREE.Color( 0xffffff );
+ this.emissive = new THREE.Color( 0x000000 );
+ this.specular = new THREE.Color( 0x111111 );
+ this.shininess = 30;
+
+ this.metal = false;
+ this.perPixel = true;
+
+ this.wrapAround = false;
+ this.wrapRGB = new THREE.Vector3( 1, 1, 1 );
+
+ this.map = null;
+
+ this.lightMap = null;
+
+ this.bumpMap = null;
+ this.bumpScale = 1;
+
+ this.normalMap = null;
+ this.normalScale = new THREE.Vector2( 1, 1 );
+
+ this.specularMap = null;
+
+ this.envMap = null;
+ this.combine = THREE.MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.fog = true;
+
+ this.shading = THREE.SmoothShading;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.vertexColors = THREE.NoColors;
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshPhongMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshPhongMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshPhongMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+ material.ambient.copy( this.ambient );
+ material.emissive.copy( this.emissive );
+ material.specular.copy( this.specular );
+ material.shininess = this.shininess;
+
+ material.metal = this.metal;
+ material.perPixel = this.perPixel;
+
+ material.wrapAround = this.wrapAround;
+ material.wrapRGB.copy( this.wrapRGB );
+
+ material.map = this.map;
+
+ material.lightMap = this.lightMap;
+
+ material.bumpMap = this.bumpMap;
+ material.bumpScale = this.bumpScale;
+
+ material.normalMap = this.normalMap;
+ material.normalScale.copy( this.normalScale );
+
+ material.specularMap = this.specularMap;
+
+ material.envMap = this.envMap;
+ material.combine = this.combine;
+ material.reflectivity = this.reflectivity;
+ material.refractionRatio = this.refractionRatio;
+
+ material.fog = this.fog;
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+ material.wireframeLinecap = this.wireframeLinecap;
+ material.wireframeLinejoin = this.wireframeLinejoin;
+
+ material.vertexColors = this.vertexColors;
+
+ material.skinning = this.skinning;
+ material.morphTargets = this.morphTargets;
+ material.morphNormals = this.morphNormals;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * opacity: ,
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth:
+ * }
+ */
+
+THREE.MeshDepthMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshDepthMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshDepthMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshDepthMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ * opacity: ,
+ *
+ * shading: THREE.FlatShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth:
+ * }
+ */
+
+THREE.MeshNormalMaterial = function ( parameters ) {
+
+ THREE.Material.call( this, parameters );
+
+ this.shading = THREE.FlatShading;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.morphTargets = false;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshNormalMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshNormalMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshNormalMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.MeshFaceMaterial = function ( materials ) {
+
+ this.materials = materials instanceof Array ? materials : [];
+
+};
+
+THREE.MeshFaceMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshFaceMaterial();
+
+ for ( var i = 0; i < this.materials.length; i ++ ) {
+
+ material.materials.push( this.materials[ i ].clone() );
+
+ }
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ * map: new THREE.Texture( ),
+ *
+ * size: ,
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * vertexColors: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.ParticleBasicMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff );
+
+ this.map = null;
+
+ this.size = 1;
+ this.sizeAttenuation = true;
+
+ this.vertexColors = false;
+
+ this.fog = true;
+
+ this.setValues( parameters );
+
+};
+
+THREE.ParticleBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ParticleBasicMaterial.prototype.clone = function () {
+
+ var material = new THREE.ParticleBasicMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+
+ material.map = this.map;
+
+ material.size = this.size;
+ material.sizeAttenuation = this.sizeAttenuation;
+
+ material.vertexColors = this.vertexColors;
+
+ material.fog = this.fog;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ * color: ,
+ * program: ,
+ * opacity: ,
+ * blending: THREE.NormalBlending
+ * }
+ */
+
+THREE.ParticleCanvasMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff );
+ this.program = function ( context, color ) {};
+
+ this.setValues( parameters );
+
+};
+
+THREE.ParticleCanvasMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ParticleCanvasMaterial.prototype.clone = function () {
+
+ var material = new THREE.ParticleCanvasMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+ material.program = this.program;
+
+ return material;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * fragmentShader: ,
+ * vertexShader: ,
+ *
+ * uniforms: { "parameter1": { type: "f", value: 1.0 }, "parameter2": { type: "i" value2: 2 } },
+ *
+ * defines: { "label" : "value" },
+ *
+ * shading: THREE.SmoothShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth: ,
+ *
+ * lights: ,
+ *
+ * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ * skinning: ,
+ * morphTargets: ,
+ * morphNormals: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.ShaderMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.fragmentShader = "void main() {}";
+ this.vertexShader = "void main() {}";
+ this.uniforms = {};
+ this.defines = {};
+ this.attributes = null;
+
+ this.shading = THREE.SmoothShading;
+
+ this.linewidth = 1;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.fog = false; // set to use scene fog
+
+ this.lights = false; // set to use scene lights
+
+ this.vertexColors = THREE.NoColors; // set to use "color" attribute stream
+
+ this.skinning = false; // set to use skinning attribute streams
+
+ this.morphTargets = false; // set to use morph targets
+ this.morphNormals = false; // set to use morph normals
+
+ // When rendered geometry doesn't include these attributes but the material does,
+ // use these default values in WebGL. This avoids errors when buffer data is missing.
+ this.defaultAttributeValues = {
+ "color" : [ 1, 1, 1],
+ "uv" : [ 0, 0 ],
+ "uv2" : [ 0, 0 ]
+ };
+
+ // By default, bind position to attribute index 0. In WebGL, attribute 0
+ // should always be used to avoid potentially expensive emulation.
+ this.index0AttributeName = "position";
+
+ this.setValues( parameters );
+
+};
+
+THREE.ShaderMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ShaderMaterial.prototype.clone = function () {
+
+ var material = new THREE.ShaderMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.fragmentShader = this.fragmentShader;
+ material.vertexShader = this.vertexShader;
+
+ material.uniforms = THREE.UniformsUtils.clone( this.uniforms );
+
+ material.attributes = this.attributes;
+ material.defines = this.defines;
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+
+ material.fog = this.fog;
+
+ material.lights = this.lights;
+
+ material.vertexColors = this.vertexColors;
+
+ material.skinning = this.skinning;
+
+ material.morphTargets = this.morphTargets;
+ material.morphNormals = this.morphNormals;
+
+ return material;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ * map: new THREE.Texture( ),
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * useScreenCoordinates: ,
+ * sizeAttenuation: ,
+ * scaleByViewport: ,
+ * alignment: THREE.SpriteAlignment.center,
+ *
+ * uvOffset: new THREE.Vector2(),
+ * uvScale: new THREE.Vector2(),
+ *
+ * fog:
+ * }
+ */
+
+THREE.SpriteMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ // defaults
+
+ this.color = new THREE.Color( 0xffffff );
+ this.map = new THREE.Texture();
+
+ this.useScreenCoordinates = true;
+ this.depthTest = !this.useScreenCoordinates;
+ this.sizeAttenuation = !this.useScreenCoordinates;
+ this.scaleByViewport = !this.sizeAttenuation;
+ this.alignment = THREE.SpriteAlignment.center.clone();
+
+ this.fog = false;
+
+ this.uvOffset = new THREE.Vector2( 0, 0 );
+ this.uvScale = new THREE.Vector2( 1, 1 );
+
+ // set parameters
+
+ this.setValues( parameters );
+
+ // override coupled defaults if not specified explicitly by parameters
+
+ parameters = parameters || {};
+
+ if ( parameters.depthTest === undefined ) this.depthTest = !this.useScreenCoordinates;
+ if ( parameters.sizeAttenuation === undefined ) this.sizeAttenuation = !this.useScreenCoordinates;
+ if ( parameters.scaleByViewport === undefined ) this.scaleByViewport = !this.sizeAttenuation;
+
+};
+
+THREE.SpriteMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.SpriteMaterial.prototype.clone = function () {
+
+ var material = new THREE.SpriteMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+ material.map = this.map;
+
+ material.useScreenCoordinates = this.useScreenCoordinates;
+ material.sizeAttenuation = this.sizeAttenuation;
+ material.scaleByViewport = this.scaleByViewport;
+ material.alignment.copy( this.alignment );
+
+ material.uvOffset.copy( this.uvOffset );
+ material.uvScale.copy( this.uvScale );
+
+ material.fog = this.fog;
+
+ return material;
+
+};
+
+// Alignment enums
+
+THREE.SpriteAlignment = {};
+THREE.SpriteAlignment.topLeft = new THREE.Vector2( 1, -1 );
+THREE.SpriteAlignment.topCenter = new THREE.Vector2( 0, -1 );
+THREE.SpriteAlignment.topRight = new THREE.Vector2( -1, -1 );
+THREE.SpriteAlignment.centerLeft = new THREE.Vector2( 1, 0 );
+THREE.SpriteAlignment.center = new THREE.Vector2( 0, 0 );
+THREE.SpriteAlignment.centerRight = new THREE.Vector2( -1, 0 );
+THREE.SpriteAlignment.bottomLeft = new THREE.Vector2( 1, 1 );
+THREE.SpriteAlignment.bottomCenter = new THREE.Vector2( 0, 1 );
+THREE.SpriteAlignment.bottomRight = new THREE.Vector2( -1, 1 );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ */
+
+THREE.Texture = function ( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+
+ this.id = THREE.TextureIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ this.image = image;
+ this.mipmaps = [];
+
+ this.mapping = mapping !== undefined ? mapping : new THREE.UVMapping();
+
+ this.wrapS = wrapS !== undefined ? wrapS : THREE.ClampToEdgeWrapping;
+ this.wrapT = wrapT !== undefined ? wrapT : THREE.ClampToEdgeWrapping;
+
+ this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter;
+ this.minFilter = minFilter !== undefined ? minFilter : THREE.LinearMipMapLinearFilter;
+
+ this.anisotropy = anisotropy !== undefined ? anisotropy : 1;
+
+ this.format = format !== undefined ? format : THREE.RGBAFormat;
+ this.type = type !== undefined ? type : THREE.UnsignedByteType;
+
+ this.offset = new THREE.Vector2( 0, 0 );
+ this.repeat = new THREE.Vector2( 1, 1 );
+
+ this.generateMipmaps = true;
+ this.premultiplyAlpha = false;
+ this.flipY = true;
+ this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)
+
+ this.needsUpdate = false;
+ this.onUpdate = null;
+
+};
+
+THREE.Texture.prototype = {
+
+ constructor: THREE.Texture,
+
+ clone: function ( texture ) {
+
+ if ( texture === undefined ) texture = new THREE.Texture();
+
+ texture.image = this.image;
+ texture.mipmaps = this.mipmaps.slice(0);
+
+ texture.mapping = this.mapping;
+
+ texture.wrapS = this.wrapS;
+ texture.wrapT = this.wrapT;
+
+ texture.magFilter = this.magFilter;
+ texture.minFilter = this.minFilter;
+
+ texture.anisotropy = this.anisotropy;
+
+ texture.format = this.format;
+ texture.type = this.type;
+
+ texture.offset.copy( this.offset );
+ texture.repeat.copy( this.repeat );
+
+ texture.generateMipmaps = this.generateMipmaps;
+ texture.premultiplyAlpha = this.premultiplyAlpha;
+ texture.flipY = this.flipY;
+ texture.unpackAlignment = this.unpackAlignment;
+
+ return texture;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Texture.prototype );
+
+THREE.TextureIdCount = 0;
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.CompressedTexture = function ( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) {
+
+ THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+ this.image = { width: width, height: height };
+ this.mipmaps = mipmaps;
+
+ this.generateMipmaps = false; // WebGL currently can't generate mipmaps for compressed textures, they must be embedded in DDS file
+
+};
+
+THREE.CompressedTexture.prototype = Object.create( THREE.Texture.prototype );
+
+THREE.CompressedTexture.prototype.clone = function () {
+
+ var texture = new THREE.CompressedTexture();
+
+ THREE.Texture.prototype.clone.call( this, texture );
+
+ return texture;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DataTexture = function ( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) {
+
+ THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+ this.image = { data: data, width: width, height: height };
+
+};
+
+THREE.DataTexture.prototype = Object.create( THREE.Texture.prototype );
+
+THREE.DataTexture.prototype.clone = function () {
+
+ var texture = new THREE.DataTexture();
+
+ THREE.Texture.prototype.clone.call( this, texture );
+
+ return texture;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Particle = function ( material ) {
+
+ THREE.Object3D.call( this );
+
+ this.material = material;
+
+};
+
+THREE.Particle.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Particle.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Particle( this.material );
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ParticleSystem = function ( geometry, material ) {
+
+ THREE.Object3D.call( this );
+
+ this.geometry = geometry !== undefined ? geometry : new THREE.Geometry();
+ this.material = material !== undefined ? material : new THREE.ParticleBasicMaterial( { color: Math.random() * 0xffffff } );
+
+ this.sortParticles = false;
+ this.frustumCulled = false;
+
+};
+
+THREE.ParticleSystem.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.ParticleSystem.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.ParticleSystem( this.geometry, this.material );
+
+ object.sortParticles = this.sortParticles;
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Line = function ( geometry, material, type ) {
+
+ THREE.Object3D.call( this );
+
+ this.geometry = geometry !== undefined ? geometry : new THREE.Geometry();
+ this.material = material !== undefined ? material : new THREE.LineBasicMaterial( { color: Math.random() * 0xffffff } );
+
+ this.type = ( type !== undefined ) ? type : THREE.LineStrip;
+
+};
+
+THREE.LineStrip = 0;
+THREE.LinePieces = 1;
+
+THREE.Line.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Line.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Line( this.geometry, this.material, this.type );
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author jonobr1 / http://jonobr1.com/
+ */
+
+THREE.Mesh = function ( geometry, material ) {
+
+ THREE.Object3D.call( this );
+
+ this.geometry = geometry !== undefined ? geometry : new THREE.Geometry();
+ this.material = material !== undefined ? material : new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } );
+
+ this.updateMorphTargets();
+
+};
+
+THREE.Mesh.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Mesh.prototype.updateMorphTargets = function () {
+
+ if ( this.geometry.morphTargets.length > 0 ) {
+
+ this.morphTargetBase = -1;
+ this.morphTargetForcedOrder = [];
+ this.morphTargetInfluences = [];
+ this.morphTargetDictionary = {};
+
+ for ( var m = 0, ml = this.geometry.morphTargets.length; m < ml; m ++ ) {
+
+ this.morphTargetInfluences.push( 0 );
+ this.morphTargetDictionary[ this.geometry.morphTargets[ m ].name ] = m;
+
+ }
+
+ }
+
+};
+
+THREE.Mesh.prototype.getMorphTargetIndexByName = function ( name ) {
+
+ if ( this.morphTargetDictionary[ name ] !== undefined ) {
+
+ return this.morphTargetDictionary[ name ];
+
+ }
+
+ console.log( "THREE.Mesh.getMorphTargetIndexByName: morph target " + name + " does not exist. Returning 0." );
+
+ return 0;
+
+};
+
+THREE.Mesh.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Mesh( this.geometry, this.material );
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Bone = function( belongsToSkin ) {
+
+ THREE.Object3D.call( this );
+
+ this.skin = belongsToSkin;
+ this.skinMatrix = new THREE.Matrix4();
+
+};
+
+THREE.Bone.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Bone.prototype.update = function ( parentSkinMatrix, forceUpdate ) {
+
+ // update local
+
+ if ( this.matrixAutoUpdate ) {
+
+ forceUpdate |= this.updateMatrix();
+
+ }
+
+ // update skin matrix
+
+ if ( forceUpdate || this.matrixWorldNeedsUpdate ) {
+
+ if( parentSkinMatrix ) {
+
+ this.skinMatrix.multiplyMatrices( parentSkinMatrix, this.matrix );
+
+ } else {
+
+ this.skinMatrix.copy( this.matrix );
+
+ }
+
+ this.matrixWorldNeedsUpdate = false;
+ forceUpdate = true;
+
+ }
+
+ // update children
+
+ var child, i, l = this.children.length;
+
+ for ( i = 0; i < l; i ++ ) {
+
+ this.children[ i ].update( this.skinMatrix, forceUpdate );
+
+ }
+
+};
+
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) {
+
+ THREE.Mesh.call( this, geometry, material );
+
+ //
+
+ this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true;
+
+ // init bones
+
+ this.identityMatrix = new THREE.Matrix4();
+
+ this.bones = [];
+ this.boneMatrices = [];
+
+ var b, bone, gbone, p, q, s;
+
+ if ( this.geometry && this.geometry.bones !== undefined ) {
+
+ for ( b = 0; b < this.geometry.bones.length; b ++ ) {
+
+ gbone = this.geometry.bones[ b ];
+
+ p = gbone.pos;
+ q = gbone.rotq;
+ s = gbone.scl;
+
+ bone = this.addBone();
+
+ bone.name = gbone.name;
+ bone.position.set( p[0], p[1], p[2] );
+ bone.quaternion.set( q[0], q[1], q[2], q[3] );
+
+ if ( s !== undefined ) {
+
+ bone.scale.set( s[0], s[1], s[2] );
+
+ } else {
+
+ bone.scale.set( 1, 1, 1 );
+
+ }
+
+ }
+
+ for ( b = 0; b < this.bones.length; b ++ ) {
+
+ gbone = this.geometry.bones[ b ];
+ bone = this.bones[ b ];
+
+ if ( gbone.parent === -1 ) {
+
+ this.add( bone );
+
+ } else {
+
+ this.bones[ gbone.parent ].add( bone );
+
+ }
+
+ }
+
+ //
+
+ var nBones = this.bones.length;
+
+ if ( this.useVertexTexture ) {
+
+ // layout (1 matrix = 4 pixels)
+ // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
+ // with 8x8 pixel texture max 16 bones (8 * 8 / 4)
+ // 16x16 pixel texture max 64 bones (16 * 16 / 4)
+ // 32x32 pixel texture max 256 bones (32 * 32 / 4)
+ // 64x64 pixel texture max 1024 bones (64 * 64 / 4)
+
+ var size;
+
+ if ( nBones > 256 )
+ size = 64;
+ else if ( nBones > 64 )
+ size = 32;
+ else if ( nBones > 16 )
+ size = 16;
+ else
+ size = 8;
+
+ this.boneTextureWidth = size;
+ this.boneTextureHeight = size;
+
+ this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel
+ this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType );
+ this.boneTexture.minFilter = THREE.NearestFilter;
+ this.boneTexture.magFilter = THREE.NearestFilter;
+ this.boneTexture.generateMipmaps = false;
+ this.boneTexture.flipY = false;
+
+ } else {
+
+ this.boneMatrices = new Float32Array( 16 * nBones );
+
+ }
+
+ this.pose();
+
+ }
+
+};
+
+THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.SkinnedMesh.prototype.addBone = function( bone ) {
+
+ if ( bone === undefined ) {
+
+ bone = new THREE.Bone( this );
+
+ }
+
+ this.bones.push( bone );
+
+ return bone;
+
+};
+
+THREE.SkinnedMesh.prototype.updateMatrixWorld = function () {
+
+ var offsetMatrix = new THREE.Matrix4();
+
+ return function ( force ) {
+
+ this.matrixAutoUpdate && this.updateMatrix();
+
+ // update matrixWorld
+
+ if ( this.matrixWorldNeedsUpdate || force ) {
+
+ if ( this.parent ) {
+
+ this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+ } else {
+
+ this.matrixWorld.copy( this.matrix );
+
+ }
+
+ this.matrixWorldNeedsUpdate = false;
+
+ force = true;
+
+ }
+
+ // update children
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ var child = this.children[ i ];
+
+ if ( child instanceof THREE.Bone ) {
+
+ child.update( this.identityMatrix, false );
+
+ } else {
+
+ child.updateMatrixWorld( true );
+
+ }
+
+ }
+
+ // make a snapshot of the bones' rest position
+
+ if ( this.boneInverses == undefined ) {
+
+ this.boneInverses = [];
+
+ for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
+
+ var inverse = new THREE.Matrix4();
+
+ inverse.getInverse( this.bones[ b ].skinMatrix );
+
+ this.boneInverses.push( inverse );
+
+ }
+
+ }
+
+ // flatten bone matrices to array
+
+ for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
+
+ // compute the offset between the current and the original transform;
+
+ // TODO: we could get rid of this multiplication step if the skinMatrix
+ // was already representing the offset; however, this requires some
+ // major changes to the animation system
+
+ offsetMatrix.multiplyMatrices( this.bones[ b ].skinMatrix, this.boneInverses[ b ] );
+ offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 );
+
+ }
+
+ if ( this.useVertexTexture ) {
+
+ this.boneTexture.needsUpdate = true;
+
+ }
+
+ };
+
+}();
+
+THREE.SkinnedMesh.prototype.pose = function () {
+
+ this.updateMatrixWorld( true );
+
+ this.normalizeSkinWeights();
+
+};
+
+THREE.SkinnedMesh.prototype.normalizeSkinWeights = function () {
+
+ if ( this.geometry instanceof THREE.Geometry ) {
+
+ for ( var i = 0; i < this.geometry.skinIndices.length; i ++ ) {
+
+ var sw = this.geometry.skinWeights[ i ];
+
+ var scale = 1.0 / sw.lengthManhattan();
+
+ if ( scale !== Infinity ) {
+
+ sw.multiplyScalar( scale );
+
+ } else {
+
+ sw.set( 1 ); // this will be normalized by the shader anyway
+
+ }
+
+ }
+
+ } else {
+
+ // skinning weights assumed to be normalized for THREE.BufferGeometry
+
+ }
+
+};
+
+THREE.SkinnedMesh.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) {
+
+ object = new THREE.SkinnedMesh( this.geometry, this.material, this.useVertexTexture );
+
+ }
+
+ THREE.Mesh.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.MorphAnimMesh = function ( geometry, material ) {
+
+ THREE.Mesh.call( this, geometry, material );
+
+ // API
+
+ this.duration = 1000; // milliseconds
+ this.mirroredLoop = false;
+ this.time = 0;
+
+ // internals
+
+ this.lastKeyframe = 0;
+ this.currentKeyframe = 0;
+
+ this.direction = 1;
+ this.directionBackwards = false;
+
+ this.setFrameRange( 0, this.geometry.morphTargets.length - 1 );
+
+};
+
+THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.MorphAnimMesh.prototype.setFrameRange = function ( start, end ) {
+
+ this.startKeyframe = start;
+ this.endKeyframe = end;
+
+ this.length = this.endKeyframe - this.startKeyframe + 1;
+
+};
+
+THREE.MorphAnimMesh.prototype.setDirectionForward = function () {
+
+ this.direction = 1;
+ this.directionBackwards = false;
+
+};
+
+THREE.MorphAnimMesh.prototype.setDirectionBackward = function () {
+
+ this.direction = -1;
+ this.directionBackwards = true;
+
+};
+
+THREE.MorphAnimMesh.prototype.parseAnimations = function () {
+
+ var geometry = this.geometry;
+
+ if ( ! geometry.animations ) geometry.animations = {};
+
+ var firstAnimation, animations = geometry.animations;
+
+ var pattern = /([a-z]+)(\d+)/;
+
+ for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
+
+ var morph = geometry.morphTargets[ i ];
+ var parts = morph.name.match( pattern );
+
+ if ( parts && parts.length > 1 ) {
+
+ var label = parts[ 1 ];
+ var num = parts[ 2 ];
+
+ if ( ! animations[ label ] ) animations[ label ] = { start: Infinity, end: -Infinity };
+
+ var animation = animations[ label ];
+
+ if ( i < animation.start ) animation.start = i;
+ if ( i > animation.end ) animation.end = i;
+
+ if ( ! firstAnimation ) firstAnimation = label;
+
+ }
+
+ }
+
+ geometry.firstAnimation = firstAnimation;
+
+};
+
+THREE.MorphAnimMesh.prototype.setAnimationLabel = function ( label, start, end ) {
+
+ if ( ! this.geometry.animations ) this.geometry.animations = {};
+
+ this.geometry.animations[ label ] = { start: start, end: end };
+
+};
+
+THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
+
+ var animation = this.geometry.animations[ label ];
+
+ if ( animation ) {
+
+ this.setFrameRange( animation.start, animation.end );
+ this.duration = 1000 * ( ( animation.end - animation.start ) / fps );
+ this.time = 0;
+
+ } else {
+
+ console.warn( "animation[" + label + "] undefined" );
+
+ }
+
+};
+
+THREE.MorphAnimMesh.prototype.updateAnimation = function ( delta ) {
+
+ var frameTime = this.duration / this.length;
+
+ this.time += this.direction * delta;
+
+ if ( this.mirroredLoop ) {
+
+ if ( this.time > this.duration || this.time < 0 ) {
+
+ this.direction *= -1;
+
+ if ( this.time > this.duration ) {
+
+ this.time = this.duration;
+ this.directionBackwards = true;
+
+ }
+
+ if ( this.time < 0 ) {
+
+ this.time = 0;
+ this.directionBackwards = false;
+
+ }
+
+ }
+
+ } else {
+
+ this.time = this.time % this.duration;
+
+ if ( this.time < 0 ) this.time += this.duration;
+
+ }
+
+ var keyframe = this.startKeyframe + THREE.Math.clamp( Math.floor( this.time / frameTime ), 0, this.length - 1 );
+
+ if ( keyframe !== this.currentKeyframe ) {
+
+ this.morphTargetInfluences[ this.lastKeyframe ] = 0;
+ this.morphTargetInfluences[ this.currentKeyframe ] = 1;
+
+ this.morphTargetInfluences[ keyframe ] = 0;
+
+ this.lastKeyframe = this.currentKeyframe;
+ this.currentKeyframe = keyframe;
+
+ }
+
+ var mix = ( this.time % frameTime ) / frameTime;
+
+ if ( this.directionBackwards ) {
+
+ mix = 1 - mix;
+
+ }
+
+ this.morphTargetInfluences[ this.currentKeyframe ] = mix;
+ this.morphTargetInfluences[ this.lastKeyframe ] = 1 - mix;
+
+};
+
+THREE.MorphAnimMesh.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.MorphAnimMesh( this.geometry, this.material );
+
+ object.duration = this.duration;
+ object.mirroredLoop = this.mirroredLoop;
+ object.time = this.time;
+
+ object.lastKeyframe = this.lastKeyframe;
+ object.currentKeyframe = this.currentKeyframe;
+
+ object.direction = this.direction;
+ object.directionBackwards = this.directionBackwards;
+
+ THREE.Mesh.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.LOD = function () {
+
+ THREE.Object3D.call( this );
+
+ this.objects = [];
+
+};
+
+
+THREE.LOD.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.LOD.prototype.addLevel = function ( object, distance ) {
+
+ if ( distance === undefined ) distance = 0;
+
+ distance = Math.abs( distance );
+
+ for ( var l = 0; l < this.objects.length; l ++ ) {
+
+ if ( distance < this.objects[ l ].distance ) {
+
+ break;
+
+ }
+
+ }
+
+ this.objects.splice( l, 0, { distance: distance, object: object } );
+ this.add( object );
+
+};
+
+THREE.LOD.prototype.getObjectForDistance = function ( distance ) {
+
+ for ( var i = 1, l = this.objects.length; i < l; i ++ ) {
+
+ if ( distance < this.objects[ i ].distance ) {
+
+ break;
+
+ }
+
+ }
+
+ return this.objects[ i - 1 ].object;
+
+};
+
+THREE.LOD.prototype.update = function () {
+
+ var v1 = new THREE.Vector3();
+ var v2 = new THREE.Vector3();
+
+ return function ( camera ) {
+
+ if ( this.objects.length > 1 ) {
+
+ v1.getPositionFromMatrix( camera.matrixWorld );
+ v2.getPositionFromMatrix( this.matrixWorld );
+
+ var distance = v1.distanceTo( v2 );
+
+ this.objects[ 0 ].object.visible = true;
+
+ for ( var i = 1, l = this.objects.length; i < l; i ++ ) {
+
+ if ( distance >= this.objects[ i ].distance ) {
+
+ this.objects[ i - 1 ].object.visible = false;
+ this.objects[ i ].object.visible = true;
+
+ } else {
+
+ break;
+
+ }
+
+ }
+
+ for( ; i < l; i ++ ) {
+
+ this.objects[ i ].object.visible = false;
+
+ }
+
+ }
+
+ };
+
+}();
+
+THREE.LOD.prototype.clone = function () {
+
+ // TODO
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Sprite = function ( material ) {
+
+ THREE.Object3D.call( this );
+
+ this.material = ( material !== undefined ) ? material : new THREE.SpriteMaterial();
+
+ this.rotation3d = this.rotation;
+ this.rotation = 0;
+
+};
+
+THREE.Sprite.prototype = Object.create( THREE.Object3D.prototype );
+
+/*
+ * Custom update matrix
+ */
+
+THREE.Sprite.prototype.updateMatrix = function () {
+
+ this.rotation3d.set( 0, 0, this.rotation, this.rotation3d.order );
+ this.quaternion.setFromEuler( this.rotation3d );
+ this.matrix.compose( this.position, this.quaternion, this.scale );
+
+ this.matrixWorldNeedsUpdate = true;
+
+};
+
+THREE.Sprite.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Sprite( this.material );
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Scene = function () {
+
+ THREE.Object3D.call( this );
+
+ this.fog = null;
+ this.overrideMaterial = null;
+
+ this.autoUpdate = true; // checked by the renderer
+ this.matrixAutoUpdate = false;
+
+ this.__lights = [];
+
+ this.__objectsAdded = [];
+ this.__objectsRemoved = [];
+
+};
+
+THREE.Scene.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Scene.prototype.__addObject = function ( object ) {
+
+ if ( object instanceof THREE.Light ) {
+
+ if ( this.__lights.indexOf( object ) === - 1 ) {
+
+ this.__lights.push( object );
+
+ }
+
+ if ( object.target && object.target.parent === undefined ) {
+
+ this.add( object.target );
+
+ }
+
+ } else if ( !( object instanceof THREE.Camera || object instanceof THREE.Bone ) ) {
+
+ this.__objectsAdded.push( object );
+
+ // check if previously removed
+
+ var i = this.__objectsRemoved.indexOf( object );
+
+ if ( i !== -1 ) {
+
+ this.__objectsRemoved.splice( i, 1 );
+
+ }
+
+ }
+
+ for ( var c = 0; c < object.children.length; c ++ ) {
+
+ this.__addObject( object.children[ c ] );
+
+ }
+
+};
+
+THREE.Scene.prototype.__removeObject = function ( object ) {
+
+ if ( object instanceof THREE.Light ) {
+
+ var i = this.__lights.indexOf( object );
+
+ if ( i !== -1 ) {
+
+ this.__lights.splice( i, 1 );
+
+ }
+
+ if ( object.shadowCascadeArray ) {
+
+ for ( var x = 0; x < object.shadowCascadeArray.length; x ++ ) {
+
+ this.__removeObject( object.shadowCascadeArray[ x ] );
+
+ }
+
+ }
+
+ } else if ( !( object instanceof THREE.Camera ) ) {
+
+ this.__objectsRemoved.push( object );
+
+ // check if previously added
+
+ var i = this.__objectsAdded.indexOf( object );
+
+ if ( i !== -1 ) {
+
+ this.__objectsAdded.splice( i, 1 );
+
+ }
+
+ }
+
+ for ( var c = 0; c < object.children.length; c ++ ) {
+
+ this.__removeObject( object.children[ c ] );
+
+ }
+
+};
+
+THREE.Scene.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Scene();
+
+ THREE.Object3D.prototype.clone.call(this, object);
+
+ if ( this.fog !== null ) object.fog = this.fog.clone();
+ if ( this.overrideMaterial !== null ) object.overrideMaterial = this.overrideMaterial.clone();
+
+ object.autoUpdate = this.autoUpdate;
+ object.matrixAutoUpdate = this.matrixAutoUpdate;
+
+ return object;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Fog = function ( hex, near, far ) {
+
+ this.name = '';
+
+ this.color = new THREE.Color( hex );
+
+ this.near = ( near !== undefined ) ? near : 1;
+ this.far = ( far !== undefined ) ? far : 1000;
+
+};
+
+THREE.Fog.prototype.clone = function () {
+
+ return new THREE.Fog( this.color.getHex(), this.near, this.far );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.FogExp2 = function ( hex, density ) {
+
+ this.name = '';
+
+ this.color = new THREE.Color( hex );
+ this.density = ( density !== undefined ) ? density : 0.00025;
+
+};
+
+THREE.FogExp2.prototype.clone = function () {
+
+ return new THREE.FogExp2( this.color.getHex(), this.density );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.CanvasRenderer = function ( parameters ) {
+
+ console.log( 'THREE.CanvasRenderer', THREE.REVISION );
+
+ var smoothstep = THREE.Math.smoothstep;
+
+ parameters = parameters || {};
+
+ var _this = this,
+ _renderData, _elements, _lights,
+ _projector = new THREE.Projector(),
+
+ _canvas = parameters.canvas !== undefined
+ ? parameters.canvas
+ : document.createElement( 'canvas' ),
+
+ _canvasWidth, _canvasHeight, _canvasWidthHalf, _canvasHeightHalf,
+ _context = _canvas.getContext( '2d' ),
+
+ _clearColor = new THREE.Color( 0x000000 ),
+ _clearAlpha = 0,
+
+ _contextGlobalAlpha = 1,
+ _contextGlobalCompositeOperation = 0,
+ _contextStrokeStyle = null,
+ _contextFillStyle = null,
+ _contextLineWidth = null,
+ _contextLineCap = null,
+ _contextLineJoin = null,
+ _contextDashSize = null,
+ _contextGapSize = 0,
+
+ _camera,
+
+ _v1, _v2, _v3, _v4,
+ _v5 = new THREE.RenderableVertex(),
+ _v6 = new THREE.RenderableVertex(),
+
+ _v1x, _v1y, _v2x, _v2y, _v3x, _v3y,
+ _v4x, _v4y, _v5x, _v5y, _v6x, _v6y,
+
+ _color = new THREE.Color(),
+ _color1 = new THREE.Color(),
+ _color2 = new THREE.Color(),
+ _color3 = new THREE.Color(),
+ _color4 = new THREE.Color(),
+
+ _diffuseColor = new THREE.Color(),
+ _emissiveColor = new THREE.Color(),
+
+ _lightColor = new THREE.Color(),
+
+ _patterns = {}, _imagedatas = {},
+
+ _near, _far,
+
+ _image, _uvs,
+ _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y,
+
+ _clipBox = new THREE.Box2(),
+ _clearBox = new THREE.Box2(),
+ _elemBox = new THREE.Box2(),
+
+ _ambientLight = new THREE.Color(),
+ _directionalLights = new THREE.Color(),
+ _pointLights = new THREE.Color(),
+
+ _vector3 = new THREE.Vector3(), // Needed for PointLight
+
+ _pixelMap, _pixelMapContext, _pixelMapImage, _pixelMapData,
+ _gradientMap, _gradientMapContext, _gradientMapQuality = 16;
+
+ _pixelMap = document.createElement( 'canvas' );
+ _pixelMap.width = _pixelMap.height = 2;
+
+ _pixelMapContext = _pixelMap.getContext( '2d' );
+ _pixelMapContext.fillStyle = 'rgba(0,0,0,1)';
+ _pixelMapContext.fillRect( 0, 0, 2, 2 );
+
+ _pixelMapImage = _pixelMapContext.getImageData( 0, 0, 2, 2 );
+ _pixelMapData = _pixelMapImage.data;
+
+ _gradientMap = document.createElement( 'canvas' );
+ _gradientMap.width = _gradientMap.height = _gradientMapQuality;
+
+ _gradientMapContext = _gradientMap.getContext( '2d' );
+ _gradientMapContext.translate( - _gradientMapQuality / 2, - _gradientMapQuality / 2 );
+ _gradientMapContext.scale( _gradientMapQuality, _gradientMapQuality );
+
+ _gradientMapQuality --; // Fix UVs
+
+ // dash+gap fallbacks for Firefox and everything else
+
+ if ( _context.setLineDash === undefined ) {
+
+ if ( _context.mozDash !== undefined ) {
+
+ _context.setLineDash = function ( values ) {
+
+ _context.mozDash = values[ 0 ] !== null ? values : null;
+
+ }
+
+ } else {
+
+ _context.setLineDash = function () {}
+
+ }
+
+ }
+
+ this.domElement = _canvas;
+
+ this.devicePixelRatio = parameters.devicePixelRatio !== undefined
+ ? parameters.devicePixelRatio
+ : self.devicePixelRatio !== undefined
+ ? self.devicePixelRatio
+ : 1;
+
+ this.autoClear = true;
+ this.sortObjects = true;
+ this.sortElements = true;
+
+ this.info = {
+
+ render: {
+
+ vertices: 0,
+ faces: 0
+
+ }
+
+ }
+
+ // WebGLRenderer compatibility
+
+ this.supportsVertexTextures = function () {};
+ this.setFaceCulling = function () {};
+
+ this.setSize = function ( width, height, updateStyle ) {
+
+ _canvasWidth = width * this.devicePixelRatio;
+ _canvasHeight = height * this.devicePixelRatio;
+
+ _canvasWidthHalf = Math.floor( _canvasWidth / 2 );
+ _canvasHeightHalf = Math.floor( _canvasHeight / 2 );
+
+ _canvas.width = _canvasWidth;
+ _canvas.height = _canvasHeight;
+
+ if ( this.devicePixelRatio !== 1 && updateStyle !== false ) {
+
+ _canvas.style.width = width + 'px';
+ _canvas.style.height = height + 'px';
+
+ }
+
+ _clipBox.set(
+ new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+ new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+ );
+
+ _clearBox.set(
+ new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+ new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+ );
+
+ _contextGlobalAlpha = 1;
+ _contextGlobalCompositeOperation = 0;
+ _contextStrokeStyle = null;
+ _contextFillStyle = null;
+ _contextLineWidth = null;
+ _contextLineCap = null;
+ _contextLineJoin = null;
+
+ };
+
+ this.setClearColor = function ( color, alpha ) {
+
+ _clearColor.set( color );
+ _clearAlpha = alpha !== undefined ? alpha : 1;
+
+ _clearBox.set(
+ new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+ new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+ );
+
+ };
+
+ this.setClearColorHex = function ( hex, alpha ) {
+
+ console.warn( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' );
+ this.setClearColor( hex, alpha );
+
+ };
+
+ this.getMaxAnisotropy = function () {
+
+ return 0;
+
+ };
+
+ this.clear = function () {
+
+ _context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf );
+
+ if ( _clearBox.empty() === false ) {
+
+ _clearBox.intersect( _clipBox );
+ _clearBox.expandByScalar( 2 );
+
+ if ( _clearAlpha < 1 ) {
+
+ _context.clearRect(
+ _clearBox.min.x | 0,
+ _clearBox.min.y | 0,
+ ( _clearBox.max.x - _clearBox.min.x ) | 0,
+ ( _clearBox.max.y - _clearBox.min.y ) | 0
+ );
+
+ }
+
+ if ( _clearAlpha > 0 ) {
+
+ setBlending( THREE.NormalBlending );
+ setOpacity( 1 );
+
+ setFillStyle( 'rgba(' + Math.floor( _clearColor.r * 255 ) + ',' + Math.floor( _clearColor.g * 255 ) + ',' + Math.floor( _clearColor.b * 255 ) + ',' + _clearAlpha + ')' );
+
+ _context.fillRect(
+ _clearBox.min.x | 0,
+ _clearBox.min.y | 0,
+ ( _clearBox.max.x - _clearBox.min.x ) | 0,
+ ( _clearBox.max.y - _clearBox.min.y ) | 0
+ );
+
+ }
+
+ _clearBox.makeEmpty();
+
+ }
+
+
+ };
+
+ this.render = function ( scene, camera ) {
+
+ if ( camera instanceof THREE.Camera === false ) {
+
+ console.error( 'THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.' );
+ return;
+
+ }
+
+ if ( this.autoClear === true ) this.clear();
+
+ _context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf );
+
+ _this.info.render.vertices = 0;
+ _this.info.render.faces = 0;
+
+ _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
+ _elements = _renderData.elements;
+ _lights = _renderData.lights;
+ _camera = camera;
+
+ /* DEBUG
+ setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
+ _context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
+ */
+
+ calculateLights();
+
+ for ( var e = 0, el = _elements.length; e < el; e++ ) {
+
+ var element = _elements[ e ];
+
+ var material = element.material;
+
+ if ( material === undefined || material.visible === false ) continue;
+
+ _elemBox.makeEmpty();
+
+ if ( element instanceof THREE.RenderableParticle ) {
+
+ _v1 = element;
+ _v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf;
+
+ renderParticle( _v1, element, material );
+
+ } else if ( element instanceof THREE.RenderableLine ) {
+
+ _v1 = element.v1; _v2 = element.v2;
+
+ _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+ _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+
+ _elemBox.setFromPoints( [
+ _v1.positionScreen,
+ _v2.positionScreen
+ ] );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+ renderLine( _v1, _v2, element, material );
+
+ }
+
+ } else if ( element instanceof THREE.RenderableFace3 ) {
+
+ _v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
+
+ if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue;
+ if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue;
+ if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue;
+
+ _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+ _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+ _v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf;
+
+ if ( material.overdraw > 0 ) {
+
+ expand( _v1.positionScreen, _v2.positionScreen, material.overdraw );
+ expand( _v2.positionScreen, _v3.positionScreen, material.overdraw );
+ expand( _v3.positionScreen, _v1.positionScreen, material.overdraw );
+
+ }
+
+ _elemBox.setFromPoints( [
+ _v1.positionScreen,
+ _v2.positionScreen,
+ _v3.positionScreen
+ ] );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+ renderFace3( _v1, _v2, _v3, 0, 1, 2, element, material );
+
+ }
+
+ }
+
+ /* DEBUG
+ setLineWidth( 1 );
+ setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
+ _context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
+ */
+
+ _clearBox.union( _elemBox );
+
+ }
+
+ /* DEBUG
+ setLineWidth( 1 );
+ setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
+ _context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
+ */
+
+ _context.setTransform( 1, 0, 0, 1, 0, 0 );
+
+ };
+
+ //
+
+ function calculateLights() {
+
+ _ambientLight.setRGB( 0, 0, 0 );
+ _directionalLights.setRGB( 0, 0, 0 );
+ _pointLights.setRGB( 0, 0, 0 );
+
+ for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
+
+ var light = _lights[ l ];
+ var lightColor = light.color;
+
+ if ( light instanceof THREE.AmbientLight ) {
+
+ _ambientLight.add( lightColor );
+
+ } else if ( light instanceof THREE.DirectionalLight ) {
+
+ // for particles
+
+ _directionalLights.add( lightColor );
+
+ } else if ( light instanceof THREE.PointLight ) {
+
+ // for particles
+
+ _pointLights.add( lightColor );
+
+ }
+
+ }
+
+ }
+
+ function calculateLight( position, normal, color ) {
+
+ for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
+
+ var light = _lights[ l ];
+
+ _lightColor.copy( light.color );
+
+ if ( light instanceof THREE.DirectionalLight ) {
+
+ var lightPosition = _vector3.getPositionFromMatrix( light.matrixWorld ).normalize();
+
+ var amount = normal.dot( lightPosition );
+
+ if ( amount <= 0 ) continue;
+
+ amount *= light.intensity;
+
+ color.add( _lightColor.multiplyScalar( amount ) );
+
+ } else if ( light instanceof THREE.PointLight ) {
+
+ var lightPosition = _vector3.getPositionFromMatrix( light.matrixWorld );
+
+ var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
+
+ if ( amount <= 0 ) continue;
+
+ amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
+
+ if ( amount == 0 ) continue;
+
+ amount *= light.intensity;
+
+ color.add( _lightColor.multiplyScalar( amount ) );
+
+ }
+
+ }
+
+ }
+
+ function renderParticle( v1, element, material ) {
+
+ setOpacity( material.opacity );
+ setBlending( material.blending );
+
+ var width, height, scaleX, scaleY,
+ bitmap, bitmapWidth, bitmapHeight;
+
+ if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+ if ( material.map === null ) {
+
+ scaleX = element.object.scale.x;
+ scaleY = element.object.scale.y;
+
+ // TODO: Be able to disable this
+
+ scaleX *= element.scale.x * _canvasWidthHalf;
+ scaleY *= element.scale.y * _canvasHeightHalf;
+
+ _elemBox.min.set( v1.x - scaleX, v1.y - scaleY );
+ _elemBox.max.set( v1.x + scaleX, v1.y + scaleY );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+ _elemBox.makeEmpty();
+ return;
+
+ }
+
+ setFillStyle( material.color.getStyle() );
+
+ _context.save();
+ _context.translate( v1.x, v1.y );
+ _context.rotate( - element.rotation );
+ _context.scale( scaleX, scaleY );
+ _context.fillRect( -1, -1, 2, 2 );
+ _context.restore();
+
+ } else {
+
+ bitmap = material.map.image;
+ bitmapWidth = bitmap.width >> 1;
+ bitmapHeight = bitmap.height >> 1;
+
+ scaleX = element.scale.x * _canvasWidthHalf;
+ scaleY = element.scale.y * _canvasHeightHalf;
+
+ width = scaleX * bitmapWidth;
+ height = scaleY * bitmapHeight;
+
+ // TODO: Rotations break this...
+
+ _elemBox.min.set( v1.x - width, v1.y - height );
+ _elemBox.max.set( v1.x + width, v1.y + height );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+ _elemBox.makeEmpty();
+ return;
+
+ }
+
+ _context.save();
+ _context.translate( v1.x, v1.y );
+ _context.rotate( - element.rotation );
+ _context.scale( scaleX, - scaleY );
+
+ _context.translate( - bitmapWidth, - bitmapHeight );
+ _context.drawImage( bitmap, 0, 0 );
+ _context.restore();
+
+ }
+
+ /* DEBUG
+ setStrokeStyle( 'rgb(255,255,0)' );
+ _context.beginPath();
+ _context.moveTo( v1.x - 10, v1.y );
+ _context.lineTo( v1.x + 10, v1.y );
+ _context.moveTo( v1.x, v1.y - 10 );
+ _context.lineTo( v1.x, v1.y + 10 );
+ _context.stroke();
+ */
+
+ } else if ( material instanceof THREE.ParticleCanvasMaterial ) {
+
+ width = element.scale.x * _canvasWidthHalf;
+ height = element.scale.y * _canvasHeightHalf;
+
+ _elemBox.min.set( v1.x - width, v1.y - height );
+ _elemBox.max.set( v1.x + width, v1.y + height );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+ _elemBox.makeEmpty();
+ return;
+
+ }
+
+ setStrokeStyle( material.color.getStyle() );
+ setFillStyle( material.color.getStyle() );
+
+ _context.save();
+ _context.translate( v1.x, v1.y );
+ _context.rotate( - element.rotation );
+ _context.scale( width, height );
+
+ material.program( _context );
+
+ _context.restore();
+
+ }
+
+ }
+
+ function renderLine( v1, v2, element, material ) {
+
+ setOpacity( material.opacity );
+ setBlending( material.blending );
+
+ _context.beginPath();
+ _context.moveTo( v1.positionScreen.x, v1.positionScreen.y );
+ _context.lineTo( v2.positionScreen.x, v2.positionScreen.y );
+
+ if ( material instanceof THREE.LineBasicMaterial ) {
+
+ setLineWidth( material.linewidth );
+ setLineCap( material.linecap );
+ setLineJoin( material.linejoin );
+
+ if ( material.vertexColors !== THREE.VertexColors ) {
+
+ setStrokeStyle( material.color.getStyle() );
+
+ } else {
+
+ var colorStyle1 = element.vertexColors[0].getStyle();
+ var colorStyle2 = element.vertexColors[1].getStyle();
+
+ if ( colorStyle1 === colorStyle2 ) {
+
+ setStrokeStyle( colorStyle1 );
+
+ } else {
+
+ try {
+
+ var grad = _context.createLinearGradient(
+ v1.positionScreen.x,
+ v1.positionScreen.y,
+ v2.positionScreen.x,
+ v2.positionScreen.y
+ );
+ grad.addColorStop( 0, colorStyle1 );
+ grad.addColorStop( 1, colorStyle2 );
+
+ } catch ( exception ) {
+
+ grad = colorStyle1;
+
+ }
+
+ setStrokeStyle( grad );
+
+ }
+
+ }
+
+ _context.stroke();
+ _elemBox.expandByScalar( material.linewidth * 2 );
+
+ } else if ( material instanceof THREE.LineDashedMaterial ) {
+
+ setLineWidth( material.linewidth );
+ setLineCap( material.linecap );
+ setLineJoin( material.linejoin );
+ setStrokeStyle( material.color.getStyle() );
+ setDashAndGap( material.dashSize, material.gapSize );
+
+ _context.stroke();
+
+ _elemBox.expandByScalar( material.linewidth * 2 );
+
+ setDashAndGap( null, null );
+
+ }
+
+ }
+
+ function renderFace3( v1, v2, v3, uv1, uv2, uv3, element, material ) {
+
+ _this.info.render.vertices += 3;
+ _this.info.render.faces ++;
+
+ setOpacity( material.opacity );
+ setBlending( material.blending );
+
+ _v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y;
+ _v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y;
+ _v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y;
+
+ drawTriangle( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y );
+
+ if ( ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) && material.map === null ) {
+
+ _diffuseColor.copy( material.color );
+ _emissiveColor.copy( material.emissive );
+
+ if ( material.vertexColors === THREE.FaceColors ) {
+
+ _diffuseColor.multiply( element.color );
+
+ }
+
+ if ( material.wireframe === false && material.shading == THREE.SmoothShading && element.vertexNormalsLength == 3 ) {
+
+ _color1.copy( _ambientLight );
+ _color2.copy( _ambientLight );
+ _color3.copy( _ambientLight );
+
+ calculateLight( element.v1.positionWorld, element.vertexNormalsModel[ 0 ], _color1 );
+ calculateLight( element.v2.positionWorld, element.vertexNormalsModel[ 1 ], _color2 );
+ calculateLight( element.v3.positionWorld, element.vertexNormalsModel[ 2 ], _color3 );
+
+ _color1.multiply( _diffuseColor ).add( _emissiveColor );
+ _color2.multiply( _diffuseColor ).add( _emissiveColor );
+ _color3.multiply( _diffuseColor ).add( _emissiveColor );
+ _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+ _image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+ clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+ } else {
+
+ _color.copy( _ambientLight );
+
+ calculateLight( element.centroidModel, element.normalModel, _color );
+
+ _color.multiply( _diffuseColor ).add( _emissiveColor );
+
+ material.wireframe === true
+ ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+ : fillPath( _color );
+
+ }
+
+ } else if ( material instanceof THREE.MeshBasicMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) {
+
+ if ( material.map !== null ) {
+
+ if ( material.map.mapping instanceof THREE.UVMapping ) {
+
+ _uvs = element.uvs[ 0 ];
+ patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[ uv1 ].x, _uvs[ uv1 ].y, _uvs[ uv2 ].x, _uvs[ uv2 ].y, _uvs[ uv3 ].x, _uvs[ uv3 ].y, material.map );
+
+ }
+
+
+ } else if ( material.envMap !== null ) {
+
+ if ( material.envMap.mapping instanceof THREE.SphericalReflectionMapping ) {
+
+ _vector3.copy( element.vertexNormalsModelView[ uv1 ] );
+ _uv1x = 0.5 * _vector3.x + 0.5;
+ _uv1y = 0.5 * _vector3.y + 0.5;
+
+ _vector3.copy( element.vertexNormalsModelView[ uv2 ] );
+ _uv2x = 0.5 * _vector3.x + 0.5;
+ _uv2y = 0.5 * _vector3.y + 0.5;
+
+ _vector3.copy( element.vertexNormalsModelView[ uv3 ] );
+ _uv3x = 0.5 * _vector3.x + 0.5;
+ _uv3y = 0.5 * _vector3.y + 0.5;
+
+ patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap );
+
+ }/* else if ( material.envMap.mapping == THREE.SphericalRefractionMapping ) {
+
+
+
+ }*/
+
+
+ } else {
+
+ _color.copy( material.color );
+
+ if ( material.vertexColors === THREE.FaceColors ) {
+
+ _color.multiply( element.color );
+
+ }
+
+ material.wireframe === true
+ ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+ : fillPath( _color );
+
+ }
+
+ } else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+ _near = _camera.near;
+ _far = _camera.far;
+
+ _color1.r = _color1.g = _color1.b = 1 - smoothstep( v1.positionScreen.z * v1.positionScreen.w, _near, _far );
+ _color2.r = _color2.g = _color2.b = 1 - smoothstep( v2.positionScreen.z * v2.positionScreen.w, _near, _far );
+ _color3.r = _color3.g = _color3.b = 1 - smoothstep( v3.positionScreen.z * v3.positionScreen.w, _near, _far );
+ _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+ _image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+ clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+ } else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+ var normal;
+
+ if ( material.shading == THREE.FlatShading ) {
+
+ normal = element.normalModelView;
+
+ _color.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+ material.wireframe === true
+ ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+ : fillPath( _color );
+
+ } else if ( material.shading == THREE.SmoothShading ) {
+
+ normal = element.vertexNormalsModelView[ uv1 ];
+ _color1.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+ normal = element.vertexNormalsModelView[ uv2 ];
+ _color2.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+ normal = element.vertexNormalsModelView[ uv3 ];
+ _color3.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+ _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+ _image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+ clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+ }
+
+ }
+
+ }
+
+ //
+
+ function drawTriangle( x0, y0, x1, y1, x2, y2 ) {
+
+ _context.beginPath();
+ _context.moveTo( x0, y0 );
+ _context.lineTo( x1, y1 );
+ _context.lineTo( x2, y2 );
+ _context.closePath();
+
+ }
+
+ function strokePath( color, linewidth, linecap, linejoin ) {
+
+ setLineWidth( linewidth );
+ setLineCap( linecap );
+ setLineJoin( linejoin );
+ setStrokeStyle( color.getStyle() );
+
+ _context.stroke();
+
+ _elemBox.expandByScalar( linewidth * 2 );
+
+ }
+
+ function fillPath( color ) {
+
+ setFillStyle( color.getStyle() );
+ _context.fill();
+
+ }
+
+ function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) {
+
+ if ( texture instanceof THREE.DataTexture || texture.image === undefined || texture.image.width == 0 ) return;
+
+ if ( texture.needsUpdate === true ) {
+
+ var repeatX = texture.wrapS == THREE.RepeatWrapping;
+ var repeatY = texture.wrapT == THREE.RepeatWrapping;
+
+ _patterns[ texture.id ] = _context.createPattern(
+ texture.image, repeatX === true && repeatY === true
+ ? 'repeat'
+ : repeatX === true && repeatY === false
+ ? 'repeat-x'
+ : repeatX === false && repeatY === true
+ ? 'repeat-y'
+ : 'no-repeat'
+ );
+
+ texture.needsUpdate = false;
+
+ }
+
+ _patterns[ texture.id ] === undefined
+ ? setFillStyle( 'rgba(0,0,0,1)' )
+ : setFillStyle( _patterns[ texture.id ] );
+
+ // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+
+ var a, b, c, d, e, f, det, idet,
+ offsetX = texture.offset.x / texture.repeat.x,
+ offsetY = texture.offset.y / texture.repeat.y,
+ width = texture.image.width * texture.repeat.x,
+ height = texture.image.height * texture.repeat.y;
+
+ u0 = ( u0 + offsetX ) * width;
+ v0 = ( 1.0 - v0 + offsetY ) * height;
+
+ u1 = ( u1 + offsetX ) * width;
+ v1 = ( 1.0 - v1 + offsetY ) * height;
+
+ u2 = ( u2 + offsetX ) * width;
+ v2 = ( 1.0 - v2 + offsetY ) * height;
+
+ x1 -= x0; y1 -= y0;
+ x2 -= x0; y2 -= y0;
+
+ u1 -= u0; v1 -= v0;
+ u2 -= u0; v2 -= v0;
+
+ det = u1 * v2 - u2 * v1;
+
+ if ( det === 0 ) {
+
+ if ( _imagedatas[ texture.id ] === undefined ) {
+
+ var canvas = document.createElement( 'canvas' )
+ canvas.width = texture.image.width;
+ canvas.height = texture.image.height;
+
+ var context = canvas.getContext( '2d' );
+ context.drawImage( texture.image, 0, 0 );
+
+ _imagedatas[ texture.id ] = context.getImageData( 0, 0, texture.image.width, texture.image.height ).data;
+
+ }
+
+ var data = _imagedatas[ texture.id ];
+ var index = ( Math.floor( u0 ) + Math.floor( v0 ) * texture.image.width ) * 4;
+
+ _color.setRGB( data[ index ] / 255, data[ index + 1 ] / 255, data[ index + 2 ] / 255 );
+ fillPath( _color );
+
+ return;
+
+ }
+
+ idet = 1 / det;
+
+ a = ( v2 * x1 - v1 * x2 ) * idet;
+ b = ( v2 * y1 - v1 * y2 ) * idet;
+ c = ( u1 * x2 - u2 * x1 ) * idet;
+ d = ( u1 * y2 - u2 * y1 ) * idet;
+
+ e = x0 - a * u0 - c * v0;
+ f = y0 - b * u0 - d * v0;
+
+ _context.save();
+ _context.transform( a, b, c, d, e, f );
+ _context.fill();
+ _context.restore();
+
+ }
+
+ function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {
+
+ // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+
+ var a, b, c, d, e, f, det, idet,
+ width = image.width - 1,
+ height = image.height - 1;
+
+ u0 *= width; v0 *= height;
+ u1 *= width; v1 *= height;
+ u2 *= width; v2 *= height;
+
+ x1 -= x0; y1 -= y0;
+ x2 -= x0; y2 -= y0;
+
+ u1 -= u0; v1 -= v0;
+ u2 -= u0; v2 -= v0;
+
+ det = u1 * v2 - u2 * v1;
+
+ idet = 1 / det;
+
+ a = ( v2 * x1 - v1 * x2 ) * idet;
+ b = ( v2 * y1 - v1 * y2 ) * idet;
+ c = ( u1 * x2 - u2 * x1 ) * idet;
+ d = ( u1 * y2 - u2 * y1 ) * idet;
+
+ e = x0 - a * u0 - c * v0;
+ f = y0 - b * u0 - d * v0;
+
+ _context.save();
+ _context.transform( a, b, c, d, e, f );
+ _context.clip();
+ _context.drawImage( image, 0, 0 );
+ _context.restore();
+
+ }
+
+ function getGradientTexture( color1, color2, color3, color4 ) {
+
+ // http://mrdoob.com/blog/post/710
+
+ _pixelMapData[ 0 ] = ( color1.r * 255 ) | 0;
+ _pixelMapData[ 1 ] = ( color1.g * 255 ) | 0;
+ _pixelMapData[ 2 ] = ( color1.b * 255 ) | 0;
+
+ _pixelMapData[ 4 ] = ( color2.r * 255 ) | 0;
+ _pixelMapData[ 5 ] = ( color2.g * 255 ) | 0;
+ _pixelMapData[ 6 ] = ( color2.b * 255 ) | 0;
+
+ _pixelMapData[ 8 ] = ( color3.r * 255 ) | 0;
+ _pixelMapData[ 9 ] = ( color3.g * 255 ) | 0;
+ _pixelMapData[ 10 ] = ( color3.b * 255 ) | 0;
+
+ _pixelMapData[ 12 ] = ( color4.r * 255 ) | 0;
+ _pixelMapData[ 13 ] = ( color4.g * 255 ) | 0;
+ _pixelMapData[ 14 ] = ( color4.b * 255 ) | 0;
+
+ _pixelMapContext.putImageData( _pixelMapImage, 0, 0 );
+ _gradientMapContext.drawImage( _pixelMap, 0, 0 );
+
+ return _gradientMap;
+
+ }
+
+ // Hide anti-alias gaps
+
+ function expand( v1, v2, pixels ) {
+
+ var x = v2.x - v1.x, y = v2.y - v1.y,
+ det = x * x + y * y, idet;
+
+ if ( det === 0 ) return;
+
+ idet = pixels / Math.sqrt( det );
+
+ x *= idet; y *= idet;
+
+ v2.x += x; v2.y += y;
+ v1.x -= x; v1.y -= y;
+
+ }
+
+ // Context cached methods.
+
+ function setOpacity( value ) {
+
+ if ( _contextGlobalAlpha !== value ) {
+
+ _context.globalAlpha = value;
+ _contextGlobalAlpha = value;
+
+ }
+
+ }
+
+ function setBlending( value ) {
+
+ if ( _contextGlobalCompositeOperation !== value ) {
+
+ if ( value === THREE.NormalBlending ) {
+
+ _context.globalCompositeOperation = 'source-over';
+
+ } else if ( value === THREE.AdditiveBlending ) {
+
+ _context.globalCompositeOperation = 'lighter';
+
+ } else if ( value === THREE.SubtractiveBlending ) {
+
+ _context.globalCompositeOperation = 'darker';
+
+ }
+
+ _contextGlobalCompositeOperation = value;
+
+ }
+
+ }
+
+ function setLineWidth( value ) {
+
+ if ( _contextLineWidth !== value ) {
+
+ _context.lineWidth = value;
+ _contextLineWidth = value;
+
+ }
+
+ }
+
+ function setLineCap( value ) {
+
+ // "butt", "round", "square"
+
+ if ( _contextLineCap !== value ) {
+
+ _context.lineCap = value;
+ _contextLineCap = value;
+
+ }
+
+ }
+
+ function setLineJoin( value ) {
+
+ // "round", "bevel", "miter"
+
+ if ( _contextLineJoin !== value ) {
+
+ _context.lineJoin = value;
+ _contextLineJoin = value;
+
+ }
+
+ }
+
+ function setStrokeStyle( value ) {
+
+ if ( _contextStrokeStyle !== value ) {
+
+ _context.strokeStyle = value;
+ _contextStrokeStyle = value;
+
+ }
+
+ }
+
+ function setFillStyle( value ) {
+
+ if ( _contextFillStyle !== value ) {
+
+ _context.fillStyle = value;
+ _contextFillStyle = value;
+
+ }
+
+ }
+
+ function setDashAndGap( dashSizeValue, gapSizeValue ) {
+
+ if ( _contextDashSize !== dashSizeValue || _contextGapSize !== gapSizeValue ) {
+
+ _context.setLineDash( [ dashSizeValue, gapSizeValue ] );
+ _contextDashSize = dashSizeValue;
+ _contextGapSize = gapSizeValue;
+
+ }
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+THREE.ShaderChunk = {
+
+ // FOG
+
+ fog_pars_fragment: [
+
+ "#ifdef USE_FOG",
+
+ "uniform vec3 fogColor;",
+
+ "#ifdef FOG_EXP2",
+
+ "uniform float fogDensity;",
+
+ "#else",
+
+ "uniform float fogNear;",
+ "uniform float fogFar;",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ fog_fragment: [
+
+ "#ifdef USE_FOG",
+
+ "float depth = gl_FragCoord.z / gl_FragCoord.w;",
+
+ "#ifdef FOG_EXP2",
+
+ "const float LOG2 = 1.442695;",
+ "float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );",
+ "fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );",
+
+ "#else",
+
+ "float fogFactor = smoothstep( fogNear, fogFar, depth );",
+
+ "#endif",
+
+ "gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // ENVIRONMENT MAP
+
+ envmap_pars_fragment: [
+
+ "#ifdef USE_ENVMAP",
+
+ "uniform float reflectivity;",
+ "uniform samplerCube envMap;",
+ "uniform float flipEnvMap;",
+ "uniform int combine;",
+
+ "#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )",
+
+ "uniform bool useRefract;",
+ "uniform float refractionRatio;",
+
+ "#else",
+
+ "varying vec3 vReflect;",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ envmap_fragment: [
+
+ "#ifdef USE_ENVMAP",
+
+ "vec3 reflectVec;",
+
+ "#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )",
+
+ "vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+ "if ( useRefract ) {",
+
+ "reflectVec = refract( cameraToVertex, normal, refractionRatio );",
+
+ "} else { ",
+
+ "reflectVec = reflect( cameraToVertex, normal );",
+
+ "}",
+
+ "#else",
+
+ "reflectVec = vReflect;",
+
+ "#endif",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "float flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );",
+ "vec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );",
+
+ "#else",
+
+ "vec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );",
+
+ "#endif",
+
+ "#ifdef GAMMA_INPUT",
+
+ "cubeColor.xyz *= cubeColor.xyz;",
+
+ "#endif",
+
+ "if ( combine == 1 ) {",
+
+ "gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );",
+
+ "} else if ( combine == 2 ) {",
+
+ "gl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;",
+
+ "} else {",
+
+ "gl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ envmap_pars_vertex: [
+
+ "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )",
+
+ "varying vec3 vReflect;",
+
+ "uniform float refractionRatio;",
+ "uniform bool useRefract;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ worldpos_vertex : [
+
+ "#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )",
+
+ "#ifdef USE_SKINNING",
+
+ "vec4 worldPosition = modelMatrix * skinned;",
+
+ "#endif",
+
+ "#if defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )",
+
+ "vec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );",
+
+ "#endif",
+
+ "#if ! defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )",
+
+ "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ envmap_vertex : [
+
+ "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )",
+
+ "vec3 worldNormal = mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * objectNormal;",
+ "worldNormal = normalize( worldNormal );",
+
+ "vec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );",
+
+ "if ( useRefract ) {",
+
+ "vReflect = refract( cameraToVertex, worldNormal, refractionRatio );",
+
+ "} else {",
+
+ "vReflect = reflect( cameraToVertex, worldNormal );",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // COLOR MAP (particles)
+
+ map_particle_pars_fragment: [
+
+ "#ifdef USE_MAP",
+
+ "uniform sampler2D map;",
+
+ "#endif"
+
+ ].join("\n"),
+
+
+ map_particle_fragment: [
+
+ "#ifdef USE_MAP",
+
+ "gl_FragColor = gl_FragColor * texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // COLOR MAP (triangles)
+
+ map_pars_vertex: [
+
+ "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+ "varying vec2 vUv;",
+ "uniform vec4 offsetRepeat;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ map_pars_fragment: [
+
+ "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+ "varying vec2 vUv;",
+
+ "#endif",
+
+ "#ifdef USE_MAP",
+
+ "uniform sampler2D map;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ map_vertex: [
+
+ "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+ "vUv = uv * offsetRepeat.zw + offsetRepeat.xy;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ map_fragment: [
+
+ "#ifdef USE_MAP",
+
+ "vec4 texelColor = texture2D( map, vUv );",
+
+ "#ifdef GAMMA_INPUT",
+
+ "texelColor.xyz *= texelColor.xyz;",
+
+ "#endif",
+
+ "gl_FragColor = gl_FragColor * texelColor;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // LIGHT MAP
+
+ lightmap_pars_fragment: [
+
+ "#ifdef USE_LIGHTMAP",
+
+ "varying vec2 vUv2;",
+ "uniform sampler2D lightMap;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lightmap_pars_vertex: [
+
+ "#ifdef USE_LIGHTMAP",
+
+ "varying vec2 vUv2;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lightmap_fragment: [
+
+ "#ifdef USE_LIGHTMAP",
+
+ "gl_FragColor = gl_FragColor * texture2D( lightMap, vUv2 );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lightmap_vertex: [
+
+ "#ifdef USE_LIGHTMAP",
+
+ "vUv2 = uv2;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // BUMP MAP
+
+ bumpmap_pars_fragment: [
+
+ "#ifdef USE_BUMPMAP",
+
+ "uniform sampler2D bumpMap;",
+ "uniform float bumpScale;",
+
+ // Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen
+ // http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html
+
+ // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)
+
+ "vec2 dHdxy_fwd() {",
+
+ "vec2 dSTdx = dFdx( vUv );",
+ "vec2 dSTdy = dFdy( vUv );",
+
+ "float Hll = bumpScale * texture2D( bumpMap, vUv ).x;",
+ "float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;",
+ "float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;",
+
+ "return vec2( dBx, dBy );",
+
+ "}",
+
+ "vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {",
+
+ "vec3 vSigmaX = dFdx( surf_pos );",
+ "vec3 vSigmaY = dFdy( surf_pos );",
+ "vec3 vN = surf_norm;", // normalized
+
+ "vec3 R1 = cross( vSigmaY, vN );",
+ "vec3 R2 = cross( vN, vSigmaX );",
+
+ "float fDet = dot( vSigmaX, R1 );",
+
+ "vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );",
+ "return normalize( abs( fDet ) * surf_norm - vGrad );",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // NORMAL MAP
+
+ normalmap_pars_fragment: [
+
+ "#ifdef USE_NORMALMAP",
+
+ "uniform sampler2D normalMap;",
+ "uniform vec2 normalScale;",
+
+ // Per-Pixel Tangent Space Normal Mapping
+ // http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html
+
+ "vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {",
+
+ "vec3 q0 = dFdx( eye_pos.xyz );",
+ "vec3 q1 = dFdy( eye_pos.xyz );",
+ "vec2 st0 = dFdx( vUv.st );",
+ "vec2 st1 = dFdy( vUv.st );",
+
+ "vec3 S = normalize( q0 * st1.t - q1 * st0.t );",
+ "vec3 T = normalize( -q0 * st1.s + q1 * st0.s );",
+ "vec3 N = normalize( surf_norm );",
+
+ "vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;",
+ "mapN.xy = normalScale * mapN.xy;",
+ "mat3 tsn = mat3( S, T, N );",
+ "return normalize( tsn * mapN );",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // SPECULAR MAP
+
+ specularmap_pars_fragment: [
+
+ "#ifdef USE_SPECULARMAP",
+
+ "uniform sampler2D specularMap;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ specularmap_fragment: [
+
+ "float specularStrength;",
+
+ "#ifdef USE_SPECULARMAP",
+
+ "vec4 texelSpecular = texture2D( specularMap, vUv );",
+ "specularStrength = texelSpecular.r;",
+
+ "#else",
+
+ "specularStrength = 1.0;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // LIGHTS LAMBERT
+
+ lights_lambert_pars_vertex: [
+
+ "uniform vec3 ambient;",
+ "uniform vec3 diffuse;",
+ "uniform vec3 emissive;",
+
+ "uniform vec3 ambientLightColor;",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+ "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+ "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+ "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "uniform vec3 wrapRGB;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lights_lambert_vertex: [
+
+ "vLightFront = vec3( 0.0 );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack = vec3( 0.0 );",
+
+ "#endif",
+
+ "transformedNormal = normalize( transformedNormal );",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+ "vec3 dirVector = normalize( lDirection.xyz );",
+
+ "float dotProduct = dot( transformedNormal, dirVector );",
+ "vec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+ "#endif",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+ "directionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "directionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );",
+
+ "#endif",
+
+ "#endif",
+
+ "vLightFront += directionalLightColor[ i ] * directionalLightWeighting;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( pointLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+ "lVector = normalize( lVector );",
+ "float dotProduct = dot( transformedNormal, lVector );",
+
+ "vec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+ "#endif",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+ "pointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "pointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );",
+
+ "#endif",
+
+ "#endif",
+
+ "vLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+ "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );",
+
+ "if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+ "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+ "float lDistance = 1.0;",
+ "if ( spotLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+ "lVector = normalize( lVector );",
+
+ "float dotProduct = dot( transformedNormal, lVector );",
+ "vec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+ "#endif",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+ "spotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "spotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );",
+
+ "#endif",
+
+ "#endif",
+
+ "vLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;",
+
+ "#endif",
+
+ "}",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+ "vec3 lVector = normalize( lDirection.xyz );",
+
+ "float dotProduct = dot( transformedNormal, lVector );",
+
+ "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+ "float hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;",
+
+ "vLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "vLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // LIGHTS PHONG
+
+ lights_phong_pars_vertex: [
+
+ "#ifndef PHONG_PER_PIXEL",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+ "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+ "varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+ "varying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];",
+
+ "#endif",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+ "varying vec3 vWorldPosition;",
+
+ "#endif"
+
+ ].join("\n"),
+
+
+ lights_phong_vertex: [
+
+ "#ifndef PHONG_PER_PIXEL",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( pointLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+ "vPointLight[ i ] = vec4( lVector, lDistance );",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( spotLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+ "vSpotLight[ i ] = vec4( lVector, lDistance );",
+
+ "}",
+
+ "#endif",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+ "vWorldPosition = worldPosition.xyz;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lights_phong_pars_fragment: [
+
+ "uniform vec3 ambientLightColor;",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+ "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+
+ "#ifdef PHONG_PER_PIXEL",
+
+ "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+ "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+ "#else",
+
+ "varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+
+ "#endif",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+
+ "#ifdef PHONG_PER_PIXEL",
+
+ "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+ "#else",
+
+ "varying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];",
+
+ "#endif",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+ "varying vec3 vWorldPosition;",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "uniform vec3 wrapRGB;",
+
+ "#endif",
+
+ "varying vec3 vViewPosition;",
+ "varying vec3 vNormal;"
+
+ ].join("\n"),
+
+ lights_phong_fragment: [
+
+ "vec3 normal = normalize( vNormal );",
+ "vec3 viewPosition = normalize( vViewPosition );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );",
+
+ "#endif",
+
+ "#ifdef USE_NORMALMAP",
+
+ "normal = perturbNormal2Arb( -vViewPosition, normal );",
+
+ "#elif defined( USE_BUMPMAP )",
+
+ "normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "vec3 pointDiffuse = vec3( 0.0 );",
+ "vec3 pointSpecular = vec3( 0.0 );",
+
+ "for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+ "#ifdef PHONG_PER_PIXEL",
+
+ "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz + vViewPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( pointLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+ "lVector = normalize( lVector );",
+
+ "#else",
+
+ "vec3 lVector = normalize( vPointLight[ i ].xyz );",
+ "float lDistance = vPointLight[ i ].w;",
+
+ "#endif",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, lVector );",
+
+ "#ifdef WRAP_AROUND",
+
+ "float pointDiffuseWeightFull = max( dotProduct, 0.0 );",
+ "float pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+ "vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float pointDiffuseWeight = max( dotProduct, 0.0 );",
+
+ "#endif",
+
+ "pointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;",
+
+ // specular
+
+ "vec3 pointHalfVector = normalize( lVector + viewPosition );",
+ "float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+ "float pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, pointHalfVector ), 5.0 );",
+ "pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;",
+
+ "#else",
+
+ "pointSpecular += specular * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "vec3 spotDiffuse = vec3( 0.0 );",
+ "vec3 spotSpecular = vec3( 0.0 );",
+
+ "for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+ "#ifdef PHONG_PER_PIXEL",
+
+ "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz + vViewPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( spotLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+ "lVector = normalize( lVector );",
+
+ "#else",
+
+ "vec3 lVector = normalize( vSpotLight[ i ].xyz );",
+ "float lDistance = vSpotLight[ i ].w;",
+
+ "#endif",
+
+ "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );",
+
+ "if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+ "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, lVector );",
+
+ "#ifdef WRAP_AROUND",
+
+ "float spotDiffuseWeightFull = max( dotProduct, 0.0 );",
+ "float spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+ "vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float spotDiffuseWeight = max( dotProduct, 0.0 );",
+
+ "#endif",
+
+ "spotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;",
+
+ // specular
+
+ "vec3 spotHalfVector = normalize( lVector + viewPosition );",
+ "float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );",
+ "float spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, spotHalfVector ), 5.0 );",
+ "spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;",
+
+ "#else",
+
+ "spotSpecular += specular * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * spotEffect;",
+
+ "#endif",
+
+ "}",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "vec3 dirDiffuse = vec3( 0.0 );",
+ "vec3 dirSpecular = vec3( 0.0 );" ,
+
+ "for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+ "vec3 dirVector = normalize( lDirection.xyz );",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, dirVector );",
+
+ "#ifdef WRAP_AROUND",
+
+ "float dirDiffuseWeightFull = max( dotProduct, 0.0 );",
+ "float dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+ "vec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float dirDiffuseWeight = max( dotProduct, 0.0 );",
+
+ "#endif",
+
+ "dirDiffuse += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;",
+
+ // specular
+
+ "vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+ "float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+ "float dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ /*
+ // fresnel term from skin shader
+ "const float F0 = 0.128;",
+
+ "float base = 1.0 - dot( viewPosition, dirHalfVector );",
+ "float exponential = pow( base, 5.0 );",
+
+ "float fresnel = exponential + F0 * ( 1.0 - exponential );",
+ */
+
+ /*
+ // fresnel term from fresnel shader
+ "const float mFresnelBias = 0.08;",
+ "const float mFresnelScale = 0.3;",
+ "const float mFresnelPower = 5.0;",
+
+ "float fresnel = mFresnelBias + mFresnelScale * pow( 1.0 + dot( normalize( -viewPosition ), normal ), mFresnelPower );",
+ */
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+ //"dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization * fresnel;",
+
+ "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );",
+ "dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;",
+
+ "#else",
+
+ "dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "vec3 hemiDiffuse = vec3( 0.0 );",
+ "vec3 hemiSpecular = vec3( 0.0 );" ,
+
+ "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+ "vec3 lVector = normalize( lDirection.xyz );",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, lVector );",
+ "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+
+ "vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+ "hemiDiffuse += diffuse * hemiColor;",
+
+ // specular (sky light)
+
+ "vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );",
+ "float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;",
+ "float hemiSpecularWeightSky = specularStrength * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );",
+
+ // specular (ground light)
+
+ "vec3 lVectorGround = -lVector;",
+
+ "vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );",
+ "float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
+ "float hemiSpecularWeightGround = specularStrength * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ "float dotProductGround = dot( normal, lVectorGround );",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );",
+ "vec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );",
+ "hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );",
+
+ "#else",
+
+ "hemiSpecular += specular * hemiColor * ( hemiSpecularWeightSky + hemiSpecularWeightGround ) * hemiDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "vec3 totalDiffuse = vec3( 0.0 );",
+ "vec3 totalSpecular = vec3( 0.0 );",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "totalDiffuse += dirDiffuse;",
+ "totalSpecular += dirSpecular;",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "totalDiffuse += hemiDiffuse;",
+ "totalSpecular += hemiSpecular;",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "totalDiffuse += pointDiffuse;",
+ "totalSpecular += pointSpecular;",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "totalDiffuse += spotDiffuse;",
+ "totalSpecular += spotSpecular;",
+
+ "#endif",
+
+ "#ifdef METAL",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );",
+
+ "#else",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // VERTEX COLORS
+
+ color_pars_fragment: [
+
+ "#ifdef USE_COLOR",
+
+ "varying vec3 vColor;",
+
+ "#endif"
+
+ ].join("\n"),
+
+
+ color_fragment: [
+
+ "#ifdef USE_COLOR",
+
+ "gl_FragColor = gl_FragColor * vec4( vColor, opacity );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ color_pars_vertex: [
+
+ "#ifdef USE_COLOR",
+
+ "varying vec3 vColor;",
+
+ "#endif"
+
+ ].join("\n"),
+
+
+ color_vertex: [
+
+ "#ifdef USE_COLOR",
+
+ "#ifdef GAMMA_INPUT",
+
+ "vColor = color * color;",
+
+ "#else",
+
+ "vColor = color;",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // SKINNING
+
+ skinning_pars_vertex: [
+
+ "#ifdef USE_SKINNING",
+
+ "#ifdef BONE_TEXTURE",
+
+ "uniform sampler2D boneTexture;",
+ "uniform int boneTextureWidth;",
+ "uniform int boneTextureHeight;",
+
+ "mat4 getBoneMatrix( const in float i ) {",
+
+ "float j = i * 4.0;",
+ "float x = mod( j, float( boneTextureWidth ) );",
+ "float y = floor( j / float( boneTextureWidth ) );",
+
+ "float dx = 1.0 / float( boneTextureWidth );",
+ "float dy = 1.0 / float( boneTextureHeight );",
+
+ "y = dy * ( y + 0.5 );",
+
+ "vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );",
+ "vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );",
+ "vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );",
+ "vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );",
+
+ "mat4 bone = mat4( v1, v2, v3, v4 );",
+
+ "return bone;",
+
+ "}",
+
+ "#else",
+
+ "uniform mat4 boneGlobalMatrices[ MAX_BONES ];",
+
+ "mat4 getBoneMatrix( const in float i ) {",
+
+ "mat4 bone = boneGlobalMatrices[ int(i) ];",
+ "return bone;",
+
+ "}",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ skinbase_vertex: [
+
+ "#ifdef USE_SKINNING",
+
+ "mat4 boneMatX = getBoneMatrix( skinIndex.x );",
+ "mat4 boneMatY = getBoneMatrix( skinIndex.y );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ skinning_vertex: [
+
+ "#ifdef USE_SKINNING",
+
+ "#ifdef USE_MORPHTARGETS",
+
+ "vec4 skinVertex = vec4( morphed, 1.0 );",
+
+ "#else",
+
+ "vec4 skinVertex = vec4( position, 1.0 );",
+
+ "#endif",
+
+ "vec4 skinned = boneMatX * skinVertex * skinWeight.x;",
+ "skinned += boneMatY * skinVertex * skinWeight.y;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // MORPHING
+
+ morphtarget_pars_vertex: [
+
+ "#ifdef USE_MORPHTARGETS",
+
+ "#ifndef USE_MORPHNORMALS",
+
+ "uniform float morphTargetInfluences[ 8 ];",
+
+ "#else",
+
+ "uniform float morphTargetInfluences[ 4 ];",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ morphtarget_vertex: [
+
+ "#ifdef USE_MORPHTARGETS",
+
+ "vec3 morphed = vec3( 0.0 );",
+ "morphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];",
+ "morphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];",
+ "morphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];",
+ "morphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];",
+
+ "#ifndef USE_MORPHNORMALS",
+
+ "morphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];",
+ "morphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];",
+ "morphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];",
+ "morphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];",
+
+ "#endif",
+
+ "morphed += position;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ default_vertex : [
+
+ "vec4 mvPosition;",
+
+ "#ifdef USE_SKINNING",
+
+ "mvPosition = modelViewMatrix * skinned;",
+
+ "#endif",
+
+ "#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )",
+
+ "mvPosition = modelViewMatrix * vec4( morphed, 1.0 );",
+
+ "#endif",
+
+ "#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )",
+
+ "mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+ "#endif",
+
+ "gl_Position = projectionMatrix * mvPosition;"
+
+ ].join("\n"),
+
+ morphnormal_vertex: [
+
+ "#ifdef USE_MORPHNORMALS",
+
+ "vec3 morphedNormal = vec3( 0.0 );",
+
+ "morphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];",
+ "morphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];",
+ "morphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];",
+ "morphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];",
+
+ "morphedNormal += normal;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ skinnormal_vertex: [
+
+ "#ifdef USE_SKINNING",
+
+ "mat4 skinMatrix = skinWeight.x * boneMatX;",
+ "skinMatrix += skinWeight.y * boneMatY;",
+
+ "#ifdef USE_MORPHNORMALS",
+
+ "vec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );",
+
+ "#else",
+
+ "vec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ defaultnormal_vertex: [
+
+ "vec3 objectNormal;",
+
+ "#ifdef USE_SKINNING",
+
+ "objectNormal = skinnedNormal.xyz;",
+
+ "#endif",
+
+ "#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )",
+
+ "objectNormal = morphedNormal;",
+
+ "#endif",
+
+ "#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )",
+
+ "objectNormal = normal;",
+
+ "#endif",
+
+ "#ifdef FLIP_SIDED",
+
+ "objectNormal = -objectNormal;",
+
+ "#endif",
+
+ "vec3 transformedNormal = normalMatrix * objectNormal;"
+
+ ].join("\n"),
+
+ // SHADOW MAP
+
+ // based on SpiderGL shadow map and Fabien Sanglard's GLSL shadow mapping examples
+ // http://spidergl.org/example.php?id=6
+ // http://fabiensanglard.net/shadowmapping
+
+ shadowmap_pars_fragment: [
+
+ "#ifdef USE_SHADOWMAP",
+
+ "uniform sampler2D shadowMap[ MAX_SHADOWS ];",
+ "uniform vec2 shadowMapSize[ MAX_SHADOWS ];",
+
+ "uniform float shadowDarkness[ MAX_SHADOWS ];",
+ "uniform float shadowBias[ MAX_SHADOWS ];",
+
+ "varying vec4 vShadowCoord[ MAX_SHADOWS ];",
+
+ "float unpackDepth( const in vec4 rgba_depth ) {",
+
+ "const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );",
+ "float depth = dot( rgba_depth, bit_shift );",
+ "return depth;",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ shadowmap_fragment: [
+
+ "#ifdef USE_SHADOWMAP",
+
+ "#ifdef SHADOWMAP_DEBUG",
+
+ "vec3 frustumColors[3];",
+ "frustumColors[0] = vec3( 1.0, 0.5, 0.0 );",
+ "frustumColors[1] = vec3( 0.0, 1.0, 0.8 );",
+ "frustumColors[2] = vec3( 0.0, 0.5, 1.0 );",
+
+ "#endif",
+
+ "#ifdef SHADOWMAP_CASCADE",
+
+ "int inFrustumCount = 0;",
+
+ "#endif",
+
+ "float fDepth;",
+ "vec3 shadowColor = vec3( 1.0 );",
+
+ "for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+ "vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;",
+
+ // "if ( something && something )" breaks ATI OpenGL shader compiler
+ // "if ( all( something, something ) )" using this instead
+
+ "bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );",
+ "bool inFrustum = all( inFrustumVec );",
+
+ // don't shadow pixels outside of light frustum
+ // use just first frustum (for cascades)
+ // don't shadow pixels behind far plane of light frustum
+
+ "#ifdef SHADOWMAP_CASCADE",
+
+ "inFrustumCount += int( inFrustum );",
+ "bvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );",
+
+ "#else",
+
+ "bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );",
+
+ "#endif",
+
+ "bool frustumTest = all( frustumTestVec );",
+
+ "if ( frustumTest ) {",
+
+ "shadowCoord.z += shadowBias[ i ];",
+
+ "#if defined( SHADOWMAP_TYPE_PCF )",
+
+ // Percentage-close filtering
+ // (9 pixel kernel)
+ // http://fabiensanglard.net/shadowmappingPCF/
+
+ "float shadow = 0.0;",
+
+ /*
+ // nested loops breaks shader compiler / validator on some ATI cards when using OpenGL
+ // must enroll loop manually
+
+ "for ( float y = -1.25; y <= 1.25; y += 1.25 )",
+ "for ( float x = -1.25; x <= 1.25; x += 1.25 ) {",
+
+ "vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );",
+
+ // doesn't seem to produce any noticeable visual difference compared to simple "texture2D" lookup
+ //"vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );",
+
+ "float fDepth = unpackDepth( rgbaDepth );",
+
+ "if ( fDepth < shadowCoord.z )",
+ "shadow += 1.0;",
+
+ "}",
+
+ "shadow /= 9.0;",
+
+ */
+
+ "const float shadowDelta = 1.0 / 9.0;",
+
+ "float xPixelOffset = 1.0 / shadowMapSize[ i ].x;",
+ "float yPixelOffset = 1.0 / shadowMapSize[ i ].y;",
+
+ "float dx0 = -1.25 * xPixelOffset;",
+ "float dy0 = -1.25 * yPixelOffset;",
+ "float dx1 = 1.25 * xPixelOffset;",
+ "float dy1 = 1.25 * yPixelOffset;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );",
+
+ "#elif defined( SHADOWMAP_TYPE_PCF_SOFT )",
+
+ // Percentage-close filtering
+ // (9 pixel kernel)
+ // http://fabiensanglard.net/shadowmappingPCF/
+
+ "float shadow = 0.0;",
+
+ "float xPixelOffset = 1.0 / shadowMapSize[ i ].x;",
+ "float yPixelOffset = 1.0 / shadowMapSize[ i ].y;",
+
+ "float dx0 = -1.0 * xPixelOffset;",
+ "float dy0 = -1.0 * yPixelOffset;",
+ "float dx1 = 1.0 * xPixelOffset;",
+ "float dy1 = 1.0 * yPixelOffset;",
+
+ "mat3 shadowKernel;",
+ "mat3 depthKernel;",
+
+ "depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );",
+ "depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );",
+ "depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );",
+ "depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );",
+ "depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );",
+ "depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );",
+ "depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );",
+ "depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );",
+ "depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );",
+
+ "vec3 shadowZ = vec3( shadowCoord.z );",
+ "shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));",
+ "shadowKernel[0] *= vec3(0.25);",
+
+ "shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));",
+ "shadowKernel[1] *= vec3(0.25);",
+
+ "shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));",
+ "shadowKernel[2] *= vec3(0.25);",
+
+ "vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );",
+
+ "shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );",
+ "shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );",
+
+ "vec4 shadowValues;",
+ "shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );",
+ "shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );",
+ "shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );",
+ "shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );",
+
+ "shadow = dot( shadowValues, vec4( 1.0 ) );",
+
+ "shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );",
+
+ "#else",
+
+ "vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );",
+ "float fDepth = unpackDepth( rgbaDepth );",
+
+ "if ( fDepth < shadowCoord.z )",
+
+ // spot with multiple shadows is darker
+
+ "shadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );",
+
+ // spot with multiple shadows has the same color as single shadow spot
+
+ //"shadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );",
+
+ "#endif",
+
+ "}",
+
+
+ "#ifdef SHADOWMAP_DEBUG",
+
+ "#ifdef SHADOWMAP_CASCADE",
+
+ "if ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];",
+
+ "#else",
+
+ "if ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];",
+
+ "#endif",
+
+ "#endif",
+
+ "}",
+
+ "#ifdef GAMMA_OUTPUT",
+
+ "shadowColor *= shadowColor;",
+
+ "#endif",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * shadowColor;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ shadowmap_pars_vertex: [
+
+ "#ifdef USE_SHADOWMAP",
+
+ "varying vec4 vShadowCoord[ MAX_SHADOWS ];",
+ "uniform mat4 shadowMatrix[ MAX_SHADOWS ];",
+
+ "#endif"
+
+ ].join("\n"),
+
+ shadowmap_vertex: [
+
+ "#ifdef USE_SHADOWMAP",
+
+ "for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+ "vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // ALPHATEST
+
+ alphatest_fragment: [
+
+ "#ifdef ALPHATEST",
+
+ "if ( gl_FragColor.a < ALPHATEST ) discard;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // LINEAR SPACE
+
+ linear_to_gamma_fragment: [
+
+ "#ifdef GAMMA_OUTPUT",
+
+ "gl_FragColor.xyz = sqrt( gl_FragColor.xyz );",
+
+ "#endif"
+
+ ].join("\n")
+
+
+};
+
+THREE.UniformsUtils = {
+
+ merge: function ( uniforms ) {
+
+ var u, p, tmp, merged = {};
+
+ for ( u = 0; u < uniforms.length; u ++ ) {
+
+ tmp = this.clone( uniforms[ u ] );
+
+ for ( p in tmp ) {
+
+ merged[ p ] = tmp[ p ];
+
+ }
+
+ }
+
+ return merged;
+
+ },
+
+ clone: function ( uniforms_src ) {
+
+ var u, p, parameter, parameter_src, uniforms_dst = {};
+
+ for ( u in uniforms_src ) {
+
+ uniforms_dst[ u ] = {};
+
+ for ( p in uniforms_src[ u ] ) {
+
+ parameter_src = uniforms_src[ u ][ p ];
+
+ if ( parameter_src instanceof THREE.Color ||
+ parameter_src instanceof THREE.Vector2 ||
+ parameter_src instanceof THREE.Vector3 ||
+ parameter_src instanceof THREE.Vector4 ||
+ parameter_src instanceof THREE.Matrix4 ||
+ parameter_src instanceof THREE.Texture ) {
+
+ uniforms_dst[ u ][ p ] = parameter_src.clone();
+
+ } else if ( parameter_src instanceof Array ) {
+
+ uniforms_dst[ u ][ p ] = parameter_src.slice();
+
+ } else {
+
+ uniforms_dst[ u ][ p ] = parameter_src;
+
+ }
+
+ }
+
+ }
+
+ return uniforms_dst;
+
+ }
+
+};
+
+THREE.UniformsLib = {
+
+ common: {
+
+ "diffuse" : { type: "c", value: new THREE.Color( 0xeeeeee ) },
+ "opacity" : { type: "f", value: 1.0 },
+
+ "map" : { type: "t", value: null },
+ "offsetRepeat" : { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) },
+
+ "lightMap" : { type: "t", value: null },
+ "specularMap" : { type: "t", value: null },
+
+ "envMap" : { type: "t", value: null },
+ "flipEnvMap" : { type: "f", value: -1 },
+ "useRefract" : { type: "i", value: 0 },
+ "reflectivity" : { type: "f", value: 1.0 },
+ "refractionRatio" : { type: "f", value: 0.98 },
+ "combine" : { type: "i", value: 0 },
+
+ "morphTargetInfluences" : { type: "f", value: 0 }
+
+ },
+
+ bump: {
+
+ "bumpMap" : { type: "t", value: null },
+ "bumpScale" : { type: "f", value: 1 }
+
+ },
+
+ normalmap: {
+
+ "normalMap" : { type: "t", value: null },
+ "normalScale" : { type: "v2", value: new THREE.Vector2( 1, 1 ) }
+ },
+
+ fog : {
+
+ "fogDensity" : { type: "f", value: 0.00025 },
+ "fogNear" : { type: "f", value: 1 },
+ "fogFar" : { type: "f", value: 2000 },
+ "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) }
+
+ },
+
+ lights: {
+
+ "ambientLightColor" : { type: "fv", value: [] },
+
+ "directionalLightDirection" : { type: "fv", value: [] },
+ "directionalLightColor" : { type: "fv", value: [] },
+
+ "hemisphereLightDirection" : { type: "fv", value: [] },
+ "hemisphereLightSkyColor" : { type: "fv", value: [] },
+ "hemisphereLightGroundColor" : { type: "fv", value: [] },
+
+ "pointLightColor" : { type: "fv", value: [] },
+ "pointLightPosition" : { type: "fv", value: [] },
+ "pointLightDistance" : { type: "fv1", value: [] },
+
+ "spotLightColor" : { type: "fv", value: [] },
+ "spotLightPosition" : { type: "fv", value: [] },
+ "spotLightDirection" : { type: "fv", value: [] },
+ "spotLightDistance" : { type: "fv1", value: [] },
+ "spotLightAngleCos" : { type: "fv1", value: [] },
+ "spotLightExponent" : { type: "fv1", value: [] }
+
+ },
+
+ particle: {
+
+ "psColor" : { type: "c", value: new THREE.Color( 0xeeeeee ) },
+ "opacity" : { type: "f", value: 1.0 },
+ "size" : { type: "f", value: 1.0 },
+ "scale" : { type: "f", value: 1.0 },
+ "map" : { type: "t", value: null },
+
+ "fogDensity" : { type: "f", value: 0.00025 },
+ "fogNear" : { type: "f", value: 1 },
+ "fogFar" : { type: "f", value: 2000 },
+ "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) }
+
+ },
+
+ shadowmap: {
+
+ "shadowMap": { type: "tv", value: [] },
+ "shadowMapSize": { type: "v2v", value: [] },
+
+ "shadowBias" : { type: "fv1", value: [] },
+ "shadowDarkness": { type: "fv1", value: [] },
+
+ "shadowMatrix" : { type: "m4v", value: [] }
+
+ }
+
+};
+
+THREE.ShaderLib = {
+
+ 'basic': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "common" ],
+ THREE.UniformsLib[ "fog" ],
+ THREE.UniformsLib[ "shadowmap" ]
+
+ ] ),
+
+ vertexShader: [
+
+ THREE.ShaderChunk[ "map_pars_vertex" ],
+ THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+ THREE.ShaderChunk[ "envmap_pars_vertex" ],
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "map_vertex" ],
+ THREE.ShaderChunk[ "lightmap_vertex" ],
+ THREE.ShaderChunk[ "color_vertex" ],
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+
+ "#ifdef USE_ENVMAP",
+
+ THREE.ShaderChunk[ "morphnormal_vertex" ],
+ THREE.ShaderChunk[ "skinnormal_vertex" ],
+ THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+ "#endif",
+
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "skinning_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ THREE.ShaderChunk[ "worldpos_vertex" ],
+ THREE.ShaderChunk[ "envmap_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform vec3 diffuse;",
+ "uniform float opacity;",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "map_pars_fragment" ],
+ THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+ THREE.ShaderChunk[ "envmap_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+ THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( diffuse, opacity );",
+
+ THREE.ShaderChunk[ "map_fragment" ],
+ THREE.ShaderChunk[ "alphatest_fragment" ],
+ THREE.ShaderChunk[ "specularmap_fragment" ],
+ THREE.ShaderChunk[ "lightmap_fragment" ],
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "envmap_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+ THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'lambert': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "common" ],
+ THREE.UniformsLib[ "fog" ],
+ THREE.UniformsLib[ "lights" ],
+ THREE.UniformsLib[ "shadowmap" ],
+
+ {
+ "ambient" : { type: "c", value: new THREE.Color( 0xffffff ) },
+ "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) },
+ "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+ }
+
+ ] ),
+
+ vertexShader: [
+
+ "#define LAMBERT",
+
+ "varying vec3 vLightFront;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "varying vec3 vLightBack;",
+
+ "#endif",
+
+ THREE.ShaderChunk[ "map_pars_vertex" ],
+ THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+ THREE.ShaderChunk[ "envmap_pars_vertex" ],
+ THREE.ShaderChunk[ "lights_lambert_pars_vertex" ],
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "map_vertex" ],
+ THREE.ShaderChunk[ "lightmap_vertex" ],
+ THREE.ShaderChunk[ "color_vertex" ],
+
+ THREE.ShaderChunk[ "morphnormal_vertex" ],
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+ THREE.ShaderChunk[ "skinnormal_vertex" ],
+ THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "skinning_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ THREE.ShaderChunk[ "worldpos_vertex" ],
+ THREE.ShaderChunk[ "envmap_vertex" ],
+ THREE.ShaderChunk[ "lights_lambert_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform float opacity;",
+
+ "varying vec3 vLightFront;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "varying vec3 vLightBack;",
+
+ "#endif",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "map_pars_fragment" ],
+ THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+ THREE.ShaderChunk[ "envmap_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+ THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( vec3 ( 1.0 ), opacity );",
+
+ THREE.ShaderChunk[ "map_fragment" ],
+ THREE.ShaderChunk[ "alphatest_fragment" ],
+ THREE.ShaderChunk[ "specularmap_fragment" ],
+
+ "#ifdef DOUBLE_SIDED",
+
+ //"float isFront = float( gl_FrontFacing );",
+ //"gl_FragColor.xyz *= isFront * vLightFront + ( 1.0 - isFront ) * vLightBack;",
+
+ "if ( gl_FrontFacing )",
+ "gl_FragColor.xyz *= vLightFront;",
+ "else",
+ "gl_FragColor.xyz *= vLightBack;",
+
+ "#else",
+
+ "gl_FragColor.xyz *= vLightFront;",
+
+ "#endif",
+
+ THREE.ShaderChunk[ "lightmap_fragment" ],
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "envmap_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+ THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'phong': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "common" ],
+ THREE.UniformsLib[ "bump" ],
+ THREE.UniformsLib[ "normalmap" ],
+ THREE.UniformsLib[ "fog" ],
+ THREE.UniformsLib[ "lights" ],
+ THREE.UniformsLib[ "shadowmap" ],
+
+ {
+ "ambient" : { type: "c", value: new THREE.Color( 0xffffff ) },
+ "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) },
+ "specular" : { type: "c", value: new THREE.Color( 0x111111 ) },
+ "shininess": { type: "f", value: 30 },
+ "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+ }
+
+ ] ),
+
+ vertexShader: [
+
+ "#define PHONG",
+
+ "varying vec3 vViewPosition;",
+ "varying vec3 vNormal;",
+
+ THREE.ShaderChunk[ "map_pars_vertex" ],
+ THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+ THREE.ShaderChunk[ "envmap_pars_vertex" ],
+ THREE.ShaderChunk[ "lights_phong_pars_vertex" ],
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "map_vertex" ],
+ THREE.ShaderChunk[ "lightmap_vertex" ],
+ THREE.ShaderChunk[ "color_vertex" ],
+
+ THREE.ShaderChunk[ "morphnormal_vertex" ],
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+ THREE.ShaderChunk[ "skinnormal_vertex" ],
+ THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+ "vNormal = normalize( transformedNormal );",
+
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "skinning_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ "vViewPosition = -mvPosition.xyz;",
+
+ THREE.ShaderChunk[ "worldpos_vertex" ],
+ THREE.ShaderChunk[ "envmap_vertex" ],
+ THREE.ShaderChunk[ "lights_phong_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform vec3 diffuse;",
+ "uniform float opacity;",
+
+ "uniform vec3 ambient;",
+ "uniform vec3 emissive;",
+ "uniform vec3 specular;",
+ "uniform float shininess;",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "map_pars_fragment" ],
+ THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+ THREE.ShaderChunk[ "envmap_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+ THREE.ShaderChunk[ "lights_phong_pars_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+ THREE.ShaderChunk[ "bumpmap_pars_fragment" ],
+ THREE.ShaderChunk[ "normalmap_pars_fragment" ],
+ THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( vec3 ( 1.0 ), opacity );",
+
+ THREE.ShaderChunk[ "map_fragment" ],
+ THREE.ShaderChunk[ "alphatest_fragment" ],
+ THREE.ShaderChunk[ "specularmap_fragment" ],
+
+ THREE.ShaderChunk[ "lights_phong_fragment" ],
+
+ THREE.ShaderChunk[ "lightmap_fragment" ],
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "envmap_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+ THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'particle_basic': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "particle" ],
+ THREE.UniformsLib[ "shadowmap" ]
+
+ ] ),
+
+ vertexShader: [
+
+ "uniform float size;",
+ "uniform float scale;",
+
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "color_vertex" ],
+
+ "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+ "#ifdef USE_SIZEATTENUATION",
+ "gl_PointSize = size * ( scale / length( mvPosition.xyz ) );",
+ "#else",
+ "gl_PointSize = size;",
+ "#endif",
+
+ "gl_Position = projectionMatrix * mvPosition;",
+
+ THREE.ShaderChunk[ "worldpos_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform vec3 psColor;",
+ "uniform float opacity;",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "map_particle_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( psColor, opacity );",
+
+ THREE.ShaderChunk[ "map_particle_fragment" ],
+ THREE.ShaderChunk[ "alphatest_fragment" ],
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'dashed': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "common" ],
+ THREE.UniformsLib[ "fog" ],
+
+ {
+ "scale": { type: "f", value: 1 },
+ "dashSize": { type: "f", value: 1 },
+ "totalSize": { type: "f", value: 2 }
+ }
+
+ ] ),
+
+ vertexShader: [
+
+ "uniform float scale;",
+ "attribute float lineDistance;",
+
+ "varying float vLineDistance;",
+
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "color_vertex" ],
+
+ "vLineDistance = scale * lineDistance;",
+
+ "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+ "gl_Position = projectionMatrix * mvPosition;",
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform vec3 diffuse;",
+ "uniform float opacity;",
+
+ "uniform float dashSize;",
+ "uniform float totalSize;",
+
+ "varying float vLineDistance;",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+ "void main() {",
+
+ "if ( mod( vLineDistance, totalSize ) > dashSize ) {",
+
+ "discard;",
+
+ "}",
+
+ "gl_FragColor = vec4( diffuse, opacity );",
+
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'depth': {
+
+ uniforms: {
+
+ "mNear": { type: "f", value: 1.0 },
+ "mFar" : { type: "f", value: 2000.0 },
+ "opacity" : { type: "f", value: 1.0 }
+
+ },
+
+ vertexShader: [
+
+ "void main() {",
+
+ "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform float mNear;",
+ "uniform float mFar;",
+ "uniform float opacity;",
+
+ "void main() {",
+
+ "float depth = gl_FragCoord.z / gl_FragCoord.w;",
+ "float color = 1.0 - smoothstep( mNear, mFar, depth );",
+ "gl_FragColor = vec4( vec3( color ), opacity );",
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'normal': {
+
+ uniforms: {
+
+ "opacity" : { type: "f", value: 1.0 }
+
+ },
+
+ vertexShader: [
+
+ "varying vec3 vNormal;",
+
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+
+ "void main() {",
+
+ "vNormal = normalize( normalMatrix * normal );",
+
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform float opacity;",
+ "varying vec3 vNormal;",
+
+ "void main() {",
+
+ "gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );",
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ /* -------------------------------------------------------------------------
+ // Normal map shader
+ // - Blinn-Phong
+ // - normal + diffuse + specular + AO + displacement + reflection + shadow maps
+ // - point and directional lights (use with "lights: true" material option)
+ ------------------------------------------------------------------------- */
+
+ 'normalmap' : {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "fog" ],
+ THREE.UniformsLib[ "lights" ],
+ THREE.UniformsLib[ "shadowmap" ],
+
+ {
+
+ "enableAO" : { type: "i", value: 0 },
+ "enableDiffuse" : { type: "i", value: 0 },
+ "enableSpecular" : { type: "i", value: 0 },
+ "enableReflection": { type: "i", value: 0 },
+ "enableDisplacement": { type: "i", value: 0 },
+
+ "tDisplacement": { type: "t", value: null }, // must go first as this is vertex texture
+ "tDiffuse" : { type: "t", value: null },
+ "tCube" : { type: "t", value: null },
+ "tNormal" : { type: "t", value: null },
+ "tSpecular" : { type: "t", value: null },
+ "tAO" : { type: "t", value: null },
+
+ "uNormalScale": { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+
+ "uDisplacementBias": { type: "f", value: 0.0 },
+ "uDisplacementScale": { type: "f", value: 1.0 },
+
+ "uDiffuseColor": { type: "c", value: new THREE.Color( 0xffffff ) },
+ "uSpecularColor": { type: "c", value: new THREE.Color( 0x111111 ) },
+ "uAmbientColor": { type: "c", value: new THREE.Color( 0xffffff ) },
+ "uShininess": { type: "f", value: 30 },
+ "uOpacity": { type: "f", value: 1 },
+
+ "useRefract": { type: "i", value: 0 },
+ "uRefractionRatio": { type: "f", value: 0.98 },
+ "uReflectivity": { type: "f", value: 0.5 },
+
+ "uOffset" : { type: "v2", value: new THREE.Vector2( 0, 0 ) },
+ "uRepeat" : { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+
+ "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+
+ }
+
+ ] ),
+
+ fragmentShader: [
+
+ "uniform vec3 uAmbientColor;",
+ "uniform vec3 uDiffuseColor;",
+ "uniform vec3 uSpecularColor;",
+ "uniform float uShininess;",
+ "uniform float uOpacity;",
+
+ "uniform bool enableDiffuse;",
+ "uniform bool enableSpecular;",
+ "uniform bool enableAO;",
+ "uniform bool enableReflection;",
+
+ "uniform sampler2D tDiffuse;",
+ "uniform sampler2D tNormal;",
+ "uniform sampler2D tSpecular;",
+ "uniform sampler2D tAO;",
+
+ "uniform samplerCube tCube;",
+
+ "uniform vec2 uNormalScale;",
+
+ "uniform bool useRefract;",
+ "uniform float uRefractionRatio;",
+ "uniform float uReflectivity;",
+
+ "varying vec3 vTangent;",
+ "varying vec3 vBinormal;",
+ "varying vec3 vNormal;",
+ "varying vec2 vUv;",
+
+ "uniform vec3 ambientLightColor;",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+ "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+ "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+ "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "uniform vec3 wrapRGB;",
+
+ "#endif",
+
+ "varying vec3 vWorldPosition;",
+ "varying vec3 vViewPosition;",
+
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( vec3( 1.0 ), uOpacity );",
+
+ "vec3 specularTex = vec3( 1.0 );",
+
+ "vec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;",
+ "normalTex.xy *= uNormalScale;",
+ "normalTex = normalize( normalTex );",
+
+ "if( enableDiffuse ) {",
+
+ "#ifdef GAMMA_INPUT",
+
+ "vec4 texelColor = texture2D( tDiffuse, vUv );",
+ "texelColor.xyz *= texelColor.xyz;",
+
+ "gl_FragColor = gl_FragColor * texelColor;",
+
+ "#else",
+
+ "gl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );",
+
+ "#endif",
+
+ "}",
+
+ "if( enableAO ) {",
+
+ "#ifdef GAMMA_INPUT",
+
+ "vec4 aoColor = texture2D( tAO, vUv );",
+ "aoColor.xyz *= aoColor.xyz;",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;",
+
+ "#else",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;",
+
+ "#endif",
+
+ "}",
+
+ "if( enableSpecular )",
+ "specularTex = texture2D( tSpecular, vUv ).xyz;",
+
+ "mat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );",
+ "vec3 finalNormal = tsb * normalTex;",
+
+ "#ifdef FLIP_SIDED",
+
+ "finalNormal = -finalNormal;",
+
+ "#endif",
+
+ "vec3 normal = normalize( finalNormal );",
+ "vec3 viewPosition = normalize( vViewPosition );",
+
+ // point lights
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "vec3 pointDiffuse = vec3( 0.0 );",
+ "vec3 pointSpecular = vec3( 0.0 );",
+
+ "for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+ "vec3 pointVector = lPosition.xyz + vViewPosition.xyz;",
+
+ "float pointDistance = 1.0;",
+ "if ( pointLightDistance[ i ] > 0.0 )",
+ "pointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+ "pointVector = normalize( pointVector );",
+
+ // diffuse
+
+ "#ifdef WRAP_AROUND",
+
+ "float pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );",
+ "float pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );",
+
+ "vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );",
+
+ "#endif",
+
+ "pointDiffuse += pointDistance * pointLightColor[ i ] * uDiffuseColor * pointDiffuseWeight;",
+
+ // specular
+
+ "vec3 pointHalfVector = normalize( pointVector + viewPosition );",
+ "float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+ "float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, uShininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( pointVector, pointHalfVector ), 5.0 );",
+ "pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;",
+
+ "#else",
+
+ "pointSpecular += pointDistance * pointLightColor[ i ] * uSpecularColor * pointSpecularWeight * pointDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ // spot lights
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "vec3 spotDiffuse = vec3( 0.0 );",
+ "vec3 spotSpecular = vec3( 0.0 );",
+
+ "for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+ "vec3 spotVector = lPosition.xyz + vViewPosition.xyz;",
+
+ "float spotDistance = 1.0;",
+ "if ( spotLightDistance[ i ] > 0.0 )",
+ "spotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+ "spotVector = normalize( spotVector );",
+
+ "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );",
+
+ "if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+ "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+ // diffuse
+
+ "#ifdef WRAP_AROUND",
+
+ "float spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );",
+ "float spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );",
+
+ "vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );",
+
+ "#endif",
+
+ "spotDiffuse += spotDistance * spotLightColor[ i ] * uDiffuseColor * spotDiffuseWeight * spotEffect;",
+
+ // specular
+
+ "vec3 spotHalfVector = normalize( spotVector + viewPosition );",
+ "float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );",
+ "float spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, uShininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( spotVector, spotHalfVector ), 5.0 );",
+ "spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;",
+
+ "#else",
+
+ "spotSpecular += spotDistance * spotLightColor[ i ] * uSpecularColor * spotSpecularWeight * spotDiffuseWeight * spotEffect;",
+
+ "#endif",
+
+ "}",
+
+ "}",
+
+ "#endif",
+
+ // directional lights
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "vec3 dirDiffuse = vec3( 0.0 );",
+ "vec3 dirSpecular = vec3( 0.0 );",
+
+ "for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+ "vec3 dirVector = normalize( lDirection.xyz );",
+
+ // diffuse
+
+ "#ifdef WRAP_AROUND",
+
+ "float directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );",
+ "float directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );",
+
+ "vec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );",
+
+ "#else",
+
+ "float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );",
+
+ "#endif",
+
+ "dirDiffuse += directionalLightColor[ i ] * uDiffuseColor * dirDiffuseWeight;",
+
+ // specular
+
+ "vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+ "float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+ "float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, uShininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );",
+ "dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;",
+
+ "#else",
+
+ "dirSpecular += directionalLightColor[ i ] * uSpecularColor * dirSpecularWeight * dirDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ // hemisphere lights
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "vec3 hemiDiffuse = vec3( 0.0 );",
+ "vec3 hemiSpecular = vec3( 0.0 );" ,
+
+ "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+ "vec3 lVector = normalize( lDirection.xyz );",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, lVector );",
+ "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+
+ "vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+ "hemiDiffuse += uDiffuseColor * hemiColor;",
+
+ // specular (sky light)
+
+
+ "vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );",
+ "float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;",
+ "float hemiSpecularWeightSky = specularTex.r * max( pow( hemiDotNormalHalfSky, uShininess ), 0.0 );",
+
+ // specular (ground light)
+
+ "vec3 lVectorGround = -lVector;",
+
+ "vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );",
+ "float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
+ "float hemiSpecularWeightGround = specularTex.r * max( pow( hemiDotNormalHalfGround, uShininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ "float dotProductGround = dot( normal, lVectorGround );",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlickSky = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );",
+ "vec3 schlickGround = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );",
+ "hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );",
+
+ "#else",
+
+ "hemiSpecular += uSpecularColor * hemiColor * ( hemiSpecularWeightSky + hemiSpecularWeightGround ) * hemiDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ // all lights contribution summation
+
+ "vec3 totalDiffuse = vec3( 0.0 );",
+ "vec3 totalSpecular = vec3( 0.0 );",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "totalDiffuse += dirDiffuse;",
+ "totalSpecular += dirSpecular;",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "totalDiffuse += hemiDiffuse;",
+ "totalSpecular += hemiSpecular;",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "totalDiffuse += pointDiffuse;",
+ "totalSpecular += pointSpecular;",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "totalDiffuse += spotDiffuse;",
+ "totalSpecular += spotSpecular;",
+
+ "#endif",
+
+ "#ifdef METAL",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor + totalSpecular );",
+
+ "#else",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor ) + totalSpecular;",
+
+ "#endif",
+
+ "if ( enableReflection ) {",
+
+ "vec3 vReflect;",
+ "vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+ "if ( useRefract ) {",
+
+ "vReflect = refract( cameraToVertex, normal, uRefractionRatio );",
+
+ "} else {",
+
+ "vReflect = reflect( cameraToVertex, normal );",
+
+ "}",
+
+ "vec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );",
+
+ "#ifdef GAMMA_INPUT",
+
+ "cubeColor.xyz *= cubeColor.xyz;",
+
+ "#endif",
+
+ "gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * uReflectivity );",
+
+ "}",
+
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+ THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n"),
+
+ vertexShader: [
+
+ "attribute vec4 tangent;",
+
+ "uniform vec2 uOffset;",
+ "uniform vec2 uRepeat;",
+
+ "uniform bool enableDisplacement;",
+
+ "#ifdef VERTEX_TEXTURES",
+
+ "uniform sampler2D tDisplacement;",
+ "uniform float uDisplacementScale;",
+ "uniform float uDisplacementBias;",
+
+ "#endif",
+
+ "varying vec3 vTangent;",
+ "varying vec3 vBinormal;",
+ "varying vec3 vNormal;",
+ "varying vec2 vUv;",
+
+ "varying vec3 vWorldPosition;",
+ "varying vec3 vViewPosition;",
+
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+ THREE.ShaderChunk[ "skinnormal_vertex" ],
+
+ // normal, tangent and binormal vectors
+
+ "#ifdef USE_SKINNING",
+
+ "vNormal = normalize( normalMatrix * skinnedNormal.xyz );",
+
+ "vec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );",
+ "vTangent = normalize( normalMatrix * skinnedTangent.xyz );",
+
+ "#else",
+
+ "vNormal = normalize( normalMatrix * normal );",
+ "vTangent = normalize( normalMatrix * tangent.xyz );",
+
+ "#endif",
+
+ "vBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );",
+
+ "vUv = uv * uRepeat + uOffset;",
+
+ // displacement mapping
+
+ "vec3 displacedPosition;",
+
+ "#ifdef VERTEX_TEXTURES",
+
+ "if ( enableDisplacement ) {",
+
+ "vec3 dv = texture2D( tDisplacement, uv ).xyz;",
+ "float df = uDisplacementScale * dv.x + uDisplacementBias;",
+ "displacedPosition = position + normalize( normal ) * df;",
+
+ "} else {",
+
+ "#ifdef USE_SKINNING",
+
+ "vec4 skinVertex = vec4( position, 1.0 );",
+
+ "vec4 skinned = boneMatX * skinVertex * skinWeight.x;",
+ "skinned += boneMatY * skinVertex * skinWeight.y;",
+
+ "displacedPosition = skinned.xyz;",
+
+ "#else",
+
+ "displacedPosition = position;",
+
+ "#endif",
+
+ "}",
+
+ "#else",
+
+ "#ifdef USE_SKINNING",
+
+ "vec4 skinVertex = vec4( position, 1.0 );",
+
+ "vec4 skinned = boneMatX * skinVertex * skinWeight.x;",
+ "skinned += boneMatY * skinVertex * skinWeight.y;",
+
+ "displacedPosition = skinned.xyz;",
+
+ "#else",
+
+ "displacedPosition = position;",
+
+ "#endif",
+
+ "#endif",
+
+ //
+
+ "vec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );",
+ "vec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );",
+
+ "gl_Position = projectionMatrix * mvPosition;",
+
+ //
+
+ "vWorldPosition = worldPosition.xyz;",
+ "vViewPosition = -mvPosition.xyz;",
+
+ // shadows
+
+ "#ifdef USE_SHADOWMAP",
+
+ "for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+ "vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;",
+
+ "}",
+
+ "#endif",
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ /* -------------------------------------------------------------------------
+ // Cube map shader
+ ------------------------------------------------------------------------- */
+
+ 'cube': {
+
+ uniforms: { "tCube": { type: "t", value: null },
+ "tFlip": { type: "f", value: -1 } },
+
+ vertexShader: [
+
+ "varying vec3 vWorldPosition;",
+
+ "void main() {",
+
+ "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
+ "vWorldPosition = worldPosition.xyz;",
+
+ "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform samplerCube tCube;",
+ "uniform float tFlip;",
+
+ "varying vec3 vWorldPosition;",
+
+ "void main() {",
+
+ "gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );",
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ // Depth encoding into RGBA texture
+ // based on SpiderGL shadow map example
+ // http://spidergl.org/example.php?id=6
+ // originally from
+ // http://www.gamedev.net/topic/442138-packing-a-float-into-a-a8r8g8b8-texture-shader/page__whichpage__1%25EF%25BF%25BD
+ // see also here:
+ // http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/
+
+ 'depthRGBA': {
+
+ uniforms: {},
+
+ vertexShader: [
+
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "skinning_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "vec4 pack_depth( const in float depth ) {",
+
+ "const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );",
+ "const vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );",
+ "vec4 res = fract( depth * bit_shift );",
+ "res -= res.xxyz * bit_mask;",
+ "return res;",
+
+ "}",
+
+ "void main() {",
+
+ "gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );",
+
+ //"gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z / gl_FragCoord.w );",
+ //"float z = ( ( gl_FragCoord.z / gl_FragCoord.w ) - 3.0 ) / ( 4000.0 - 3.0 );",
+ //"gl_FragData[ 0 ] = pack_depth( z );",
+ //"gl_FragData[ 0 ] = vec4( z, z, z, 1.0 );",
+
+ "}"
+
+ ].join("\n")
+
+ }
+
+};
+
+/**
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ */
+
+THREE.WebGLRenderer = function ( parameters ) {
+
+ console.log( 'THREE.WebGLRenderer', THREE.REVISION );
+
+ parameters = parameters || {};
+
+ var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement( 'canvas' ),
+
+ _precision = parameters.precision !== undefined ? parameters.precision : 'highp',
+
+ _alpha = parameters.alpha !== undefined ? parameters.alpha : true,
+ _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,
+ _antialias = parameters.antialias !== undefined ? parameters.antialias : false,
+ _stencil = parameters.stencil !== undefined ? parameters.stencil : true,
+ _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false,
+
+ _clearColor = new THREE.Color( 0x000000 ),
+ _clearAlpha = 0;
+
+ // public properties
+
+ this.domElement = _canvas;
+ this.context = null;
+ this.devicePixelRatio = parameters.devicePixelRatio !== undefined
+ ? parameters.devicePixelRatio
+ : self.devicePixelRatio !== undefined
+ ? self.devicePixelRatio
+ : 1;
+
+ // clearing
+
+ this.autoClear = true;
+ this.autoClearColor = true;
+ this.autoClearDepth = true;
+ this.autoClearStencil = true;
+
+ // scene graph
+
+ this.sortObjects = true;
+ this.autoUpdateObjects = true;
+
+ // physically based shading
+
+ this.gammaInput = false;
+ this.gammaOutput = false;
+ this.physicallyBasedShading = false;
+
+ // shadow map
+
+ this.shadowMapEnabled = false;
+ this.shadowMapAutoUpdate = true;
+ this.shadowMapType = THREE.PCFShadowMap;
+ this.shadowMapCullFace = THREE.CullFaceFront;
+ this.shadowMapDebug = false;
+ this.shadowMapCascade = false;
+
+ // morphs
+
+ this.maxMorphTargets = 8;
+ this.maxMorphNormals = 4;
+
+ // flags
+
+ this.autoScaleCubemaps = true;
+
+ // custom render plugins
+
+ this.renderPluginsPre = [];
+ this.renderPluginsPost = [];
+
+ // info
+
+ this.info = {
+
+ memory: {
+
+ programs: 0,
+ geometries: 0,
+ textures: 0
+
+ },
+
+ render: {
+
+ calls: 0,
+ vertices: 0,
+ faces: 0,
+ points: 0
+
+ }
+
+ };
+
+ // internal properties
+
+ var _this = this,
+
+ _programs = [],
+ _programs_counter = 0,
+
+ // internal state cache
+
+ _currentProgram = null,
+ _currentFramebuffer = null,
+ _currentMaterialId = -1,
+ _currentGeometryGroupHash = null,
+ _currentCamera = null,
+ _geometryGroupCounter = 0,
+
+ _usedTextureUnits = 0,
+
+ // GL state cache
+
+ _oldDoubleSided = -1,
+ _oldFlipSided = -1,
+
+ _oldBlending = -1,
+
+ _oldBlendEquation = -1,
+ _oldBlendSrc = -1,
+ _oldBlendDst = -1,
+
+ _oldDepthTest = -1,
+ _oldDepthWrite = -1,
+
+ _oldPolygonOffset = null,
+ _oldPolygonOffsetFactor = null,
+ _oldPolygonOffsetUnits = null,
+
+ _oldLineWidth = null,
+
+ _viewportX = 0,
+ _viewportY = 0,
+ _viewportWidth = 0,
+ _viewportHeight = 0,
+ _currentWidth = 0,
+ _currentHeight = 0,
+
+ _enabledAttributes = {},
+
+ // frustum
+
+ _frustum = new THREE.Frustum(),
+
+ // camera matrices cache
+
+ _projScreenMatrix = new THREE.Matrix4(),
+ _projScreenMatrixPS = new THREE.Matrix4(),
+
+ _vector3 = new THREE.Vector3(),
+
+ // light arrays cache
+
+ _direction = new THREE.Vector3(),
+
+ _lightsNeedUpdate = true,
+
+ _lights = {
+
+ ambient: [ 0, 0, 0 ],
+ directional: { length: 0, colors: new Array(), positions: new Array() },
+ point: { length: 0, colors: new Array(), positions: new Array(), distances: new Array() },
+ spot: { length: 0, colors: new Array(), positions: new Array(), distances: new Array(), directions: new Array(), anglesCos: new Array(), exponents: new Array() },
+ hemi: { length: 0, skyColors: new Array(), groundColors: new Array(), positions: new Array() }
+
+ };
+
+ // initialize
+
+ var _gl;
+
+ var _glExtensionTextureFloat;
+ var _glExtensionTextureFloatLinear;
+ var _glExtensionStandardDerivatives;
+ var _glExtensionTextureFilterAnisotropic;
+ var _glExtensionCompressedTextureS3TC;
+
+ initGL();
+
+ setDefaultGLState();
+
+ this.context = _gl;
+
+ // GPU capabilities
+
+ var _maxTextures = _gl.getParameter( _gl.MAX_TEXTURE_IMAGE_UNITS );
+ var _maxVertexTextures = _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS );
+ var _maxTextureSize = _gl.getParameter( _gl.MAX_TEXTURE_SIZE );
+ var _maxCubemapSize = _gl.getParameter( _gl.MAX_CUBE_MAP_TEXTURE_SIZE );
+
+ var _maxAnisotropy = _glExtensionTextureFilterAnisotropic ? _gl.getParameter( _glExtensionTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT ) : 0;
+
+ var _supportsVertexTextures = ( _maxVertexTextures > 0 );
+ var _supportsBoneTextures = _supportsVertexTextures && _glExtensionTextureFloat;
+
+ var _compressedTextureFormats = _glExtensionCompressedTextureS3TC ? _gl.getParameter( _gl.COMPRESSED_TEXTURE_FORMATS ) : [];
+
+ //
+
+ var _vertexShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_FLOAT );
+ var _vertexShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_FLOAT );
+ var _vertexShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_FLOAT );
+
+ var _fragmentShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_FLOAT );
+ var _fragmentShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_FLOAT );
+ var _fragmentShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_FLOAT );
+
+ var _vertexShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_INT );
+ var _vertexShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_INT );
+ var _vertexShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_INT );
+
+ var _fragmentShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_INT );
+ var _fragmentShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_INT );
+ var _fragmentShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_INT );
+
+ // clamp precision to maximum available
+
+ var highpAvailable = _vertexShaderPrecisionHighpFloat.precision > 0 && _fragmentShaderPrecisionHighpFloat.precision > 0;
+ var mediumpAvailable = _vertexShaderPrecisionMediumpFloat.precision > 0 && _fragmentShaderPrecisionMediumpFloat.precision > 0;
+
+ if ( _precision === "highp" && ! highpAvailable ) {
+
+ if ( mediumpAvailable ) {
+
+ _precision = "mediump";
+ console.warn( "WebGLRenderer: highp not supported, using mediump" );
+
+ } else {
+
+ _precision = "lowp";
+ console.warn( "WebGLRenderer: highp and mediump not supported, using lowp" );
+
+ }
+
+ }
+
+ if ( _precision === "mediump" && ! mediumpAvailable ) {
+
+ _precision = "lowp";
+ console.warn( "WebGLRenderer: mediump not supported, using lowp" );
+
+ }
+
+ // API
+
+ this.getContext = function () {
+
+ return _gl;
+
+ };
+
+ this.supportsVertexTextures = function () {
+
+ return _supportsVertexTextures;
+
+ };
+
+ this.supportsFloatTextures = function () {
+
+ return _glExtensionTextureFloat;
+
+ };
+
+ this.supportsStandardDerivatives = function () {
+
+ return _glExtensionStandardDerivatives;
+
+ };
+
+ this.supportsCompressedTextureS3TC = function () {
+
+ return _glExtensionCompressedTextureS3TC;
+
+ };
+
+ this.getMaxAnisotropy = function () {
+
+ return _maxAnisotropy;
+
+ };
+
+ this.getPrecision = function () {
+
+ return _precision;
+
+ };
+
+ this.setSize = function ( width, height, updateStyle ) {
+
+ _canvas.width = width * this.devicePixelRatio;
+ _canvas.height = height * this.devicePixelRatio;
+
+ if ( this.devicePixelRatio !== 1 && updateStyle !== false ) {
+
+ _canvas.style.width = width + 'px';
+ _canvas.style.height = height + 'px';
+
+ }
+
+ this.setViewport( 0, 0, _canvas.width, _canvas.height );
+
+ };
+
+ this.setViewport = function ( x, y, width, height ) {
+
+ _viewportX = x !== undefined ? x : 0;
+ _viewportY = y !== undefined ? y : 0;
+
+ _viewportWidth = width !== undefined ? width : _canvas.width;
+ _viewportHeight = height !== undefined ? height : _canvas.height;
+
+ _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight );
+
+ };
+
+ this.setScissor = function ( x, y, width, height ) {
+
+ _gl.scissor( x, y, width, height );
+
+ };
+
+ this.enableScissorTest = function ( enable ) {
+
+ enable ? _gl.enable( _gl.SCISSOR_TEST ) : _gl.disable( _gl.SCISSOR_TEST );
+
+ };
+
+ // Clearing
+
+ this.setClearColor = function ( color, alpha ) {
+
+ _clearColor.set( color );
+ _clearAlpha = alpha !== undefined ? alpha : 1;
+
+ _gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
+
+ };
+
+ this.setClearColorHex = function ( hex, alpha ) {
+
+ console.warn( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' );
+ this.setClearColor( hex, alpha );
+
+ };
+
+ this.getClearColor = function () {
+
+ return _clearColor;
+
+ };
+
+ this.getClearAlpha = function () {
+
+ return _clearAlpha;
+
+ };
+
+ this.clear = function ( color, depth, stencil ) {
+
+ var bits = 0;
+
+ if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT;
+ if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT;
+ if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT;
+
+ _gl.clear( bits );
+
+ };
+
+ this.clearTarget = function ( renderTarget, color, depth, stencil ) {
+
+ this.setRenderTarget( renderTarget );
+ this.clear( color, depth, stencil );
+
+ };
+
+ // Plugins
+
+ this.addPostPlugin = function ( plugin ) {
+
+ plugin.init( this );
+ this.renderPluginsPost.push( plugin );
+
+ };
+
+ this.addPrePlugin = function ( plugin ) {
+
+ plugin.init( this );
+ this.renderPluginsPre.push( plugin );
+
+ };
+
+ // Rendering
+
+ this.updateShadowMap = function ( scene, camera ) {
+
+ _currentProgram = null;
+ _oldBlending = -1;
+ _oldDepthTest = -1;
+ _oldDepthWrite = -1;
+ _currentGeometryGroupHash = -1;
+ _currentMaterialId = -1;
+ _lightsNeedUpdate = true;
+ _oldDoubleSided = -1;
+ _oldFlipSided = -1;
+
+ this.shadowMapPlugin.update( scene, camera );
+
+ };
+
+ // Internal functions
+
+ // Buffer allocation
+
+ function createParticleBuffers ( geometry ) {
+
+ geometry.__webglVertexBuffer = _gl.createBuffer();
+ geometry.__webglColorBuffer = _gl.createBuffer();
+
+ _this.info.memory.geometries ++;
+
+ };
+
+ function createLineBuffers ( geometry ) {
+
+ geometry.__webglVertexBuffer = _gl.createBuffer();
+ geometry.__webglColorBuffer = _gl.createBuffer();
+ geometry.__webglLineDistanceBuffer = _gl.createBuffer();
+
+ _this.info.memory.geometries ++;
+
+ };
+
+ function createMeshBuffers ( geometryGroup ) {
+
+ geometryGroup.__webglVertexBuffer = _gl.createBuffer();
+ geometryGroup.__webglNormalBuffer = _gl.createBuffer();
+ geometryGroup.__webglTangentBuffer = _gl.createBuffer();
+ geometryGroup.__webglColorBuffer = _gl.createBuffer();
+ geometryGroup.__webglUVBuffer = _gl.createBuffer();
+ geometryGroup.__webglUV2Buffer = _gl.createBuffer();
+
+ geometryGroup.__webglSkinIndicesBuffer = _gl.createBuffer();
+ geometryGroup.__webglSkinWeightsBuffer = _gl.createBuffer();
+
+ geometryGroup.__webglFaceBuffer = _gl.createBuffer();
+ geometryGroup.__webglLineBuffer = _gl.createBuffer();
+
+ var m, ml;
+
+ if ( geometryGroup.numMorphTargets ) {
+
+ geometryGroup.__webglMorphTargetsBuffers = [];
+
+ for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+ geometryGroup.__webglMorphTargetsBuffers.push( _gl.createBuffer() );
+
+ }
+
+ }
+
+ if ( geometryGroup.numMorphNormals ) {
+
+ geometryGroup.__webglMorphNormalsBuffers = [];
+
+ for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+ geometryGroup.__webglMorphNormalsBuffers.push( _gl.createBuffer() );
+
+ }
+
+ }
+
+ _this.info.memory.geometries ++;
+
+ };
+
+ // Events
+
+ var onGeometryDispose = function ( event ) {
+
+ var geometry = event.target;
+
+ geometry.removeEventListener( 'dispose', onGeometryDispose );
+
+ deallocateGeometry( geometry );
+
+ };
+
+ var onTextureDispose = function ( event ) {
+
+ var texture = event.target;
+
+ texture.removeEventListener( 'dispose', onTextureDispose );
+
+ deallocateTexture( texture );
+
+ _this.info.memory.textures --;
+
+
+ };
+
+ var onRenderTargetDispose = function ( event ) {
+
+ var renderTarget = event.target;
+
+ renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
+
+ deallocateRenderTarget( renderTarget );
+
+ _this.info.memory.textures --;
+
+ };
+
+ var onMaterialDispose = function ( event ) {
+
+ var material = event.target;
+
+ material.removeEventListener( 'dispose', onMaterialDispose );
+
+ deallocateMaterial( material );
+
+ };
+
+ // Buffer deallocation
+
+ var deleteBuffers = function ( geometry ) {
+
+ if ( geometry.__webglVertexBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglVertexBuffer );
+ if ( geometry.__webglNormalBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglNormalBuffer );
+ if ( geometry.__webglTangentBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglTangentBuffer );
+ if ( geometry.__webglColorBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglColorBuffer );
+ if ( geometry.__webglUVBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglUVBuffer );
+ if ( geometry.__webglUV2Buffer !== undefined ) _gl.deleteBuffer( geometry.__webglUV2Buffer );
+
+ if ( geometry.__webglSkinIndicesBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinIndicesBuffer );
+ if ( geometry.__webglSkinWeightsBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinWeightsBuffer );
+
+ if ( geometry.__webglFaceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglFaceBuffer );
+ if ( geometry.__webglLineBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineBuffer );
+
+ if ( geometry.__webglLineDistanceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineDistanceBuffer );
+ // custom attributes
+
+ if ( geometry.__webglCustomAttributesList !== undefined ) {
+
+ for ( var id in geometry.__webglCustomAttributesList ) {
+
+ _gl.deleteBuffer( geometry.__webglCustomAttributesList[ id ].buffer );
+
+ }
+
+ }
+
+ _this.info.memory.geometries --;
+
+ };
+
+ var deallocateGeometry = function ( geometry ) {
+
+ geometry.__webglInit = undefined;
+
+ if ( geometry instanceof THREE.BufferGeometry ) {
+
+ var attributes = geometry.attributes;
+
+ for ( var key in attributes ) {
+
+ if ( attributes[ key ].buffer !== undefined ) {
+
+ _gl.deleteBuffer( attributes[ key ].buffer );
+
+ }
+
+ }
+
+ _this.info.memory.geometries --;
+
+ } else {
+
+ if ( geometry.geometryGroups !== undefined ) {
+
+ for ( var g in geometry.geometryGroups ) {
+
+ var geometryGroup = geometry.geometryGroups[ g ];
+
+ if ( geometryGroup.numMorphTargets !== undefined ) {
+
+ for ( var m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+ _gl.deleteBuffer( geometryGroup.__webglMorphTargetsBuffers[ m ] );
+
+ }
+
+ }
+
+ if ( geometryGroup.numMorphNormals !== undefined ) {
+
+ for ( var m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+ _gl.deleteBuffer( geometryGroup.__webglMorphNormalsBuffers[ m ] );
+
+ }
+
+ }
+
+ deleteBuffers( geometryGroup );
+
+ }
+
+ } else {
+
+ deleteBuffers( geometry );
+
+ }
+
+ }
+
+ };
+
+ var deallocateTexture = function ( texture ) {
+
+ if ( texture.image && texture.image.__webglTextureCube ) {
+
+ // cube texture
+
+ _gl.deleteTexture( texture.image.__webglTextureCube );
+
+ } else {
+
+ // 2D texture
+
+ if ( ! texture.__webglInit ) return;
+
+ texture.__webglInit = false;
+ _gl.deleteTexture( texture.__webglTexture );
+
+ }
+
+ };
+
+ var deallocateRenderTarget = function ( renderTarget ) {
+
+ if ( !renderTarget || ! renderTarget.__webglTexture ) return;
+
+ _gl.deleteTexture( renderTarget.__webglTexture );
+
+ if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) {
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ _gl.deleteFramebuffer( renderTarget.__webglFramebuffer[ i ] );
+ _gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer[ i ] );
+
+ }
+
+ } else {
+
+ _gl.deleteFramebuffer( renderTarget.__webglFramebuffer );
+ _gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer );
+
+ }
+
+ };
+
+ var deallocateMaterial = function ( material ) {
+
+ var program = material.program;
+
+ if ( program === undefined ) return;
+
+ material.program = undefined;
+
+ // only deallocate GL program if this was the last use of shared program
+ // assumed there is only single copy of any program in the _programs list
+ // (that's how it's constructed)
+
+ var i, il, programInfo;
+ var deleteProgram = false;
+
+ for ( i = 0, il = _programs.length; i < il; i ++ ) {
+
+ programInfo = _programs[ i ];
+
+ if ( programInfo.program === program ) {
+
+ programInfo.usedTimes --;
+
+ if ( programInfo.usedTimes === 0 ) {
+
+ deleteProgram = true;
+
+ }
+
+ break;
+
+ }
+
+ }
+
+ if ( deleteProgram === true ) {
+
+ // avoid using array.splice, this is costlier than creating new array from scratch
+
+ var newPrograms = [];
+
+ for ( i = 0, il = _programs.length; i < il; i ++ ) {
+
+ programInfo = _programs[ i ];
+
+ if ( programInfo.program !== program ) {
+
+ newPrograms.push( programInfo );
+
+ }
+
+ }
+
+ _programs = newPrograms;
+
+ _gl.deleteProgram( program );
+
+ _this.info.memory.programs --;
+
+ }
+
+ };
+
+ // Buffer initialization
+
+ function initCustomAttributes ( geometry, object ) {
+
+ var nvertices = geometry.vertices.length;
+
+ var material = object.material;
+
+ if ( material.attributes ) {
+
+ if ( geometry.__webglCustomAttributesList === undefined ) {
+
+ geometry.__webglCustomAttributesList = [];
+
+ }
+
+ for ( var a in material.attributes ) {
+
+ var attribute = material.attributes[ a ];
+
+ if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) {
+
+ attribute.__webglInitialized = true;
+
+ var size = 1; // "f" and "i"
+
+ if ( attribute.type === "v2" ) size = 2;
+ else if ( attribute.type === "v3" ) size = 3;
+ else if ( attribute.type === "v4" ) size = 4;
+ else if ( attribute.type === "c" ) size = 3;
+
+ attribute.size = size;
+
+ attribute.array = new Float32Array( nvertices * size );
+
+ attribute.buffer = _gl.createBuffer();
+ attribute.buffer.belongsToAttribute = a;
+
+ attribute.needsUpdate = true;
+
+ }
+
+ geometry.__webglCustomAttributesList.push( attribute );
+
+ }
+
+ }
+
+ };
+
+ function initParticleBuffers ( geometry, object ) {
+
+ var nvertices = geometry.vertices.length;
+
+ geometry.__vertexArray = new Float32Array( nvertices * 3 );
+ geometry.__colorArray = new Float32Array( nvertices * 3 );
+
+ geometry.__sortArray = [];
+
+ geometry.__webglParticleCount = nvertices;
+
+ initCustomAttributes ( geometry, object );
+
+ };
+
+ function initLineBuffers ( geometry, object ) {
+
+ var nvertices = geometry.vertices.length;
+
+ geometry.__vertexArray = new Float32Array( nvertices * 3 );
+ geometry.__colorArray = new Float32Array( nvertices * 3 );
+ geometry.__lineDistanceArray = new Float32Array( nvertices * 1 );
+
+ geometry.__webglLineCount = nvertices;
+
+ initCustomAttributes ( geometry, object );
+
+ };
+
+ function initMeshBuffers ( geometryGroup, object ) {
+
+ var geometry = object.geometry,
+ faces3 = geometryGroup.faces3,
+
+ nvertices = faces3.length * 3,
+ ntris = faces3.length * 1,
+ nlines = faces3.length * 3,
+
+ material = getBufferMaterial( object, geometryGroup ),
+
+ uvType = bufferGuessUVType( material ),
+ normalType = bufferGuessNormalType( material ),
+ vertexColorType = bufferGuessVertexColorType( material );
+
+ // console.log( "uvType", uvType, "normalType", normalType, "vertexColorType", vertexColorType, object, geometryGroup, material );
+
+ geometryGroup.__vertexArray = new Float32Array( nvertices * 3 );
+
+ if ( normalType ) {
+
+ geometryGroup.__normalArray = new Float32Array( nvertices * 3 );
+
+ }
+
+ if ( geometry.hasTangents ) {
+
+ geometryGroup.__tangentArray = new Float32Array( nvertices * 4 );
+
+ }
+
+ if ( vertexColorType ) {
+
+ geometryGroup.__colorArray = new Float32Array( nvertices * 3 );
+
+ }
+
+ if ( uvType ) {
+
+ if ( geometry.faceVertexUvs.length > 0 ) {
+
+ geometryGroup.__uvArray = new Float32Array( nvertices * 2 );
+
+ }
+
+ if ( geometry.faceVertexUvs.length > 1 ) {
+
+ geometryGroup.__uv2Array = new Float32Array( nvertices * 2 );
+
+ }
+
+ }
+
+ if ( object.geometry.skinWeights.length && object.geometry.skinIndices.length ) {
+
+ geometryGroup.__skinIndexArray = new Float32Array( nvertices * 4 );
+ geometryGroup.__skinWeightArray = new Float32Array( nvertices * 4 );
+
+ }
+
+ geometryGroup.__faceArray = new Uint16Array( ntris * 3 );
+ geometryGroup.__lineArray = new Uint16Array( nlines * 2 );
+
+ var m, ml;
+
+ if ( geometryGroup.numMorphTargets ) {
+
+ geometryGroup.__morphTargetsArrays = [];
+
+ for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+ geometryGroup.__morphTargetsArrays.push( new Float32Array( nvertices * 3 ) );
+
+ }
+
+ }
+
+ if ( geometryGroup.numMorphNormals ) {
+
+ geometryGroup.__morphNormalsArrays = [];
+
+ for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+ geometryGroup.__morphNormalsArrays.push( new Float32Array( nvertices * 3 ) );
+
+ }
+
+ }
+
+ geometryGroup.__webglFaceCount = ntris * 3;
+ geometryGroup.__webglLineCount = nlines * 2;
+
+
+ // custom attributes
+
+ if ( material.attributes ) {
+
+ if ( geometryGroup.__webglCustomAttributesList === undefined ) {
+
+ geometryGroup.__webglCustomAttributesList = [];
+
+ }
+
+ for ( var a in material.attributes ) {
+
+ // Do a shallow copy of the attribute object so different geometryGroup chunks use different
+ // attribute buffers which are correctly indexed in the setMeshBuffers function
+
+ var originalAttribute = material.attributes[ a ];
+
+ var attribute = {};
+
+ for ( var property in originalAttribute ) {
+
+ attribute[ property ] = originalAttribute[ property ];
+
+ }
+
+ if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) {
+
+ attribute.__webglInitialized = true;
+
+ var size = 1; // "f" and "i"
+
+ if( attribute.type === "v2" ) size = 2;
+ else if( attribute.type === "v3" ) size = 3;
+ else if( attribute.type === "v4" ) size = 4;
+ else if( attribute.type === "c" ) size = 3;
+
+ attribute.size = size;
+
+ attribute.array = new Float32Array( nvertices * size );
+
+ attribute.buffer = _gl.createBuffer();
+ attribute.buffer.belongsToAttribute = a;
+
+ originalAttribute.needsUpdate = true;
+ attribute.__original = originalAttribute;
+
+ }
+
+ geometryGroup.__webglCustomAttributesList.push( attribute );
+
+ }
+
+ }
+
+ geometryGroup.__inittedArrays = true;
+
+ };
+
+ function getBufferMaterial( object, geometryGroup ) {
+
+ return object.material instanceof THREE.MeshFaceMaterial
+ ? object.material.materials[ geometryGroup.materialIndex ]
+ : object.material;
+
+ };
+
+ function materialNeedsSmoothNormals ( material ) {
+
+ return material && material.shading !== undefined && material.shading === THREE.SmoothShading;
+
+ };
+
+ function bufferGuessNormalType ( material ) {
+
+ // only MeshBasicMaterial and MeshDepthMaterial don't need normals
+
+ if ( ( material instanceof THREE.MeshBasicMaterial && !material.envMap ) || material instanceof THREE.MeshDepthMaterial ) {
+
+ return false;
+
+ }
+
+ if ( materialNeedsSmoothNormals( material ) ) {
+
+ return THREE.SmoothShading;
+
+ } else {
+
+ return THREE.FlatShading;
+
+ }
+
+ };
+
+ function bufferGuessVertexColorType( material ) {
+
+ if ( material.vertexColors ) {
+
+ return material.vertexColors;
+
+ }
+
+ return false;
+
+ };
+
+ function bufferGuessUVType( material ) {
+
+ // material must use some texture to require uvs
+
+ if ( material.map ||
+ material.lightMap ||
+ material.bumpMap ||
+ material.normalMap ||
+ material.specularMap ||
+ material instanceof THREE.ShaderMaterial ) {
+
+ return true;
+
+ }
+
+ return false;
+
+ };
+
+ //
+
+ function initDirectBuffers( geometry ) {
+
+ var a, attribute, type;
+
+ for ( a in geometry.attributes ) {
+
+ if ( a === "index" ) {
+
+ type = _gl.ELEMENT_ARRAY_BUFFER;
+
+ } else {
+
+ type = _gl.ARRAY_BUFFER;
+
+ }
+
+ attribute = geometry.attributes[ a ];
+
+ if ( attribute.numItems === undefined ) {
+
+ attribute.numItems = attribute.array.length;
+
+ }
+
+ attribute.buffer = _gl.createBuffer();
+
+ _gl.bindBuffer( type, attribute.buffer );
+ _gl.bufferData( type, attribute.array, _gl.STATIC_DRAW );
+
+ }
+
+ };
+
+ // Buffer setting
+
+ function setParticleBuffers ( geometry, hint, object ) {
+
+ var v, c, vertex, offset, index, color,
+
+ vertices = geometry.vertices,
+ vl = vertices.length,
+
+ colors = geometry.colors,
+ cl = colors.length,
+
+ vertexArray = geometry.__vertexArray,
+ colorArray = geometry.__colorArray,
+
+ sortArray = geometry.__sortArray,
+
+ dirtyVertices = geometry.verticesNeedUpdate,
+ dirtyElements = geometry.elementsNeedUpdate,
+ dirtyColors = geometry.colorsNeedUpdate,
+
+ customAttributes = geometry.__webglCustomAttributesList,
+ i, il,
+ a, ca, cal, value,
+ customAttribute;
+
+ if ( object.sortParticles ) {
+
+ _projScreenMatrixPS.copy( _projScreenMatrix );
+ _projScreenMatrixPS.multiply( object.matrixWorld );
+
+ for ( v = 0; v < vl; v ++ ) {
+
+ vertex = vertices[ v ];
+
+ _vector3.copy( vertex );
+ _vector3.applyProjection( _projScreenMatrixPS );
+
+ sortArray[ v ] = [ _vector3.z, v ];
+
+ }
+
+ sortArray.sort( numericalSort );
+
+ for ( v = 0; v < vl; v ++ ) {
+
+ vertex = vertices[ sortArray[v][1] ];
+
+ offset = v * 3;
+
+ vertexArray[ offset ] = vertex.x;
+ vertexArray[ offset + 1 ] = vertex.y;
+ vertexArray[ offset + 2 ] = vertex.z;
+
+ }
+
+ for ( c = 0; c < cl; c ++ ) {
+
+ offset = c * 3;
+
+ color = colors[ sortArray[c][1] ];
+
+ colorArray[ offset ] = color.r;
+ colorArray[ offset + 1 ] = color.g;
+ colorArray[ offset + 2 ] = color.b;
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( ! ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) ) continue;
+
+ offset = 0;
+
+ cal = customAttribute.value.length;
+
+ if ( customAttribute.size === 1 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ customAttribute.array[ ca ] = customAttribute.value[ index ];
+
+ }
+
+ } else if ( customAttribute.size === 2 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ value = customAttribute.value[ index ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+
+ offset += 2;
+
+ }
+
+ } else if ( customAttribute.size === 3 ) {
+
+ if ( customAttribute.type === "c" ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ value = customAttribute.value[ index ];
+
+ customAttribute.array[ offset ] = value.r;
+ customAttribute.array[ offset + 1 ] = value.g;
+ customAttribute.array[ offset + 2 ] = value.b;
+
+ offset += 3;
+
+ }
+
+ } else {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ value = customAttribute.value[ index ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+
+ offset += 3;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 4 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ value = customAttribute.value[ index ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+ customAttribute.array[ offset + 3 ] = value.w;
+
+ offset += 4;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ } else {
+
+ if ( dirtyVertices ) {
+
+ for ( v = 0; v < vl; v ++ ) {
+
+ vertex = vertices[ v ];
+
+ offset = v * 3;
+
+ vertexArray[ offset ] = vertex.x;
+ vertexArray[ offset + 1 ] = vertex.y;
+ vertexArray[ offset + 2 ] = vertex.z;
+
+ }
+
+ }
+
+ if ( dirtyColors ) {
+
+ for ( c = 0; c < cl; c ++ ) {
+
+ color = colors[ c ];
+
+ offset = c * 3;
+
+ colorArray[ offset ] = color.r;
+ colorArray[ offset + 1 ] = color.g;
+ colorArray[ offset + 2 ] = color.b;
+
+ }
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( customAttribute.needsUpdate &&
+ ( customAttribute.boundTo === undefined ||
+ customAttribute.boundTo === "vertices") ) {
+
+ cal = customAttribute.value.length;
+
+ offset = 0;
+
+ if ( customAttribute.size === 1 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ customAttribute.array[ ca ] = customAttribute.value[ ca ];
+
+ }
+
+ } else if ( customAttribute.size === 2 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+
+ offset += 2;
+
+ }
+
+ } else if ( customAttribute.size === 3 ) {
+
+ if ( customAttribute.type === "c" ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.r;
+ customAttribute.array[ offset + 1 ] = value.g;
+ customAttribute.array[ offset + 2 ] = value.b;
+
+ offset += 3;
+
+ }
+
+ } else {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+
+ offset += 3;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 4 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+ customAttribute.array[ offset + 3 ] = value.w;
+
+ offset += 4;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( dirtyVertices || object.sortParticles ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+ }
+
+ if ( dirtyColors || object.sortParticles ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( customAttribute.needsUpdate || object.sortParticles ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+ }
+
+ }
+
+ }
+
+
+ };
+
+ function setLineBuffers ( geometry, hint ) {
+
+ var v, c, d, vertex, offset, color,
+
+ vertices = geometry.vertices,
+ colors = geometry.colors,
+ lineDistances = geometry.lineDistances,
+
+ vl = vertices.length,
+ cl = colors.length,
+ dl = lineDistances.length,
+
+ vertexArray = geometry.__vertexArray,
+ colorArray = geometry.__colorArray,
+ lineDistanceArray = geometry.__lineDistanceArray,
+
+ dirtyVertices = geometry.verticesNeedUpdate,
+ dirtyColors = geometry.colorsNeedUpdate,
+ dirtyLineDistances = geometry.lineDistancesNeedUpdate,
+
+ customAttributes = geometry.__webglCustomAttributesList,
+
+ i, il,
+ a, ca, cal, value,
+ customAttribute;
+
+ if ( dirtyVertices ) {
+
+ for ( v = 0; v < vl; v ++ ) {
+
+ vertex = vertices[ v ];
+
+ offset = v * 3;
+
+ vertexArray[ offset ] = vertex.x;
+ vertexArray[ offset + 1 ] = vertex.y;
+ vertexArray[ offset + 2 ] = vertex.z;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+ }
+
+ if ( dirtyColors ) {
+
+ for ( c = 0; c < cl; c ++ ) {
+
+ color = colors[ c ];
+
+ offset = c * 3;
+
+ colorArray[ offset ] = color.r;
+ colorArray[ offset + 1 ] = color.g;
+ colorArray[ offset + 2 ] = color.b;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+ }
+
+ if ( dirtyLineDistances ) {
+
+ for ( d = 0; d < dl; d ++ ) {
+
+ lineDistanceArray[ d ] = lineDistances[ d ];
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglLineDistanceBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, lineDistanceArray, hint );
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( customAttribute.needsUpdate &&
+ ( customAttribute.boundTo === undefined ||
+ customAttribute.boundTo === "vertices" ) ) {
+
+ offset = 0;
+
+ cal = customAttribute.value.length;
+
+ if ( customAttribute.size === 1 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ customAttribute.array[ ca ] = customAttribute.value[ ca ];
+
+ }
+
+ } else if ( customAttribute.size === 2 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+
+ offset += 2;
+
+ }
+
+ } else if ( customAttribute.size === 3 ) {
+
+ if ( customAttribute.type === "c" ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.r;
+ customAttribute.array[ offset + 1 ] = value.g;
+ customAttribute.array[ offset + 2 ] = value.b;
+
+ offset += 3;
+
+ }
+
+ } else {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+
+ offset += 3;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 4 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+ customAttribute.array[ offset + 3 ] = value.w;
+
+ offset += 4;
+
+ }
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ function setMeshBuffers( geometryGroup, object, hint, dispose, material ) {
+
+ if ( ! geometryGroup.__inittedArrays ) {
+
+ return;
+
+ }
+
+ var normalType = bufferGuessNormalType( material ),
+ vertexColorType = bufferGuessVertexColorType( material ),
+ uvType = bufferGuessUVType( material ),
+
+ needsSmoothNormals = ( normalType === THREE.SmoothShading );
+
+ var f, fl, fi, face,
+ vertexNormals, faceNormal, normal,
+ vertexColors, faceColor,
+ vertexTangents,
+ uv, uv2, v1, v2, v3, v4, t1, t2, t3, t4, n1, n2, n3, n4,
+ c1, c2, c3, c4,
+ sw1, sw2, sw3, sw4,
+ si1, si2, si3, si4,
+ sa1, sa2, sa3, sa4,
+ sb1, sb2, sb3, sb4,
+ m, ml, i, il,
+ vn, uvi, uv2i,
+ vk, vkl, vka,
+ nka, chf, faceVertexNormals,
+ a,
+
+ vertexIndex = 0,
+
+ offset = 0,
+ offset_uv = 0,
+ offset_uv2 = 0,
+ offset_face = 0,
+ offset_normal = 0,
+ offset_tangent = 0,
+ offset_line = 0,
+ offset_color = 0,
+ offset_skin = 0,
+ offset_morphTarget = 0,
+ offset_custom = 0,
+ offset_customSrc = 0,
+
+ value,
+
+ vertexArray = geometryGroup.__vertexArray,
+ uvArray = geometryGroup.__uvArray,
+ uv2Array = geometryGroup.__uv2Array,
+ normalArray = geometryGroup.__normalArray,
+ tangentArray = geometryGroup.__tangentArray,
+ colorArray = geometryGroup.__colorArray,
+
+ skinIndexArray = geometryGroup.__skinIndexArray,
+ skinWeightArray = geometryGroup.__skinWeightArray,
+
+ morphTargetsArrays = geometryGroup.__morphTargetsArrays,
+ morphNormalsArrays = geometryGroup.__morphNormalsArrays,
+
+ customAttributes = geometryGroup.__webglCustomAttributesList,
+ customAttribute,
+
+ faceArray = geometryGroup.__faceArray,
+ lineArray = geometryGroup.__lineArray,
+
+ geometry = object.geometry, // this is shared for all chunks
+
+ dirtyVertices = geometry.verticesNeedUpdate,
+ dirtyElements = geometry.elementsNeedUpdate,
+ dirtyUvs = geometry.uvsNeedUpdate,
+ dirtyNormals = geometry.normalsNeedUpdate,
+ dirtyTangents = geometry.tangentsNeedUpdate,
+ dirtyColors = geometry.colorsNeedUpdate,
+ dirtyMorphTargets = geometry.morphTargetsNeedUpdate,
+
+ vertices = geometry.vertices,
+ chunk_faces3 = geometryGroup.faces3,
+ obj_faces = geometry.faces,
+
+ obj_uvs = geometry.faceVertexUvs[ 0 ],
+ obj_uvs2 = geometry.faceVertexUvs[ 1 ],
+
+ obj_colors = geometry.colors,
+
+ obj_skinIndices = geometry.skinIndices,
+ obj_skinWeights = geometry.skinWeights,
+
+ morphTargets = geometry.morphTargets,
+ morphNormals = geometry.morphNormals;
+
+ if ( dirtyVertices ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ v1 = vertices[ face.a ];
+ v2 = vertices[ face.b ];
+ v3 = vertices[ face.c ];
+
+ vertexArray[ offset ] = v1.x;
+ vertexArray[ offset + 1 ] = v1.y;
+ vertexArray[ offset + 2 ] = v1.z;
+
+ vertexArray[ offset + 3 ] = v2.x;
+ vertexArray[ offset + 4 ] = v2.y;
+ vertexArray[ offset + 5 ] = v2.z;
+
+ vertexArray[ offset + 6 ] = v3.x;
+ vertexArray[ offset + 7 ] = v3.y;
+ vertexArray[ offset + 8 ] = v3.z;
+
+ offset += 9;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+ }
+
+ if ( dirtyMorphTargets ) {
+
+ for ( vk = 0, vkl = morphTargets.length; vk < vkl; vk ++ ) {
+
+ offset_morphTarget = 0;
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ chf = chunk_faces3[ f ];
+ face = obj_faces[ chf ];
+
+ // morph positions
+
+ v1 = morphTargets[ vk ].vertices[ face.a ];
+ v2 = morphTargets[ vk ].vertices[ face.b ];
+ v3 = morphTargets[ vk ].vertices[ face.c ];
+
+ vka = morphTargetsArrays[ vk ];
+
+ vka[ offset_morphTarget ] = v1.x;
+ vka[ offset_morphTarget + 1 ] = v1.y;
+ vka[ offset_morphTarget + 2 ] = v1.z;
+
+ vka[ offset_morphTarget + 3 ] = v2.x;
+ vka[ offset_morphTarget + 4 ] = v2.y;
+ vka[ offset_morphTarget + 5 ] = v2.z;
+
+ vka[ offset_morphTarget + 6 ] = v3.x;
+ vka[ offset_morphTarget + 7 ] = v3.y;
+ vka[ offset_morphTarget + 8 ] = v3.z;
+
+ // morph normals
+
+ if ( material.morphNormals ) {
+
+ if ( needsSmoothNormals ) {
+
+ faceVertexNormals = morphNormals[ vk ].vertexNormals[ chf ];
+
+ n1 = faceVertexNormals.a;
+ n2 = faceVertexNormals.b;
+ n3 = faceVertexNormals.c;
+
+ } else {
+
+ n1 = morphNormals[ vk ].faceNormals[ chf ];
+ n2 = n1;
+ n3 = n1;
+
+ }
+
+ nka = morphNormalsArrays[ vk ];
+
+ nka[ offset_morphTarget ] = n1.x;
+ nka[ offset_morphTarget + 1 ] = n1.y;
+ nka[ offset_morphTarget + 2 ] = n1.z;
+
+ nka[ offset_morphTarget + 3 ] = n2.x;
+ nka[ offset_morphTarget + 4 ] = n2.y;
+ nka[ offset_morphTarget + 5 ] = n2.z;
+
+ nka[ offset_morphTarget + 6 ] = n3.x;
+ nka[ offset_morphTarget + 7 ] = n3.y;
+ nka[ offset_morphTarget + 8 ] = n3.z;
+
+ }
+
+ //
+
+ offset_morphTarget += 9;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ vk ] );
+ _gl.bufferData( _gl.ARRAY_BUFFER, morphTargetsArrays[ vk ], hint );
+
+ if ( material.morphNormals ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ vk ] );
+ _gl.bufferData( _gl.ARRAY_BUFFER, morphNormalsArrays[ vk ], hint );
+
+ }
+
+ }
+
+ }
+
+ if ( obj_skinWeights.length ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ // weights
+
+ sw1 = obj_skinWeights[ face.a ];
+ sw2 = obj_skinWeights[ face.b ];
+ sw3 = obj_skinWeights[ face.c ];
+
+ skinWeightArray[ offset_skin ] = sw1.x;
+ skinWeightArray[ offset_skin + 1 ] = sw1.y;
+ skinWeightArray[ offset_skin + 2 ] = sw1.z;
+ skinWeightArray[ offset_skin + 3 ] = sw1.w;
+
+ skinWeightArray[ offset_skin + 4 ] = sw2.x;
+ skinWeightArray[ offset_skin + 5 ] = sw2.y;
+ skinWeightArray[ offset_skin + 6 ] = sw2.z;
+ skinWeightArray[ offset_skin + 7 ] = sw2.w;
+
+ skinWeightArray[ offset_skin + 8 ] = sw3.x;
+ skinWeightArray[ offset_skin + 9 ] = sw3.y;
+ skinWeightArray[ offset_skin + 10 ] = sw3.z;
+ skinWeightArray[ offset_skin + 11 ] = sw3.w;
+
+ // indices
+
+ si1 = obj_skinIndices[ face.a ];
+ si2 = obj_skinIndices[ face.b ];
+ si3 = obj_skinIndices[ face.c ];
+
+ skinIndexArray[ offset_skin ] = si1.x;
+ skinIndexArray[ offset_skin + 1 ] = si1.y;
+ skinIndexArray[ offset_skin + 2 ] = si1.z;
+ skinIndexArray[ offset_skin + 3 ] = si1.w;
+
+ skinIndexArray[ offset_skin + 4 ] = si2.x;
+ skinIndexArray[ offset_skin + 5 ] = si2.y;
+ skinIndexArray[ offset_skin + 6 ] = si2.z;
+ skinIndexArray[ offset_skin + 7 ] = si2.w;
+
+ skinIndexArray[ offset_skin + 8 ] = si3.x;
+ skinIndexArray[ offset_skin + 9 ] = si3.y;
+ skinIndexArray[ offset_skin + 10 ] = si3.z;
+ skinIndexArray[ offset_skin + 11 ] = si3.w;
+
+ offset_skin += 12;
+
+ }
+
+ if ( offset_skin > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, skinIndexArray, hint );
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, skinWeightArray, hint );
+
+ }
+
+ }
+
+ if ( dirtyColors && vertexColorType ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ vertexColors = face.vertexColors;
+ faceColor = face.color;
+
+ if ( vertexColors.length === 3 && vertexColorType === THREE.VertexColors ) {
+
+ c1 = vertexColors[ 0 ];
+ c2 = vertexColors[ 1 ];
+ c3 = vertexColors[ 2 ];
+
+ } else {
+
+ c1 = faceColor;
+ c2 = faceColor;
+ c3 = faceColor;
+
+ }
+
+ colorArray[ offset_color ] = c1.r;
+ colorArray[ offset_color + 1 ] = c1.g;
+ colorArray[ offset_color + 2 ] = c1.b;
+
+ colorArray[ offset_color + 3 ] = c2.r;
+ colorArray[ offset_color + 4 ] = c2.g;
+ colorArray[ offset_color + 5 ] = c2.b;
+
+ colorArray[ offset_color + 6 ] = c3.r;
+ colorArray[ offset_color + 7 ] = c3.g;
+ colorArray[ offset_color + 8 ] = c3.b;
+
+ offset_color += 9;
+
+ }
+
+ if ( offset_color > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+ }
+
+ }
+
+ if ( dirtyTangents && geometry.hasTangents ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ vertexTangents = face.vertexTangents;
+
+ t1 = vertexTangents[ 0 ];
+ t2 = vertexTangents[ 1 ];
+ t3 = vertexTangents[ 2 ];
+
+ tangentArray[ offset_tangent ] = t1.x;
+ tangentArray[ offset_tangent + 1 ] = t1.y;
+ tangentArray[ offset_tangent + 2 ] = t1.z;
+ tangentArray[ offset_tangent + 3 ] = t1.w;
+
+ tangentArray[ offset_tangent + 4 ] = t2.x;
+ tangentArray[ offset_tangent + 5 ] = t2.y;
+ tangentArray[ offset_tangent + 6 ] = t2.z;
+ tangentArray[ offset_tangent + 7 ] = t2.w;
+
+ tangentArray[ offset_tangent + 8 ] = t3.x;
+ tangentArray[ offset_tangent + 9 ] = t3.y;
+ tangentArray[ offset_tangent + 10 ] = t3.z;
+ tangentArray[ offset_tangent + 11 ] = t3.w;
+
+ offset_tangent += 12;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, tangentArray, hint );
+
+ }
+
+ if ( dirtyNormals && normalType ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ vertexNormals = face.vertexNormals;
+ faceNormal = face.normal;
+
+ if ( vertexNormals.length === 3 && needsSmoothNormals ) {
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ vn = vertexNormals[ i ];
+
+ normalArray[ offset_normal ] = vn.x;
+ normalArray[ offset_normal + 1 ] = vn.y;
+ normalArray[ offset_normal + 2 ] = vn.z;
+
+ offset_normal += 3;
+
+ }
+
+ } else {
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ normalArray[ offset_normal ] = faceNormal.x;
+ normalArray[ offset_normal + 1 ] = faceNormal.y;
+ normalArray[ offset_normal + 2 ] = faceNormal.z;
+
+ offset_normal += 3;
+
+ }
+
+ }
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, normalArray, hint );
+
+ }
+
+ if ( dirtyUvs && obj_uvs && uvType ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ fi = chunk_faces3[ f ];
+
+ uv = obj_uvs[ fi ];
+
+ if ( uv === undefined ) continue;
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ uvi = uv[ i ];
+
+ uvArray[ offset_uv ] = uvi.x;
+ uvArray[ offset_uv + 1 ] = uvi.y;
+
+ offset_uv += 2;
+
+ }
+
+ }
+
+ if ( offset_uv > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, uvArray, hint );
+
+ }
+
+ }
+
+ if ( dirtyUvs && obj_uvs2 && uvType ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ fi = chunk_faces3[ f ];
+
+ uv2 = obj_uvs2[ fi ];
+
+ if ( uv2 === undefined ) continue;
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ uv2i = uv2[ i ];
+
+ uv2Array[ offset_uv2 ] = uv2i.x;
+ uv2Array[ offset_uv2 + 1 ] = uv2i.y;
+
+ offset_uv2 += 2;
+
+ }
+
+ }
+
+ if ( offset_uv2 > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, uv2Array, hint );
+
+ }
+
+ }
+
+ if ( dirtyElements ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ faceArray[ offset_face ] = vertexIndex;
+ faceArray[ offset_face + 1 ] = vertexIndex + 1;
+ faceArray[ offset_face + 2 ] = vertexIndex + 2;
+
+ offset_face += 3;
+
+ lineArray[ offset_line ] = vertexIndex;
+ lineArray[ offset_line + 1 ] = vertexIndex + 1;
+
+ lineArray[ offset_line + 2 ] = vertexIndex;
+ lineArray[ offset_line + 3 ] = vertexIndex + 2;
+
+ lineArray[ offset_line + 4 ] = vertexIndex + 1;
+ lineArray[ offset_line + 5 ] = vertexIndex + 2;
+
+ offset_line += 6;
+
+ vertexIndex += 3;
+
+ }
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer );
+ _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faceArray, hint );
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer );
+ _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, lineArray, hint );
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( ! customAttribute.__original.needsUpdate ) continue;
+
+ offset_custom = 0;
+ offset_customSrc = 0;
+
+ if ( customAttribute.size === 1 ) {
+
+ if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ customAttribute.array[ offset_custom ] = customAttribute.value[ face.a ];
+ customAttribute.array[ offset_custom + 1 ] = customAttribute.value[ face.b ];
+ customAttribute.array[ offset_custom + 2 ] = customAttribute.value[ face.c ];
+
+ offset_custom += 3;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faces" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ customAttribute.array[ offset_custom ] = value;
+ customAttribute.array[ offset_custom + 1 ] = value;
+ customAttribute.array[ offset_custom + 2 ] = value;
+
+ offset_custom += 3;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 2 ) {
+
+ if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ v1 = customAttribute.value[ face.a ];
+ v2 = customAttribute.value[ face.b ];
+ v3 = customAttribute.value[ face.c ];
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+
+ customAttribute.array[ offset_custom + 2 ] = v2.x;
+ customAttribute.array[ offset_custom + 3 ] = v2.y;
+
+ customAttribute.array[ offset_custom + 4 ] = v3.x;
+ customAttribute.array[ offset_custom + 5 ] = v3.y;
+
+ offset_custom += 6;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faces" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value;
+ v2 = value;
+ v3 = value;
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+
+ customAttribute.array[ offset_custom + 2 ] = v2.x;
+ customAttribute.array[ offset_custom + 3 ] = v2.y;
+
+ customAttribute.array[ offset_custom + 4 ] = v3.x;
+ customAttribute.array[ offset_custom + 5 ] = v3.y;
+
+ offset_custom += 6;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 3 ) {
+
+ var pp;
+
+ if ( customAttribute.type === "c" ) {
+
+ pp = [ "r", "g", "b" ];
+
+ } else {
+
+ pp = [ "x", "y", "z" ];
+
+ }
+
+ if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ v1 = customAttribute.value[ face.a ];
+ v2 = customAttribute.value[ face.b ];
+ v3 = customAttribute.value[ face.c ];
+
+ customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+ offset_custom += 9;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faces" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value;
+ v2 = value;
+ v3 = value;
+
+ customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+ offset_custom += 9;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faceVertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value[ 0 ];
+ v2 = value[ 1 ];
+ v3 = value[ 2 ];
+
+ customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+ offset_custom += 9;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 4 ) {
+
+ if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ v1 = customAttribute.value[ face.a ];
+ v2 = customAttribute.value[ face.b ];
+ v3 = customAttribute.value[ face.c ];
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+ customAttribute.array[ offset_custom + 2 ] = v1.z;
+ customAttribute.array[ offset_custom + 3 ] = v1.w;
+
+ customAttribute.array[ offset_custom + 4 ] = v2.x;
+ customAttribute.array[ offset_custom + 5 ] = v2.y;
+ customAttribute.array[ offset_custom + 6 ] = v2.z;
+ customAttribute.array[ offset_custom + 7 ] = v2.w;
+
+ customAttribute.array[ offset_custom + 8 ] = v3.x;
+ customAttribute.array[ offset_custom + 9 ] = v3.y;
+ customAttribute.array[ offset_custom + 10 ] = v3.z;
+ customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+ offset_custom += 12;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faces" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value;
+ v2 = value;
+ v3 = value;
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+ customAttribute.array[ offset_custom + 2 ] = v1.z;
+ customAttribute.array[ offset_custom + 3 ] = v1.w;
+
+ customAttribute.array[ offset_custom + 4 ] = v2.x;
+ customAttribute.array[ offset_custom + 5 ] = v2.y;
+ customAttribute.array[ offset_custom + 6 ] = v2.z;
+ customAttribute.array[ offset_custom + 7 ] = v2.w;
+
+ customAttribute.array[ offset_custom + 8 ] = v3.x;
+ customAttribute.array[ offset_custom + 9 ] = v3.y;
+ customAttribute.array[ offset_custom + 10 ] = v3.z;
+ customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+ offset_custom += 12;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faceVertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value[ 0 ];
+ v2 = value[ 1 ];
+ v3 = value[ 2 ];
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+ customAttribute.array[ offset_custom + 2 ] = v1.z;
+ customAttribute.array[ offset_custom + 3 ] = v1.w;
+
+ customAttribute.array[ offset_custom + 4 ] = v2.x;
+ customAttribute.array[ offset_custom + 5 ] = v2.y;
+ customAttribute.array[ offset_custom + 6 ] = v2.z;
+ customAttribute.array[ offset_custom + 7 ] = v2.w;
+
+ customAttribute.array[ offset_custom + 8 ] = v3.x;
+ customAttribute.array[ offset_custom + 9 ] = v3.y;
+ customAttribute.array[ offset_custom + 10 ] = v3.z;
+ customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+ offset_custom += 12;
+
+ }
+
+ }
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+ }
+
+ }
+
+ if ( dispose ) {
+
+ delete geometryGroup.__inittedArrays;
+ delete geometryGroup.__colorArray;
+ delete geometryGroup.__normalArray;
+ delete geometryGroup.__tangentArray;
+ delete geometryGroup.__uvArray;
+ delete geometryGroup.__uv2Array;
+ delete geometryGroup.__faceArray;
+ delete geometryGroup.__vertexArray;
+ delete geometryGroup.__lineArray;
+ delete geometryGroup.__skinIndexArray;
+ delete geometryGroup.__skinWeightArray;
+
+ }
+
+ };
+
+ function setDirectBuffers ( geometry, hint, dispose ) {
+
+ var attributes = geometry.attributes;
+
+ var attributeName, attributeItem;
+
+ for ( attributeName in attributes ) {
+
+ attributeItem = attributes[ attributeName ];
+
+ if ( attributeItem.needsUpdate ) {
+
+ if ( attributeName === 'index' ) {
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.buffer );
+ _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.array, hint );
+
+ } else {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, attributeItem.array, hint );
+
+ }
+
+ attributeItem.needsUpdate = false;
+
+ }
+
+ if ( dispose && ! attributeItem.dynamic ) {
+
+ attributeItem.array = null;
+
+ }
+
+ }
+
+ };
+
+ // Buffer rendering
+
+ this.renderBufferImmediate = function ( object, program, material ) {
+
+ if ( object.hasPositions && ! object.__webglVertexBuffer ) object.__webglVertexBuffer = _gl.createBuffer();
+ if ( object.hasNormals && ! object.__webglNormalBuffer ) object.__webglNormalBuffer = _gl.createBuffer();
+ if ( object.hasUvs && ! object.__webglUvBuffer ) object.__webglUvBuffer = _gl.createBuffer();
+ if ( object.hasColors && ! object.__webglColorBuffer ) object.__webglColorBuffer = _gl.createBuffer();
+
+ if ( object.hasPositions ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglVertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW );
+ _gl.enableVertexAttribArray( program.attributes.position );
+ _gl.vertexAttribPointer( program.attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( object.hasNormals ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglNormalBuffer );
+
+ if ( material.shading === THREE.FlatShading ) {
+
+ var nx, ny, nz,
+ nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz,
+ normalArray,
+ i, il = object.count * 3;
+
+ for( i = 0; i < il; i += 9 ) {
+
+ normalArray = object.normalArray;
+
+ nax = normalArray[ i ];
+ nay = normalArray[ i + 1 ];
+ naz = normalArray[ i + 2 ];
+
+ nbx = normalArray[ i + 3 ];
+ nby = normalArray[ i + 4 ];
+ nbz = normalArray[ i + 5 ];
+
+ ncx = normalArray[ i + 6 ];
+ ncy = normalArray[ i + 7 ];
+ ncz = normalArray[ i + 8 ];
+
+ nx = ( nax + nbx + ncx ) / 3;
+ ny = ( nay + nby + ncy ) / 3;
+ nz = ( naz + nbz + ncz ) / 3;
+
+ normalArray[ i ] = nx;
+ normalArray[ i + 1 ] = ny;
+ normalArray[ i + 2 ] = nz;
+
+ normalArray[ i + 3 ] = nx;
+ normalArray[ i + 4 ] = ny;
+ normalArray[ i + 5 ] = nz;
+
+ normalArray[ i + 6 ] = nx;
+ normalArray[ i + 7 ] = ny;
+ normalArray[ i + 8 ] = nz;
+
+ }
+
+ }
+
+ _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW );
+ _gl.enableVertexAttribArray( program.attributes.normal );
+ _gl.vertexAttribPointer( program.attributes.normal, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( object.hasUvs && material.map ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglUvBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW );
+ _gl.enableVertexAttribArray( program.attributes.uv );
+ _gl.vertexAttribPointer( program.attributes.uv, 2, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( object.hasColors && material.vertexColors !== THREE.NoColors ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglColorBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW );
+ _gl.enableVertexAttribArray( program.attributes.color );
+ _gl.vertexAttribPointer( program.attributes.color, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ _gl.drawArrays( _gl.TRIANGLES, 0, object.count );
+
+ object.count = 0;
+
+ };
+
+ this.renderBufferDirect = function ( camera, lights, fog, material, geometry, object ) {
+
+ if ( material.visible === false ) return;
+
+ var linewidth, a, attribute;
+ var attributeItem, attributeName, attributePointer, attributeSize;
+
+ var program = setProgram( camera, lights, fog, material, object );
+
+ var programAttributes = program.attributes;
+ var geometryAttributes = geometry.attributes;
+
+ var updateBuffers = false,
+ wireframeBit = material.wireframe ? 1 : 0,
+ geometryHash = ( geometry.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit;
+
+ if ( geometryHash !== _currentGeometryGroupHash ) {
+
+ _currentGeometryGroupHash = geometryHash;
+ updateBuffers = true;
+
+ }
+
+ if ( updateBuffers ) {
+
+ disableAttributes();
+
+ }
+
+ // render mesh
+
+ if ( object instanceof THREE.Mesh ) {
+
+ var index = geometryAttributes[ "index" ];
+
+ // indexed triangles
+
+ if ( index ) {
+
+ var offsets = geometry.offsets;
+
+ // if there is more than 1 chunk
+ // must set attribute pointers to use new offsets for each chunk
+ // even if geometry and materials didn't change
+
+ if ( offsets.length > 1 ) updateBuffers = true;
+
+ for ( var i = 0, il = offsets.length; i < il; i ++ ) {
+
+ var startIndex = offsets[ i ].index;
+
+ if ( updateBuffers ) {
+
+ for ( attributeName in programAttributes ) {
+
+ attributePointer = programAttributes[ attributeName ];
+ attributeItem = geometryAttributes[ attributeName ];
+
+ if ( attributePointer >= 0 ) {
+
+ if ( attributeItem ) {
+
+ attributeSize = attributeItem.itemSize;
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ enableAttribute( attributePointer );
+ _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, startIndex * attributeSize * 4 ); // 4 bytes per Float32
+
+ } else if ( material.defaultAttributeValues ) {
+
+ if ( material.defaultAttributeValues[ attributeName ].length === 2 ) {
+
+ _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) {
+
+ _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ // indices
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer );
+
+ }
+
+ // render indexed triangles
+
+ _gl.drawElements( _gl.TRIANGLES, offsets[ i ].count, _gl.UNSIGNED_SHORT, offsets[ i ].start * 2 ); // 2 bytes per Uint16
+
+ _this.info.render.calls ++;
+ _this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared
+ _this.info.render.faces += offsets[ i ].count / 3;
+
+ }
+
+ // non-indexed triangles
+
+ } else {
+
+ if ( updateBuffers ) {
+
+ for ( attributeName in programAttributes ) {
+
+ if ( attributeName === 'index') continue;
+
+ attributePointer = programAttributes[ attributeName ];
+ attributeItem = geometryAttributes[ attributeName ];
+
+ if ( attributePointer >= 0 ) {
+
+ if ( attributeItem ) {
+
+ attributeSize = attributeItem.itemSize;
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ enableAttribute( attributePointer );
+ _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) {
+
+ if ( material.defaultAttributeValues[ attributeName ].length === 2 ) {
+
+ _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) {
+
+ _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ var position = geometry.attributes[ "position" ];
+
+ // render non-indexed triangles
+
+ _gl.drawArrays( _gl.TRIANGLES, 0, position.numItems / 3 );
+
+ _this.info.render.calls ++;
+ _this.info.render.vertices += position.numItems / 3;
+ _this.info.render.faces += position.numItems / 3 / 3;
+
+ }
+
+ // render particles
+
+ } else if ( object instanceof THREE.ParticleSystem ) {
+
+ if ( updateBuffers ) {
+
+ for ( attributeName in programAttributes ) {
+
+ attributePointer = programAttributes[ attributeName ];
+ attributeItem = geometryAttributes[ attributeName ];
+
+ if ( attributePointer >= 0 ) {
+
+ if ( attributeItem ) {
+
+ attributeSize = attributeItem.itemSize;
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ enableAttribute( attributePointer );
+ _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) {
+
+ if ( material.defaultAttributeValues[ attributeName ].length === 2 ) {
+
+ _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) {
+
+ _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ var position = geometryAttributes[ "position" ];
+
+ // render particles
+
+ _gl.drawArrays( _gl.POINTS, 0, position.numItems / 3 );
+
+ _this.info.render.calls ++;
+ _this.info.render.points += position.numItems / 3;
+
+ }
+
+ } else if ( object instanceof THREE.Line ) {
+
+ if ( updateBuffers ) {
+
+ for ( attributeName in programAttributes ) {
+
+ attributePointer = programAttributes[ attributeName ];
+ attributeItem = geometryAttributes[ attributeName ];
+
+ if ( attributePointer >= 0 ) {
+
+ if ( attributeItem ) {
+
+ attributeSize = attributeItem.itemSize;
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ enableAttribute( attributePointer );
+ _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) {
+
+ if ( material.defaultAttributeValues[ attributeName ].length === 2 ) {
+
+ _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) {
+
+ _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ // render lines
+
+ var primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES;
+
+ setLineWidth( material.linewidth );
+
+ var position = geometryAttributes[ "position" ];
+
+ _gl.drawArrays( primitives, 0, position.numItems / 3 );
+
+ _this.info.render.calls ++;
+ _this.info.render.points += position.numItems;
+
+ }
+
+ }
+
+ };
+
+ this.renderBuffer = function ( camera, lights, fog, material, geometryGroup, object ) {
+
+ if ( material.visible === false ) return;
+
+ var linewidth, a, attribute, i, il;
+
+ var program = setProgram( camera, lights, fog, material, object );
+
+ var attributes = program.attributes;
+
+ var updateBuffers = false,
+ wireframeBit = material.wireframe ? 1 : 0,
+ geometryGroupHash = ( geometryGroup.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit;
+
+ if ( geometryGroupHash !== _currentGeometryGroupHash ) {
+
+ _currentGeometryGroupHash = geometryGroupHash;
+ updateBuffers = true;
+
+ }
+
+ if ( updateBuffers ) {
+
+ disableAttributes();
+
+ }
+
+ // vertices
+
+ if ( !material.morphTargets && attributes.position >= 0 ) {
+
+ if ( updateBuffers ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+ enableAttribute( attributes.position );
+ _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ } else {
+
+ if ( object.morphTargetBase ) {
+
+ setupMorphTargets( material, geometryGroup, object );
+
+ }
+
+ }
+
+
+ if ( updateBuffers ) {
+
+ // custom attributes
+
+ // Use the per-geometryGroup custom attribute arrays which are setup in initMeshBuffers
+
+ if ( geometryGroup.__webglCustomAttributesList ) {
+
+ for ( i = 0, il = geometryGroup.__webglCustomAttributesList.length; i < il; i ++ ) {
+
+ attribute = geometryGroup.__webglCustomAttributesList[ i ];
+
+ if ( attributes[ attribute.buffer.belongsToAttribute ] >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attribute.buffer );
+ enableAttribute( attributes[ attribute.buffer.belongsToAttribute ] );
+ _gl.vertexAttribPointer( attributes[ attribute.buffer.belongsToAttribute ], attribute.size, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ }
+
+ }
+
+
+ // colors
+
+ if ( attributes.color >= 0 ) {
+
+ if ( object.geometry.colors.length > 0 || object.geometry.faces.length > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer );
+ enableAttribute( attributes.color );
+ _gl.vertexAttribPointer( attributes.color, 3, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues ) {
+
+
+ _gl.vertexAttrib3fv( attributes.color, material.defaultAttributeValues.color );
+
+ }
+
+ }
+
+ // normals
+
+ if ( attributes.normal >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer );
+ enableAttribute( attributes.normal );
+ _gl.vertexAttribPointer( attributes.normal, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ // tangents
+
+ if ( attributes.tangent >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer );
+ enableAttribute( attributes.tangent );
+ _gl.vertexAttribPointer( attributes.tangent, 4, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ // uvs
+
+ if ( attributes.uv >= 0 ) {
+
+ if ( object.geometry.faceVertexUvs[0] ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer );
+ enableAttribute( attributes.uv );
+ _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues ) {
+
+
+ _gl.vertexAttrib2fv( attributes.uv, material.defaultAttributeValues.uv );
+
+ }
+
+ }
+
+ if ( attributes.uv2 >= 0 ) {
+
+ if ( object.geometry.faceVertexUvs[1] ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer );
+ enableAttribute( attributes.uv2 );
+ _gl.vertexAttribPointer( attributes.uv2, 2, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues ) {
+
+
+ _gl.vertexAttrib2fv( attributes.uv2, material.defaultAttributeValues.uv2 );
+
+ }
+
+ }
+
+ if ( material.skinning &&
+ attributes.skinIndex >= 0 && attributes.skinWeight >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer );
+ enableAttribute( attributes.skinIndex );
+ _gl.vertexAttribPointer( attributes.skinIndex, 4, _gl.FLOAT, false, 0, 0 );
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer );
+ enableAttribute( attributes.skinWeight );
+ _gl.vertexAttribPointer( attributes.skinWeight, 4, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ // line distances
+
+ if ( attributes.lineDistance >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglLineDistanceBuffer );
+ enableAttribute( attributes.lineDistance );
+ _gl.vertexAttribPointer( attributes.lineDistance, 1, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ }
+
+ // render mesh
+
+ if ( object instanceof THREE.Mesh ) {
+
+ // wireframe
+
+ if ( material.wireframe ) {
+
+ setLineWidth( material.wireframeLinewidth );
+
+ if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer );
+ _gl.drawElements( _gl.LINES, geometryGroup.__webglLineCount, _gl.UNSIGNED_SHORT, 0 );
+
+ // triangles
+
+ } else {
+
+ if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer );
+ _gl.drawElements( _gl.TRIANGLES, geometryGroup.__webglFaceCount, _gl.UNSIGNED_SHORT, 0 );
+
+ }
+
+ _this.info.render.calls ++;
+ _this.info.render.vertices += geometryGroup.__webglFaceCount;
+ _this.info.render.faces += geometryGroup.__webglFaceCount / 3;
+
+ // render lines
+
+ } else if ( object instanceof THREE.Line ) {
+
+ var primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES;
+
+ setLineWidth( material.linewidth );
+
+ _gl.drawArrays( primitives, 0, geometryGroup.__webglLineCount );
+
+ _this.info.render.calls ++;
+
+ // render particles
+
+ } else if ( object instanceof THREE.ParticleSystem ) {
+
+ _gl.drawArrays( _gl.POINTS, 0, geometryGroup.__webglParticleCount );
+
+ _this.info.render.calls ++;
+ _this.info.render.points += geometryGroup.__webglParticleCount;
+
+ }
+
+ };
+
+ function enableAttribute( attribute ) {
+
+ if ( ! _enabledAttributes[ attribute ] ) {
+
+ _gl.enableVertexAttribArray( attribute );
+ _enabledAttributes[ attribute ] = true;
+
+ }
+
+ };
+
+ function disableAttributes() {
+
+ for ( var attribute in _enabledAttributes ) {
+
+ if ( _enabledAttributes[ attribute ] ) {
+
+ _gl.disableVertexAttribArray( attribute );
+ _enabledAttributes[ attribute ] = false;
+
+ }
+
+ }
+
+ };
+
+ function setupMorphTargets ( material, geometryGroup, object ) {
+
+ // set base
+
+ var attributes = material.program.attributes;
+
+ if ( object.morphTargetBase !== -1 && attributes.position >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ object.morphTargetBase ] );
+ enableAttribute( attributes.position );
+ _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( attributes.position >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+ enableAttribute( attributes.position );
+ _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( object.morphTargetForcedOrder.length ) {
+
+ // set forced order
+
+ var m = 0;
+ var order = object.morphTargetForcedOrder;
+ var influences = object.morphTargetInfluences;
+
+ while ( m < material.numSupportedMorphTargets && m < order.length ) {
+
+ if ( attributes[ "morphTarget" + m ] >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ order[ m ] ] );
+ enableAttribute( attributes[ "morphTarget" + m ] );
+ _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ order[ m ] ] );
+ enableAttribute( attributes[ "morphNormal" + m ] );
+ _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ object.__webglMorphTargetInfluences[ m ] = influences[ order[ m ] ];
+
+ m ++;
+ }
+
+ } else {
+
+ // find the most influencing
+
+ var influence, activeInfluenceIndices = [];
+ var influences = object.morphTargetInfluences;
+ var i, il = influences.length;
+
+ for ( i = 0; i < il; i ++ ) {
+
+ influence = influences[ i ];
+
+ if ( influence > 0 ) {
+
+ activeInfluenceIndices.push( [ influence, i ] );
+
+ }
+
+ }
+
+ if ( activeInfluenceIndices.length > material.numSupportedMorphTargets ) {
+
+ activeInfluenceIndices.sort( numericalSort );
+ activeInfluenceIndices.length = material.numSupportedMorphTargets;
+
+ } else if ( activeInfluenceIndices.length > material.numSupportedMorphNormals ) {
+
+ activeInfluenceIndices.sort( numericalSort );
+
+ } else if ( activeInfluenceIndices.length === 0 ) {
+
+ activeInfluenceIndices.push( [ 0, 0 ] );
+
+ };
+
+ var influenceIndex, m = 0;
+
+ while ( m < material.numSupportedMorphTargets ) {
+
+ if ( activeInfluenceIndices[ m ] ) {
+
+ influenceIndex = activeInfluenceIndices[ m ][ 1 ];
+
+ if ( attributes[ "morphTarget" + m ] >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ influenceIndex ] );
+ enableAttribute( attributes[ "morphTarget" + m ] );
+ _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ influenceIndex ] );
+ enableAttribute( attributes[ "morphNormal" + m ] );
+ _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+
+ }
+
+ object.__webglMorphTargetInfluences[ m ] = influences[ influenceIndex ];
+
+ } else {
+
+ /*
+ _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ if ( material.morphNormals ) {
+
+ _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+ */
+
+ object.__webglMorphTargetInfluences[ m ] = 0;
+
+ }
+
+ m ++;
+
+ }
+
+ }
+
+ // load updated influences uniform
+
+ if ( material.program.uniforms.morphTargetInfluences !== null ) {
+
+ _gl.uniform1fv( material.program.uniforms.morphTargetInfluences, object.__webglMorphTargetInfluences );
+
+ }
+
+ };
+
+ // Sorting
+
+ function painterSortStable ( a, b ) {
+
+ if ( a.z !== b.z ) {
+
+ return b.z - a.z;
+
+ } else {
+
+ return a.id - b.id;
+
+ }
+
+ };
+
+ function numericalSort ( a, b ) {
+
+ return b[ 0 ] - a[ 0 ];
+
+ };
+
+
+ // Rendering
+
+ this.render = function ( scene, camera, renderTarget, forceClear ) {
+
+ if ( camera instanceof THREE.Camera === false ) {
+
+ console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
+ return;
+
+ }
+
+ var i, il,
+
+ webglObject, object,
+ renderList,
+
+ lights = scene.__lights,
+ fog = scene.fog;
+
+ // reset caching for this frame
+
+ _currentMaterialId = -1;
+ _lightsNeedUpdate = true;
+
+ // update scene graph
+
+ if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+ // update camera matrices and frustum
+
+ if ( camera.parent === undefined ) camera.updateMatrixWorld();
+
+ camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+
+ _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+ _frustum.setFromMatrix( _projScreenMatrix );
+
+ // update WebGL objects
+
+ if ( this.autoUpdateObjects ) this.initWebGLObjects( scene );
+
+ // custom render plugins (pre pass)
+
+ renderPlugins( this.renderPluginsPre, scene, camera );
+
+ //
+
+ _this.info.render.calls = 0;
+ _this.info.render.vertices = 0;
+ _this.info.render.faces = 0;
+ _this.info.render.points = 0;
+
+ this.setRenderTarget( renderTarget );
+
+ if ( this.autoClear || forceClear ) {
+
+ this.clear( this.autoClearColor, this.autoClearDepth, this.autoClearStencil );
+
+ }
+
+ // set matrices for regular objects (frustum culled)
+
+ renderList = scene.__webglObjects;
+
+ for ( i = 0, il = renderList.length; i < il; i ++ ) {
+
+ webglObject = renderList[ i ];
+ object = webglObject.object;
+
+ webglObject.id = i;
+ webglObject.render = false;
+
+ if ( object.visible ) {
+
+ if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) {
+
+ setupMatrices( object, camera );
+
+ unrollBufferMaterial( webglObject );
+
+ webglObject.render = true;
+
+ if ( this.sortObjects === true ) {
+
+ if ( object.renderDepth !== null ) {
+
+ webglObject.z = object.renderDepth;
+
+ } else {
+
+ _vector3.getPositionFromMatrix( object.matrixWorld );
+ _vector3.applyProjection( _projScreenMatrix );
+
+ webglObject.z = _vector3.z;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( this.sortObjects ) {
+
+ renderList.sort( painterSortStable );
+
+ }
+
+ // set matrices for immediate objects
+
+ renderList = scene.__webglObjectsImmediate;
+
+ for ( i = 0, il = renderList.length; i < il; i ++ ) {
+
+ webglObject = renderList[ i ];
+ object = webglObject.object;
+
+ if ( object.visible ) {
+
+ setupMatrices( object, camera );
+
+ unrollImmediateBufferMaterial( webglObject );
+
+ }
+
+ }
+
+ if ( scene.overrideMaterial ) {
+
+ var material = scene.overrideMaterial;
+
+ this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+ this.setDepthTest( material.depthTest );
+ this.setDepthWrite( material.depthWrite );
+ setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+ renderObjects( scene.__webglObjects, false, "", camera, lights, fog, true, material );
+ renderObjectsImmediate( scene.__webglObjectsImmediate, "", camera, lights, fog, false, material );
+
+ } else {
+
+ var material = null;
+
+ // opaque pass (front-to-back order)
+
+ this.setBlending( THREE.NoBlending );
+
+ renderObjects( scene.__webglObjects, true, "opaque", camera, lights, fog, false, material );
+ renderObjectsImmediate( scene.__webglObjectsImmediate, "opaque", camera, lights, fog, false, material );
+
+ // transparent pass (back-to-front order)
+
+ renderObjects( scene.__webglObjects, false, "transparent", camera, lights, fog, true, material );
+ renderObjectsImmediate( scene.__webglObjectsImmediate, "transparent", camera, lights, fog, true, material );
+
+ }
+
+ // custom render plugins (post pass)
+
+ renderPlugins( this.renderPluginsPost, scene, camera );
+
+
+ // Generate mipmap if we're using any kind of mipmap filtering
+
+ if ( renderTarget && renderTarget.generateMipmaps && renderTarget.minFilter !== THREE.NearestFilter && renderTarget.minFilter !== THREE.LinearFilter ) {
+
+ updateRenderTargetMipmap( renderTarget );
+
+ }
+
+ // Ensure depth buffer writing is enabled so it can be cleared on next render
+
+ this.setDepthTest( true );
+ this.setDepthWrite( true );
+
+ // _gl.finish();
+
+ };
+
+ function renderPlugins( plugins, scene, camera ) {
+
+ if ( ! plugins.length ) return;
+
+ for ( var i = 0, il = plugins.length; i < il; i ++ ) {
+
+ // reset state for plugin (to start from clean slate)
+
+ _currentProgram = null;
+ _currentCamera = null;
+
+ _oldBlending = -1;
+ _oldDepthTest = -1;
+ _oldDepthWrite = -1;
+ _oldDoubleSided = -1;
+ _oldFlipSided = -1;
+ _currentGeometryGroupHash = -1;
+ _currentMaterialId = -1;
+
+ _lightsNeedUpdate = true;
+
+ plugins[ i ].render( scene, camera, _currentWidth, _currentHeight );
+
+ // reset state after plugin (anything could have changed)
+
+ _currentProgram = null;
+ _currentCamera = null;
+
+ _oldBlending = -1;
+ _oldDepthTest = -1;
+ _oldDepthWrite = -1;
+ _oldDoubleSided = -1;
+ _oldFlipSided = -1;
+ _currentGeometryGroupHash = -1;
+ _currentMaterialId = -1;
+
+ _lightsNeedUpdate = true;
+
+ }
+
+ };
+
+ function renderObjects ( renderList, reverse, materialType, camera, lights, fog, useBlending, overrideMaterial ) {
+
+ var webglObject, object, buffer, material, start, end, delta;
+
+ if ( reverse ) {
+
+ start = renderList.length - 1;
+ end = -1;
+ delta = -1;
+
+ } else {
+
+ start = 0;
+ end = renderList.length;
+ delta = 1;
+ }
+
+ for ( var i = start; i !== end; i += delta ) {
+
+ webglObject = renderList[ i ];
+
+ if ( webglObject.render ) {
+
+ object = webglObject.object;
+ buffer = webglObject.buffer;
+
+ if ( overrideMaterial ) {
+
+ material = overrideMaterial;
+
+ } else {
+
+ material = webglObject[ materialType ];
+
+ if ( ! material ) continue;
+
+ if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+
+ _this.setDepthTest( material.depthTest );
+ _this.setDepthWrite( material.depthWrite );
+ setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+ }
+
+ _this.setMaterialFaces( material );
+
+ if ( buffer instanceof THREE.BufferGeometry ) {
+
+ _this.renderBufferDirect( camera, lights, fog, material, buffer, object );
+
+ } else {
+
+ _this.renderBuffer( camera, lights, fog, material, buffer, object );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ function renderObjectsImmediate ( renderList, materialType, camera, lights, fog, useBlending, overrideMaterial ) {
+
+ var webglObject, object, material, program;
+
+ for ( var i = 0, il = renderList.length; i < il; i ++ ) {
+
+ webglObject = renderList[ i ];
+ object = webglObject.object;
+
+ if ( object.visible ) {
+
+ if ( overrideMaterial ) {
+
+ material = overrideMaterial;
+
+ } else {
+
+ material = webglObject[ materialType ];
+
+ if ( ! material ) continue;
+
+ if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+
+ _this.setDepthTest( material.depthTest );
+ _this.setDepthWrite( material.depthWrite );
+ setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+ }
+
+ _this.renderImmediateObject( camera, lights, fog, material, object );
+
+ }
+
+ }
+
+ };
+
+ this.renderImmediateObject = function ( camera, lights, fog, material, object ) {
+
+ var program = setProgram( camera, lights, fog, material, object );
+
+ _currentGeometryGroupHash = -1;
+
+ _this.setMaterialFaces( material );
+
+ if ( object.immediateRenderCallback ) {
+
+ object.immediateRenderCallback( program, _gl, _frustum );
+
+ } else {
+
+ object.render( function( object ) { _this.renderBufferImmediate( object, program, material ); } );
+
+ }
+
+ };
+
+ function unrollImmediateBufferMaterial ( globject ) {
+
+ var object = globject.object,
+ material = object.material;
+
+ if ( material.transparent ) {
+
+ globject.transparent = material;
+ globject.opaque = null;
+
+ } else {
+
+ globject.opaque = material;
+ globject.transparent = null;
+
+ }
+
+ };
+
+ function unrollBufferMaterial ( globject ) {
+
+ var object = globject.object,
+ buffer = globject.buffer,
+ material, materialIndex, meshMaterial;
+
+ meshMaterial = object.material;
+
+ if ( meshMaterial instanceof THREE.MeshFaceMaterial ) {
+
+ materialIndex = buffer.materialIndex;
+
+ material = meshMaterial.materials[ materialIndex ];
+
+ if ( material.transparent ) {
+
+ globject.transparent = material;
+ globject.opaque = null;
+
+ } else {
+
+ globject.opaque = material;
+ globject.transparent = null;
+
+ }
+
+ } else {
+
+ material = meshMaterial;
+
+ if ( material ) {
+
+ if ( material.transparent ) {
+
+ globject.transparent = material;
+ globject.opaque = null;
+
+ } else {
+
+ globject.opaque = material;
+ globject.transparent = null;
+
+ }
+
+ }
+
+ }
+
+ };
+
+ // Geometry splitting
+
+ function sortFacesByMaterial ( geometry, material ) {
+
+ var f, fl, face, materialIndex, vertices,
+ groupHash, hash_map = {};
+
+ var numMorphTargets = geometry.morphTargets.length;
+ var numMorphNormals = geometry.morphNormals.length;
+
+ var usesFaceMaterial = material instanceof THREE.MeshFaceMaterial;
+
+ geometry.geometryGroups = {};
+
+ for ( f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
+
+ face = geometry.faces[ f ];
+ materialIndex = usesFaceMaterial ? face.materialIndex : 0;
+
+ if ( hash_map[ materialIndex ] === undefined ) {
+
+ hash_map[ materialIndex ] = { 'hash': materialIndex, 'counter': 0 };
+
+ }
+
+ groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter;
+
+ if ( geometry.geometryGroups[ groupHash ] === undefined ) {
+
+ geometry.geometryGroups[ groupHash ] = { 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals };
+
+ }
+
+ vertices = 3;
+
+ if ( geometry.geometryGroups[ groupHash ].vertices + vertices > 65535 ) {
+
+ hash_map[ materialIndex ].counter += 1;
+ groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter;
+
+ if ( geometry.geometryGroups[ groupHash ] === undefined ) {
+
+ geometry.geometryGroups[ groupHash ] = { 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals };
+
+ }
+
+ }
+
+ geometry.geometryGroups[ groupHash ].faces3.push( f );
+ geometry.geometryGroups[ groupHash ].vertices += vertices;
+
+ }
+
+ geometry.geometryGroupsList = [];
+
+ for ( var g in geometry.geometryGroups ) {
+
+ geometry.geometryGroups[ g ].id = _geometryGroupCounter ++;
+
+ geometry.geometryGroupsList.push( geometry.geometryGroups[ g ] );
+
+ }
+
+ };
+
+ // Objects refresh
+
+ this.initWebGLObjects = function ( scene ) {
+
+ if ( !scene.__webglObjects ) {
+
+ scene.__webglObjects = [];
+ scene.__webglObjectsImmediate = [];
+ scene.__webglSprites = [];
+ scene.__webglFlares = [];
+
+ }
+
+ while ( scene.__objectsAdded.length ) {
+
+ addObject( scene.__objectsAdded[ 0 ], scene );
+ scene.__objectsAdded.splice( 0, 1 );
+
+ }
+
+ while ( scene.__objectsRemoved.length ) {
+
+ removeObject( scene.__objectsRemoved[ 0 ], scene );
+ scene.__objectsRemoved.splice( 0, 1 );
+
+ }
+
+ // update must be called after objects adding / removal
+
+ for ( var o = 0, ol = scene.__webglObjects.length; o < ol; o ++ ) {
+
+ var object = scene.__webglObjects[ o ].object;
+
+ // TODO: Remove this hack (WebGLRenderer refactoring)
+
+ if ( object.__webglInit === undefined ) {
+
+ if ( object.__webglActive !== undefined ) {
+
+ removeObject( object, scene );
+
+ }
+
+ addObject( object, scene );
+
+ }
+
+ updateObject( object );
+
+ }
+
+ };
+
+ // Objects adding
+
+ function addObject( object, scene ) {
+
+ var g, geometry, material, geometryGroup;
+
+ if ( object.__webglInit === undefined ) {
+
+ object.__webglInit = true;
+
+ object._modelViewMatrix = new THREE.Matrix4();
+ object._normalMatrix = new THREE.Matrix3();
+
+ if ( object.geometry !== undefined && object.geometry.__webglInit === undefined ) {
+
+ object.geometry.__webglInit = true;
+ object.geometry.addEventListener( 'dispose', onGeometryDispose );
+
+ }
+
+ geometry = object.geometry;
+
+ if ( geometry === undefined ) {
+
+ // fail silently for now
+
+ } else if ( geometry instanceof THREE.BufferGeometry ) {
+
+ initDirectBuffers( geometry );
+
+ } else if ( object instanceof THREE.Mesh ) {
+
+ material = object.material;
+
+ if ( geometry.geometryGroups === undefined ) {
+
+ sortFacesByMaterial( geometry, material );
+
+ }
+
+ // create separate VBOs per geometry chunk
+
+ for ( g in geometry.geometryGroups ) {
+
+ geometryGroup = geometry.geometryGroups[ g ];
+
+ // initialise VBO on the first access
+
+ if ( ! geometryGroup.__webglVertexBuffer ) {
+
+ createMeshBuffers( geometryGroup );
+ initMeshBuffers( geometryGroup, object );
+
+ geometry.verticesNeedUpdate = true;
+ geometry.morphTargetsNeedUpdate = true;
+ geometry.elementsNeedUpdate = true;
+ geometry.uvsNeedUpdate = true;
+ geometry.normalsNeedUpdate = true;
+ geometry.tangentsNeedUpdate = true;
+ geometry.colorsNeedUpdate = true;
+
+ }
+
+ }
+
+ } else if ( object instanceof THREE.Line ) {
+
+ if ( ! geometry.__webglVertexBuffer ) {
+
+ createLineBuffers( geometry );
+ initLineBuffers( geometry, object );
+
+ geometry.verticesNeedUpdate = true;
+ geometry.colorsNeedUpdate = true;
+ geometry.lineDistancesNeedUpdate = true;
+
+ }
+
+ } else if ( object instanceof THREE.ParticleSystem ) {
+
+ if ( ! geometry.__webglVertexBuffer ) {
+
+ createParticleBuffers( geometry );
+ initParticleBuffers( geometry, object );
+
+ geometry.verticesNeedUpdate = true;
+ geometry.colorsNeedUpdate = true;
+
+ }
+
+ }
+
+ }
+
+ if ( object.__webglActive === undefined ) {
+
+ if ( object instanceof THREE.Mesh ) {
+
+ geometry = object.geometry;
+
+ if ( geometry instanceof THREE.BufferGeometry ) {
+
+ addBuffer( scene.__webglObjects, geometry, object );
+
+ } else if ( geometry instanceof THREE.Geometry ) {
+
+ for ( g in geometry.geometryGroups ) {
+
+ geometryGroup = geometry.geometryGroups[ g ];
+
+ addBuffer( scene.__webglObjects, geometryGroup, object );
+
+ }
+
+ }
+
+ } else if ( object instanceof THREE.Line ||
+ object instanceof THREE.ParticleSystem ) {
+
+ geometry = object.geometry;
+ addBuffer( scene.__webglObjects, geometry, object );
+
+ } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
+
+ addBufferImmediate( scene.__webglObjectsImmediate, object );
+
+ } else if ( object instanceof THREE.Sprite ) {
+
+ scene.__webglSprites.push( object );
+
+ } else if ( object instanceof THREE.LensFlare ) {
+
+ scene.__webglFlares.push( object );
+
+ }
+
+ object.__webglActive = true;
+
+ }
+
+ };
+
+ function addBuffer( objlist, buffer, object ) {
+
+ objlist.push(
+ {
+ id: null,
+ buffer: buffer,
+ object: object,
+ opaque: null,
+ transparent: null,
+ z: 0
+ }
+ );
+
+ };
+
+ function addBufferImmediate( objlist, object ) {
+
+ objlist.push(
+ {
+ id: null,
+ object: object,
+ opaque: null,
+ transparent: null,
+ z: 0
+ }
+ );
+
+ };
+
+ // Objects updates
+
+ function updateObject( object ) {
+
+ var geometry = object.geometry,
+ geometryGroup, customAttributesDirty, material;
+
+ if ( geometry instanceof THREE.BufferGeometry ) {
+
+ setDirectBuffers( geometry, _gl.DYNAMIC_DRAW, !geometry.dynamic );
+
+ } else if ( object instanceof THREE.Mesh ) {
+
+ // check all geometry groups
+
+ for( var i = 0, il = geometry.geometryGroupsList.length; i < il; i ++ ) {
+
+ geometryGroup = geometry.geometryGroupsList[ i ];
+
+ material = getBufferMaterial( object, geometryGroup );
+
+ if ( geometry.buffersNeedUpdate ) {
+
+ initMeshBuffers( geometryGroup, object );
+
+ }
+
+ customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+ if ( geometry.verticesNeedUpdate || geometry.morphTargetsNeedUpdate || geometry.elementsNeedUpdate ||
+ geometry.uvsNeedUpdate || geometry.normalsNeedUpdate ||
+ geometry.colorsNeedUpdate || geometry.tangentsNeedUpdate || customAttributesDirty ) {
+
+ setMeshBuffers( geometryGroup, object, _gl.DYNAMIC_DRAW, !geometry.dynamic, material );
+
+ }
+
+ }
+
+ geometry.verticesNeedUpdate = false;
+ geometry.morphTargetsNeedUpdate = false;
+ geometry.elementsNeedUpdate = false;
+ geometry.uvsNeedUpdate = false;
+ geometry.normalsNeedUpdate = false;
+ geometry.colorsNeedUpdate = false;
+ geometry.tangentsNeedUpdate = false;
+
+ geometry.buffersNeedUpdate = false;
+
+ material.attributes && clearCustomAttributes( material );
+
+ } else if ( object instanceof THREE.Line ) {
+
+ material = getBufferMaterial( object, geometry );
+
+ customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+ if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || geometry.lineDistancesNeedUpdate || customAttributesDirty ) {
+
+ setLineBuffers( geometry, _gl.DYNAMIC_DRAW );
+
+ }
+
+ geometry.verticesNeedUpdate = false;
+ geometry.colorsNeedUpdate = false;
+ geometry.lineDistancesNeedUpdate = false;
+
+ material.attributes && clearCustomAttributes( material );
+
+
+ } else if ( object instanceof THREE.ParticleSystem ) {
+
+ material = getBufferMaterial( object, geometry );
+
+ customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+ if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || object.sortParticles || customAttributesDirty ) {
+
+ setParticleBuffers( geometry, _gl.DYNAMIC_DRAW, object );
+
+ }
+
+ geometry.verticesNeedUpdate = false;
+ geometry.colorsNeedUpdate = false;
+
+ material.attributes && clearCustomAttributes( material );
+
+ }
+
+ };
+
+ // Objects updates - custom attributes check
+
+ function areCustomAttributesDirty( material ) {
+
+ for ( var a in material.attributes ) {
+
+ if ( material.attributes[ a ].needsUpdate ) return true;
+
+ }
+
+ return false;
+
+ };
+
+ function clearCustomAttributes( material ) {
+
+ for ( var a in material.attributes ) {
+
+ material.attributes[ a ].needsUpdate = false;
+
+ }
+
+ };
+
+ // Objects removal
+
+ function removeObject( object, scene ) {
+
+ if ( object instanceof THREE.Mesh ||
+ object instanceof THREE.ParticleSystem ||
+ object instanceof THREE.Line ) {
+
+ removeInstances( scene.__webglObjects, object );
+
+ } else if ( object instanceof THREE.Sprite ) {
+
+ removeInstancesDirect( scene.__webglSprites, object );
+
+ } else if ( object instanceof THREE.LensFlare ) {
+
+ removeInstancesDirect( scene.__webglFlares, object );
+
+ } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
+
+ removeInstances( scene.__webglObjectsImmediate, object );
+
+ }
+
+ delete object.__webglActive;
+
+ };
+
+ function removeInstances( objlist, object ) {
+
+ for ( var o = objlist.length - 1; o >= 0; o -- ) {
+
+ if ( objlist[ o ].object === object ) {
+
+ objlist.splice( o, 1 );
+
+ }
+
+ }
+
+ };
+
+ function removeInstancesDirect( objlist, object ) {
+
+ for ( var o = objlist.length - 1; o >= 0; o -- ) {
+
+ if ( objlist[ o ] === object ) {
+
+ objlist.splice( o, 1 );
+
+ }
+
+ }
+
+ };
+
+ // Materials
+
+ this.initMaterial = function ( material, lights, fog, object ) {
+
+ material.addEventListener( 'dispose', onMaterialDispose );
+
+ var u, a, identifiers, i, parameters, maxLightCount, maxBones, maxShadows, shaderID;
+
+ if ( material instanceof THREE.MeshDepthMaterial ) {
+
+ shaderID = 'depth';
+
+ } else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+ shaderID = 'normal';
+
+ } else if ( material instanceof THREE.MeshBasicMaterial ) {
+
+ shaderID = 'basic';
+
+ } else if ( material instanceof THREE.MeshLambertMaterial ) {
+
+ shaderID = 'lambert';
+
+ } else if ( material instanceof THREE.MeshPhongMaterial ) {
+
+ shaderID = 'phong';
+
+ } else if ( material instanceof THREE.LineBasicMaterial ) {
+
+ shaderID = 'basic';
+
+ } else if ( material instanceof THREE.LineDashedMaterial ) {
+
+ shaderID = 'dashed';
+
+ } else if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+ shaderID = 'particle_basic';
+
+ }
+
+ if ( shaderID ) {
+
+ setMaterialShaders( material, THREE.ShaderLib[ shaderID ] );
+
+ }
+
+ // heuristics to create shader parameters according to lights in the scene
+ // (not to blow over maxLights budget)
+
+ maxLightCount = allocateLights( lights );
+
+ maxShadows = allocateShadows( lights );
+
+ maxBones = allocateBones( object );
+
+ parameters = {
+
+ map: !!material.map,
+ envMap: !!material.envMap,
+ lightMap: !!material.lightMap,
+ bumpMap: !!material.bumpMap,
+ normalMap: !!material.normalMap,
+ specularMap: !!material.specularMap,
+
+ vertexColors: material.vertexColors,
+
+ fog: fog,
+ useFog: material.fog,
+ fogExp: fog instanceof THREE.FogExp2,
+
+ sizeAttenuation: material.sizeAttenuation,
+
+ skinning: material.skinning,
+ maxBones: maxBones,
+ useVertexTexture: _supportsBoneTextures && object && object.useVertexTexture,
+
+ morphTargets: material.morphTargets,
+ morphNormals: material.morphNormals,
+ maxMorphTargets: this.maxMorphTargets,
+ maxMorphNormals: this.maxMorphNormals,
+
+ maxDirLights: maxLightCount.directional,
+ maxPointLights: maxLightCount.point,
+ maxSpotLights: maxLightCount.spot,
+ maxHemiLights: maxLightCount.hemi,
+
+ maxShadows: maxShadows,
+ shadowMapEnabled: this.shadowMapEnabled && object.receiveShadow,
+ shadowMapType: this.shadowMapType,
+ shadowMapDebug: this.shadowMapDebug,
+ shadowMapCascade: this.shadowMapCascade,
+
+ alphaTest: material.alphaTest,
+ metal: material.metal,
+ perPixel: material.perPixel,
+ wrapAround: material.wrapAround,
+ doubleSided: material.side === THREE.DoubleSide,
+ flipSided: material.side === THREE.BackSide
+
+ };
+
+ material.program = buildProgram( shaderID, material.fragmentShader, material.vertexShader, material.uniforms, material.attributes, material.defines, parameters, material.index0AttributeName );
+
+ var attributes = material.program.attributes;
+
+ if ( material.morphTargets ) {
+
+ material.numSupportedMorphTargets = 0;
+
+ var id, base = "morphTarget";
+
+ for ( i = 0; i < this.maxMorphTargets; i ++ ) {
+
+ id = base + i;
+
+ if ( attributes[ id ] >= 0 ) {
+
+ material.numSupportedMorphTargets ++;
+
+ }
+
+ }
+
+ }
+
+ if ( material.morphNormals ) {
+
+ material.numSupportedMorphNormals = 0;
+
+ var id, base = "morphNormal";
+
+ for ( i = 0; i < this.maxMorphNormals; i ++ ) {
+
+ id = base + i;
+
+ if ( attributes[ id ] >= 0 ) {
+
+ material.numSupportedMorphNormals ++;
+
+ }
+
+ }
+
+ }
+
+ material.uniformsList = [];
+
+ for ( u in material.uniforms ) {
+
+ material.uniformsList.push( [ material.uniforms[ u ], u ] );
+
+ }
+
+ };
+
+ function setMaterialShaders( material, shaders ) {
+
+ material.uniforms = THREE.UniformsUtils.clone( shaders.uniforms );
+ material.vertexShader = shaders.vertexShader;
+ material.fragmentShader = shaders.fragmentShader;
+
+ };
+
+ function setProgram( camera, lights, fog, material, object ) {
+
+ _usedTextureUnits = 0;
+
+ if ( material.needsUpdate ) {
+
+ if ( material.program ) deallocateMaterial( material );
+
+ _this.initMaterial( material, lights, fog, object );
+ material.needsUpdate = false;
+
+ }
+
+ if ( material.morphTargets ) {
+
+ if ( ! object.__webglMorphTargetInfluences ) {
+
+ object.__webglMorphTargetInfluences = new Float32Array( _this.maxMorphTargets );
+
+ }
+
+ }
+
+ var refreshMaterial = false;
+
+ var program = material.program,
+ p_uniforms = program.uniforms,
+ m_uniforms = material.uniforms;
+
+ if ( program !== _currentProgram ) {
+
+ _gl.useProgram( program );
+ _currentProgram = program;
+
+ refreshMaterial = true;
+
+ }
+
+ if ( material.id !== _currentMaterialId ) {
+
+ _currentMaterialId = material.id;
+ refreshMaterial = true;
+
+ }
+
+ if ( refreshMaterial || camera !== _currentCamera ) {
+
+ _gl.uniformMatrix4fv( p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements );
+
+ if ( camera !== _currentCamera ) _currentCamera = camera;
+
+ }
+
+ // skinning uniforms must be set even if material didn't change
+ // auto-setting of texture unit for bone texture must go before other textures
+ // not sure why, but otherwise weird things happen
+
+ if ( material.skinning ) {
+
+ if ( _supportsBoneTextures && object.useVertexTexture ) {
+
+ if ( p_uniforms.boneTexture !== null ) {
+
+ var textureUnit = getTextureUnit();
+
+ _gl.uniform1i( p_uniforms.boneTexture, textureUnit );
+ _this.setTexture( object.boneTexture, textureUnit );
+
+ }
+
+ if ( p_uniforms.boneTextureWidth !== null ) {
+
+ _gl.uniform1i( p_uniforms.boneTextureWidth, object.boneTextureWidth );
+
+ }
+
+ if ( p_uniforms.boneTextureHeight !== null ) {
+
+ _gl.uniform1i( p_uniforms.boneTextureHeight, object.boneTextureHeight );
+
+ }
+
+ } else {
+
+ if ( p_uniforms.boneGlobalMatrices !== null ) {
+
+ _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.boneMatrices );
+
+ }
+
+ }
+
+ }
+
+ if ( refreshMaterial ) {
+
+ // refresh uniforms common to several materials
+
+ if ( fog && material.fog ) {
+
+ refreshUniformsFog( m_uniforms, fog );
+
+ }
+
+ if ( material instanceof THREE.MeshPhongMaterial ||
+ material instanceof THREE.MeshLambertMaterial ||
+ material.lights ) {
+
+ if ( _lightsNeedUpdate ) {
+
+ setupLights( program, lights );
+ _lightsNeedUpdate = false;
+
+ }
+
+ refreshUniformsLights( m_uniforms, _lights );
+
+ }
+
+ if ( material instanceof THREE.MeshBasicMaterial ||
+ material instanceof THREE.MeshLambertMaterial ||
+ material instanceof THREE.MeshPhongMaterial ) {
+
+ refreshUniformsCommon( m_uniforms, material );
+
+ }
+
+ // refresh single material specific uniforms
+
+ if ( material instanceof THREE.LineBasicMaterial ) {
+
+ refreshUniformsLine( m_uniforms, material );
+
+ } else if ( material instanceof THREE.LineDashedMaterial ) {
+
+ refreshUniformsLine( m_uniforms, material );
+ refreshUniformsDash( m_uniforms, material );
+
+ } else if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+ refreshUniformsParticle( m_uniforms, material );
+
+ } else if ( material instanceof THREE.MeshPhongMaterial ) {
+
+ refreshUniformsPhong( m_uniforms, material );
+
+ } else if ( material instanceof THREE.MeshLambertMaterial ) {
+
+ refreshUniformsLambert( m_uniforms, material );
+
+ } else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+ m_uniforms.mNear.value = camera.near;
+ m_uniforms.mFar.value = camera.far;
+ m_uniforms.opacity.value = material.opacity;
+
+ } else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+ m_uniforms.opacity.value = material.opacity;
+
+ }
+
+ if ( object.receiveShadow && ! material._shadowPass ) {
+
+ refreshUniformsShadow( m_uniforms, lights );
+
+ }
+
+ // load common uniforms
+
+ loadUniformsGeneric( program, material.uniformsList );
+
+ // load material specific uniforms
+ // (shader material also gets them for the sake of genericity)
+
+ if ( material instanceof THREE.ShaderMaterial ||
+ material instanceof THREE.MeshPhongMaterial ||
+ material.envMap ) {
+
+ if ( p_uniforms.cameraPosition !== null ) {
+
+ _vector3.getPositionFromMatrix( camera.matrixWorld );
+ _gl.uniform3f( p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z );
+
+ }
+
+ }
+
+ if ( material instanceof THREE.MeshPhongMaterial ||
+ material instanceof THREE.MeshLambertMaterial ||
+ material instanceof THREE.ShaderMaterial ||
+ material.skinning ) {
+
+ if ( p_uniforms.viewMatrix !== null ) {
+
+ _gl.uniformMatrix4fv( p_uniforms.viewMatrix, false, camera.matrixWorldInverse.elements );
+
+ }
+
+ }
+
+ }
+
+ loadUniformsMatrices( p_uniforms, object );
+
+ if ( p_uniforms.modelMatrix !== null ) {
+
+ _gl.uniformMatrix4fv( p_uniforms.modelMatrix, false, object.matrixWorld.elements );
+
+ }
+
+ return program;
+
+ };
+
+ // Uniforms (refresh uniforms objects)
+
+ function refreshUniformsCommon ( uniforms, material ) {
+
+ uniforms.opacity.value = material.opacity;
+
+ if ( _this.gammaInput ) {
+
+ uniforms.diffuse.value.copyGammaToLinear( material.color );
+
+ } else {
+
+ uniforms.diffuse.value = material.color;
+
+ }
+
+ uniforms.map.value = material.map;
+ uniforms.lightMap.value = material.lightMap;
+ uniforms.specularMap.value = material.specularMap;
+
+ if ( material.bumpMap ) {
+
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+
+ }
+
+ if ( material.normalMap ) {
+
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy( material.normalScale );
+
+ }
+
+ // uv repeat and offset setting priorities
+ // 1. color map
+ // 2. specular map
+ // 3. normal map
+ // 4. bump map
+
+ var uvScaleMap;
+
+ if ( material.map ) {
+
+ uvScaleMap = material.map;
+
+ } else if ( material.specularMap ) {
+
+ uvScaleMap = material.specularMap;
+
+ } else if ( material.normalMap ) {
+
+ uvScaleMap = material.normalMap;
+
+ } else if ( material.bumpMap ) {
+
+ uvScaleMap = material.bumpMap;
+
+ }
+
+ if ( uvScaleMap !== undefined ) {
+
+ var offset = uvScaleMap.offset;
+ var repeat = uvScaleMap.repeat;
+
+ uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
+
+ }
+
+ uniforms.envMap.value = material.envMap;
+ uniforms.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : -1;
+
+ if ( _this.gammaInput ) {
+
+ //uniforms.reflectivity.value = material.reflectivity * material.reflectivity;
+ uniforms.reflectivity.value = material.reflectivity;
+
+ } else {
+
+ uniforms.reflectivity.value = material.reflectivity;
+
+ }
+
+ uniforms.refractionRatio.value = material.refractionRatio;
+ uniforms.combine.value = material.combine;
+ uniforms.useRefract.value = material.envMap && material.envMap.mapping instanceof THREE.CubeRefractionMapping;
+
+ };
+
+ function refreshUniformsLine ( uniforms, material ) {
+
+ uniforms.diffuse.value = material.color;
+ uniforms.opacity.value = material.opacity;
+
+ };
+
+ function refreshUniformsDash ( uniforms, material ) {
+
+ uniforms.dashSize.value = material.dashSize;
+ uniforms.totalSize.value = material.dashSize + material.gapSize;
+ uniforms.scale.value = material.scale;
+
+ };
+
+ function refreshUniformsParticle ( uniforms, material ) {
+
+ uniforms.psColor.value = material.color;
+ uniforms.opacity.value = material.opacity;
+ uniforms.size.value = material.size;
+ uniforms.scale.value = _canvas.height / 2.0; // TODO: Cache this.
+
+ uniforms.map.value = material.map;
+
+ };
+
+ function refreshUniformsFog ( uniforms, fog ) {
+
+ uniforms.fogColor.value = fog.color;
+
+ if ( fog instanceof THREE.Fog ) {
+
+ uniforms.fogNear.value = fog.near;
+ uniforms.fogFar.value = fog.far;
+
+ } else if ( fog instanceof THREE.FogExp2 ) {
+
+ uniforms.fogDensity.value = fog.density;
+
+ }
+
+ };
+
+ function refreshUniformsPhong ( uniforms, material ) {
+
+ uniforms.shininess.value = material.shininess;
+
+ if ( _this.gammaInput ) {
+
+ uniforms.ambient.value.copyGammaToLinear( material.ambient );
+ uniforms.emissive.value.copyGammaToLinear( material.emissive );
+ uniforms.specular.value.copyGammaToLinear( material.specular );
+
+ } else {
+
+ uniforms.ambient.value = material.ambient;
+ uniforms.emissive.value = material.emissive;
+ uniforms.specular.value = material.specular;
+
+ }
+
+ if ( material.wrapAround ) {
+
+ uniforms.wrapRGB.value.copy( material.wrapRGB );
+
+ }
+
+ };
+
+ function refreshUniformsLambert ( uniforms, material ) {
+
+ if ( _this.gammaInput ) {
+
+ uniforms.ambient.value.copyGammaToLinear( material.ambient );
+ uniforms.emissive.value.copyGammaToLinear( material.emissive );
+
+ } else {
+
+ uniforms.ambient.value = material.ambient;
+ uniforms.emissive.value = material.emissive;
+
+ }
+
+ if ( material.wrapAround ) {
+
+ uniforms.wrapRGB.value.copy( material.wrapRGB );
+
+ }
+
+ };
+
+ function refreshUniformsLights ( uniforms, lights ) {
+
+ uniforms.ambientLightColor.value = lights.ambient;
+
+ uniforms.directionalLightColor.value = lights.directional.colors;
+ uniforms.directionalLightDirection.value = lights.directional.positions;
+
+ uniforms.pointLightColor.value = lights.point.colors;
+ uniforms.pointLightPosition.value = lights.point.positions;
+ uniforms.pointLightDistance.value = lights.point.distances;
+
+ uniforms.spotLightColor.value = lights.spot.colors;
+ uniforms.spotLightPosition.value = lights.spot.positions;
+ uniforms.spotLightDistance.value = lights.spot.distances;
+ uniforms.spotLightDirection.value = lights.spot.directions;
+ uniforms.spotLightAngleCos.value = lights.spot.anglesCos;
+ uniforms.spotLightExponent.value = lights.spot.exponents;
+
+ uniforms.hemisphereLightSkyColor.value = lights.hemi.skyColors;
+ uniforms.hemisphereLightGroundColor.value = lights.hemi.groundColors;
+ uniforms.hemisphereLightDirection.value = lights.hemi.positions;
+
+ };
+
+ function refreshUniformsShadow ( uniforms, lights ) {
+
+ if ( uniforms.shadowMatrix ) {
+
+ var j = 0;
+
+ for ( var i = 0, il = lights.length; i < il; i ++ ) {
+
+ var light = lights[ i ];
+
+ if ( ! light.castShadow ) continue;
+
+ if ( light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) ) {
+
+ uniforms.shadowMap.value[ j ] = light.shadowMap;
+ uniforms.shadowMapSize.value[ j ] = light.shadowMapSize;
+
+ uniforms.shadowMatrix.value[ j ] = light.shadowMatrix;
+
+ uniforms.shadowDarkness.value[ j ] = light.shadowDarkness;
+ uniforms.shadowBias.value[ j ] = light.shadowBias;
+
+ j ++;
+
+ }
+
+ }
+
+ }
+
+ };
+
+ // Uniforms (load to GPU)
+
+ function loadUniformsMatrices ( uniforms, object ) {
+
+ _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, object._modelViewMatrix.elements );
+
+ if ( uniforms.normalMatrix ) {
+
+ _gl.uniformMatrix3fv( uniforms.normalMatrix, false, object._normalMatrix.elements );
+
+ }
+
+ };
+
+ function getTextureUnit() {
+
+ var textureUnit = _usedTextureUnits;
+
+ if ( textureUnit >= _maxTextures ) {
+
+ console.warn( "WebGLRenderer: trying to use " + textureUnit + " texture units while this GPU supports only " + _maxTextures );
+
+ }
+
+ _usedTextureUnits += 1;
+
+ return textureUnit;
+
+ };
+
+ function loadUniformsGeneric ( program, uniforms ) {
+
+ var uniform, value, type, location, texture, textureUnit, i, il, j, jl, offset;
+
+ for ( j = 0, jl = uniforms.length; j < jl; j ++ ) {
+
+ location = program.uniforms[ uniforms[ j ][ 1 ] ];
+ if ( !location ) continue;
+
+ uniform = uniforms[ j ][ 0 ];
+
+ type = uniform.type;
+ value = uniform.value;
+
+ if ( type === "i" ) { // single integer
+
+ _gl.uniform1i( location, value );
+
+ } else if ( type === "f" ) { // single float
+
+ _gl.uniform1f( location, value );
+
+ } else if ( type === "v2" ) { // single THREE.Vector2
+
+ _gl.uniform2f( location, value.x, value.y );
+
+ } else if ( type === "v3" ) { // single THREE.Vector3
+
+ _gl.uniform3f( location, value.x, value.y, value.z );
+
+ } else if ( type === "v4" ) { // single THREE.Vector4
+
+ _gl.uniform4f( location, value.x, value.y, value.z, value.w );
+
+ } else if ( type === "c" ) { // single THREE.Color
+
+ _gl.uniform3f( location, value.r, value.g, value.b );
+
+ } else if ( type === "iv1" ) { // flat array of integers (JS or typed array)
+
+ _gl.uniform1iv( location, value );
+
+ } else if ( type === "iv" ) { // flat array of integers with 3 x N size (JS or typed array)
+
+ _gl.uniform3iv( location, value );
+
+ } else if ( type === "fv1" ) { // flat array of floats (JS or typed array)
+
+ _gl.uniform1fv( location, value );
+
+ } else if ( type === "fv" ) { // flat array of floats with 3 x N size (JS or typed array)
+
+ _gl.uniform3fv( location, value );
+
+ } else if ( type === "v2v" ) { // array of THREE.Vector2
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 2 * value.length );
+
+ }
+
+ for ( i = 0, il = value.length; i < il; i ++ ) {
+
+ offset = i * 2;
+
+ uniform._array[ offset ] = value[ i ].x;
+ uniform._array[ offset + 1 ] = value[ i ].y;
+
+ }
+
+ _gl.uniform2fv( location, uniform._array );
+
+ } else if ( type === "v3v" ) { // array of THREE.Vector3
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 3 * value.length );
+
+ }
+
+ for ( i = 0, il = value.length; i < il; i ++ ) {
+
+ offset = i * 3;
+
+ uniform._array[ offset ] = value[ i ].x;
+ uniform._array[ offset + 1 ] = value[ i ].y;
+ uniform._array[ offset + 2 ] = value[ i ].z;
+
+ }
+
+ _gl.uniform3fv( location, uniform._array );
+
+ } else if ( type === "v4v" ) { // array of THREE.Vector4
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 4 * value.length );
+
+ }
+
+ for ( i = 0, il = value.length; i < il; i ++ ) {
+
+ offset = i * 4;
+
+ uniform._array[ offset ] = value[ i ].x;
+ uniform._array[ offset + 1 ] = value[ i ].y;
+ uniform._array[ offset + 2 ] = value[ i ].z;
+ uniform._array[ offset + 3 ] = value[ i ].w;
+
+ }
+
+ _gl.uniform4fv( location, uniform._array );
+
+ } else if ( type === "m4") { // single THREE.Matrix4
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 16 );
+
+ }
+
+ value.flattenToArray( uniform._array );
+ _gl.uniformMatrix4fv( location, false, uniform._array );
+
+ } else if ( type === "m4v" ) { // array of THREE.Matrix4
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 16 * value.length );
+
+ }
+
+ for ( i = 0, il = value.length; i < il; i ++ ) {
+
+ value[ i ].flattenToArrayOffset( uniform._array, i * 16 );
+
+ }
+
+ _gl.uniformMatrix4fv( location, false, uniform._array );
+
+ } else if ( type === "t" ) { // single THREE.Texture (2d or cube)
+
+ texture = value;
+ textureUnit = getTextureUnit();
+
+ _gl.uniform1i( location, textureUnit );
+
+ if ( !texture ) continue;
+
+ if ( texture.image instanceof Array && texture.image.length === 6 ) {
+
+ setCubeTexture( texture, textureUnit );
+
+ } else if ( texture instanceof THREE.WebGLRenderTargetCube ) {
+
+ setCubeTextureDynamic( texture, textureUnit );
+
+ } else {
+
+ _this.setTexture( texture, textureUnit );
+
+ }
+
+ } else if ( type === "tv" ) { // array of THREE.Texture (2d)
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = [];
+
+ }
+
+ for( i = 0, il = uniform.value.length; i < il; i ++ ) {
+
+ uniform._array[ i ] = getTextureUnit();
+
+ }
+
+ _gl.uniform1iv( location, uniform._array );
+
+ for( i = 0, il = uniform.value.length; i < il; i ++ ) {
+
+ texture = uniform.value[ i ];
+ textureUnit = uniform._array[ i ];
+
+ if ( !texture ) continue;
+
+ _this.setTexture( texture, textureUnit );
+
+ }
+
+ } else {
+
+ console.warn( 'THREE.WebGLRenderer: Unknown uniform type: ' + type );
+
+ }
+
+ }
+
+ };
+
+ function setupMatrices ( object, camera ) {
+
+ object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+ object._normalMatrix.getNormalMatrix( object._modelViewMatrix );
+
+ };
+
+ //
+
+ function setColorGamma( array, offset, color, intensitySq ) {
+
+ array[ offset ] = color.r * color.r * intensitySq;
+ array[ offset + 1 ] = color.g * color.g * intensitySq;
+ array[ offset + 2 ] = color.b * color.b * intensitySq;
+
+ };
+
+ function setColorLinear( array, offset, color, intensity ) {
+
+ array[ offset ] = color.r * intensity;
+ array[ offset + 1 ] = color.g * intensity;
+ array[ offset + 2 ] = color.b * intensity;
+
+ };
+
+ function setupLights ( program, lights ) {
+
+ var l, ll, light, n,
+ r = 0, g = 0, b = 0,
+ color, skyColor, groundColor,
+ intensity, intensitySq,
+ position,
+ distance,
+
+ zlights = _lights,
+
+ dirColors = zlights.directional.colors,
+ dirPositions = zlights.directional.positions,
+
+ pointColors = zlights.point.colors,
+ pointPositions = zlights.point.positions,
+ pointDistances = zlights.point.distances,
+
+ spotColors = zlights.spot.colors,
+ spotPositions = zlights.spot.positions,
+ spotDistances = zlights.spot.distances,
+ spotDirections = zlights.spot.directions,
+ spotAnglesCos = zlights.spot.anglesCos,
+ spotExponents = zlights.spot.exponents,
+
+ hemiSkyColors = zlights.hemi.skyColors,
+ hemiGroundColors = zlights.hemi.groundColors,
+ hemiPositions = zlights.hemi.positions,
+
+ dirLength = 0,
+ pointLength = 0,
+ spotLength = 0,
+ hemiLength = 0,
+
+ dirCount = 0,
+ pointCount = 0,
+ spotCount = 0,
+ hemiCount = 0,
+
+ dirOffset = 0,
+ pointOffset = 0,
+ spotOffset = 0,
+ hemiOffset = 0;
+
+ for ( l = 0, ll = lights.length; l < ll; l ++ ) {
+
+ light = lights[ l ];
+
+ if ( light.onlyShadow ) continue;
+
+ color = light.color;
+ intensity = light.intensity;
+ distance = light.distance;
+
+ if ( light instanceof THREE.AmbientLight ) {
+
+ if ( ! light.visible ) continue;
+
+ if ( _this.gammaInput ) {
+
+ r += color.r * color.r;
+ g += color.g * color.g;
+ b += color.b * color.b;
+
+ } else {
+
+ r += color.r;
+ g += color.g;
+ b += color.b;
+
+ }
+
+ } else if ( light instanceof THREE.DirectionalLight ) {
+
+ dirCount += 1;
+
+ if ( ! light.visible ) continue;
+
+ _direction.getPositionFromMatrix( light.matrixWorld );
+ _vector3.getPositionFromMatrix( light.target.matrixWorld );
+ _direction.sub( _vector3 );
+ _direction.normalize();
+
+ // skip lights with undefined direction
+ // these create troubles in OpenGL (making pixel black)
+
+ if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue;
+
+ dirOffset = dirLength * 3;
+
+ dirPositions[ dirOffset ] = _direction.x;
+ dirPositions[ dirOffset + 1 ] = _direction.y;
+ dirPositions[ dirOffset + 2 ] = _direction.z;
+
+ if ( _this.gammaInput ) {
+
+ setColorGamma( dirColors, dirOffset, color, intensity * intensity );
+
+ } else {
+
+ setColorLinear( dirColors, dirOffset, color, intensity );
+
+ }
+
+ dirLength += 1;
+
+ } else if ( light instanceof THREE.PointLight ) {
+
+ pointCount += 1;
+
+ if ( ! light.visible ) continue;
+
+ pointOffset = pointLength * 3;
+
+ if ( _this.gammaInput ) {
+
+ setColorGamma( pointColors, pointOffset, color, intensity * intensity );
+
+ } else {
+
+ setColorLinear( pointColors, pointOffset, color, intensity );
+
+ }
+
+ _vector3.getPositionFromMatrix( light.matrixWorld );
+
+ pointPositions[ pointOffset ] = _vector3.x;
+ pointPositions[ pointOffset + 1 ] = _vector3.y;
+ pointPositions[ pointOffset + 2 ] = _vector3.z;
+
+ pointDistances[ pointLength ] = distance;
+
+ pointLength += 1;
+
+ } else if ( light instanceof THREE.SpotLight ) {
+
+ spotCount += 1;
+
+ if ( ! light.visible ) continue;
+
+ spotOffset = spotLength * 3;
+
+ if ( _this.gammaInput ) {
+
+ setColorGamma( spotColors, spotOffset, color, intensity * intensity );
+
+ } else {
+
+ setColorLinear( spotColors, spotOffset, color, intensity );
+
+ }
+
+ _vector3.getPositionFromMatrix( light.matrixWorld );
+
+ spotPositions[ spotOffset ] = _vector3.x;
+ spotPositions[ spotOffset + 1 ] = _vector3.y;
+ spotPositions[ spotOffset + 2 ] = _vector3.z;
+
+ spotDistances[ spotLength ] = distance;
+
+ _direction.copy( _vector3 );
+ _vector3.getPositionFromMatrix( light.target.matrixWorld );
+ _direction.sub( _vector3 );
+ _direction.normalize();
+
+ spotDirections[ spotOffset ] = _direction.x;
+ spotDirections[ spotOffset + 1 ] = _direction.y;
+ spotDirections[ spotOffset + 2 ] = _direction.z;
+
+ spotAnglesCos[ spotLength ] = Math.cos( light.angle );
+ spotExponents[ spotLength ] = light.exponent;
+
+ spotLength += 1;
+
+ } else if ( light instanceof THREE.HemisphereLight ) {
+
+ hemiCount += 1;
+
+ if ( ! light.visible ) continue;
+
+ _direction.getPositionFromMatrix( light.matrixWorld );
+ _direction.normalize();
+
+ // skip lights with undefined direction
+ // these create troubles in OpenGL (making pixel black)
+
+ if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue;
+
+ hemiOffset = hemiLength * 3;
+
+ hemiPositions[ hemiOffset ] = _direction.x;
+ hemiPositions[ hemiOffset + 1 ] = _direction.y;
+ hemiPositions[ hemiOffset + 2 ] = _direction.z;
+
+ skyColor = light.color;
+ groundColor = light.groundColor;
+
+ if ( _this.gammaInput ) {
+
+ intensitySq = intensity * intensity;
+
+ setColorGamma( hemiSkyColors, hemiOffset, skyColor, intensitySq );
+ setColorGamma( hemiGroundColors, hemiOffset, groundColor, intensitySq );
+
+ } else {
+
+ setColorLinear( hemiSkyColors, hemiOffset, skyColor, intensity );
+ setColorLinear( hemiGroundColors, hemiOffset, groundColor, intensity );
+
+ }
+
+ hemiLength += 1;
+
+ }
+
+ }
+
+ // null eventual remains from removed lights
+ // (this is to avoid if in shader)
+
+ for ( l = dirLength * 3, ll = Math.max( dirColors.length, dirCount * 3 ); l < ll; l ++ ) dirColors[ l ] = 0.0;
+ for ( l = pointLength * 3, ll = Math.max( pointColors.length, pointCount * 3 ); l < ll; l ++ ) pointColors[ l ] = 0.0;
+ for ( l = spotLength * 3, ll = Math.max( spotColors.length, spotCount * 3 ); l < ll; l ++ ) spotColors[ l ] = 0.0;
+ for ( l = hemiLength * 3, ll = Math.max( hemiSkyColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiSkyColors[ l ] = 0.0;
+ for ( l = hemiLength * 3, ll = Math.max( hemiGroundColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiGroundColors[ l ] = 0.0;
+
+ zlights.directional.length = dirLength;
+ zlights.point.length = pointLength;
+ zlights.spot.length = spotLength;
+ zlights.hemi.length = hemiLength;
+
+ zlights.ambient[ 0 ] = r;
+ zlights.ambient[ 1 ] = g;
+ zlights.ambient[ 2 ] = b;
+
+ };
+
+ // GL state setting
+
+ this.setFaceCulling = function ( cullFace, frontFaceDirection ) {
+
+ if ( cullFace === THREE.CullFaceNone ) {
+
+ _gl.disable( _gl.CULL_FACE );
+
+ } else {
+
+ if ( frontFaceDirection === THREE.FrontFaceDirectionCW ) {
+
+ _gl.frontFace( _gl.CW );
+
+ } else {
+
+ _gl.frontFace( _gl.CCW );
+
+ }
+
+ if ( cullFace === THREE.CullFaceBack ) {
+
+ _gl.cullFace( _gl.BACK );
+
+ } else if ( cullFace === THREE.CullFaceFront ) {
+
+ _gl.cullFace( _gl.FRONT );
+
+ } else {
+
+ _gl.cullFace( _gl.FRONT_AND_BACK );
+
+ }
+
+ _gl.enable( _gl.CULL_FACE );
+
+ }
+
+ };
+
+ this.setMaterialFaces = function ( material ) {
+
+ var doubleSided = material.side === THREE.DoubleSide;
+ var flipSided = material.side === THREE.BackSide;
+
+ if ( _oldDoubleSided !== doubleSided ) {
+
+ if ( doubleSided ) {
+
+ _gl.disable( _gl.CULL_FACE );
+
+ } else {
+
+ _gl.enable( _gl.CULL_FACE );
+
+ }
+
+ _oldDoubleSided = doubleSided;
+
+ }
+
+ if ( _oldFlipSided !== flipSided ) {
+
+ if ( flipSided ) {
+
+ _gl.frontFace( _gl.CW );
+
+ } else {
+
+ _gl.frontFace( _gl.CCW );
+
+ }
+
+ _oldFlipSided = flipSided;
+
+ }
+
+ };
+
+ this.setDepthTest = function ( depthTest ) {
+
+ if ( _oldDepthTest !== depthTest ) {
+
+ if ( depthTest ) {
+
+ _gl.enable( _gl.DEPTH_TEST );
+
+ } else {
+
+ _gl.disable( _gl.DEPTH_TEST );
+
+ }
+
+ _oldDepthTest = depthTest;
+
+ }
+
+ };
+
+ this.setDepthWrite = function ( depthWrite ) {
+
+ if ( _oldDepthWrite !== depthWrite ) {
+
+ _gl.depthMask( depthWrite );
+ _oldDepthWrite = depthWrite;
+
+ }
+
+ };
+
+ function setLineWidth ( width ) {
+
+ if ( width !== _oldLineWidth ) {
+
+ _gl.lineWidth( width );
+
+ _oldLineWidth = width;
+
+ }
+
+ };
+
+ function setPolygonOffset ( polygonoffset, factor, units ) {
+
+ if ( _oldPolygonOffset !== polygonoffset ) {
+
+ if ( polygonoffset ) {
+
+ _gl.enable( _gl.POLYGON_OFFSET_FILL );
+
+ } else {
+
+ _gl.disable( _gl.POLYGON_OFFSET_FILL );
+
+ }
+
+ _oldPolygonOffset = polygonoffset;
+
+ }
+
+ if ( polygonoffset && ( _oldPolygonOffsetFactor !== factor || _oldPolygonOffsetUnits !== units ) ) {
+
+ _gl.polygonOffset( factor, units );
+
+ _oldPolygonOffsetFactor = factor;
+ _oldPolygonOffsetUnits = units;
+
+ }
+
+ };
+
+ this.setBlending = function ( blending, blendEquation, blendSrc, blendDst ) {
+
+ if ( blending !== _oldBlending ) {
+
+ if ( blending === THREE.NoBlending ) {
+
+ _gl.disable( _gl.BLEND );
+
+ } else if ( blending === THREE.AdditiveBlending ) {
+
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquation( _gl.FUNC_ADD );
+ _gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE );
+
+ } else if ( blending === THREE.SubtractiveBlending ) {
+
+ // TODO: Find blendFuncSeparate() combination
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquation( _gl.FUNC_ADD );
+ _gl.blendFunc( _gl.ZERO, _gl.ONE_MINUS_SRC_COLOR );
+
+ } else if ( blending === THREE.MultiplyBlending ) {
+
+ // TODO: Find blendFuncSeparate() combination
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquation( _gl.FUNC_ADD );
+ _gl.blendFunc( _gl.ZERO, _gl.SRC_COLOR );
+
+ } else if ( blending === THREE.CustomBlending ) {
+
+ _gl.enable( _gl.BLEND );
+
+ } else {
+
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquationSeparate( _gl.FUNC_ADD, _gl.FUNC_ADD );
+ _gl.blendFuncSeparate( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA );
+
+ }
+
+ _oldBlending = blending;
+
+ }
+
+ if ( blending === THREE.CustomBlending ) {
+
+ if ( blendEquation !== _oldBlendEquation ) {
+
+ _gl.blendEquation( paramThreeToGL( blendEquation ) );
+
+ _oldBlendEquation = blendEquation;
+
+ }
+
+ if ( blendSrc !== _oldBlendSrc || blendDst !== _oldBlendDst ) {
+
+ _gl.blendFunc( paramThreeToGL( blendSrc ), paramThreeToGL( blendDst ) );
+
+ _oldBlendSrc = blendSrc;
+ _oldBlendDst = blendDst;
+
+ }
+
+ } else {
+
+ _oldBlendEquation = null;
+ _oldBlendSrc = null;
+ _oldBlendDst = null;
+
+ }
+
+ };
+
+ // Defines
+
+ function generateDefines ( defines ) {
+
+ var value, chunk, chunks = [];
+
+ for ( var d in defines ) {
+
+ value = defines[ d ];
+ if ( value === false ) continue;
+
+ chunk = "#define " + d + " " + value;
+ chunks.push( chunk );
+
+ }
+
+ return chunks.join( "\n" );
+
+ };
+
+ // Shaders
+
+ function buildProgram ( shaderID, fragmentShader, vertexShader, uniforms, attributes, defines, parameters, index0AttributeName ) {
+
+ var p, pl, d, program, code;
+ var chunks = [];
+
+ // Generate code
+
+ if ( shaderID ) {
+
+ chunks.push( shaderID );
+
+ } else {
+
+ chunks.push( fragmentShader );
+ chunks.push( vertexShader );
+
+ }
+
+ for ( d in defines ) {
+
+ chunks.push( d );
+ chunks.push( defines[ d ] );
+
+ }
+
+ for ( p in parameters ) {
+
+ chunks.push( p );
+ chunks.push( parameters[ p ] );
+
+ }
+
+ code = chunks.join();
+
+ // Check if code has been already compiled
+
+ for ( p = 0, pl = _programs.length; p < pl; p ++ ) {
+
+ var programInfo = _programs[ p ];
+
+ if ( programInfo.code === code ) {
+
+ // console.log( "Code already compiled." /*: \n\n" + code*/ );
+
+ programInfo.usedTimes ++;
+
+ return programInfo.program;
+
+ }
+
+ }
+
+ var shadowMapTypeDefine = "SHADOWMAP_TYPE_BASIC";
+
+ if ( parameters.shadowMapType === THREE.PCFShadowMap ) {
+
+ shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF";
+
+ } else if ( parameters.shadowMapType === THREE.PCFSoftShadowMap ) {
+
+ shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF_SOFT";
+
+ }
+
+ // console.log( "building new program " );
+
+ //
+
+ var customDefines = generateDefines( defines );
+
+ //
+
+ program = _gl.createProgram();
+
+ var prefix_vertex = [
+
+ "precision " + _precision + " float;",
+ "precision " + _precision + " int;",
+
+ customDefines,
+
+ _supportsVertexTextures ? "#define VERTEX_TEXTURES" : "",
+
+ _this.gammaInput ? "#define GAMMA_INPUT" : "",
+ _this.gammaOutput ? "#define GAMMA_OUTPUT" : "",
+ _this.physicallyBasedShading ? "#define PHYSICALLY_BASED_SHADING" : "",
+
+ "#define MAX_DIR_LIGHTS " + parameters.maxDirLights,
+ "#define MAX_POINT_LIGHTS " + parameters.maxPointLights,
+ "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights,
+ "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights,
+
+ "#define MAX_SHADOWS " + parameters.maxShadows,
+
+ "#define MAX_BONES " + parameters.maxBones,
+
+ parameters.map ? "#define USE_MAP" : "",
+ parameters.envMap ? "#define USE_ENVMAP" : "",
+ parameters.lightMap ? "#define USE_LIGHTMAP" : "",
+ parameters.bumpMap ? "#define USE_BUMPMAP" : "",
+ parameters.normalMap ? "#define USE_NORMALMAP" : "",
+ parameters.specularMap ? "#define USE_SPECULARMAP" : "",
+ parameters.vertexColors ? "#define USE_COLOR" : "",
+
+ parameters.skinning ? "#define USE_SKINNING" : "",
+ parameters.useVertexTexture ? "#define BONE_TEXTURE" : "",
+
+ parameters.morphTargets ? "#define USE_MORPHTARGETS" : "",
+ parameters.morphNormals ? "#define USE_MORPHNORMALS" : "",
+ parameters.perPixel ? "#define PHONG_PER_PIXEL" : "",
+ parameters.wrapAround ? "#define WRAP_AROUND" : "",
+ parameters.doubleSided ? "#define DOUBLE_SIDED" : "",
+ parameters.flipSided ? "#define FLIP_SIDED" : "",
+
+ parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "",
+ parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "",
+ parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "",
+ parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "",
+
+ parameters.sizeAttenuation ? "#define USE_SIZEATTENUATION" : "",
+
+ "uniform mat4 modelMatrix;",
+ "uniform mat4 modelViewMatrix;",
+ "uniform mat4 projectionMatrix;",
+ "uniform mat4 viewMatrix;",
+ "uniform mat3 normalMatrix;",
+ "uniform vec3 cameraPosition;",
+
+ "attribute vec3 position;",
+ "attribute vec3 normal;",
+ "attribute vec2 uv;",
+ "attribute vec2 uv2;",
+
+ "#ifdef USE_COLOR",
+
+ "attribute vec3 color;",
+
+ "#endif",
+
+ "#ifdef USE_MORPHTARGETS",
+
+ "attribute vec3 morphTarget0;",
+ "attribute vec3 morphTarget1;",
+ "attribute vec3 morphTarget2;",
+ "attribute vec3 morphTarget3;",
+
+ "#ifdef USE_MORPHNORMALS",
+
+ "attribute vec3 morphNormal0;",
+ "attribute vec3 morphNormal1;",
+ "attribute vec3 morphNormal2;",
+ "attribute vec3 morphNormal3;",
+
+ "#else",
+
+ "attribute vec3 morphTarget4;",
+ "attribute vec3 morphTarget5;",
+ "attribute vec3 morphTarget6;",
+ "attribute vec3 morphTarget7;",
+
+ "#endif",
+
+ "#endif",
+
+ "#ifdef USE_SKINNING",
+
+ "attribute vec4 skinIndex;",
+ "attribute vec4 skinWeight;",
+
+ "#endif",
+
+ ""
+
+ ].join("\n");
+
+ var prefix_fragment = [
+
+ "precision " + _precision + " float;",
+ "precision " + _precision + " int;",
+
+ ( parameters.bumpMap || parameters.normalMap ) ? "#extension GL_OES_standard_derivatives : enable" : "",
+
+ customDefines,
+
+ "#define MAX_DIR_LIGHTS " + parameters.maxDirLights,
+ "#define MAX_POINT_LIGHTS " + parameters.maxPointLights,
+ "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights,
+ "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights,
+
+ "#define MAX_SHADOWS " + parameters.maxShadows,
+
+ parameters.alphaTest ? "#define ALPHATEST " + parameters.alphaTest: "",
+
+ _this.gammaInput ? "#define GAMMA_INPUT" : "",
+ _this.gammaOutput ? "#define GAMMA_OUTPUT" : "",
+ _this.physicallyBasedShading ? "#define PHYSICALLY_BASED_SHADING" : "",
+
+ ( parameters.useFog && parameters.fog ) ? "#define USE_FOG" : "",
+ ( parameters.useFog && parameters.fogExp ) ? "#define FOG_EXP2" : "",
+
+ parameters.map ? "#define USE_MAP" : "",
+ parameters.envMap ? "#define USE_ENVMAP" : "",
+ parameters.lightMap ? "#define USE_LIGHTMAP" : "",
+ parameters.bumpMap ? "#define USE_BUMPMAP" : "",
+ parameters.normalMap ? "#define USE_NORMALMAP" : "",
+ parameters.specularMap ? "#define USE_SPECULARMAP" : "",
+ parameters.vertexColors ? "#define USE_COLOR" : "",
+
+ parameters.metal ? "#define METAL" : "",
+ parameters.perPixel ? "#define PHONG_PER_PIXEL" : "",
+ parameters.wrapAround ? "#define WRAP_AROUND" : "",
+ parameters.doubleSided ? "#define DOUBLE_SIDED" : "",
+ parameters.flipSided ? "#define FLIP_SIDED" : "",
+
+ parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "",
+ parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "",
+ parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "",
+ parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "",
+
+ "uniform mat4 viewMatrix;",
+ "uniform vec3 cameraPosition;",
+ ""
+
+ ].join("\n");
+
+ var glVertexShader = getShader( "vertex", prefix_vertex + vertexShader );
+ var glFragmentShader = getShader( "fragment", prefix_fragment + fragmentShader );
+
+ _gl.attachShader( program, glVertexShader );
+ _gl.attachShader( program, glFragmentShader );
+
+ //Force a particular attribute to index 0.
+ // because potentially expensive emulation is done by browser if attribute 0 is disabled.
+ //And, color, for example is often automatically bound to index 0 so disabling it
+ if ( index0AttributeName ) {
+ _gl.bindAttribLocation( program, 0, index0AttributeName );
+ }
+
+ _gl.linkProgram( program );
+
+ if ( !_gl.getProgramParameter( program, _gl.LINK_STATUS ) ) {
+
+ console.error( "Could not initialise shader\n" + "VALIDATE_STATUS: " + _gl.getProgramParameter( program, _gl.VALIDATE_STATUS ) + ", gl error [" + _gl.getError() + "]" );
+ console.error( "Program Info Log: " + _gl.getProgramInfoLog( program ) );
+ }
+
+ // clean up
+
+ _gl.deleteShader( glFragmentShader );
+ _gl.deleteShader( glVertexShader );
+
+ // console.log( prefix_fragment + fragmentShader );
+ // console.log( prefix_vertex + vertexShader );
+
+ program.uniforms = {};
+ program.attributes = {};
+
+ var identifiers, u, a, i;
+
+ // cache uniform locations
+
+ identifiers = [
+
+ 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition',
+ 'morphTargetInfluences'
+
+ ];
+
+ if ( parameters.useVertexTexture ) {
+
+ identifiers.push( 'boneTexture' );
+ identifiers.push( 'boneTextureWidth' );
+ identifiers.push( 'boneTextureHeight' );
+
+ } else {
+
+ identifiers.push( 'boneGlobalMatrices' );
+
+ }
+
+ for ( u in uniforms ) {
+
+ identifiers.push( u );
+
+ }
+
+ cacheUniformLocations( program, identifiers );
+
+ // cache attributes locations
+
+ identifiers = [
+
+ "position", "normal", "uv", "uv2", "tangent", "color",
+ "skinIndex", "skinWeight", "lineDistance"
+
+ ];
+
+ for ( i = 0; i < parameters.maxMorphTargets; i ++ ) {
+
+ identifiers.push( "morphTarget" + i );
+
+ }
+
+ for ( i = 0; i < parameters.maxMorphNormals; i ++ ) {
+
+ identifiers.push( "morphNormal" + i );
+
+ }
+
+ for ( a in attributes ) {
+
+ identifiers.push( a );
+
+ }
+
+ cacheAttributeLocations( program, identifiers );
+
+ program.id = _programs_counter ++;
+
+ _programs.push( { program: program, code: code, usedTimes: 1 } );
+
+ _this.info.memory.programs = _programs.length;
+
+ return program;
+
+ };
+
+ // Shader parameters cache
+
+ function cacheUniformLocations ( program, identifiers ) {
+
+ var i, l, id;
+
+ for( i = 0, l = identifiers.length; i < l; i ++ ) {
+
+ id = identifiers[ i ];
+ program.uniforms[ id ] = _gl.getUniformLocation( program, id );
+
+ }
+
+ };
+
+ function cacheAttributeLocations ( program, identifiers ) {
+
+ var i, l, id;
+
+ for( i = 0, l = identifiers.length; i < l; i ++ ) {
+
+ id = identifiers[ i ];
+ program.attributes[ id ] = _gl.getAttribLocation( program, id );
+
+ }
+
+ };
+
+ function addLineNumbers ( string ) {
+
+ var chunks = string.split( "\n" );
+
+ for ( var i = 0, il = chunks.length; i < il; i ++ ) {
+
+ // Chrome reports shader errors on lines
+ // starting counting from 1
+
+ chunks[ i ] = ( i + 1 ) + ": " + chunks[ i ];
+
+ }
+
+ return chunks.join( "\n" );
+
+ };
+
+ function getShader ( type, string ) {
+
+ var shader;
+
+ if ( type === "fragment" ) {
+
+ shader = _gl.createShader( _gl.FRAGMENT_SHADER );
+
+ } else if ( type === "vertex" ) {
+
+ shader = _gl.createShader( _gl.VERTEX_SHADER );
+
+ }
+
+ _gl.shaderSource( shader, string );
+ _gl.compileShader( shader );
+
+ if ( !_gl.getShaderParameter( shader, _gl.COMPILE_STATUS ) ) {
+
+ console.error( _gl.getShaderInfoLog( shader ) );
+ console.error( addLineNumbers( string ) );
+ return null;
+
+ }
+
+ return shader;
+
+ };
+
+ // Textures
+
+
+ function isPowerOfTwo ( value ) {
+
+ return ( value & ( value - 1 ) ) === 0;
+
+ };
+
+ function setTextureParameters ( textureType, texture, isImagePowerOfTwo ) {
+
+ if ( isImagePowerOfTwo ) {
+
+ _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, paramThreeToGL( texture.wrapS ) );
+ _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, paramThreeToGL( texture.wrapT ) );
+
+ _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, paramThreeToGL( texture.magFilter ) );
+ _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, paramThreeToGL( texture.minFilter ) );
+
+ } else {
+
+ _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );
+ _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );
+
+ _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) );
+ _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) );
+
+ }
+
+ if ( _glExtensionTextureFilterAnisotropic && texture.type !== THREE.FloatType ) {
+
+ if ( texture.anisotropy > 1 || texture.__oldAnisotropy ) {
+
+ _gl.texParameterf( textureType, _glExtensionTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, _maxAnisotropy ) );
+ texture.__oldAnisotropy = texture.anisotropy;
+
+ }
+
+ }
+
+ };
+
+ this.setTexture = function ( texture, slot ) {
+
+ if ( texture.needsUpdate ) {
+
+ if ( ! texture.__webglInit ) {
+
+ texture.__webglInit = true;
+
+ texture.addEventListener( 'dispose', onTextureDispose );
+
+ texture.__webglTexture = _gl.createTexture();
+
+ _this.info.memory.textures ++;
+
+ }
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture );
+
+ _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
+ _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );
+ _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment );
+
+ var image = texture.image,
+ isImagePowerOfTwo = isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ),
+ glFormat = paramThreeToGL( texture.format ),
+ glType = paramThreeToGL( texture.type );
+
+ setTextureParameters( _gl.TEXTURE_2D, texture, isImagePowerOfTwo );
+
+ var mipmap, mipmaps = texture.mipmaps;
+
+ if ( texture instanceof THREE.DataTexture ) {
+
+ // use manually created mipmaps if available
+ // if there are no manual mipmaps
+ // set 0 level mipmap and then use GL to generate other mipmap levels
+
+ if ( mipmaps.length > 0 && isImagePowerOfTwo ) {
+
+ for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+ _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+ }
+
+ texture.generateMipmaps = false;
+
+ } else {
+
+ _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data );
+
+ }
+
+ } else if ( texture instanceof THREE.CompressedTexture ) {
+
+ for( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+ if ( texture.format!==THREE.RGBAFormat ) {
+ _gl.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+ } else {
+ _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+ }
+
+ }
+
+ } else { // regular Texture (image, video, canvas)
+
+ // use manually created mipmaps if available
+ // if there are no manual mipmaps
+ // set 0 level mipmap and then use GL to generate other mipmap levels
+
+ if ( mipmaps.length > 0 && isImagePowerOfTwo ) {
+
+ for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+ _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap );
+
+ }
+
+ texture.generateMipmaps = false;
+
+ } else {
+
+ _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, texture.image );
+
+ }
+
+ }
+
+ if ( texture.generateMipmaps && isImagePowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D );
+
+ texture.needsUpdate = false;
+
+ if ( texture.onUpdate ) texture.onUpdate();
+
+ } else {
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture );
+
+ }
+
+ };
+
+ function clampToMaxSize ( image, maxSize ) {
+
+ if ( image.width <= maxSize && image.height <= maxSize ) {
+
+ return image;
+
+ }
+
+ // Warning: Scaling through the canvas will only work with images that use
+ // premultiplied alpha.
+
+ var maxDimension = Math.max( image.width, image.height );
+ var newWidth = Math.floor( image.width * maxSize / maxDimension );
+ var newHeight = Math.floor( image.height * maxSize / maxDimension );
+
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = newWidth;
+ canvas.height = newHeight;
+
+ var ctx = canvas.getContext( "2d" );
+ ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight );
+
+ return canvas;
+
+ }
+
+ function setCubeTexture ( texture, slot ) {
+
+ if ( texture.image.length === 6 ) {
+
+ if ( texture.needsUpdate ) {
+
+ if ( ! texture.image.__webglTextureCube ) {
+
+ texture.addEventListener( 'dispose', onTextureDispose );
+
+ texture.image.__webglTextureCube = _gl.createTexture();
+
+ _this.info.memory.textures ++;
+
+ }
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube );
+
+ _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
+
+ var isCompressed = texture instanceof THREE.CompressedTexture;
+
+ var cubeImage = [];
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ if ( _this.autoScaleCubemaps && ! isCompressed ) {
+
+ cubeImage[ i ] = clampToMaxSize( texture.image[ i ], _maxCubemapSize );
+
+ } else {
+
+ cubeImage[ i ] = texture.image[ i ];
+
+ }
+
+ }
+
+ var image = cubeImage[ 0 ],
+ isImagePowerOfTwo = isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ),
+ glFormat = paramThreeToGL( texture.format ),
+ glType = paramThreeToGL( texture.type );
+
+ setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isImagePowerOfTwo );
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ if( !isCompressed ) {
+
+ _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] );
+
+ } else {
+
+ var mipmap, mipmaps = cubeImage[ i ].mipmaps;
+
+ for( var j = 0, jl = mipmaps.length; j < jl; j ++ ) {
+
+ mipmap = mipmaps[ j ];
+ if ( texture.format!==THREE.RGBAFormat ) {
+
+ _gl.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+ } else {
+ _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+ }
+
+ }
+ }
+ }
+
+ if ( texture.generateMipmaps && isImagePowerOfTwo ) {
+
+ _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+
+ }
+
+ texture.needsUpdate = false;
+
+ if ( texture.onUpdate ) texture.onUpdate();
+
+ } else {
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube );
+
+ }
+
+ }
+
+ };
+
+ function setCubeTextureDynamic ( texture, slot ) {
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.__webglTexture );
+
+ };
+
+ // Render targets
+
+ function setupFrameBuffer ( framebuffer, renderTarget, textureTarget ) {
+
+ _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+ _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureTarget, renderTarget.__webglTexture, 0 );
+
+ };
+
+ function setupRenderBuffer ( renderbuffer, renderTarget ) {
+
+ _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer );
+
+ if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+
+ _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height );
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+
+ /* For some reason this is not working. Defaulting to RGBA4.
+ } else if( ! renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+ _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.STENCIL_INDEX8, renderTarget.width, renderTarget.height );
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+ */
+ } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+ _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height );
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+
+ } else {
+
+ _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height );
+
+ }
+
+ };
+
+ this.setRenderTarget = function ( renderTarget ) {
+
+ var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube );
+
+ if ( renderTarget && ! renderTarget.__webglFramebuffer ) {
+
+ if ( renderTarget.depthBuffer === undefined ) renderTarget.depthBuffer = true;
+ if ( renderTarget.stencilBuffer === undefined ) renderTarget.stencilBuffer = true;
+
+ renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
+
+ renderTarget.__webglTexture = _gl.createTexture();
+
+ _this.info.memory.textures ++;
+
+ // Setup texture, create render and frame buffers
+
+ var isTargetPowerOfTwo = isPowerOfTwo( renderTarget.width ) && isPowerOfTwo( renderTarget.height ),
+ glFormat = paramThreeToGL( renderTarget.format ),
+ glType = paramThreeToGL( renderTarget.type );
+
+ if ( isCube ) {
+
+ renderTarget.__webglFramebuffer = [];
+ renderTarget.__webglRenderbuffer = [];
+
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture );
+ setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget, isTargetPowerOfTwo );
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ renderTarget.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+ renderTarget.__webglRenderbuffer[ i ] = _gl.createRenderbuffer();
+
+ _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+
+ setupFrameBuffer( renderTarget.__webglFramebuffer[ i ], renderTarget, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );
+ setupRenderBuffer( renderTarget.__webglRenderbuffer[ i ], renderTarget );
+
+ }
+
+ if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+
+ } else {
+
+ renderTarget.__webglFramebuffer = _gl.createFramebuffer();
+
+ if ( renderTarget.shareDepthFrom ) {
+
+ renderTarget.__webglRenderbuffer = renderTarget.shareDepthFrom.__webglRenderbuffer;
+
+ } else {
+
+ renderTarget.__webglRenderbuffer = _gl.createRenderbuffer();
+
+ }
+
+ _gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture );
+ setTextureParameters( _gl.TEXTURE_2D, renderTarget, isTargetPowerOfTwo );
+
+ _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+
+ setupFrameBuffer( renderTarget.__webglFramebuffer, renderTarget, _gl.TEXTURE_2D );
+
+ if ( renderTarget.shareDepthFrom ) {
+
+ if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer );
+
+ } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer );
+
+ }
+
+ } else {
+
+ setupRenderBuffer( renderTarget.__webglRenderbuffer, renderTarget );
+
+ }
+
+ if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D );
+
+ }
+
+ // Release everything
+
+ if ( isCube ) {
+
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null );
+
+ } else {
+
+ _gl.bindTexture( _gl.TEXTURE_2D, null );
+
+ }
+
+ _gl.bindRenderbuffer( _gl.RENDERBUFFER, null );
+ _gl.bindFramebuffer( _gl.FRAMEBUFFER, null );
+
+ }
+
+ var framebuffer, width, height, vx, vy;
+
+ if ( renderTarget ) {
+
+ if ( isCube ) {
+
+ framebuffer = renderTarget.__webglFramebuffer[ renderTarget.activeCubeFace ];
+
+ } else {
+
+ framebuffer = renderTarget.__webglFramebuffer;
+
+ }
+
+ width = renderTarget.width;
+ height = renderTarget.height;
+
+ vx = 0;
+ vy = 0;
+
+ } else {
+
+ framebuffer = null;
+
+ width = _viewportWidth;
+ height = _viewportHeight;
+
+ vx = _viewportX;
+ vy = _viewportY;
+
+ }
+
+ if ( framebuffer !== _currentFramebuffer ) {
+
+ _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+ _gl.viewport( vx, vy, width, height );
+
+ _currentFramebuffer = framebuffer;
+
+ }
+
+ _currentWidth = width;
+ _currentHeight = height;
+
+ };
+
+ function updateRenderTargetMipmap ( renderTarget ) {
+
+ if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) {
+
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture );
+ _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null );
+
+ } else {
+
+ _gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture );
+ _gl.generateMipmap( _gl.TEXTURE_2D );
+ _gl.bindTexture( _gl.TEXTURE_2D, null );
+
+ }
+
+ };
+
+ // Fallback filters for non-power-of-2 textures
+
+ function filterFallback ( f ) {
+
+ if ( f === THREE.NearestFilter || f === THREE.NearestMipMapNearestFilter || f === THREE.NearestMipMapLinearFilter ) {
+
+ return _gl.NEAREST;
+
+ }
+
+ return _gl.LINEAR;
+
+ };
+
+ // Map three.js constants to WebGL constants
+
+ function paramThreeToGL ( p ) {
+
+ if ( p === THREE.RepeatWrapping ) return _gl.REPEAT;
+ if ( p === THREE.ClampToEdgeWrapping ) return _gl.CLAMP_TO_EDGE;
+ if ( p === THREE.MirroredRepeatWrapping ) return _gl.MIRRORED_REPEAT;
+
+ if ( p === THREE.NearestFilter ) return _gl.NEAREST;
+ if ( p === THREE.NearestMipMapNearestFilter ) return _gl.NEAREST_MIPMAP_NEAREST;
+ if ( p === THREE.NearestMipMapLinearFilter ) return _gl.NEAREST_MIPMAP_LINEAR;
+
+ if ( p === THREE.LinearFilter ) return _gl.LINEAR;
+ if ( p === THREE.LinearMipMapNearestFilter ) return _gl.LINEAR_MIPMAP_NEAREST;
+ if ( p === THREE.LinearMipMapLinearFilter ) return _gl.LINEAR_MIPMAP_LINEAR;
+
+ if ( p === THREE.UnsignedByteType ) return _gl.UNSIGNED_BYTE;
+ if ( p === THREE.UnsignedShort4444Type ) return _gl.UNSIGNED_SHORT_4_4_4_4;
+ if ( p === THREE.UnsignedShort5551Type ) return _gl.UNSIGNED_SHORT_5_5_5_1;
+ if ( p === THREE.UnsignedShort565Type ) return _gl.UNSIGNED_SHORT_5_6_5;
+
+ if ( p === THREE.ByteType ) return _gl.BYTE;
+ if ( p === THREE.ShortType ) return _gl.SHORT;
+ if ( p === THREE.UnsignedShortType ) return _gl.UNSIGNED_SHORT;
+ if ( p === THREE.IntType ) return _gl.INT;
+ if ( p === THREE.UnsignedIntType ) return _gl.UNSIGNED_INT;
+ if ( p === THREE.FloatType ) return _gl.FLOAT;
+
+ if ( p === THREE.AlphaFormat ) return _gl.ALPHA;
+ if ( p === THREE.RGBFormat ) return _gl.RGB;
+ if ( p === THREE.RGBAFormat ) return _gl.RGBA;
+ if ( p === THREE.LuminanceFormat ) return _gl.LUMINANCE;
+ if ( p === THREE.LuminanceAlphaFormat ) return _gl.LUMINANCE_ALPHA;
+
+ if ( p === THREE.AddEquation ) return _gl.FUNC_ADD;
+ if ( p === THREE.SubtractEquation ) return _gl.FUNC_SUBTRACT;
+ if ( p === THREE.ReverseSubtractEquation ) return _gl.FUNC_REVERSE_SUBTRACT;
+
+ if ( p === THREE.ZeroFactor ) return _gl.ZERO;
+ if ( p === THREE.OneFactor ) return _gl.ONE;
+ if ( p === THREE.SrcColorFactor ) return _gl.SRC_COLOR;
+ if ( p === THREE.OneMinusSrcColorFactor ) return _gl.ONE_MINUS_SRC_COLOR;
+ if ( p === THREE.SrcAlphaFactor ) return _gl.SRC_ALPHA;
+ if ( p === THREE.OneMinusSrcAlphaFactor ) return _gl.ONE_MINUS_SRC_ALPHA;
+ if ( p === THREE.DstAlphaFactor ) return _gl.DST_ALPHA;
+ if ( p === THREE.OneMinusDstAlphaFactor ) return _gl.ONE_MINUS_DST_ALPHA;
+
+ if ( p === THREE.DstColorFactor ) return _gl.DST_COLOR;
+ if ( p === THREE.OneMinusDstColorFactor ) return _gl.ONE_MINUS_DST_COLOR;
+ if ( p === THREE.SrcAlphaSaturateFactor ) return _gl.SRC_ALPHA_SATURATE;
+
+ if ( _glExtensionCompressedTextureS3TC !== undefined ) {
+
+ if ( p === THREE.RGB_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGB_S3TC_DXT1_EXT;
+ if ( p === THREE.RGBA_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ if ( p === THREE.RGBA_S3TC_DXT3_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ if ( p === THREE.RGBA_S3TC_DXT5_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+ }
+
+ return 0;
+
+ };
+
+ // Allocations
+
+ function allocateBones ( object ) {
+
+ if ( _supportsBoneTextures && object && object.useVertexTexture ) {
+
+ return 1024;
+
+ } else {
+
+ // default for when object is not specified
+ // ( for example when prebuilding shader
+ // to be used with multiple objects )
+ //
+ // - leave some extra space for other uniforms
+ // - limit here is ANGLE's 254 max uniform vectors
+ // (up to 54 should be safe)
+
+ var nVertexUniforms = _gl.getParameter( _gl.MAX_VERTEX_UNIFORM_VECTORS );
+ var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );
+
+ var maxBones = nVertexMatrices;
+
+ if ( object !== undefined && object instanceof THREE.SkinnedMesh ) {
+
+ maxBones = Math.min( object.bones.length, maxBones );
+
+ if ( maxBones < object.bones.length ) {
+
+ console.warn( "WebGLRenderer: too many bones - " + object.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" );
+
+ }
+
+ }
+
+ return maxBones;
+
+ }
+
+ };
+
+ function allocateLights( lights ) {
+
+ var dirLights = 0;
+ var pointLights = 0;
+ var spotLights = 0;
+ var hemiLights = 0;
+
+ for ( var l = 0, ll = lights.length; l < ll; l ++ ) {
+
+ var light = lights[ l ];
+
+ if ( light.onlyShadow ) continue;
+
+ if ( light instanceof THREE.DirectionalLight ) dirLights ++;
+ if ( light instanceof THREE.PointLight ) pointLights ++;
+ if ( light instanceof THREE.SpotLight ) spotLights ++;
+ if ( light instanceof THREE.HemisphereLight ) hemiLights ++;
+
+ }
+
+ return { 'directional' : dirLights, 'point' : pointLights, 'spot': spotLights, 'hemi': hemiLights };
+
+ };
+
+ function allocateShadows( lights ) {
+
+ var maxShadows = 0;
+
+ for ( var l = 0, ll = lights.length; l < ll; l++ ) {
+
+ var light = lights[ l ];
+
+ if ( ! light.castShadow ) continue;
+
+ if ( light instanceof THREE.SpotLight ) maxShadows ++;
+ if ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) maxShadows ++;
+
+ }
+
+ return maxShadows;
+
+ };
+
+ // Initialization
+
+ function initGL() {
+
+ try {
+
+ var attributes = {
+ alpha: _alpha,
+ premultipliedAlpha: _premultipliedAlpha,
+ antialias: _antialias,
+ stencil: _stencil,
+ preserveDrawingBuffer: _preserveDrawingBuffer
+ };
+
+ _gl = _canvas.getContext( 'webgl', attributes ) || _canvas.getContext( 'experimental-webgl', attributes );
+
+ if ( _gl === null ) {
+
+ throw 'Error creating WebGL context.';
+
+ }
+
+ } catch ( error ) {
+
+ console.error( error );
+
+ }
+
+ _glExtensionTextureFloat = _gl.getExtension( 'OES_texture_float' );
+ _glExtensionTextureFloatLinear = _gl.getExtension( 'OES_texture_float_linear' );
+ _glExtensionStandardDerivatives = _gl.getExtension( 'OES_standard_derivatives' );
+
+ _glExtensionTextureFilterAnisotropic = _gl.getExtension( 'EXT_texture_filter_anisotropic' ) || _gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || _gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );
+
+ _glExtensionCompressedTextureS3TC = _gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || _gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || _gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );
+
+ if ( ! _glExtensionTextureFloat ) {
+
+ console.log( 'THREE.WebGLRenderer: Float textures not supported.' );
+
+ }
+
+ if ( ! _glExtensionStandardDerivatives ) {
+
+ console.log( 'THREE.WebGLRenderer: Standard derivatives not supported.' );
+
+ }
+
+ if ( ! _glExtensionTextureFilterAnisotropic ) {
+
+ console.log( 'THREE.WebGLRenderer: Anisotropic texture filtering not supported.' );
+
+ }
+
+ if ( ! _glExtensionCompressedTextureS3TC ) {
+
+ console.log( 'THREE.WebGLRenderer: S3TC compressed textures not supported.' );
+
+ }
+
+ if ( _gl.getShaderPrecisionFormat === undefined ) {
+
+ _gl.getShaderPrecisionFormat = function() {
+
+ return {
+ "rangeMin" : 1,
+ "rangeMax" : 1,
+ "precision" : 1
+ };
+
+ }
+ }
+
+ };
+
+ function setDefaultGLState () {
+
+ _gl.clearColor( 0, 0, 0, 1 );
+ _gl.clearDepth( 1 );
+ _gl.clearStencil( 0 );
+
+ _gl.enable( _gl.DEPTH_TEST );
+ _gl.depthFunc( _gl.LEQUAL );
+
+ _gl.frontFace( _gl.CCW );
+ _gl.cullFace( _gl.BACK );
+ _gl.enable( _gl.CULL_FACE );
+
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquation( _gl.FUNC_ADD );
+ _gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA );
+
+ _gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
+
+ };
+
+ // default plugins (order is important)
+
+ this.shadowMapPlugin = new THREE.ShadowMapPlugin();
+ this.addPrePlugin( this.shadowMapPlugin );
+
+ this.addPostPlugin( new THREE.SpritePlugin() );
+ this.addPostPlugin( new THREE.LensFlarePlugin() );
+
+};
+
+/**
+ * @author szimek / https://github.com/szimek/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.WebGLRenderTarget = function ( width, height, options ) {
+
+ this.width = width;
+ this.height = height;
+
+ options = options || {};
+
+ this.wrapS = options.wrapS !== undefined ? options.wrapS : THREE.ClampToEdgeWrapping;
+ this.wrapT = options.wrapT !== undefined ? options.wrapT : THREE.ClampToEdgeWrapping;
+
+ this.magFilter = options.magFilter !== undefined ? options.magFilter : THREE.LinearFilter;
+ this.minFilter = options.minFilter !== undefined ? options.minFilter : THREE.LinearMipMapLinearFilter;
+
+ this.anisotropy = options.anisotropy !== undefined ? options.anisotropy : 1;
+
+ this.offset = new THREE.Vector2( 0, 0 );
+ this.repeat = new THREE.Vector2( 1, 1 );
+
+ this.format = options.format !== undefined ? options.format : THREE.RGBAFormat;
+ this.type = options.type !== undefined ? options.type : THREE.UnsignedByteType;
+
+ this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
+ this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true;
+
+ this.generateMipmaps = true;
+
+ this.shareDepthFrom = null;
+
+};
+
+THREE.WebGLRenderTarget.prototype = {
+
+ constructor: THREE.WebGLRenderTarget,
+
+ clone: function () {
+
+ var tmp = new THREE.WebGLRenderTarget( this.width, this.height );
+
+ tmp.wrapS = this.wrapS;
+ tmp.wrapT = this.wrapT;
+
+ tmp.magFilter = this.magFilter;
+ tmp.minFilter = this.minFilter;
+
+ tmp.anisotropy = this.anisotropy;
+
+ tmp.offset.copy( this.offset );
+ tmp.repeat.copy( this.repeat );
+
+ tmp.format = this.format;
+ tmp.type = this.type;
+
+ tmp.depthBuffer = this.depthBuffer;
+ tmp.stencilBuffer = this.stencilBuffer;
+
+ tmp.generateMipmaps = this.generateMipmaps;
+
+ tmp.shareDepthFrom = this.shareDepthFrom;
+
+ return tmp;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.WebGLRenderTarget.prototype );
+
+/**
+ * @author alteredq / http://alteredqualia.com
+ */
+
+THREE.WebGLRenderTargetCube = function ( width, height, options ) {
+
+ THREE.WebGLRenderTarget.call( this, width, height, options );
+
+ this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5
+
+};
+
+THREE.WebGLRenderTargetCube.prototype = Object.create( THREE.WebGLRenderTarget.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableVertex = function () {
+
+ this.positionWorld = new THREE.Vector3();
+ this.positionScreen = new THREE.Vector4();
+
+ this.visible = true;
+
+};
+
+THREE.RenderableVertex.prototype.copy = function ( vertex ) {
+
+ this.positionWorld.copy( vertex.positionWorld );
+ this.positionScreen.copy( vertex.positionScreen );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableFace3 = function () {
+
+ this.id = 0;
+
+ this.v1 = new THREE.RenderableVertex();
+ this.v2 = new THREE.RenderableVertex();
+ this.v3 = new THREE.RenderableVertex();
+
+ this.centroidModel = new THREE.Vector3();
+
+ this.normalModel = new THREE.Vector3();
+ this.normalModelView = new THREE.Vector3();
+
+ this.vertexNormalsLength = 0;
+ this.vertexNormalsModel = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+ this.vertexNormalsModelView = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+
+ this.color = null;
+ this.material = null;
+ this.uvs = [[]];
+
+ this.z = 0;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableObject = function () {
+
+ this.id = 0;
+
+ this.object = null;
+ this.z = 0;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableParticle = function () {
+
+ this.id = 0;
+
+ this.object = null;
+
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+
+ this.rotation = null;
+ this.scale = new THREE.Vector2();
+
+ this.material = null;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableLine = function () {
+
+ this.id = 0;
+
+ this.v1 = new THREE.RenderableVertex();
+ this.v2 = new THREE.RenderableVertex();
+
+ this.vertexColors = [ new THREE.Color(), new THREE.Color() ];
+ this.material = null;
+
+ this.z = 0;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.GeometryUtils = {
+
+ // Merge two geometries or geometry and geometry from object (using object's transform)
+
+ merge: function ( geometry1, object2 /* mesh | geometry */, materialIndexOffset ) {
+
+ var matrix, normalMatrix,
+ vertexOffset = geometry1.vertices.length,
+ uvPosition = geometry1.faceVertexUvs[ 0 ].length,
+ geometry2 = object2 instanceof THREE.Mesh ? object2.geometry : object2,
+ vertices1 = geometry1.vertices,
+ vertices2 = geometry2.vertices,
+ faces1 = geometry1.faces,
+ faces2 = geometry2.faces,
+ uvs1 = geometry1.faceVertexUvs[ 0 ],
+ uvs2 = geometry2.faceVertexUvs[ 0 ];
+
+ if ( materialIndexOffset === undefined ) materialIndexOffset = 0;
+
+ if ( object2 instanceof THREE.Mesh ) {
+
+ object2.matrixAutoUpdate && object2.updateMatrix();
+
+ matrix = object2.matrix;
+
+ normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+ }
+
+ // vertices
+
+ for ( var i = 0, il = vertices2.length; i < il; i ++ ) {
+
+ var vertex = vertices2[ i ];
+
+ var vertexCopy = vertex.clone();
+
+ if ( matrix ) vertexCopy.applyMatrix4( matrix );
+
+ vertices1.push( vertexCopy );
+
+ }
+
+ // faces
+
+ for ( i = 0, il = faces2.length; i < il; i ++ ) {
+
+ var face = faces2[ i ], faceCopy, normal, color,
+ faceVertexNormals = face.vertexNormals,
+ faceVertexColors = face.vertexColors;
+
+ faceCopy = new THREE.Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset );
+ faceCopy.normal.copy( face.normal );
+
+ if ( normalMatrix ) {
+
+ faceCopy.normal.applyMatrix3( normalMatrix ).normalize();
+
+ }
+
+ for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) {
+
+ normal = faceVertexNormals[ j ].clone();
+
+ if ( normalMatrix ) {
+
+ normal.applyMatrix3( normalMatrix ).normalize();
+
+ }
+
+ faceCopy.vertexNormals.push( normal );
+
+ }
+
+ faceCopy.color.copy( face.color );
+
+ for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) {
+
+ color = faceVertexColors[ j ];
+ faceCopy.vertexColors.push( color.clone() );
+
+ }
+
+ faceCopy.materialIndex = face.materialIndex + materialIndexOffset;
+
+ faceCopy.centroid.copy( face.centroid );
+
+ if ( matrix ) {
+
+ faceCopy.centroid.applyMatrix4( matrix );
+
+ }
+
+ faces1.push( faceCopy );
+
+ }
+
+ // uvs
+
+ for ( i = 0, il = uvs2.length; i < il; i ++ ) {
+
+ var uv = uvs2[ i ], uvCopy = [];
+
+ for ( var j = 0, jl = uv.length; j < jl; j ++ ) {
+
+ uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) );
+
+ }
+
+ uvs1.push( uvCopy );
+
+ }
+
+ },
+
+ // Get random point in triangle (via barycentric coordinates)
+ // (uniform distribution)
+ // http://www.cgafaq.info/wiki/Random_Point_In_Triangle
+
+ randomPointInTriangle: function () {
+
+ var vector = new THREE.Vector3();
+
+ return function ( vectorA, vectorB, vectorC ) {
+
+ var point = new THREE.Vector3();
+
+ var a = THREE.Math.random16();
+ var b = THREE.Math.random16();
+
+ if ( ( a + b ) > 1 ) {
+
+ a = 1 - a;
+ b = 1 - b;
+
+ }
+
+ var c = 1 - a - b;
+
+ point.copy( vectorA );
+ point.multiplyScalar( a );
+
+ vector.copy( vectorB );
+ vector.multiplyScalar( b );
+
+ point.add( vector );
+
+ vector.copy( vectorC );
+ vector.multiplyScalar( c );
+
+ point.add( vector );
+
+ return point;
+
+ };
+
+ }(),
+
+ // Get random point in face (triangle / quad)
+ // (uniform distribution)
+
+ randomPointInFace: function ( face, geometry, useCachedAreas ) {
+
+ var vA, vB, vC, vD;
+
+ vA = geometry.vertices[ face.a ];
+ vB = geometry.vertices[ face.b ];
+ vC = geometry.vertices[ face.c ];
+
+ return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vC );
+
+ },
+
+ // Get uniformly distributed random points in mesh
+ // - create array with cumulative sums of face areas
+ // - pick random number from 0 to total area
+ // - find corresponding place in area array by binary search
+ // - get random point in face
+
+ randomPointsInGeometry: function ( geometry, n ) {
+
+ var face, i,
+ faces = geometry.faces,
+ vertices = geometry.vertices,
+ il = faces.length,
+ totalArea = 0,
+ cumulativeAreas = [],
+ vA, vB, vC, vD;
+
+ // precompute face areas
+
+ for ( i = 0; i < il; i ++ ) {
+
+ face = faces[ i ];
+
+ vA = vertices[ face.a ];
+ vB = vertices[ face.b ];
+ vC = vertices[ face.c ];
+
+ face._area = THREE.GeometryUtils.triangleArea( vA, vB, vC );
+
+ totalArea += face._area;
+
+ cumulativeAreas[ i ] = totalArea;
+
+ }
+
+ // binary search cumulative areas array
+
+ function binarySearchIndices( value ) {
+
+ function binarySearch( start, end ) {
+
+ // return closest larger index
+ // if exact number is not found
+
+ if ( end < start )
+ return start;
+
+ var mid = start + Math.floor( ( end - start ) / 2 );
+
+ if ( cumulativeAreas[ mid ] > value ) {
+
+ return binarySearch( start, mid - 1 );
+
+ } else if ( cumulativeAreas[ mid ] < value ) {
+
+ return binarySearch( mid + 1, end );
+
+ } else {
+
+ return mid;
+
+ }
+
+ }
+
+ var result = binarySearch( 0, cumulativeAreas.length - 1 )
+ return result;
+
+ }
+
+ // pick random face weighted by face area
+
+ var r, index,
+ result = [];
+
+ var stats = {};
+
+ for ( i = 0; i < n; i ++ ) {
+
+ r = THREE.Math.random16() * totalArea;
+
+ index = binarySearchIndices( r );
+
+ result[ i ] = THREE.GeometryUtils.randomPointInFace( faces[ index ], geometry, true );
+
+ if ( ! stats[ index ] ) {
+
+ stats[ index ] = 1;
+
+ } else {
+
+ stats[ index ] += 1;
+
+ }
+
+ }
+
+ return result;
+
+ },
+
+ // Get triangle area (half of parallelogram)
+ // http://mathworld.wolfram.com/TriangleArea.html
+
+ triangleArea: function () {
+
+ var vector1 = new THREE.Vector3();
+ var vector2 = new THREE.Vector3();
+
+ return function ( vectorA, vectorB, vectorC ) {
+
+ vector1.subVectors( vectorB, vectorA );
+ vector2.subVectors( vectorC, vectorA );
+ vector1.cross( vector2 );
+
+ return 0.5 * vector1.length();
+
+ };
+
+ }(),
+
+ // Center geometry so that 0,0,0 is in center of bounding box
+
+ center: function ( geometry ) {
+
+ geometry.computeBoundingBox();
+
+ var bb = geometry.boundingBox;
+
+ var offset = new THREE.Vector3();
+
+ offset.addVectors( bb.min, bb.max );
+ offset.multiplyScalar( -0.5 );
+
+ geometry.applyMatrix( new THREE.Matrix4().makeTranslation( offset.x, offset.y, offset.z ) );
+ geometry.computeBoundingBox();
+
+ return offset;
+
+ },
+
+ triangulateQuads: function ( geometry ) {
+
+ var i, il, j, jl;
+
+ var faces = [];
+ var faceVertexUvs = [];
+
+ for ( i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) {
+
+ faceVertexUvs[ i ] = [];
+
+ }
+
+ for ( i = 0, il = geometry.faces.length; i < il; i ++ ) {
+
+ var face = geometry.faces[ i ];
+
+ faces.push( face );
+
+ for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) {
+
+ faceVertexUvs[ j ].push( geometry.faceVertexUvs[ j ][ i ] );
+
+ }
+
+ }
+
+ geometry.faces = faces;
+ geometry.faceVertexUvs = faceVertexUvs;
+
+ geometry.computeCentroids();
+ geometry.computeFaceNormals();
+ geometry.computeVertexNormals();
+
+ if ( geometry.hasTangents ) geometry.computeTangents();
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ImageUtils = {
+
+ crossOrigin: 'anonymous',
+
+ loadTexture: function ( url, mapping, onLoad, onError ) {
+
+ var loader = new THREE.ImageLoader();
+ loader.crossOrigin = this.crossOrigin;
+
+ var texture = new THREE.Texture( undefined, mapping );
+
+ var image = loader.load( url, function () {
+
+ texture.needsUpdate = true;
+
+ if ( onLoad ) onLoad( texture );
+
+ } );
+
+ texture.image = image;
+ texture.sourceFile = url;
+
+ return texture;
+
+ },
+
+ loadCompressedTexture: function ( url, mapping, onLoad, onError ) {
+
+ var texture = new THREE.CompressedTexture();
+ texture.mapping = mapping;
+
+ var request = new XMLHttpRequest();
+
+ request.onload = function () {
+
+ var buffer = request.response;
+ var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+ texture.format = dds.format;
+
+ texture.mipmaps = dds.mipmaps;
+ texture.image.width = dds.width;
+ texture.image.height = dds.height;
+
+ // gl.generateMipmap fails for compressed textures
+ // mipmaps must be embedded in the DDS file
+ // or texture filters must not use mipmapping
+
+ texture.generateMipmaps = false;
+
+ texture.needsUpdate = true;
+
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ request.onerror = onError;
+
+ request.open( 'GET', url, true );
+ request.responseType = "arraybuffer";
+ request.send( null );
+
+ return texture;
+
+ },
+
+ loadTextureCube: function ( array, mapping, onLoad, onError ) {
+
+ var images = [];
+ images.loadCount = 0;
+
+ var texture = new THREE.Texture();
+ texture.image = images;
+ if ( mapping !== undefined ) texture.mapping = mapping;
+
+ // no flipping needed for cube textures
+
+ texture.flipY = false;
+
+ for ( var i = 0, il = array.length; i < il; ++ i ) {
+
+ var cubeImage = new Image();
+ images[ i ] = cubeImage;
+
+ cubeImage.onload = function () {
+
+ images.loadCount += 1;
+
+ if ( images.loadCount === 6 ) {
+
+ texture.needsUpdate = true;
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ };
+
+ cubeImage.onerror = onError;
+
+ cubeImage.crossOrigin = this.crossOrigin;
+ cubeImage.src = array[ i ];
+
+ }
+
+ return texture;
+
+ },
+
+ loadCompressedTextureCube: function ( array, mapping, onLoad, onError ) {
+
+ var images = [];
+ images.loadCount = 0;
+
+ var texture = new THREE.CompressedTexture();
+ texture.image = images;
+ if ( mapping !== undefined ) texture.mapping = mapping;
+
+ // no flipping for cube textures
+ // (also flipping doesn't work for compressed textures )
+
+ texture.flipY = false;
+
+ // can't generate mipmaps for compressed textures
+ // mips must be embedded in DDS files
+
+ texture.generateMipmaps = false;
+
+ var generateCubeFaceCallback = function ( rq, img ) {
+
+ return function () {
+
+ var buffer = rq.response;
+ var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+ img.format = dds.format;
+
+ img.mipmaps = dds.mipmaps;
+ img.width = dds.width;
+ img.height = dds.height;
+
+ images.loadCount += 1;
+
+ if ( images.loadCount === 6 ) {
+
+ texture.format = dds.format;
+ texture.needsUpdate = true;
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ }
+
+ }
+
+ // compressed cubemap textures as 6 separate DDS files
+
+ if ( array instanceof Array ) {
+
+ for ( var i = 0, il = array.length; i < il; ++ i ) {
+
+ var cubeImage = {};
+ images[ i ] = cubeImage;
+
+ var request = new XMLHttpRequest();
+
+ request.onload = generateCubeFaceCallback( request, cubeImage );
+ request.onerror = onError;
+
+ var url = array[ i ];
+
+ request.open( 'GET', url, true );
+ request.responseType = "arraybuffer";
+ request.send( null );
+
+ }
+
+ // compressed cubemap texture stored in a single DDS file
+
+ } else {
+
+ var url = array;
+ var request = new XMLHttpRequest();
+
+ request.onload = function( ) {
+
+ var buffer = request.response;
+ var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+ if ( dds.isCubemap ) {
+
+ var faces = dds.mipmaps.length / dds.mipmapCount;
+
+ for ( var f = 0; f < faces; f ++ ) {
+
+ images[ f ] = { mipmaps : [] };
+
+ for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+ images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] );
+ images[ f ].format = dds.format;
+ images[ f ].width = dds.width;
+ images[ f ].height = dds.height;
+
+ }
+
+ }
+
+ texture.format = dds.format;
+ texture.needsUpdate = true;
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ }
+
+ request.onerror = onError;
+
+ request.open( 'GET', url, true );
+ request.responseType = "arraybuffer";
+ request.send( null );
+
+ }
+
+ return texture;
+
+ },
+
+ loadDDSTexture: function ( url, mapping, onLoad, onError ) {
+
+ var images = [];
+ images.loadCount = 0;
+
+ var texture = new THREE.CompressedTexture();
+ texture.image = images;
+ if ( mapping !== undefined ) texture.mapping = mapping;
+
+ // no flipping for cube textures
+ // (also flipping doesn't work for compressed textures )
+
+ texture.flipY = false;
+
+ // can't generate mipmaps for compressed textures
+ // mips must be embedded in DDS files
+
+ texture.generateMipmaps = false;
+
+ {
+ var request = new XMLHttpRequest();
+
+ request.onload = function( ) {
+
+ var buffer = request.response;
+ var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+ if ( dds.isCubemap ) {
+
+ var faces = dds.mipmaps.length / dds.mipmapCount;
+
+ for ( var f = 0; f < faces; f ++ ) {
+
+ images[ f ] = { mipmaps : [] };
+
+ for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+ images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] );
+ images[ f ].format = dds.format;
+ images[ f ].width = dds.width;
+ images[ f ].height = dds.height;
+
+ }
+
+ }
+
+
+ } else {
+ texture.image.width = dds.width;
+ texture.image.height = dds.height;
+ texture.mipmaps = dds.mipmaps;
+ }
+
+ texture.format = dds.format;
+ texture.needsUpdate = true;
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ request.onerror = onError;
+
+ request.open( 'GET', url, true );
+ request.responseType = "arraybuffer";
+ request.send( null );
+
+ }
+
+ return texture;
+
+ },
+
+ parseDDS: function ( buffer, loadMipmaps ) {
+
+ var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 };
+
+ // Adapted from @toji's DDS utils
+ // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
+
+ // All values and structures referenced from:
+ // http://msdn.microsoft.com/en-us/library/bb943991.aspx/
+
+ var DDS_MAGIC = 0x20534444;
+
+ var DDSD_CAPS = 0x1,
+ DDSD_HEIGHT = 0x2,
+ DDSD_WIDTH = 0x4,
+ DDSD_PITCH = 0x8,
+ DDSD_PIXELFORMAT = 0x1000,
+ DDSD_MIPMAPCOUNT = 0x20000,
+ DDSD_LINEARSIZE = 0x80000,
+ DDSD_DEPTH = 0x800000;
+
+ var DDSCAPS_COMPLEX = 0x8,
+ DDSCAPS_MIPMAP = 0x400000,
+ DDSCAPS_TEXTURE = 0x1000;
+
+ var DDSCAPS2_CUBEMAP = 0x200,
+ DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
+ DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
+ DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
+ DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
+ DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
+ DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
+ DDSCAPS2_VOLUME = 0x200000;
+
+ var DDPF_ALPHAPIXELS = 0x1,
+ DDPF_ALPHA = 0x2,
+ DDPF_FOURCC = 0x4,
+ DDPF_RGB = 0x40,
+ DDPF_YUV = 0x200,
+ DDPF_LUMINANCE = 0x20000;
+
+ function fourCCToInt32( value ) {
+
+ return value.charCodeAt(0) +
+ (value.charCodeAt(1) << 8) +
+ (value.charCodeAt(2) << 16) +
+ (value.charCodeAt(3) << 24);
+
+ }
+
+ function int32ToFourCC( value ) {
+
+ return String.fromCharCode(
+ value & 0xff,
+ (value >> 8) & 0xff,
+ (value >> 16) & 0xff,
+ (value >> 24) & 0xff
+ );
+ }
+
+ function loadARGBMip( buffer, dataOffset, width, height ) {
+ var dataLength = width*height*4;
+ var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength );
+ var byteArray = new Uint8Array( dataLength );
+ var dst = 0;
+ var src = 0;
+ for ( var y = 0; y < height; y++ ) {
+ for ( var x = 0; x < width; x++ ) {
+ var b = srcBuffer[src]; src++;
+ var g = srcBuffer[src]; src++;
+ var r = srcBuffer[src]; src++;
+ var a = srcBuffer[src]; src++;
+ byteArray[dst] = r; dst++; //r
+ byteArray[dst] = g; dst++; //g
+ byteArray[dst] = b; dst++; //b
+ byteArray[dst] = a; dst++; //a
+ }
+ }
+ return byteArray;
+ }
+
+ var FOURCC_DXT1 = fourCCToInt32("DXT1");
+ var FOURCC_DXT3 = fourCCToInt32("DXT3");
+ var FOURCC_DXT5 = fourCCToInt32("DXT5");
+
+ var headerLengthInt = 31; // The header length in 32 bit ints
+
+ // Offsets into the header array
+
+ var off_magic = 0;
+
+ var off_size = 1;
+ var off_flags = 2;
+ var off_height = 3;
+ var off_width = 4;
+
+ var off_mipmapCount = 7;
+
+ var off_pfFlags = 20;
+ var off_pfFourCC = 21;
+ var off_RGBBitCount = 22;
+ var off_RBitMask = 23;
+ var off_GBitMask = 24;
+ var off_BBitMask = 25;
+ var off_ABitMask = 26;
+
+ var off_caps = 27;
+ var off_caps2 = 28;
+ var off_caps3 = 29;
+ var off_caps4 = 30;
+
+ // Parse header
+
+ var header = new Int32Array( buffer, 0, headerLengthInt );
+
+ if ( header[ off_magic ] !== DDS_MAGIC ) {
+
+ console.error( "ImageUtils.parseDDS(): Invalid magic number in DDS header" );
+ return dds;
+
+ }
+
+ if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
+
+ console.error( "ImageUtils.parseDDS(): Unsupported format, must contain a FourCC code" );
+ return dds;
+
+ }
+
+ var blockBytes;
+
+ var fourCC = header[ off_pfFourCC ];
+
+ var isRGBAUncompressed = false;
+
+ switch ( fourCC ) {
+
+ case FOURCC_DXT1:
+
+ blockBytes = 8;
+ dds.format = THREE.RGB_S3TC_DXT1_Format;
+ break;
+
+ case FOURCC_DXT3:
+
+ blockBytes = 16;
+ dds.format = THREE.RGBA_S3TC_DXT3_Format;
+ break;
+
+ case FOURCC_DXT5:
+
+ blockBytes = 16;
+ dds.format = THREE.RGBA_S3TC_DXT5_Format;
+ break;
+
+ default:
+
+ if( header[off_RGBBitCount] ==32
+ && header[off_RBitMask]&0xff0000
+ && header[off_GBitMask]&0xff00
+ && header[off_BBitMask]&0xff
+ && header[off_ABitMask]&0xff000000 ) {
+ isRGBAUncompressed = true;
+ blockBytes = 64;
+ dds.format = THREE.RGBAFormat;
+ } else {
+ console.error( "ImageUtils.parseDDS(): Unsupported FourCC code: ", int32ToFourCC( fourCC ) );
+ return dds;
+ }
+ }
+
+ dds.mipmapCount = 1;
+
+ if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
+
+ dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
+
+ }
+
+ //TODO: Verify that all faces of the cubemap are present with DDSCAPS2_CUBEMAP_POSITIVEX, etc.
+
+ dds.isCubemap = header[ off_caps2 ] & DDSCAPS2_CUBEMAP ? true : false;
+
+ dds.width = header[ off_width ];
+ dds.height = header[ off_height ];
+
+ var dataOffset = header[ off_size ] + 4;
+
+ // Extract mipmaps buffers
+
+ var width = dds.width;
+ var height = dds.height;
+
+ var faces = dds.isCubemap ? 6 : 1;
+
+ for ( var face = 0; face < faces; face ++ ) {
+
+ for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+ if( isRGBAUncompressed ) {
+ var byteArray = loadARGBMip( buffer, dataOffset, width, height );
+ var dataLength = byteArray.length;
+ } else {
+ var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
+ var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
+ }
+
+ var mipmap = { "data": byteArray, "width": width, "height": height };
+ dds.mipmaps.push( mipmap );
+
+ dataOffset += dataLength;
+
+ width = Math.max( width * 0.5, 1 );
+ height = Math.max( height * 0.5, 1 );
+
+ }
+
+ width = dds.width;
+ height = dds.height;
+
+ }
+
+ return dds;
+
+ },
+
+ getNormalMap: function ( image, depth ) {
+
+ // Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/
+
+ var cross = function ( a, b ) {
+
+ return [ a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ], a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ], a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ] ];
+
+ }
+
+ var subtract = function ( a, b ) {
+
+ return [ a[ 0 ] - b[ 0 ], a[ 1 ] - b[ 1 ], a[ 2 ] - b[ 2 ] ];
+
+ }
+
+ var normalize = function ( a ) {
+
+ var l = Math.sqrt( a[ 0 ] * a[ 0 ] + a[ 1 ] * a[ 1 ] + a[ 2 ] * a[ 2 ] );
+ return [ a[ 0 ] / l, a[ 1 ] / l, a[ 2 ] / l ];
+
+ }
+
+ depth = depth | 1;
+
+ var width = image.width;
+ var height = image.height;
+
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = width;
+ canvas.height = height;
+
+ var context = canvas.getContext( '2d' );
+ context.drawImage( image, 0, 0 );
+
+ var data = context.getImageData( 0, 0, width, height ).data;
+ var imageData = context.createImageData( width, height );
+ var output = imageData.data;
+
+ for ( var x = 0; x < width; x ++ ) {
+
+ for ( var y = 0; y < height; y ++ ) {
+
+ var ly = y - 1 < 0 ? 0 : y - 1;
+ var uy = y + 1 > height - 1 ? height - 1 : y + 1;
+ var lx = x - 1 < 0 ? 0 : x - 1;
+ var ux = x + 1 > width - 1 ? width - 1 : x + 1;
+
+ var points = [];
+ var origin = [ 0, 0, data[ ( y * width + x ) * 4 ] / 255 * depth ];
+ points.push( [ - 1, 0, data[ ( y * width + lx ) * 4 ] / 255 * depth ] );
+ points.push( [ - 1, - 1, data[ ( ly * width + lx ) * 4 ] / 255 * depth ] );
+ points.push( [ 0, - 1, data[ ( ly * width + x ) * 4 ] / 255 * depth ] );
+ points.push( [ 1, - 1, data[ ( ly * width + ux ) * 4 ] / 255 * depth ] );
+ points.push( [ 1, 0, data[ ( y * width + ux ) * 4 ] / 255 * depth ] );
+ points.push( [ 1, 1, data[ ( uy * width + ux ) * 4 ] / 255 * depth ] );
+ points.push( [ 0, 1, data[ ( uy * width + x ) * 4 ] / 255 * depth ] );
+ points.push( [ - 1, 1, data[ ( uy * width + lx ) * 4 ] / 255 * depth ] );
+
+ var normals = [];
+ var num_points = points.length;
+
+ for ( var i = 0; i < num_points; i ++ ) {
+
+ var v1 = points[ i ];
+ var v2 = points[ ( i + 1 ) % num_points ];
+ v1 = subtract( v1, origin );
+ v2 = subtract( v2, origin );
+ normals.push( normalize( cross( v1, v2 ) ) );
+
+ }
+
+ var normal = [ 0, 0, 0 ];
+
+ for ( var i = 0; i < normals.length; i ++ ) {
+
+ normal[ 0 ] += normals[ i ][ 0 ];
+ normal[ 1 ] += normals[ i ][ 1 ];
+ normal[ 2 ] += normals[ i ][ 2 ];
+
+ }
+
+ normal[ 0 ] /= normals.length;
+ normal[ 1 ] /= normals.length;
+ normal[ 2 ] /= normals.length;
+
+ var idx = ( y * width + x ) * 4;
+
+ output[ idx ] = ( ( normal[ 0 ] + 1.0 ) / 2.0 * 255 ) | 0;
+ output[ idx + 1 ] = ( ( normal[ 1 ] + 1.0 ) / 2.0 * 255 ) | 0;
+ output[ idx + 2 ] = ( normal[ 2 ] * 255 ) | 0;
+ output[ idx + 3 ] = 255;
+
+ }
+
+ }
+
+ context.putImageData( imageData, 0, 0 );
+
+ return canvas;
+
+ },
+
+ generateDataTexture: function ( width, height, color ) {
+
+ var size = width * height;
+ var data = new Uint8Array( 3 * size );
+
+ var r = Math.floor( color.r * 255 );
+ var g = Math.floor( color.g * 255 );
+ var b = Math.floor( color.b * 255 );
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ data[ i * 3 ] = r;
+ data[ i * 3 + 1 ] = g;
+ data[ i * 3 + 2 ] = b;
+
+ }
+
+ var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat );
+ texture.needsUpdate = true;
+
+ return texture;
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SceneUtils = {
+
+ createMultiMaterialObject: function ( geometry, materials ) {
+
+ var group = new THREE.Object3D();
+
+ for ( var i = 0, l = materials.length; i < l; i ++ ) {
+
+ group.add( new THREE.Mesh( geometry, materials[ i ] ) );
+
+ }
+
+ return group;
+
+ },
+
+ detach : function ( child, parent, scene ) {
+
+ child.applyMatrix( parent.matrixWorld );
+ parent.remove( child );
+ scene.add( child );
+
+ },
+
+ attach: function ( child, scene, parent ) {
+
+ var matrixWorldInverse = new THREE.Matrix4();
+ matrixWorldInverse.getInverse( parent.matrixWorld );
+ child.applyMatrix( matrixWorldInverse );
+
+ scene.remove( child );
+ parent.add( child );
+
+ }
+
+};
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * For Text operations in three.js (See TextGeometry)
+ *
+ * It uses techniques used in:
+ *
+ * typeface.js and canvastext
+ * For converting fonts and rendering with javascript
+ * http://typeface.neocracy.org
+ *
+ * Triangulation ported from AS3
+ * Simple Polygon Triangulation
+ * http://actionsnippet.com/?p=1462
+ *
+ * A Method to triangulate shapes with holes
+ * http://www.sakri.net/blog/2009/06/12/an-approach-to-triangulating-polygons-with-holes/
+ *
+ */
+
+THREE.FontUtils = {
+
+ faces : {},
+
+ // Just for now. face[weight][style]
+
+ face : "helvetiker",
+ weight: "normal",
+ style : "normal",
+ size : 150,
+ divisions : 10,
+
+ getFace : function() {
+
+ return this.faces[ this.face ][ this.weight ][ this.style ];
+
+ },
+
+ loadFace : function( data ) {
+
+ var family = data.familyName.toLowerCase();
+
+ var ThreeFont = this;
+
+ ThreeFont.faces[ family ] = ThreeFont.faces[ family ] || {};
+
+ ThreeFont.faces[ family ][ data.cssFontWeight ] = ThreeFont.faces[ family ][ data.cssFontWeight ] || {};
+ ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data;
+
+ var face = ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data;
+
+ return data;
+
+ },
+
+ drawText : function( text ) {
+
+ var characterPts = [], allPts = [];
+
+ // RenderText
+
+ var i, p,
+ face = this.getFace(),
+ scale = this.size / face.resolution,
+ offset = 0,
+ chars = String( text ).split( '' ),
+ length = chars.length;
+
+ var fontPaths = [];
+
+ for ( i = 0; i < length; i ++ ) {
+
+ var path = new THREE.Path();
+
+ var ret = this.extractGlyphPoints( chars[ i ], face, scale, offset, path );
+ offset += ret.offset;
+
+ fontPaths.push( ret.path );
+
+ }
+
+ // get the width
+
+ var width = offset / 2;
+ //
+ // for ( p = 0; p < allPts.length; p++ ) {
+ //
+ // allPts[ p ].x -= width;
+ //
+ // }
+
+ //var extract = this.extractPoints( allPts, characterPts );
+ //extract.contour = allPts;
+
+ //extract.paths = fontPaths;
+ //extract.offset = width;
+
+ return { paths : fontPaths, offset : width };
+
+ },
+
+
+
+
+ extractGlyphPoints : function( c, face, scale, offset, path ) {
+
+ var pts = [];
+
+ var i, i2, divisions,
+ outline, action, length,
+ scaleX, scaleY,
+ x, y, cpx, cpy, cpx0, cpy0, cpx1, cpy1, cpx2, cpy2,
+ laste,
+ glyph = face.glyphs[ c ] || face.glyphs[ '?' ];
+
+ if ( !glyph ) return;
+
+ if ( glyph.o ) {
+
+ outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
+ length = outline.length;
+
+ scaleX = scale;
+ scaleY = scale;
+
+ for ( i = 0; i < length; ) {
+
+ action = outline[ i ++ ];
+
+ //console.log( action );
+
+ switch( action ) {
+
+ case 'm':
+
+ // Move To
+
+ x = outline[ i++ ] * scaleX + offset;
+ y = outline[ i++ ] * scaleY;
+
+ path.moveTo( x, y );
+ break;
+
+ case 'l':
+
+ // Line To
+
+ x = outline[ i++ ] * scaleX + offset;
+ y = outline[ i++ ] * scaleY;
+ path.lineTo(x,y);
+ break;
+
+ case 'q':
+
+ // QuadraticCurveTo
+
+ cpx = outline[ i++ ] * scaleX + offset;
+ cpy = outline[ i++ ] * scaleY;
+ cpx1 = outline[ i++ ] * scaleX + offset;
+ cpy1 = outline[ i++ ] * scaleY;
+
+ path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);
+
+ laste = pts[ pts.length - 1 ];
+
+ if ( laste ) {
+
+ cpx0 = laste.x;
+ cpy0 = laste.y;
+
+ for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) {
+
+ var t = i2 / divisions;
+ var tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx );
+ var ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy );
+ }
+
+ }
+
+ break;
+
+ case 'b':
+
+ // Cubic Bezier Curve
+
+ cpx = outline[ i++ ] * scaleX + offset;
+ cpy = outline[ i++ ] * scaleY;
+ cpx1 = outline[ i++ ] * scaleX + offset;
+ cpy1 = outline[ i++ ] * -scaleY;
+ cpx2 = outline[ i++ ] * scaleX + offset;
+ cpy2 = outline[ i++ ] * -scaleY;
+
+ path.bezierCurveTo( cpx, cpy, cpx1, cpy1, cpx2, cpy2 );
+
+ laste = pts[ pts.length - 1 ];
+
+ if ( laste ) {
+
+ cpx0 = laste.x;
+ cpy0 = laste.y;
+
+ for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) {
+
+ var t = i2 / divisions;
+ var tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx );
+ var ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy );
+
+ }
+
+ }
+
+ break;
+
+ }
+
+ }
+ }
+
+
+
+ return { offset: glyph.ha*scale, path:path};
+ }
+
+};
+
+
+THREE.FontUtils.generateShapes = function( text, parameters ) {
+
+ // Parameters
+
+ parameters = parameters || {};
+
+ var size = parameters.size !== undefined ? parameters.size : 100;
+ var curveSegments = parameters.curveSegments !== undefined ? parameters.curveSegments: 4;
+
+ var font = parameters.font !== undefined ? parameters.font : "helvetiker";
+ var weight = parameters.weight !== undefined ? parameters.weight : "normal";
+ var style = parameters.style !== undefined ? parameters.style : "normal";
+
+ THREE.FontUtils.size = size;
+ THREE.FontUtils.divisions = curveSegments;
+
+ THREE.FontUtils.face = font;
+ THREE.FontUtils.weight = weight;
+ THREE.FontUtils.style = style;
+
+ // Get a Font data json object
+
+ var data = THREE.FontUtils.drawText( text );
+
+ var paths = data.paths;
+ var shapes = [];
+
+ for ( var p = 0, pl = paths.length; p < pl; p ++ ) {
+
+ Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+
+ }
+
+ return shapes;
+
+};
+
+
+/**
+ * This code is a quick port of code written in C++ which was submitted to
+ * flipcode.com by John W. Ratcliff // July 22, 2000
+ * See original code and more information here:
+ * http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml
+ *
+ * ported to actionscript by Zevan Rosser
+ * www.actionsnippet.com
+ *
+ * ported to javascript by Joshua Koo
+ * http://www.lab4games.net/zz85/blog
+ *
+ */
+
+
+( function( namespace ) {
+
+ var EPSILON = 0.0000000001;
+
+ // takes in an contour array and returns
+
+ var process = function( contour, indices ) {
+
+ var n = contour.length;
+
+ if ( n < 3 ) return null;
+
+ var result = [],
+ verts = [],
+ vertIndices = [];
+
+ /* we want a counter-clockwise polygon in verts */
+
+ var u, v, w;
+
+ if ( area( contour ) > 0.0 ) {
+
+ for ( v = 0; v < n; v++ ) verts[ v ] = v;
+
+ } else {
+
+ for ( v = 0; v < n; v++ ) verts[ v ] = ( n - 1 ) - v;
+
+ }
+
+ var nv = n;
+
+ /* remove nv - 2 vertices, creating 1 triangle every time */
+
+ var count = 2 * nv; /* error detection */
+
+ for( v = nv - 1; nv > 2; ) {
+
+ /* if we loop, it is probably a non-simple polygon */
+
+ if ( ( count-- ) <= 0 ) {
+
+ //** Triangulate: ERROR - probable bad polygon!
+
+ //throw ( "Warning, unable to triangulate polygon!" );
+ //return null;
+ // Sometimes warning is fine, especially polygons are triangulated in reverse.
+ console.log( "Warning, unable to triangulate polygon!" );
+
+ if ( indices ) return vertIndices;
+ return result;
+
+ }
+
+ /* three consecutive vertices in current polygon, */
+
+ u = v; if ( nv <= u ) u = 0; /* previous */
+ v = u + 1; if ( nv <= v ) v = 0; /* new v */
+ w = v + 1; if ( nv <= w ) w = 0; /* next */
+
+ if ( snip( contour, u, v, w, nv, verts ) ) {
+
+ var a, b, c, s, t;
+
+ /* true names of the vertices */
+
+ a = verts[ u ];
+ b = verts[ v ];
+ c = verts[ w ];
+
+ /* output Triangle */
+
+ result.push( [ contour[ a ],
+ contour[ b ],
+ contour[ c ] ] );
+
+
+ vertIndices.push( [ verts[ u ], verts[ v ], verts[ w ] ] );
+
+ /* remove v from the remaining polygon */
+
+ for( s = v, t = v + 1; t < nv; s++, t++ ) {
+
+ verts[ s ] = verts[ t ];
+
+ }
+
+ nv--;
+
+ /* reset error detection counter */
+
+ count = 2 * nv;
+
+ }
+
+ }
+
+ if ( indices ) return vertIndices;
+ return result;
+
+ };
+
+ // calculate area of the contour polygon
+
+ var area = function ( contour ) {
+
+ var n = contour.length;
+ var a = 0.0;
+
+ for( var p = n - 1, q = 0; q < n; p = q++ ) {
+
+ a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
+
+ }
+
+ return a * 0.5;
+
+ };
+
+ var snip = function ( contour, u, v, w, n, verts ) {
+
+ var p;
+ var ax, ay, bx, by;
+ var cx, cy, px, py;
+
+ ax = contour[ verts[ u ] ].x;
+ ay = contour[ verts[ u ] ].y;
+
+ bx = contour[ verts[ v ] ].x;
+ by = contour[ verts[ v ] ].y;
+
+ cx = contour[ verts[ w ] ].x;
+ cy = contour[ verts[ w ] ].y;
+
+ if ( EPSILON > (((bx-ax)*(cy-ay)) - ((by-ay)*(cx-ax))) ) return false;
+
+ var aX, aY, bX, bY, cX, cY;
+ var apx, apy, bpx, bpy, cpx, cpy;
+ var cCROSSap, bCROSScp, aCROSSbp;
+
+ aX = cx - bx; aY = cy - by;
+ bX = ax - cx; bY = ay - cy;
+ cX = bx - ax; cY = by - ay;
+
+ for ( p = 0; p < n; p++ ) {
+
+ if( (p === u) || (p === v) || (p === w) ) continue;
+
+ px = contour[ verts[ p ] ].x
+ py = contour[ verts[ p ] ].y
+
+ apx = px - ax; apy = py - ay;
+ bpx = px - bx; bpy = py - by;
+ cpx = px - cx; cpy = py - cy;
+
+ // see if p is inside triangle abc
+
+ aCROSSbp = aX*bpy - aY*bpx;
+ cCROSSap = cX*apy - cY*apx;
+ bCROSScp = bX*cpy - bY*cpx;
+
+ if ( (aCROSSbp >= -EPSILON) && (bCROSScp >= -EPSILON) && (cCROSSap >= -EPSILON) ) return false;
+
+ }
+
+ return true;
+
+ };
+
+
+ namespace.Triangulate = process;
+ namespace.Triangulate.area = area;
+
+ return namespace;
+
+})(THREE.FontUtils);
+
+// To use the typeface.js face files, hook up the API
+self._typeface_js = { faces: THREE.FontUtils.faces, loadFace: THREE.FontUtils.loadFace };
+THREE.typeface_js = self._typeface_js;
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Extensible curve object
+ *
+ * Some common of Curve methods
+ * .getPoint(t), getTangent(t)
+ * .getPointAt(u), getTagentAt(u)
+ * .getPoints(), .getSpacedPoints()
+ * .getLength()
+ * .updateArcLengths()
+ *
+ * This following classes subclasses THREE.Curve:
+ *
+ * -- 2d classes --
+ * THREE.LineCurve
+ * THREE.QuadraticBezierCurve
+ * THREE.CubicBezierCurve
+ * THREE.SplineCurve
+ * THREE.ArcCurve
+ * THREE.EllipseCurve
+ *
+ * -- 3d classes --
+ * THREE.LineCurve3
+ * THREE.QuadraticBezierCurve3
+ * THREE.CubicBezierCurve3
+ * THREE.SplineCurve3
+ * THREE.ClosedSplineCurve3
+ *
+ * A series of curves can be represented as a THREE.CurvePath
+ *
+ **/
+
+/**************************************************************
+ * Abstract Curve base class
+ **************************************************************/
+
+THREE.Curve = function () {
+
+};
+
+// Virtual base class method to overwrite and implement in subclasses
+// - t [0 .. 1]
+
+THREE.Curve.prototype.getPoint = function ( t ) {
+
+ console.log( "Warning, getPoint() not implemented!" );
+ return null;
+
+};
+
+// Get point at relative position in curve according to arc length
+// - u [0 .. 1]
+
+THREE.Curve.prototype.getPointAt = function ( u ) {
+
+ var t = this.getUtoTmapping( u );
+ return this.getPoint( t );
+
+};
+
+// Get sequence of points using getPoint( t )
+
+THREE.Curve.prototype.getPoints = function ( divisions ) {
+
+ if ( !divisions ) divisions = 5;
+
+ var d, pts = [];
+
+ for ( d = 0; d <= divisions; d ++ ) {
+
+ pts.push( this.getPoint( d / divisions ) );
+
+ }
+
+ return pts;
+
+};
+
+// Get sequence of points using getPointAt( u )
+
+THREE.Curve.prototype.getSpacedPoints = function ( divisions ) {
+
+ if ( !divisions ) divisions = 5;
+
+ var d, pts = [];
+
+ for ( d = 0; d <= divisions; d ++ ) {
+
+ pts.push( this.getPointAt( d / divisions ) );
+
+ }
+
+ return pts;
+
+};
+
+// Get total curve arc length
+
+THREE.Curve.prototype.getLength = function () {
+
+ var lengths = this.getLengths();
+ return lengths[ lengths.length - 1 ];
+
+};
+
+// Get list of cumulative segment lengths
+
+THREE.Curve.prototype.getLengths = function ( divisions ) {
+
+ if ( !divisions ) divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions): 200;
+
+ if ( this.cacheArcLengths
+ && ( this.cacheArcLengths.length == divisions + 1 )
+ && !this.needsUpdate) {
+
+ //console.log( "cached", this.cacheArcLengths );
+ return this.cacheArcLengths;
+
+ }
+
+ this.needsUpdate = false;
+
+ var cache = [];
+ var current, last = this.getPoint( 0 );
+ var p, sum = 0;
+
+ cache.push( 0 );
+
+ for ( p = 1; p <= divisions; p ++ ) {
+
+ current = this.getPoint ( p / divisions );
+ sum += current.distanceTo( last );
+ cache.push( sum );
+ last = current;
+
+ }
+
+ this.cacheArcLengths = cache;
+
+ return cache; // { sums: cache, sum:sum }; Sum is in the last element.
+
+};
+
+
+THREE.Curve.prototype.updateArcLengths = function() {
+ this.needsUpdate = true;
+ this.getLengths();
+};
+
+// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equi distance
+
+THREE.Curve.prototype.getUtoTmapping = function ( u, distance ) {
+
+ var arcLengths = this.getLengths();
+
+ var i = 0, il = arcLengths.length;
+
+ var targetArcLength; // The targeted u distance value to get
+
+ if ( distance ) {
+
+ targetArcLength = distance;
+
+ } else {
+
+ targetArcLength = u * arcLengths[ il - 1 ];
+
+ }
+
+ //var time = Date.now();
+
+ // binary search for the index with largest value smaller than target u distance
+
+ var low = 0, high = il - 1, comparison;
+
+ while ( low <= high ) {
+
+ i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
+
+ comparison = arcLengths[ i ] - targetArcLength;
+
+ if ( comparison < 0 ) {
+
+ low = i + 1;
+ continue;
+
+ } else if ( comparison > 0 ) {
+
+ high = i - 1;
+ continue;
+
+ } else {
+
+ high = i;
+ break;
+
+ // DONE
+
+ }
+
+ }
+
+ i = high;
+
+ //console.log('b' , i, low, high, Date.now()- time);
+
+ if ( arcLengths[ i ] == targetArcLength ) {
+
+ var t = i / ( il - 1 );
+ return t;
+
+ }
+
+ // we could get finer grain at lengths, or use simple interpolatation between two points
+
+ var lengthBefore = arcLengths[ i ];
+ var lengthAfter = arcLengths[ i + 1 ];
+
+ var segmentLength = lengthAfter - lengthBefore;
+
+ // determine where we are between the 'before' and 'after' points
+
+ var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
+
+ // add that fractional amount to t
+
+ var t = ( i + segmentFraction ) / ( il -1 );
+
+ return t;
+
+};
+
+// Returns a unit vector tangent at t
+// In case any sub curve does not implement its tangent derivation,
+// 2 points a small delta apart will be used to find its gradient
+// which seems to give a reasonable approximation
+
+THREE.Curve.prototype.getTangent = function( t ) {
+
+ var delta = 0.0001;
+ var t1 = t - delta;
+ var t2 = t + delta;
+
+ // Capping in case of danger
+
+ if ( t1 < 0 ) t1 = 0;
+ if ( t2 > 1 ) t2 = 1;
+
+ var pt1 = this.getPoint( t1 );
+ var pt2 = this.getPoint( t2 );
+
+ var vec = pt2.clone().sub(pt1);
+ return vec.normalize();
+
+};
+
+
+THREE.Curve.prototype.getTangentAt = function ( u ) {
+
+ var t = this.getUtoTmapping( u );
+ return this.getTangent( t );
+
+};
+
+
+
+
+
+/**************************************************************
+ * Utils
+ **************************************************************/
+
+THREE.Curve.Utils = {
+
+ tangentQuadraticBezier: function ( t, p0, p1, p2 ) {
+
+ return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 );
+
+ },
+
+ // Puay Bing, thanks for helping with this derivative!
+
+ tangentCubicBezier: function (t, p0, p1, p2, p3 ) {
+
+ return -3 * p0 * (1 - t) * (1 - t) +
+ 3 * p1 * (1 - t) * (1-t) - 6 *t *p1 * (1-t) +
+ 6 * t * p2 * (1-t) - 3 * t * t * p2 +
+ 3 * t * t * p3;
+ },
+
+
+ tangentSpline: function ( t, p0, p1, p2, p3 ) {
+
+ // To check if my formulas are correct
+
+ var h00 = 6 * t * t - 6 * t; // derived from 2t^3 − 3t^2 + 1
+ var h10 = 3 * t * t - 4 * t + 1; // t^3 − 2t^2 + t
+ var h01 = -6 * t * t + 6 * t; // − 2t3 + 3t2
+ var h11 = 3 * t * t - 2 * t; // t3 − t2
+
+ return h00 + h10 + h01 + h11;
+
+ },
+
+ // Catmull-Rom
+
+ interpolate: function( p0, p1, p2, p3, t ) {
+
+ var v0 = ( p2 - p0 ) * 0.5;
+ var v1 = ( p3 - p1 ) * 0.5;
+ var t2 = t * t;
+ var t3 = t * t2;
+ return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+ }
+
+};
+
+
+// TODO: Transformation for Curves?
+
+/**************************************************************
+ * 3D Curves
+ **************************************************************/
+
+// A Factory method for creating new curve subclasses
+
+THREE.Curve.create = function ( constructor, getPointFunc ) {
+
+ constructor.prototype = Object.create( THREE.Curve.prototype );
+ constructor.prototype.getPoint = getPointFunc;
+
+ return constructor;
+
+};
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ *
+ **/
+
+/**************************************************************
+ * Curved Path - a curve path is simply a array of connected
+ * curves, but retains the api of a curve
+ **************************************************************/
+
+THREE.CurvePath = function () {
+
+ this.curves = [];
+ this.bends = [];
+
+ this.autoClose = false; // Automatically closes the path
+};
+
+THREE.CurvePath.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.CurvePath.prototype.add = function ( curve ) {
+
+ this.curves.push( curve );
+
+};
+
+THREE.CurvePath.prototype.checkConnection = function() {
+ // TODO
+ // If the ending of curve is not connected to the starting
+ // or the next curve, then, this is not a real path
+};
+
+THREE.CurvePath.prototype.closePath = function() {
+ // TODO Test
+ // and verify for vector3 (needs to implement equals)
+ // Add a line curve if start and end of lines are not connected
+ var startPoint = this.curves[0].getPoint(0);
+ var endPoint = this.curves[this.curves.length-1].getPoint(1);
+
+ if (!startPoint.equals(endPoint)) {
+ this.curves.push( new THREE.LineCurve(endPoint, startPoint) );
+ }
+
+};
+
+// To get accurate point with reference to
+// entire path distance at time t,
+// following has to be done:
+
+// 1. Length of each sub path have to be known
+// 2. Locate and identify type of curve
+// 3. Get t for the curve
+// 4. Return curve.getPointAt(t')
+
+THREE.CurvePath.prototype.getPoint = function( t ) {
+
+ var d = t * this.getLength();
+ var curveLengths = this.getCurveLengths();
+ var i = 0, diff, curve;
+
+ // To think about boundaries points.
+
+ while ( i < curveLengths.length ) {
+
+ if ( curveLengths[ i ] >= d ) {
+
+ diff = curveLengths[ i ] - d;
+ curve = this.curves[ i ];
+
+ var u = 1 - diff / curve.getLength();
+
+ return curve.getPointAt( u );
+
+ break;
+ }
+
+ i ++;
+
+ }
+
+ return null;
+
+ // loop where sum != 0, sum > d , sum+1 maxX ) maxX = p.x;
+ else if ( p.x < minX ) minX = p.x;
+
+ if ( p.y > maxY ) maxY = p.y;
+ else if ( p.y < minY ) minY = p.y;
+
+ if ( v3 ) {
+
+ if ( p.z > maxZ ) maxZ = p.z;
+ else if ( p.z < minZ ) minZ = p.z;
+
+ }
+
+ sum.add( p );
+
+ }
+
+ var ret = {
+
+ minX: minX,
+ minY: minY,
+ maxX: maxX,
+ maxY: maxY,
+ centroid: sum.divideScalar( il )
+
+ };
+
+ if ( v3 ) {
+
+ ret.maxZ = maxZ;
+ ret.minZ = minZ;
+
+ }
+
+ return ret;
+
+};
+
+/**************************************************************
+ * Create Geometries Helpers
+ **************************************************************/
+
+/// Generate geometry from path points (for Line or ParticleSystem objects)
+
+THREE.CurvePath.prototype.createPointsGeometry = function( divisions ) {
+
+ var pts = this.getPoints( divisions, true );
+ return this.createGeometry( pts );
+
+};
+
+// Generate geometry from equidistance sampling along the path
+
+THREE.CurvePath.prototype.createSpacedPointsGeometry = function( divisions ) {
+
+ var pts = this.getSpacedPoints( divisions, true );
+ return this.createGeometry( pts );
+
+};
+
+THREE.CurvePath.prototype.createGeometry = function( points ) {
+
+ var geometry = new THREE.Geometry();
+
+ for ( var i = 0; i < points.length; i ++ ) {
+
+ geometry.vertices.push( new THREE.Vector3( points[ i ].x, points[ i ].y, points[ i ].z || 0) );
+
+ }
+
+ return geometry;
+
+};
+
+
+/**************************************************************
+ * Bend / Wrap Helper Methods
+ **************************************************************/
+
+// Wrap path / Bend modifiers?
+
+THREE.CurvePath.prototype.addWrapPath = function ( bendpath ) {
+
+ this.bends.push( bendpath );
+
+};
+
+THREE.CurvePath.prototype.getTransformedPoints = function( segments, bends ) {
+
+ var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints
+ var i, il;
+
+ if ( !bends ) {
+
+ bends = this.bends;
+
+ }
+
+ for ( i = 0, il = bends.length; i < il; i ++ ) {
+
+ oldPts = this.getWrapPoints( oldPts, bends[ i ] );
+
+ }
+
+ return oldPts;
+
+};
+
+THREE.CurvePath.prototype.getTransformedSpacedPoints = function( segments, bends ) {
+
+ var oldPts = this.getSpacedPoints( segments );
+
+ var i, il;
+
+ if ( !bends ) {
+
+ bends = this.bends;
+
+ }
+
+ for ( i = 0, il = bends.length; i < il; i ++ ) {
+
+ oldPts = this.getWrapPoints( oldPts, bends[ i ] );
+
+ }
+
+ return oldPts;
+
+};
+
+// This returns getPoints() bend/wrapped around the contour of a path.
+// Read http://www.planetclegg.com/projects/WarpingTextToSplines.html
+
+THREE.CurvePath.prototype.getWrapPoints = function ( oldPts, path ) {
+
+ var bounds = this.getBoundingBox();
+
+ var i, il, p, oldX, oldY, xNorm;
+
+ for ( i = 0, il = oldPts.length; i < il; i ++ ) {
+
+ p = oldPts[ i ];
+
+ oldX = p.x;
+ oldY = p.y;
+
+ xNorm = oldX / bounds.maxX;
+
+ // If using actual distance, for length > path, requires line extrusions
+ //xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance
+
+ xNorm = path.getUtoTmapping( xNorm, oldX );
+
+ // check for out of bounds?
+
+ var pathPt = path.getPoint( xNorm );
+ var normal = path.getNormalVector( xNorm ).multiplyScalar( oldY );
+
+ p.x = pathPt.x + normal.x;
+ p.y = pathPt.y + normal.y;
+
+ }
+
+ return oldPts;
+
+};
+
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Gyroscope = function () {
+
+ THREE.Object3D.call( this );
+
+};
+
+THREE.Gyroscope.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Gyroscope.prototype.updateMatrixWorld = function ( force ) {
+
+ this.matrixAutoUpdate && this.updateMatrix();
+
+ // update matrixWorld
+
+ if ( this.matrixWorldNeedsUpdate || force ) {
+
+ if ( this.parent ) {
+
+ this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+ this.matrixWorld.decompose( this.translationWorld, this.quaternionWorld, this.scaleWorld );
+ this.matrix.decompose( this.translationObject, this.quaternionObject, this.scaleObject );
+
+ this.matrixWorld.compose( this.translationWorld, this.quaternionObject, this.scaleWorld );
+
+
+ } else {
+
+ this.matrixWorld.copy( this.matrix );
+
+ }
+
+
+ this.matrixWorldNeedsUpdate = false;
+
+ force = true;
+
+ }
+
+ // update children
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ this.children[ i ].updateMatrixWorld( force );
+
+ }
+
+};
+
+THREE.Gyroscope.prototype.translationWorld = new THREE.Vector3();
+THREE.Gyroscope.prototype.translationObject = new THREE.Vector3();
+THREE.Gyroscope.prototype.quaternionWorld = new THREE.Quaternion();
+THREE.Gyroscope.prototype.quaternionObject = new THREE.Quaternion();
+THREE.Gyroscope.prototype.scaleWorld = new THREE.Vector3();
+THREE.Gyroscope.prototype.scaleObject = new THREE.Vector3();
+
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Creates free form 2d path using series of points, lines or curves.
+ *
+ **/
+
+THREE.Path = function ( points ) {
+
+ THREE.CurvePath.call(this);
+
+ this.actions = [];
+
+ if ( points ) {
+
+ this.fromPoints( points );
+
+ }
+
+};
+
+THREE.Path.prototype = Object.create( THREE.CurvePath.prototype );
+
+THREE.PathActions = {
+
+ MOVE_TO: 'moveTo',
+ LINE_TO: 'lineTo',
+ QUADRATIC_CURVE_TO: 'quadraticCurveTo', // Bezier quadratic curve
+ BEZIER_CURVE_TO: 'bezierCurveTo', // Bezier cubic curve
+ CSPLINE_THRU: 'splineThru', // Catmull-rom spline
+ ARC: 'arc', // Circle
+ ELLIPSE: 'ellipse'
+};
+
+// TODO Clean up PATH API
+
+// Create path using straight lines to connect all points
+// - vectors: array of Vector2
+
+THREE.Path.prototype.fromPoints = function ( vectors ) {
+
+ this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y );
+
+ for ( var v = 1, vlen = vectors.length; v < vlen; v ++ ) {
+
+ this.lineTo( vectors[ v ].x, vectors[ v ].y );
+
+ };
+
+};
+
+// startPath() endPath()?
+
+THREE.Path.prototype.moveTo = function ( x, y ) {
+
+ var args = Array.prototype.slice.call( arguments );
+ this.actions.push( { action: THREE.PathActions.MOVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.lineTo = function ( x, y ) {
+
+ var args = Array.prototype.slice.call( arguments );
+
+ var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ var curve = new THREE.LineCurve( new THREE.Vector2( x0, y0 ), new THREE.Vector2( x, y ) );
+ this.curves.push( curve );
+
+ this.actions.push( { action: THREE.PathActions.LINE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.quadraticCurveTo = function( aCPx, aCPy, aX, aY ) {
+
+ var args = Array.prototype.slice.call( arguments );
+
+ var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ var curve = new THREE.QuadraticBezierCurve( new THREE.Vector2( x0, y0 ),
+ new THREE.Vector2( aCPx, aCPy ),
+ new THREE.Vector2( aX, aY ) );
+ this.curves.push( curve );
+
+ this.actions.push( { action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.bezierCurveTo = function( aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY ) {
+
+ var args = Array.prototype.slice.call( arguments );
+
+ var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ var curve = new THREE.CubicBezierCurve( new THREE.Vector2( x0, y0 ),
+ new THREE.Vector2( aCP1x, aCP1y ),
+ new THREE.Vector2( aCP2x, aCP2y ),
+ new THREE.Vector2( aX, aY ) );
+ this.curves.push( curve );
+
+ this.actions.push( { action: THREE.PathActions.BEZIER_CURVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.splineThru = function( pts /*Array of Vector*/ ) {
+
+ var args = Array.prototype.slice.call( arguments );
+ var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+//---
+ var npts = [ new THREE.Vector2( x0, y0 ) ];
+ Array.prototype.push.apply( npts, pts );
+
+ var curve = new THREE.SplineCurve( npts );
+ this.curves.push( curve );
+
+ this.actions.push( { action: THREE.PathActions.CSPLINE_THRU, args: args } );
+
+};
+
+// FUTURE: Change the API or follow canvas API?
+
+THREE.Path.prototype.arc = function ( aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise ) {
+
+ var lastargs = this.actions[ this.actions.length - 1].args;
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ this.absarc(aX + x0, aY + y0, aRadius,
+ aStartAngle, aEndAngle, aClockwise );
+
+ };
+
+ THREE.Path.prototype.absarc = function ( aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise ) {
+ this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);
+ };
+
+THREE.Path.prototype.ellipse = function ( aX, aY, xRadius, yRadius,
+ aStartAngle, aEndAngle, aClockwise ) {
+
+ var lastargs = this.actions[ this.actions.length - 1].args;
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ this.absellipse(aX + x0, aY + y0, xRadius, yRadius,
+ aStartAngle, aEndAngle, aClockwise );
+
+ };
+
+
+THREE.Path.prototype.absellipse = function ( aX, aY, xRadius, yRadius,
+ aStartAngle, aEndAngle, aClockwise ) {
+
+ var args = Array.prototype.slice.call( arguments );
+ var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius,
+ aStartAngle, aEndAngle, aClockwise );
+ this.curves.push( curve );
+
+ var lastPoint = curve.getPoint(1);
+ args.push(lastPoint.x);
+ args.push(lastPoint.y);
+
+ this.actions.push( { action: THREE.PathActions.ELLIPSE, args: args } );
+
+ };
+
+THREE.Path.prototype.getSpacedPoints = function ( divisions, closedPath ) {
+
+ if ( ! divisions ) divisions = 40;
+
+ var points = [];
+
+ for ( var i = 0; i < divisions; i ++ ) {
+
+ points.push( this.getPoint( i / divisions ) );
+
+ //if( !this.getPoint( i / divisions ) ) throw "DIE";
+
+ }
+
+ // if ( closedPath ) {
+ //
+ // points.push( points[ 0 ] );
+ //
+ // }
+
+ return points;
+
+};
+
+/* Return an array of vectors based on contour of the path */
+
+THREE.Path.prototype.getPoints = function( divisions, closedPath ) {
+
+ if (this.useSpacedPoints) {
+ console.log('tata');
+ return this.getSpacedPoints( divisions, closedPath );
+ }
+
+ divisions = divisions || 12;
+
+ var points = [];
+
+ var i, il, item, action, args;
+ var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
+ laste, j,
+ t, tx, ty;
+
+ for ( i = 0, il = this.actions.length; i < il; i ++ ) {
+
+ item = this.actions[ i ];
+
+ action = item.action;
+ args = item.args;
+
+ switch( action ) {
+
+ case THREE.PathActions.MOVE_TO:
+
+ points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
+
+ break;
+
+ case THREE.PathActions.LINE_TO:
+
+ points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
+
+ break;
+
+ case THREE.PathActions.QUADRATIC_CURVE_TO:
+
+ cpx = args[ 2 ];
+ cpy = args[ 3 ];
+
+ cpx1 = args[ 0 ];
+ cpy1 = args[ 1 ];
+
+ if ( points.length > 0 ) {
+
+ laste = points[ points.length - 1 ];
+
+ cpx0 = laste.x;
+ cpy0 = laste.y;
+
+ } else {
+
+ laste = this.actions[ i - 1 ].args;
+
+ cpx0 = laste[ laste.length - 2 ];
+ cpy0 = laste[ laste.length - 1 ];
+
+ }
+
+ for ( j = 1; j <= divisions; j ++ ) {
+
+ t = j / divisions;
+
+ tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx );
+ ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy );
+
+ points.push( new THREE.Vector2( tx, ty ) );
+
+ }
+
+ break;
+
+ case THREE.PathActions.BEZIER_CURVE_TO:
+
+ cpx = args[ 4 ];
+ cpy = args[ 5 ];
+
+ cpx1 = args[ 0 ];
+ cpy1 = args[ 1 ];
+
+ cpx2 = args[ 2 ];
+ cpy2 = args[ 3 ];
+
+ if ( points.length > 0 ) {
+
+ laste = points[ points.length - 1 ];
+
+ cpx0 = laste.x;
+ cpy0 = laste.y;
+
+ } else {
+
+ laste = this.actions[ i - 1 ].args;
+
+ cpx0 = laste[ laste.length - 2 ];
+ cpy0 = laste[ laste.length - 1 ];
+
+ }
+
+
+ for ( j = 1; j <= divisions; j ++ ) {
+
+ t = j / divisions;
+
+ tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx );
+ ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy );
+
+ points.push( new THREE.Vector2( tx, ty ) );
+
+ }
+
+ break;
+
+ case THREE.PathActions.CSPLINE_THRU:
+
+ laste = this.actions[ i - 1 ].args;
+
+ var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
+ var spts = [ last ];
+
+ var n = divisions * args[ 0 ].length;
+
+ spts = spts.concat( args[ 0 ] );
+
+ var spline = new THREE.SplineCurve( spts );
+
+ for ( j = 1; j <= n; j ++ ) {
+
+ points.push( spline.getPointAt( j / n ) ) ;
+
+ }
+
+ break;
+
+ case THREE.PathActions.ARC:
+
+ var aX = args[ 0 ], aY = args[ 1 ],
+ aRadius = args[ 2 ],
+ aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
+ aClockwise = !!args[ 5 ];
+
+ var deltaAngle = aEndAngle - aStartAngle;
+ var angle;
+ var tdivisions = divisions * 2;
+
+ for ( j = 1; j <= tdivisions; j ++ ) {
+
+ t = j / tdivisions;
+
+ if ( ! aClockwise ) {
+
+ t = 1 - t;
+
+ }
+
+ angle = aStartAngle + t * deltaAngle;
+
+ tx = aX + aRadius * Math.cos( angle );
+ ty = aY + aRadius * Math.sin( angle );
+
+ //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
+
+ points.push( new THREE.Vector2( tx, ty ) );
+
+ }
+
+ //console.log(points);
+
+ break;
+
+ case THREE.PathActions.ELLIPSE:
+
+ var aX = args[ 0 ], aY = args[ 1 ],
+ xRadius = args[ 2 ],
+ yRadius = args[ 3 ],
+ aStartAngle = args[ 4 ], aEndAngle = args[ 5 ],
+ aClockwise = !!args[ 6 ];
+
+
+ var deltaAngle = aEndAngle - aStartAngle;
+ var angle;
+ var tdivisions = divisions * 2;
+
+ for ( j = 1; j <= tdivisions; j ++ ) {
+
+ t = j / tdivisions;
+
+ if ( ! aClockwise ) {
+
+ t = 1 - t;
+
+ }
+
+ angle = aStartAngle + t * deltaAngle;
+
+ tx = aX + xRadius * Math.cos( angle );
+ ty = aY + yRadius * Math.sin( angle );
+
+ //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
+
+ points.push( new THREE.Vector2( tx, ty ) );
+
+ }
+
+ //console.log(points);
+
+ break;
+
+ } // end switch
+
+ }
+
+
+
+ // Normalize to remove the closing point by default.
+ var lastPoint = points[ points.length - 1];
+ var EPSILON = 0.0000000001;
+ if ( Math.abs(lastPoint.x - points[ 0 ].x) < EPSILON &&
+ Math.abs(lastPoint.y - points[ 0 ].y) < EPSILON)
+ points.splice( points.length - 1, 1);
+ if ( closedPath ) {
+
+ points.push( points[ 0 ] );
+
+ }
+
+ return points;
+
+};
+
+// Breaks path into shapes
+
+THREE.Path.prototype.toShapes = function( isCCW ) {
+
+ var i, il, item, action, args;
+
+ var subPaths = [], lastPath = new THREE.Path();
+
+ for ( i = 0, il = this.actions.length; i < il; i ++ ) {
+
+ item = this.actions[ i ];
+
+ args = item.args;
+ action = item.action;
+
+ if ( action == THREE.PathActions.MOVE_TO ) {
+
+ if ( lastPath.actions.length != 0 ) {
+
+ subPaths.push( lastPath );
+ lastPath = new THREE.Path();
+
+ }
+
+ }
+
+ lastPath[ action ].apply( lastPath, args );
+
+ }
+
+ if ( lastPath.actions.length != 0 ) {
+
+ subPaths.push( lastPath );
+
+ }
+
+ // console.log(subPaths);
+
+ if ( subPaths.length == 0 ) return [];
+
+ var solid, tmpPath, tmpShape, shapes = [];
+
+ if ( subPaths.length == 1) {
+
+ tmpPath = subPaths[0];
+ tmpShape = new THREE.Shape();
+ tmpShape.actions = tmpPath.actions;
+ tmpShape.curves = tmpPath.curves;
+ shapes.push( tmpShape );
+ return shapes;
+
+ }
+
+ var holesFirst = !THREE.Shape.Utils.isClockWise( subPaths[ 0 ].getPoints() );
+ holesFirst = isCCW ? !holesFirst : holesFirst;
+
+ // console.log("Holes first", holesFirst);
+
+ if ( holesFirst ) {
+
+ tmpShape = new THREE.Shape();
+
+ for ( i = 0, il = subPaths.length; i < il; i ++ ) {
+
+ tmpPath = subPaths[ i ];
+ solid = THREE.Shape.Utils.isClockWise( tmpPath.getPoints() );
+ solid = isCCW ? !solid : solid;
+
+ if ( solid ) {
+
+ tmpShape.actions = tmpPath.actions;
+ tmpShape.curves = tmpPath.curves;
+
+ shapes.push( tmpShape );
+ tmpShape = new THREE.Shape();
+
+ //console.log('cw', i);
+
+ } else {
+
+ tmpShape.holes.push( tmpPath );
+
+ //console.log('ccw', i);
+
+ }
+
+ }
+
+ } else {
+
+ // Shapes first
+ tmpShape = undefined;
+
+ for ( i = 0, il = subPaths.length; i < il; i ++ ) {
+
+ tmpPath = subPaths[ i ];
+ solid = THREE.Shape.Utils.isClockWise( tmpPath.getPoints() );
+ solid = isCCW ? !solid : solid;
+
+ if ( solid ) {
+
+ if ( tmpShape ) shapes.push( tmpShape );
+
+ tmpShape = new THREE.Shape();
+ tmpShape.actions = tmpPath.actions;
+ tmpShape.curves = tmpPath.curves;
+
+ } else {
+
+ tmpShape.holes.push( tmpPath );
+
+ }
+
+ }
+
+ shapes.push( tmpShape );
+
+ }
+
+ //console.log("shape", shapes);
+
+ return shapes;
+
+};
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Defines a 2d shape plane using paths.
+ **/
+
+// STEP 1 Create a path.
+// STEP 2 Turn path into shape.
+// STEP 3 ExtrudeGeometry takes in Shape/Shapes
+// STEP 3a - Extract points from each shape, turn to vertices
+// STEP 3b - Triangulate each shape, add faces.
+
+THREE.Shape = function () {
+
+ THREE.Path.apply( this, arguments );
+ this.holes = [];
+
+};
+
+THREE.Shape.prototype = Object.create( THREE.Path.prototype );
+
+// Convenience method to return ExtrudeGeometry
+
+THREE.Shape.prototype.extrude = function ( options ) {
+
+ var extruded = new THREE.ExtrudeGeometry( this, options );
+ return extruded;
+
+};
+
+// Convenience method to return ShapeGeometry
+
+THREE.Shape.prototype.makeGeometry = function ( options ) {
+
+ var geometry = new THREE.ShapeGeometry( this, options );
+ return geometry;
+
+};
+
+// Get points of holes
+
+THREE.Shape.prototype.getPointsHoles = function ( divisions ) {
+
+ var i, il = this.holes.length, holesPts = [];
+
+ for ( i = 0; i < il; i ++ ) {
+
+ holesPts[ i ] = this.holes[ i ].getTransformedPoints( divisions, this.bends );
+
+ }
+
+ return holesPts;
+
+};
+
+// Get points of holes (spaced by regular distance)
+
+THREE.Shape.prototype.getSpacedPointsHoles = function ( divisions ) {
+
+ var i, il = this.holes.length, holesPts = [];
+
+ for ( i = 0; i < il; i ++ ) {
+
+ holesPts[ i ] = this.holes[ i ].getTransformedSpacedPoints( divisions, this.bends );
+
+ }
+
+ return holesPts;
+
+};
+
+
+// Get points of shape and holes (keypoints based on segments parameter)
+
+THREE.Shape.prototype.extractAllPoints = function ( divisions ) {
+
+ return {
+
+ shape: this.getTransformedPoints( divisions ),
+ holes: this.getPointsHoles( divisions )
+
+ };
+
+};
+
+THREE.Shape.prototype.extractPoints = function ( divisions ) {
+
+ if (this.useSpacedPoints) {
+ return this.extractAllSpacedPoints(divisions);
+ }
+
+ return this.extractAllPoints(divisions);
+
+};
+
+//
+// THREE.Shape.prototype.extractAllPointsWithBend = function ( divisions, bend ) {
+//
+// return {
+//
+// shape: this.transform( bend, divisions ),
+// holes: this.getPointsHoles( divisions, bend )
+//
+// };
+//
+// };
+
+// Get points of shape and holes (spaced by regular distance)
+
+THREE.Shape.prototype.extractAllSpacedPoints = function ( divisions ) {
+
+ return {
+
+ shape: this.getTransformedSpacedPoints( divisions ),
+ holes: this.getSpacedPointsHoles( divisions )
+
+ };
+
+};
+
+/**************************************************************
+ * Utils
+ **************************************************************/
+
+THREE.Shape.Utils = {
+
+ /*
+ contour - array of vector2 for contour
+ holes - array of array of vector2
+ */
+
+ removeHoles: function ( contour, holes ) {
+
+ var shape = contour.concat(); // work on this shape
+ var allpoints = shape.concat();
+
+ /* For each isolated shape, find the closest points and break to the hole to allow triangulation */
+
+
+ var prevShapeVert, nextShapeVert,
+ prevHoleVert, nextHoleVert,
+ holeIndex, shapeIndex,
+ shapeId, shapeGroup,
+ h, h2,
+ hole, shortest, d,
+ p, pts1, pts2,
+ tmpShape1, tmpShape2,
+ tmpHole1, tmpHole2,
+ verts = [];
+
+ for ( h = 0; h < holes.length; h ++ ) {
+
+ hole = holes[ h ];
+
+ /*
+ shapeholes[ h ].concat(); // preserves original
+ holes.push( hole );
+ */
+
+ Array.prototype.push.apply( allpoints, hole );
+
+ shortest = Number.POSITIVE_INFINITY;
+
+
+ // Find the shortest pair of pts between shape and hole
+
+ // Note: Actually, I'm not sure now if we could optimize this to be faster than O(m*n)
+ // Using distanceToSquared() intead of distanceTo() should speed a little
+ // since running square roots operations are reduced.
+
+ for ( h2 = 0; h2 < hole.length; h2 ++ ) {
+
+ pts1 = hole[ h2 ];
+ var dist = [];
+
+ for ( p = 0; p < shape.length; p++ ) {
+
+ pts2 = shape[ p ];
+ d = pts1.distanceToSquared( pts2 );
+ dist.push( d );
+
+ if ( d < shortest ) {
+
+ shortest = d;
+ holeIndex = h2;
+ shapeIndex = p;
+
+ }
+
+ }
+
+ }
+
+ //console.log("shortest", shortest, dist);
+
+ prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+ prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+ var areaapts = [
+
+ hole[ holeIndex ],
+ shape[ shapeIndex ],
+ shape[ prevShapeVert ]
+
+ ];
+
+ var areaa = THREE.FontUtils.Triangulate.area( areaapts );
+
+ var areabpts = [
+
+ hole[ holeIndex ],
+ hole[ prevHoleVert ],
+ shape[ shapeIndex ]
+
+ ];
+
+ var areab = THREE.FontUtils.Triangulate.area( areabpts );
+
+ var shapeOffset = 1;
+ var holeOffset = -1;
+
+ var oldShapeIndex = shapeIndex, oldHoleIndex = holeIndex;
+ shapeIndex += shapeOffset;
+ holeIndex += holeOffset;
+
+ if ( shapeIndex < 0 ) { shapeIndex += shape.length; }
+ shapeIndex %= shape.length;
+
+ if ( holeIndex < 0 ) { holeIndex += hole.length; }
+ holeIndex %= hole.length;
+
+ prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+ prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+ areaapts = [
+
+ hole[ holeIndex ],
+ shape[ shapeIndex ],
+ shape[ prevShapeVert ]
+
+ ];
+
+ var areaa2 = THREE.FontUtils.Triangulate.area( areaapts );
+
+ areabpts = [
+
+ hole[ holeIndex ],
+ hole[ prevHoleVert ],
+ shape[ shapeIndex ]
+
+ ];
+
+ var areab2 = THREE.FontUtils.Triangulate.area( areabpts );
+ //console.log(areaa,areab ,areaa2,areab2, ( areaa + areab ), ( areaa2 + areab2 ));
+
+ if ( ( areaa + areab ) > ( areaa2 + areab2 ) ) {
+
+ // In case areas are not correct.
+ //console.log("USE THIS");
+
+ shapeIndex = oldShapeIndex;
+ holeIndex = oldHoleIndex ;
+
+ if ( shapeIndex < 0 ) { shapeIndex += shape.length; }
+ shapeIndex %= shape.length;
+
+ if ( holeIndex < 0 ) { holeIndex += hole.length; }
+ holeIndex %= hole.length;
+
+ prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+ prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+ } else {
+
+ //console.log("USE THAT ")
+
+ }
+
+ tmpShape1 = shape.slice( 0, shapeIndex );
+ tmpShape2 = shape.slice( shapeIndex );
+ tmpHole1 = hole.slice( holeIndex );
+ tmpHole2 = hole.slice( 0, holeIndex );
+
+ // Should check orders here again?
+
+ var trianglea = [
+
+ hole[ holeIndex ],
+ shape[ shapeIndex ],
+ shape[ prevShapeVert ]
+
+ ];
+
+ var triangleb = [
+
+ hole[ holeIndex ] ,
+ hole[ prevHoleVert ],
+ shape[ shapeIndex ]
+
+ ];
+
+ verts.push( trianglea );
+ verts.push( triangleb );
+
+ shape = tmpShape1.concat( tmpHole1 ).concat( tmpHole2 ).concat( tmpShape2 );
+
+ }
+
+ return {
+
+ shape:shape, /* shape with no holes */
+ isolatedPts: verts, /* isolated faces */
+ allpoints: allpoints
+
+ }
+
+
+ },
+
+ triangulateShape: function ( contour, holes ) {
+
+ var shapeWithoutHoles = THREE.Shape.Utils.removeHoles( contour, holes );
+
+ var shape = shapeWithoutHoles.shape,
+ allpoints = shapeWithoutHoles.allpoints,
+ isolatedPts = shapeWithoutHoles.isolatedPts;
+
+ var triangles = THREE.FontUtils.Triangulate( shape, false ); // True returns indices for points of spooled shape
+
+ // To maintain reference to old shape, one must match coordinates, or offset the indices from original arrays. It's probably easier to do the first.
+
+ //console.log( "triangles",triangles, triangles.length );
+ //console.log( "allpoints",allpoints, allpoints.length );
+
+ var i, il, f, face,
+ key, index,
+ allPointsMap = {},
+ isolatedPointsMap = {};
+
+ // prepare all points map
+
+ for ( i = 0, il = allpoints.length; i < il; i ++ ) {
+
+ key = allpoints[ i ].x + ":" + allpoints[ i ].y;
+
+ if ( allPointsMap[ key ] !== undefined ) {
+
+ console.log( "Duplicate point", key );
+
+ }
+
+ allPointsMap[ key ] = i;
+
+ }
+
+ // check all face vertices against all points map
+
+ for ( i = 0, il = triangles.length; i < il; i ++ ) {
+
+ face = triangles[ i ];
+
+ for ( f = 0; f < 3; f ++ ) {
+
+ key = face[ f ].x + ":" + face[ f ].y;
+
+ index = allPointsMap[ key ];
+
+ if ( index !== undefined ) {
+
+ face[ f ] = index;
+
+ }
+
+ }
+
+ }
+
+ // check isolated points vertices against all points map
+
+ for ( i = 0, il = isolatedPts.length; i < il; i ++ ) {
+
+ face = isolatedPts[ i ];
+
+ for ( f = 0; f < 3; f ++ ) {
+
+ key = face[ f ].x + ":" + face[ f ].y;
+
+ index = allPointsMap[ key ];
+
+ if ( index !== undefined ) {
+
+ face[ f ] = index;
+
+ }
+
+ }
+
+ }
+
+ return triangles.concat( isolatedPts );
+
+ }, // end triangulate shapes
+
+ /*
+ triangulate2 : function( pts, holes ) {
+
+ // For use with Poly2Tri.js
+
+ var allpts = pts.concat();
+ var shape = [];
+ for (var p in pts) {
+ shape.push(new js.poly2tri.Point(pts[p].x, pts[p].y));
+ }
+
+ var swctx = new js.poly2tri.SweepContext(shape);
+
+ for (var h in holes) {
+ var aHole = holes[h];
+ var newHole = []
+ for (i in aHole) {
+ newHole.push(new js.poly2tri.Point(aHole[i].x, aHole[i].y));
+ allpts.push(aHole[i]);
+ }
+ swctx.AddHole(newHole);
+ }
+
+ var find;
+ var findIndexForPt = function (pt) {
+ find = new THREE.Vector2(pt.x, pt.y);
+ var p;
+ for (p=0, pl = allpts.length; p points.length - 2 ? points.length -1 : intPoint + 1;
+ c[ 3 ] = intPoint > points.length - 3 ? points.length -1 : intPoint + 2;
+
+ v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight );
+ v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight );
+
+ return v;
+
+};
+/**************************************************************
+ * Ellipse curve
+ **************************************************************/
+
+THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+ this.aX = aX;
+ this.aY = aY;
+
+ this.xRadius = xRadius;
+ this.yRadius = yRadius;
+
+ this.aStartAngle = aStartAngle;
+ this.aEndAngle = aEndAngle;
+
+ this.aClockwise = aClockwise;
+
+};
+
+THREE.EllipseCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.EllipseCurve.prototype.getPoint = function ( t ) {
+
+ var angle;
+ var deltaAngle = this.aEndAngle - this.aStartAngle;
+
+ if ( deltaAngle < 0 ) deltaAngle += Math.PI * 2;
+ if ( deltaAngle > Math.PI * 2 ) deltaAngle -= Math.PI * 2;
+
+ if ( this.aClockwise === true ) {
+
+ angle = this.aEndAngle + ( 1 - t ) * ( Math.PI * 2 - deltaAngle );
+
+ } else {
+
+ angle = this.aStartAngle + t * deltaAngle;
+
+ }
+
+ var tx = this.aX + this.xRadius * Math.cos( angle );
+ var ty = this.aY + this.yRadius * Math.sin( angle );
+
+ return new THREE.Vector2( tx, ty );
+
+};
+
+/**************************************************************
+ * Arc curve
+ **************************************************************/
+
+THREE.ArcCurve = function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+ THREE.EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+};
+
+THREE.ArcCurve.prototype = Object.create( THREE.EllipseCurve.prototype );
+/**************************************************************
+ * Line3D
+ **************************************************************/
+
+THREE.LineCurve3 = THREE.Curve.create(
+
+ function ( v1, v2 ) {
+
+ this.v1 = v1;
+ this.v2 = v2;
+
+ },
+
+ function ( t ) {
+
+ var r = new THREE.Vector3();
+
+
+ r.subVectors( this.v2, this.v1 ); // diff
+ r.multiplyScalar( t );
+ r.add( this.v1 );
+
+ return r;
+
+ }
+
+);
+
+/**************************************************************
+ * Quadratic Bezier 3D curve
+ **************************************************************/
+
+THREE.QuadraticBezierCurve3 = THREE.Curve.create(
+
+ function ( v0, v1, v2 ) {
+
+ this.v0 = v0;
+ this.v1 = v1;
+ this.v2 = v2;
+
+ },
+
+ function ( t ) {
+
+ var tx, ty, tz;
+
+ tx = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x );
+ ty = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y );
+ tz = THREE.Shape.Utils.b2( t, this.v0.z, this.v1.z, this.v2.z );
+
+ return new THREE.Vector3( tx, ty, tz );
+
+ }
+
+);
+/**************************************************************
+ * Cubic Bezier 3D curve
+ **************************************************************/
+
+THREE.CubicBezierCurve3 = THREE.Curve.create(
+
+ function ( v0, v1, v2, v3 ) {
+
+ this.v0 = v0;
+ this.v1 = v1;
+ this.v2 = v2;
+ this.v3 = v3;
+
+ },
+
+ function ( t ) {
+
+ var tx, ty, tz;
+
+ tx = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x );
+ ty = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y );
+ tz = THREE.Shape.Utils.b3( t, this.v0.z, this.v1.z, this.v2.z, this.v3.z );
+
+ return new THREE.Vector3( tx, ty, tz );
+
+ }
+
+);
+/**************************************************************
+ * Spline 3D curve
+ **************************************************************/
+
+
+THREE.SplineCurve3 = THREE.Curve.create(
+
+ function ( points /* array of Vector3 */) {
+
+ this.points = (points == undefined) ? [] : points;
+
+ },
+
+ function ( t ) {
+
+ var v = new THREE.Vector3();
+ var c = [];
+ var points = this.points, point, intPoint, weight;
+ point = ( points.length - 1 ) * t;
+
+ intPoint = Math.floor( point );
+ weight = point - intPoint;
+
+ c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1;
+ c[ 1 ] = intPoint;
+ c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1;
+ c[ 3 ] = intPoint > points.length - 3 ? points.length - 1 : intPoint + 2;
+
+ var pt0 = points[ c[0] ],
+ pt1 = points[ c[1] ],
+ pt2 = points[ c[2] ],
+ pt3 = points[ c[3] ];
+
+ v.x = THREE.Curve.Utils.interpolate(pt0.x, pt1.x, pt2.x, pt3.x, weight);
+ v.y = THREE.Curve.Utils.interpolate(pt0.y, pt1.y, pt2.y, pt3.y, weight);
+ v.z = THREE.Curve.Utils.interpolate(pt0.z, pt1.z, pt2.z, pt3.z, weight);
+
+ return v;
+
+ }
+
+);
+
+
+// THREE.SplineCurve3.prototype.getTangent = function(t) {
+// var v = new THREE.Vector3();
+// var c = [];
+// var points = this.points, point, intPoint, weight;
+// point = ( points.length - 1 ) * t;
+
+// intPoint = Math.floor( point );
+// weight = point - intPoint;
+
+// c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1;
+// c[ 1 ] = intPoint;
+// c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1;
+// c[ 3 ] = intPoint > points.length - 3 ? points.length - 1 : intPoint + 2;
+
+// var pt0 = points[ c[0] ],
+// pt1 = points[ c[1] ],
+// pt2 = points[ c[2] ],
+// pt3 = points[ c[3] ];
+
+// // t = weight;
+// v.x = THREE.Curve.Utils.tangentSpline( t, pt0.x, pt1.x, pt2.x, pt3.x );
+// v.y = THREE.Curve.Utils.tangentSpline( t, pt0.y, pt1.y, pt2.y, pt3.y );
+// v.z = THREE.Curve.Utils.tangentSpline( t, pt0.z, pt1.z, pt2.z, pt3.z );
+
+// return v;
+
+// }
+/**************************************************************
+ * Closed Spline 3D curve
+ **************************************************************/
+
+
+THREE.ClosedSplineCurve3 = THREE.Curve.create(
+
+ function ( points /* array of Vector3 */) {
+
+ this.points = (points == undefined) ? [] : points;
+
+ },
+
+ function ( t ) {
+
+ var v = new THREE.Vector3();
+ var c = [];
+ var points = this.points, point, intPoint, weight;
+ point = ( points.length - 0 ) * t;
+ // This needs to be from 0-length +1
+
+ intPoint = Math.floor( point );
+ weight = point - intPoint;
+
+ intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length;
+ c[ 0 ] = ( intPoint - 1 ) % points.length;
+ c[ 1 ] = ( intPoint ) % points.length;
+ c[ 2 ] = ( intPoint + 1 ) % points.length;
+ c[ 3 ] = ( intPoint + 2 ) % points.length;
+
+ v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight );
+ v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight );
+ v.z = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].z, points[ c[ 1 ] ].z, points[ c[ 2 ] ].z, points[ c[ 3 ] ].z, weight );
+
+ return v;
+
+ }
+
+);
+/**
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+THREE.AnimationHandler = (function() {
+
+ var playing = [];
+ var library = {};
+ var that = {};
+
+
+ //--- update ---
+
+ that.update = function( deltaTimeMS ) {
+
+ for( var i = 0; i < playing.length; i ++ )
+ playing[ i ].update( deltaTimeMS );
+
+ };
+
+
+ //--- add ---
+
+ that.addToUpdate = function( animation ) {
+
+ if ( playing.indexOf( animation ) === -1 )
+ playing.push( animation );
+
+ };
+
+
+ //--- remove ---
+
+ that.removeFromUpdate = function( animation ) {
+
+ var index = playing.indexOf( animation );
+
+ if( index !== -1 )
+ playing.splice( index, 1 );
+
+ };
+
+
+ //--- add ---
+
+ that.add = function( data ) {
+
+ if ( library[ data.name ] !== undefined )
+ console.log( "THREE.AnimationHandler.add: Warning! " + data.name + " already exists in library. Overwriting." );
+
+ library[ data.name ] = data;
+ initData( data );
+
+ };
+
+
+ //--- get ---
+
+ that.get = function( name ) {
+
+ if ( typeof name === "string" ) {
+
+ if ( library[ name ] ) {
+
+ return library[ name ];
+
+ } else {
+
+ console.log( "THREE.AnimationHandler.get: Couldn't find animation " + name );
+ return null;
+
+ }
+
+ } else {
+
+ // todo: add simple tween library
+
+ }
+
+ };
+
+ //--- parse ---
+
+ that.parse = function( root ) {
+
+ // setup hierarchy
+
+ var hierarchy = [];
+
+ if ( root instanceof THREE.SkinnedMesh ) {
+
+ for( var b = 0; b < root.bones.length; b++ ) {
+
+ hierarchy.push( root.bones[ b ] );
+
+ }
+
+ } else {
+
+ parseRecurseHierarchy( root, hierarchy );
+
+ }
+
+ return hierarchy;
+
+ };
+
+ var parseRecurseHierarchy = function( root, hierarchy ) {
+
+ hierarchy.push( root );
+
+ for( var c = 0; c < root.children.length; c++ )
+ parseRecurseHierarchy( root.children[ c ], hierarchy );
+
+ }
+
+
+ //--- init data ---
+
+ var initData = function( data ) {
+
+ if( data.initialized === true )
+ return;
+
+
+ // loop through all keys
+
+ for( var h = 0; h < data.hierarchy.length; h ++ ) {
+
+ for( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ // remove minus times
+
+ if( data.hierarchy[ h ].keys[ k ].time < 0 )
+ data.hierarchy[ h ].keys[ k ].time = 0;
+
+
+ // create quaternions
+
+ if( data.hierarchy[ h ].keys[ k ].rot !== undefined &&
+ !( data.hierarchy[ h ].keys[ k ].rot instanceof THREE.Quaternion ) ) {
+
+ var quat = data.hierarchy[ h ].keys[ k ].rot;
+ data.hierarchy[ h ].keys[ k ].rot = new THREE.Quaternion( quat[0], quat[1], quat[2], quat[3] );
+
+ }
+
+ }
+
+
+ // prepare morph target keys
+
+ if( data.hierarchy[ h ].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) {
+
+ // get all used
+
+ var usedMorphTargets = {};
+
+ for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) {
+
+ var morphTargetName = data.hierarchy[ h ].keys[ k ].morphTargets[ m ];
+ usedMorphTargets[ morphTargetName ] = -1;
+
+ }
+
+ }
+
+ data.hierarchy[ h ].usedMorphTargets = usedMorphTargets;
+
+
+ // set all used on all frames
+
+ for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ var influences = {};
+
+ for ( var morphTargetName in usedMorphTargets ) {
+
+ for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) {
+
+ if ( data.hierarchy[ h ].keys[ k ].morphTargets[ m ] === morphTargetName ) {
+
+ influences[ morphTargetName ] = data.hierarchy[ h ].keys[ k ].morphTargetsInfluences[ m ];
+ break;
+
+ }
+
+ }
+
+ if ( m === data.hierarchy[ h ].keys[ k ].morphTargets.length ) {
+
+ influences[ morphTargetName ] = 0;
+
+ }
+
+ }
+
+ data.hierarchy[ h ].keys[ k ].morphTargetsInfluences = influences;
+
+ }
+
+ }
+
+
+ // remove all keys that are on the same time
+
+ for ( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ if ( data.hierarchy[ h ].keys[ k ].time === data.hierarchy[ h ].keys[ k - 1 ].time ) {
+
+ data.hierarchy[ h ].keys.splice( k, 1 );
+ k --;
+
+ }
+
+ }
+
+
+ // set index
+
+ for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ data.hierarchy[ h ].keys[ k ].index = k;
+
+ }
+
+ }
+
+
+ // JIT
+
+ var lengthInFrames = parseInt( data.length * data.fps, 10 );
+
+ data.JIT = {};
+ data.JIT.hierarchy = [];
+
+ for( var h = 0; h < data.hierarchy.length; h ++ )
+ data.JIT.hierarchy.push( new Array( lengthInFrames ) );
+
+
+ // done
+
+ data.initialized = true;
+
+ };
+
+
+ // interpolation types
+
+ that.LINEAR = 0;
+ that.CATMULLROM = 1;
+ that.CATMULLROM_FORWARD = 2;
+
+ return that;
+
+}());
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Animation = function ( root, name, interpolationType ) {
+
+ this.root = root;
+ this.data = THREE.AnimationHandler.get( name );
+ this.hierarchy = THREE.AnimationHandler.parse( root );
+
+ this.currentTime = 0;
+ this.timeScale = 1;
+
+ this.isPlaying = false;
+ this.isPaused = true;
+ this.loop = true;
+
+ this.interpolationType = interpolationType !== undefined ? interpolationType : THREE.AnimationHandler.LINEAR;
+
+ this.points = [];
+ this.target = new THREE.Vector3();
+
+};
+
+THREE.Animation.prototype.play = function ( loop, startTimeMS ) {
+
+ if ( this.isPlaying === false ) {
+
+ this.isPlaying = true;
+ this.loop = loop !== undefined ? loop : true;
+ this.currentTime = startTimeMS !== undefined ? startTimeMS : 0;
+
+ // reset key cache
+
+ var h, hl = this.hierarchy.length,
+ object;
+
+ for ( h = 0; h < hl; h ++ ) {
+
+ object = this.hierarchy[ h ];
+
+ object.matrixAutoUpdate = true;
+
+ if ( object.animationCache === undefined ) {
+
+ object.animationCache = {};
+ object.animationCache.prevKey = { pos: 0, rot: 0, scl: 0 };
+ object.animationCache.nextKey = { pos: 0, rot: 0, scl: 0 };
+ object.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix;
+
+ }
+
+ var prevKey = object.animationCache.prevKey;
+ var nextKey = object.animationCache.nextKey;
+
+ prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ];
+ prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ];
+ prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ];
+
+ nextKey.pos = this.getNextKeyWith( "pos", h, 1 );
+ nextKey.rot = this.getNextKeyWith( "rot", h, 1 );
+ nextKey.scl = this.getNextKeyWith( "scl", h, 1 );
+
+ }
+
+ this.update( 0 );
+
+ }
+
+ this.isPaused = false;
+
+ THREE.AnimationHandler.addToUpdate( this );
+
+};
+
+
+THREE.Animation.prototype.pause = function() {
+
+ if ( this.isPaused === true ) {
+
+ THREE.AnimationHandler.addToUpdate( this );
+
+ } else {
+
+ THREE.AnimationHandler.removeFromUpdate( this );
+
+ }
+
+ this.isPaused = !this.isPaused;
+
+};
+
+
+THREE.Animation.prototype.stop = function() {
+
+ this.isPlaying = false;
+ this.isPaused = false;
+ THREE.AnimationHandler.removeFromUpdate( this );
+
+};
+
+
+THREE.Animation.prototype.update = function ( deltaTimeMS ) {
+
+ // early out
+
+ if ( this.isPlaying === false ) return;
+
+
+ // vars
+
+ var types = [ "pos", "rot", "scl" ];
+ var type;
+ var scale;
+ var vector;
+ var prevXYZ, nextXYZ;
+ var prevKey, nextKey;
+ var object;
+ var animationCache;
+ var frame;
+ var JIThierarchy = this.data.JIT.hierarchy;
+ var currentTime, unloopedCurrentTime;
+ var currentPoint, forwardPoint, angle;
+
+
+ this.currentTime += deltaTimeMS * this.timeScale;
+
+ unloopedCurrentTime = this.currentTime;
+ currentTime = this.currentTime = this.currentTime % this.data.length;
+ frame = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 );
+
+
+ for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) {
+
+ object = this.hierarchy[ h ];
+ animationCache = object.animationCache;
+
+ // loop through pos/rot/scl
+
+ for ( var t = 0; t < 3; t ++ ) {
+
+ // get keys
+
+ type = types[ t ];
+ prevKey = animationCache.prevKey[ type ];
+ nextKey = animationCache.nextKey[ type ];
+
+ // switch keys?
+
+ if ( nextKey.time <= unloopedCurrentTime ) {
+
+ // did we loop?
+
+ if ( currentTime < unloopedCurrentTime ) {
+
+ if ( this.loop ) {
+
+ prevKey = this.data.hierarchy[ h ].keys[ 0 ];
+ nextKey = this.getNextKeyWith( type, h, 1 );
+
+ while( nextKey.time < currentTime ) {
+
+ prevKey = nextKey;
+ nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );
+
+ }
+
+ } else {
+
+ this.stop();
+ return;
+
+ }
+
+ } else {
+
+ do {
+
+ prevKey = nextKey;
+ nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );
+
+ } while( nextKey.time < currentTime )
+
+ }
+
+ animationCache.prevKey[ type ] = prevKey;
+ animationCache.nextKey[ type ] = nextKey;
+
+ }
+
+
+ object.matrixAutoUpdate = true;
+ object.matrixWorldNeedsUpdate = true;
+
+ scale = ( currentTime - prevKey.time ) / ( nextKey.time - prevKey.time );
+ prevXYZ = prevKey[ type ];
+ nextXYZ = nextKey[ type ];
+
+
+ // check scale error
+
+ if ( scale < 0 || scale > 1 ) {
+
+ console.log( "THREE.Animation.update: Warning! Scale out of bounds:" + scale + " on bone " + h );
+ scale = scale < 0 ? 0 : 1;
+
+ }
+
+ // interpolate
+
+ if ( type === "pos" ) {
+
+ vector = object.position;
+
+ if ( this.interpolationType === THREE.AnimationHandler.LINEAR ) {
+
+ vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
+ vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
+ vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;
+
+ } else if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+ this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+ this.points[ 0 ] = this.getPrevKeyWith( "pos", h, prevKey.index - 1 )[ "pos" ];
+ this.points[ 1 ] = prevXYZ;
+ this.points[ 2 ] = nextXYZ;
+ this.points[ 3 ] = this.getNextKeyWith( "pos", h, nextKey.index + 1 )[ "pos" ];
+
+ scale = scale * 0.33 + 0.33;
+
+ currentPoint = this.interpolateCatmullRom( this.points, scale );
+
+ vector.x = currentPoint[ 0 ];
+ vector.y = currentPoint[ 1 ];
+ vector.z = currentPoint[ 2 ];
+
+ if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+ forwardPoint = this.interpolateCatmullRom( this.points, scale * 1.01 );
+
+ this.target.set( forwardPoint[ 0 ], forwardPoint[ 1 ], forwardPoint[ 2 ] );
+ this.target.sub( vector );
+ this.target.y = 0;
+ this.target.normalize();
+
+ angle = Math.atan2( this.target.x, this.target.z );
+ object.rotation.set( 0, angle, 0 );
+
+ }
+
+ }
+
+ } else if ( type === "rot" ) {
+
+ THREE.Quaternion.slerp( prevXYZ, nextXYZ, object.quaternion, scale );
+
+ } else if ( type === "scl" ) {
+
+ vector = object.scale;
+
+ vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
+ vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
+ vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;
+
+ }
+
+ }
+
+ }
+
+};
+
+// Catmull-Rom spline
+
+THREE.Animation.prototype.interpolateCatmullRom = function ( points, scale ) {
+
+ var c = [], v3 = [],
+ point, intPoint, weight, w2, w3,
+ pa, pb, pc, pd;
+
+ point = ( points.length - 1 ) * scale;
+ intPoint = Math.floor( point );
+ weight = point - intPoint;
+
+ c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1;
+ c[ 1 ] = intPoint;
+ c[ 2 ] = intPoint > points.length - 2 ? intPoint : intPoint + 1;
+ c[ 3 ] = intPoint > points.length - 3 ? intPoint : intPoint + 2;
+
+ pa = points[ c[ 0 ] ];
+ pb = points[ c[ 1 ] ];
+ pc = points[ c[ 2 ] ];
+ pd = points[ c[ 3 ] ];
+
+ w2 = weight * weight;
+ w3 = weight * w2;
+
+ v3[ 0 ] = this.interpolate( pa[ 0 ], pb[ 0 ], pc[ 0 ], pd[ 0 ], weight, w2, w3 );
+ v3[ 1 ] = this.interpolate( pa[ 1 ], pb[ 1 ], pc[ 1 ], pd[ 1 ], weight, w2, w3 );
+ v3[ 2 ] = this.interpolate( pa[ 2 ], pb[ 2 ], pc[ 2 ], pd[ 2 ], weight, w2, w3 );
+
+ return v3;
+
+};
+
+THREE.Animation.prototype.interpolate = function ( p0, p1, p2, p3, t, t2, t3 ) {
+
+ var v0 = ( p2 - p0 ) * 0.5,
+ v1 = ( p3 - p1 ) * 0.5;
+
+ return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+};
+
+
+
+// Get next key with
+
+THREE.Animation.prototype.getNextKeyWith = function ( type, h, key ) {
+
+ var keys = this.data.hierarchy[ h ].keys;
+
+ if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+ this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+ key = key < keys.length - 1 ? key : keys.length - 1;
+
+ } else {
+
+ key = key % keys.length;
+
+ }
+
+ for ( ; key < keys.length; key++ ) {
+
+ if ( keys[ key ][ type ] !== undefined ) {
+
+ return keys[ key ];
+
+ }
+
+ }
+
+ return this.data.hierarchy[ h ].keys[ 0 ];
+
+};
+
+// Get previous key with
+
+THREE.Animation.prototype.getPrevKeyWith = function ( type, h, key ) {
+
+ var keys = this.data.hierarchy[ h ].keys;
+
+ if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+ this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+ key = key > 0 ? key : 0;
+
+ } else {
+
+ key = key >= 0 ? key : key + keys.length;
+
+ }
+
+
+ for ( ; key >= 0; key -- ) {
+
+ if ( keys[ key ][ type ] !== undefined ) {
+
+ return keys[ key ];
+
+ }
+
+ }
+
+ return this.data.hierarchy[ h ].keys[ keys.length - 1 ];
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author khang duong
+ * @author erik kitson
+ */
+
+THREE.KeyFrameAnimation = function( root, data, JITCompile ) {
+
+ this.root = root;
+ this.data = THREE.AnimationHandler.get( data );
+ this.hierarchy = THREE.AnimationHandler.parse( root );
+ this.currentTime = 0;
+ this.timeScale = 0.001;
+ this.isPlaying = false;
+ this.isPaused = true;
+ this.loop = true;
+ this.JITCompile = JITCompile !== undefined ? JITCompile : true;
+
+ // initialize to first keyframes
+
+ for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+ var keys = this.data.hierarchy[h].keys,
+ sids = this.data.hierarchy[h].sids,
+ obj = this.hierarchy[h];
+
+ if ( keys.length && sids ) {
+
+ for ( var s = 0; s < sids.length; s++ ) {
+
+ var sid = sids[ s ],
+ next = this.getNextKeyWith( sid, h, 0 );
+
+ if ( next ) {
+
+ next.apply( sid );
+
+ }
+
+ }
+
+ obj.matrixAutoUpdate = false;
+ this.data.hierarchy[h].node.updateMatrix();
+ obj.matrixWorldNeedsUpdate = true;
+
+ }
+
+ }
+
+};
+
+// Play
+
+THREE.KeyFrameAnimation.prototype.play = function( loop, startTimeMS ) {
+
+ if( !this.isPlaying ) {
+
+ this.isPlaying = true;
+ this.loop = loop !== undefined ? loop : true;
+ this.currentTime = startTimeMS !== undefined ? startTimeMS : 0;
+ this.startTimeMs = startTimeMS;
+ this.startTime = 10000000;
+ this.endTime = -this.startTime;
+
+
+ // reset key cache
+
+ var h, hl = this.hierarchy.length,
+ object,
+ node;
+
+ for ( h = 0; h < hl; h++ ) {
+
+ object = this.hierarchy[ h ];
+ node = this.data.hierarchy[ h ];
+
+ if ( node.animationCache === undefined ) {
+
+ node.animationCache = {};
+ node.animationCache.prevKey = null;
+ node.animationCache.nextKey = null;
+ node.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix;
+
+ }
+
+ var keys = this.data.hierarchy[h].keys;
+
+ if (keys.length) {
+
+ node.animationCache.prevKey = keys[ 0 ];
+ node.animationCache.nextKey = keys[ 1 ];
+
+ this.startTime = Math.min( keys[0].time, this.startTime );
+ this.endTime = Math.max( keys[keys.length - 1].time, this.endTime );
+
+ }
+
+ }
+
+ this.update( 0 );
+
+ }
+
+ this.isPaused = false;
+
+ THREE.AnimationHandler.addToUpdate( this );
+
+};
+
+
+
+// Pause
+
+THREE.KeyFrameAnimation.prototype.pause = function() {
+
+ if( this.isPaused ) {
+
+ THREE.AnimationHandler.addToUpdate( this );
+
+ } else {
+
+ THREE.AnimationHandler.removeFromUpdate( this );
+
+ }
+
+ this.isPaused = !this.isPaused;
+
+};
+
+
+// Stop
+
+THREE.KeyFrameAnimation.prototype.stop = function() {
+
+ this.isPlaying = false;
+ this.isPaused = false;
+ THREE.AnimationHandler.removeFromUpdate( this );
+
+
+ // reset JIT matrix and remove cache
+
+ for ( var h = 0; h < this.data.hierarchy.length; h++ ) {
+
+ var obj = this.hierarchy[ h ];
+ var node = this.data.hierarchy[ h ];
+
+ if ( node.animationCache !== undefined ) {
+
+ var original = node.animationCache.originalMatrix;
+
+ if( obj instanceof THREE.Bone ) {
+
+ original.copy( obj.skinMatrix );
+ obj.skinMatrix = original;
+
+ } else {
+
+ original.copy( obj.matrix );
+ obj.matrix = original;
+
+ }
+
+ delete node.animationCache;
+
+ }
+
+ }
+
+};
+
+
+// Update
+
+THREE.KeyFrameAnimation.prototype.update = function( deltaTimeMS ) {
+
+ // early out
+
+ if( !this.isPlaying ) return;
+
+
+ // vars
+
+ var prevKey, nextKey;
+ var object;
+ var node;
+ var frame;
+ var JIThierarchy = this.data.JIT.hierarchy;
+ var currentTime, unloopedCurrentTime;
+ var looped;
+
+
+ // update
+
+ this.currentTime += deltaTimeMS * this.timeScale;
+
+ unloopedCurrentTime = this.currentTime;
+ currentTime = this.currentTime = this.currentTime % this.data.length;
+
+ // if looped around, the current time should be based on the startTime
+ if ( currentTime < this.startTimeMs ) {
+
+ currentTime = this.currentTime = this.startTimeMs + currentTime;
+
+ }
+
+ frame = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 );
+ looped = currentTime < unloopedCurrentTime;
+
+ if ( looped && !this.loop ) {
+
+ // Set the animation to the last keyframes and stop
+ for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+ var keys = this.data.hierarchy[h].keys,
+ sids = this.data.hierarchy[h].sids,
+ end = keys.length-1,
+ obj = this.hierarchy[h];
+
+ if ( keys.length ) {
+
+ for ( var s = 0; s < sids.length; s++ ) {
+
+ var sid = sids[ s ],
+ prev = this.getPrevKeyWith( sid, h, end );
+
+ if ( prev ) {
+ prev.apply( sid );
+
+ }
+
+ }
+
+ this.data.hierarchy[h].node.updateMatrix();
+ obj.matrixWorldNeedsUpdate = true;
+
+ }
+
+ }
+
+ this.stop();
+ return;
+
+ }
+
+ // check pre-infinity
+ if ( currentTime < this.startTime ) {
+
+ return;
+
+ }
+
+ // update
+
+ for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+ object = this.hierarchy[ h ];
+ node = this.data.hierarchy[ h ];
+
+ var keys = node.keys,
+ animationCache = node.animationCache;
+
+ // use JIT?
+
+ if ( this.JITCompile && JIThierarchy[ h ][ frame ] !== undefined ) {
+
+ if( object instanceof THREE.Bone ) {
+
+ object.skinMatrix = JIThierarchy[ h ][ frame ];
+ object.matrixWorldNeedsUpdate = false;
+
+ } else {
+
+ object.matrix = JIThierarchy[ h ][ frame ];
+ object.matrixWorldNeedsUpdate = true;
+
+ }
+
+ // use interpolation
+
+ } else if ( keys.length ) {
+
+ // make sure so original matrix and not JIT matrix is set
+
+ if ( this.JITCompile && animationCache ) {
+
+ if( object instanceof THREE.Bone ) {
+
+ object.skinMatrix = animationCache.originalMatrix;
+
+ } else {
+
+ object.matrix = animationCache.originalMatrix;
+
+ }
+
+ }
+
+ prevKey = animationCache.prevKey;
+ nextKey = animationCache.nextKey;
+
+ if ( prevKey && nextKey ) {
+
+ // switch keys?
+
+ if ( nextKey.time <= unloopedCurrentTime ) {
+
+ // did we loop?
+
+ if ( looped && this.loop ) {
+
+ prevKey = keys[ 0 ];
+ nextKey = keys[ 1 ];
+
+ while ( nextKey.time < currentTime ) {
+
+ prevKey = nextKey;
+ nextKey = keys[ prevKey.index + 1 ];
+
+ }
+
+ } else if ( !looped ) {
+
+ var lastIndex = keys.length - 1;
+
+ while ( nextKey.time < currentTime && nextKey.index !== lastIndex ) {
+
+ prevKey = nextKey;
+ nextKey = keys[ prevKey.index + 1 ];
+
+ }
+
+ }
+
+ animationCache.prevKey = prevKey;
+ animationCache.nextKey = nextKey;
+
+ }
+ if(nextKey.time >= currentTime)
+ prevKey.interpolate( nextKey, currentTime );
+ else
+ prevKey.interpolate( nextKey, nextKey.time);
+
+ }
+
+ this.data.hierarchy[h].node.updateMatrix();
+ object.matrixWorldNeedsUpdate = true;
+
+ }
+
+ }
+
+ // update JIT?
+
+ if ( this.JITCompile ) {
+
+ if ( JIThierarchy[ 0 ][ frame ] === undefined ) {
+
+ this.hierarchy[ 0 ].updateMatrixWorld( true );
+
+ for ( var h = 0; h < this.hierarchy.length; h++ ) {
+
+ if( this.hierarchy[ h ] instanceof THREE.Bone ) {
+
+ JIThierarchy[ h ][ frame ] = this.hierarchy[ h ].skinMatrix.clone();
+
+ } else {
+
+ JIThierarchy[ h ][ frame ] = this.hierarchy[ h ].matrix.clone();
+
+ }
+
+ }
+
+ }
+
+ }
+
+};
+
+// Get next key with
+
+THREE.KeyFrameAnimation.prototype.getNextKeyWith = function( sid, h, key ) {
+
+ var keys = this.data.hierarchy[ h ].keys;
+ key = key % keys.length;
+
+ for ( ; key < keys.length; key++ ) {
+
+ if ( keys[ key ].hasTarget( sid ) ) {
+
+ return keys[ key ];
+
+ }
+
+ }
+
+ return keys[ 0 ];
+
+};
+
+// Get previous key with
+
+THREE.KeyFrameAnimation.prototype.getPrevKeyWith = function( sid, h, key ) {
+
+ var keys = this.data.hierarchy[ h ].keys;
+ key = key >= 0 ? key : key + keys.length;
+
+ for ( ; key >= 0; key-- ) {
+
+ if ( keys[ key ].hasTarget( sid ) ) {
+
+ return keys[ key ];
+
+ }
+
+ }
+
+ return keys[ keys.length - 1 ];
+
+};
+
+/**
+ * Camera for rendering cube maps
+ * - renders scene into axis-aligned cube
+ *
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.CubeCamera = function ( near, far, cubeResolution ) {
+
+ THREE.Object3D.call( this );
+
+ var fov = 90, aspect = 1;
+
+ var cameraPX = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraPX.up.set( 0, -1, 0 );
+ cameraPX.lookAt( new THREE.Vector3( 1, 0, 0 ) );
+ this.add( cameraPX );
+
+ var cameraNX = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraNX.up.set( 0, -1, 0 );
+ cameraNX.lookAt( new THREE.Vector3( -1, 0, 0 ) );
+ this.add( cameraNX );
+
+ var cameraPY = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraPY.up.set( 0, 0, 1 );
+ cameraPY.lookAt( new THREE.Vector3( 0, 1, 0 ) );
+ this.add( cameraPY );
+
+ var cameraNY = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraNY.up.set( 0, 0, -1 );
+ cameraNY.lookAt( new THREE.Vector3( 0, -1, 0 ) );
+ this.add( cameraNY );
+
+ var cameraPZ = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraPZ.up.set( 0, -1, 0 );
+ cameraPZ.lookAt( new THREE.Vector3( 0, 0, 1 ) );
+ this.add( cameraPZ );
+
+ var cameraNZ = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraNZ.up.set( 0, -1, 0 );
+ cameraNZ.lookAt( new THREE.Vector3( 0, 0, -1 ) );
+ this.add( cameraNZ );
+
+ this.renderTarget = new THREE.WebGLRenderTargetCube( cubeResolution, cubeResolution, { format: THREE.RGBFormat, magFilter: THREE.LinearFilter, minFilter: THREE.LinearFilter } );
+
+ this.updateCubeMap = function ( renderer, scene ) {
+
+ var renderTarget = this.renderTarget;
+ var generateMipmaps = renderTarget.generateMipmaps;
+
+ renderTarget.generateMipmaps = false;
+
+ renderTarget.activeCubeFace = 0;
+ renderer.render( scene, cameraPX, renderTarget );
+
+ renderTarget.activeCubeFace = 1;
+ renderer.render( scene, cameraNX, renderTarget );
+
+ renderTarget.activeCubeFace = 2;
+ renderer.render( scene, cameraPY, renderTarget );
+
+ renderTarget.activeCubeFace = 3;
+ renderer.render( scene, cameraNY, renderTarget );
+
+ renderTarget.activeCubeFace = 4;
+ renderer.render( scene, cameraPZ, renderTarget );
+
+ renderTarget.generateMipmaps = generateMipmaps;
+
+ renderTarget.activeCubeFace = 5;
+ renderer.render( scene, cameraNZ, renderTarget );
+
+ };
+
+};
+
+THREE.CubeCamera.prototype = Object.create( THREE.Object3D.prototype );
+
+/*
+ * @author zz85 / http://twitter.com/blurspline / http://www.lab4games.net/zz85/blog
+ *
+ * A general perpose camera, for setting FOV, Lens Focal Length,
+ * and switching between perspective and orthographic views easily.
+ * Use this only if you do not wish to manage
+ * both a Orthographic and Perspective Camera
+ *
+ */
+
+
+THREE.CombinedCamera = function ( width, height, fov, near, far, orthoNear, orthoFar ) {
+
+ THREE.Camera.call( this );
+
+ this.fov = fov;
+
+ this.left = -width / 2;
+ this.right = width / 2
+ this.top = height / 2;
+ this.bottom = -height / 2;
+
+ // We could also handle the projectionMatrix internally, but just wanted to test nested camera objects
+
+ this.cameraO = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, orthoNear, orthoFar );
+ this.cameraP = new THREE.PerspectiveCamera( fov, width / height, near, far );
+
+ this.zoom = 1;
+
+ this.toPerspective();
+
+ var aspect = width/height;
+
+};
+
+THREE.CombinedCamera.prototype = Object.create( THREE.Camera.prototype );
+
+THREE.CombinedCamera.prototype.toPerspective = function () {
+
+ // Switches to the Perspective Camera
+
+ this.near = this.cameraP.near;
+ this.far = this.cameraP.far;
+
+ this.cameraP.fov = this.fov / this.zoom ;
+
+ this.cameraP.updateProjectionMatrix();
+
+ this.projectionMatrix = this.cameraP.projectionMatrix;
+
+ this.inPerspectiveMode = true;
+ this.inOrthographicMode = false;
+
+};
+
+THREE.CombinedCamera.prototype.toOrthographic = function () {
+
+ // Switches to the Orthographic camera estimating viewport from Perspective
+
+ var fov = this.fov;
+ var aspect = this.cameraP.aspect;
+ var near = this.cameraP.near;
+ var far = this.cameraP.far;
+
+ // The size that we set is the mid plane of the viewing frustum
+
+ var hyperfocus = ( near + far ) / 2;
+
+ var halfHeight = Math.tan( fov / 2 ) * hyperfocus;
+ var planeHeight = 2 * halfHeight;
+ var planeWidth = planeHeight * aspect;
+ var halfWidth = planeWidth / 2;
+
+ halfHeight /= this.zoom;
+ halfWidth /= this.zoom;
+
+ this.cameraO.left = -halfWidth;
+ this.cameraO.right = halfWidth;
+ this.cameraO.top = halfHeight;
+ this.cameraO.bottom = -halfHeight;
+
+ // this.cameraO.left = -farHalfWidth;
+ // this.cameraO.right = farHalfWidth;
+ // this.cameraO.top = farHalfHeight;
+ // this.cameraO.bottom = -farHalfHeight;
+
+ // this.cameraO.left = this.left / this.zoom;
+ // this.cameraO.right = this.right / this.zoom;
+ // this.cameraO.top = this.top / this.zoom;
+ // this.cameraO.bottom = this.bottom / this.zoom;
+
+ this.cameraO.updateProjectionMatrix();
+
+ this.near = this.cameraO.near;
+ this.far = this.cameraO.far;
+ this.projectionMatrix = this.cameraO.projectionMatrix;
+
+ this.inPerspectiveMode = false;
+ this.inOrthographicMode = true;
+
+};
+
+
+THREE.CombinedCamera.prototype.setSize = function( width, height ) {
+
+ this.cameraP.aspect = width / height;
+ this.left = -width / 2;
+ this.right = width / 2
+ this.top = height / 2;
+ this.bottom = -height / 2;
+
+};
+
+
+THREE.CombinedCamera.prototype.setFov = function( fov ) {
+
+ this.fov = fov;
+
+ if ( this.inPerspectiveMode ) {
+
+ this.toPerspective();
+
+ } else {
+
+ this.toOrthographic();
+
+ }
+
+};
+
+// For mantaining similar API with PerspectiveCamera
+
+THREE.CombinedCamera.prototype.updateProjectionMatrix = function() {
+
+ if ( this.inPerspectiveMode ) {
+
+ this.toPerspective();
+
+ } else {
+
+ this.toPerspective();
+ this.toOrthographic();
+
+ }
+
+};
+
+/*
+* Uses Focal Length (in mm) to estimate and set FOV
+* 35mm (fullframe) camera is used if frame size is not specified;
+* Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html
+*/
+THREE.CombinedCamera.prototype.setLens = function ( focalLength, frameHeight ) {
+
+ if ( frameHeight === undefined ) frameHeight = 24;
+
+ var fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) );
+
+ this.setFov( fov );
+
+ return fov;
+};
+
+
+THREE.CombinedCamera.prototype.setZoom = function( zoom ) {
+
+ this.zoom = zoom;
+
+ if ( this.inPerspectiveMode ) {
+
+ this.toPerspective();
+
+ } else {
+
+ this.toOrthographic();
+
+ }
+
+};
+
+THREE.CombinedCamera.prototype.toFrontView = function() {
+
+ this.rotation.x = 0;
+ this.rotation.y = 0;
+ this.rotation.z = 0;
+
+ // should we be modifing the matrix instead?
+
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toBackView = function() {
+
+ this.rotation.x = 0;
+ this.rotation.y = Math.PI;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toLeftView = function() {
+
+ this.rotation.x = 0;
+ this.rotation.y = - Math.PI / 2;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toRightView = function() {
+
+ this.rotation.x = 0;
+ this.rotation.y = Math.PI / 2;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toTopView = function() {
+
+ this.rotation.x = - Math.PI / 2;
+ this.rotation.y = 0;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toBottomView = function() {
+
+ this.rotation.x = Math.PI / 2;
+ this.rotation.y = 0;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+
+/**
+ * @author hughes
+ */
+
+THREE.CircleGeometry = function ( radius, segments, thetaStart, thetaLength ) {
+
+ THREE.Geometry.call( this );
+
+ this.radius = radius = radius || 50;
+ this.segments = segments = segments !== undefined ? Math.max( 3, segments ) : 8;
+
+ this.thetaStart = thetaStart = thetaStart !== undefined ? thetaStart : 0;
+ this.thetaLength = thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;
+
+ var i, uvs = [],
+ center = new THREE.Vector3(), centerUV = new THREE.Vector2( 0.5, 0.5 );
+
+ this.vertices.push(center);
+ uvs.push( centerUV );
+
+ for ( i = 0; i <= segments; i ++ ) {
+
+ var vertex = new THREE.Vector3();
+ var segment = thetaStart + i / segments * thetaLength;
+
+ vertex.x = radius * Math.cos( segment );
+ vertex.y = radius * Math.sin( segment );
+
+ this.vertices.push( vertex );
+ uvs.push( new THREE.Vector2( ( vertex.x / radius + 1 ) / 2, ( vertex.y / radius + 1 ) / 2 ) );
+
+ }
+
+ var n = new THREE.Vector3( 0, 0, 1 );
+
+ for ( i = 1; i <= segments; i ++ ) {
+
+ var v1 = i;
+ var v2 = i + 1 ;
+ var v3 = 0;
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n, n, n ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uvs[ i ], uvs[ i + 1 ], centerUV ] );
+
+ }
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+
+ this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Cube.as
+ */
+
+THREE.CubeGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) {
+
+ THREE.Geometry.call( this );
+
+ var scope = this;
+
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+
+ this.widthSegments = widthSegments || 1;
+ this.heightSegments = heightSegments || 1;
+ this.depthSegments = depthSegments || 1;
+
+ var width_half = this.width / 2;
+ var height_half = this.height / 2;
+ var depth_half = this.depth / 2;
+
+ buildPlane( 'z', 'y', - 1, - 1, this.depth, this.height, width_half, 0 ); // px
+ buildPlane( 'z', 'y', 1, - 1, this.depth, this.height, - width_half, 1 ); // nx
+ buildPlane( 'x', 'z', 1, 1, this.width, this.depth, height_half, 2 ); // py
+ buildPlane( 'x', 'z', 1, - 1, this.width, this.depth, - height_half, 3 ); // ny
+ buildPlane( 'x', 'y', 1, - 1, this.width, this.height, depth_half, 4 ); // pz
+ buildPlane( 'x', 'y', - 1, - 1, this.width, this.height, - depth_half, 5 ); // nz
+
+ function buildPlane( u, v, udir, vdir, width, height, depth, materialIndex ) {
+
+ var w, ix, iy,
+ gridX = scope.widthSegments,
+ gridY = scope.heightSegments,
+ width_half = width / 2,
+ height_half = height / 2,
+ offset = scope.vertices.length;
+
+ if ( ( u === 'x' && v === 'y' ) || ( u === 'y' && v === 'x' ) ) {
+
+ w = 'z';
+
+ } else if ( ( u === 'x' && v === 'z' ) || ( u === 'z' && v === 'x' ) ) {
+
+ w = 'y';
+ gridY = scope.depthSegments;
+
+ } else if ( ( u === 'z' && v === 'y' ) || ( u === 'y' && v === 'z' ) ) {
+
+ w = 'x';
+ gridX = scope.depthSegments;
+
+ }
+
+ var gridX1 = gridX + 1,
+ gridY1 = gridY + 1,
+ segment_width = width / gridX,
+ segment_height = height / gridY,
+ normal = new THREE.Vector3();
+
+ normal[ w ] = depth > 0 ? 1 : - 1;
+
+ for ( iy = 0; iy < gridY1; iy ++ ) {
+
+ for ( ix = 0; ix < gridX1; ix ++ ) {
+
+ var vector = new THREE.Vector3();
+ vector[ u ] = ( ix * segment_width - width_half ) * udir;
+ vector[ v ] = ( iy * segment_height - height_half ) * vdir;
+ vector[ w ] = depth;
+
+ scope.vertices.push( vector );
+
+ }
+
+ }
+
+ for ( iy = 0; iy < gridY; iy++ ) {
+
+ for ( ix = 0; ix < gridX; ix++ ) {
+
+ var a = ix + gridX1 * iy;
+ var b = ix + gridX1 * ( iy + 1 );
+ var c = ( ix + 1 ) + gridX1 * ( iy + 1 );
+ var d = ( ix + 1 ) + gridX1 * iy;
+
+ var uva = new THREE.Vector2( ix / gridX, 1 - iy / gridY );
+ var uvb = new THREE.Vector2( ix / gridX, 1 - ( iy + 1 ) / gridY );
+ var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iy + 1 ) / gridY );
+ var uvd = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iy / gridY );
+
+ var face = new THREE.Face3( a + offset, b + offset, d + offset );
+ face.normal.copy( normal );
+ face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
+ face.materialIndex = materialIndex;
+
+ scope.faces.push( face );
+ scope.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] );
+
+ face = new THREE.Face3( b + offset, c + offset, d + offset );
+ face.normal.copy( normal );
+ face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
+ face.materialIndex = materialIndex;
+
+ scope.faces.push( face );
+ scope.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] );
+
+ }
+
+ }
+
+ }
+
+ this.computeCentroids();
+ this.mergeVertices();
+
+};
+
+THREE.CubeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.CylinderGeometry = function ( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded ) {
+
+ THREE.Geometry.call( this );
+
+ this.radiusTop = radiusTop = radiusTop !== undefined ? radiusTop : 20;
+ this.radiusBottom = radiusBottom = radiusBottom !== undefined ? radiusBottom : 20;
+ this.height = height = height !== undefined ? height : 100;
+
+ this.radialSegments = radialSegments = radialSegments || 8;
+ this.heightSegments = heightSegments = heightSegments || 1;
+
+ this.openEnded = openEnded = openEnded !== undefined ? openEnded : false;
+
+ var heightHalf = height / 2;
+
+ var x, y, vertices = [], uvs = [];
+
+ for ( y = 0; y <= heightSegments; y ++ ) {
+
+ var verticesRow = [];
+ var uvsRow = [];
+
+ var v = y / heightSegments;
+ var radius = v * ( radiusBottom - radiusTop ) + radiusTop;
+
+ for ( x = 0; x <= radialSegments; x ++ ) {
+
+ var u = x / radialSegments;
+
+ var vertex = new THREE.Vector3();
+ vertex.x = radius * Math.sin( u * Math.PI * 2 );
+ vertex.y = - v * height + heightHalf;
+ vertex.z = radius * Math.cos( u * Math.PI * 2 );
+
+ this.vertices.push( vertex );
+
+ verticesRow.push( this.vertices.length - 1 );
+ uvsRow.push( new THREE.Vector2( u, 1 - v ) );
+
+ }
+
+ vertices.push( verticesRow );
+ uvs.push( uvsRow );
+
+ }
+
+ var tanTheta = ( radiusBottom - radiusTop ) / height;
+ var na, nb;
+
+ for ( x = 0; x < radialSegments; x ++ ) {
+
+ if ( radiusTop !== 0 ) {
+
+ na = this.vertices[ vertices[ 0 ][ x ] ].clone();
+ nb = this.vertices[ vertices[ 0 ][ x + 1 ] ].clone();
+
+ } else {
+
+ na = this.vertices[ vertices[ 1 ][ x ] ].clone();
+ nb = this.vertices[ vertices[ 1 ][ x + 1 ] ].clone();
+
+ }
+
+ na.setY( Math.sqrt( na.x * na.x + na.z * na.z ) * tanTheta ).normalize();
+ nb.setY( Math.sqrt( nb.x * nb.x + nb.z * nb.z ) * tanTheta ).normalize();
+
+ for ( y = 0; y < heightSegments; y ++ ) {
+
+ var v1 = vertices[ y ][ x ];
+ var v2 = vertices[ y + 1 ][ x ];
+ var v3 = vertices[ y + 1 ][ x + 1 ];
+ var v4 = vertices[ y ][ x + 1 ];
+
+ var n1 = na.clone();
+ var n2 = na.clone();
+ var n3 = nb.clone();
+ var n4 = nb.clone();
+
+ var uv1 = uvs[ y ][ x ].clone();
+ var uv2 = uvs[ y + 1 ][ x ].clone();
+ var uv3 = uvs[ y + 1 ][ x + 1 ].clone();
+ var uv4 = uvs[ y ][ x + 1 ].clone();
+
+ this.faces.push( new THREE.Face3( v1, v2, v4, [ n1, n2, n4 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv4 ] );
+
+ this.faces.push( new THREE.Face3( v2, v3, v4, [ n2, n3, n4 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv2, uv3, uv4 ] );
+
+ }
+
+ }
+
+ // top cap
+
+ if ( openEnded === false && radiusTop > 0 ) {
+
+ this.vertices.push( new THREE.Vector3( 0, heightHalf, 0 ) );
+
+ for ( x = 0; x < radialSegments; x ++ ) {
+
+ var v1 = vertices[ 0 ][ x ];
+ var v2 = vertices[ 0 ][ x + 1 ];
+ var v3 = this.vertices.length - 1;
+
+ var n1 = new THREE.Vector3( 0, 1, 0 );
+ var n2 = new THREE.Vector3( 0, 1, 0 );
+ var n3 = new THREE.Vector3( 0, 1, 0 );
+
+ var uv1 = uvs[ 0 ][ x ].clone();
+ var uv2 = uvs[ 0 ][ x + 1 ].clone();
+ var uv3 = new THREE.Vector2( uv2.u, 0 );
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] );
+
+ }
+
+ }
+
+ // bottom cap
+
+ if ( openEnded === false && radiusBottom > 0 ) {
+
+ this.vertices.push( new THREE.Vector3( 0, - heightHalf, 0 ) );
+
+ for ( x = 0; x < radialSegments; x ++ ) {
+
+ var v1 = vertices[ y ][ x + 1 ];
+ var v2 = vertices[ y ][ x ];
+ var v3 = this.vertices.length - 1;
+
+ var n1 = new THREE.Vector3( 0, - 1, 0 );
+ var n2 = new THREE.Vector3( 0, - 1, 0 );
+ var n3 = new THREE.Vector3( 0, - 1, 0 );
+
+ var uv1 = uvs[ y ][ x + 1 ].clone();
+ var uv2 = uvs[ y ][ x ].clone();
+ var uv3 = new THREE.Vector2( uv2.u, 1 );
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] );
+
+ }
+
+ }
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+
+}
+
+THREE.CylinderGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ *
+ * Creates extruded geometry from a path shape.
+ *
+ * parameters = {
+ *
+ * size: , // size of the text
+ * height: , // thickness to extrude text
+ * curveSegments: , // number of points on the curves
+ * steps: , // number of points for z-side extrusions / used for subdividing segements of extrude spline too
+ * amount: , // Amount
+ *
+ * bevelEnabled: , // turn on bevel
+ * bevelThickness: , // how deep into text bevel goes
+ * bevelSize: , // how far from text outline is bevel
+ * bevelSegments: , // number of bevel layers
+ *
+ * extrudePath: // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined)
+ * frames: // containing arrays of tangents, normals, binormals
+ *
+ * material: // material index for front and back faces
+ * extrudeMaterial: // material index for extrusion and beveled faces
+ * uvGenerator: .
',
+ 'Find out how to get it
here.'
+ ].join( '\n' ) : [
+ 'Your browser does not seem to support
WebGL.
',
+ 'Find out how to get it
here.'
+ ].join( '\n' );
+
+ }
+
+ return element;
+
+ },
+
+ addGetWebGLMessage: function ( parameters ) {
+
+ var parent, id, element;
+
+ parameters = parameters || {};
+
+ parent = parameters.parent !== undefined ? parameters.parent : document.body;
+ id = parameters.id !== undefined ? parameters.id : 'oldie';
+
+ element = Detector.getWebGLErrorMessage();
+ element.id = id;
+
+ parent.appendChild( element );
+
+ }
+
+};
\ No newline at end of file
diff --git a/papi/plugin/dpp/Human/viewer/JSONLoader.js b/papi/plugin/dpp/Human/viewer/JSONLoader.js
new file mode 100755
index 00000000..5862ca73
--- /dev/null
+++ b/papi/plugin/dpp/Human/viewer/JSONLoader.js
@@ -0,0 +1,544 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.JSONLoader = function ( showStatus ) {
+
+ THREE.Loader.call( this, showStatus );
+
+ this.withCredentials = false;
+
+};
+
+THREE.JSONLoader.prototype = Object.create( THREE.Loader.prototype );
+
+THREE.JSONLoader.prototype.load = function ( url, callback, texturePath ) {
+
+ var scope = this;
+
+ // todo: unify load API to for easier SceneLoader use
+
+ texturePath = texturePath && ( typeof texturePath === 'string' ) ? texturePath : this.extractUrlBase( url );
+
+ this.onLoadStart();
+ this.loadAjaxJSON( this, url, callback, texturePath );
+
+};
+
+THREE.JSONLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, callbackProgress ) {
+
+ var xhr = new XMLHttpRequest();
+
+ var length = 0;
+
+ xhr.onreadystatechange = function () {
+
+ if ( xhr.readyState === xhr.DONE ) {
+
+ if ( xhr.status === 200 || xhr.status === 0 ) {
+
+ if ( xhr.responseText ) {
+
+ var json = JSON.parse( xhr.responseText );
+
+ if ( json.metadata !== undefined && json.metadata.type === 'scene' ) {
+
+ console.error( 'THREE.JSONLoader: "' + url + '" seems to be a Scene. Use THREE.SceneLoader instead.' );
+ return;
+
+ }
+
+ var result = context.parse( json, texturePath );
+ callback( result.geometry, result.materials );
+
+ } else {
+
+ console.error( 'THREE.JSONLoader: "' + url + '" seems to be unreachable or the file is empty.' );
+
+ }
+
+ // in context of more complex asset initialization
+ // do not block on single failed file
+ // maybe should go even one more level up
+
+ context.onLoadComplete();
+
+ } else {
+
+ console.error( 'THREE.JSONLoader: Couldn\'t load "' + url + '" (' + xhr.status + ')' );
+
+ }
+
+ } else if ( xhr.readyState === xhr.LOADING ) {
+
+ if ( callbackProgress ) {
+
+ if ( length === 0 ) {
+
+ length = xhr.getResponseHeader( 'Content-Length' );
+
+ }
+
+ callbackProgress( { total: length, loaded: xhr.responseText.length } );
+
+ }
+
+ } else if ( xhr.readyState === xhr.HEADERS_RECEIVED ) {
+
+ if ( callbackProgress !== undefined ) {
+
+ length = xhr.getResponseHeader( 'Content-Length' );
+
+ }
+
+ }
+
+ };
+
+ xhr.open( 'GET', url, true );
+ xhr.withCredentials = this.withCredentials;
+ xhr.send( null );
+
+};
+
+THREE.JSONLoader.prototype.parse = function ( json, texturePath ) {
+
+ var scope = this,
+ geometry = new THREE.Geometry(),
+ scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0;
+
+ parseModel( scale );
+
+ parseSkin();
+ parseMorphing( scale );
+
+ geometry.computeFaceNormals();
+ geometry.computeBoundingSphere();
+
+ function parseModel( scale ) {
+
+ function isBitSet( value, position ) {
+
+ return value & ( 1 << position );
+
+ }
+
+ var i, j, fi,
+
+ offset, zLength,
+
+ colorIndex, normalIndex, uvIndex, materialIndex,
+
+ type,
+ isQuad,
+ hasMaterial,
+ hasFaceVertexUv,
+ hasFaceNormal, hasFaceVertexNormal,
+ hasFaceColor, hasFaceVertexColor,
+
+ vertex, face, faceA, faceB, color, hex, normal,
+
+ uvLayer, uv, u, v,
+
+ faces = json.faces,
+ vertices = json.vertices,
+ normals = json.normals,
+ colors = json.colors,
+
+ nUvLayers = 0;
+
+ if ( json.uvs !== undefined ) {
+
+ // disregard empty arrays
+
+ for ( i = 0; i < json.uvs.length; i ++ ) {
+
+ if ( json.uvs[ i ].length ) nUvLayers ++;
+
+ }
+
+ for ( i = 0; i < nUvLayers; i ++ ) {
+
+ geometry.faceVertexUvs[ i ] = [];
+
+ }
+
+ }
+
+ offset = 0;
+ zLength = vertices.length;
+
+ while ( offset < zLength ) {
+
+ vertex = new THREE.Vector3();
+
+ vertex.x = vertices[ offset ++ ] * scale;
+ vertex.y = vertices[ offset ++ ] * scale;
+ vertex.z = vertices[ offset ++ ] * scale;
+
+ geometry.vertices.push( vertex );
+
+ }
+
+ offset = 0;
+ zLength = faces.length;
+
+ while ( offset < zLength ) {
+
+ type = faces[ offset ++ ];
+
+
+ isQuad = isBitSet( type, 0 );
+ hasMaterial = isBitSet( type, 1 );
+ hasFaceVertexUv = isBitSet( type, 3 );
+ hasFaceNormal = isBitSet( type, 4 );
+ hasFaceVertexNormal = isBitSet( type, 5 );
+ hasFaceColor = isBitSet( type, 6 );
+ hasFaceVertexColor = isBitSet( type, 7 );
+
+ // console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);
+
+ if ( isQuad ) {
+
+ faceA = new THREE.Face3();
+ faceA.a = faces[ offset ];
+ faceA.b = faces[ offset + 1 ];
+ faceA.c = faces[ offset + 3 ];
+
+ faceB = new THREE.Face3();
+ faceB.a = faces[ offset + 1 ];
+ faceB.b = faces[ offset + 2 ];
+ faceB.c = faces[ offset + 3 ];
+
+ offset += 4;
+
+ if ( hasMaterial ) {
+
+ materialIndex = faces[ offset ++ ];
+ faceA.materialIndex = materialIndex;
+ faceB.materialIndex = materialIndex;
+
+ }
+
+ // to get face <=> uv index correspondence
+
+ fi = geometry.faces.length;
+
+ if ( hasFaceVertexUv ) {
+
+ for ( i = 0; i < nUvLayers; i ++ ) {
+
+ uvLayer = json.uvs[ i ];
+
+ geometry.faceVertexUvs[ i ][ fi ] = [];
+ geometry.faceVertexUvs[ i ][ fi + 1 ] = []
+
+ for ( j = 0; j < 4; j ++ ) {
+
+ uvIndex = faces[ offset ++ ];
+
+ u = uvLayer[ uvIndex * 2 ];
+ v = uvLayer[ uvIndex * 2 + 1 ];
+
+ uv = new THREE.Vector2( u, v );
+
+ if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv );
+ if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv );
+
+ }
+
+ }
+
+ }
+
+ if ( hasFaceNormal ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ faceA.normal.set(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ faceB.normal.copy( faceA.normal );
+
+ }
+
+ if ( hasFaceVertexNormal ) {
+
+ for ( i = 0; i < 4; i ++ ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ normal = new THREE.Vector3(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+
+ if ( i !== 2 ) faceA.vertexNormals.push( normal );
+ if ( i !== 0 ) faceB.vertexNormals.push( normal );
+
+ }
+
+ }
+
+
+ if ( hasFaceColor ) {
+
+ colorIndex = faces[ offset ++ ];
+ hex = colors[ colorIndex ];
+
+ faceA.color.setHex( hex );
+ faceB.color.setHex( hex );
+
+ }
+
+
+ if ( hasFaceVertexColor ) {
+
+ for ( i = 0; i < 4; i ++ ) {
+
+ colorIndex = faces[ offset ++ ];
+ hex = colors[ colorIndex ];
+
+ if ( i !== 2 ) faceA.vertexColors.push( new THREE.Color( hex ) );
+ if ( i !== 0 ) faceB.vertexColors.push( new THREE.Color( hex ) );
+
+ }
+
+ }
+
+ geometry.faces.push( faceA );
+ geometry.faces.push( faceB );
+
+ } else {
+
+ face = new THREE.Face3();
+ face.a = faces[ offset ++ ];
+ face.b = faces[ offset ++ ];
+ face.c = faces[ offset ++ ];
+
+ if ( hasMaterial ) {
+
+ materialIndex = faces[ offset ++ ];
+ face.materialIndex = materialIndex;
+
+ }
+
+ // to get face <=> uv index correspondence
+
+ fi = geometry.faces.length;
+
+ if ( hasFaceVertexUv ) {
+
+ for ( i = 0; i < nUvLayers; i ++ ) {
+
+ uvLayer = json.uvs[ i ];
+
+ geometry.faceVertexUvs[ i ][ fi ] = [];
+
+ for ( j = 0; j < 3; j ++ ) {
+
+ uvIndex = faces[ offset ++ ];
+
+ u = uvLayer[ uvIndex * 2 ];
+ v = uvLayer[ uvIndex * 2 + 1 ];
+
+ uv = new THREE.Vector2( u, v );
+
+ geometry.faceVertexUvs[ i ][ fi ].push( uv );
+
+ }
+
+ }
+
+ }
+
+ if ( hasFaceNormal ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ face.normal.set(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ }
+
+ if ( hasFaceVertexNormal ) {
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ normal = new THREE.Vector3(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ face.vertexNormals.push( normal );
+
+ }
+
+ }
+
+
+ if ( hasFaceColor ) {
+
+ colorIndex = faces[ offset ++ ];
+ face.color.setHex( colors[ colorIndex ] );
+
+ }
+
+
+ if ( hasFaceVertexColor ) {
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ colorIndex = faces[ offset ++ ];
+ face.vertexColors.push( new THREE.Color( colors[ colorIndex ] ) );
+
+ }
+
+ }
+
+ geometry.faces.push( face );
+
+ }
+
+ }
+
+ };
+
+ function parseSkin() {
+ var influencesPerVertex = ( json.influencesPerVertex !== undefined ) ? json.influencesPerVertex : 2;
+
+ if ( json.skinWeights ) {
+
+ for ( var i = 0, l = json.skinWeights.length; i < l; i += influencesPerVertex ) {
+
+ var x = json.skinWeights[ i ];
+ var y = ( influencesPerVertex > 1 ) ? json.skinWeights[ i + 1 ] : 0;
+ var z = ( influencesPerVertex > 2 ) ? json.skinWeights[ i + 2 ] : 0;
+ var w = ( influencesPerVertex > 3 ) ? json.skinWeights[ i + 3 ] : 0;
+
+ geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) );
+
+ }
+
+ }
+
+ if ( json.skinIndices ) {
+
+ for ( var i = 0, l = json.skinIndices.length; i < l; i += influencesPerVertex ) {
+
+ var a = json.skinIndices[ i ];
+ var b = ( influencesPerVertex > 1 ) ? json.skinIndices[ i + 1 ] : 0;
+ var c = ( influencesPerVertex > 2 ) ? json.skinIndices[ i + 2 ] : 0;
+ var d = ( influencesPerVertex > 3 ) ? json.skinIndices[ i + 3 ] : 0;
+
+ geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) );
+
+ }
+
+ }
+
+ geometry.bones = json.bones;
+
+ if ( geometry.bones && geometry.bones.length > 0 && ( geometry.skinWeights.length !== geometry.skinIndices.length || geometry.skinIndices.length !== geometry.vertices.length ) ) {
+
+ console.warn( 'When skinning, number of vertices (' + geometry.vertices.length + '), skinIndices (' +
+ geometry.skinIndices.length + '), and skinWeights (' + geometry.skinWeights.length + ') should match.' );
+
+ }
+
+
+ // could change this to json.animations[0] or remove completely
+
+ geometry.animation = json.animation;
+ geometry.animations = json.animations;
+
+ };
+
+ function parseMorphing( scale ) {
+
+ if ( json.morphTargets !== undefined ) {
+
+ var i, l, v, vl, dstVertices, srcVertices;
+
+ for ( i = 0, l = json.morphTargets.length; i < l; i ++ ) {
+
+ geometry.morphTargets[ i ] = {};
+ geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;
+ geometry.morphTargets[ i ].vertices = [];
+
+ dstVertices = geometry.morphTargets[ i ].vertices;
+ srcVertices = json.morphTargets [ i ].vertices;
+
+ for ( v = 0, vl = srcVertices.length; v < vl; v += 3 ) {
+
+ var vertex = new THREE.Vector3();
+ vertex.x = srcVertices[ v ] * scale;
+ vertex.y = srcVertices[ v + 1 ] * scale;
+ vertex.z = srcVertices[ v + 2 ] * scale;
+
+ dstVertices.push( vertex );
+
+ }
+
+ }
+
+ }
+
+ if ( json.morphColors !== undefined ) {
+
+ var i, l, c, cl, dstColors, srcColors, color;
+
+ for ( i = 0, l = json.morphColors.length; i < l; i ++ ) {
+
+ geometry.morphColors[ i ] = {};
+ geometry.morphColors[ i ].name = json.morphColors[ i ].name;
+ geometry.morphColors[ i ].colors = [];
+
+ dstColors = geometry.morphColors[ i ].colors;
+ srcColors = json.morphColors [ i ].colors;
+
+ for ( c = 0, cl = srcColors.length; c < cl; c += 3 ) {
+
+ color = new THREE.Color( 0xffaa00 );
+ color.setRGB( srcColors[ c ], srcColors[ c + 1 ], srcColors[ c + 2 ] );
+ dstColors.push( color );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ if ( json.materials === undefined || json.materials.length === 0 ) {
+
+ return { geometry: geometry };
+
+ } else {
+
+ var materials = this.initMaterials( json.materials, texturePath );
+
+ if ( this.needsTangents( materials ) ) {
+
+ geometry.computeTangents();
+
+ }
+
+ return { geometry: geometry, materials: materials };
+
+ }
+
+};
diff --git a/papi/plugin/dpp/Human/viewer/OBJLoader.js b/papi/plugin/dpp/Human/viewer/OBJLoader.js
new file mode 100644
index 00000000..5afad800
--- /dev/null
+++ b/papi/plugin/dpp/Human/viewer/OBJLoader.js
@@ -0,0 +1,291 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.OBJLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.OBJLoader.prototype = {
+
+ constructor: THREE.OBJLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader( scope.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( text ) );
+
+ } );
+
+ },
+
+ parse: function ( text ) {
+
+ // fixes
+
+ text = text.replace( /\ \\\r\n/g, '' ); // rhino adds ' \\r\n' some times.
+
+ var replacement = '/f$1$2$4\n/f$2$3$4'; // quads to tris
+ text = text.replace( /f( +\d+)( +\d+)( +\d+)( +\d+)/g, replacement );
+ text = text.replace( /f( +\d+\/\d+)( +\d+\/\d+)( +\d+\/\d+)( +\d+\/\d+)/g, replacement );
+ text = text.replace( /f( +\d+\/\d+\/\d+)( +\d+\/\d+\/\d+)( +\d+\/\d+\/\d+)( +\d+\/\d+\/\d+)/g, replacement );
+ text = text.replace( /f( +\d+\/\/\d+)( +\d+\/\/\d+)( +\d+\/\/\d+)( +\d+\/\/\d+)/g, replacement );
+
+ //
+
+ function vector( x, y, z ) {
+
+ return new THREE.Vector3( x, y, z );
+
+ }
+
+ function uv( u, v ) {
+
+ return new THREE.Vector2( u, v );
+
+ }
+
+ function face3( a, b, c, normals ) {
+
+ return new THREE.Face3( a, b, c, normals );
+
+ }
+
+ var object = new THREE.Object3D();
+ var geometry, material, mesh;
+
+ // create mesh if no objects in text
+
+ if ( /^o /gm.test( text ) === false ) {
+
+ geometry = new THREE.Geometry();
+ material = new THREE.MeshLambertMaterial();
+ mesh = new THREE.Mesh( geometry, material );
+ object.add( mesh );
+
+ }
+
+ var vertices = [];
+ var verticesCount = 0;
+ var normals = [];
+ var uvs = [];
+
+ // v float float float
+
+ var vertex_pattern = /v( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;
+
+ // vn float float float
+
+ var normal_pattern = /vn( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;
+
+ // vt float float
+
+ var uv_pattern = /vt( +[\d|\.|\+|\-|e]+)( +[\d|\.|\+|\-|e]+)/;
+
+ // f vertex vertex vertex
+
+ var face_pattern1 = /f( +\d+)( +\d+)( +\d+)/;
+
+ // f vertex/uv vertex/uv vertex/uv
+
+ var face_pattern2 = /f( +(\d+)\/(\d+))( +(\d+)\/(\d+))( +(\d+)\/(\d+))/;
+
+ // f vertex/uv/normal vertex/uv/normal vertex/uv/normal
+
+ var face_pattern3 = /f( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))( +(\d+)\/(\d+)\/(\d+))/;
+
+ // f vertex//normal vertex//normal vertex//normal
+
+ var face_pattern4 = /f( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))( +(\d+)\/\/(\d+))/;
+
+ //
+
+ var lines = text.split( '\n' );
+
+ for ( var i = 0; i < lines.length; i ++ ) {
+
+ var line = lines[ i ];
+ line = line.trim();
+
+ var result;
+
+ if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
+
+ continue;
+
+ } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) {
+
+ // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
+
+ vertices.push( vector(
+ parseFloat( result[ 1 ] ),
+ parseFloat( result[ 2 ] ),
+ parseFloat( result[ 3 ] )
+ ) );
+
+ } else if ( ( result = normal_pattern.exec( line ) ) !== null ) {
+
+ // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"]
+
+ normals.push( vector(
+ parseFloat( result[ 1 ] ),
+ parseFloat( result[ 2 ] ),
+ parseFloat( result[ 3 ] )
+ ) );
+
+ } else if ( ( result = uv_pattern.exec( line ) ) !== null ) {
+
+ // ["vt 0.1 0.2", "0.1", "0.2"]
+
+ uvs.push( uv(
+ parseFloat( result[ 1 ] ),
+ parseFloat( result[ 2 ] )
+ ) );
+
+ } else if ( ( result = face_pattern1.exec( line ) ) !== null ) {
+
+ // ["f 1 2 3", "1", "2", "3"]
+
+ geometry.vertices.push(
+ vertices[ parseInt( result[ 1 ] ) - 1 ],
+ vertices[ parseInt( result[ 2 ] ) - 1 ],
+ vertices[ parseInt( result[ 3 ] ) - 1 ]
+ );
+
+ geometry.faces.push( face3(
+ verticesCount ++,
+ verticesCount ++,
+ verticesCount ++
+ ) );
+
+ } else if ( ( result = face_pattern2.exec( line ) ) !== null ) {
+
+ // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3"]
+
+ geometry.vertices.push(
+ vertices[ parseInt( result[ 2 ] ) - 1 ],
+ vertices[ parseInt( result[ 5 ] ) - 1 ],
+ vertices[ parseInt( result[ 8 ] ) - 1 ]
+ );
+
+ geometry.faces.push( face3(
+ verticesCount ++,
+ verticesCount ++,
+ verticesCount ++
+ ) );
+
+ geometry.faceVertexUvs[ 0 ].push( [
+ uvs[ parseInt( result[ 3 ] ) - 1 ],
+ uvs[ parseInt( result[ 6 ] ) - 1 ],
+ uvs[ parseInt( result[ 9 ] ) - 1 ]
+ ] );
+
+ } else if ( ( result = face_pattern3.exec( line ) ) !== null ) {
+
+ // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3"]
+
+ geometry.vertices.push(
+ vertices[ parseInt( result[ 2 ] ) - 1 ],
+ vertices[ parseInt( result[ 6 ] ) - 1 ],
+ vertices[ parseInt( result[ 10 ] ) - 1 ]
+ );
+
+ geometry.faces.push( face3(
+ verticesCount ++,
+ verticesCount ++,
+ verticesCount ++,
+ [
+ normals[ parseInt( result[ 4 ] ) - 1 ],
+ normals[ parseInt( result[ 8 ] ) - 1 ],
+ normals[ parseInt( result[ 12 ] ) - 1 ]
+ ]
+ ) );
+
+ geometry.faceVertexUvs[ 0 ].push( [
+ uvs[ parseInt( result[ 3 ] ) - 1 ],
+ uvs[ parseInt( result[ 7 ] ) - 1 ],
+ uvs[ parseInt( result[ 11 ] ) - 1 ]
+ ] );
+
+ } else if ( ( result = face_pattern4.exec( line ) ) !== null ) {
+
+ // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3"]
+
+ geometry.vertices.push(
+ vertices[ parseInt( result[ 2 ] ) - 1 ],
+ vertices[ parseInt( result[ 5 ] ) - 1 ],
+ vertices[ parseInt( result[ 8 ] ) - 1 ]
+ );
+
+ geometry.faces.push( face3(
+ verticesCount ++,
+ verticesCount ++,
+ verticesCount ++,
+ [
+ normals[ parseInt( result[ 3 ] ) - 1 ],
+ normals[ parseInt( result[ 6 ] ) - 1 ],
+ normals[ parseInt( result[ 9 ] ) - 1 ]
+ ]
+ ) );
+
+ } else if ( /^o /.test( line ) ) {
+
+ // object
+
+ geometry = new THREE.Geometry();
+ material = new THREE.MeshLambertMaterial();
+
+ mesh = new THREE.Mesh( geometry, material );
+ mesh.name = line.substring( 2 ).trim();
+ object.add( mesh );
+
+ verticesCount = 0;
+
+ } else if ( /^g /.test( line ) ) {
+
+ // group
+
+ } else if ( /^usemtl /.test( line ) ) {
+
+ // material
+
+ material.name = line.substring( 7 ).trim();
+
+ } else if ( /^mtllib /.test( line ) ) {
+
+ // mtl file
+
+ } else if ( /^s /.test( line ) ) {
+
+ // smooth shading
+
+ } else {
+
+ // console.log( "THREE.OBJLoader: Unhandled line " + line );
+
+ }
+
+ }
+
+ for ( var i = 0, l = object.children.length; i < l; i ++ ) {
+
+ var geometry = object.children[ i ].geometry;
+
+ geometry.computeCentroids();
+ geometry.computeFaceNormals();
+ geometry.computeBoundingSphere();
+
+ }
+
+ return object;
+
+ }
+
+};
diff --git a/papi/plugin/dpp/Human/viewer/TrackballControls.js b/papi/plugin/dpp/Human/viewer/TrackballControls.js
new file mode 100644
index 00000000..3fb0f512
--- /dev/null
+++ b/papi/plugin/dpp/Human/viewer/TrackballControls.js
@@ -0,0 +1,557 @@
+/**
+ * @author Eberhard Graether / http://egraether.com/
+ */
+
+THREE.TrackballControls = function ( object, domElement ) {
+
+ var _this = this;
+ var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM: 4, TOUCH_PAN: 5 };
+
+ this.object = object;
+ this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+ // API
+
+ this.enabled = true;
+
+ this.screen = { left: 0, top: 0, width: 0, height: 0 };
+
+ this.rotateSpeed = 1.0;
+ this.zoomSpeed = 1.2;
+ this.panSpeed = 0.3;
+
+ this.noRotate = false;
+ this.noZoom = false;
+ this.noPan = false;
+ this.noRoll = false;
+
+ this.staticMoving = false;
+ this.dynamicDampingFactor = 0.2;
+
+ this.minDistance = 0;
+ this.maxDistance = Infinity;
+
+ this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
+
+ // internals
+
+ this.target = new THREE.Vector3();
+
+ var lastPosition = new THREE.Vector3();
+
+ var _state = STATE.NONE,
+ _prevState = STATE.NONE,
+
+ _eye = new THREE.Vector3(),
+
+ _rotateStart = new THREE.Vector3(),
+ _rotateEnd = new THREE.Vector3(),
+
+ _zoomStart = new THREE.Vector2(),
+ _zoomEnd = new THREE.Vector2(),
+
+ _touchZoomDistanceStart = 0,
+ _touchZoomDistanceEnd = 0,
+
+ _panStart = new THREE.Vector2(),
+ _panEnd = new THREE.Vector2();
+
+ // for reset
+
+ this.target0 = this.target.clone();
+ this.position0 = this.object.position.clone();
+ this.up0 = this.object.up.clone();
+
+ // events
+
+ var changeEvent = { type: 'change' };
+
+
+ // methods
+
+ this.handleResize = function () {
+
+ if ( this.domElement === document ) {
+
+ this.screen.left = 0;
+ this.screen.top = 0;
+ this.screen.width = window.innerWidth;
+ this.screen.height = window.innerHeight;
+
+ } else {
+
+ this.screen = this.domElement.getBoundingClientRect();
+
+ }
+
+ };
+
+ this.handleEvent = function ( event ) {
+
+ if ( typeof this[ event.type ] == 'function' ) {
+
+ this[ event.type ]( event );
+
+ }
+
+ };
+
+ this.getMouseOnScreen = function ( clientX, clientY ) {
+
+ return new THREE.Vector2(
+ ( clientX - _this.screen.left ) / _this.screen.width,
+ ( clientY - _this.screen.top ) / _this.screen.height
+ );
+
+ };
+
+ this.getMouseProjectionOnBall = function ( clientX, clientY ) {
+
+ var mouseOnBall = new THREE.Vector3(
+ ( clientX - _this.screen.width * 0.5 - _this.screen.left ) / (_this.screen.width*.5),
+ ( _this.screen.height * 0.5 + _this.screen.top - clientY ) / (_this.screen.height*.5),
+ 0.0
+ );
+
+ var length = mouseOnBall.length();
+
+ if ( _this.noRoll ) {
+
+ if ( length < Math.SQRT1_2 ) {
+
+ mouseOnBall.z = Math.sqrt( 1.0 - length*length );
+
+ } else {
+
+ mouseOnBall.z = .5 / length;
+
+ }
+
+ } else if ( length > 1.0 ) {
+
+ mouseOnBall.normalize();
+
+ } else {
+
+ mouseOnBall.z = Math.sqrt( 1.0 - length * length );
+
+ }
+
+ _eye.copy( _this.object.position ).sub( _this.target );
+
+ var projection = _this.object.up.clone().setLength( mouseOnBall.y );
+ projection.add( _this.object.up.clone().cross( _eye ).setLength( mouseOnBall.x ) );
+ projection.add( _eye.setLength( mouseOnBall.z ) );
+
+ return projection;
+
+ };
+
+ this.rotateCamera = function () {
+
+ var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
+
+ if ( angle ) {
+
+ var axis = ( new THREE.Vector3() ).crossVectors( _rotateStart, _rotateEnd ).normalize(),
+ quaternion = new THREE.Quaternion();
+
+ angle *= _this.rotateSpeed;
+
+ quaternion.setFromAxisAngle( axis, -angle );
+
+ _eye.applyQuaternion( quaternion );
+ _this.object.up.applyQuaternion( quaternion );
+
+ _rotateEnd.applyQuaternion( quaternion );
+
+ if ( _this.staticMoving ) {
+
+ _rotateStart.copy( _rotateEnd );
+
+ } else {
+
+ quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
+ _rotateStart.applyQuaternion( quaternion );
+
+ }
+
+ }
+
+ };
+
+ this.zoomCamera = function () {
+
+ if ( _state === STATE.TOUCH_ZOOM ) {
+
+ var factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
+ _touchZoomDistanceStart = _touchZoomDistanceEnd;
+ _eye.multiplyScalar( factor );
+
+ } else {
+
+ var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
+
+ if ( factor !== 1.0 && factor > 0.0 ) {
+
+ _eye.multiplyScalar( factor );
+
+ if ( _this.staticMoving ) {
+
+ _zoomStart.copy( _zoomEnd );
+
+ } else {
+
+ _zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
+
+ }
+
+ }
+
+ }
+
+ };
+
+ this.panCamera = function () {
+
+ var mouseChange = _panEnd.clone().sub( _panStart );
+
+ if ( mouseChange.lengthSq() ) {
+
+ mouseChange.multiplyScalar( _eye.length() * _this.panSpeed );
+
+ var pan = _eye.clone().cross( _this.object.up ).setLength( mouseChange.x );
+ pan.add( _this.object.up.clone().setLength( mouseChange.y ) );
+
+ _this.object.position.add( pan );
+ _this.target.add( pan );
+
+ if ( _this.staticMoving ) {
+
+ _panStart = _panEnd;
+
+ } else {
+
+ _panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
+
+ }
+
+ }
+
+ };
+
+ this.checkDistances = function () {
+
+ if ( !_this.noZoom || !_this.noPan ) {
+
+ if ( _eye.lengthSq() > _this.maxDistance * _this.maxDistance ) {
+
+ _this.object.position.addVectors( _this.target, _eye.setLength( _this.maxDistance ) );
+
+ }
+
+ if ( _eye.lengthSq() < _this.minDistance * _this.minDistance ) {
+
+ _this.object.position.addVectors( _this.target, _eye.setLength( _this.minDistance ) );
+
+ }
+
+ }
+
+ };
+
+ this.update = function () {
+
+ _eye.subVectors( _this.object.position, _this.target );
+
+ if ( !_this.noRotate ) {
+
+ _this.rotateCamera();
+
+ }
+
+ if ( !_this.noZoom ) {
+
+ _this.zoomCamera();
+
+ }
+
+ if ( !_this.noPan ) {
+
+ _this.panCamera();
+
+ }
+
+ _this.object.position.addVectors( _this.target, _eye );
+
+ _this.checkDistances();
+
+ _this.object.lookAt( _this.target );
+
+ if ( lastPosition.distanceToSquared( _this.object.position ) > 0 ) {
+
+ _this.dispatchEvent( changeEvent );
+
+ lastPosition.copy( _this.object.position );
+
+ }
+
+ };
+
+ this.reset = function () {
+
+ _state = STATE.NONE;
+ _prevState = STATE.NONE;
+
+ _this.target.copy( _this.target0 );
+ _this.object.position.copy( _this.position0 );
+ _this.object.up.copy( _this.up0 );
+
+ _eye.subVectors( _this.object.position, _this.target );
+
+ _this.object.lookAt( _this.target );
+
+ _this.dispatchEvent( changeEvent );
+
+ lastPosition.copy( _this.object.position );
+
+ };
+
+ // listeners
+
+ function keydown( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ window.removeEventListener( 'keydown', keydown );
+
+ _prevState = _state;
+
+ if ( _state !== STATE.NONE ) {
+
+ return;
+
+ } else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && !_this.noRotate ) {
+
+ _state = STATE.ROTATE;
+
+ } else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && !_this.noZoom ) {
+
+ _state = STATE.ZOOM;
+
+ } else if ( event.keyCode === _this.keys[ STATE.PAN ] && !_this.noPan ) {
+
+ _state = STATE.PAN;
+
+ }
+
+ }
+
+ function keyup( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ _state = _prevState;
+
+ window.addEventListener( 'keydown', keydown, false );
+
+ }
+
+ function mousedown( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ if ( _state === STATE.NONE ) {
+
+ _state = event.button;
+
+ }
+
+ if ( _state === STATE.ROTATE && !_this.noRotate ) {
+
+ _rotateStart = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
+ _rotateEnd.copy(_rotateStart)
+
+ } else if ( _state === STATE.ZOOM && !_this.noZoom ) {
+
+ _zoomStart = _this.getMouseOnScreen( event.clientX, event.clientY );
+ _zoomEnd.copy(_zoomStart);
+
+ } else if ( _state === STATE.PAN && !_this.noPan ) {
+
+ _panStart = _this.getMouseOnScreen( event.clientX, event.clientY );
+ _panEnd.copy(_panStart)
+
+ }
+
+ document.addEventListener( 'mousemove', mousemove, false );
+ document.addEventListener( 'mouseup', mouseup, false );
+
+ }
+
+ function mousemove( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ if ( _state === STATE.ROTATE && !_this.noRotate ) {
+
+ _rotateEnd = _this.getMouseProjectionOnBall( event.clientX, event.clientY );
+
+ } else if ( _state === STATE.ZOOM && !_this.noZoom ) {
+
+ _zoomEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
+
+ } else if ( _state === STATE.PAN && !_this.noPan ) {
+
+ _panEnd = _this.getMouseOnScreen( event.clientX, event.clientY );
+
+ }
+
+ }
+
+ function mouseup( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ _state = STATE.NONE;
+
+ document.removeEventListener( 'mousemove', mousemove );
+ document.removeEventListener( 'mouseup', mouseup );
+
+ }
+
+ function mousewheel( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ var delta = 0;
+
+ if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9
+
+ delta = event.wheelDelta / 40;
+
+ } else if ( event.detail ) { // Firefox
+
+ delta = - event.detail / 3;
+
+ }
+
+ _zoomStart.y += delta * 0.01;
+
+ }
+
+ function touchstart( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ switch ( event.touches.length ) {
+
+ case 1:
+ _state = STATE.TOUCH_ROTATE;
+ _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+ break;
+
+ case 2:
+ _state = STATE.TOUCH_ZOOM;
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+ _touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
+ break;
+
+ case 3:
+ _state = STATE.TOUCH_PAN;
+ _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+ break;
+
+ default:
+ _state = STATE.NONE;
+
+ }
+
+ }
+
+ function touchmove( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ event.preventDefault();
+ event.stopPropagation();
+
+ switch ( event.touches.length ) {
+
+ case 1:
+ _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+ break;
+
+ case 2:
+ var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+ var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+ _touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy )
+ break;
+
+ case 3:
+ _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+ break;
+
+ default:
+ _state = STATE.NONE;
+
+ }
+
+ }
+
+ function touchend( event ) {
+
+ if ( _this.enabled === false ) return;
+
+ switch ( event.touches.length ) {
+
+ case 1:
+ _rotateStart = _rotateEnd = _this.getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+ break;
+
+ case 2:
+ _touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
+ break;
+
+ case 3:
+ _panStart = _panEnd = _this.getMouseOnScreen( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+ break;
+
+ }
+
+ _state = STATE.NONE;
+
+ }
+
+ this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
+
+ this.domElement.addEventListener( 'mousedown', mousedown, false );
+
+ this.domElement.addEventListener( 'mousewheel', mousewheel, false );
+ this.domElement.addEventListener( 'DOMMouseScroll', mousewheel, false ); // firefox
+
+ this.domElement.addEventListener( 'touchstart', touchstart, false );
+ this.domElement.addEventListener( 'touchend', touchend, false );
+ this.domElement.addEventListener( 'touchmove', touchmove, false );
+
+ window.addEventListener( 'keydown', keydown, false );
+ window.addEventListener( 'keyup', keyup, false );
+
+ this.handleResize();
+
+};
+
+THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
diff --git a/papi/plugin/dpp/Human/viewer/three.js b/papi/plugin/dpp/Human/viewer/three.js
new file mode 100755
index 00000000..b6c74723
--- /dev/null
+++ b/papi/plugin/dpp/Human/viewer/three.js
@@ -0,0 +1,36949 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author Larry Battle / http://bateru.com/news
+ * @author bhouston / http://exocortex.com
+ */
+
+var THREE = THREE || { REVISION: '61' };
+
+self.console = self.console || {
+
+ info: function () {},
+ log: function () {},
+ debug: function () {},
+ warn: function () {},
+ error: function () {}
+
+};
+
+String.prototype.trim = String.prototype.trim || function () {
+
+ return this.replace( /^\s+|\s+$/g, '' );
+
+};
+
+// based on https://github.com/documentcloud/underscore/blob/bf657be243a075b5e72acc8a83e6f12a564d8f55/underscore.js#L767
+THREE.extend = function ( obj, source ) {
+
+ // ECMAScript5 compatibility based on: http://www.nczonline.net/blog/2012/12/11/are-your-mixins-ecmascript-5-compatible/
+ if ( Object.keys ) {
+
+ var keys = Object.keys( source );
+
+ for (var i = 0, il = keys.length; i < il; i++) {
+
+ var prop = keys[i];
+ Object.defineProperty( obj, prop, Object.getOwnPropertyDescriptor( source, prop ) );
+
+ }
+
+ } else {
+
+ var safeHasOwnProperty = {}.hasOwnProperty;
+
+ for ( var prop in source ) {
+
+ if ( safeHasOwnProperty.call( source, prop ) ) {
+
+ obj[prop] = source[prop];
+
+ }
+
+ }
+
+ }
+
+ return obj;
+
+};
+
+// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
+// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
+
+// requestAnimationFrame polyfill by Erik Möller
+// fixes from Paul Irish and Tino Zijdel
+// using 'self' instead of 'window' for compatibility with both NodeJS and IE10.
+( function () {
+
+ var lastTime = 0;
+ var vendors = [ 'ms', 'moz', 'webkit', 'o' ];
+
+ for ( var x = 0; x < vendors.length && !self.requestAnimationFrame; ++ x ) {
+
+ self.requestAnimationFrame = self[ vendors[ x ] + 'RequestAnimationFrame' ];
+ self.cancelAnimationFrame = self[ vendors[ x ] + 'CancelAnimationFrame' ] || self[ vendors[ x ] + 'CancelRequestAnimationFrame' ];
+
+ }
+
+ if ( self.requestAnimationFrame === undefined && self['setTimeout'] !== undefined ) {
+
+ self.requestAnimationFrame = function ( callback ) {
+
+ var currTime = Date.now(), timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
+ var id = self.setTimeout( function() { callback( currTime + timeToCall ); }, timeToCall );
+ lastTime = currTime + timeToCall;
+ return id;
+
+ };
+
+ }
+
+ if( self.cancelAnimationFrame === undefined && self['clearTimeout'] !== undefined ) {
+
+ self.cancelAnimationFrame = function ( id ) { self.clearTimeout( id ) };
+
+ }
+
+}() );
+
+// GL STATE CONSTANTS
+
+THREE.CullFaceNone = 0;
+THREE.CullFaceBack = 1;
+THREE.CullFaceFront = 2;
+THREE.CullFaceFrontBack = 3;
+
+THREE.FrontFaceDirectionCW = 0;
+THREE.FrontFaceDirectionCCW = 1;
+
+// SHADOWING TYPES
+
+THREE.BasicShadowMap = 0;
+THREE.PCFShadowMap = 1;
+THREE.PCFSoftShadowMap = 2;
+
+// MATERIAL CONSTANTS
+
+// side
+
+THREE.FrontSide = 0;
+THREE.BackSide = 1;
+THREE.DoubleSide = 2;
+
+// shading
+
+THREE.NoShading = 0;
+THREE.FlatShading = 1;
+THREE.SmoothShading = 2;
+
+// colors
+
+THREE.NoColors = 0;
+THREE.FaceColors = 1;
+THREE.VertexColors = 2;
+
+// blending modes
+
+THREE.NoBlending = 0;
+THREE.NormalBlending = 1;
+THREE.AdditiveBlending = 2;
+THREE.SubtractiveBlending = 3;
+THREE.MultiplyBlending = 4;
+THREE.CustomBlending = 5;
+
+// custom blending equations
+// (numbers start from 100 not to clash with other
+// mappings to OpenGL constants defined in Texture.js)
+
+THREE.AddEquation = 100;
+THREE.SubtractEquation = 101;
+THREE.ReverseSubtractEquation = 102;
+
+// custom blending destination factors
+
+THREE.ZeroFactor = 200;
+THREE.OneFactor = 201;
+THREE.SrcColorFactor = 202;
+THREE.OneMinusSrcColorFactor = 203;
+THREE.SrcAlphaFactor = 204;
+THREE.OneMinusSrcAlphaFactor = 205;
+THREE.DstAlphaFactor = 206;
+THREE.OneMinusDstAlphaFactor = 207;
+
+// custom blending source factors
+
+//THREE.ZeroFactor = 200;
+//THREE.OneFactor = 201;
+//THREE.SrcAlphaFactor = 204;
+//THREE.OneMinusSrcAlphaFactor = 205;
+//THREE.DstAlphaFactor = 206;
+//THREE.OneMinusDstAlphaFactor = 207;
+THREE.DstColorFactor = 208;
+THREE.OneMinusDstColorFactor = 209;
+THREE.SrcAlphaSaturateFactor = 210;
+
+
+// TEXTURE CONSTANTS
+
+THREE.MultiplyOperation = 0;
+THREE.MixOperation = 1;
+THREE.AddOperation = 2;
+
+// Mapping modes
+
+THREE.UVMapping = function () {};
+
+THREE.CubeReflectionMapping = function () {};
+THREE.CubeRefractionMapping = function () {};
+
+THREE.SphericalReflectionMapping = function () {};
+THREE.SphericalRefractionMapping = function () {};
+
+// Wrapping modes
+
+THREE.RepeatWrapping = 1000;
+THREE.ClampToEdgeWrapping = 1001;
+THREE.MirroredRepeatWrapping = 1002;
+
+// Filters
+
+THREE.NearestFilter = 1003;
+THREE.NearestMipMapNearestFilter = 1004;
+THREE.NearestMipMapLinearFilter = 1005;
+THREE.LinearFilter = 1006;
+THREE.LinearMipMapNearestFilter = 1007;
+THREE.LinearMipMapLinearFilter = 1008;
+
+// Data types
+
+THREE.UnsignedByteType = 1009;
+THREE.ByteType = 1010;
+THREE.ShortType = 1011;
+THREE.UnsignedShortType = 1012;
+THREE.IntType = 1013;
+THREE.UnsignedIntType = 1014;
+THREE.FloatType = 1015;
+
+// Pixel types
+
+//THREE.UnsignedByteType = 1009;
+THREE.UnsignedShort4444Type = 1016;
+THREE.UnsignedShort5551Type = 1017;
+THREE.UnsignedShort565Type = 1018;
+
+// Pixel formats
+
+THREE.AlphaFormat = 1019;
+THREE.RGBFormat = 1020;
+THREE.RGBAFormat = 1021;
+THREE.LuminanceFormat = 1022;
+THREE.LuminanceAlphaFormat = 1023;
+
+// Compressed texture formats
+
+THREE.RGB_S3TC_DXT1_Format = 2001;
+THREE.RGBA_S3TC_DXT1_Format = 2002;
+THREE.RGBA_S3TC_DXT3_Format = 2003;
+THREE.RGBA_S3TC_DXT5_Format = 2004;
+
+/*
+// Potential future PVRTC compressed texture formats
+THREE.RGB_PVRTC_4BPPV1_Format = 2100;
+THREE.RGB_PVRTC_2BPPV1_Format = 2101;
+THREE.RGBA_PVRTC_4BPPV1_Format = 2102;
+THREE.RGBA_PVRTC_2BPPV1_Format = 2103;
+*/
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Color = function ( value ) {
+
+ if ( value !== undefined ) this.set( value );
+
+ return this;
+
+};
+
+THREE.Color.prototype = {
+
+ constructor: THREE.Color,
+
+ r: 1, g: 1, b: 1,
+
+ set: function ( value ) {
+
+ if ( value instanceof THREE.Color ) {
+
+ this.copy( value );
+
+ } else if ( typeof value === 'number' ) {
+
+ this.setHex( value );
+
+ } else if ( typeof value === 'string' ) {
+
+ this.setStyle( value );
+
+ }
+
+ return this;
+
+ },
+
+ setHex: function ( hex ) {
+
+ hex = Math.floor( hex );
+
+ this.r = ( hex >> 16 & 255 ) / 255;
+ this.g = ( hex >> 8 & 255 ) / 255;
+ this.b = ( hex & 255 ) / 255;
+
+ return this;
+
+ },
+
+ setRGB: function ( r, g, b ) {
+
+ this.r = r;
+ this.g = g;
+ this.b = b;
+
+ return this;
+
+ },
+
+ setHSL: function ( h, s, l ) {
+
+ // h,s,l ranges are in 0.0 - 1.0
+
+ if ( s === 0 ) {
+
+ this.r = this.g = this.b = l;
+
+ } else {
+
+ var hue2rgb = function ( p, q, t ) {
+
+ if ( t < 0 ) t += 1;
+ if ( t > 1 ) t -= 1;
+ if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;
+ if ( t < 1 / 2 ) return q;
+ if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );
+ return p;
+
+ };
+
+ var p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );
+ var q = ( 2 * l ) - p;
+
+ this.r = hue2rgb( q, p, h + 1 / 3 );
+ this.g = hue2rgb( q, p, h );
+ this.b = hue2rgb( q, p, h - 1 / 3 );
+
+ }
+
+ return this;
+
+ },
+
+ setStyle: function ( style ) {
+
+ // rgb(255,0,0)
+
+ if ( /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test( style ) ) {
+
+ var color = /^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec( style );
+
+ this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;
+ this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;
+ this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;
+
+ return this;
+
+ }
+
+ // rgb(100%,0%,0%)
+
+ if ( /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test( style ) ) {
+
+ var color = /^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.exec( style );
+
+ this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;
+ this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;
+ this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;
+
+ return this;
+
+ }
+
+ // #ff0000
+
+ if ( /^\#([0-9a-f]{6})$/i.test( style ) ) {
+
+ var color = /^\#([0-9a-f]{6})$/i.exec( style );
+
+ this.setHex( parseInt( color[ 1 ], 16 ) );
+
+ return this;
+
+ }
+
+ // #f00
+
+ if ( /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test( style ) ) {
+
+ var color = /^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec( style );
+
+ this.setHex( parseInt( color[ 1 ] + color[ 1 ] + color[ 2 ] + color[ 2 ] + color[ 3 ] + color[ 3 ], 16 ) );
+
+ return this;
+
+ }
+
+ // red
+
+ if ( /^(\w+)$/i.test( style ) ) {
+
+ this.setHex( THREE.ColorKeywords[ style ] );
+
+ return this;
+
+ }
+
+
+ },
+
+ copy: function ( color ) {
+
+ this.r = color.r;
+ this.g = color.g;
+ this.b = color.b;
+
+ return this;
+
+ },
+
+ copyGammaToLinear: function ( color ) {
+
+ this.r = color.r * color.r;
+ this.g = color.g * color.g;
+ this.b = color.b * color.b;
+
+ return this;
+
+ },
+
+ copyLinearToGamma: function ( color ) {
+
+ this.r = Math.sqrt( color.r );
+ this.g = Math.sqrt( color.g );
+ this.b = Math.sqrt( color.b );
+
+ return this;
+
+ },
+
+ convertGammaToLinear: function () {
+
+ var r = this.r, g = this.g, b = this.b;
+
+ this.r = r * r;
+ this.g = g * g;
+ this.b = b * b;
+
+ return this;
+
+ },
+
+ convertLinearToGamma: function () {
+
+ this.r = Math.sqrt( this.r );
+ this.g = Math.sqrt( this.g );
+ this.b = Math.sqrt( this.b );
+
+ return this;
+
+ },
+
+ getHex: function () {
+
+ return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;
+
+ },
+
+ getHexString: function () {
+
+ return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
+
+ },
+
+ getHSL: function () {
+
+ var hsl = { h: 0, s: 0, l: 0 };
+
+ return function () {
+
+ // h,s,l ranges are in 0.0 - 1.0
+
+ var r = this.r, g = this.g, b = this.b;
+
+ var max = Math.max( r, g, b );
+ var min = Math.min( r, g, b );
+
+ var hue, saturation;
+ var lightness = ( min + max ) / 2.0;
+
+ if ( min === max ) {
+
+ hue = 0;
+ saturation = 0;
+
+ } else {
+
+ var delta = max - min;
+
+ saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );
+
+ switch ( max ) {
+
+ case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;
+ case g: hue = ( b - r ) / delta + 2; break;
+ case b: hue = ( r - g ) / delta + 4; break;
+
+ }
+
+ hue /= 6;
+
+ }
+
+ hsl.h = hue;
+ hsl.s = saturation;
+ hsl.l = lightness;
+
+ return hsl;
+
+ };
+
+ }(),
+
+ getStyle: function () {
+
+ return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';
+
+ },
+
+ offsetHSL: function ( h, s, l ) {
+
+ var hsl = this.getHSL();
+
+ hsl.h += h; hsl.s += s; hsl.l += l;
+
+ this.setHSL( hsl.h, hsl.s, hsl.l );
+
+ return this;
+
+ },
+
+ add: function ( color ) {
+
+ this.r += color.r;
+ this.g += color.g;
+ this.b += color.b;
+
+ return this;
+
+ },
+
+ addColors: function ( color1, color2 ) {
+
+ this.r = color1.r + color2.r;
+ this.g = color1.g + color2.g;
+ this.b = color1.b + color2.b;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.r += s;
+ this.g += s;
+ this.b += s;
+
+ return this;
+
+ },
+
+ multiply: function ( color ) {
+
+ this.r *= color.r;
+ this.g *= color.g;
+ this.b *= color.b;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( s ) {
+
+ this.r *= s;
+ this.g *= s;
+ this.b *= s;
+
+ return this;
+
+ },
+
+ lerp: function ( color, alpha ) {
+
+ this.r += ( color.r - this.r ) * alpha;
+ this.g += ( color.g - this.g ) * alpha;
+ this.b += ( color.b - this.b ) * alpha;
+
+ return this;
+
+ },
+
+ equals: function ( c ) {
+
+ return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.r = array[ 0 ];
+ this.g = array[ 1 ];
+ this.b = array[ 2 ];
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this.r, this.g, this.b ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Color().setRGB( this.r, this.g, this.b );
+
+ }
+
+};
+
+THREE.ColorKeywords = { "aliceblue": 0xF0F8FF, "antiquewhite": 0xFAEBD7, "aqua": 0x00FFFF, "aquamarine": 0x7FFFD4, "azure": 0xF0FFFF,
+"beige": 0xF5F5DC, "bisque": 0xFFE4C4, "black": 0x000000, "blanchedalmond": 0xFFEBCD, "blue": 0x0000FF, "blueviolet": 0x8A2BE2,
+"brown": 0xA52A2A, "burlywood": 0xDEB887, "cadetblue": 0x5F9EA0, "chartreuse": 0x7FFF00, "chocolate": 0xD2691E, "coral": 0xFF7F50,
+"cornflowerblue": 0x6495ED, "cornsilk": 0xFFF8DC, "crimson": 0xDC143C, "cyan": 0x00FFFF, "darkblue": 0x00008B, "darkcyan": 0x008B8B,
+"darkgoldenrod": 0xB8860B, "darkgray": 0xA9A9A9, "darkgreen": 0x006400, "darkgrey": 0xA9A9A9, "darkkhaki": 0xBDB76B, "darkmagenta": 0x8B008B,
+"darkolivegreen": 0x556B2F, "darkorange": 0xFF8C00, "darkorchid": 0x9932CC, "darkred": 0x8B0000, "darksalmon": 0xE9967A, "darkseagreen": 0x8FBC8F,
+"darkslateblue": 0x483D8B, "darkslategray": 0x2F4F4F, "darkslategrey": 0x2F4F4F, "darkturquoise": 0x00CED1, "darkviolet": 0x9400D3,
+"deeppink": 0xFF1493, "deepskyblue": 0x00BFFF, "dimgray": 0x696969, "dimgrey": 0x696969, "dodgerblue": 0x1E90FF, "firebrick": 0xB22222,
+"floralwhite": 0xFFFAF0, "forestgreen": 0x228B22, "fuchsia": 0xFF00FF, "gainsboro": 0xDCDCDC, "ghostwhite": 0xF8F8FF, "gold": 0xFFD700,
+"goldenrod": 0xDAA520, "gray": 0x808080, "green": 0x008000, "greenyellow": 0xADFF2F, "grey": 0x808080, "honeydew": 0xF0FFF0, "hotpink": 0xFF69B4,
+"indianred": 0xCD5C5C, "indigo": 0x4B0082, "ivory": 0xFFFFF0, "khaki": 0xF0E68C, "lavender": 0xE6E6FA, "lavenderblush": 0xFFF0F5, "lawngreen": 0x7CFC00,
+"lemonchiffon": 0xFFFACD, "lightblue": 0xADD8E6, "lightcoral": 0xF08080, "lightcyan": 0xE0FFFF, "lightgoldenrodyellow": 0xFAFAD2, "lightgray": 0xD3D3D3,
+"lightgreen": 0x90EE90, "lightgrey": 0xD3D3D3, "lightpink": 0xFFB6C1, "lightsalmon": 0xFFA07A, "lightseagreen": 0x20B2AA, "lightskyblue": 0x87CEFA,
+"lightslategray": 0x778899, "lightslategrey": 0x778899, "lightsteelblue": 0xB0C4DE, "lightyellow": 0xFFFFE0, "lime": 0x00FF00, "limegreen": 0x32CD32,
+"linen": 0xFAF0E6, "magenta": 0xFF00FF, "maroon": 0x800000, "mediumaquamarine": 0x66CDAA, "mediumblue": 0x0000CD, "mediumorchid": 0xBA55D3,
+"mediumpurple": 0x9370DB, "mediumseagreen": 0x3CB371, "mediumslateblue": 0x7B68EE, "mediumspringgreen": 0x00FA9A, "mediumturquoise": 0x48D1CC,
+"mediumvioletred": 0xC71585, "midnightblue": 0x191970, "mintcream": 0xF5FFFA, "mistyrose": 0xFFE4E1, "moccasin": 0xFFE4B5, "navajowhite": 0xFFDEAD,
+"navy": 0x000080, "oldlace": 0xFDF5E6, "olive": 0x808000, "olivedrab": 0x6B8E23, "orange": 0xFFA500, "orangered": 0xFF4500, "orchid": 0xDA70D6,
+"palegoldenrod": 0xEEE8AA, "palegreen": 0x98FB98, "paleturquoise": 0xAFEEEE, "palevioletred": 0xDB7093, "papayawhip": 0xFFEFD5, "peachpuff": 0xFFDAB9,
+"peru": 0xCD853F, "pink": 0xFFC0CB, "plum": 0xDDA0DD, "powderblue": 0xB0E0E6, "purple": 0x800080, "red": 0xFF0000, "rosybrown": 0xBC8F8F,
+"royalblue": 0x4169E1, "saddlebrown": 0x8B4513, "salmon": 0xFA8072, "sandybrown": 0xF4A460, "seagreen": 0x2E8B57, "seashell": 0xFFF5EE,
+"sienna": 0xA0522D, "silver": 0xC0C0C0, "skyblue": 0x87CEEB, "slateblue": 0x6A5ACD, "slategray": 0x708090, "slategrey": 0x708090, "snow": 0xFFFAFA,
+"springgreen": 0x00FF7F, "steelblue": 0x4682B4, "tan": 0xD2B48C, "teal": 0x008080, "thistle": 0xD8BFD8, "tomato": 0xFF6347, "turquoise": 0x40E0D0,
+"violet": 0xEE82EE, "wheat": 0xF5DEB3, "white": 0xFFFFFF, "whitesmoke": 0xF5F5F5, "yellow": 0xFFFF00, "yellowgreen": 0x9ACD32 };
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Quaternion = function ( x, y, z, w ) {
+
+ this._x = x || 0;
+ this._y = y || 0;
+ this._z = z || 0;
+ this._w = ( w !== undefined ) ? w : 1;
+
+};
+
+THREE.Quaternion.prototype = {
+
+ constructor: THREE.Quaternion,
+
+ _x: 0,_y: 0, _z: 0, _w: 0,
+
+ _euler: undefined,
+
+ _updateEuler: function ( callback ) {
+
+ if ( this._euler !== undefined ) {
+
+ this._euler.setFromQuaternion( this, undefined, false );
+
+ }
+
+ },
+
+ get x () {
+
+ return this._x;
+
+ },
+
+ set x ( value ) {
+
+ this._x = value;
+ this._updateEuler();
+
+ },
+
+ get y () {
+
+ return this._y;
+
+ },
+
+ set y ( value ) {
+
+ this._y = value;
+ this._updateEuler();
+
+ },
+
+ get z () {
+
+ return this._z;
+
+ },
+
+ set z ( value ) {
+
+ this._z = value;
+ this._updateEuler();
+
+ },
+
+ get w () {
+
+ return this._w;
+
+ },
+
+ set w ( value ) {
+
+ this._w = value;
+ this._updateEuler();
+
+ },
+
+ set: function ( x, y, z, w ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._w = w;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ copy: function ( quaternion ) {
+
+ this._x = quaternion._x;
+ this._y = quaternion._y;
+ this._z = quaternion._z;
+ this._w = quaternion._w;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromEuler: function ( euler, update ) {
+
+ if ( euler instanceof THREE.Euler === false ) {
+
+ throw new Error( 'ERROR: Quaternion\'s .setFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' );
+ }
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/
+ // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
+ // content/SpinCalc.m
+
+ var c1 = Math.cos( euler._x / 2 );
+ var c2 = Math.cos( euler._y / 2 );
+ var c3 = Math.cos( euler._z / 2 );
+ var s1 = Math.sin( euler._x / 2 );
+ var s2 = Math.sin( euler._y / 2 );
+ var s3 = Math.sin( euler._z / 2 );
+
+ if ( euler.order === 'XYZ' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( euler.order === 'YXZ' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ } else if ( euler.order === 'ZXY' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( euler.order === 'ZYX' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ } else if ( euler.order === 'YZX' ) {
+
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+
+ } else if ( euler.order === 'XZY' ) {
+
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+
+ }
+
+ if ( update !== false ) this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromAxisAngle: function ( axis, angle ) {
+
+ // from http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
+ // axis have to be normalized
+
+ var halfAngle = angle / 2, s = Math.sin( halfAngle );
+
+ this._x = axis.x * s;
+ this._y = axis.y * s;
+ this._z = axis.z * s;
+ this._w = Math.cos( halfAngle );
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ setFromRotationMatrix: function ( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ var te = m.elements,
+
+ m11 = te[0], m12 = te[4], m13 = te[8],
+ m21 = te[1], m22 = te[5], m23 = te[9],
+ m31 = te[2], m32 = te[6], m33 = te[10],
+
+ trace = m11 + m22 + m33,
+ s;
+
+ if ( trace > 0 ) {
+
+ s = 0.5 / Math.sqrt( trace + 1.0 );
+
+ this._w = 0.25 / s;
+ this._x = ( m32 - m23 ) * s;
+ this._y = ( m13 - m31 ) * s;
+ this._z = ( m21 - m12 ) * s;
+
+ } else if ( m11 > m22 && m11 > m33 ) {
+
+ s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
+
+ this._w = (m32 - m23 ) / s;
+ this._x = 0.25 * s;
+ this._y = (m12 + m21 ) / s;
+ this._z = (m13 + m31 ) / s;
+
+ } else if ( m22 > m33 ) {
+
+ s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
+
+ this._w = (m13 - m31 ) / s;
+ this._x = (m12 + m21 ) / s;
+ this._y = 0.25 * s;
+ this._z = (m23 + m32 ) / s;
+
+ } else {
+
+ s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
+
+ this._w = ( m21 - m12 ) / s;
+ this._x = ( m13 + m31 ) / s;
+ this._y = ( m23 + m32 ) / s;
+ this._z = 0.25 * s;
+
+ }
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ inverse: function () {
+
+ this.conjugate().normalize();
+
+ return this;
+
+ },
+
+ conjugate: function () {
+
+ this._x *= -1;
+ this._y *= -1;
+ this._z *= -1;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ lengthSq: function () {
+
+ return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
+
+ },
+
+ normalize: function () {
+
+ var l = this.length();
+
+ if ( l === 0 ) {
+
+ this._x = 0;
+ this._y = 0;
+ this._z = 0;
+ this._w = 1;
+
+ } else {
+
+ l = 1 / l;
+
+ this._x = this._x * l;
+ this._y = this._y * l;
+ this._z = this._z * l;
+ this._w = this._w * l;
+
+ }
+
+ return this;
+
+ },
+
+ multiply: function ( q, p ) {
+
+ if ( p !== undefined ) {
+
+ console.warn( 'DEPRECATED: Quaternion\'s .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
+ return this.multiplyQuaternions( q, p );
+
+ }
+
+ return this.multiplyQuaternions( this, q );
+
+ },
+
+ multiplyQuaternions: function ( a, b ) {
+
+ // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
+
+ var qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
+ var qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
+
+ this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
+ this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
+ this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
+ this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Quaternion\'s .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
+ return vector.applyQuaternion( this );
+
+ },
+
+ slerp: function ( qb, t ) {
+
+ var x = this._x, y = this._y, z = this._z, w = this._w;
+
+ // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
+
+ var cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
+
+ if ( cosHalfTheta < 0 ) {
+
+ this._w = -qb._w;
+ this._x = -qb._x;
+ this._y = -qb._y;
+ this._z = -qb._z;
+
+ cosHalfTheta = -cosHalfTheta;
+
+ } else {
+
+ this.copy( qb );
+
+ }
+
+ if ( cosHalfTheta >= 1.0 ) {
+
+ this._w = w;
+ this._x = x;
+ this._y = y;
+ this._z = z;
+
+ return this;
+
+ }
+
+ var halfTheta = Math.acos( cosHalfTheta );
+ var sinHalfTheta = Math.sqrt( 1.0 - cosHalfTheta * cosHalfTheta );
+
+ if ( Math.abs( sinHalfTheta ) < 0.001 ) {
+
+ this._w = 0.5 * ( w + this._w );
+ this._x = 0.5 * ( x + this._x );
+ this._y = 0.5 * ( y + this._y );
+ this._z = 0.5 * ( z + this._z );
+
+ return this;
+
+ }
+
+ var ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
+ ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
+
+ this._w = ( w * ratioA + this._w * ratioB );
+ this._x = ( x * ratioA + this._x * ratioB );
+ this._y = ( y * ratioA + this._y * ratioB );
+ this._z = ( z * ratioA + this._z * ratioB );
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ equals: function ( quaternion ) {
+
+ return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this._x = array[ 0 ];
+ this._y = array[ 1 ];
+ this._z = array[ 2 ];
+ this._w = array[ 3 ];
+
+ this._updateEuler();
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this._x, this._y, this._z, this._w ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Quaternion( this._x, this._y, this._z, this._w );
+
+ }
+
+};
+
+THREE.Quaternion.slerp = function ( qa, qb, qm, t ) {
+
+ return qm.copy( qa ).slerp( qb, t );
+
+}
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author philogb / http://blog.thejit.org/
+ * @author egraether / http://egraether.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ */
+
+THREE.Vector2 = function ( x, y ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+
+};
+
+THREE.Vector2.prototype = {
+
+ constructor: THREE.Vector2,
+
+ set: function ( x, y ) {
+
+ this.x = x;
+ this.y = y;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector2\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector2\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( s ) {
+
+ this.x *= s;
+ this.y *= s;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ if ( scalar !== 0 ) {
+
+ var invScalar = 1 / scalar;
+
+ this.x *= invScalar;
+ this.y *= invScalar;
+
+ } else {
+
+ this.x = 0;
+ this.y = 0;
+
+ }
+
+ return this;
+
+ },
+
+ min: function ( v ) {
+
+ if ( this.x > v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y > v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ if ( this.x < v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y < v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+ if ( this.x < min.x ) {
+
+ this.x = min.x;
+
+ } else if ( this.x > max.x ) {
+
+ this.x = max.x;
+
+ }
+
+ if ( this.y < min.y ) {
+
+ this.y = min.y;
+
+ } else if ( this.y > max.y ) {
+
+ this.y = max.y;
+
+ }
+
+ return this;
+
+ },
+
+ negate: function() {
+
+ return this.multiplyScalar( - 1 );
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y;
+
+ },
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() );
+
+ },
+
+ distanceTo: function ( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ },
+
+ distanceToSquared: function ( v ) {
+
+ var dx = this.x - v.x, dy = this.y - v.y;
+ return dx * dx + dy * dy;
+
+ },
+
+ setLength: function ( l ) {
+
+ var oldLength = this.length();
+
+ if ( oldLength !== 0 && l !== oldLength ) {
+
+ this.multiplyScalar( l / oldLength );
+ }
+
+ return this;
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+
+ return this;
+
+ },
+
+ equals: function( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.x = array[ 0 ];
+ this.y = array[ 1 ];
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this.x, this.y ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Vector2( this.x, this.y );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author *kile / http://kile.stravaganza.org/
+ * @author philogb / http://blog.thejit.org/
+ * @author mikael emtinger / http://gomo.se/
+ * @author egraether / http://egraether.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Vector3 = function ( x, y, z ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+ this.z = z || 0;
+
+};
+
+THREE.Vector3.prototype = {
+
+ constructor: THREE.Vector3,
+
+ set: function ( x, y, z ) {
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+ setZ: function ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ },
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ case 2: return this.z;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector3\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector3\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+
+ return this;
+
+ },
+
+ multiply: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector3\'s .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );
+ return this.multiplyVectors( v, w );
+
+ }
+
+ this.x *= v.x;
+ this.y *= v.y;
+ this.z *= v.z;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+
+ return this;
+
+ },
+
+ multiplyVectors: function ( a, b ) {
+
+ this.x = a.x * b.x;
+ this.y = a.y * b.y;
+ this.z = a.z * b.z;
+
+ return this;
+
+ },
+
+ applyMatrix3: function ( m ) {
+
+ var x = this.x;
+ var y = this.y;
+ var z = this.z;
+
+ var e = m.elements;
+
+ this.x = e[0] * x + e[3] * y + e[6] * z;
+ this.y = e[1] * x + e[4] * y + e[7] * z;
+ this.z = e[2] * x + e[5] * y + e[8] * z;
+
+ return this;
+
+ },
+
+ applyMatrix4: function ( m ) {
+
+ // input: THREE.Matrix4 affine matrix
+
+ var x = this.x, y = this.y, z = this.z;
+
+ var e = m.elements;
+
+ this.x = e[0] * x + e[4] * y + e[8] * z + e[12];
+ this.y = e[1] * x + e[5] * y + e[9] * z + e[13];
+ this.z = e[2] * x + e[6] * y + e[10] * z + e[14];
+
+ return this;
+
+ },
+
+ applyProjection: function ( m ) {
+
+ // input: THREE.Matrix4 projection matrix
+
+ var x = this.x, y = this.y, z = this.z;
+
+ var e = m.elements;
+ var d = 1 / ( e[3] * x + e[7] * y + e[11] * z + e[15] ); // perspective divide
+
+ this.x = ( e[0] * x + e[4] * y + e[8] * z + e[12] ) * d;
+ this.y = ( e[1] * x + e[5] * y + e[9] * z + e[13] ) * d;
+ this.z = ( e[2] * x + e[6] * y + e[10] * z + e[14] ) * d;
+
+ return this;
+
+ },
+
+ applyQuaternion: function ( q ) {
+
+ var x = this.x;
+ var y = this.y;
+ var z = this.z;
+
+ var qx = q.x;
+ var qy = q.y;
+ var qz = q.z;
+ var qw = q.w;
+
+ // calculate quat * vector
+
+ var ix = qw * x + qy * z - qz * y;
+ var iy = qw * y + qz * x - qx * z;
+ var iz = qw * z + qx * y - qy * x;
+ var iw = -qx * x - qy * y - qz * z;
+
+ // calculate result * inverse quat
+
+ this.x = ix * qw + iw * -qx + iy * -qz - iz * -qy;
+ this.y = iy * qw + iw * -qy + iz * -qx - ix * -qz;
+ this.z = iz * qw + iw * -qz + ix * -qy - iy * -qx;
+
+ return this;
+
+ },
+
+ transformDirection: function ( m ) {
+
+ // input: THREE.Matrix4 affine matrix
+ // vector interpreted as a direction
+
+ var x = this.x, y = this.y, z = this.z;
+
+ var e = m.elements;
+
+ this.x = e[0] * x + e[4] * y + e[8] * z;
+ this.y = e[1] * x + e[5] * y + e[9] * z;
+ this.z = e[2] * x + e[6] * y + e[10] * z;
+
+ this.normalize();
+
+ return this;
+
+ },
+
+ divide: function ( v ) {
+
+ this.x /= v.x;
+ this.y /= v.y;
+ this.z /= v.z;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ if ( scalar !== 0 ) {
+
+ var invScalar = 1 / scalar;
+
+ this.x *= invScalar;
+ this.y *= invScalar;
+ this.z *= invScalar;
+
+ } else {
+
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+
+ }
+
+ return this;
+
+ },
+
+ min: function ( v ) {
+
+ if ( this.x > v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y > v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ if ( this.z > v.z ) {
+
+ this.z = v.z;
+
+ }
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ if ( this.x < v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y < v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ if ( this.z < v.z ) {
+
+ this.z = v.z;
+
+ }
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+ if ( this.x < min.x ) {
+
+ this.x = min.x;
+
+ } else if ( this.x > max.x ) {
+
+ this.x = max.x;
+
+ }
+
+ if ( this.y < min.y ) {
+
+ this.y = min.y;
+
+ } else if ( this.y > max.y ) {
+
+ this.y = max.y;
+
+ }
+
+ if ( this.z < min.z ) {
+
+ this.z = min.z;
+
+ } else if ( this.z > max.z ) {
+
+ this.z = max.z;
+
+ }
+
+ return this;
+
+ },
+
+ negate: function () {
+
+ return this.multiplyScalar( - 1 );
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+
+ },
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+ },
+
+ lengthManhattan: function () {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() );
+
+ },
+
+ setLength: function ( l ) {
+
+ var oldLength = this.length();
+
+ if ( oldLength !== 0 && l !== oldLength ) {
+
+ this.multiplyScalar( l / oldLength );
+ }
+
+ return this;
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+
+ return this;
+
+ },
+
+ cross: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector3\'s .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
+ return this.crossVectors( v, w );
+
+ }
+
+ var x = this.x, y = this.y, z = this.z;
+
+ this.x = y * v.z - z * v.y;
+ this.y = z * v.x - x * v.z;
+ this.z = x * v.y - y * v.x;
+
+ return this;
+
+ },
+
+ crossVectors: function ( a, b ) {
+
+ var ax = a.x, ay = a.y, az = a.z;
+ var bx = b.x, by = b.y, bz = b.z;
+
+ this.x = ay * bz - az * by;
+ this.y = az * bx - ax * bz;
+ this.z = ax * by - ay * bx;
+
+ return this;
+
+ },
+
+ angleTo: function ( v ) {
+
+ var theta = this.dot( v ) / ( this.length() * v.length() );
+
+ // clamp, to handle numerical problems
+
+ return Math.acos( THREE.Math.clamp( theta, -1, 1 ) );
+
+ },
+
+ distanceTo: function ( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ },
+
+ distanceToSquared: function ( v ) {
+
+ var dx = this.x - v.x;
+ var dy = this.y - v.y;
+ var dz = this.z - v.z;
+
+ return dx * dx + dy * dy + dz * dz;
+
+ },
+
+ setEulerFromRotationMatrix: function ( m, order ) {
+
+ console.error( "REMOVED: Vector3\'s setEulerFromRotationMatrix has been removed in favor of Euler.setFromRotationMatrix(), please update your code.");
+
+ },
+
+ setEulerFromQuaternion: function ( q, order ) {
+
+ console.error( "REMOVED: Vector3\'s setEulerFromQuaternion: has been removed in favor of Euler.setFromQuaternion(), please update your code.");
+
+ },
+
+ getPositionFromMatrix: function ( m ) {
+
+ this.x = m.elements[12];
+ this.y = m.elements[13];
+ this.z = m.elements[14];
+
+ return this;
+
+ },
+
+ getScaleFromMatrix: function ( m ) {
+
+ var sx = this.set( m.elements[0], m.elements[1], m.elements[2] ).length();
+ var sy = this.set( m.elements[4], m.elements[5], m.elements[6] ).length();
+ var sz = this.set( m.elements[8], m.elements[9], m.elements[10] ).length();
+
+ this.x = sx;
+ this.y = sy;
+ this.z = sz;
+
+ return this;
+ },
+
+ getColumnFromMatrix: function ( index, matrix ) {
+
+ var offset = index * 4;
+
+ var me = matrix.elements;
+
+ this.x = me[ offset ];
+ this.y = me[ offset + 1 ];
+ this.z = me[ offset + 2 ];
+
+ return this;
+
+ },
+
+ equals: function ( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.x = array[ 0 ];
+ this.y = array[ 1 ];
+ this.z = array[ 2 ];
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this.x, this.y, this.z ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Vector3( this.x, this.y, this.z );
+
+ }
+
+};
+
+THREE.extend( THREE.Vector3.prototype, {
+
+ applyEuler: function () {
+
+ var quaternion = new THREE.Quaternion();
+
+ return function ( euler ) {
+
+ if ( euler instanceof THREE.Euler === false ) {
+
+ console.error( 'ERROR: Vector3\'s .applyEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' );
+
+ }
+
+ this.applyQuaternion( quaternion.setFromEuler( euler ) );
+
+ return this;
+
+ };
+
+ }(),
+
+ applyAxisAngle: function () {
+
+ var quaternion = new THREE.Quaternion();
+
+ return function ( axis, angle ) {
+
+ this.applyQuaternion( quaternion.setFromAxisAngle( axis, angle ) );
+
+ return this;
+
+ };
+
+ }(),
+
+ projectOnVector: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( vector ) {
+
+ v1.copy( vector ).normalize();
+ var d = this.dot( v1 );
+ return this.copy( v1 ).multiplyScalar( d );
+
+ };
+
+ }(),
+
+ projectOnPlane: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( planeNormal ) {
+
+ v1.copy( this ).projectOnVector( planeNormal );
+
+ return this.sub( v1 );
+
+ }
+
+ }(),
+
+ reflect: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( vector ) {
+
+ v1.copy( this ).projectOnVector( vector ).multiplyScalar( 2 );
+
+ return this.subVectors( v1, this );
+
+ }
+
+ }()
+
+} );
+
+/**
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author philogb / http://blog.thejit.org/
+ * @author mikael emtinger / http://gomo.se/
+ * @author egraether / http://egraether.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Vector4 = function ( x, y, z, w ) {
+
+ this.x = x || 0;
+ this.y = y || 0;
+ this.z = z || 0;
+ this.w = ( w !== undefined ) ? w : 1;
+
+};
+
+THREE.Vector4.prototype = {
+
+ constructor: THREE.Vector4,
+
+ set: function ( x, y, z, w ) {
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+
+ return this;
+
+ },
+
+ setX: function ( x ) {
+
+ this.x = x;
+
+ return this;
+
+ },
+
+ setY: function ( y ) {
+
+ this.y = y;
+
+ return this;
+
+ },
+
+ setZ: function ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ },
+
+ setW: function ( w ) {
+
+ this.w = w;
+
+ return this;
+
+ },
+
+ setComponent: function ( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ case 3: this.w = value; break;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ getComponent: function ( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ case 2: return this.z;
+ case 3: return this.w;
+ default: throw new Error( "index is out of range: " + index );
+
+ }
+
+ },
+
+ copy: function ( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ this.w = ( v.w !== undefined ) ? v.w : 1;
+
+ return this;
+
+ },
+
+ add: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector4\'s .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+ this.w += v.w;
+
+ return this;
+
+ },
+
+ addScalar: function ( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+ this.w += s;
+
+ return this;
+
+ },
+
+ addVectors: function ( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+ this.w = a.w + b.w;
+
+ return this;
+
+ },
+
+ sub: function ( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'DEPRECATED: Vector4\'s .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+ this.w -= v.w;
+
+ return this;
+
+ },
+
+ subVectors: function ( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+ this.w = a.w - b.w;
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ this.w *= scalar;
+
+ return this;
+
+ },
+
+ applyMatrix4: function ( m ) {
+
+ var x = this.x;
+ var y = this.y;
+ var z = this.z;
+ var w = this.w;
+
+ var e = m.elements;
+
+ this.x = e[0] * x + e[4] * y + e[8] * z + e[12] * w;
+ this.y = e[1] * x + e[5] * y + e[9] * z + e[13] * w;
+ this.z = e[2] * x + e[6] * y + e[10] * z + e[14] * w;
+ this.w = e[3] * x + e[7] * y + e[11] * z + e[15] * w;
+
+ return this;
+
+ },
+
+ divideScalar: function ( scalar ) {
+
+ if ( scalar !== 0 ) {
+
+ var invScalar = 1 / scalar;
+
+ this.x *= invScalar;
+ this.y *= invScalar;
+ this.z *= invScalar;
+ this.w *= invScalar;
+
+ } else {
+
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+ this.w = 1;
+
+ }
+
+ return this;
+
+ },
+
+ setAxisAngleFromQuaternion: function ( q ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
+
+ // q is assumed to be normalized
+
+ this.w = 2 * Math.acos( q.w );
+
+ var s = Math.sqrt( 1 - q.w * q.w );
+
+ if ( s < 0.0001 ) {
+
+ this.x = 1;
+ this.y = 0;
+ this.z = 0;
+
+ } else {
+
+ this.x = q.x / s;
+ this.y = q.y / s;
+ this.z = q.z / s;
+
+ }
+
+ return this;
+
+ },
+
+ setAxisAngleFromRotationMatrix: function ( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ var angle, x, y, z, // variables for result
+ epsilon = 0.01, // margin to allow for rounding errors
+ epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees
+
+ te = m.elements,
+
+ m11 = te[0], m12 = te[4], m13 = te[8],
+ m21 = te[1], m22 = te[5], m23 = te[9],
+ m31 = te[2], m32 = te[6], m33 = te[10];
+
+ if ( ( Math.abs( m12 - m21 ) < epsilon )
+ && ( Math.abs( m13 - m31 ) < epsilon )
+ && ( Math.abs( m23 - m32 ) < epsilon ) ) {
+
+ // singularity found
+ // first check for identity matrix which must have +1 for all terms
+ // in leading diagonal and zero in other terms
+
+ if ( ( Math.abs( m12 + m21 ) < epsilon2 )
+ && ( Math.abs( m13 + m31 ) < epsilon2 )
+ && ( Math.abs( m23 + m32 ) < epsilon2 )
+ && ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
+
+ // this singularity is identity matrix so angle = 0
+
+ this.set( 1, 0, 0, 0 );
+
+ return this; // zero angle, arbitrary axis
+
+ }
+
+ // otherwise this singularity is angle = 180
+
+ angle = Math.PI;
+
+ var xx = ( m11 + 1 ) / 2;
+ var yy = ( m22 + 1 ) / 2;
+ var zz = ( m33 + 1 ) / 2;
+ var xy = ( m12 + m21 ) / 4;
+ var xz = ( m13 + m31 ) / 4;
+ var yz = ( m23 + m32 ) / 4;
+
+ if ( ( xx > yy ) && ( xx > zz ) ) { // m11 is the largest diagonal term
+
+ if ( xx < epsilon ) {
+
+ x = 0;
+ y = 0.707106781;
+ z = 0.707106781;
+
+ } else {
+
+ x = Math.sqrt( xx );
+ y = xy / x;
+ z = xz / x;
+
+ }
+
+ } else if ( yy > zz ) { // m22 is the largest diagonal term
+
+ if ( yy < epsilon ) {
+
+ x = 0.707106781;
+ y = 0;
+ z = 0.707106781;
+
+ } else {
+
+ y = Math.sqrt( yy );
+ x = xy / y;
+ z = yz / y;
+
+ }
+
+ } else { // m33 is the largest diagonal term so base result on this
+
+ if ( zz < epsilon ) {
+
+ x = 0.707106781;
+ y = 0.707106781;
+ z = 0;
+
+ } else {
+
+ z = Math.sqrt( zz );
+ x = xz / z;
+ y = yz / z;
+
+ }
+
+ }
+
+ this.set( x, y, z, angle );
+
+ return this; // return 180 deg rotation
+
+ }
+
+ // as we have reached here there are no singularities so we can handle normally
+
+ var s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 )
+ + ( m13 - m31 ) * ( m13 - m31 )
+ + ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize
+
+ if ( Math.abs( s ) < 0.001 ) s = 1;
+
+ // prevent divide by zero, should not happen if matrix is orthogonal and should be
+ // caught by singularity test above, but I've left it in just in case
+
+ this.x = ( m32 - m23 ) / s;
+ this.y = ( m13 - m31 ) / s;
+ this.z = ( m21 - m12 ) / s;
+ this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );
+
+ return this;
+
+ },
+
+ min: function ( v ) {
+
+ if ( this.x > v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y > v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ if ( this.z > v.z ) {
+
+ this.z = v.z;
+
+ }
+
+ if ( this.w > v.w ) {
+
+ this.w = v.w;
+
+ }
+
+ return this;
+
+ },
+
+ max: function ( v ) {
+
+ if ( this.x < v.x ) {
+
+ this.x = v.x;
+
+ }
+
+ if ( this.y < v.y ) {
+
+ this.y = v.y;
+
+ }
+
+ if ( this.z < v.z ) {
+
+ this.z = v.z;
+
+ }
+
+ if ( this.w < v.w ) {
+
+ this.w = v.w;
+
+ }
+
+ return this;
+
+ },
+
+ clamp: function ( min, max ) {
+
+ // This function assumes min < max, if this assumption isn't true it will not operate correctly
+
+ if ( this.x < min.x ) {
+
+ this.x = min.x;
+
+ } else if ( this.x > max.x ) {
+
+ this.x = max.x;
+
+ }
+
+ if ( this.y < min.y ) {
+
+ this.y = min.y;
+
+ } else if ( this.y > max.y ) {
+
+ this.y = max.y;
+
+ }
+
+ if ( this.z < min.z ) {
+
+ this.z = min.z;
+
+ } else if ( this.z > max.z ) {
+
+ this.z = max.z;
+
+ }
+
+ if ( this.w < min.w ) {
+
+ this.w = min.w;
+
+ } else if ( this.w > max.w ) {
+
+ this.w = max.w;
+
+ }
+
+ return this;
+
+ },
+
+ negate: function() {
+
+ return this.multiplyScalar( -1 );
+
+ },
+
+ dot: function ( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
+
+ },
+
+ lengthSq: function () {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+
+ },
+
+ length: function () {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+
+ },
+
+ lengthManhattan: function () {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );
+
+ },
+
+ normalize: function () {
+
+ return this.divideScalar( this.length() );
+
+ },
+
+ setLength: function ( l ) {
+
+ var oldLength = this.length();
+
+ if ( oldLength !== 0 && l !== oldLength ) {
+
+ this.multiplyScalar( l / oldLength );
+
+ }
+
+ return this;
+
+ },
+
+ lerp: function ( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+ this.w += ( v.w - this.w ) * alpha;
+
+ return this;
+
+ },
+
+ equals: function ( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.x = array[ 0 ];
+ this.y = array[ 1 ];
+ this.z = array[ 2 ];
+ this.w = array[ 3 ];
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this.x, this.y, this.z, this.w ];
+
+ },
+
+ clone: function () {
+
+ return new THREE.Vector4( this.x, this.y, this.z, this.w );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Euler = function ( x, y, z, order ) {
+
+ this._x = x || 0;
+ this._y = y || 0;
+ this._z = z || 0;
+ this._order = order || THREE.Euler.DefaultOrder;
+
+};
+
+THREE.Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];
+
+THREE.Euler.DefaultOrder = 'XYZ';
+
+THREE.Euler.prototype = {
+
+ constructor: THREE.Euler,
+
+ _x: 0, _y: 0, _z: 0, _order: THREE.Euler.DefaultOrder,
+
+ _quaternion: undefined,
+
+ _updateQuaternion: function () {
+
+ if ( this._quaternion !== undefined ) {
+
+ this._quaternion.setFromEuler( this, false );
+
+ }
+
+ },
+
+ get x () {
+
+ return this._x;
+
+ },
+
+ set x ( value ) {
+
+ this._x = value;
+ this._updateQuaternion();
+
+ },
+
+ get y () {
+
+ return this._y;
+
+ },
+
+ set y ( value ) {
+
+ this._y = value;
+ this._updateQuaternion();
+
+ },
+
+ get z () {
+
+ return this._z;
+
+ },
+
+ set z ( value ) {
+
+ this._z = value;
+ this._updateQuaternion();
+
+ },
+
+ get order () {
+
+ return this._order;
+
+ },
+
+ set order ( value ) {
+
+ this._order = value;
+ this._updateQuaternion();
+
+ },
+
+ set: function ( x, y, z, order ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._order = order || this._order;
+
+ this._updateQuaternion();
+
+ return this;
+
+ },
+
+ copy: function ( euler ) {
+
+ this._x = euler._x;
+ this._y = euler._y;
+ this._z = euler._z;
+ this._order = euler._order;
+
+ this._updateQuaternion();
+
+ return this;
+
+ },
+
+ setFromRotationMatrix: function ( m, order ) {
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ // clamp, to handle numerical problems
+
+ function clamp( x ) {
+
+ return Math.min( Math.max( x, -1 ), 1 );
+
+ }
+
+ var te = m.elements;
+ var m11 = te[0], m12 = te[4], m13 = te[8];
+ var m21 = te[1], m22 = te[5], m23 = te[9];
+ var m31 = te[2], m32 = te[6], m33 = te[10];
+
+ order = order || this._order;
+
+ if ( order === 'XYZ' ) {
+
+ this._y = Math.asin( clamp( m13 ) );
+
+ if ( Math.abs( m13 ) < 0.99999 ) {
+
+ this._x = Math.atan2( - m23, m33 );
+ this._z = Math.atan2( - m12, m11 );
+
+ } else {
+
+ this._x = Math.atan2( m32, m22 );
+ this._z = 0;
+
+ }
+
+ } else if ( order === 'YXZ' ) {
+
+ this._x = Math.asin( - clamp( m23 ) );
+
+ if ( Math.abs( m23 ) < 0.99999 ) {
+
+ this._y = Math.atan2( m13, m33 );
+ this._z = Math.atan2( m21, m22 );
+
+ } else {
+
+ this._y = Math.atan2( - m31, m11 );
+ this._z = 0;
+
+ }
+
+ } else if ( order === 'ZXY' ) {
+
+ this._x = Math.asin( clamp( m32 ) );
+
+ if ( Math.abs( m32 ) < 0.99999 ) {
+
+ this._y = Math.atan2( - m31, m33 );
+ this._z = Math.atan2( - m12, m22 );
+
+ } else {
+
+ this._y = 0;
+ this._z = Math.atan2( m21, m11 );
+
+ }
+
+ } else if ( order === 'ZYX' ) {
+
+ this._y = Math.asin( - clamp( m31 ) );
+
+ if ( Math.abs( m31 ) < 0.99999 ) {
+
+ this._x = Math.atan2( m32, m33 );
+ this._z = Math.atan2( m21, m11 );
+
+ } else {
+
+ this._x = 0;
+ this._z = Math.atan2( - m12, m22 );
+
+ }
+
+ } else if ( order === 'YZX' ) {
+
+ this._z = Math.asin( clamp( m21 ) );
+
+ if ( Math.abs( m21 ) < 0.99999 ) {
+
+ this._x = Math.atan2( - m23, m22 );
+ this._y = Math.atan2( - m31, m11 );
+
+ } else {
+
+ this._x = 0;
+ this._y = Math.atan2( m13, m33 );
+
+ }
+
+ } else if ( order === 'XZY' ) {
+
+ this._z = Math.asin( - clamp( m12 ) );
+
+ if ( Math.abs( m12 ) < 0.99999 ) {
+
+ this._x = Math.atan2( m32, m22 );
+ this._y = Math.atan2( m13, m11 );
+
+ } else {
+
+ this._x = Math.atan2( - m23, m33 );
+ this._y = 0;
+
+ }
+
+ } else {
+
+ console.warn( 'WARNING: Euler.setFromRotationMatrix() given unsupported order: ' + order )
+
+ }
+
+ this._order = order;
+
+ this._updateQuaternion();
+
+ return this;
+
+ },
+
+ setFromQuaternion: function ( q, order, update ) {
+
+ // q is assumed to be normalized
+
+ // clamp, to handle numerical problems
+
+ function clamp( x ) {
+
+ return Math.min( Math.max( x, -1 ), 1 );
+
+ }
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/content/SpinCalc.m
+
+ var sqx = q.x * q.x;
+ var sqy = q.y * q.y;
+ var sqz = q.z * q.z;
+ var sqw = q.w * q.w;
+
+ order = order || this._order;
+
+ if ( order === 'XYZ' ) {
+
+ this._x = Math.atan2( 2 * ( q.x * q.w - q.y * q.z ), ( sqw - sqx - sqy + sqz ) );
+ this._y = Math.asin( clamp( 2 * ( q.x * q.z + q.y * q.w ) ) );
+ this._z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw + sqx - sqy - sqz ) );
+
+ } else if ( order === 'YXZ' ) {
+
+ this._x = Math.asin( clamp( 2 * ( q.x * q.w - q.y * q.z ) ) );
+ this._y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw - sqx - sqy + sqz ) );
+ this._z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw - sqx + sqy - sqz ) );
+
+ } else if ( order === 'ZXY' ) {
+
+ this._x = Math.asin( clamp( 2 * ( q.x * q.w + q.y * q.z ) ) );
+ this._y = Math.atan2( 2 * ( q.y * q.w - q.z * q.x ), ( sqw - sqx - sqy + sqz ) );
+ this._z = Math.atan2( 2 * ( q.z * q.w - q.x * q.y ), ( sqw - sqx + sqy - sqz ) );
+
+ } else if ( order === 'ZYX' ) {
+
+ this._x = Math.atan2( 2 * ( q.x * q.w + q.z * q.y ), ( sqw - sqx - sqy + sqz ) );
+ this._y = Math.asin( clamp( 2 * ( q.y * q.w - q.x * q.z ) ) );
+ this._z = Math.atan2( 2 * ( q.x * q.y + q.z * q.w ), ( sqw + sqx - sqy - sqz ) );
+
+ } else if ( order === 'YZX' ) {
+
+ this._x = Math.atan2( 2 * ( q.x * q.w - q.z * q.y ), ( sqw - sqx + sqy - sqz ) );
+ this._y = Math.atan2( 2 * ( q.y * q.w - q.x * q.z ), ( sqw + sqx - sqy - sqz ) );
+ this._z = Math.asin( clamp( 2 * ( q.x * q.y + q.z * q.w ) ) );
+
+ } else if ( order === 'XZY' ) {
+
+ this._x = Math.atan2( 2 * ( q.x * q.w + q.y * q.z ), ( sqw - sqx + sqy - sqz ) );
+ this._y = Math.atan2( 2 * ( q.x * q.z + q.y * q.w ), ( sqw + sqx - sqy - sqz ) );
+ this._z = Math.asin( clamp( 2 * ( q.z * q.w - q.x * q.y ) ) );
+
+ } else {
+
+ console.warn( 'WARNING: Euler.setFromQuaternion() given unsupported order: ' + order )
+
+ }
+
+ this._order = order;
+
+ if ( update !== false ) this._updateQuaternion();
+
+ return this;
+
+ },
+
+ reorder: function () {
+
+ // WARNING: this discards revolution information -bhouston
+
+ var q = new THREE.Quaternion();
+
+ return function ( newOrder ) {
+
+ q.setFromEuler( this );
+ this.setFromQuaternion( q, newOrder );
+
+ };
+
+
+ }(),
+
+ fromArray: function ( array ) {
+
+ this._x = array[ 0 ];
+ this._y = array[ 1 ];
+ this._z = array[ 2 ];
+ if ( array[ 3 ] !== undefined ) this._order = array[ 3 ];
+
+ this._updateQuaternion();
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ return [ this._x, this._y, this._z, this._order ];
+
+ },
+
+ equals: function ( euler ) {
+
+ return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Euler( this._x, this._y, this._z, this._order );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Line3 = function ( start, end ) {
+
+ this.start = ( start !== undefined ) ? start : new THREE.Vector3();
+ this.end = ( end !== undefined ) ? end : new THREE.Vector3();
+
+};
+
+THREE.Line3.prototype = {
+
+ constructor: THREE.Line3,
+
+ set: function ( start, end ) {
+
+ this.start.copy( start );
+ this.end.copy( end );
+
+ return this;
+
+ },
+
+ copy: function ( line ) {
+
+ this.start.copy( line.start );
+ this.end.copy( line.end );
+
+ return this;
+
+ },
+
+ center: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.addVectors( this.start, this.end ).multiplyScalar( 0.5 );
+
+ },
+
+ delta: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.subVectors( this.end, this.start );
+
+ },
+
+ distanceSq: function () {
+
+ return this.start.distanceToSquared( this.end );
+
+ },
+
+ distance: function () {
+
+ return this.start.distanceTo( this.end );
+
+ },
+
+ at: function ( t, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ return this.delta( result ).multiplyScalar( t ).add( this.start );
+
+ },
+
+ closestPointToPointParameter: function() {
+
+ var startP = new THREE.Vector3();
+ var startEnd = new THREE.Vector3();
+
+ return function ( point, clampToLine ) {
+
+ startP.subVectors( point, this.start );
+ startEnd.subVectors( this.end, this.start );
+
+ var startEnd2 = startEnd.dot( startEnd );
+ var startEnd_startP = startEnd.dot( startP );
+
+ var t = startEnd_startP / startEnd2;
+
+ if ( clampToLine ) {
+
+ t = THREE.Math.clamp( t, 0, 1 );
+
+ }
+
+ return t;
+
+ };
+
+ }(),
+
+ closestPointToPoint: function ( point, clampToLine, optionalTarget ) {
+
+ var t = this.closestPointToPointParameter( point, clampToLine );
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ return this.delta( result ).multiplyScalar( t ).add( this.start );
+
+ },
+
+ applyMatrix4: function ( matrix ) {
+
+ this.start.applyMatrix4( matrix );
+ this.end.applyMatrix4( matrix );
+
+ return this;
+
+ },
+
+ equals: function ( line ) {
+
+ return line.start.equals( this.start ) && line.end.equals( this.end );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Line3().copy( this );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Box2 = function ( min, max ) {
+
+ this.min = ( min !== undefined ) ? min : new THREE.Vector2( Infinity, Infinity );
+ this.max = ( max !== undefined ) ? max : new THREE.Vector2( -Infinity, -Infinity );
+
+};
+
+THREE.Box2.prototype = {
+
+ constructor: THREE.Box2,
+
+ set: function ( min, max ) {
+
+ this.min.copy( min );
+ this.max.copy( max );
+
+ return this;
+
+ },
+
+ setFromPoints: function ( points ) {
+
+ if ( points.length > 0 ) {
+
+ var point = points[ 0 ];
+
+ this.min.copy( point );
+ this.max.copy( point );
+
+ for ( var i = 1, il = points.length; i < il; i ++ ) {
+
+ point = points[ i ];
+
+ if ( point.x < this.min.x ) {
+
+ this.min.x = point.x;
+
+ } else if ( point.x > this.max.x ) {
+
+ this.max.x = point.x;
+
+ }
+
+ if ( point.y < this.min.y ) {
+
+ this.min.y = point.y;
+
+ } else if ( point.y > this.max.y ) {
+
+ this.max.y = point.y;
+
+ }
+
+ }
+
+ } else {
+
+ this.makeEmpty();
+
+ }
+
+ return this;
+
+ },
+
+ setFromCenterAndSize: function () {
+
+ var v1 = new THREE.Vector2();
+
+ return function ( center, size ) {
+
+ var halfSize = v1.copy( size ).multiplyScalar( 0.5 );
+ this.min.copy( center ).sub( halfSize );
+ this.max.copy( center ).add( halfSize );
+
+ return this;
+
+ };
+
+ }(),
+
+ copy: function ( box ) {
+
+ this.min.copy( box.min );
+ this.max.copy( box.max );
+
+ return this;
+
+ },
+
+ makeEmpty: function () {
+
+ this.min.x = this.min.y = Infinity;
+ this.max.x = this.max.y = -Infinity;
+
+ return this;
+
+ },
+
+ empty: function () {
+
+ // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+ return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y );
+
+ },
+
+ center: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector2();
+ return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+ },
+
+ size: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector2();
+ return result.subVectors( this.max, this.min );
+
+ },
+
+ expandByPoint: function ( point ) {
+
+ this.min.min( point );
+ this.max.max( point );
+
+ return this;
+ },
+
+ expandByVector: function ( vector ) {
+
+ this.min.sub( vector );
+ this.max.add( vector );
+
+ return this;
+ },
+
+ expandByScalar: function ( scalar ) {
+
+ this.min.addScalar( -scalar );
+ this.max.addScalar( scalar );
+
+ return this;
+ },
+
+ containsPoint: function ( point ) {
+
+ if ( point.x < this.min.x || point.x > this.max.x ||
+ point.y < this.min.y || point.y > this.max.y ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ },
+
+ containsBox: function ( box ) {
+
+ if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) &&
+ ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) ) {
+
+ return true;
+
+ }
+
+ return false;
+
+ },
+
+ getParameter: function ( point ) {
+
+ // This can potentially have a divide by zero if the box
+ // has a size dimension of 0.
+
+ return new THREE.Vector2(
+ ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+ ( point.y - this.min.y ) / ( this.max.y - this.min.y )
+ );
+
+ },
+
+ isIntersectionBox: function ( box ) {
+
+ // using 6 splitting planes to rule out intersections.
+
+ if ( box.max.x < this.min.x || box.min.x > this.max.x ||
+ box.max.y < this.min.y || box.min.y > this.max.y ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ },
+
+ clampPoint: function ( point, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector2();
+ return result.copy( point ).clamp( this.min, this.max );
+
+ },
+
+ distanceToPoint: function () {
+
+ var v1 = new THREE.Vector2();
+
+ return function ( point ) {
+
+ var clampedPoint = v1.copy( point ).clamp( this.min, this.max );
+ return clampedPoint.sub( point ).length();
+
+ };
+
+ }(),
+
+ intersect: function ( box ) {
+
+ this.min.max( box.min );
+ this.max.min( box.max );
+
+ return this;
+
+ },
+
+ union: function ( box ) {
+
+ this.min.min( box.min );
+ this.max.max( box.max );
+
+ return this;
+
+ },
+
+ translate: function ( offset ) {
+
+ this.min.add( offset );
+ this.max.add( offset );
+
+ return this;
+
+ },
+
+ equals: function ( box ) {
+
+ return box.min.equals( this.min ) && box.max.equals( this.max );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Box2().copy( this );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Box3 = function ( min, max ) {
+
+ this.min = ( min !== undefined ) ? min : new THREE.Vector3( Infinity, Infinity, Infinity );
+ this.max = ( max !== undefined ) ? max : new THREE.Vector3( -Infinity, -Infinity, -Infinity );
+
+};
+
+THREE.Box3.prototype = {
+
+ constructor: THREE.Box3,
+
+ set: function ( min, max ) {
+
+ this.min.copy( min );
+ this.max.copy( max );
+
+ return this;
+
+ },
+
+ addPoint: function ( point ) {
+
+ if ( point.x < this.min.x ) {
+
+ this.min.x = point.x;
+
+ } else if ( point.x > this.max.x ) {
+
+ this.max.x = point.x;
+
+ }
+
+ if ( point.y < this.min.y ) {
+
+ this.min.y = point.y;
+
+ } else if ( point.y > this.max.y ) {
+
+ this.max.y = point.y;
+
+ }
+
+ if ( point.z < this.min.z ) {
+
+ this.min.z = point.z;
+
+ } else if ( point.z > this.max.z ) {
+
+ this.max.z = point.z;
+
+ }
+
+ },
+
+ setFromPoints: function ( points ) {
+
+ if ( points.length > 0 ) {
+
+ var point = points[ 0 ];
+
+ this.min.copy( point );
+ this.max.copy( point );
+
+ for ( var i = 1, il = points.length; i < il; i ++ ) {
+
+ this.addPoint( points[ i ] )
+
+ }
+
+ } else {
+
+ this.makeEmpty();
+
+ }
+
+ return this;
+
+ },
+
+ setFromCenterAndSize: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( center, size ) {
+
+ var halfSize = v1.copy( size ).multiplyScalar( 0.5 );
+
+ this.min.copy( center ).sub( halfSize );
+ this.max.copy( center ).add( halfSize );
+
+ return this;
+
+ };
+
+ }(),
+
+ setFromObject: function() {
+
+ // Computes the world-axis-aligned bounding box of an object (including its children),
+ // accounting for both the object's, and childrens', world transforms
+
+ var v1 = new THREE.Vector3();
+
+ return function( object ) {
+
+ var scope = this;
+
+ object.updateMatrixWorld( true );
+
+ this.makeEmpty();
+
+ object.traverse( function ( node ) {
+
+ if ( node.geometry !== undefined && node.geometry.vertices !== undefined ) {
+
+ var vertices = node.geometry.vertices;
+
+ for ( var i = 0, il = vertices.length; i < il; i++ ) {
+
+ v1.copy( vertices[ i ] );
+
+ v1.applyMatrix4( node.matrixWorld );
+
+ scope.expandByPoint( v1 );
+
+ }
+
+ }
+
+ } );
+
+ return this;
+
+ };
+
+ }(),
+
+ copy: function ( box ) {
+
+ this.min.copy( box.min );
+ this.max.copy( box.max );
+
+ return this;
+
+ },
+
+ makeEmpty: function () {
+
+ this.min.x = this.min.y = this.min.z = Infinity;
+ this.max.x = this.max.y = this.max.z = -Infinity;
+
+ return this;
+
+ },
+
+ empty: function () {
+
+ // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+ return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );
+
+ },
+
+ center: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+ },
+
+ size: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.subVectors( this.max, this.min );
+
+ },
+
+ expandByPoint: function ( point ) {
+
+ this.min.min( point );
+ this.max.max( point );
+
+ return this;
+
+ },
+
+ expandByVector: function ( vector ) {
+
+ this.min.sub( vector );
+ this.max.add( vector );
+
+ return this;
+
+ },
+
+ expandByScalar: function ( scalar ) {
+
+ this.min.addScalar( -scalar );
+ this.max.addScalar( scalar );
+
+ return this;
+
+ },
+
+ containsPoint: function ( point ) {
+
+ if ( point.x < this.min.x || point.x > this.max.x ||
+ point.y < this.min.y || point.y > this.max.y ||
+ point.z < this.min.z || point.z > this.max.z ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ },
+
+ containsBox: function ( box ) {
+
+ if ( ( this.min.x <= box.min.x ) && ( box.max.x <= this.max.x ) &&
+ ( this.min.y <= box.min.y ) && ( box.max.y <= this.max.y ) &&
+ ( this.min.z <= box.min.z ) && ( box.max.z <= this.max.z ) ) {
+
+ return true;
+
+ }
+
+ return false;
+
+ },
+
+ getParameter: function ( point ) {
+
+ // This can potentially have a divide by zero if the box
+ // has a size dimension of 0.
+
+ return new THREE.Vector3(
+ ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+ ( point.y - this.min.y ) / ( this.max.y - this.min.y ),
+ ( point.z - this.min.z ) / ( this.max.z - this.min.z )
+ );
+
+ },
+
+ isIntersectionBox: function ( box ) {
+
+ // using 6 splitting planes to rule out intersections.
+
+ if ( box.max.x < this.min.x || box.min.x > this.max.x ||
+ box.max.y < this.min.y || box.min.y > this.max.y ||
+ box.max.z < this.min.z || box.min.z > this.max.z ) {
+
+ return false;
+
+ }
+
+ return true;
+
+ },
+
+ clampPoint: function ( point, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.copy( point ).clamp( this.min, this.max );
+
+ },
+
+ distanceToPoint: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( point ) {
+
+ var clampedPoint = v1.copy( point ).clamp( this.min, this.max );
+ return clampedPoint.sub( point ).length();
+
+ };
+
+ }(),
+
+ getBoundingSphere: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Sphere();
+
+ result.center = this.center();
+ result.radius = this.size( v1 ).length() * 0.5;
+
+ return result;
+
+ };
+
+ }(),
+
+ intersect: function ( box ) {
+
+ this.min.max( box.min );
+ this.max.min( box.max );
+
+ return this;
+
+ },
+
+ union: function ( box ) {
+
+ this.min.min( box.min );
+ this.max.max( box.max );
+
+ return this;
+
+ },
+
+ applyMatrix4: function() {
+
+ var points = [
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3(),
+ new THREE.Vector3()
+ ];
+
+ return function ( matrix ) {
+
+ // NOTE: I am using a binary pattern to specify all 2^3 combinations below
+ points[0].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000
+ points[1].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001
+ points[2].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010
+ points[3].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011
+ points[4].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100
+ points[5].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101
+ points[6].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110
+ points[7].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111
+
+ this.makeEmpty();
+ this.setFromPoints( points );
+
+ return this;
+
+ };
+
+ }(),
+
+ translate: function ( offset ) {
+
+ this.min.add( offset );
+ this.max.add( offset );
+
+ return this;
+
+ },
+
+ equals: function ( box ) {
+
+ return box.min.equals( this.min ) && box.max.equals( this.max );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Box3().copy( this );
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Matrix3 = function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+ this.elements = new Float32Array(9);
+
+ this.set(
+
+ ( n11 !== undefined ) ? n11 : 1, n12 || 0, n13 || 0,
+ n21 || 0, ( n22 !== undefined ) ? n22 : 1, n23 || 0,
+ n31 || 0, n32 || 0, ( n33 !== undefined ) ? n33 : 1
+
+ );
+};
+
+THREE.Matrix3.prototype = {
+
+ constructor: THREE.Matrix3,
+
+ set: function ( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+ var te = this.elements;
+
+ te[0] = n11; te[3] = n12; te[6] = n13;
+ te[1] = n21; te[4] = n22; te[7] = n23;
+ te[2] = n31; te[5] = n32; te[8] = n33;
+
+ return this;
+
+ },
+
+ identity: function () {
+
+ this.set(
+
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ copy: function ( m ) {
+
+ var me = m.elements;
+
+ this.set(
+
+ me[0], me[3], me[6],
+ me[1], me[4], me[7],
+ me[2], me[5], me[8]
+
+ );
+
+ return this;
+
+ },
+
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Matrix3\'s .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
+ return vector.applyMatrix3( this );
+
+ },
+
+ multiplyVector3Array: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( a ) {
+
+ for ( var i = 0, il = a.length; i < il; i += 3 ) {
+
+ v1.x = a[ i ];
+ v1.y = a[ i + 1 ];
+ v1.z = a[ i + 2 ];
+
+ v1.applyMatrix3(this);
+
+ a[ i ] = v1.x;
+ a[ i + 1 ] = v1.y;
+ a[ i + 2 ] = v1.z;
+
+ }
+
+ return a;
+
+ };
+
+ }(),
+
+ multiplyScalar: function ( s ) {
+
+ var te = this.elements;
+
+ te[0] *= s; te[3] *= s; te[6] *= s;
+ te[1] *= s; te[4] *= s; te[7] *= s;
+ te[2] *= s; te[5] *= s; te[8] *= s;
+
+ return this;
+
+ },
+
+ determinant: function () {
+
+ var te = this.elements;
+
+ var a = te[0], b = te[1], c = te[2],
+ d = te[3], e = te[4], f = te[5],
+ g = te[6], h = te[7], i = te[8];
+
+ return a*e*i - a*f*h - b*d*i + b*f*g + c*d*h - c*e*g;
+
+ },
+
+ getInverse: function ( matrix, throwOnInvertible ) {
+
+ // input: THREE.Matrix4
+ // ( based on http://code.google.com/p/webgl-mjs/ )
+
+ var me = matrix.elements;
+ var te = this.elements;
+
+ te[ 0 ] = me[10] * me[5] - me[6] * me[9];
+ te[ 1 ] = - me[10] * me[1] + me[2] * me[9];
+ te[ 2 ] = me[6] * me[1] - me[2] * me[5];
+ te[ 3 ] = - me[10] * me[4] + me[6] * me[8];
+ te[ 4 ] = me[10] * me[0] - me[2] * me[8];
+ te[ 5 ] = - me[6] * me[0] + me[2] * me[4];
+ te[ 6 ] = me[9] * me[4] - me[5] * me[8];
+ te[ 7 ] = - me[9] * me[0] + me[1] * me[8];
+ te[ 8 ] = me[5] * me[0] - me[1] * me[4];
+
+ var det = me[ 0 ] * te[ 0 ] + me[ 1 ] * te[ 3 ] + me[ 2 ] * te[ 6 ];
+
+ // no inverse
+
+ if ( det === 0 ) {
+
+ var msg = "Matrix3.getInverse(): can't invert matrix, determinant is 0";
+
+ if ( throwOnInvertible || false ) {
+
+ throw new Error( msg );
+
+ } else {
+
+ console.warn( msg );
+
+ }
+
+ this.identity();
+
+ return this;
+
+ }
+
+ this.multiplyScalar( 1.0 / det );
+
+ return this;
+
+ },
+
+ transpose: function () {
+
+ var tmp, m = this.elements;
+
+ tmp = m[1]; m[1] = m[3]; m[3] = tmp;
+ tmp = m[2]; m[2] = m[6]; m[6] = tmp;
+ tmp = m[5]; m[5] = m[7]; m[7] = tmp;
+
+ return this;
+
+ },
+
+ getNormalMatrix: function ( m ) {
+
+ // input: THREE.Matrix4
+
+ this.getInverse( m ).transpose();
+
+ return this;
+
+ },
+
+ transposeIntoArray: function ( r ) {
+
+ var m = this.elements;
+
+ r[ 0 ] = m[ 0 ];
+ r[ 1 ] = m[ 3 ];
+ r[ 2 ] = m[ 6 ];
+ r[ 3 ] = m[ 1 ];
+ r[ 4 ] = m[ 4 ];
+ r[ 5 ] = m[ 7 ];
+ r[ 6 ] = m[ 2 ];
+ r[ 7 ] = m[ 5 ];
+ r[ 8 ] = m[ 8 ];
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ var te = this.elements;
+
+ return new THREE.Matrix3(
+
+ te[0], te[3], te[6],
+ te[1], te[4], te[7],
+ te[2], te[5], te[8]
+
+ );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author philogb / http://blog.thejit.org/
+ * @author jordi_ros / http://plattsoft.com
+ * @author D1plo1d / http://github.com/D1plo1d
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author timknip / http://www.floorplanner.com/
+ * @author bhouston / http://exocortex.com
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+
+THREE.Matrix4 = function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+ this.elements = new Float32Array( 16 );
+
+ // TODO: if n11 is undefined, then just set to identity, otherwise copy all other values into matrix
+ // we should not support semi specification of Matrix4, it is just weird.
+
+ var te = this.elements;
+
+ te[0] = ( n11 !== undefined ) ? n11 : 1; te[4] = n12 || 0; te[8] = n13 || 0; te[12] = n14 || 0;
+ te[1] = n21 || 0; te[5] = ( n22 !== undefined ) ? n22 : 1; te[9] = n23 || 0; te[13] = n24 || 0;
+ te[2] = n31 || 0; te[6] = n32 || 0; te[10] = ( n33 !== undefined ) ? n33 : 1; te[14] = n34 || 0;
+ te[3] = n41 || 0; te[7] = n42 || 0; te[11] = n43 || 0; te[15] = ( n44 !== undefined ) ? n44 : 1;
+
+};
+
+THREE.Matrix4.prototype = {
+
+ constructor: THREE.Matrix4,
+
+ set: function ( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+ var te = this.elements;
+
+ te[0] = n11; te[4] = n12; te[8] = n13; te[12] = n14;
+ te[1] = n21; te[5] = n22; te[9] = n23; te[13] = n24;
+ te[2] = n31; te[6] = n32; te[10] = n33; te[14] = n34;
+ te[3] = n41; te[7] = n42; te[11] = n43; te[15] = n44;
+
+ return this;
+
+ },
+
+ identity: function () {
+
+ this.set(
+
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ copy: function ( m ) {
+
+ this.elements.set( m.elements );
+
+ return this;
+
+ },
+
+ extractPosition: function ( m ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .extractPosition() has been renamed to .copyPosition().' );
+ return this.copyPosition( m );
+
+ },
+
+ copyPosition: function ( m ) {
+
+ var te = this.elements;
+ var me = m.elements;
+
+ te[12] = me[12];
+ te[13] = me[13];
+ te[14] = me[14];
+
+ return this;
+
+ },
+
+ extractRotation: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( m ) {
+
+ var te = this.elements;
+ var me = m.elements;
+
+ var scaleX = 1 / v1.set( me[0], me[1], me[2] ).length();
+ var scaleY = 1 / v1.set( me[4], me[5], me[6] ).length();
+ var scaleZ = 1 / v1.set( me[8], me[9], me[10] ).length();
+
+ te[0] = me[0] * scaleX;
+ te[1] = me[1] * scaleX;
+ te[2] = me[2] * scaleX;
+
+ te[4] = me[4] * scaleY;
+ te[5] = me[5] * scaleY;
+ te[6] = me[6] * scaleY;
+
+ te[8] = me[8] * scaleZ;
+ te[9] = me[9] * scaleZ;
+ te[10] = me[10] * scaleZ;
+
+ return this;
+
+ };
+
+ }(),
+
+ makeRotationFromEuler: function ( euler ) {
+
+ if ( euler instanceof THREE.Euler === false ) {
+
+ console.error( 'ERROR: Matrix\'s .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order. Please update your code.' );
+
+ }
+
+ var te = this.elements;
+
+ var x = euler.x, y = euler.y, z = euler.z;
+ var a = Math.cos( x ), b = Math.sin( x );
+ var c = Math.cos( y ), d = Math.sin( y );
+ var e = Math.cos( z ), f = Math.sin( z );
+
+ if ( euler.order === 'XYZ' ) {
+
+ var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+ te[0] = c * e;
+ te[4] = - c * f;
+ te[8] = d;
+
+ te[1] = af + be * d;
+ te[5] = ae - bf * d;
+ te[9] = - b * c;
+
+ te[2] = bf - ae * d;
+ te[6] = be + af * d;
+ te[10] = a * c;
+
+ } else if ( euler.order === 'YXZ' ) {
+
+ var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+ te[0] = ce + df * b;
+ te[4] = de * b - cf;
+ te[8] = a * d;
+
+ te[1] = a * f;
+ te[5] = a * e;
+ te[9] = - b;
+
+ te[2] = cf * b - de;
+ te[6] = df + ce * b;
+ te[10] = a * c;
+
+ } else if ( euler.order === 'ZXY' ) {
+
+ var ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+ te[0] = ce - df * b;
+ te[4] = - a * f;
+ te[8] = de + cf * b;
+
+ te[1] = cf + de * b;
+ te[5] = a * e;
+ te[9] = df - ce * b;
+
+ te[2] = - a * d;
+ te[6] = b;
+ te[10] = a * c;
+
+ } else if ( euler.order === 'ZYX' ) {
+
+ var ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+ te[0] = c * e;
+ te[4] = be * d - af;
+ te[8] = ae * d + bf;
+
+ te[1] = c * f;
+ te[5] = bf * d + ae;
+ te[9] = af * d - be;
+
+ te[2] = - d;
+ te[6] = b * c;
+ te[10] = a * c;
+
+ } else if ( euler.order === 'YZX' ) {
+
+ var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+ te[0] = c * e;
+ te[4] = bd - ac * f;
+ te[8] = bc * f + ad;
+
+ te[1] = f;
+ te[5] = a * e;
+ te[9] = - b * e;
+
+ te[2] = - d * e;
+ te[6] = ad * f + bc;
+ te[10] = ac - bd * f;
+
+ } else if ( euler.order === 'XZY' ) {
+
+ var ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+ te[0] = c * e;
+ te[4] = - f;
+ te[8] = d * e;
+
+ te[1] = ac * f + bd;
+ te[5] = a * e;
+ te[9] = ad * f - bc;
+
+ te[2] = bc * f - ad;
+ te[6] = b * e;
+ te[10] = bd * f + ac;
+
+ }
+
+ // last column
+ te[3] = 0;
+ te[7] = 0;
+ te[11] = 0;
+
+ // bottom row
+ te[12] = 0;
+ te[13] = 0;
+ te[14] = 0;
+ te[15] = 1;
+
+ return this;
+
+ },
+
+ setRotationFromQuaternion: function ( q ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .setRotationFromQuaternion() has been deprecated in favor of makeRotationFromQuaternion. Please update your code.' );
+
+ return this.makeRotationFromQuaternion( q );
+
+ },
+
+ makeRotationFromQuaternion: function ( q ) {
+
+ var te = this.elements;
+
+ var x = q.x, y = q.y, z = q.z, w = q.w;
+ var x2 = x + x, y2 = y + y, z2 = z + z;
+ var xx = x * x2, xy = x * y2, xz = x * z2;
+ var yy = y * y2, yz = y * z2, zz = z * z2;
+ var wx = w * x2, wy = w * y2, wz = w * z2;
+
+ te[0] = 1 - ( yy + zz );
+ te[4] = xy - wz;
+ te[8] = xz + wy;
+
+ te[1] = xy + wz;
+ te[5] = 1 - ( xx + zz );
+ te[9] = yz - wx;
+
+ te[2] = xz - wy;
+ te[6] = yz + wx;
+ te[10] = 1 - ( xx + yy );
+
+ // last column
+ te[3] = 0;
+ te[7] = 0;
+ te[11] = 0;
+
+ // bottom row
+ te[12] = 0;
+ te[13] = 0;
+ te[14] = 0;
+ te[15] = 1;
+
+ return this;
+
+ },
+
+ lookAt: function() {
+
+ var x = new THREE.Vector3();
+ var y = new THREE.Vector3();
+ var z = new THREE.Vector3();
+
+ return function ( eye, target, up ) {
+
+ var te = this.elements;
+
+ z.subVectors( eye, target ).normalize();
+
+ if ( z.length() === 0 ) {
+
+ z.z = 1;
+
+ }
+
+ x.crossVectors( up, z ).normalize();
+
+ if ( x.length() === 0 ) {
+
+ z.x += 0.0001;
+ x.crossVectors( up, z ).normalize();
+
+ }
+
+ y.crossVectors( z, x );
+
+
+ te[0] = x.x; te[4] = y.x; te[8] = z.x;
+ te[1] = x.y; te[5] = y.y; te[9] = z.y;
+ te[2] = x.z; te[6] = y.z; te[10] = z.z;
+
+ return this;
+
+ };
+
+ }(),
+
+ multiply: function ( m, n ) {
+
+ if ( n !== undefined ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
+ return this.multiplyMatrices( m, n );
+
+ }
+
+ return this.multiplyMatrices( this, m );
+
+ },
+
+ multiplyMatrices: function ( a, b ) {
+
+ var ae = a.elements;
+ var be = b.elements;
+ var te = this.elements;
+
+ var a11 = ae[0], a12 = ae[4], a13 = ae[8], a14 = ae[12];
+ var a21 = ae[1], a22 = ae[5], a23 = ae[9], a24 = ae[13];
+ var a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14];
+ var a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15];
+
+ var b11 = be[0], b12 = be[4], b13 = be[8], b14 = be[12];
+ var b21 = be[1], b22 = be[5], b23 = be[9], b24 = be[13];
+ var b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14];
+ var b41 = be[3], b42 = be[7], b43 = be[11], b44 = be[15];
+
+ te[0] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
+ te[4] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
+ te[8] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
+ te[12] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
+
+ te[1] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
+ te[5] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
+ te[9] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
+ te[13] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
+
+ te[2] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
+ te[6] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
+ te[10] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
+ te[14] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
+
+ te[3] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
+ te[7] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
+ te[11] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
+ te[15] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
+
+ return this;
+
+ },
+
+ multiplyToArray: function ( a, b, r ) {
+
+ var te = this.elements;
+
+ this.multiplyMatrices( a, b );
+
+ r[ 0 ] = te[0]; r[ 1 ] = te[1]; r[ 2 ] = te[2]; r[ 3 ] = te[3];
+ r[ 4 ] = te[4]; r[ 5 ] = te[5]; r[ 6 ] = te[6]; r[ 7 ] = te[7];
+ r[ 8 ] = te[8]; r[ 9 ] = te[9]; r[ 10 ] = te[10]; r[ 11 ] = te[11];
+ r[ 12 ] = te[12]; r[ 13 ] = te[13]; r[ 14 ] = te[14]; r[ 15 ] = te[15];
+
+ return this;
+
+ },
+
+ multiplyScalar: function ( s ) {
+
+ var te = this.elements;
+
+ te[0] *= s; te[4] *= s; te[8] *= s; te[12] *= s;
+ te[1] *= s; te[5] *= s; te[9] *= s; te[13] *= s;
+ te[2] *= s; te[6] *= s; te[10] *= s; te[14] *= s;
+ te[3] *= s; te[7] *= s; te[11] *= s; te[15] *= s;
+
+ return this;
+
+ },
+
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.' );
+ return vector.applyProjection( this );
+
+ },
+
+ multiplyVector4: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+ return vector.applyMatrix4( this );
+
+ },
+
+ multiplyVector3Array: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( a ) {
+
+ for ( var i = 0, il = a.length; i < il; i += 3 ) {
+
+ v1.x = a[ i ];
+ v1.y = a[ i + 1 ];
+ v1.z = a[ i + 2 ];
+
+ v1.applyProjection( this );
+
+ a[ i ] = v1.x;
+ a[ i + 1 ] = v1.y;
+ a[ i + 2 ] = v1.z;
+
+ }
+
+ return a;
+
+ };
+
+ }(),
+
+ rotateAxis: function ( v ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );
+
+ v.transformDirection( this );
+
+ },
+
+ crossVector: function ( vector ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+ return vector.applyMatrix4( this );
+
+ },
+
+ determinant: function () {
+
+ var te = this.elements;
+
+ var n11 = te[0], n12 = te[4], n13 = te[8], n14 = te[12];
+ var n21 = te[1], n22 = te[5], n23 = te[9], n24 = te[13];
+ var n31 = te[2], n32 = te[6], n33 = te[10], n34 = te[14];
+ var n41 = te[3], n42 = te[7], n43 = te[11], n44 = te[15];
+
+ //TODO: make this more efficient
+ //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
+
+ return (
+ n41 * (
+ +n14 * n23 * n32
+ -n13 * n24 * n32
+ -n14 * n22 * n33
+ +n12 * n24 * n33
+ +n13 * n22 * n34
+ -n12 * n23 * n34
+ ) +
+ n42 * (
+ +n11 * n23 * n34
+ -n11 * n24 * n33
+ +n14 * n21 * n33
+ -n13 * n21 * n34
+ +n13 * n24 * n31
+ -n14 * n23 * n31
+ ) +
+ n43 * (
+ +n11 * n24 * n32
+ -n11 * n22 * n34
+ -n14 * n21 * n32
+ +n12 * n21 * n34
+ +n14 * n22 * n31
+ -n12 * n24 * n31
+ ) +
+ n44 * (
+ -n13 * n22 * n31
+ -n11 * n23 * n32
+ +n11 * n22 * n33
+ +n13 * n21 * n32
+ -n12 * n21 * n33
+ +n12 * n23 * n31
+ )
+
+ );
+
+ },
+
+ transpose: function () {
+
+ var te = this.elements;
+ var tmp;
+
+ tmp = te[1]; te[1] = te[4]; te[4] = tmp;
+ tmp = te[2]; te[2] = te[8]; te[8] = tmp;
+ tmp = te[6]; te[6] = te[9]; te[9] = tmp;
+
+ tmp = te[3]; te[3] = te[12]; te[12] = tmp;
+ tmp = te[7]; te[7] = te[13]; te[13] = tmp;
+ tmp = te[11]; te[11] = te[14]; te[14] = tmp;
+
+ return this;
+
+ },
+
+ flattenToArray: function ( flat ) {
+
+ var te = this.elements;
+ flat[ 0 ] = te[0]; flat[ 1 ] = te[1]; flat[ 2 ] = te[2]; flat[ 3 ] = te[3];
+ flat[ 4 ] = te[4]; flat[ 5 ] = te[5]; flat[ 6 ] = te[6]; flat[ 7 ] = te[7];
+ flat[ 8 ] = te[8]; flat[ 9 ] = te[9]; flat[ 10 ] = te[10]; flat[ 11 ] = te[11];
+ flat[ 12 ] = te[12]; flat[ 13 ] = te[13]; flat[ 14 ] = te[14]; flat[ 15 ] = te[15];
+
+ return flat;
+
+ },
+
+ flattenToArrayOffset: function( flat, offset ) {
+
+ var te = this.elements;
+ flat[ offset ] = te[0];
+ flat[ offset + 1 ] = te[1];
+ flat[ offset + 2 ] = te[2];
+ flat[ offset + 3 ] = te[3];
+
+ flat[ offset + 4 ] = te[4];
+ flat[ offset + 5 ] = te[5];
+ flat[ offset + 6 ] = te[6];
+ flat[ offset + 7 ] = te[7];
+
+ flat[ offset + 8 ] = te[8];
+ flat[ offset + 9 ] = te[9];
+ flat[ offset + 10 ] = te[10];
+ flat[ offset + 11 ] = te[11];
+
+ flat[ offset + 12 ] = te[12];
+ flat[ offset + 13 ] = te[13];
+ flat[ offset + 14 ] = te[14];
+ flat[ offset + 15 ] = te[15];
+
+ return flat;
+
+ },
+
+ getPosition: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function () {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .getPosition() has been removed. Use Vector3.getPositionFromMatrix( matrix ) instead.' );
+
+ var te = this.elements;
+ return v1.set( te[12], te[13], te[14] );
+
+ };
+
+ }(),
+
+ setPosition: function ( v ) {
+
+ var te = this.elements;
+
+ te[12] = v.x;
+ te[13] = v.y;
+ te[14] = v.z;
+
+ return this;
+
+ },
+
+ getInverse: function ( m, throwOnInvertible ) {
+
+ // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
+ var te = this.elements;
+ var me = m.elements;
+
+ var n11 = me[0], n12 = me[4], n13 = me[8], n14 = me[12];
+ var n21 = me[1], n22 = me[5], n23 = me[9], n24 = me[13];
+ var n31 = me[2], n32 = me[6], n33 = me[10], n34 = me[14];
+ var n41 = me[3], n42 = me[7], n43 = me[11], n44 = me[15];
+
+ te[0] = n23*n34*n42 - n24*n33*n42 + n24*n32*n43 - n22*n34*n43 - n23*n32*n44 + n22*n33*n44;
+ te[4] = n14*n33*n42 - n13*n34*n42 - n14*n32*n43 + n12*n34*n43 + n13*n32*n44 - n12*n33*n44;
+ te[8] = n13*n24*n42 - n14*n23*n42 + n14*n22*n43 - n12*n24*n43 - n13*n22*n44 + n12*n23*n44;
+ te[12] = n14*n23*n32 - n13*n24*n32 - n14*n22*n33 + n12*n24*n33 + n13*n22*n34 - n12*n23*n34;
+ te[1] = n24*n33*n41 - n23*n34*n41 - n24*n31*n43 + n21*n34*n43 + n23*n31*n44 - n21*n33*n44;
+ te[5] = n13*n34*n41 - n14*n33*n41 + n14*n31*n43 - n11*n34*n43 - n13*n31*n44 + n11*n33*n44;
+ te[9] = n14*n23*n41 - n13*n24*n41 - n14*n21*n43 + n11*n24*n43 + n13*n21*n44 - n11*n23*n44;
+ te[13] = n13*n24*n31 - n14*n23*n31 + n14*n21*n33 - n11*n24*n33 - n13*n21*n34 + n11*n23*n34;
+ te[2] = n22*n34*n41 - n24*n32*n41 + n24*n31*n42 - n21*n34*n42 - n22*n31*n44 + n21*n32*n44;
+ te[6] = n14*n32*n41 - n12*n34*n41 - n14*n31*n42 + n11*n34*n42 + n12*n31*n44 - n11*n32*n44;
+ te[10] = n12*n24*n41 - n14*n22*n41 + n14*n21*n42 - n11*n24*n42 - n12*n21*n44 + n11*n22*n44;
+ te[14] = n14*n22*n31 - n12*n24*n31 - n14*n21*n32 + n11*n24*n32 + n12*n21*n34 - n11*n22*n34;
+ te[3] = n23*n32*n41 - n22*n33*n41 - n23*n31*n42 + n21*n33*n42 + n22*n31*n43 - n21*n32*n43;
+ te[7] = n12*n33*n41 - n13*n32*n41 + n13*n31*n42 - n11*n33*n42 - n12*n31*n43 + n11*n32*n43;
+ te[11] = n13*n22*n41 - n12*n23*n41 - n13*n21*n42 + n11*n23*n42 + n12*n21*n43 - n11*n22*n43;
+ te[15] = n12*n23*n31 - n13*n22*n31 + n13*n21*n32 - n11*n23*n32 - n12*n21*n33 + n11*n22*n33;
+
+ var det = n11 * te[ 0 ] + n21 * te[ 4 ] + n31 * te[ 8 ] + n41 * te[ 12 ];
+
+ if ( det == 0 ) {
+
+ var msg = "Matrix4.getInverse(): can't invert matrix, determinant is 0";
+
+ if ( throwOnInvertible || false ) {
+
+ throw new Error( msg );
+
+ } else {
+
+ console.warn( msg );
+
+ }
+
+ this.identity();
+
+ return this;
+ }
+
+ this.multiplyScalar( 1 / det );
+
+ return this;
+
+ },
+
+ translate: function ( v ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .translate() has been removed.');
+
+ },
+
+ rotateX: function ( angle ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateX() has been removed.');
+
+ },
+
+ rotateY: function ( angle ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateY() has been removed.');
+
+ },
+
+ rotateZ: function ( angle ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateZ() has been removed.');
+
+ },
+
+ rotateByAxis: function ( axis, angle ) {
+
+ console.warn( 'DEPRECATED: Matrix4\'s .rotateByAxis() has been removed.');
+
+ },
+
+ scale: function ( v ) {
+
+ var te = this.elements;
+ var x = v.x, y = v.y, z = v.z;
+
+ te[0] *= x; te[4] *= y; te[8] *= z;
+ te[1] *= x; te[5] *= y; te[9] *= z;
+ te[2] *= x; te[6] *= y; te[10] *= z;
+ te[3] *= x; te[7] *= y; te[11] *= z;
+
+ return this;
+
+ },
+
+ getMaxScaleOnAxis: function () {
+
+ var te = this.elements;
+
+ var scaleXSq = te[0] * te[0] + te[1] * te[1] + te[2] * te[2];
+ var scaleYSq = te[4] * te[4] + te[5] * te[5] + te[6] * te[6];
+ var scaleZSq = te[8] * te[8] + te[9] * te[9] + te[10] * te[10];
+
+ return Math.sqrt( Math.max( scaleXSq, Math.max( scaleYSq, scaleZSq ) ) );
+
+ },
+
+ makeTranslation: function ( x, y, z ) {
+
+ this.set(
+
+ 1, 0, 0, x,
+ 0, 1, 0, y,
+ 0, 0, 1, z,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationX: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ 1, 0, 0, 0,
+ 0, c, -s, 0,
+ 0, s, c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationY: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ c, 0, s, 0,
+ 0, 1, 0, 0,
+ -s, 0, c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationZ: function ( theta ) {
+
+ var c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ c, -s, 0, 0,
+ s, c, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeRotationAxis: function ( axis, angle ) {
+
+ // Based on http://www.gamedev.net/reference/articles/article1199.asp
+
+ var c = Math.cos( angle );
+ var s = Math.sin( angle );
+ var t = 1 - c;
+ var x = axis.x, y = axis.y, z = axis.z;
+ var tx = t * x, ty = t * y;
+
+ this.set(
+
+ tx * x + c, tx * y - s * z, tx * z + s * y, 0,
+ tx * y + s * z, ty * y + c, ty * z - s * x, 0,
+ tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ makeScale: function ( x, y, z ) {
+
+ this.set(
+
+ x, 0, 0, 0,
+ 0, y, 0, 0,
+ 0, 0, z, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ },
+
+ compose: function ( position, quaternion, scale ) {
+
+ this.makeRotationFromQuaternion( quaternion );
+ this.scale( scale );
+ this.setPosition( position );
+
+ return this;
+
+ },
+
+ decompose: function () {
+
+ var vector = new THREE.Vector3();
+ var matrix = new THREE.Matrix4();
+
+ return function ( position, quaternion, scale ) {
+
+ var te = this.elements;
+
+ var sx = vector.set( te[0], te[1], te[2] ).length();
+ var sy = vector.set( te[4], te[5], te[6] ).length();
+ var sz = vector.set( te[8], te[9], te[10] ).length();
+
+ position.x = te[12];
+ position.y = te[13];
+ position.z = te[14];
+
+ // scale the rotation part
+
+ matrix.elements.set( this.elements ); // at this point matrix is incomplete so we can't use .copy()
+
+ var invSX = 1 / sx;
+ var invSY = 1 / sy;
+ var invSZ = 1 / sz;
+
+ matrix.elements[0] *= invSX;
+ matrix.elements[1] *= invSX;
+ matrix.elements[2] *= invSX;
+
+ matrix.elements[4] *= invSY;
+ matrix.elements[5] *= invSY;
+ matrix.elements[6] *= invSY;
+
+ matrix.elements[8] *= invSZ;
+ matrix.elements[9] *= invSZ;
+ matrix.elements[10] *= invSZ;
+
+ quaternion.setFromRotationMatrix( matrix );
+
+ scale.x = sx;
+ scale.y = sy;
+ scale.z = sz;
+
+ return this;
+
+ };
+
+ }(),
+
+ makeFrustum: function ( left, right, bottom, top, near, far ) {
+
+ var te = this.elements;
+ var x = 2 * near / ( right - left );
+ var y = 2 * near / ( top - bottom );
+
+ var a = ( right + left ) / ( right - left );
+ var b = ( top + bottom ) / ( top - bottom );
+ var c = - ( far + near ) / ( far - near );
+ var d = - 2 * far * near / ( far - near );
+
+ te[0] = x; te[4] = 0; te[8] = a; te[12] = 0;
+ te[1] = 0; te[5] = y; te[9] = b; te[13] = 0;
+ te[2] = 0; te[6] = 0; te[10] = c; te[14] = d;
+ te[3] = 0; te[7] = 0; te[11] = - 1; te[15] = 0;
+
+ return this;
+
+ },
+
+ makePerspective: function ( fov, aspect, near, far ) {
+
+ var ymax = near * Math.tan( THREE.Math.degToRad( fov * 0.5 ) );
+ var ymin = - ymax;
+ var xmin = ymin * aspect;
+ var xmax = ymax * aspect;
+
+ return this.makeFrustum( xmin, xmax, ymin, ymax, near, far );
+
+ },
+
+ makeOrthographic: function ( left, right, top, bottom, near, far ) {
+
+ var te = this.elements;
+ var w = right - left;
+ var h = top - bottom;
+ var p = far - near;
+
+ var x = ( right + left ) / w;
+ var y = ( top + bottom ) / h;
+ var z = ( far + near ) / p;
+
+ te[0] = 2 / w; te[4] = 0; te[8] = 0; te[12] = -x;
+ te[1] = 0; te[5] = 2 / h; te[9] = 0; te[13] = -y;
+ te[2] = 0; te[6] = 0; te[10] = -2/p; te[14] = -z;
+ te[3] = 0; te[7] = 0; te[11] = 0; te[15] = 1;
+
+ return this;
+
+ },
+
+ fromArray: function ( array ) {
+
+ this.elements.set( array );
+
+ return this;
+
+ },
+
+ toArray: function () {
+
+ var te = this.elements;
+
+ return [
+ te[ 0 ], te[ 1 ], te[ 2 ], te[ 3 ],
+ te[ 4 ], te[ 5 ], te[ 6 ], te[ 7 ],
+ te[ 8 ], te[ 9 ], te[ 10 ], te[ 11 ],
+ te[ 12 ], te[ 13 ], te[ 14 ], te[ 15 ]
+ ];
+
+ },
+
+ clone: function () {
+
+ var te = this.elements;
+
+ return new THREE.Matrix4(
+
+ te[0], te[4], te[8], te[12],
+ te[1], te[5], te[9], te[13],
+ te[2], te[6], te[10], te[14],
+ te[3], te[7], te[11], te[15]
+
+ );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Ray = function ( origin, direction ) {
+
+ this.origin = ( origin !== undefined ) ? origin : new THREE.Vector3();
+ this.direction = ( direction !== undefined ) ? direction : new THREE.Vector3();
+
+};
+
+THREE.Ray.prototype = {
+
+ constructor: THREE.Ray,
+
+ set: function ( origin, direction ) {
+
+ this.origin.copy( origin );
+ this.direction.copy( direction );
+
+ return this;
+
+ },
+
+ copy: function ( ray ) {
+
+ this.origin.copy( ray.origin );
+ this.direction.copy( ray.direction );
+
+ return this;
+
+ },
+
+ at: function ( t, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ return result.copy( this.direction ).multiplyScalar( t ).add( this.origin );
+
+ },
+
+ recast: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( t ) {
+
+ this.origin.copy( this.at( t, v1 ) );
+
+ return this;
+
+ };
+
+ }(),
+
+ closestPointToPoint: function ( point, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ result.subVectors( point, this.origin );
+ var directionDistance = result.dot( this.direction );
+
+ if ( directionDistance < 0 ) {
+
+ return result.copy( this.origin );
+
+ }
+
+ return result.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+ },
+
+ distanceToPoint: function () {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( point ) {
+
+ var directionDistance = v1.subVectors( point, this.origin ).dot( this.direction );
+
+ // point behind the ray
+
+ if ( directionDistance < 0 ) {
+
+ return this.origin.distanceTo( point );
+
+ }
+
+ v1.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+ return v1.distanceTo( point );
+
+ };
+
+ }(),
+
+ distanceSqToSegment: function( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
+
+ // from http://www.geometrictools.com/LibMathematics/Distance/Wm5DistRay3Segment3.cpp
+ // It returns the min distance between the ray and the segment
+ // defined by v0 and v1
+ // It can also set two optional targets :
+ // - The closest point on the ray
+ // - The closest point on the segment
+
+ var segCenter = v0.clone().add( v1 ).multiplyScalar( 0.5 );
+ var segDir = v1.clone().sub( v0 ).normalize();
+ var segExtent = v0.distanceTo( v1 ) * 0.5;
+ var diff = this.origin.clone().sub( segCenter );
+ var a01 = - this.direction.dot( segDir );
+ var b0 = diff.dot( this.direction );
+ var b1 = - diff.dot( segDir );
+ var c = diff.lengthSq();
+ var det = Math.abs( 1 - a01 * a01 );
+ var s0, s1, sqrDist, extDet;
+
+ if ( det >= 0 ) {
+
+ // The ray and segment are not parallel.
+
+ s0 = a01 * b1 - b0;
+ s1 = a01 * b0 - b1;
+ extDet = segExtent * det;
+
+ if ( s0 >= 0 ) {
+
+ if ( s1 >= - extDet ) {
+
+ if ( s1 <= extDet ) {
+
+ // region 0
+ // Minimum at interior points of ray and segment.
+
+ var invDet = 1 / det;
+ s0 *= invDet;
+ s1 *= invDet;
+ sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
+
+ } else {
+
+ // region 1
+
+ s1 = segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ } else {
+
+ // region 5
+
+ s1 = - segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ } else {
+
+ if ( s1 <= - extDet) {
+
+ // region 4
+
+ s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
+ s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ } else if ( s1 <= extDet ) {
+
+ // region 3
+
+ s0 = 0;
+ s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = s1 * ( s1 + 2 * b1 ) + c;
+
+ } else {
+
+ // region 2
+
+ s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
+ s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ }
+
+ } else {
+
+ // Ray and segment are parallel.
+
+ s1 = ( a01 > 0 ) ? - segExtent : segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ if ( optionalPointOnRay ) {
+
+ optionalPointOnRay.copy( this.direction.clone().multiplyScalar( s0 ).add( this.origin ) );
+
+ }
+
+ if ( optionalPointOnSegment ) {
+
+ optionalPointOnSegment.copy( segDir.clone().multiplyScalar( s1 ).add( segCenter ) );
+
+ }
+
+ return sqrDist;
+
+ },
+
+ isIntersectionSphere: function ( sphere ) {
+
+ return this.distanceToPoint( sphere.center ) <= sphere.radius;
+
+ },
+
+ isIntersectionPlane: function ( plane ) {
+
+ // check if the ray lies on the plane first
+
+ var distToPoint = plane.distanceToPoint( this.origin );
+
+ if ( distToPoint === 0 ) {
+
+ return true;
+
+ }
+
+ var denominator = plane.normal.dot( this.direction );
+
+ if ( denominator * distToPoint < 0 ) {
+
+ return true
+
+ }
+
+ // ray origin is behind the plane (and is pointing behind it)
+
+ return false;
+
+ },
+
+ distanceToPlane: function ( plane ) {
+
+ var denominator = plane.normal.dot( this.direction );
+ if ( denominator == 0 ) {
+
+ // line is coplanar, return origin
+ if( plane.distanceToPoint( this.origin ) == 0 ) {
+
+ return 0;
+
+ }
+
+ // Null is preferable to undefined since undefined means.... it is undefined
+
+ return null;
+
+ }
+
+ var t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
+
+ // Return if the ray never intersects the plane
+
+ return t >= 0 ? t : null;
+
+ },
+
+ intersectPlane: function ( plane, optionalTarget ) {
+
+ var t = this.distanceToPlane( plane );
+
+ if ( t === null ) {
+
+ return null;
+ }
+
+ return this.at( t, optionalTarget );
+
+ },
+
+ isIntersectionBox: function () {
+
+ var v = new THREE.Vector3();
+
+ return function ( box ) {
+
+ return this.intersectBox( box, v ) !== null;
+
+ }
+
+ }(),
+
+ intersectBox: function ( box , optionalTarget ) {
+
+ // http://www.scratchapixel.com/lessons/3d-basic-lessons/lesson-7-intersecting-simple-shapes/ray-box-intersection/
+
+ var tmin,tmax,tymin,tymax,tzmin,tzmax;
+
+ var invdirx = 1/this.direction.x,
+ invdiry = 1/this.direction.y,
+ invdirz = 1/this.direction.z;
+
+ var origin = this.origin;
+
+ if (invdirx >= 0) {
+
+ tmin = (box.min.x - origin.x) * invdirx;
+ tmax = (box.max.x - origin.x) * invdirx;
+
+ } else {
+
+ tmin = (box.max.x - origin.x) * invdirx;
+ tmax = (box.min.x - origin.x) * invdirx;
+ }
+
+ if (invdiry >= 0) {
+
+ tymin = (box.min.y - origin.y) * invdiry;
+ tymax = (box.max.y - origin.y) * invdiry;
+
+ } else {
+
+ tymin = (box.max.y - origin.y) * invdiry;
+ tymax = (box.min.y - origin.y) * invdiry;
+ }
+
+ if ((tmin > tymax) || (tymin > tmax)) return null;
+
+ // These lines also handle the case where tmin or tmax is NaN
+ // (result of 0 * Infinity). x !== x returns true if x is NaN
+
+ if (tymin > tmin || tmin !== tmin ) tmin = tymin;
+
+ if (tymax < tmax || tmax !== tmax ) tmax = tymax;
+
+ if (invdirz >= 0) {
+
+ tzmin = (box.min.z - origin.z) * invdirz;
+ tzmax = (box.max.z - origin.z) * invdirz;
+
+ } else {
+
+ tzmin = (box.max.z - origin.z) * invdirz;
+ tzmax = (box.min.z - origin.z) * invdirz;
+ }
+
+ if ((tmin > tzmax) || (tzmin > tmax)) return null;
+
+ if (tzmin > tmin || tmin !== tmin ) tmin = tzmin;
+
+ if (tzmax < tmax || tmax !== tmax ) tmax = tzmax;
+
+ //return point closest to the ray (positive side)
+
+ if ( tmax < 0 ) return null;
+
+ return this.at( tmin >= 0 ? tmin : tmax, optionalTarget );
+
+ },
+
+ intersectTriangle: function() {
+
+ // Compute the offset origin, edges, and normal.
+ var diff = new THREE.Vector3();
+ var edge1 = new THREE.Vector3();
+ var edge2 = new THREE.Vector3();
+ var normal = new THREE.Vector3();
+
+ return function ( a, b, c, backfaceCulling, optionalTarget ) {
+
+ // from http://www.geometrictools.com/LibMathematics/Intersection/Wm5IntrRay3Triangle3.cpp
+
+ edge1.subVectors( b, a );
+ edge2.subVectors( c, a );
+ normal.crossVectors( edge1, edge2 );
+
+ // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
+ // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
+ // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
+ // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
+ // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
+ var DdN = this.direction.dot( normal );
+ var sign;
+
+ if ( DdN > 0 ) {
+
+ if ( backfaceCulling ) return null;
+ sign = 1;
+
+ } else if ( DdN < 0 ) {
+
+ sign = - 1;
+ DdN = - DdN;
+
+ } else {
+
+ return null;
+
+ }
+
+ diff.subVectors( this.origin, a );
+ var DdQxE2 = sign * this.direction.dot( edge2.crossVectors( diff, edge2 ) );
+
+ // b1 < 0, no intersection
+ if ( DdQxE2 < 0 ) {
+
+ return null;
+
+ }
+
+ var DdE1xQ = sign * this.direction.dot( edge1.cross( diff ) );
+
+ // b2 < 0, no intersection
+ if ( DdE1xQ < 0 ) {
+
+ return null;
+
+ }
+
+ // b1+b2 > 1, no intersection
+ if ( DdQxE2 + DdE1xQ > DdN ) {
+
+ return null;
+
+ }
+
+ // Line intersects triangle, check if ray does.
+ var QdN = - sign * diff.dot( normal );
+
+ // t < 0, no intersection
+ if ( QdN < 0 ) {
+
+ return null;
+
+ }
+
+ // Ray intersects triangle.
+ return this.at( QdN / DdN, optionalTarget );
+
+ }
+
+ }(),
+
+ applyMatrix4: function ( matrix4 ) {
+
+ this.direction.add( this.origin ).applyMatrix4( matrix4 );
+ this.origin.applyMatrix4( matrix4 );
+ this.direction.sub( this.origin );
+ this.direction.normalize();
+
+ return this;
+ },
+
+ equals: function ( ray ) {
+
+ return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Ray().copy( this );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Sphere = function ( center, radius ) {
+
+ this.center = ( center !== undefined ) ? center : new THREE.Vector3();
+ this.radius = ( radius !== undefined ) ? radius : 0;
+
+};
+
+THREE.Sphere.prototype = {
+
+ constructor: THREE.Sphere,
+
+ set: function ( center, radius ) {
+
+ this.center.copy( center );
+ this.radius = radius;
+
+ return this;
+ },
+
+
+ setFromPoints: function () {
+
+ var box = new THREE.Box3();
+
+ return function ( points, optionalCenter ) {
+
+ var center = this.center;
+
+ if ( optionalCenter !== undefined ) {
+
+ center.copy( optionalCenter );
+
+ } else {
+
+ box.setFromPoints( points ).center( center );
+
+ }
+
+ var maxRadiusSq = 0;
+
+ for ( var i = 0, il = points.length; i < il; i ++ ) {
+
+ maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) );
+
+ }
+
+ this.radius = Math.sqrt( maxRadiusSq );
+
+ return this;
+
+ };
+
+ }(),
+
+ copy: function ( sphere ) {
+
+ this.center.copy( sphere.center );
+ this.radius = sphere.radius;
+
+ return this;
+
+ },
+
+ empty: function () {
+
+ return ( this.radius <= 0 );
+
+ },
+
+ containsPoint: function ( point ) {
+
+ return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );
+
+ },
+
+ distanceToPoint: function ( point ) {
+
+ return ( point.distanceTo( this.center ) - this.radius );
+
+ },
+
+ intersectsSphere: function ( sphere ) {
+
+ var radiusSum = this.radius + sphere.radius;
+
+ return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );
+
+ },
+
+ clampPoint: function ( point, optionalTarget ) {
+
+ var deltaLengthSq = this.center.distanceToSquared( point );
+
+ var result = optionalTarget || new THREE.Vector3();
+ result.copy( point );
+
+ if ( deltaLengthSq > ( this.radius * this.radius ) ) {
+
+ result.sub( this.center ).normalize();
+ result.multiplyScalar( this.radius ).add( this.center );
+
+ }
+
+ return result;
+
+ },
+
+ getBoundingBox: function ( optionalTarget ) {
+
+ var box = optionalTarget || new THREE.Box3();
+
+ box.set( this.center, this.center );
+ box.expandByScalar( this.radius );
+
+ return box;
+
+ },
+
+ applyMatrix4: function ( matrix ) {
+
+ this.center.applyMatrix4( matrix );
+ this.radius = this.radius * matrix.getMaxScaleOnAxis();
+
+ return this;
+
+ },
+
+ translate: function ( offset ) {
+
+ this.center.add( offset );
+
+ return this;
+
+ },
+
+ equals: function ( sphere ) {
+
+ return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Sphere().copy( this );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Frustum = function ( p0, p1, p2, p3, p4, p5 ) {
+
+ this.planes = [
+
+ ( p0 !== undefined ) ? p0 : new THREE.Plane(),
+ ( p1 !== undefined ) ? p1 : new THREE.Plane(),
+ ( p2 !== undefined ) ? p2 : new THREE.Plane(),
+ ( p3 !== undefined ) ? p3 : new THREE.Plane(),
+ ( p4 !== undefined ) ? p4 : new THREE.Plane(),
+ ( p5 !== undefined ) ? p5 : new THREE.Plane()
+
+ ];
+
+};
+
+THREE.Frustum.prototype = {
+
+ constructor: THREE.Frustum,
+
+ set: function ( p0, p1, p2, p3, p4, p5 ) {
+
+ var planes = this.planes;
+
+ planes[0].copy( p0 );
+ planes[1].copy( p1 );
+ planes[2].copy( p2 );
+ planes[3].copy( p3 );
+ planes[4].copy( p4 );
+ planes[5].copy( p5 );
+
+ return this;
+
+ },
+
+ copy: function ( frustum ) {
+
+ var planes = this.planes;
+
+ for( var i = 0; i < 6; i ++ ) {
+
+ planes[i].copy( frustum.planes[i] );
+
+ }
+
+ return this;
+
+ },
+
+ setFromMatrix: function ( m ) {
+
+ var planes = this.planes;
+ var me = m.elements;
+ var me0 = me[0], me1 = me[1], me2 = me[2], me3 = me[3];
+ var me4 = me[4], me5 = me[5], me6 = me[6], me7 = me[7];
+ var me8 = me[8], me9 = me[9], me10 = me[10], me11 = me[11];
+ var me12 = me[12], me13 = me[13], me14 = me[14], me15 = me[15];
+
+ planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();
+ planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();
+ planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();
+ planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();
+ planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();
+ planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();
+
+ return this;
+
+ },
+
+ intersectsObject: function () {
+
+ var sphere = new THREE.Sphere();
+
+ return function ( object ) {
+
+ var geometry = object.geometry;
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ sphere.copy( geometry.boundingSphere );
+ sphere.applyMatrix4( object.matrixWorld );
+
+ return this.intersectsSphere( sphere );
+
+ };
+
+ }(),
+
+ intersectsSphere: function ( sphere ) {
+
+ var planes = this.planes;
+ var center = sphere.center;
+ var negRadius = -sphere.radius;
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ var distance = planes[ i ].distanceToPoint( center );
+
+ if ( distance < negRadius ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ },
+
+ intersectsBox : function() {
+
+ var p1 = new THREE.Vector3(),
+ p2 = new THREE.Vector3();
+
+ return function( box ) {
+
+ var planes = this.planes;
+
+ for ( var i = 0; i < 6 ; i ++ ) {
+
+ var plane = planes[i];
+
+ p1.x = plane.normal.x > 0 ? box.min.x : box.max.x;
+ p2.x = plane.normal.x > 0 ? box.max.x : box.min.x;
+ p1.y = plane.normal.y > 0 ? box.min.y : box.max.y;
+ p2.y = plane.normal.y > 0 ? box.max.y : box.min.y;
+ p1.z = plane.normal.z > 0 ? box.min.z : box.max.z;
+ p2.z = plane.normal.z > 0 ? box.max.z : box.min.z;
+
+ var d1 = plane.distanceToPoint( p1 );
+ var d2 = plane.distanceToPoint( p2 );
+
+ // if both outside plane, no intersection
+
+ if ( d1 < 0 && d2 < 0 ) {
+
+ return false;
+
+ }
+ }
+
+ return true;
+ };
+
+ }(),
+
+
+ containsPoint: function ( point ) {
+
+ var planes = this.planes;
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ if ( planes[ i ].distanceToPoint( point ) < 0 ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ },
+
+ clone: function () {
+
+ return new THREE.Frustum().copy( this );
+
+ }
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Plane = function ( normal, constant ) {
+
+ this.normal = ( normal !== undefined ) ? normal : new THREE.Vector3( 1, 0, 0 );
+ this.constant = ( constant !== undefined ) ? constant : 0;
+
+};
+
+THREE.Plane.prototype = {
+
+ constructor: THREE.Plane,
+
+ set: function ( normal, constant ) {
+
+ this.normal.copy( normal );
+ this.constant = constant;
+
+ return this;
+
+ },
+
+ setComponents: function ( x, y, z, w ) {
+
+ this.normal.set( x, y, z );
+ this.constant = w;
+
+ return this;
+
+ },
+
+ setFromNormalAndCoplanarPoint: function ( normal, point ) {
+
+ this.normal.copy( normal );
+ this.constant = - point.dot( this.normal ); // must be this.normal, not normal, as this.normal is normalized
+
+ return this;
+
+ },
+
+ setFromCoplanarPoints: function() {
+
+ var v1 = new THREE.Vector3();
+ var v2 = new THREE.Vector3();
+
+ return function ( a, b, c ) {
+
+ var normal = v1.subVectors( c, b ).cross( v2.subVectors( a, b ) ).normalize();
+
+ // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
+
+ this.setFromNormalAndCoplanarPoint( normal, a );
+
+ return this;
+
+ };
+
+ }(),
+
+
+ copy: function ( plane ) {
+
+ this.normal.copy( plane.normal );
+ this.constant = plane.constant;
+
+ return this;
+
+ },
+
+ normalize: function () {
+
+ // Note: will lead to a divide by zero if the plane is invalid.
+
+ var inverseNormalLength = 1.0 / this.normal.length();
+ this.normal.multiplyScalar( inverseNormalLength );
+ this.constant *= inverseNormalLength;
+
+ return this;
+
+ },
+
+ negate: function () {
+
+ this.constant *= -1;
+ this.normal.negate();
+
+ return this;
+
+ },
+
+ distanceToPoint: function ( point ) {
+
+ return this.normal.dot( point ) + this.constant;
+
+ },
+
+ distanceToSphere: function ( sphere ) {
+
+ return this.distanceToPoint( sphere.center ) - sphere.radius;
+
+ },
+
+ projectPoint: function ( point, optionalTarget ) {
+
+ return this.orthoPoint( point, optionalTarget ).sub( point ).negate();
+
+ },
+
+ orthoPoint: function ( point, optionalTarget ) {
+
+ var perpendicularMagnitude = this.distanceToPoint( point );
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.copy( this.normal ).multiplyScalar( perpendicularMagnitude );
+
+ },
+
+ isIntersectionLine: function ( line ) {
+
+ // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
+
+ var startSign = this.distanceToPoint( line.start );
+ var endSign = this.distanceToPoint( line.end );
+
+ return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );
+
+ },
+
+ intersectLine: function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( line, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ var direction = line.delta( v1 );
+
+ var denominator = this.normal.dot( direction );
+
+ if ( denominator == 0 ) {
+
+ // line is coplanar, return origin
+ if( this.distanceToPoint( line.start ) == 0 ) {
+
+ return result.copy( line.start );
+
+ }
+
+ // Unsure if this is the correct method to handle this case.
+ return undefined;
+
+ }
+
+ var t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;
+
+ if( t < 0 || t > 1 ) {
+
+ return undefined;
+
+ }
+
+ return result.copy( direction ).multiplyScalar( t ).add( line.start );
+
+ };
+
+ }(),
+
+
+ coplanarPoint: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.copy( this.normal ).multiplyScalar( - this.constant );
+
+ },
+
+ applyMatrix4: function() {
+
+ var v1 = new THREE.Vector3();
+ var v2 = new THREE.Vector3();
+
+ return function ( matrix, optionalNormalMatrix ) {
+
+ // compute new normal based on theory here:
+ // http://www.songho.ca/opengl/gl_normaltransform.html
+ optionalNormalMatrix = optionalNormalMatrix || new THREE.Matrix3().getNormalMatrix( matrix );
+ var newNormal = v1.copy( this.normal ).applyMatrix3( optionalNormalMatrix );
+
+ var newCoplanarPoint = this.coplanarPoint( v2 );
+ newCoplanarPoint.applyMatrix4( matrix );
+
+ this.setFromNormalAndCoplanarPoint( newNormal, newCoplanarPoint );
+
+ return this;
+
+ };
+
+ }(),
+
+ translate: function ( offset ) {
+
+ this.constant = this.constant - offset.dot( this.normal );
+
+ return this;
+
+ },
+
+ equals: function ( plane ) {
+
+ return plane.normal.equals( this.normal ) && ( plane.constant == this.constant );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Plane().copy( this );
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Math = {
+
+ PI2: Math.PI * 2,
+
+ generateUUID: function () {
+
+ // http://www.broofa.com/Tools/Math.uuid.htm
+
+ var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
+ var uuid = new Array(36);
+ var rnd = 0, r;
+
+ return function () {
+
+ for ( var i = 0; i < 36; i ++ ) {
+
+ if ( i == 8 || i == 13 || i == 18 || i == 23 ) {
+
+ uuid[ i ] = '-';
+
+ } else if ( i == 14 ) {
+
+ uuid[ i ] = '4';
+
+ } else {
+
+ if (rnd <= 0x02) rnd = 0x2000000 + (Math.random()*0x1000000)|0;
+ r = rnd & 0xf;
+ rnd = rnd >> 4;
+ uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
+
+ }
+ }
+
+ return uuid.join('');
+
+ };
+
+ }(),
+
+ // Clamp value to range
+
+ clamp: function ( x, a, b ) {
+
+ return ( x < a ) ? a : ( ( x > b ) ? b : x );
+
+ },
+
+ // Clamp value to range to range
+
+ mapLinear: function ( x, a1, a2, b1, b2 ) {
+
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+
+ },
+
+ // http://en.wikipedia.org/wiki/Smoothstep
+
+ smoothstep: function ( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min )/( max - min );
+
+ return x*x*(3 - 2*x);
+
+ },
+
+ smootherstep: function ( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min )/( max - min );
+
+ return x*x*x*(x*(x*6 - 15) + 10);
+
+ },
+
+ // Random float from <0, 1> with 16 bits of randomness
+ // (standard Math.random() creates repetitive patterns when applied over larger space)
+
+ random16: function () {
+
+ return ( 65280 * Math.random() + 255 * Math.random() ) / 65535;
+
+ },
+
+ // Random integer from interval
+
+ randInt: function ( low, high ) {
+
+ return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+ },
+
+ // Random float from interval
+
+ randFloat: function ( low, high ) {
+
+ return low + Math.random() * ( high - low );
+
+ },
+
+ // Random float from <-range/2, range/2> interval
+
+ randFloatSpread: function ( range ) {
+
+ return range * ( 0.5 - Math.random() );
+
+ },
+
+ sign: function ( x ) {
+
+ return ( x < 0 ) ? -1 : ( ( x > 0 ) ? 1 : 0 );
+
+ },
+
+ degToRad: function() {
+
+ var degreeToRadiansFactor = Math.PI / 180;
+
+ return function ( degrees ) {
+
+ return degrees * degreeToRadiansFactor;
+
+ };
+
+ }(),
+
+ radToDeg: function() {
+
+ var radianToDegreesFactor = 180 / Math.PI;
+
+ return function ( radians ) {
+
+ return radians * radianToDegreesFactor;
+
+ };
+
+ }()
+
+};
+
+/**
+ * Spline from Tween.js, slightly optimized (and trashed)
+ * http://sole.github.com/tween.js/examples/05_spline.html
+ *
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Spline = function ( points ) {
+
+ this.points = points;
+
+ var c = [], v3 = { x: 0, y: 0, z: 0 },
+ point, intPoint, weight, w2, w3,
+ pa, pb, pc, pd;
+
+ this.initFromArray = function( a ) {
+
+ this.points = [];
+
+ for ( var i = 0; i < a.length; i++ ) {
+
+ this.points[ i ] = { x: a[ i ][ 0 ], y: a[ i ][ 1 ], z: a[ i ][ 2 ] };
+
+ }
+
+ };
+
+ this.getPoint = function ( k ) {
+
+ point = ( this.points.length - 1 ) * k;
+ intPoint = Math.floor( point );
+ weight = point - intPoint;
+
+ c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1;
+ c[ 1 ] = intPoint;
+ c[ 2 ] = intPoint > this.points.length - 2 ? this.points.length - 1 : intPoint + 1;
+ c[ 3 ] = intPoint > this.points.length - 3 ? this.points.length - 1 : intPoint + 2;
+
+ pa = this.points[ c[ 0 ] ];
+ pb = this.points[ c[ 1 ] ];
+ pc = this.points[ c[ 2 ] ];
+ pd = this.points[ c[ 3 ] ];
+
+ w2 = weight * weight;
+ w3 = weight * w2;
+
+ v3.x = interpolate( pa.x, pb.x, pc.x, pd.x, weight, w2, w3 );
+ v3.y = interpolate( pa.y, pb.y, pc.y, pd.y, weight, w2, w3 );
+ v3.z = interpolate( pa.z, pb.z, pc.z, pd.z, weight, w2, w3 );
+
+ return v3;
+
+ };
+
+ this.getControlPointsArray = function () {
+
+ var i, p, l = this.points.length,
+ coords = [];
+
+ for ( i = 0; i < l; i ++ ) {
+
+ p = this.points[ i ];
+ coords[ i ] = [ p.x, p.y, p.z ];
+
+ }
+
+ return coords;
+
+ };
+
+ // approximate length by summing linear segments
+
+ this.getLength = function ( nSubDivisions ) {
+
+ var i, index, nSamples, position,
+ point = 0, intPoint = 0, oldIntPoint = 0,
+ oldPosition = new THREE.Vector3(),
+ tmpVec = new THREE.Vector3(),
+ chunkLengths = [],
+ totalLength = 0;
+
+ // first point has 0 length
+
+ chunkLengths[ 0 ] = 0;
+
+ if ( !nSubDivisions ) nSubDivisions = 100;
+
+ nSamples = this.points.length * nSubDivisions;
+
+ oldPosition.copy( this.points[ 0 ] );
+
+ for ( i = 1; i < nSamples; i ++ ) {
+
+ index = i / nSamples;
+
+ position = this.getPoint( index );
+ tmpVec.copy( position );
+
+ totalLength += tmpVec.distanceTo( oldPosition );
+
+ oldPosition.copy( position );
+
+ point = ( this.points.length - 1 ) * index;
+ intPoint = Math.floor( point );
+
+ if ( intPoint != oldIntPoint ) {
+
+ chunkLengths[ intPoint ] = totalLength;
+ oldIntPoint = intPoint;
+
+ }
+
+ }
+
+ // last point ends with total length
+
+ chunkLengths[ chunkLengths.length ] = totalLength;
+
+ return { chunks: chunkLengths, total: totalLength };
+
+ };
+
+ this.reparametrizeByArcLength = function ( samplingCoef ) {
+
+ var i, j,
+ index, indexCurrent, indexNext,
+ linearDistance, realDistance,
+ sampling, position,
+ newpoints = [],
+ tmpVec = new THREE.Vector3(),
+ sl = this.getLength();
+
+ newpoints.push( tmpVec.copy( this.points[ 0 ] ).clone() );
+
+ for ( i = 1; i < this.points.length; i++ ) {
+
+ //tmpVec.copy( this.points[ i - 1 ] );
+ //linearDistance = tmpVec.distanceTo( this.points[ i ] );
+
+ realDistance = sl.chunks[ i ] - sl.chunks[ i - 1 ];
+
+ sampling = Math.ceil( samplingCoef * realDistance / sl.total );
+
+ indexCurrent = ( i - 1 ) / ( this.points.length - 1 );
+ indexNext = i / ( this.points.length - 1 );
+
+ for ( j = 1; j < sampling - 1; j++ ) {
+
+ index = indexCurrent + j * ( 1 / sampling ) * ( indexNext - indexCurrent );
+
+ position = this.getPoint( index );
+ newpoints.push( tmpVec.copy( position ).clone() );
+
+ }
+
+ newpoints.push( tmpVec.copy( this.points[ i ] ).clone() );
+
+ }
+
+ this.points = newpoints;
+
+ };
+
+ // Catmull-Rom
+
+ function interpolate( p0, p1, p2, p3, t, t2, t3 ) {
+
+ var v0 = ( p2 - p0 ) * 0.5,
+ v1 = ( p3 - p1 ) * 0.5;
+
+ return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+ };
+
+};
+
+/**
+ * @author bhouston / http://exocortex.com
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Triangle = function ( a, b, c ) {
+
+ this.a = ( a !== undefined ) ? a : new THREE.Vector3();
+ this.b = ( b !== undefined ) ? b : new THREE.Vector3();
+ this.c = ( c !== undefined ) ? c : new THREE.Vector3();
+
+};
+
+THREE.Triangle.normal = function() {
+
+ var v0 = new THREE.Vector3();
+
+ return function ( a, b, c, optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ result.subVectors( c, b );
+ v0.subVectors( a, b );
+ result.cross( v0 );
+
+ var resultLengthSq = result.lengthSq();
+ if( resultLengthSq > 0 ) {
+
+ return result.multiplyScalar( 1 / Math.sqrt( resultLengthSq ) );
+
+ }
+
+ return result.set( 0, 0, 0 );
+
+ };
+
+}();
+
+// static/instance method to calculate barycoordinates
+// based on: http://www.blackpawn.com/texts/pointinpoly/default.html
+THREE.Triangle.barycoordFromPoint = function() {
+
+ var v0 = new THREE.Vector3();
+ var v1 = new THREE.Vector3();
+ var v2 = new THREE.Vector3();
+
+ return function ( point, a, b, c, optionalTarget ) {
+
+ v0.subVectors( c, a );
+ v1.subVectors( b, a );
+ v2.subVectors( point, a );
+
+ var dot00 = v0.dot( v0 );
+ var dot01 = v0.dot( v1 );
+ var dot02 = v0.dot( v2 );
+ var dot11 = v1.dot( v1 );
+ var dot12 = v1.dot( v2 );
+
+ var denom = ( dot00 * dot11 - dot01 * dot01 );
+
+ var result = optionalTarget || new THREE.Vector3();
+
+ // colinear or singular triangle
+ if( denom == 0 ) {
+ // arbitrary location outside of triangle?
+ // not sure if this is the best idea, maybe should be returning undefined
+ return result.set( -2, -1, -1 );
+ }
+
+ var invDenom = 1 / denom;
+ var u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
+ var v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
+
+ // barycoordinates must always sum to 1
+ return result.set( 1 - u - v, v, u );
+
+ };
+
+}();
+
+THREE.Triangle.containsPoint = function() {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( point, a, b, c ) {
+
+ var result = THREE.Triangle.barycoordFromPoint( point, a, b, c, v1 );
+
+ return ( result.x >= 0 ) && ( result.y >= 0 ) && ( ( result.x + result.y ) <= 1 );
+
+ };
+
+}();
+
+THREE.Triangle.prototype = {
+
+ constructor: THREE.Triangle,
+
+ set: function ( a, b, c ) {
+
+ this.a.copy( a );
+ this.b.copy( b );
+ this.c.copy( c );
+
+ return this;
+
+ },
+
+ setFromPointsAndIndices: function ( points, i0, i1, i2 ) {
+
+ this.a.copy( points[i0] );
+ this.b.copy( points[i1] );
+ this.c.copy( points[i2] );
+
+ return this;
+
+ },
+
+ copy: function ( triangle ) {
+
+ this.a.copy( triangle.a );
+ this.b.copy( triangle.b );
+ this.c.copy( triangle.c );
+
+ return this;
+
+ },
+
+ area: function() {
+
+ var v0 = new THREE.Vector3();
+ var v1 = new THREE.Vector3();
+
+ return function () {
+
+ v0.subVectors( this.c, this.b );
+ v1.subVectors( this.a, this.b );
+
+ return v0.cross( v1 ).length() * 0.5;
+
+ };
+
+ }(),
+
+ midpoint: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Vector3();
+ return result.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 );
+
+ },
+
+ normal: function ( optionalTarget ) {
+
+ return THREE.Triangle.normal( this.a, this.b, this.c, optionalTarget );
+
+ },
+
+ plane: function ( optionalTarget ) {
+
+ var result = optionalTarget || new THREE.Plane();
+
+ return result.setFromCoplanarPoints( this.a, this.b, this.c );
+
+ },
+
+ barycoordFromPoint: function ( point, optionalTarget ) {
+
+ return THREE.Triangle.barycoordFromPoint( point, this.a, this.b, this.c, optionalTarget );
+
+ },
+
+ containsPoint: function ( point ) {
+
+ return THREE.Triangle.containsPoint( point, this.a, this.b, this.c );
+
+ },
+
+ equals: function ( triangle ) {
+
+ return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c );
+
+ },
+
+ clone: function () {
+
+ return new THREE.Triangle().copy( this );
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Vertex = function ( v ) {
+
+ console.warn( 'THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.')
+ return v;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.UV = function ( u, v ) {
+
+ console.warn( 'THREE.UV has been DEPRECATED. Use THREE.Vector2 instead.')
+ return new THREE.Vector2( u, v );
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Clock = function ( autoStart ) {
+
+ this.autoStart = ( autoStart !== undefined ) ? autoStart : true;
+
+ this.startTime = 0;
+ this.oldTime = 0;
+ this.elapsedTime = 0;
+
+ this.running = false;
+
+};
+
+THREE.Clock.prototype = {
+
+ constructor: THREE.Clock,
+
+ start: function () {
+
+ this.startTime = self.performance !== undefined && self.performance.now !== undefined
+ ? self.performance.now()
+ : Date.now();
+
+ this.oldTime = this.startTime;
+ this.running = true;
+ },
+
+ stop: function () {
+
+ this.getElapsedTime();
+ this.running = false;
+
+ },
+
+ getElapsedTime: function () {
+
+ this.getDelta();
+ return this.elapsedTime;
+
+ },
+
+ getDelta: function () {
+
+ var diff = 0;
+
+ if ( this.autoStart && ! this.running ) {
+
+ this.start();
+
+ }
+
+ if ( this.running ) {
+
+ var newTime = self.performance !== undefined && self.performance.now !== undefined
+ ? self.performance.now()
+ : Date.now();
+
+ diff = 0.001 * ( newTime - this.oldTime );
+ this.oldTime = newTime;
+
+ this.elapsedTime += diff;
+
+ }
+
+ return diff;
+
+ }
+
+};
+
+/**
+ * https://github.com/mrdoob/eventdispatcher.js/
+ */
+
+THREE.EventDispatcher = function () {}
+
+THREE.EventDispatcher.prototype = {
+
+ constructor: THREE.EventDispatcher,
+
+ apply: function ( object ) {
+
+ object.addEventListener = THREE.EventDispatcher.prototype.addEventListener;
+ object.hasEventListener = THREE.EventDispatcher.prototype.hasEventListener;
+ object.removeEventListener = THREE.EventDispatcher.prototype.removeEventListener;
+ object.dispatchEvent = THREE.EventDispatcher.prototype.dispatchEvent;
+
+ },
+
+ addEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) this._listeners = {};
+
+ var listeners = this._listeners;
+
+ if ( listeners[ type ] === undefined ) {
+
+ listeners[ type ] = [];
+
+ }
+
+ if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+
+ listeners[ type ].push( listener );
+
+ }
+
+ },
+
+ hasEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) return false;
+
+ var listeners = this._listeners;
+
+ if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) {
+
+ return true;
+
+ }
+
+ return false;
+
+ },
+
+ removeEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) return;
+
+ var listeners = this._listeners;
+ var index = listeners[ type ].indexOf( listener );
+
+ if ( index !== - 1 ) {
+
+ listeners[ type ].splice( index, 1 );
+
+ }
+
+ },
+
+ dispatchEvent: function ( event ) {
+
+ if ( this._listeners === undefined ) return;
+
+ var listeners = this._listeners;
+ var listenerArray = listeners[ event.type ];
+
+ if ( listenerArray !== undefined ) {
+
+ event.target = this;
+
+ for ( var i = 0, l = listenerArray.length; i < l; i ++ ) {
+
+ listenerArray[ i ].call( this, event );
+
+ }
+
+ }
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author bhouston / http://exocortex.com/
+ * @author stephomi / http://stephaneginier.com/
+ */
+
+( function ( THREE ) {
+
+ THREE.Raycaster = function ( origin, direction, near, far ) {
+
+ this.ray = new THREE.Ray( origin, direction );
+ // direction is assumed to be normalized (for accurate distance calculations)
+
+ this.near = near || 0;
+ this.far = far || Infinity;
+
+ };
+
+ var sphere = new THREE.Sphere();
+ var localRay = new THREE.Ray();
+ var facePlane = new THREE.Plane();
+ var intersectPoint = new THREE.Vector3();
+ var matrixPosition = new THREE.Vector3();
+
+ var inverseMatrix = new THREE.Matrix4();
+
+ var descSort = function ( a, b ) {
+
+ return a.distance - b.distance;
+
+ };
+
+ var vA = new THREE.Vector3();
+ var vB = new THREE.Vector3();
+ var vC = new THREE.Vector3();
+
+ var intersectObject = function ( object, raycaster, intersects ) {
+
+ if ( object instanceof THREE.Particle ) {
+
+ matrixPosition.getPositionFromMatrix( object.matrixWorld );
+ var distance = raycaster.ray.distanceToPoint( matrixPosition );
+
+ if ( distance > object.scale.x ) {
+
+ return intersects;
+
+ }
+
+ intersects.push( {
+
+ distance: distance,
+ point: object.position,
+ face: null,
+ object: object
+
+ } );
+
+ } else if ( object instanceof THREE.LOD ) {
+
+ matrixPosition.getPositionFromMatrix( object.matrixWorld );
+ var distance = raycaster.ray.origin.distanceTo( matrixPosition );
+
+ intersectObject( object.getObjectForDistance( distance ), raycaster, intersects );
+
+ } else if ( object instanceof THREE.Mesh ) {
+
+ var geometry = object.geometry;
+
+ // Checking boundingSphere distance to ray
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ sphere.copy( geometry.boundingSphere );
+ sphere.applyMatrix4( object.matrixWorld );
+
+ if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {
+
+ return intersects;
+
+ }
+
+ // Check boundingBox before continuing
+
+ inverseMatrix.getInverse( object.matrixWorld );
+ localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );
+
+ if ( geometry.boundingBox !== null ) {
+
+ if ( localRay.isIntersectionBox( geometry.boundingBox ) === false ) {
+
+ return intersects;
+
+ }
+
+ }
+
+ if ( geometry instanceof THREE.BufferGeometry ) {
+
+ var material = object.material;
+
+ if ( material === undefined ) return intersects;
+ if ( geometry.dynamic === false ) return intersects;
+
+ var a, b, c;
+ var precision = raycaster.precision;
+
+ if ( geometry.attributes.index !== undefined ) {
+
+ var offsets = geometry.offsets;
+ var indices = geometry.attributes.index.array;
+ var positions = geometry.attributes.position.array;
+ var offLength = geometry.offsets.length;
+
+ var fl = geometry.attributes.index.array.length / 3;
+
+ for ( var oi = 0; oi < offLength; ++oi ) {
+
+ var start = offsets[ oi ].start;
+ var count = offsets[ oi ].count;
+ var index = offsets[ oi ].index;
+
+ for ( var i = start, il = start + count; i < il; i += 3 ) {
+
+ a = index + indices[ i ];
+ b = index + indices[ i + 1 ];
+ c = index + indices[ i + 2 ];
+
+ vA.set(
+ positions[ a * 3 ],
+ positions[ a * 3 + 1 ],
+ positions[ a * 3 + 2 ]
+ );
+ vB.set(
+ positions[ b * 3 ],
+ positions[ b * 3 + 1 ],
+ positions[ b * 3 + 2 ]
+ );
+ vC.set(
+ positions[ c * 3 ],
+ positions[ c * 3 + 1 ],
+ positions[ c * 3 + 2 ]
+ );
+
+ var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide );
+
+ if ( intersectionPoint === null ) continue;
+
+ intersectionPoint.applyMatrix4( object.matrixWorld );
+
+ var distance = raycaster.ray.origin.distanceTo( intersectionPoint );
+
+ if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ point: intersectionPoint,
+ face: null,
+ faceIndex: null,
+ object: object
+
+ } );
+
+ }
+
+ }
+
+ } else {
+
+ var offsets = geometry.offsets;
+ var positions = geometry.attributes.position.array;
+ var offLength = geometry.offsets.length;
+
+ var fl = geometry.attributes.position.array.length;
+
+ for ( var i = 0; i < fl; i += 3 ) {
+
+ a = i;
+ b = i + 1;
+ c = i + 2;
+
+ vA.set(
+ positions[ a * 3 ],
+ positions[ a * 3 + 1 ],
+ positions[ a * 3 + 2 ]
+ );
+ vB.set(
+ positions[ b * 3 ],
+ positions[ b * 3 + 1 ],
+ positions[ b * 3 + 2 ]
+ );
+ vC.set(
+ positions[ c * 3 ],
+ positions[ c * 3 + 1 ],
+ positions[ c * 3 + 2 ]
+ );
+
+ var intersectionPoint = localRay.intersectTriangle( vA, vB, vC, material.side !== THREE.DoubleSide );
+
+ if ( intersectionPoint === null ) continue;
+
+ intersectionPoint.applyMatrix4( object.matrixWorld );
+
+ var distance = raycaster.ray.origin.distanceTo( intersectionPoint );
+
+ if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ point: intersectionPoint,
+ face: null,
+ faceIndex: null,
+ object: object
+
+ } );
+
+ }
+
+ }
+
+ } else if ( geometry instanceof THREE.Geometry ) {
+
+ var isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
+ var objectMaterials = isFaceMaterial === true ? object.material.materials : null;
+
+ var a, b, c, d;
+ var precision = raycaster.precision;
+
+ var vertices = geometry.vertices;
+
+ for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
+
+ var face = geometry.faces[ f ];
+
+ var material = isFaceMaterial === true ? objectMaterials[ face.materialIndex ] : object.material;
+
+ if ( material === undefined ) continue;
+
+ a = vertices[ face.a ];
+ b = vertices[ face.b ];
+ c = vertices[ face.c ];
+
+ var intersectionPoint = localRay.intersectTriangle( a, b, c, material.side !== THREE.DoubleSide );
+
+ if ( intersectionPoint === null ) continue;
+
+ intersectionPoint.applyMatrix4( object.matrixWorld );
+
+ var distance = raycaster.ray.origin.distanceTo( intersectionPoint );
+
+ if ( distance < precision || distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ point: intersectionPoint,
+ face: face,
+ faceIndex: f,
+ object: object
+
+ } );
+
+ }
+
+ }
+
+ } else if ( object instanceof THREE.Line ) {
+
+ var precision = raycaster.linePrecision;
+ var precisionSq = precision * precision;
+
+ var geometry = object.geometry;
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ // Checking boundingSphere distance to ray
+
+ sphere.copy( geometry.boundingSphere );
+ sphere.applyMatrix4( object.matrixWorld );
+
+ if ( raycaster.ray.isIntersectionSphere( sphere ) === false ) {
+
+ return intersects;
+
+ }
+
+ inverseMatrix.getInverse( object.matrixWorld );
+ localRay.copy( raycaster.ray ).applyMatrix4( inverseMatrix );
+
+ /* if ( geometry instanceof THREE.BufferGeometry ) {
+
+ } else */ if ( geometry instanceof THREE.Geometry ) {
+
+ var vertices = geometry.vertices;
+ var nbVertices = vertices.length;
+ var interSegment = new THREE.Vector3();
+ var interRay = new THREE.Vector3();
+ var step = object.type === THREE.LineStrip ? 1 : 2;
+
+ for ( var i = 0; i < nbVertices - 1; i = i + step ) {
+
+ var distSq = localRay.distanceSqToSegment( vertices[ i ], vertices[ i + 1 ], interRay, interSegment );
+
+ if ( distSq > precisionSq ) continue;
+
+ var distance = localRay.origin.distanceTo( interRay );
+
+ if ( distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ // What do we want? intersection point on the ray or on the segment??
+ // point: raycaster.ray.at( distance ),
+ point: interSegment.clone().applyMatrix4( object.matrixWorld ),
+ face: null,
+ faceIndex: null,
+ object: object
+
+ } );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ var intersectDescendants = function ( object, raycaster, intersects ) {
+
+ var descendants = object.getDescendants();
+
+ for ( var i = 0, l = descendants.length; i < l; i ++ ) {
+
+ intersectObject( descendants[ i ], raycaster, intersects );
+
+ }
+ };
+
+ //
+
+ THREE.Raycaster.prototype.precision = 0.0001;
+ THREE.Raycaster.prototype.linePrecision = 1;
+
+ THREE.Raycaster.prototype.set = function ( origin, direction ) {
+
+ this.ray.set( origin, direction );
+ // direction is assumed to be normalized (for accurate distance calculations)
+
+ };
+
+ THREE.Raycaster.prototype.intersectObject = function ( object, recursive ) {
+
+ var intersects = [];
+
+ if ( recursive === true ) {
+
+ intersectDescendants( object, this, intersects );
+
+ }
+
+ intersectObject( object, this, intersects );
+
+ intersects.sort( descSort );
+
+ return intersects;
+
+ };
+
+ THREE.Raycaster.prototype.intersectObjects = function ( objects, recursive ) {
+
+ var intersects = [];
+
+ for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+ intersectObject( objects[ i ], this, intersects );
+
+ if ( recursive === true ) {
+
+ intersectDescendants( objects[ i ], this, intersects );
+
+ }
+
+ }
+
+ intersects.sort( descSort );
+
+ return intersects;
+
+ };
+
+}( THREE ) );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.Object3D = function () {
+
+ this.id = THREE.Object3DIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ this.parent = undefined;
+ this.children = [];
+
+ this.up = new THREE.Vector3( 0, 1, 0 );
+
+ this.position = new THREE.Vector3();
+ this.rotation = new THREE.Euler();
+ this.quaternion = new THREE.Quaternion();
+ this.scale = new THREE.Vector3( 1, 1, 1 );
+
+ // keep rotation and quaternion in sync
+
+ this.rotation._quaternion = this.quaternion;
+ this.quaternion._euler = this.rotation;
+
+ this.renderDepth = null;
+
+ this.rotationAutoUpdate = true;
+
+ this.matrix = new THREE.Matrix4();
+ this.matrixWorld = new THREE.Matrix4();
+
+ this.matrixAutoUpdate = true;
+ this.matrixWorldNeedsUpdate = true;
+
+ this.visible = true;
+
+ this.castShadow = false;
+ this.receiveShadow = false;
+
+ this.frustumCulled = true;
+
+ this.userData = {};
+
+};
+
+
+THREE.Object3D.prototype = {
+
+ constructor: THREE.Object3D,
+
+ get eulerOrder () {
+
+ console.warn( 'DEPRECATED: Object3D\'s .eulerOrder has been moved to Object3D\'s .rotation.order.' );
+
+ return this.rotation.order;
+
+ },
+
+ set eulerOrder ( value ) {
+
+ console.warn( 'DEPRECATED: Object3D\'s .eulerOrder has been moved to Object3D\'s .rotation.order.' );
+
+ this.rotation.order = value;
+
+ },
+
+ get useQuaternion () {
+
+ console.warn( 'DEPRECATED: Object3D\'s .useQuaternion has been removed. The library now uses quaternions by default.' );
+
+ },
+
+ set useQuaternion ( value ) {
+
+ console.warn( 'DEPRECATED: Object3D\'s .useQuaternion has been removed. The library now uses quaternions by default.' );
+
+ },
+
+ applyMatrix: function () {
+
+ var m1 = new THREE.Matrix4();
+
+ return function ( matrix ) {
+
+ this.matrix.multiplyMatrices( matrix, this.matrix );
+
+ this.position.getPositionFromMatrix( this.matrix );
+
+ this.scale.getScaleFromMatrix( this.matrix );
+
+ m1.extractRotation( this.matrix );
+
+ this.quaternion.setFromRotationMatrix( m1 );
+
+ }
+
+ }(),
+
+ setRotationFromAxisAngle: function ( axis, angle ) {
+
+ // assumes axis is normalized
+
+ this.quaternion.setFromAxisAngle( axis, angle );
+
+ },
+
+ setRotationFromEuler: function ( euler ) {
+
+ this.quaternion.setFromEuler( euler, true );
+
+ },
+
+ setRotationFromMatrix: function ( m ) {
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ this.quaternion.setFromRotationMatrix( m );
+
+ },
+
+ setRotationFromQuaternion: function ( q ) {
+
+ // assumes q is normalized
+
+ this.quaternion.copy( q );
+
+ },
+
+ rotateOnAxis: function() {
+
+ // rotate object on axis in object space
+ // axis is assumed to be normalized
+
+ var q1 = new THREE.Quaternion();
+
+ return function ( axis, angle ) {
+
+ q1.setFromAxisAngle( axis, angle );
+
+ this.quaternion.multiply( q1 );
+
+ return this;
+
+ }
+
+ }(),
+
+ rotateX: function () {
+
+ var v1 = new THREE.Vector3( 1, 0, 0 );
+
+ return function ( angle ) {
+
+ return this.rotateOnAxis( v1, angle );
+
+ };
+
+ }(),
+
+ rotateY: function () {
+
+ var v1 = new THREE.Vector3( 0, 1, 0 );
+
+ return function ( angle ) {
+
+ return this.rotateOnAxis( v1, angle );
+
+ };
+
+ }(),
+
+ rotateZ: function () {
+
+ var v1 = new THREE.Vector3( 0, 0, 1 );
+
+ return function ( angle ) {
+
+ return this.rotateOnAxis( v1, angle );
+
+ };
+
+ }(),
+
+ translateOnAxis: function () {
+
+ // translate object by distance along axis in object space
+ // axis is assumed to be normalized
+
+ var v1 = new THREE.Vector3();
+
+ return function ( axis, distance ) {
+
+ v1.copy( axis );
+
+ v1.applyQuaternion( this.quaternion );
+
+ this.position.add( v1.multiplyScalar( distance ) );
+
+ return this;
+
+ }
+
+ }(),
+
+ translate: function ( distance, axis ) {
+
+ console.warn( 'DEPRECATED: Object3D\'s .translate() has been removed. Use .translateOnAxis( axis, distance ) instead. Note args have been changed.' );
+ return this.translateOnAxis( axis, distance );
+
+ },
+
+ translateX: function () {
+
+ var v1 = new THREE.Vector3( 1, 0, 0 );
+
+ return function ( distance ) {
+
+ return this.translateOnAxis( v1, distance );
+
+ };
+
+ }(),
+
+ translateY: function () {
+
+ var v1 = new THREE.Vector3( 0, 1, 0 );
+
+ return function ( distance ) {
+
+ return this.translateOnAxis( v1, distance );
+
+ };
+
+ }(),
+
+ translateZ: function () {
+
+ var v1 = new THREE.Vector3( 0, 0, 1 );
+
+ return function ( distance ) {
+
+ return this.translateOnAxis( v1, distance );
+
+ };
+
+ }(),
+
+ localToWorld: function ( vector ) {
+
+ return vector.applyMatrix4( this.matrixWorld );
+
+ },
+
+ worldToLocal: function () {
+
+ var m1 = new THREE.Matrix4();
+
+ return function ( vector ) {
+
+ return vector.applyMatrix4( m1.getInverse( this.matrixWorld ) );
+
+ };
+
+ }(),
+
+ lookAt: function () {
+
+ // This routine does not support objects with rotated and/or translated parent(s)
+
+ var m1 = new THREE.Matrix4();
+
+ return function ( vector ) {
+
+ m1.lookAt( vector, this.position, this.up );
+
+ this.quaternion.setFromRotationMatrix( m1 );
+
+ };
+
+ }(),
+
+ add: function ( object ) {
+
+ if ( object === this ) {
+
+ console.warn( 'THREE.Object3D.add: An object can\'t be added as a child of itself.' );
+ return;
+
+ }
+
+ if ( object instanceof THREE.Object3D ) {
+
+ if ( object.parent !== undefined ) {
+
+ object.parent.remove( object );
+
+ }
+
+ object.parent = this;
+ object.dispatchEvent( { type: 'added' } );
+
+ this.children.push( object );
+
+ // add to scene
+
+ var scene = this;
+
+ while ( scene.parent !== undefined ) {
+
+ scene = scene.parent;
+
+ }
+
+ if ( scene !== undefined && scene instanceof THREE.Scene ) {
+
+ scene.__addObject( object );
+
+ }
+
+ }
+
+ },
+
+ remove: function ( object ) {
+
+ var index = this.children.indexOf( object );
+
+ if ( index !== - 1 ) {
+
+ object.parent = undefined;
+ object.dispatchEvent( { type: 'removed' } );
+
+ this.children.splice( index, 1 );
+
+ // remove from scene
+
+ var scene = this;
+
+ while ( scene.parent !== undefined ) {
+
+ scene = scene.parent;
+
+ }
+
+ if ( scene !== undefined && scene instanceof THREE.Scene ) {
+
+ scene.__removeObject( object );
+
+ }
+
+ }
+
+ },
+
+ traverse: function ( callback ) {
+
+ callback( this );
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ this.children[ i ].traverse( callback );
+
+ }
+
+ },
+
+ getObjectById: function ( id, recursive ) {
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ var child = this.children[ i ];
+
+ if ( child.id === id ) {
+
+ return child;
+
+ }
+
+ if ( recursive === true ) {
+
+ child = child.getObjectById( id, recursive );
+
+ if ( child !== undefined ) {
+
+ return child;
+
+ }
+
+ }
+
+ }
+
+ return undefined;
+
+ },
+
+ getObjectByName: function ( name, recursive ) {
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ var child = this.children[ i ];
+
+ if ( child.name === name ) {
+
+ return child;
+
+ }
+
+ if ( recursive === true ) {
+
+ child = child.getObjectByName( name, recursive );
+
+ if ( child !== undefined ) {
+
+ return child;
+
+ }
+
+ }
+
+ }
+
+ return undefined;
+
+ },
+
+ getChildByName: function ( name, recursive ) {
+
+ console.warn( 'DEPRECATED: Object3D\'s .getChildByName() has been renamed to .getObjectByName().' );
+ return this.getObjectByName( name, recursive );
+
+ },
+
+ getDescendants: function ( array ) {
+
+ if ( array === undefined ) array = [];
+
+ Array.prototype.push.apply( array, this.children );
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ this.children[ i ].getDescendants( array );
+
+ }
+
+ return array;
+
+ },
+
+ updateMatrix: function () {
+
+ this.matrix.compose( this.position, this.quaternion, this.scale );
+
+ this.matrixWorldNeedsUpdate = true;
+
+ },
+
+ updateMatrixWorld: function ( force ) {
+
+ if ( this.matrixAutoUpdate === true ) this.updateMatrix();
+
+ if ( this.matrixWorldNeedsUpdate === true || force === true ) {
+
+ if ( this.parent === undefined ) {
+
+ this.matrixWorld.copy( this.matrix );
+
+ } else {
+
+ this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+ }
+
+ this.matrixWorldNeedsUpdate = false;
+
+ force = true;
+
+ }
+
+ // update children
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ this.children[ i ].updateMatrixWorld( force );
+
+ }
+
+ },
+
+ clone: function ( object, recursive ) {
+
+ if ( object === undefined ) object = new THREE.Object3D();
+ if ( recursive === undefined ) recursive = true;
+
+ object.name = this.name;
+
+ object.up.copy( this.up );
+
+ object.position.copy( this.position );
+ object.quaternion.copy( this.quaternion );
+ object.scale.copy( this.scale );
+
+ object.renderDepth = this.renderDepth;
+
+ object.rotationAutoUpdate = this.rotationAutoUpdate;
+
+ object.matrix.copy( this.matrix );
+ object.matrixWorld.copy( this.matrixWorld );
+
+ object.matrixAutoUpdate = this.matrixAutoUpdate;
+ object.matrixWorldNeedsUpdate = this.matrixWorldNeedsUpdate;
+
+ object.visible = this.visible;
+
+ object.castShadow = this.castShadow;
+ object.receiveShadow = this.receiveShadow;
+
+ object.frustumCulled = this.frustumCulled;
+
+ object.userData = JSON.parse( JSON.stringify( this.userData ) );
+
+ if ( recursive === true ) {
+
+ for ( var i = 0; i < this.children.length; i ++ ) {
+
+ var child = this.children[ i ];
+ object.add( child.clone() );
+
+ }
+
+ }
+
+ return object;
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Object3D.prototype );
+
+THREE.Object3DIdCount = 0;
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author julianwa / https://github.com/julianwa
+ */
+
+THREE.Projector = function () {
+
+ var _object, _objectCount, _objectPool = [], _objectPoolLength = 0,
+ _vertex, _vertexCount, _vertexPool = [], _vertexPoolLength = 0,
+ _face, _face3Count, _face3Pool = [], _face3PoolLength = 0,
+ _line, _lineCount, _linePool = [], _linePoolLength = 0,
+ _particle, _particleCount, _particlePool = [], _particlePoolLength = 0,
+
+ _renderData = { objects: [], sprites: [], lights: [], elements: [] },
+
+ _vector3 = new THREE.Vector3(),
+ _vector4 = new THREE.Vector4(),
+
+ _clipBox = new THREE.Box3( new THREE.Vector3( -1, -1, -1 ), new THREE.Vector3( 1, 1, 1 ) ),
+ _boundingBox = new THREE.Box3(),
+ _points3 = new Array( 3 ),
+ _points4 = new Array( 4 ),
+
+ _viewMatrix = new THREE.Matrix4(),
+ _viewProjectionMatrix = new THREE.Matrix4(),
+
+ _modelMatrix,
+ _modelViewProjectionMatrix = new THREE.Matrix4(),
+
+ _normalMatrix = new THREE.Matrix3(),
+ _normalViewMatrix = new THREE.Matrix3(),
+
+ _centroid = new THREE.Vector3(),
+
+ _frustum = new THREE.Frustum(),
+
+ _clippedVertex1PositionScreen = new THREE.Vector4(),
+ _clippedVertex2PositionScreen = new THREE.Vector4();
+
+ this.projectVector = function ( vector, camera ) {
+
+ camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+
+ _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+
+ return vector.applyProjection( _viewProjectionMatrix );
+
+ };
+
+ this.unprojectVector = function ( vector, camera ) {
+
+ camera.projectionMatrixInverse.getInverse( camera.projectionMatrix );
+
+ _viewProjectionMatrix.multiplyMatrices( camera.matrixWorld, camera.projectionMatrixInverse );
+
+ return vector.applyProjection( _viewProjectionMatrix );
+
+ };
+
+ this.pickingRay = function ( vector, camera ) {
+
+ // set two vectors with opposing z values
+ vector.z = -1.0;
+ var end = new THREE.Vector3( vector.x, vector.y, 1.0 );
+
+ this.unprojectVector( vector, camera );
+ this.unprojectVector( end, camera );
+
+ // find direction from vector to end
+ end.sub( vector ).normalize();
+
+ return new THREE.Raycaster( vector, end );
+
+ };
+
+ var getObject = function ( object ) {
+
+ _object = getNextObjectInPool();
+ _object.id = object.id;
+ _object.object = object;
+
+ if ( object.renderDepth !== null ) {
+
+ _object.z = object.renderDepth;
+
+ } else {
+
+ _vector3.getPositionFromMatrix( object.matrixWorld );
+ _vector3.applyProjection( _viewProjectionMatrix );
+ _object.z = _vector3.z;
+
+ }
+
+ return _object;
+
+ };
+
+ var projectObject = function ( object ) {
+
+ if ( object.visible === false ) return;
+
+ if ( object instanceof THREE.Light ) {
+
+ _renderData.lights.push( object );
+
+ } else if ( object instanceof THREE.Mesh || object instanceof THREE.Line ) {
+
+ if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) {
+
+ _renderData.objects.push( getObject( object ) );
+
+ }
+
+ } else if ( object instanceof THREE.Sprite || object instanceof THREE.Particle ) {
+
+ _renderData.sprites.push( getObject( object ) );
+
+ }
+
+ for ( var i = 0, l = object.children.length; i < l; i ++ ) {
+
+ projectObject( object.children[ i ] );
+
+ }
+
+ };
+
+ var projectGraph = function ( root, sortObjects ) {
+
+ _objectCount = 0;
+
+ _renderData.objects.length = 0;
+ _renderData.sprites.length = 0;
+ _renderData.lights.length = 0;
+
+ projectObject( root );
+
+ if ( sortObjects === true ) {
+
+ _renderData.objects.sort( painterSort );
+
+ }
+
+ };
+
+ this.projectScene = function ( scene, camera, sortObjects, sortElements ) {
+
+ var visible = false,
+ o, ol, v, vl, f, fl, n, nl, c, cl, u, ul, object,
+ geometry, vertices, faces, face, faceVertexNormals, faceVertexUvs, uvs,
+ v1, v2, v3, v4, isFaceMaterial, objectMaterials;
+
+ _face3Count = 0;
+ _lineCount = 0;
+ _particleCount = 0;
+
+ _renderData.elements.length = 0;
+
+ if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+ if ( camera.parent === undefined ) camera.updateMatrixWorld();
+
+ _viewMatrix.copy( camera.matrixWorldInverse.getInverse( camera.matrixWorld ) );
+ _viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, _viewMatrix );
+
+ _normalViewMatrix.getNormalMatrix( _viewMatrix );
+
+ _frustum.setFromMatrix( _viewProjectionMatrix );
+
+ projectGraph( scene, sortObjects );
+
+ for ( o = 0, ol = _renderData.objects.length; o < ol; o ++ ) {
+
+ object = _renderData.objects[ o ].object;
+
+ _modelMatrix = object.matrixWorld;
+
+ _vertexCount = 0;
+
+ if ( object instanceof THREE.Mesh ) {
+
+ geometry = object.geometry;
+
+ vertices = geometry.vertices;
+ faces = geometry.faces;
+ faceVertexUvs = geometry.faceVertexUvs;
+
+ _normalMatrix.getNormalMatrix( _modelMatrix );
+
+ isFaceMaterial = object.material instanceof THREE.MeshFaceMaterial;
+ objectMaterials = isFaceMaterial === true ? object.material : null;
+
+ for ( v = 0, vl = vertices.length; v < vl; v ++ ) {
+
+ _vertex = getNextVertexInPool();
+
+ _vertex.positionWorld.copy( vertices[ v ] ).applyMatrix4( _modelMatrix );
+ _vertex.positionScreen.copy( _vertex.positionWorld ).applyMatrix4( _viewProjectionMatrix );
+
+ var invW = 1 / _vertex.positionScreen.w;
+
+ _vertex.positionScreen.x *= invW;
+ _vertex.positionScreen.y *= invW;
+ _vertex.positionScreen.z *= invW;
+
+ _vertex.visible = ! ( _vertex.positionScreen.x < -1 || _vertex.positionScreen.x > 1 ||
+ _vertex.positionScreen.y < -1 || _vertex.positionScreen.y > 1 ||
+ _vertex.positionScreen.z < -1 || _vertex.positionScreen.z > 1 );
+
+ }
+
+ for ( f = 0, fl = faces.length; f < fl; f ++ ) {
+
+ face = faces[ f ];
+
+ var material = isFaceMaterial === true
+ ? objectMaterials.materials[ face.materialIndex ]
+ : object.material;
+
+ if ( material === undefined ) continue;
+
+ var side = material.side;
+
+ v1 = _vertexPool[ face.a ];
+ v2 = _vertexPool[ face.b ];
+ v3 = _vertexPool[ face.c ];
+
+ _points3[ 0 ] = v1.positionScreen;
+ _points3[ 1 ] = v2.positionScreen;
+ _points3[ 2 ] = v3.positionScreen;
+
+ if ( v1.visible === true || v2.visible === true || v3.visible === true ||
+ _clipBox.isIntersectionBox( _boundingBox.setFromPoints( _points3 ) ) ) {
+
+ visible = ( ( v3.positionScreen.x - v1.positionScreen.x ) *
+ ( v2.positionScreen.y - v1.positionScreen.y ) -
+ ( v3.positionScreen.y - v1.positionScreen.y ) *
+ ( v2.positionScreen.x - v1.positionScreen.x ) ) < 0;
+
+ if ( side === THREE.DoubleSide || visible === ( side === THREE.FrontSide ) ) {
+
+ _face = getNextFace3InPool();
+
+ _face.id = object.id;
+ _face.v1.copy( v1 );
+ _face.v2.copy( v2 );
+ _face.v3.copy( v3 );
+
+ } else {
+
+ continue;
+
+ }
+
+ } else {
+
+ continue;
+
+ }
+
+ _face.normalModel.copy( face.normal );
+
+ if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) {
+
+ _face.normalModel.negate();
+
+ }
+
+ _face.normalModel.applyMatrix3( _normalMatrix ).normalize();
+
+ _face.normalModelView.copy( _face.normalModel ).applyMatrix3( _normalViewMatrix );
+
+ _face.centroidModel.copy( face.centroid ).applyMatrix4( _modelMatrix );
+
+ faceVertexNormals = face.vertexNormals;
+
+ for ( n = 0, nl = Math.min( faceVertexNormals.length, 3 ); n < nl; n ++ ) {
+
+ var normalModel = _face.vertexNormalsModel[ n ];
+ normalModel.copy( faceVertexNormals[ n ] );
+
+ if ( visible === false && ( side === THREE.BackSide || side === THREE.DoubleSide ) ) {
+
+ normalModel.negate();
+
+ }
+
+ normalModel.applyMatrix3( _normalMatrix ).normalize();
+
+ var normalModelView = _face.vertexNormalsModelView[ n ];
+ normalModelView.copy( normalModel ).applyMatrix3( _normalViewMatrix );
+
+ }
+
+ _face.vertexNormalsLength = faceVertexNormals.length;
+
+ for ( c = 0, cl = Math.min( faceVertexUvs.length, 3 ); c < cl; c ++ ) {
+
+ uvs = faceVertexUvs[ c ][ f ];
+
+ if ( uvs === undefined ) continue;
+
+ for ( u = 0, ul = uvs.length; u < ul; u ++ ) {
+
+ _face.uvs[ c ][ u ] = uvs[ u ];
+
+ }
+
+ }
+
+ _face.color = face.color;
+ _face.material = material;
+
+ _centroid.copy( _face.centroidModel ).applyProjection( _viewProjectionMatrix );
+
+ _face.z = _centroid.z;
+
+ _renderData.elements.push( _face );
+
+ }
+
+ } else if ( object instanceof THREE.Line ) {
+
+ _modelViewProjectionMatrix.multiplyMatrices( _viewProjectionMatrix, _modelMatrix );
+
+ vertices = object.geometry.vertices;
+
+ v1 = getNextVertexInPool();
+ v1.positionScreen.copy( vertices[ 0 ] ).applyMatrix4( _modelViewProjectionMatrix );
+
+ // Handle LineStrip and LinePieces
+ var step = object.type === THREE.LinePieces ? 2 : 1;
+
+ for ( v = 1, vl = vertices.length; v < vl; v ++ ) {
+
+ v1 = getNextVertexInPool();
+ v1.positionScreen.copy( vertices[ v ] ).applyMatrix4( _modelViewProjectionMatrix );
+
+ if ( ( v + 1 ) % step > 0 ) continue;
+
+ v2 = _vertexPool[ _vertexCount - 2 ];
+
+ _clippedVertex1PositionScreen.copy( v1.positionScreen );
+ _clippedVertex2PositionScreen.copy( v2.positionScreen );
+
+ if ( clipLine( _clippedVertex1PositionScreen, _clippedVertex2PositionScreen ) === true ) {
+
+ // Perform the perspective divide
+ _clippedVertex1PositionScreen.multiplyScalar( 1 / _clippedVertex1PositionScreen.w );
+ _clippedVertex2PositionScreen.multiplyScalar( 1 / _clippedVertex2PositionScreen.w );
+
+ _line = getNextLineInPool();
+
+ _line.id = object.id;
+ _line.v1.positionScreen.copy( _clippedVertex1PositionScreen );
+ _line.v2.positionScreen.copy( _clippedVertex2PositionScreen );
+
+ _line.z = Math.max( _clippedVertex1PositionScreen.z, _clippedVertex2PositionScreen.z );
+
+ _line.material = object.material;
+
+ if ( object.material.vertexColors === THREE.VertexColors ) {
+
+ _line.vertexColors[ 0 ].copy( object.geometry.colors[ v ] );
+ _line.vertexColors[ 1 ].copy( object.geometry.colors[ v - 1 ] );
+
+ }
+
+ _renderData.elements.push( _line );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ for ( o = 0, ol = _renderData.sprites.length; o < ol; o++ ) {
+
+ object = _renderData.sprites[ o ].object;
+
+ _modelMatrix = object.matrixWorld;
+
+ if ( object instanceof THREE.Particle ) {
+
+ _vector4.set( _modelMatrix.elements[12], _modelMatrix.elements[13], _modelMatrix.elements[14], 1 );
+ _vector4.applyMatrix4( _viewProjectionMatrix );
+
+ var invW = 1 / _vector4.w;
+
+ _vector4.z *= invW;
+
+ if ( _vector4.z > 0 && _vector4.z < 1 ) {
+
+ _particle = getNextParticleInPool();
+ _particle.id = object.id;
+ _particle.x = _vector4.x * invW;
+ _particle.y = _vector4.y * invW;
+ _particle.z = _vector4.z;
+ _particle.object = object;
+
+ _particle.rotation = object.rotation.z;
+
+ _particle.scale.x = object.scale.x * Math.abs( _particle.x - ( _vector4.x + camera.projectionMatrix.elements[0] ) / ( _vector4.w + camera.projectionMatrix.elements[12] ) );
+ _particle.scale.y = object.scale.y * Math.abs( _particle.y - ( _vector4.y + camera.projectionMatrix.elements[5] ) / ( _vector4.w + camera.projectionMatrix.elements[13] ) );
+
+ _particle.material = object.material;
+
+ _renderData.elements.push( _particle );
+
+ }
+
+ }
+
+ }
+
+ if ( sortElements === true ) _renderData.elements.sort( painterSort );
+
+ return _renderData;
+
+ };
+
+ // Pools
+
+ function getNextObjectInPool() {
+
+ if ( _objectCount === _objectPoolLength ) {
+
+ var object = new THREE.RenderableObject();
+ _objectPool.push( object );
+ _objectPoolLength ++;
+ _objectCount ++;
+ return object;
+
+ }
+
+ return _objectPool[ _objectCount ++ ];
+
+ }
+
+ function getNextVertexInPool() {
+
+ if ( _vertexCount === _vertexPoolLength ) {
+
+ var vertex = new THREE.RenderableVertex();
+ _vertexPool.push( vertex );
+ _vertexPoolLength ++;
+ _vertexCount ++;
+ return vertex;
+
+ }
+
+ return _vertexPool[ _vertexCount ++ ];
+
+ }
+
+ function getNextFace3InPool() {
+
+ if ( _face3Count === _face3PoolLength ) {
+
+ var face = new THREE.RenderableFace3();
+ _face3Pool.push( face );
+ _face3PoolLength ++;
+ _face3Count ++;
+ return face;
+
+ }
+
+ return _face3Pool[ _face3Count ++ ];
+
+
+ }
+
+ function getNextLineInPool() {
+
+ if ( _lineCount === _linePoolLength ) {
+
+ var line = new THREE.RenderableLine();
+ _linePool.push( line );
+ _linePoolLength ++;
+ _lineCount ++
+ return line;
+
+ }
+
+ return _linePool[ _lineCount ++ ];
+
+ }
+
+ function getNextParticleInPool() {
+
+ if ( _particleCount === _particlePoolLength ) {
+
+ var particle = new THREE.RenderableParticle();
+ _particlePool.push( particle );
+ _particlePoolLength ++;
+ _particleCount ++
+ return particle;
+
+ }
+
+ return _particlePool[ _particleCount ++ ];
+
+ }
+
+ //
+
+ function painterSort( a, b ) {
+
+ if ( a.z !== b.z ) {
+
+ return b.z - a.z;
+
+ } else if ( a.id !== b.id ) {
+
+ return a.id - b.id;
+
+ } else {
+
+ return 0;
+
+ }
+
+ }
+
+ function clipLine( s1, s2 ) {
+
+ var alpha1 = 0, alpha2 = 1,
+
+ // Calculate the boundary coordinate of each vertex for the near and far clip planes,
+ // Z = -1 and Z = +1, respectively.
+ bc1near = s1.z + s1.w,
+ bc2near = s2.z + s2.w,
+ bc1far = - s1.z + s1.w,
+ bc2far = - s2.z + s2.w;
+
+ if ( bc1near >= 0 && bc2near >= 0 && bc1far >= 0 && bc2far >= 0 ) {
+
+ // Both vertices lie entirely within all clip planes.
+ return true;
+
+ } else if ( ( bc1near < 0 && bc2near < 0) || (bc1far < 0 && bc2far < 0 ) ) {
+
+ // Both vertices lie entirely outside one of the clip planes.
+ return false;
+
+ } else {
+
+ // The line segment spans at least one clip plane.
+
+ if ( bc1near < 0 ) {
+
+ // v1 lies outside the near plane, v2 inside
+ alpha1 = Math.max( alpha1, bc1near / ( bc1near - bc2near ) );
+
+ } else if ( bc2near < 0 ) {
+
+ // v2 lies outside the near plane, v1 inside
+ alpha2 = Math.min( alpha2, bc1near / ( bc1near - bc2near ) );
+
+ }
+
+ if ( bc1far < 0 ) {
+
+ // v1 lies outside the far plane, v2 inside
+ alpha1 = Math.max( alpha1, bc1far / ( bc1far - bc2far ) );
+
+ } else if ( bc2far < 0 ) {
+
+ // v2 lies outside the far plane, v2 inside
+ alpha2 = Math.min( alpha2, bc1far / ( bc1far - bc2far ) );
+
+ }
+
+ if ( alpha2 < alpha1 ) {
+
+ // The line segment spans two boundaries, but is outside both of them.
+ // (This can't happen when we're only clipping against just near/far but good
+ // to leave the check here for future usage if other clip planes are added.)
+ return false;
+
+ } else {
+
+ // Update the s1 and s2 vertices to match the clipped line segment.
+ s1.lerp( s2, alpha1 );
+ s2.lerp( s1, 1 - alpha2 );
+
+ return true;
+
+ }
+
+ }
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Face3 = function ( a, b, c, normal, color, materialIndex ) {
+
+ this.a = a;
+ this.b = b;
+ this.c = c;
+
+ this.normal = normal instanceof THREE.Vector3 ? normal : new THREE.Vector3();
+ this.vertexNormals = normal instanceof Array ? normal : [ ];
+
+ this.color = color instanceof THREE.Color ? color : new THREE.Color();
+ this.vertexColors = color instanceof Array ? color : [];
+
+ this.vertexTangents = [];
+
+ this.materialIndex = materialIndex !== undefined ? materialIndex : 0;
+
+ this.centroid = new THREE.Vector3();
+
+};
+
+THREE.Face3.prototype = {
+
+ constructor: THREE.Face3,
+
+ clone: function () {
+
+ var face = new THREE.Face3( this.a, this.b, this.c );
+
+ face.normal.copy( this.normal );
+ face.color.copy( this.color );
+ face.centroid.copy( this.centroid );
+
+ face.materialIndex = this.materialIndex;
+
+ var i, il;
+ for ( i = 0, il = this.vertexNormals.length; i < il; i ++ ) face.vertexNormals[ i ] = this.vertexNormals[ i ].clone();
+ for ( i = 0, il = this.vertexColors.length; i < il; i ++ ) face.vertexColors[ i ] = this.vertexColors[ i ].clone();
+ for ( i = 0, il = this.vertexTangents.length; i < il; i ++ ) face.vertexTangents[ i ] = this.vertexTangents[ i ].clone();
+
+ return face;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Face4 = function ( a, b, c, d, normal, color, materialIndex ) {
+
+ console.warn( 'THREE.Face4 has been removed. A THREE.Face3 will be created instead.')
+
+ return new THREE.Face3( a, b, c, normal, color, materialIndex );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author kile / http://kile.stravaganza.org/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author bhouston / http://exocortex.com
+ */
+
+THREE.Geometry = function () {
+
+ this.id = THREE.GeometryIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ this.vertices = [];
+ this.colors = []; // one-to-one vertex colors, used in ParticleSystem and Line
+
+ this.faces = [];
+
+ this.faceVertexUvs = [[]];
+
+ this.morphTargets = [];
+ this.morphColors = [];
+ this.morphNormals = [];
+
+ this.skinWeights = [];
+ this.skinIndices = [];
+
+ this.lineDistances = [];
+
+ this.boundingBox = null;
+ this.boundingSphere = null;
+
+ this.hasTangents = false;
+
+ this.dynamic = true; // the intermediate typed arrays will be deleted when set to false
+
+ // update flags
+
+ this.verticesNeedUpdate = false;
+ this.elementsNeedUpdate = false;
+ this.uvsNeedUpdate = false;
+ this.normalsNeedUpdate = false;
+ this.tangentsNeedUpdate = false;
+ this.colorsNeedUpdate = false;
+ this.lineDistancesNeedUpdate = false;
+
+ this.buffersNeedUpdate = false;
+
+};
+
+THREE.Geometry.prototype = {
+
+ constructor: THREE.Geometry,
+
+ applyMatrix: function ( matrix ) {
+
+ var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+ for ( var i = 0, il = this.vertices.length; i < il; i ++ ) {
+
+ var vertex = this.vertices[ i ];
+ vertex.applyMatrix4( matrix );
+
+ }
+
+ for ( var i = 0, il = this.faces.length; i < il; i ++ ) {
+
+ var face = this.faces[ i ];
+ face.normal.applyMatrix3( normalMatrix ).normalize();
+
+ for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
+
+ face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize();
+
+ }
+
+ face.centroid.applyMatrix4( matrix );
+
+ }
+
+ if ( this.boundingBox instanceof THREE.Box3 ) {
+
+ this.computeBoundingBox();
+
+ }
+
+ if ( this.boundingSphere instanceof THREE.Sphere ) {
+
+ this.computeBoundingSphere();
+
+ }
+
+ },
+
+ computeCentroids: function () {
+
+ var f, fl, face;
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+ face.centroid.set( 0, 0, 0 );
+
+ face.centroid.add( this.vertices[ face.a ] );
+ face.centroid.add( this.vertices[ face.b ] );
+ face.centroid.add( this.vertices[ face.c ] );
+ face.centroid.divideScalar( 3 );
+
+ }
+
+ },
+
+ computeFaceNormals: function () {
+
+ var cb = new THREE.Vector3(), ab = new THREE.Vector3();
+
+ for ( var f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ var face = this.faces[ f ];
+
+ var vA = this.vertices[ face.a ];
+ var vB = this.vertices[ face.b ];
+ var vC = this.vertices[ face.c ];
+
+ cb.subVectors( vC, vB );
+ ab.subVectors( vA, vB );
+ cb.cross( ab );
+
+ cb.normalize();
+
+ face.normal.copy( cb );
+
+ }
+
+ },
+
+ computeVertexNormals: function ( areaWeighted ) {
+
+ var v, vl, f, fl, face, vertices;
+
+ // create internal buffers for reuse when calling this method repeatedly
+ // (otherwise memory allocation / deallocation every frame is big resource hog)
+
+ if ( this.__tmpVertices === undefined ) {
+
+ this.__tmpVertices = new Array( this.vertices.length );
+ vertices = this.__tmpVertices;
+
+ for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+ vertices[ v ] = new THREE.Vector3();
+
+ }
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+ face.vertexNormals = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+
+ }
+
+ } else {
+
+ vertices = this.__tmpVertices;
+
+ for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+ vertices[ v ].set( 0, 0, 0 );
+
+ }
+
+ }
+
+ if ( areaWeighted ) {
+
+ // vertex normals weighted by triangle areas
+ // http://www.iquilezles.org/www/articles/normals/normals.htm
+
+ var vA, vB, vC, vD;
+ var cb = new THREE.Vector3(), ab = new THREE.Vector3(),
+ db = new THREE.Vector3(), dc = new THREE.Vector3(), bc = new THREE.Vector3();
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ vA = this.vertices[ face.a ];
+ vB = this.vertices[ face.b ];
+ vC = this.vertices[ face.c ];
+
+ cb.subVectors( vC, vB );
+ ab.subVectors( vA, vB );
+ cb.cross( ab );
+
+ vertices[ face.a ].add( cb );
+ vertices[ face.b ].add( cb );
+ vertices[ face.c ].add( cb );
+
+ }
+
+ } else {
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ vertices[ face.a ].add( face.normal );
+ vertices[ face.b ].add( face.normal );
+ vertices[ face.c ].add( face.normal );
+
+ }
+
+ }
+
+ for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+ vertices[ v ].normalize();
+
+ }
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ face.vertexNormals[ 0 ].copy( vertices[ face.a ] );
+ face.vertexNormals[ 1 ].copy( vertices[ face.b ] );
+ face.vertexNormals[ 2 ].copy( vertices[ face.c ] );
+
+ }
+
+ },
+
+ computeMorphNormals: function () {
+
+ var i, il, f, fl, face;
+
+ // save original normals
+ // - create temp variables on first access
+ // otherwise just copy (for faster repeated calls)
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ if ( ! face.__originalFaceNormal ) {
+
+ face.__originalFaceNormal = face.normal.clone();
+
+ } else {
+
+ face.__originalFaceNormal.copy( face.normal );
+
+ }
+
+ if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = [];
+
+ for ( i = 0, il = face.vertexNormals.length; i < il; i ++ ) {
+
+ if ( ! face.__originalVertexNormals[ i ] ) {
+
+ face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone();
+
+ } else {
+
+ face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] );
+
+ }
+
+ }
+
+ }
+
+ // use temp geometry to compute face and vertex normals for each morph
+
+ var tmpGeo = new THREE.Geometry();
+ tmpGeo.faces = this.faces;
+
+ for ( i = 0, il = this.morphTargets.length; i < il; i ++ ) {
+
+ // create on first access
+
+ if ( ! this.morphNormals[ i ] ) {
+
+ this.morphNormals[ i ] = {};
+ this.morphNormals[ i ].faceNormals = [];
+ this.morphNormals[ i ].vertexNormals = [];
+
+ var dstNormalsFace = this.morphNormals[ i ].faceNormals;
+ var dstNormalsVertex = this.morphNormals[ i ].vertexNormals;
+
+ var faceNormal, vertexNormals;
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ faceNormal = new THREE.Vector3();
+ vertexNormals = { a: new THREE.Vector3(), b: new THREE.Vector3(), c: new THREE.Vector3() };
+
+ dstNormalsFace.push( faceNormal );
+ dstNormalsVertex.push( vertexNormals );
+
+ }
+
+ }
+
+ var morphNormals = this.morphNormals[ i ];
+
+ // set vertices to morph target
+
+ tmpGeo.vertices = this.morphTargets[ i ].vertices;
+
+ // compute morph normals
+
+ tmpGeo.computeFaceNormals();
+ tmpGeo.computeVertexNormals();
+
+ // store morph normals
+
+ var faceNormal, vertexNormals;
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ faceNormal = morphNormals.faceNormals[ f ];
+ vertexNormals = morphNormals.vertexNormals[ f ];
+
+ faceNormal.copy( face.normal );
+
+ vertexNormals.a.copy( face.vertexNormals[ 0 ] );
+ vertexNormals.b.copy( face.vertexNormals[ 1 ] );
+ vertexNormals.c.copy( face.vertexNormals[ 2 ] );
+
+ }
+
+ }
+
+ // restore original normals
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ face.normal = face.__originalFaceNormal;
+ face.vertexNormals = face.__originalVertexNormals;
+
+ }
+
+ },
+
+ computeTangents: function () {
+
+ // based on http://www.terathon.com/code/tangent.html
+ // tangents go to vertices
+
+ var f, fl, v, vl, i, il, vertexIndex,
+ face, uv, vA, vB, vC, uvA, uvB, uvC,
+ x1, x2, y1, y2, z1, z2,
+ s1, s2, t1, t2, r, t, test,
+ tan1 = [], tan2 = [],
+ sdir = new THREE.Vector3(), tdir = new THREE.Vector3(),
+ tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3(),
+ n = new THREE.Vector3(), w;
+
+ for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+ tan1[ v ] = new THREE.Vector3();
+ tan2[ v ] = new THREE.Vector3();
+
+ }
+
+ function handleTriangle( context, a, b, c, ua, ub, uc ) {
+
+ vA = context.vertices[ a ];
+ vB = context.vertices[ b ];
+ vC = context.vertices[ c ];
+
+ uvA = uv[ ua ];
+ uvB = uv[ ub ];
+ uvC = uv[ uc ];
+
+ x1 = vB.x - vA.x;
+ x2 = vC.x - vA.x;
+ y1 = vB.y - vA.y;
+ y2 = vC.y - vA.y;
+ z1 = vB.z - vA.z;
+ z2 = vC.z - vA.z;
+
+ s1 = uvB.x - uvA.x;
+ s2 = uvC.x - uvA.x;
+ t1 = uvB.y - uvA.y;
+ t2 = uvC.y - uvA.y;
+
+ r = 1.0 / ( s1 * t2 - s2 * t1 );
+ sdir.set( ( t2 * x1 - t1 * x2 ) * r,
+ ( t2 * y1 - t1 * y2 ) * r,
+ ( t2 * z1 - t1 * z2 ) * r );
+ tdir.set( ( s1 * x2 - s2 * x1 ) * r,
+ ( s1 * y2 - s2 * y1 ) * r,
+ ( s1 * z2 - s2 * z1 ) * r );
+
+ tan1[ a ].add( sdir );
+ tan1[ b ].add( sdir );
+ tan1[ c ].add( sdir );
+
+ tan2[ a ].add( tdir );
+ tan2[ b ].add( tdir );
+ tan2[ c ].add( tdir );
+
+ }
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+ uv = this.faceVertexUvs[ 0 ][ f ]; // use UV layer 0 for tangents
+
+ handleTriangle( this, face.a, face.b, face.c, 0, 1, 2 );
+
+ }
+
+ var faceIndex = [ 'a', 'b', 'c', 'd' ];
+
+ for ( f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+ face = this.faces[ f ];
+
+ for ( i = 0; i < Math.min( face.vertexNormals.length, 3 ); i++ ) {
+
+ n.copy( face.vertexNormals[ i ] );
+
+ vertexIndex = face[ faceIndex[ i ] ];
+
+ t = tan1[ vertexIndex ];
+
+ // Gram-Schmidt orthogonalize
+
+ tmp.copy( t );
+ tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+ // Calculate handedness
+
+ tmp2.crossVectors( face.vertexNormals[ i ], t );
+ test = tmp2.dot( tan2[ vertexIndex ] );
+ w = (test < 0.0) ? -1.0 : 1.0;
+
+ face.vertexTangents[ i ] = new THREE.Vector4( tmp.x, tmp.y, tmp.z, w );
+
+ }
+
+ }
+
+ this.hasTangents = true;
+
+ },
+
+ computeLineDistances: function ( ) {
+
+ var d = 0;
+ var vertices = this.vertices;
+
+ for ( var i = 0, il = vertices.length; i < il; i ++ ) {
+
+ if ( i > 0 ) {
+
+ d += vertices[ i ].distanceTo( vertices[ i - 1 ] );
+
+ }
+
+ this.lineDistances[ i ] = d;
+
+ }
+
+ },
+
+ computeBoundingBox: function () {
+
+ if ( this.boundingBox === null ) {
+
+ this.boundingBox = new THREE.Box3();
+
+ }
+
+ this.boundingBox.setFromPoints( this.vertices );
+
+ },
+
+ computeBoundingSphere: function () {
+
+ if ( this.boundingSphere === null ) {
+
+ this.boundingSphere = new THREE.Sphere();
+
+ }
+
+ this.boundingSphere.setFromPoints( this.vertices );
+
+ },
+
+ /*
+ * Checks for duplicate vertices with hashmap.
+ * Duplicated vertices are removed
+ * and faces' vertices are updated.
+ */
+
+ mergeVertices: function () {
+
+ var verticesMap = {}; // Hashmap for looking up vertice by position coordinates (and making sure they are unique)
+ var unique = [], changes = [];
+
+ var v, key;
+ var precisionPoints = 4; // number of decimal points, eg. 4 for epsilon of 0.0001
+ var precision = Math.pow( 10, precisionPoints );
+ var i,il, face;
+ var indices, k, j, jl, u;
+
+ // reset cache of vertices as it now will be changing.
+ this.__tmpVertices = undefined;
+
+ for ( i = 0, il = this.vertices.length; i < il; i ++ ) {
+
+ v = this.vertices[ i ];
+ key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision );
+
+ if ( verticesMap[ key ] === undefined ) {
+
+ verticesMap[ key ] = i;
+ unique.push( this.vertices[ i ] );
+ changes[ i ] = unique.length - 1;
+
+ } else {
+
+ //console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);
+ changes[ i ] = changes[ verticesMap[ key ] ];
+
+ }
+
+ };
+
+
+ // if faces are completely degenerate after merging vertices, we
+ // have to remove them from the geometry.
+ var faceIndicesToRemove = [];
+
+ for( i = 0, il = this.faces.length; i < il; i ++ ) {
+
+ face = this.faces[ i ];
+
+ face.a = changes[ face.a ];
+ face.b = changes[ face.b ];
+ face.c = changes[ face.c ];
+
+ indices = [ face.a, face.b, face.c ];
+
+ var dupIndex = -1;
+
+ // if any duplicate vertices are found in a Face3
+ // we have to remove the face as nothing can be saved
+ for ( var n = 0; n < 3; n ++ ) {
+ if ( indices[ n ] == indices[ ( n + 1 ) % 3 ] ) {
+
+ dupIndex = n;
+ faceIndicesToRemove.push( i );
+ break;
+
+ }
+ }
+
+ }
+
+ for ( i = faceIndicesToRemove.length - 1; i >= 0; i -- ) {
+ var idx = faceIndicesToRemove[ i ];
+
+ this.faces.splice( idx, 1 );
+
+ for ( j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
+
+ this.faceVertexUvs[ j ].splice( idx, 1 );
+
+ }
+
+ }
+
+ // Use unique set of vertices
+
+ var diff = this.vertices.length - unique.length;
+ this.vertices = unique;
+ return diff;
+
+ },
+
+ clone: function () {
+
+ var geometry = new THREE.Geometry();
+
+ var vertices = this.vertices;
+
+ for ( var i = 0, il = vertices.length; i < il; i ++ ) {
+
+ geometry.vertices.push( vertices[ i ].clone() );
+
+ }
+
+ var faces = this.faces;
+
+ for ( var i = 0, il = faces.length; i < il; i ++ ) {
+
+ geometry.faces.push( faces[ i ].clone() );
+
+ }
+
+ var uvs = this.faceVertexUvs[ 0 ];
+
+ for ( var i = 0, il = uvs.length; i < il; i ++ ) {
+
+ var uv = uvs[ i ], uvCopy = [];
+
+ for ( var j = 0, jl = uv.length; j < jl; j ++ ) {
+
+ uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) );
+
+ }
+
+ geometry.faceVertexUvs[ 0 ].push( uvCopy );
+
+ }
+
+ return geometry;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Geometry.prototype );
+
+THREE.GeometryIdCount = 0;
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.BufferGeometry = function () {
+
+ this.id = THREE.GeometryIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ // attributes
+
+ this.attributes = {};
+
+ // attributes typed arrays are kept only if dynamic flag is set
+
+ this.dynamic = true;
+
+ // offsets for chunks when using indexed elements
+
+ this.offsets = [];
+
+ // boundings
+
+ this.boundingBox = null;
+ this.boundingSphere = null;
+
+ this.hasTangents = false;
+
+ // for compatibility
+
+ this.morphTargets = [];
+
+};
+
+THREE.BufferGeometry.prototype = {
+
+ constructor: THREE.BufferGeometry,
+
+ applyMatrix: function ( matrix ) {
+
+ var positionArray;
+ var normalArray;
+
+ if ( this.attributes[ "position" ] ) positionArray = this.attributes[ "position" ].array;
+ if ( this.attributes[ "normal" ] ) normalArray = this.attributes[ "normal" ].array;
+
+ if ( positionArray !== undefined ) {
+
+ matrix.multiplyVector3Array( positionArray );
+ this.verticesNeedUpdate = true;
+
+ }
+
+ if ( normalArray !== undefined ) {
+
+ var normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+ normalMatrix.multiplyVector3Array( normalArray );
+
+ this.normalizeNormals();
+
+ this.normalsNeedUpdate = true;
+
+ }
+
+ },
+
+ computeBoundingBox: function () {
+
+ if ( this.boundingBox === null ) {
+
+ this.boundingBox = new THREE.Box3();
+
+ }
+
+ var positions = this.attributes[ "position" ].array;
+
+ if ( positions ) {
+
+ var bb = this.boundingBox;
+ var x, y, z;
+
+ if( positions.length >= 3 ) {
+ bb.min.x = bb.max.x = positions[ 0 ];
+ bb.min.y = bb.max.y = positions[ 1 ];
+ bb.min.z = bb.max.z = positions[ 2 ];
+ }
+
+ for ( var i = 3, il = positions.length; i < il; i += 3 ) {
+
+ x = positions[ i ];
+ y = positions[ i + 1 ];
+ z = positions[ i + 2 ];
+
+ // bounding box
+
+ if ( x < bb.min.x ) {
+
+ bb.min.x = x;
+
+ } else if ( x > bb.max.x ) {
+
+ bb.max.x = x;
+
+ }
+
+ if ( y < bb.min.y ) {
+
+ bb.min.y = y;
+
+ } else if ( y > bb.max.y ) {
+
+ bb.max.y = y;
+
+ }
+
+ if ( z < bb.min.z ) {
+
+ bb.min.z = z;
+
+ } else if ( z > bb.max.z ) {
+
+ bb.max.z = z;
+
+ }
+
+ }
+
+ }
+
+ if ( positions === undefined || positions.length === 0 ) {
+
+ this.boundingBox.min.set( 0, 0, 0 );
+ this.boundingBox.max.set( 0, 0, 0 );
+
+ }
+
+ },
+
+ computeBoundingSphere: function () {
+
+ var box = new THREE.Box3();
+ var vector = new THREE.Vector3();
+
+ return function () {
+
+ if ( this.boundingSphere === null ) {
+
+ this.boundingSphere = new THREE.Sphere();
+
+ }
+
+ var positions = this.attributes[ "position" ].array;
+
+ if ( positions ) {
+
+ var center = this.boundingSphere.center;
+
+ for ( var i = 0, il = positions.length; i < il; i += 3 ) {
+
+ vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] );
+ box.addPoint( vector );
+
+ }
+
+ box.center( center );
+
+ var maxRadiusSq = 0;
+
+ for ( var i = 0, il = positions.length; i < il; i += 3 ) {
+
+ vector.set( positions[ i ], positions[ i + 1 ], positions[ i + 2 ] );
+ maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+
+ }
+
+ this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
+
+ }
+
+ }
+
+ }(),
+
+ computeVertexNormals: function () {
+
+ if ( this.attributes[ "position" ] ) {
+
+ var i, il;
+ var j, jl;
+
+ var nVertexElements = this.attributes[ "position" ].array.length;
+
+ if ( this.attributes[ "normal" ] === undefined ) {
+
+ this.attributes[ "normal" ] = {
+
+ itemSize: 3,
+ array: new Float32Array( nVertexElements )
+
+ };
+
+ } else {
+
+ // reset existing normals to zero
+
+ for ( i = 0, il = this.attributes[ "normal" ].array.length; i < il; i ++ ) {
+
+ this.attributes[ "normal" ].array[ i ] = 0;
+
+ }
+
+ }
+
+ var positions = this.attributes[ "position" ].array;
+ var normals = this.attributes[ "normal" ].array;
+
+ var vA, vB, vC, x, y, z,
+
+ pA = new THREE.Vector3(),
+ pB = new THREE.Vector3(),
+ pC = new THREE.Vector3(),
+
+ cb = new THREE.Vector3(),
+ ab = new THREE.Vector3();
+
+ // indexed elements
+
+ if ( this.attributes[ "index" ] ) {
+
+ var indices = this.attributes[ "index" ].array;
+
+ var offsets = this.offsets;
+
+ for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+ var start = offsets[ j ].start;
+ var count = offsets[ j ].count;
+ var index = offsets[ j ].index;
+
+ for ( i = start, il = start + count; i < il; i += 3 ) {
+
+ vA = index + indices[ i ];
+ vB = index + indices[ i + 1 ];
+ vC = index + indices[ i + 2 ];
+
+ x = positions[ vA * 3 ];
+ y = positions[ vA * 3 + 1 ];
+ z = positions[ vA * 3 + 2 ];
+ pA.set( x, y, z );
+
+ x = positions[ vB * 3 ];
+ y = positions[ vB * 3 + 1 ];
+ z = positions[ vB * 3 + 2 ];
+ pB.set( x, y, z );
+
+ x = positions[ vC * 3 ];
+ y = positions[ vC * 3 + 1 ];
+ z = positions[ vC * 3 + 2 ];
+ pC.set( x, y, z );
+
+ cb.subVectors( pC, pB );
+ ab.subVectors( pA, pB );
+ cb.cross( ab );
+
+ normals[ vA * 3 ] += cb.x;
+ normals[ vA * 3 + 1 ] += cb.y;
+ normals[ vA * 3 + 2 ] += cb.z;
+
+ normals[ vB * 3 ] += cb.x;
+ normals[ vB * 3 + 1 ] += cb.y;
+ normals[ vB * 3 + 2 ] += cb.z;
+
+ normals[ vC * 3 ] += cb.x;
+ normals[ vC * 3 + 1 ] += cb.y;
+ normals[ vC * 3 + 2 ] += cb.z;
+
+ }
+
+ }
+
+ // non-indexed elements (unconnected triangle soup)
+
+ } else {
+
+ for ( i = 0, il = positions.length; i < il; i += 9 ) {
+
+ x = positions[ i ];
+ y = positions[ i + 1 ];
+ z = positions[ i + 2 ];
+ pA.set( x, y, z );
+
+ x = positions[ i + 3 ];
+ y = positions[ i + 4 ];
+ z = positions[ i + 5 ];
+ pB.set( x, y, z );
+
+ x = positions[ i + 6 ];
+ y = positions[ i + 7 ];
+ z = positions[ i + 8 ];
+ pC.set( x, y, z );
+
+ cb.subVectors( pC, pB );
+ ab.subVectors( pA, pB );
+ cb.cross( ab );
+
+ normals[ i ] = cb.x;
+ normals[ i + 1 ] = cb.y;
+ normals[ i + 2 ] = cb.z;
+
+ normals[ i + 3 ] = cb.x;
+ normals[ i + 4 ] = cb.y;
+ normals[ i + 5 ] = cb.z;
+
+ normals[ i + 6 ] = cb.x;
+ normals[ i + 7 ] = cb.y;
+ normals[ i + 8 ] = cb.z;
+
+ }
+
+ }
+
+ this.normalizeNormals();
+
+ this.normalsNeedUpdate = true;
+
+ }
+
+ },
+
+ normalizeNormals: function () {
+
+ var normals = this.attributes[ "normal" ].array;
+
+ var x, y, z, n;
+
+ for ( var i = 0, il = normals.length; i < il; i += 3 ) {
+
+ x = normals[ i ];
+ y = normals[ i + 1 ];
+ z = normals[ i + 2 ];
+
+ n = 1.0 / Math.sqrt( x * x + y * y + z * z );
+
+ normals[ i ] *= n;
+ normals[ i + 1 ] *= n;
+ normals[ i + 2 ] *= n;
+
+ }
+
+ },
+
+ computeTangents: function () {
+
+ // based on http://www.terathon.com/code/tangent.html
+ // (per vertex tangents)
+
+ if ( this.attributes[ "index" ] === undefined ||
+ this.attributes[ "position" ] === undefined ||
+ this.attributes[ "normal" ] === undefined ||
+ this.attributes[ "uv" ] === undefined ) {
+
+ console.warn( "Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()" );
+ return;
+
+ }
+
+ var indices = this.attributes[ "index" ].array;
+ var positions = this.attributes[ "position" ].array;
+ var normals = this.attributes[ "normal" ].array;
+ var uvs = this.attributes[ "uv" ].array;
+
+ var nVertices = positions.length / 3;
+
+ if ( this.attributes[ "tangent" ] === undefined ) {
+
+ var nTangentElements = 4 * nVertices;
+
+ this.attributes[ "tangent" ] = {
+
+ itemSize: 4,
+ array: new Float32Array( nTangentElements )
+
+ };
+
+ }
+
+ var tangents = this.attributes[ "tangent" ].array;
+
+ var tan1 = [], tan2 = [];
+
+ for ( var k = 0; k < nVertices; k ++ ) {
+
+ tan1[ k ] = new THREE.Vector3();
+ tan2[ k ] = new THREE.Vector3();
+
+ }
+
+ var xA, yA, zA,
+ xB, yB, zB,
+ xC, yC, zC,
+
+ uA, vA,
+ uB, vB,
+ uC, vC,
+
+ x1, x2, y1, y2, z1, z2,
+ s1, s2, t1, t2, r;
+
+ var sdir = new THREE.Vector3(), tdir = new THREE.Vector3();
+
+ function handleTriangle( a, b, c ) {
+
+ xA = positions[ a * 3 ];
+ yA = positions[ a * 3 + 1 ];
+ zA = positions[ a * 3 + 2 ];
+
+ xB = positions[ b * 3 ];
+ yB = positions[ b * 3 + 1 ];
+ zB = positions[ b * 3 + 2 ];
+
+ xC = positions[ c * 3 ];
+ yC = positions[ c * 3 + 1 ];
+ zC = positions[ c * 3 + 2 ];
+
+ uA = uvs[ a * 2 ];
+ vA = uvs[ a * 2 + 1 ];
+
+ uB = uvs[ b * 2 ];
+ vB = uvs[ b * 2 + 1 ];
+
+ uC = uvs[ c * 2 ];
+ vC = uvs[ c * 2 + 1 ];
+
+ x1 = xB - xA;
+ x2 = xC - xA;
+
+ y1 = yB - yA;
+ y2 = yC - yA;
+
+ z1 = zB - zA;
+ z2 = zC - zA;
+
+ s1 = uB - uA;
+ s2 = uC - uA;
+
+ t1 = vB - vA;
+ t2 = vC - vA;
+
+ r = 1.0 / ( s1 * t2 - s2 * t1 );
+
+ sdir.set(
+ ( t2 * x1 - t1 * x2 ) * r,
+ ( t2 * y1 - t1 * y2 ) * r,
+ ( t2 * z1 - t1 * z2 ) * r
+ );
+
+ tdir.set(
+ ( s1 * x2 - s2 * x1 ) * r,
+ ( s1 * y2 - s2 * y1 ) * r,
+ ( s1 * z2 - s2 * z1 ) * r
+ );
+
+ tan1[ a ].add( sdir );
+ tan1[ b ].add( sdir );
+ tan1[ c ].add( sdir );
+
+ tan2[ a ].add( tdir );
+ tan2[ b ].add( tdir );
+ tan2[ c ].add( tdir );
+
+ }
+
+ var i, il;
+ var j, jl;
+ var iA, iB, iC;
+
+ var offsets = this.offsets;
+
+ for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+ var start = offsets[ j ].start;
+ var count = offsets[ j ].count;
+ var index = offsets[ j ].index;
+
+ for ( i = start, il = start + count; i < il; i += 3 ) {
+
+ iA = index + indices[ i ];
+ iB = index + indices[ i + 1 ];
+ iC = index + indices[ i + 2 ];
+
+ handleTriangle( iA, iB, iC );
+
+ }
+
+ }
+
+ var tmp = new THREE.Vector3(), tmp2 = new THREE.Vector3();
+ var n = new THREE.Vector3(), n2 = new THREE.Vector3();
+ var w, t, test;
+
+ function handleVertex( v ) {
+
+ n.x = normals[ v * 3 ];
+ n.y = normals[ v * 3 + 1 ];
+ n.z = normals[ v * 3 + 2 ];
+
+ n2.copy( n );
+
+ t = tan1[ v ];
+
+ // Gram-Schmidt orthogonalize
+
+ tmp.copy( t );
+ tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+ // Calculate handedness
+
+ tmp2.crossVectors( n2, t );
+ test = tmp2.dot( tan2[ v ] );
+ w = ( test < 0.0 ) ? -1.0 : 1.0;
+
+ tangents[ v * 4 ] = tmp.x;
+ tangents[ v * 4 + 1 ] = tmp.y;
+ tangents[ v * 4 + 2 ] = tmp.z;
+ tangents[ v * 4 + 3 ] = w;
+
+ }
+
+ for ( j = 0, jl = offsets.length; j < jl; ++ j ) {
+
+ var start = offsets[ j ].start;
+ var count = offsets[ j ].count;
+ var index = offsets[ j ].index;
+
+ for ( i = start, il = start + count; i < il; i += 3 ) {
+
+ iA = index + indices[ i ];
+ iB = index + indices[ i + 1 ];
+ iC = index + indices[ i + 2 ];
+
+ handleVertex( iA );
+ handleVertex( iB );
+ handleVertex( iC );
+
+ }
+
+ }
+
+ this.hasTangents = true;
+ this.tangentsNeedUpdate = true;
+
+ },
+
+ clone: function () {
+
+ var geometry = new THREE.BufferGeometry();
+
+ var types = [ Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array ];
+
+ for ( var attr in this.attributes ) {
+
+ var sourceAttr = this.attributes[ attr ];
+ var sourceArray = sourceAttr.array;
+
+ var attribute = {
+
+ itemSize: sourceAttr.itemSize,
+ numItems: sourceAttr.numItems,
+ array: null
+
+ };
+
+ for ( var i = 0, il = types.length; i < il; i ++ ) {
+
+ var type = types[ i ];
+
+ if ( sourceArray instanceof type ) {
+
+ attribute.array = new type( sourceArray );
+ break;
+
+ }
+
+ }
+
+ geometry.attributes[ attr ] = attribute;
+
+ }
+
+ for ( var i = 0, il = this.offsets.length; i < il; i ++ ) {
+
+ var offset = this.offsets[ i ];
+
+ geometry.offsets.push( {
+
+ start: offset.start,
+ index: offset.index,
+ count: offset.count
+
+ } );
+
+ }
+
+ return geometry;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.BufferGeometry.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.Camera = function () {
+
+ THREE.Object3D.call( this );
+
+ this.matrixWorldInverse = new THREE.Matrix4();
+
+ this.projectionMatrix = new THREE.Matrix4();
+ this.projectionMatrixInverse = new THREE.Matrix4();
+
+};
+
+THREE.Camera.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Camera.prototype.lookAt = function () {
+
+ // This routine does not support cameras with rotated and/or translated parent(s)
+
+ var m1 = new THREE.Matrix4();
+
+ return function ( vector ) {
+
+ m1.lookAt( this.position, vector, this.up );
+
+ this.quaternion.setFromRotationMatrix( m1 );
+
+ };
+
+}();
+
+THREE.Camera.prototype.clone = function (camera) {
+
+ if ( camera === undefined ) camera = new THREE.Camera();
+
+ THREE.Object3D.prototype.clone.call( this, camera );
+
+ camera.matrixWorldInverse.copy( this.matrixWorldInverse );
+ camera.projectionMatrix.copy( this.projectionMatrix );
+ camera.projectionMatrixInverse.copy( this.projectionMatrixInverse );
+
+ return camera;
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.OrthographicCamera = function ( left, right, top, bottom, near, far ) {
+
+ THREE.Camera.call( this );
+
+ this.left = left;
+ this.right = right;
+ this.top = top;
+ this.bottom = bottom;
+
+ this.near = ( near !== undefined ) ? near : 0.1;
+ this.far = ( far !== undefined ) ? far : 2000;
+
+ this.updateProjectionMatrix();
+
+};
+
+THREE.OrthographicCamera.prototype = Object.create( THREE.Camera.prototype );
+
+THREE.OrthographicCamera.prototype.updateProjectionMatrix = function () {
+
+ this.projectionMatrix.makeOrthographic( this.left, this.right, this.top, this.bottom, this.near, this.far );
+
+};
+
+THREE.OrthographicCamera.prototype.clone = function () {
+
+ var camera = new THREE.OrthographicCamera();
+
+ THREE.Camera.prototype.clone.call( this, camera );
+
+ camera.left = this.left;
+ camera.right = this.right;
+ camera.top = this.top;
+ camera.bottom = this.bottom;
+
+ camera.near = this.near;
+ camera.far = this.far;
+
+ return camera;
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author greggman / http://games.greggman.com/
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ */
+
+THREE.PerspectiveCamera = function ( fov, aspect, near, far ) {
+
+ THREE.Camera.call( this );
+
+ this.fov = fov !== undefined ? fov : 50;
+ this.aspect = aspect !== undefined ? aspect : 1;
+ this.near = near !== undefined ? near : 0.1;
+ this.far = far !== undefined ? far : 2000;
+
+ this.updateProjectionMatrix();
+
+};
+
+THREE.PerspectiveCamera.prototype = Object.create( THREE.Camera.prototype );
+
+
+/**
+ * Uses Focal Length (in mm) to estimate and set FOV
+ * 35mm (fullframe) camera is used if frame size is not specified;
+ * Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html
+ */
+
+THREE.PerspectiveCamera.prototype.setLens = function ( focalLength, frameHeight ) {
+
+ if ( frameHeight === undefined ) frameHeight = 24;
+
+ this.fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) );
+ this.updateProjectionMatrix();
+
+}
+
+
+/**
+ * Sets an offset in a larger frustum. This is useful for multi-window or
+ * multi-monitor/multi-machine setups.
+ *
+ * For example, if you have 3x2 monitors and each monitor is 1920x1080 and
+ * the monitors are in grid like this
+ *
+ * +---+---+---+
+ * | A | B | C |
+ * +---+---+---+
+ * | D | E | F |
+ * +---+---+---+
+ *
+ * then for each monitor you would call it like this
+ *
+ * var w = 1920;
+ * var h = 1080;
+ * var fullWidth = w * 3;
+ * var fullHeight = h * 2;
+ *
+ * --A--
+ * camera.setOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
+ * --B--
+ * camera.setOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
+ * --C--
+ * camera.setOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
+ * --D--
+ * camera.setOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
+ * --E--
+ * camera.setOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
+ * --F--
+ * camera.setOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
+ *
+ * Note there is no reason monitors have to be the same size or in a grid.
+ */
+
+THREE.PerspectiveCamera.prototype.setViewOffset = function ( fullWidth, fullHeight, x, y, width, height ) {
+
+ this.fullWidth = fullWidth;
+ this.fullHeight = fullHeight;
+ this.x = x;
+ this.y = y;
+ this.width = width;
+ this.height = height;
+
+ this.updateProjectionMatrix();
+
+};
+
+
+THREE.PerspectiveCamera.prototype.updateProjectionMatrix = function () {
+
+ if ( this.fullWidth ) {
+
+ var aspect = this.fullWidth / this.fullHeight;
+ var top = Math.tan( THREE.Math.degToRad( this.fov * 0.5 ) ) * this.near;
+ var bottom = -top;
+ var left = aspect * bottom;
+ var right = aspect * top;
+ var width = Math.abs( right - left );
+ var height = Math.abs( top - bottom );
+
+ this.projectionMatrix.makeFrustum(
+ left + this.x * width / this.fullWidth,
+ left + ( this.x + this.width ) * width / this.fullWidth,
+ top - ( this.y + this.height ) * height / this.fullHeight,
+ top - this.y * height / this.fullHeight,
+ this.near,
+ this.far
+ );
+
+ } else {
+
+ this.projectionMatrix.makePerspective( this.fov, this.aspect, this.near, this.far );
+
+ }
+
+};
+
+THREE.PerspectiveCamera.prototype.clone = function () {
+
+ var camera = new THREE.PerspectiveCamera();
+
+ THREE.Camera.prototype.clone.call( this, camera );
+
+ camera.fov = this.fov;
+ camera.aspect = this.aspect;
+ camera.near = this.near;
+ camera.far = this.far;
+
+ return camera;
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Light = function ( hex ) {
+
+ THREE.Object3D.call( this );
+
+ this.color = new THREE.Color( hex );
+
+};
+
+THREE.Light.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Light.prototype.clone = function ( light ) {
+
+ if ( light === undefined ) light = new THREE.Light();
+
+ THREE.Object3D.prototype.clone.call( this, light );
+
+ light.color.copy( this.color );
+
+ return light;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.AmbientLight = function ( hex ) {
+
+ THREE.Light.call( this, hex );
+
+};
+
+THREE.AmbientLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.AmbientLight.prototype.clone = function () {
+
+ var light = new THREE.AmbientLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ return light;
+
+};
+
+/**
+ * @author MPanknin / http://www.redplant.de/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.AreaLight = function ( hex, intensity ) {
+
+ THREE.Light.call( this, hex );
+
+ this.normal = new THREE.Vector3( 0, -1, 0 );
+ this.right = new THREE.Vector3( 1, 0, 0 );
+
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+ this.width = 1.0;
+ this.height = 1.0;
+
+ this.constantAttenuation = 1.5;
+ this.linearAttenuation = 0.5;
+ this.quadraticAttenuation = 0.1;
+
+};
+
+THREE.AreaLight.prototype = Object.create( THREE.Light.prototype );
+
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DirectionalLight = function ( hex, intensity ) {
+
+ THREE.Light.call( this, hex );
+
+ this.position.set( 0, 1, 0 );
+ this.target = new THREE.Object3D();
+
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+ this.castShadow = false;
+ this.onlyShadow = false;
+
+ //
+
+ this.shadowCameraNear = 50;
+ this.shadowCameraFar = 5000;
+
+ this.shadowCameraLeft = -500;
+ this.shadowCameraRight = 500;
+ this.shadowCameraTop = 500;
+ this.shadowCameraBottom = -500;
+
+ this.shadowCameraVisible = false;
+
+ this.shadowBias = 0;
+ this.shadowDarkness = 0.5;
+
+ this.shadowMapWidth = 512;
+ this.shadowMapHeight = 512;
+
+ //
+
+ this.shadowCascade = false;
+
+ this.shadowCascadeOffset = new THREE.Vector3( 0, 0, -1000 );
+ this.shadowCascadeCount = 2;
+
+ this.shadowCascadeBias = [ 0, 0, 0 ];
+ this.shadowCascadeWidth = [ 512, 512, 512 ];
+ this.shadowCascadeHeight = [ 512, 512, 512 ];
+
+ this.shadowCascadeNearZ = [ -1.000, 0.990, 0.998 ];
+ this.shadowCascadeFarZ = [ 0.990, 0.998, 1.000 ];
+
+ this.shadowCascadeArray = [];
+
+ //
+
+ this.shadowMap = null;
+ this.shadowMapSize = null;
+ this.shadowCamera = null;
+ this.shadowMatrix = null;
+
+};
+
+THREE.DirectionalLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.DirectionalLight.prototype.clone = function () {
+
+ var light = new THREE.DirectionalLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ light.target = this.target.clone();
+
+ light.intensity = this.intensity;
+
+ light.castShadow = this.castShadow;
+ light.onlyShadow = this.onlyShadow;
+
+ return light;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.HemisphereLight = function ( skyColorHex, groundColorHex, intensity ) {
+
+ THREE.Light.call( this, skyColorHex );
+
+ this.position.set( 0, 100, 0 );
+
+ this.groundColor = new THREE.Color( groundColorHex );
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+
+};
+
+THREE.HemisphereLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.HemisphereLight.prototype.clone = function () {
+
+ var light = new THREE.HemisphereLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ light.groundColor.copy( this.groundColor );
+ light.intensity = this.intensity;
+
+ return light;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.PointLight = function ( hex, intensity, distance ) {
+
+ THREE.Light.call( this, hex );
+
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+ this.distance = ( distance !== undefined ) ? distance : 0;
+
+};
+
+THREE.PointLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.PointLight.prototype.clone = function () {
+
+ var light = new THREE.PointLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ light.intensity = this.intensity;
+ light.distance = this.distance;
+
+ return light;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SpotLight = function ( hex, intensity, distance, angle, exponent ) {
+
+ THREE.Light.call( this, hex );
+
+ this.position.set( 0, 1, 0 );
+ this.target = new THREE.Object3D();
+
+ this.intensity = ( intensity !== undefined ) ? intensity : 1;
+ this.distance = ( distance !== undefined ) ? distance : 0;
+ this.angle = ( angle !== undefined ) ? angle : Math.PI / 3;
+ this.exponent = ( exponent !== undefined ) ? exponent : 10;
+
+ this.castShadow = false;
+ this.onlyShadow = false;
+
+ //
+
+ this.shadowCameraNear = 50;
+ this.shadowCameraFar = 5000;
+ this.shadowCameraFov = 50;
+
+ this.shadowCameraVisible = false;
+
+ this.shadowBias = 0;
+ this.shadowDarkness = 0.5;
+
+ this.shadowMapWidth = 512;
+ this.shadowMapHeight = 512;
+
+ //
+
+ this.shadowMap = null;
+ this.shadowMapSize = null;
+ this.shadowCamera = null;
+ this.shadowMatrix = null;
+
+};
+
+THREE.SpotLight.prototype = Object.create( THREE.Light.prototype );
+
+THREE.SpotLight.prototype.clone = function () {
+
+ var light = new THREE.SpotLight();
+
+ THREE.Light.prototype.clone.call( this, light );
+
+ light.target = this.target.clone();
+
+ light.intensity = this.intensity;
+ light.distance = this.distance;
+ light.angle = this.angle;
+ light.exponent = this.exponent;
+
+ light.castShadow = this.castShadow;
+ light.onlyShadow = this.onlyShadow;
+
+ return light;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Loader = function ( showStatus ) {
+
+ this.showStatus = showStatus;
+ this.statusDomElement = showStatus ? THREE.Loader.prototype.addStatusElement() : null;
+
+ this.onLoadStart = function () {};
+ this.onLoadProgress = function () {};
+ this.onLoadComplete = function () {};
+
+};
+
+THREE.Loader.prototype = {
+
+ constructor: THREE.Loader,
+
+ crossOrigin: 'anonymous',
+
+ addStatusElement: function () {
+
+ var e = document.createElement( "div" );
+
+ e.style.position = "absolute";
+ e.style.right = "0px";
+ e.style.top = "0px";
+ e.style.fontSize = "0.8em";
+ e.style.textAlign = "left";
+ e.style.background = "rgba(0,0,0,0.25)";
+ e.style.color = "#fff";
+ e.style.width = "120px";
+ e.style.padding = "0.5em 0.5em 0.5em 0.5em";
+ e.style.zIndex = 1000;
+
+ e.innerHTML = "Loading ...";
+
+ return e;
+
+ },
+
+ updateProgress: function ( progress ) {
+
+ var message = "Loaded ";
+
+ if ( progress.total ) {
+
+ message += ( 100 * progress.loaded / progress.total ).toFixed(0) + "%";
+
+
+ } else {
+
+ message += ( progress.loaded / 1000 ).toFixed(2) + " KB";
+
+ }
+
+ this.statusDomElement.innerHTML = message;
+
+ },
+
+ extractUrlBase: function ( url ) {
+
+ var parts = url.split( '/' );
+ parts.pop();
+ return ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/';
+
+ },
+
+ initMaterials: function ( materials, texturePath ) {
+
+ var array = [];
+
+ for ( var i = 0; i < materials.length; ++ i ) {
+
+ array[ i ] = THREE.Loader.prototype.createMaterial( materials[ i ], texturePath );
+
+ }
+
+ return array;
+
+ },
+
+ needsTangents: function ( materials ) {
+
+ for( var i = 0, il = materials.length; i < il; i ++ ) {
+
+ var m = materials[ i ];
+
+ if ( m instanceof THREE.ShaderMaterial ) return true;
+
+ }
+
+ return false;
+
+ },
+
+ createMaterial: function ( m, texturePath ) {
+
+ var _this = this;
+
+ function is_pow2( n ) {
+
+ var l = Math.log( n ) / Math.LN2;
+ return Math.floor( l ) == l;
+
+ }
+
+ function nearest_pow2( n ) {
+
+ var l = Math.log( n ) / Math.LN2;
+ return Math.pow( 2, Math.round( l ) );
+
+ }
+
+ function load_image( where, url ) {
+
+ var image = new Image();
+
+ image.onload = function () {
+
+ if ( !is_pow2( this.width ) || !is_pow2( this.height ) ) {
+
+ var width = nearest_pow2( this.width );
+ var height = nearest_pow2( this.height );
+
+ where.image.width = width;
+ where.image.height = height;
+ where.image.getContext( '2d' ).drawImage( this, 0, 0, width, height );
+
+ } else {
+
+ where.image = this;
+
+ }
+
+ where.needsUpdate = true;
+
+ };
+
+ image.crossOrigin = _this.crossOrigin;
+ image.src = url;
+
+ }
+
+ function create_texture( where, name, sourceFile, repeat, offset, wrap, anisotropy ) {
+
+ var isCompressed = /\.dds$/i.test( sourceFile );
+ var fullPath = texturePath + "/" + sourceFile;
+
+ if ( isCompressed ) {
+
+ var texture = THREE.ImageUtils.loadCompressedTexture( fullPath );
+
+ where[ name ] = texture;
+
+ } else {
+
+ var texture = document.createElement( 'canvas' );
+
+ where[ name ] = new THREE.Texture( texture );
+
+ }
+
+ where[ name ].sourceFile = sourceFile;
+
+ if( repeat ) {
+
+ where[ name ].repeat.set( repeat[ 0 ], repeat[ 1 ] );
+
+ if ( repeat[ 0 ] !== 1 ) where[ name ].wrapS = THREE.RepeatWrapping;
+ if ( repeat[ 1 ] !== 1 ) where[ name ].wrapT = THREE.RepeatWrapping;
+
+ }
+
+ if ( offset ) {
+
+ where[ name ].offset.set( offset[ 0 ], offset[ 1 ] );
+
+ }
+
+ if ( wrap ) {
+
+ var wrapMap = {
+ "repeat": THREE.RepeatWrapping,
+ "mirror": THREE.MirroredRepeatWrapping
+ }
+
+ if ( wrapMap[ wrap[ 0 ] ] !== undefined ) where[ name ].wrapS = wrapMap[ wrap[ 0 ] ];
+ if ( wrapMap[ wrap[ 1 ] ] !== undefined ) where[ name ].wrapT = wrapMap[ wrap[ 1 ] ];
+
+ }
+
+ if ( anisotropy ) {
+
+ where[ name ].anisotropy = anisotropy;
+
+ }
+
+ if ( ! isCompressed ) {
+
+ load_image( where[ name ], fullPath );
+
+ }
+
+ }
+
+ function rgb2hex( rgb ) {
+
+ return ( rgb[ 0 ] * 255 << 16 ) + ( rgb[ 1 ] * 255 << 8 ) + rgb[ 2 ] * 255;
+
+ }
+
+ // defaults
+
+ var mtype = "MeshLambertMaterial";
+ var mpars = { color: 0xeeeeee, opacity: 1.0, map: null, lightMap: null, normalMap: null, bumpMap: null, wireframe: false };
+
+ // parameters from model file
+
+ if ( m.shading ) {
+
+ var shading = m.shading.toLowerCase();
+
+ if ( shading === "phong" ) mtype = "MeshPhongMaterial";
+ else if ( shading === "basic" ) mtype = "MeshBasicMaterial";
+
+ }
+
+ if ( m.blending !== undefined && THREE[ m.blending ] !== undefined ) {
+
+ mpars.blending = THREE[ m.blending ];
+
+ }
+
+ if ( m.transparent !== undefined || m.opacity < 1.0 ) {
+
+ mpars.transparent = m.transparent;
+
+ }
+
+ if ( m.depthTest !== undefined ) {
+
+ mpars.depthTest = m.depthTest;
+
+ }
+
+ if ( m.depthWrite !== undefined ) {
+
+ mpars.depthWrite = m.depthWrite;
+
+ }
+
+ if ( m.visible !== undefined ) {
+
+ mpars.visible = m.visible;
+
+ }
+
+ if ( m.flipSided !== undefined ) {
+
+ mpars.side = THREE.BackSide;
+
+ }
+
+ if ( m.doubleSided !== undefined ) {
+
+ mpars.side = THREE.DoubleSide;
+
+ }
+
+ if ( m.wireframe !== undefined ) {
+
+ mpars.wireframe = m.wireframe;
+
+ }
+
+ if ( m.vertexColors !== undefined ) {
+
+ if ( m.vertexColors === "face" ) {
+
+ mpars.vertexColors = THREE.FaceColors;
+
+ } else if ( m.vertexColors ) {
+
+ mpars.vertexColors = THREE.VertexColors;
+
+ }
+
+ }
+
+ // colors
+
+ if ( m.colorDiffuse ) {
+
+ mpars.color = rgb2hex( m.colorDiffuse );
+
+ } else if ( m.DbgColor ) {
+
+ mpars.color = m.DbgColor;
+
+ }
+
+ if ( m.colorSpecular ) {
+
+ mpars.specular = rgb2hex( m.colorSpecular );
+
+ }
+
+ if ( m.colorAmbient ) {
+
+ mpars.ambient = rgb2hex( m.colorAmbient );
+
+ }
+
+ // modifiers
+
+ if ( m.transparency ) {
+
+ mpars.opacity = m.transparency;
+
+ }
+
+ if ( m.specularCoef ) {
+
+ mpars.shininess = m.specularCoef;
+
+ }
+
+ // textures
+
+ if ( m.mapDiffuse && texturePath ) {
+
+ create_texture( mpars, "map", m.mapDiffuse, m.mapDiffuseRepeat, m.mapDiffuseOffset, m.mapDiffuseWrap, m.mapDiffuseAnisotropy );
+
+ }
+
+ if ( m.mapLight && texturePath ) {
+
+ create_texture( mpars, "lightMap", m.mapLight, m.mapLightRepeat, m.mapLightOffset, m.mapLightWrap, m.mapLightAnisotropy );
+
+ }
+
+ if ( m.mapBump && texturePath ) {
+
+ create_texture( mpars, "bumpMap", m.mapBump, m.mapBumpRepeat, m.mapBumpOffset, m.mapBumpWrap, m.mapBumpAnisotropy );
+
+ }
+
+ if ( m.mapNormal && texturePath ) {
+
+ create_texture( mpars, "normalMap", m.mapNormal, m.mapNormalRepeat, m.mapNormalOffset, m.mapNormalWrap, m.mapNormalAnisotropy );
+
+ }
+
+ if ( m.mapSpecular && texturePath ) {
+
+ create_texture( mpars, "specularMap", m.mapSpecular, m.mapSpecularRepeat, m.mapSpecularOffset, m.mapSpecularWrap, m.mapSpecularAnisotropy );
+
+ }
+
+ //
+
+ if ( m.mapBumpScale ) {
+
+ mpars.bumpScale = m.mapBumpScale;
+
+ }
+
+ // special case for normal mapped material
+
+ if ( m.mapNormal ) {
+
+ var shader = THREE.ShaderLib[ "normalmap" ];
+ var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+ uniforms[ "tNormal" ].value = mpars.normalMap;
+
+ if ( m.mapNormalFactor ) {
+
+ uniforms[ "uNormalScale" ].value.set( m.mapNormalFactor, m.mapNormalFactor );
+
+ }
+
+ if ( mpars.map ) {
+
+ uniforms[ "tDiffuse" ].value = mpars.map;
+ uniforms[ "enableDiffuse" ].value = true;
+
+ }
+
+ if ( mpars.specularMap ) {
+
+ uniforms[ "tSpecular" ].value = mpars.specularMap;
+ uniforms[ "enableSpecular" ].value = true;
+
+ }
+
+ if ( mpars.lightMap ) {
+
+ uniforms[ "tAO" ].value = mpars.lightMap;
+ uniforms[ "enableAO" ].value = true;
+
+ }
+
+ // for the moment don't handle displacement texture
+
+ uniforms[ "uDiffuseColor" ].value.setHex( mpars.color );
+ uniforms[ "uSpecularColor" ].value.setHex( mpars.specular );
+ uniforms[ "uAmbientColor" ].value.setHex( mpars.ambient );
+
+ uniforms[ "uShininess" ].value = mpars.shininess;
+
+ if ( mpars.opacity !== undefined ) {
+
+ uniforms[ "uOpacity" ].value = mpars.opacity;
+
+ }
+
+ var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true };
+ var material = new THREE.ShaderMaterial( parameters );
+
+ if ( mpars.transparent ) {
+
+ material.transparent = true;
+
+ }
+
+ } else {
+
+ var material = new THREE[ mtype ]( mpars );
+
+ }
+
+ if ( m.DbgName !== undefined ) material.name = m.DbgName;
+
+ return material;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.XHRLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.XHRLoader.prototype = {
+
+ constructor: THREE.XHRLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+ var request = new XMLHttpRequest();
+
+ if ( onLoad !== undefined ) {
+
+ request.addEventListener( 'load', function ( event ) {
+
+ onLoad( event.target.responseText );
+ scope.manager.itemEnd( url );
+
+ }, false );
+
+ }
+
+ if ( onProgress !== undefined ) {
+
+ request.addEventListener( 'progress', function ( event ) {
+
+ onProgress( event );
+
+ }, false );
+
+ }
+
+ if ( onError !== undefined ) {
+
+ request.addEventListener( 'error', function ( event ) {
+
+ onError( event );
+
+ }, false );
+
+ }
+
+ if ( this.crossOrigin !== undefined ) request.crossOrigin = this.crossOrigin;
+
+ request.open( 'GET', url, true );
+ request.send( null );
+
+ scope.manager.itemStart( url );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ImageLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.ImageLoader.prototype = {
+
+ constructor: THREE.ImageLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+ var image = document.createElement( 'img' );
+
+ if ( onLoad !== undefined ) {
+
+ image.addEventListener( 'load', function ( event ) {
+
+ scope.manager.itemEnd( url );
+ onLoad( this );
+
+ }, false );
+
+ }
+
+ if ( onProgress !== undefined ) {
+
+ image.addEventListener( 'progress', function ( event ) {
+
+ onProgress( event );
+
+ }, false );
+
+ }
+
+ if ( onError !== undefined ) {
+
+ image.addEventListener( 'error', function ( event ) {
+
+ onError( event );
+
+ }, false );
+
+ }
+
+ if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
+
+ image.src = url;
+
+ scope.manager.itemStart( url );
+
+ return image;
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ }
+
+}
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.JSONLoader = function ( showStatus ) {
+
+ THREE.Loader.call( this, showStatus );
+
+ this.withCredentials = false;
+
+};
+
+THREE.JSONLoader.prototype = Object.create( THREE.Loader.prototype );
+
+THREE.JSONLoader.prototype.load = function ( url, callback, texturePath ) {
+
+ var scope = this;
+
+ // todo: unify load API to for easier SceneLoader use
+
+ texturePath = texturePath && ( typeof texturePath === "string" ) ? texturePath : this.extractUrlBase( url );
+
+ this.onLoadStart();
+ this.loadAjaxJSON( this, url, callback, texturePath );
+
+};
+
+THREE.JSONLoader.prototype.loadAjaxJSON = function ( context, url, callback, texturePath, callbackProgress ) {
+
+ var xhr = new XMLHttpRequest();
+
+ var length = 0;
+
+ xhr.onreadystatechange = function () {
+
+ if ( xhr.readyState === xhr.DONE ) {
+
+ if ( xhr.status === 200 || xhr.status === 0 ) {
+
+ if ( xhr.responseText ) {
+
+ var json = JSON.parse( xhr.responseText );
+ var result = context.parse( json, texturePath );
+ callback( result.geometry, result.materials );
+
+ } else {
+
+ console.warn( "THREE.JSONLoader: [" + url + "] seems to be unreachable or file there is empty" );
+
+ }
+
+ // in context of more complex asset initialization
+ // do not block on single failed file
+ // maybe should go even one more level up
+
+ context.onLoadComplete();
+
+ } else {
+
+ console.error( "THREE.JSONLoader: Couldn't load [" + url + "] [" + xhr.status + "]" );
+
+ }
+
+ } else if ( xhr.readyState === xhr.LOADING ) {
+
+ if ( callbackProgress ) {
+
+ if ( length === 0 ) {
+
+ length = xhr.getResponseHeader( "Content-Length" );
+
+ }
+
+ callbackProgress( { total: length, loaded: xhr.responseText.length } );
+
+ }
+
+ } else if ( xhr.readyState === xhr.HEADERS_RECEIVED ) {
+
+ if ( callbackProgress !== undefined ) {
+
+ length = xhr.getResponseHeader( "Content-Length" );
+
+ }
+
+ }
+
+ };
+
+ xhr.open( "GET", url, true );
+ xhr.withCredentials = this.withCredentials;
+ xhr.send( null );
+
+};
+
+THREE.JSONLoader.prototype.parse = function ( json, texturePath ) {
+
+ var scope = this,
+ geometry = new THREE.Geometry(),
+ scale = ( json.scale !== undefined ) ? 1.0 / json.scale : 1.0;
+
+ parseModel( scale );
+
+ parseSkin();
+ parseMorphing( scale );
+
+ geometry.computeCentroids();
+ geometry.computeFaceNormals();
+ geometry.computeBoundingSphere();
+
+ function parseModel( scale ) {
+
+ function isBitSet( value, position ) {
+
+ return value & ( 1 << position );
+
+ }
+
+ var i, j, fi,
+
+ offset, zLength,
+
+ colorIndex, normalIndex, uvIndex, materialIndex,
+
+ type,
+ isQuad,
+ hasMaterial,
+ hasFaceVertexUv,
+ hasFaceNormal, hasFaceVertexNormal,
+ hasFaceColor, hasFaceVertexColor,
+
+ vertex, face, faceA, faceB, color, hex, normal,
+
+ uvLayer, uv, u, v,
+
+ faces = json.faces,
+ vertices = json.vertices,
+ normals = json.normals,
+ colors = json.colors,
+
+ nUvLayers = 0;
+
+ if ( json.uvs !== undefined ) {
+
+ // disregard empty arrays
+
+ for ( i = 0; i < json.uvs.length; i++ ) {
+
+ if ( json.uvs[ i ].length ) nUvLayers ++;
+
+ }
+
+ for ( i = 0; i < nUvLayers; i++ ) {
+
+ geometry.faceVertexUvs[ i ] = [];
+
+ }
+
+ }
+
+ offset = 0;
+ zLength = vertices.length;
+
+ while ( offset < zLength ) {
+
+ vertex = new THREE.Vector3();
+
+ vertex.x = vertices[ offset ++ ] * scale;
+ vertex.y = vertices[ offset ++ ] * scale;
+ vertex.z = vertices[ offset ++ ] * scale;
+
+ geometry.vertices.push( vertex );
+
+ }
+
+ offset = 0;
+ zLength = faces.length;
+
+ while ( offset < zLength ) {
+
+ type = faces[ offset ++ ];
+
+
+ isQuad = isBitSet( type, 0 );
+ hasMaterial = isBitSet( type, 1 );
+ hasFaceVertexUv = isBitSet( type, 3 );
+ hasFaceNormal = isBitSet( type, 4 );
+ hasFaceVertexNormal = isBitSet( type, 5 );
+ hasFaceColor = isBitSet( type, 6 );
+ hasFaceVertexColor = isBitSet( type, 7 );
+
+ // console.log("type", type, "bits", isQuad, hasMaterial, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor);
+
+ if ( isQuad ) {
+
+ faceA = new THREE.Face3();
+ faceA.a = faces[ offset ];
+ faceA.b = faces[ offset + 1 ];
+ faceA.c = faces[ offset + 3 ];
+
+ faceB = new THREE.Face3();
+ faceB.a = faces[ offset + 1 ];
+ faceB.b = faces[ offset + 2 ];
+ faceB.c = faces[ offset + 3 ];
+
+ offset += 4;
+
+ if ( hasMaterial ) {
+
+ materialIndex = faces[ offset ++ ];
+ faceA.materialIndex = materialIndex;
+ faceB.materialIndex = materialIndex;
+
+ }
+
+ // to get face <=> uv index correspondence
+
+ fi = geometry.faces.length;
+
+ if ( hasFaceVertexUv ) {
+
+ for ( i = 0; i < nUvLayers; i++ ) {
+
+ uvLayer = json.uvs[ i ];
+
+ geometry.faceVertexUvs[ i ][ fi ] = [];
+ geometry.faceVertexUvs[ i ][ fi + 1 ] = []
+
+ for ( j = 0; j < 4; j ++ ) {
+
+ uvIndex = faces[ offset ++ ];
+
+ u = uvLayer[ uvIndex * 2 ];
+ v = uvLayer[ uvIndex * 2 + 1 ];
+
+ uv = new THREE.Vector2( u, v );
+
+ if ( j !== 2 ) geometry.faceVertexUvs[ i ][ fi ].push( uv );
+ if ( j !== 0 ) geometry.faceVertexUvs[ i ][ fi + 1 ].push( uv );
+
+ }
+
+ }
+
+ }
+
+ if ( hasFaceNormal ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ faceA.normal.set(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ faceB.normal.copy( faceA.normal );
+
+ }
+
+ if ( hasFaceVertexNormal ) {
+
+ for ( i = 0; i < 4; i++ ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ normal = new THREE.Vector3(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+
+ if ( i !== 2 ) faceA.vertexNormals.push( normal );
+ if ( i !== 0 ) faceB.vertexNormals.push( normal );
+
+ }
+
+ }
+
+
+ if ( hasFaceColor ) {
+
+ colorIndex = faces[ offset ++ ];
+ hex = colors[ colorIndex ];
+
+ faceA.color.setHex( hex );
+ faceB.color.setHex( hex );
+
+ }
+
+
+ if ( hasFaceVertexColor ) {
+
+ for ( i = 0; i < 4; i++ ) {
+
+ colorIndex = faces[ offset ++ ];
+ hex = colors[ colorIndex ];
+
+ if ( i !== 2 ) faceA.vertexColors.push( new THREE.Color( hex ) );
+ if ( i !== 0 ) faceB.vertexColors.push( new THREE.Color( hex ) );
+
+ }
+
+ }
+
+ geometry.faces.push( faceA );
+ geometry.faces.push( faceB );
+
+ } else {
+
+ face = new THREE.Face3();
+ face.a = faces[ offset ++ ];
+ face.b = faces[ offset ++ ];
+ face.c = faces[ offset ++ ];
+
+ if ( hasMaterial ) {
+
+ materialIndex = faces[ offset ++ ];
+ face.materialIndex = materialIndex;
+
+ }
+
+ // to get face <=> uv index correspondence
+
+ fi = geometry.faces.length;
+
+ if ( hasFaceVertexUv ) {
+
+ for ( i = 0; i < nUvLayers; i++ ) {
+
+ uvLayer = json.uvs[ i ];
+
+ geometry.faceVertexUvs[ i ][ fi ] = [];
+
+ for ( j = 0; j < 3; j ++ ) {
+
+ uvIndex = faces[ offset ++ ];
+
+ u = uvLayer[ uvIndex * 2 ];
+ v = uvLayer[ uvIndex * 2 + 1 ];
+
+ uv = new THREE.Vector2( u, v );
+
+ geometry.faceVertexUvs[ i ][ fi ].push( uv );
+
+ }
+
+ }
+
+ }
+
+ if ( hasFaceNormal ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ face.normal.set(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ }
+
+ if ( hasFaceVertexNormal ) {
+
+ for ( i = 0; i < 3; i++ ) {
+
+ normalIndex = faces[ offset ++ ] * 3;
+
+ normal = new THREE.Vector3(
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ++ ],
+ normals[ normalIndex ]
+ );
+
+ face.vertexNormals.push( normal );
+
+ }
+
+ }
+
+
+ if ( hasFaceColor ) {
+
+ colorIndex = faces[ offset ++ ];
+ face.color.setHex( colors[ colorIndex ] );
+
+ }
+
+
+ if ( hasFaceVertexColor ) {
+
+ for ( i = 0; i < 3; i++ ) {
+
+ colorIndex = faces[ offset ++ ];
+ face.vertexColors.push( new THREE.Color( colors[ colorIndex ] ) );
+
+ }
+
+ }
+
+ geometry.faces.push( face );
+
+ }
+
+ }
+
+ };
+
+ function parseSkin() {
+
+ var i, l, x, y, z, w, a, b, c, d;
+
+ if ( json.skinWeights ) {
+
+ for ( i = 0, l = json.skinWeights.length; i < l; i += 2 ) {
+
+ x = json.skinWeights[ i ];
+ y = json.skinWeights[ i + 1 ];
+ z = 0;
+ w = 0;
+
+ geometry.skinWeights.push( new THREE.Vector4( x, y, z, w ) );
+
+ }
+
+ }
+
+ if ( json.skinIndices ) {
+
+ for ( i = 0, l = json.skinIndices.length; i < l; i += 2 ) {
+
+ a = json.skinIndices[ i ];
+ b = json.skinIndices[ i + 1 ];
+ c = 0;
+ d = 0;
+
+ geometry.skinIndices.push( new THREE.Vector4( a, b, c, d ) );
+
+ }
+
+ }
+
+ geometry.bones = json.bones;
+ geometry.animation = json.animation;
+
+ };
+
+ function parseMorphing( scale ) {
+
+ if ( json.morphTargets !== undefined ) {
+
+ var i, l, v, vl, dstVertices, srcVertices;
+
+ for ( i = 0, l = json.morphTargets.length; i < l; i ++ ) {
+
+ geometry.morphTargets[ i ] = {};
+ geometry.morphTargets[ i ].name = json.morphTargets[ i ].name;
+ geometry.morphTargets[ i ].vertices = [];
+
+ dstVertices = geometry.morphTargets[ i ].vertices;
+ srcVertices = json.morphTargets [ i ].vertices;
+
+ for( v = 0, vl = srcVertices.length; v < vl; v += 3 ) {
+
+ var vertex = new THREE.Vector3();
+ vertex.x = srcVertices[ v ] * scale;
+ vertex.y = srcVertices[ v + 1 ] * scale;
+ vertex.z = srcVertices[ v + 2 ] * scale;
+
+ dstVertices.push( vertex );
+
+ }
+
+ }
+
+ }
+
+ if ( json.morphColors !== undefined ) {
+
+ var i, l, c, cl, dstColors, srcColors, color;
+
+ for ( i = 0, l = json.morphColors.length; i < l; i++ ) {
+
+ geometry.morphColors[ i ] = {};
+ geometry.morphColors[ i ].name = json.morphColors[ i ].name;
+ geometry.morphColors[ i ].colors = [];
+
+ dstColors = geometry.morphColors[ i ].colors;
+ srcColors = json.morphColors [ i ].colors;
+
+ for ( c = 0, cl = srcColors.length; c < cl; c += 3 ) {
+
+ color = new THREE.Color( 0xffaa00 );
+ color.setRGB( srcColors[ c ], srcColors[ c + 1 ], srcColors[ c + 2 ] );
+ dstColors.push( color );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ if ( json.materials === undefined ) {
+
+ return { geometry: geometry };
+
+ } else {
+
+ var materials = this.initMaterials( json.materials, texturePath );
+
+ if ( this.needsTangents( materials ) ) {
+
+ geometry.computeTangents();
+
+ }
+
+ return { geometry: geometry, materials: materials };
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.LoadingManager = function ( onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loaded = 0, total = 0;
+
+ this.onLoad = onLoad;
+ this.onProgress = onProgress;
+ this.onError = onError;
+
+ this.itemStart = function ( url ) {
+
+ total ++;
+
+ };
+
+ this.itemEnd = function ( url ) {
+
+ loaded ++;
+
+ if ( scope.onProgress !== undefined ) {
+
+ scope.onProgress( url, loaded, total );
+
+ }
+
+ if ( loaded === total && scope.onLoad !== undefined ) {
+
+ scope.onLoad();
+
+ }
+
+ };
+
+};
+
+THREE.DefaultLoadingManager = new THREE.LoadingManager();
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.BufferGeometryLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.BufferGeometryLoader.prototype = {
+
+ constructor: THREE.BufferGeometryLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader();
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ parse: function ( json ) {
+
+ var geometry = new THREE.BufferGeometry();
+
+ var attributes = json.attributes;
+ var offsets = json.offsets;
+ var boundingSphere = json.boundingSphere;
+
+ for ( var key in attributes ) {
+
+ var attribute = attributes[ key ];
+
+ geometry.attributes[ key ] = {
+ itemSize: attribute.itemSize,
+ array: new self[ attribute.type ]( attribute.array )
+ }
+
+ }
+
+ if ( offsets !== undefined ) {
+
+ geometry.offsets = JSON.parse( JSON.stringify( offsets ) );
+
+ }
+
+ if ( boundingSphere !== undefined ) {
+
+ geometry.boundingSphere = new THREE.Sphere(
+ new THREE.Vector3().fromArray( boundingSphere.center !== undefined ? boundingSphere.center : [ 0, 0, 0 ] ),
+ boundingSphere.radius
+ );
+
+ }
+
+ return geometry;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.GeometryLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.GeometryLoader.prototype = {
+
+ constructor: THREE.GeometryLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader();
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ parse: function ( json ) {
+
+
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.MaterialLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.MaterialLoader.prototype = {
+
+ constructor: THREE.MaterialLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader();
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ parse: function ( json ) {
+
+ var material = new THREE[ json.type ];
+
+ if ( json.color !== undefined ) material.color.setHex( json.color );
+ if ( json.ambient !== undefined ) material.ambient.setHex( json.ambient );
+ if ( json.emissive !== undefined ) material.emissive.setHex( json.emissive );
+ if ( json.specular !== undefined ) material.specular.setHex( json.specular );
+ if ( json.shininess !== undefined ) material.shininess = json.shininess;
+ if ( json.vertexColors !== undefined ) material.vertexColors = json.vertexColors;
+ if ( json.blending !== undefined ) material.blending = json.blending;
+ if ( json.opacity !== undefined ) material.opacity = json.opacity;
+ if ( json.transparent !== undefined ) material.transparent = json.transparent;
+ if ( json.wireframe !== undefined ) material.wireframe = json.wireframe;
+
+ if ( json.materials !== undefined ) {
+
+ for ( var i = 0, l = json.materials.length; i < l; i ++ ) {
+
+ material.materials.push( this.parse( json.materials[ i ] ) );
+
+ }
+
+ }
+
+ return material;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ObjectLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.ObjectLoader.prototype = {
+
+ constructor: THREE.ObjectLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader( scope.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ parse: function ( json ) {
+
+ var geometries = this.parseGeometries( json.geometries );
+ var materials = this.parseMaterials( json.materials );
+ var object = this.parseObject( json.object, geometries, materials );
+
+ return object;
+
+ },
+
+ parseGeometries: function ( json ) {
+
+ var geometries = {};
+
+ if ( json !== undefined ) {
+
+ var geometryLoader = new THREE.JSONLoader();
+ var bufferGeometryLoader = new THREE.BufferGeometryLoader();
+
+ for ( var i = 0, l = json.length; i < l; i ++ ) {
+
+ var geometry;
+ var data = json[ i ];
+
+ switch ( data.type ) {
+
+ case 'PlaneGeometry':
+
+ geometry = new THREE.PlaneGeometry(
+ data.width,
+ data.height,
+ data.widthSegments,
+ data.heightSegments
+ );
+
+ break;
+
+ case 'CircleGeometry':
+
+ geometry = new THREE.CircleGeometry(
+ data.radius,
+ data.segments
+ );
+
+ break;
+
+ case 'CubeGeometry':
+
+ geometry = new THREE.CubeGeometry(
+ data.width,
+ data.height,
+ data.depth,
+ data.widthSegments,
+ data.heightSegments,
+ data.depthSegments
+ );
+
+ break;
+
+ case 'CylinderGeometry':
+
+ geometry = new THREE.CylinderGeometry(
+ data.radiusTop,
+ data.radiusBottom,
+ data.height,
+ data.radiusSegments,
+ data.heightSegments,
+ data.openEnded
+ );
+
+ break;
+
+ case 'SphereGeometry':
+
+ geometry = new THREE.SphereGeometry(
+ data.radius,
+ data.widthSegments,
+ data.heightSegments,
+ data.phiStart,
+ data.phiLength,
+ data.thetaStart,
+ data.thetaLength
+ );
+
+ break;
+
+ case 'IcosahedronGeometry':
+
+ geometry = new THREE.IcosahedronGeometry(
+ data.radius,
+ data.detail
+ );
+
+ break;
+
+ case 'TorusGeometry':
+
+ geometry = new THREE.TorusGeometry(
+ data.radius,
+ data.tube,
+ data.radialSegments,
+ data.tubularSegments,
+ data.arc
+ );
+
+ break;
+
+ case 'TorusKnotGeometry':
+
+ geometry = new THREE.TorusKnotGeometry(
+ data.radius,
+ data.tube,
+ data.radialSegments,
+ data.tubularSegments,
+ data.p,
+ data.q,
+ data.heightScale
+ );
+
+ break;
+
+ case 'BufferGeometry':
+
+ geometry = bufferGeometryLoader.parse( data.data );
+
+ break;
+
+ case 'Geometry':
+
+ geometry = geometryLoader.parse( data.data ).geometry;
+
+ break;
+
+ }
+
+ geometry.uuid = data.uuid;
+
+ if ( data.name !== undefined ) geometry.name = data.name;
+
+ geometries[ data.uuid ] = geometry;
+
+ }
+
+ }
+
+ return geometries;
+
+ },
+
+ parseMaterials: function ( json ) {
+
+ var materials = {};
+
+ if ( json !== undefined ) {
+
+ var loader = new THREE.MaterialLoader();
+
+ for ( var i = 0, l = json.length; i < l; i ++ ) {
+
+ var data = json[ i ];
+ var material = loader.parse( data );
+
+ material.uuid = data.uuid;
+
+ if ( data.name !== undefined ) material.name = data.name;
+
+ materials[ data.uuid ] = material;
+
+ }
+
+ }
+
+ return materials;
+
+ },
+
+ parseObject: function () {
+
+ var matrix = new THREE.Matrix4();
+
+ return function ( data, geometries, materials ) {
+
+ var object;
+
+ switch ( data.type ) {
+
+ case 'Scene':
+
+ object = new THREE.Scene();
+
+ break;
+
+ case 'PerspectiveCamera':
+
+ object = new THREE.PerspectiveCamera( data.fov, data.aspect, data.near, data.far );
+
+ break;
+
+ case 'OrthographicCamera':
+
+ object = new THREE.OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far );
+
+ break;
+
+ case 'AmbientLight':
+
+ object = new THREE.AmbientLight( data.color );
+
+ break;
+
+ case 'DirectionalLight':
+
+ object = new THREE.DirectionalLight( data.color, data.intensity );
+
+ break;
+
+ case 'PointLight':
+
+ object = new THREE.PointLight( data.color, data.intensity, data.distance );
+
+ break;
+
+ case 'SpotLight':
+
+ object = new THREE.SpotLight( data.color, data.intensity, data.distance, data.angle, data.exponent );
+
+ break;
+
+ case 'HemisphereLight':
+
+ object = new THREE.HemisphereLight( data.color, data.groundColor, data.intensity );
+
+ break;
+
+ case 'Mesh':
+
+ var geometry = geometries[ data.geometry ];
+ var material = materials[ data.material ];
+
+ if ( geometry === undefined ) {
+
+ console.error( 'THREE.ObjectLoader: Undefined geometry ' + data.geometry );
+
+ }
+
+ if ( material === undefined ) {
+
+ console.error( 'THREE.ObjectLoader: Undefined material ' + data.material );
+
+ }
+
+ object = new THREE.Mesh( geometry, material );
+
+ break;
+
+ default:
+
+ object = new THREE.Object3D();
+
+ }
+
+ object.uuid = data.uuid;
+
+ if ( data.name !== undefined ) object.name = data.name;
+ if ( data.matrix !== undefined ) {
+
+ matrix.fromArray( data.matrix );
+ matrix.decompose( object.position, object.quaternion, object.scale );
+
+ } else {
+
+ if ( data.position !== undefined ) object.position.fromArray( data.position );
+ if ( data.rotation !== undefined ) object.rotation.fromArray( data.rotation );
+ if ( data.scale !== undefined ) object.scale.fromArray( data.scale );
+
+ }
+
+ if ( data.visible !== undefined ) object.visible = data.visible;
+ if ( data.userData !== undefined ) object.userData = data.userData;
+
+ if ( data.children !== undefined ) {
+
+ for ( var child in data.children ) {
+
+ object.add( this.parseObject( data.children[ child ], geometries, materials ) );
+
+ }
+
+ }
+
+ return object;
+
+ }
+
+ }()
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SceneLoader = function () {
+
+ this.onLoadStart = function () {};
+ this.onLoadProgress = function() {};
+ this.onLoadComplete = function () {};
+
+ this.callbackSync = function () {};
+ this.callbackProgress = function () {};
+
+ this.geometryHandlers = {};
+ this.hierarchyHandlers = {};
+
+ this.addGeometryHandler( "ascii", THREE.JSONLoader );
+
+};
+
+THREE.SceneLoader.prototype = {
+
+ constructor: THREE.SceneLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.XHRLoader( scope.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( text ) {
+
+ scope.parse( JSON.parse( text ), onLoad, url );
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ },
+
+ addGeometryHandler: function ( typeID, loaderClass ) {
+
+ this.geometryHandlers[ typeID ] = { "loaderClass": loaderClass };
+
+ },
+
+ addHierarchyHandler: function ( typeID, loaderClass ) {
+
+ this.hierarchyHandlers[ typeID ] = { "loaderClass": loaderClass };
+
+ },
+
+ parse: function ( json, callbackFinished, url ) {
+
+ var scope = this;
+
+ var urlBase = THREE.Loader.prototype.extractUrlBase( url );
+
+ var geometry, material, camera, fog,
+ texture, images, color,
+ light, hex, intensity,
+ counter_models, counter_textures,
+ total_models, total_textures,
+ result;
+
+ var target_array = [];
+
+ var data = json;
+
+ // async geometry loaders
+
+ for ( var typeID in this.geometryHandlers ) {
+
+ var loaderClass = this.geometryHandlers[ typeID ][ "loaderClass" ];
+ this.geometryHandlers[ typeID ][ "loaderObject" ] = new loaderClass();
+
+ }
+
+ // async hierachy loaders
+
+ for ( var typeID in this.hierarchyHandlers ) {
+
+ var loaderClass = this.hierarchyHandlers[ typeID ][ "loaderClass" ];
+ this.hierarchyHandlers[ typeID ][ "loaderObject" ] = new loaderClass();
+
+ }
+
+ counter_models = 0;
+ counter_textures = 0;
+
+ result = {
+
+ scene: new THREE.Scene(),
+ geometries: {},
+ face_materials: {},
+ materials: {},
+ textures: {},
+ objects: {},
+ cameras: {},
+ lights: {},
+ fogs: {},
+ empties: {},
+ groups: {}
+
+ };
+
+ if ( data.transform ) {
+
+ var position = data.transform.position,
+ rotation = data.transform.rotation,
+ scale = data.transform.scale;
+
+ if ( position ) {
+
+ result.scene.position.fromArray( position );
+
+ }
+
+ if ( rotation ) {
+
+ result.scene.rotation.fromArray( rotation );
+
+ }
+
+ if ( scale ) {
+
+ result.scene.scale.fromArray( scale );
+
+ }
+
+ if ( position || rotation || scale ) {
+
+ result.scene.updateMatrix();
+ result.scene.updateMatrixWorld();
+
+ }
+
+ }
+
+ function get_url( source_url, url_type ) {
+
+ if ( url_type == "relativeToHTML" ) {
+
+ return source_url;
+
+ } else {
+
+ return urlBase + "/" + source_url;
+
+ }
+
+ };
+
+ // toplevel loader function, delegates to handle_children
+
+ function handle_objects() {
+
+ handle_children( result.scene, data.objects );
+
+ }
+
+ // handle all the children from the loaded json and attach them to given parent
+
+ function handle_children( parent, children ) {
+
+ var mat, dst, pos, rot, scl, quat;
+
+ for ( var objID in children ) {
+
+ // check by id if child has already been handled,
+ // if not, create new object
+
+ var object = result.objects[ objID ];
+ var objJSON = children[ objID ];
+
+ if ( object === undefined ) {
+
+ // meshes
+
+ if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlers ) ) {
+
+ if ( objJSON.loading === undefined ) {
+
+ var reservedTypes = {
+ "type": 1, "url": 1, "material": 1,
+ "position": 1, "rotation": 1, "scale" : 1,
+ "visible": 1, "children": 1, "userData": 1,
+ "skin": 1, "morph": 1, "mirroredLoop": 1, "duration": 1
+ };
+
+ var loaderParameters = {};
+
+ for ( var parType in objJSON ) {
+
+ if ( ! ( parType in reservedTypes ) ) {
+
+ loaderParameters[ parType ] = objJSON[ parType ];
+
+ }
+
+ }
+
+ material = result.materials[ objJSON.material ];
+
+ objJSON.loading = true;
+
+ var loader = scope.hierarchyHandlers[ objJSON.type ][ "loaderObject" ];
+
+ // ColladaLoader
+
+ if ( loader.options ) {
+
+ loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ) );
+
+ // UTF8Loader
+ // OBJLoader
+
+ } else {
+
+ loader.load( get_url( objJSON.url, data.urlBaseType ), create_callback_hierachy( objID, parent, material, objJSON ), loaderParameters );
+
+ }
+
+ }
+
+ } else if ( objJSON.geometry !== undefined ) {
+
+ geometry = result.geometries[ objJSON.geometry ];
+
+ // geometry already loaded
+
+ if ( geometry ) {
+
+ var needsTangents = false;
+
+ material = result.materials[ objJSON.material ];
+ needsTangents = material instanceof THREE.ShaderMaterial;
+
+ pos = objJSON.position;
+ rot = objJSON.rotation;
+ scl = objJSON.scale;
+ mat = objJSON.matrix;
+ quat = objJSON.quaternion;
+
+ // use materials from the model file
+ // if there is no material specified in the object
+
+ if ( ! objJSON.material ) {
+
+ material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] );
+
+ }
+
+ // use materials from the model file
+ // if there is just empty face material
+ // (must create new material as each model has its own face material)
+
+ if ( ( material instanceof THREE.MeshFaceMaterial ) && material.materials.length === 0 ) {
+
+ material = new THREE.MeshFaceMaterial( result.face_materials[ objJSON.geometry ] );
+
+ }
+
+ if ( material instanceof THREE.MeshFaceMaterial ) {
+
+ for ( var i = 0; i < material.materials.length; i ++ ) {
+
+ needsTangents = needsTangents || ( material.materials[ i ] instanceof THREE.ShaderMaterial );
+
+ }
+
+ }
+
+ if ( needsTangents ) {
+
+ geometry.computeTangents();
+
+ }
+
+ if ( objJSON.skin ) {
+
+ object = new THREE.SkinnedMesh( geometry, material );
+
+ } else if ( objJSON.morph ) {
+
+ object = new THREE.MorphAnimMesh( geometry, material );
+
+ if ( objJSON.duration !== undefined ) {
+
+ object.duration = objJSON.duration;
+
+ }
+
+ if ( objJSON.time !== undefined ) {
+
+ object.time = objJSON.time;
+
+ }
+
+ if ( objJSON.mirroredLoop !== undefined ) {
+
+ object.mirroredLoop = objJSON.mirroredLoop;
+
+ }
+
+ if ( material.morphNormals ) {
+
+ geometry.computeMorphNormals();
+
+ }
+
+ } else {
+
+ object = new THREE.Mesh( geometry, material );
+
+ }
+
+ object.name = objID;
+
+ if ( mat ) {
+
+ object.matrixAutoUpdate = false;
+ object.matrix.set(
+ mat[0], mat[1], mat[2], mat[3],
+ mat[4], mat[5], mat[6], mat[7],
+ mat[8], mat[9], mat[10], mat[11],
+ mat[12], mat[13], mat[14], mat[15]
+ );
+
+ } else {
+
+ object.position.fromArray( pos );
+
+ if ( quat ) {
+
+ object.quaternion.fromArray( quat );
+
+ } else {
+
+ object.rotation.fromArray( rot );
+
+ }
+
+ object.scale.fromArray( scl );
+
+ }
+
+ object.visible = objJSON.visible;
+ object.castShadow = objJSON.castShadow;
+ object.receiveShadow = objJSON.receiveShadow;
+
+ parent.add( object );
+
+ result.objects[ objID ] = object;
+
+ }
+
+ // lights
+
+ } else if ( objJSON.type === "DirectionalLight" || objJSON.type === "PointLight" || objJSON.type === "AmbientLight" ) {
+
+ hex = ( objJSON.color !== undefined ) ? objJSON.color : 0xffffff;
+ intensity = ( objJSON.intensity !== undefined ) ? objJSON.intensity : 1;
+
+ if ( objJSON.type === "DirectionalLight" ) {
+
+ pos = objJSON.direction;
+
+ light = new THREE.DirectionalLight( hex, intensity );
+ light.position.fromArray( pos );
+
+ if ( objJSON.target ) {
+
+ target_array.push( { "object": light, "targetName" : objJSON.target } );
+
+ // kill existing default target
+ // otherwise it gets added to scene when parent gets added
+
+ light.target = null;
+
+ }
+
+ } else if ( objJSON.type === "PointLight" ) {
+
+ pos = objJSON.position;
+ dst = objJSON.distance;
+
+ light = new THREE.PointLight( hex, intensity, dst );
+ light.position.fromArray( pos );
+
+ } else if ( objJSON.type === "AmbientLight" ) {
+
+ light = new THREE.AmbientLight( hex );
+
+ }
+
+ parent.add( light );
+
+ light.name = objID;
+ result.lights[ objID ] = light;
+ result.objects[ objID ] = light;
+
+ // cameras
+
+ } else if ( objJSON.type === "PerspectiveCamera" || objJSON.type === "OrthographicCamera" ) {
+
+ pos = objJSON.position;
+ rot = objJSON.rotation;
+ quat = objJSON.quaternion;
+
+ if ( objJSON.type === "PerspectiveCamera" ) {
+
+ camera = new THREE.PerspectiveCamera( objJSON.fov, objJSON.aspect, objJSON.near, objJSON.far );
+
+ } else if ( objJSON.type === "OrthographicCamera" ) {
+
+ camera = new THREE.OrthographicCamera( objJSON.left, objJSON.right, objJSON.top, objJSON.bottom, objJSON.near, objJSON.far );
+
+ }
+
+ camera.name = objID;
+ camera.position.fromArray( pos );
+
+ if ( quat !== undefined ) {
+
+ camera.quaternion.fromArray( quat );
+
+ } else if ( rot !== undefined ) {
+
+ camera.rotation.fromArray( rot );
+
+ }
+
+ parent.add( camera );
+
+ result.cameras[ objID ] = camera;
+ result.objects[ objID ] = camera;
+
+ // pure Object3D
+
+ } else {
+
+ pos = objJSON.position;
+ rot = objJSON.rotation;
+ scl = objJSON.scale;
+ quat = objJSON.quaternion;
+
+ object = new THREE.Object3D();
+ object.name = objID;
+ object.position.fromArray( pos );
+
+ if ( quat ) {
+
+ object.quaternion.fromArray( quat );
+
+ } else {
+
+ object.rotation.fromArray( rot );
+
+ }
+
+ object.scale.fromArray( scl );
+ object.visible = ( objJSON.visible !== undefined ) ? objJSON.visible : false;
+
+ parent.add( object );
+
+ result.objects[ objID ] = object;
+ result.empties[ objID ] = object;
+
+ }
+
+ if ( object ) {
+
+ if ( objJSON.userData !== undefined ) {
+
+ for ( var key in objJSON.userData ) {
+
+ var value = objJSON.userData[ key ];
+ object.userData[ key ] = value;
+
+ }
+
+ }
+
+ if ( objJSON.groups !== undefined ) {
+
+ for ( var i = 0; i < objJSON.groups.length; i ++ ) {
+
+ var groupID = objJSON.groups[ i ];
+
+ if ( result.groups[ groupID ] === undefined ) {
+
+ result.groups[ groupID ] = [];
+
+ }
+
+ result.groups[ groupID ].push( objID );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( object !== undefined && objJSON.children !== undefined ) {
+
+ handle_children( object, objJSON.children );
+
+ }
+
+ }
+
+ };
+
+ function handle_mesh( geo, mat, id ) {
+
+ result.geometries[ id ] = geo;
+ result.face_materials[ id ] = mat;
+ handle_objects();
+
+ };
+
+ function handle_hierarchy( node, id, parent, material, obj ) {
+
+ var p = obj.position;
+ var r = obj.rotation;
+ var q = obj.quaternion;
+ var s = obj.scale;
+
+ node.position.fromArray( p );
+
+ if ( q ) {
+
+ node.quaternion.fromArray( q );
+
+ } else {
+
+ node.rotation.fromArray( r );
+
+ }
+
+ node.scale.fromArray( s );
+
+ // override children materials
+ // if object material was specified in JSON explicitly
+
+ if ( material ) {
+
+ node.traverse( function ( child ) {
+
+ child.material = material;
+
+ } );
+
+ }
+
+ // override children visibility
+ // with root node visibility as specified in JSON
+
+ var visible = ( obj.visible !== undefined ) ? obj.visible : true;
+
+ node.traverse( function ( child ) {
+
+ child.visible = visible;
+
+ } );
+
+ parent.add( node );
+
+ node.name = id;
+
+ result.objects[ id ] = node;
+ handle_objects();
+
+ };
+
+ function create_callback_geometry( id ) {
+
+ return function ( geo, mat ) {
+
+ geo.name = id;
+
+ handle_mesh( geo, mat, id );
+
+ counter_models -= 1;
+
+ scope.onLoadComplete();
+
+ async_callback_gate();
+
+ }
+
+ };
+
+ function create_callback_hierachy( id, parent, material, obj ) {
+
+ return function ( event ) {
+
+ var result;
+
+ // loaders which use EventDispatcher
+
+ if ( event.content ) {
+
+ result = event.content;
+
+ // ColladaLoader
+
+ } else if ( event.dae ) {
+
+ result = event.scene;
+
+
+ // UTF8Loader
+
+ } else {
+
+ result = event;
+
+ }
+
+ handle_hierarchy( result, id, parent, material, obj );
+
+ counter_models -= 1;
+
+ scope.onLoadComplete();
+
+ async_callback_gate();
+
+ }
+
+ };
+
+ function create_callback_embed( id ) {
+
+ return function ( geo, mat ) {
+
+ geo.name = id;
+
+ result.geometries[ id ] = geo;
+ result.face_materials[ id ] = mat;
+
+ }
+
+ };
+
+ function async_callback_gate() {
+
+ var progress = {
+
+ totalModels : total_models,
+ totalTextures : total_textures,
+ loadedModels : total_models - counter_models,
+ loadedTextures : total_textures - counter_textures
+
+ };
+
+ scope.callbackProgress( progress, result );
+
+ scope.onLoadProgress();
+
+ if ( counter_models === 0 && counter_textures === 0 ) {
+
+ finalize();
+ callbackFinished( result );
+
+ }
+
+ };
+
+ function finalize() {
+
+ // take care of targets which could be asynchronously loaded objects
+
+ for ( var i = 0; i < target_array.length; i ++ ) {
+
+ var ta = target_array[ i ];
+
+ var target = result.objects[ ta.targetName ];
+
+ if ( target ) {
+
+ ta.object.target = target;
+
+ } else {
+
+ // if there was error and target of specified name doesn't exist in the scene file
+ // create instead dummy target
+ // (target must be added to scene explicitly as parent is already added)
+
+ ta.object.target = new THREE.Object3D();
+ result.scene.add( ta.object.target );
+
+ }
+
+ ta.object.target.userData.targetInverse = ta.object;
+
+ }
+
+ };
+
+ var callbackTexture = function ( count ) {
+
+ counter_textures -= count;
+ async_callback_gate();
+
+ scope.onLoadComplete();
+
+ };
+
+ // must use this instead of just directly calling callbackTexture
+ // because of closure in the calling context loop
+
+ var generateTextureCallback = function ( count ) {
+
+ return function () {
+
+ callbackTexture( count );
+
+ };
+
+ };
+
+ function traverse_json_hierarchy( objJSON, callback ) {
+
+ callback( objJSON );
+
+ if ( objJSON.children !== undefined ) {
+
+ for ( var objChildID in objJSON.children ) {
+
+ traverse_json_hierarchy( objJSON.children[ objChildID ], callback );
+
+ }
+
+ }
+
+ };
+
+ // first go synchronous elements
+
+ // fogs
+
+ var fogID, fogJSON;
+
+ for ( fogID in data.fogs ) {
+
+ fogJSON = data.fogs[ fogID ];
+
+ if ( fogJSON.type === "linear" ) {
+
+ fog = new THREE.Fog( 0x000000, fogJSON.near, fogJSON.far );
+
+ } else if ( fogJSON.type === "exp2" ) {
+
+ fog = new THREE.FogExp2( 0x000000, fogJSON.density );
+
+ }
+
+ color = fogJSON.color;
+ fog.color.setRGB( color[0], color[1], color[2] );
+
+ result.fogs[ fogID ] = fog;
+
+ }
+
+ // now come potentially asynchronous elements
+
+ // geometries
+
+ // count how many geometries will be loaded asynchronously
+
+ var geoID, geoJSON;
+
+ for ( geoID in data.geometries ) {
+
+ geoJSON = data.geometries[ geoID ];
+
+ if ( geoJSON.type in this.geometryHandlers ) {
+
+ counter_models += 1;
+
+ scope.onLoadStart();
+
+ }
+
+ }
+
+ // count how many hierarchies will be loaded asynchronously
+
+ for ( var objID in data.objects ) {
+
+ traverse_json_hierarchy( data.objects[ objID ], function ( objJSON ) {
+
+ if ( objJSON.type && ( objJSON.type in scope.hierarchyHandlers ) ) {
+
+ counter_models += 1;
+
+ scope.onLoadStart();
+
+ }
+
+ });
+
+ }
+
+ total_models = counter_models;
+
+ for ( geoID in data.geometries ) {
+
+ geoJSON = data.geometries[ geoID ];
+
+ if ( geoJSON.type === "cube" ) {
+
+ geometry = new THREE.CubeGeometry( geoJSON.width, geoJSON.height, geoJSON.depth, geoJSON.widthSegments, geoJSON.heightSegments, geoJSON.depthSegments );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "plane" ) {
+
+ geometry = new THREE.PlaneGeometry( geoJSON.width, geoJSON.height, geoJSON.widthSegments, geoJSON.heightSegments );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "sphere" ) {
+
+ geometry = new THREE.SphereGeometry( geoJSON.radius, geoJSON.widthSegments, geoJSON.heightSegments );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "cylinder" ) {
+
+ geometry = new THREE.CylinderGeometry( geoJSON.topRad, geoJSON.botRad, geoJSON.height, geoJSON.radSegs, geoJSON.heightSegs );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "torus" ) {
+
+ geometry = new THREE.TorusGeometry( geoJSON.radius, geoJSON.tube, geoJSON.segmentsR, geoJSON.segmentsT );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type === "icosahedron" ) {
+
+ geometry = new THREE.IcosahedronGeometry( geoJSON.radius, geoJSON.subdivisions );
+ geometry.name = geoID;
+ result.geometries[ geoID ] = geometry;
+
+ } else if ( geoJSON.type in this.geometryHandlers ) {
+
+ var loaderParameters = {};
+
+ for ( var parType in geoJSON ) {
+
+ if ( parType !== "type" && parType !== "url" ) {
+
+ loaderParameters[ parType ] = geoJSON[ parType ];
+
+ }
+
+ }
+
+ var loader = this.geometryHandlers[ geoJSON.type ][ "loaderObject" ];
+ loader.load( get_url( geoJSON.url, data.urlBaseType ), create_callback_geometry( geoID ), loaderParameters );
+
+ } else if ( geoJSON.type === "embedded" ) {
+
+ var modelJson = data.embeds[ geoJSON.id ],
+ texture_path = "";
+
+ // pass metadata along to jsonLoader so it knows the format version
+
+ modelJson.metadata = data.metadata;
+
+ if ( modelJson ) {
+
+ var jsonLoader = this.geometryHandlers[ "ascii" ][ "loaderObject" ];
+ var model = jsonLoader.parse( modelJson, texture_path );
+ create_callback_embed( geoID )( model.geometry, model.materials );
+
+ }
+
+ }
+
+ }
+
+ // textures
+
+ // count how many textures will be loaded asynchronously
+
+ var textureID, textureJSON;
+
+ for ( textureID in data.textures ) {
+
+ textureJSON = data.textures[ textureID ];
+
+ if ( textureJSON.url instanceof Array ) {
+
+ counter_textures += textureJSON.url.length;
+
+ for( var n = 0; n < textureJSON.url.length; n ++ ) {
+
+ scope.onLoadStart();
+
+ }
+
+ } else {
+
+ counter_textures += 1;
+
+ scope.onLoadStart();
+
+ }
+
+ }
+
+ total_textures = counter_textures;
+
+ for ( textureID in data.textures ) {
+
+ textureJSON = data.textures[ textureID ];
+
+ if ( textureJSON.mapping !== undefined && THREE[ textureJSON.mapping ] !== undefined ) {
+
+ textureJSON.mapping = new THREE[ textureJSON.mapping ]();
+
+ }
+
+ if ( textureJSON.url instanceof Array ) {
+
+ var count = textureJSON.url.length;
+ var url_array = [];
+
+ for( var i = 0; i < count; i ++ ) {
+
+ url_array[ i ] = get_url( textureJSON.url[ i ], data.urlBaseType );
+
+ }
+
+ var isCompressed = /\.dds$/i.test( url_array[ 0 ] );
+
+ if ( isCompressed ) {
+
+ texture = THREE.ImageUtils.loadCompressedTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) );
+
+ } else {
+
+ texture = THREE.ImageUtils.loadTextureCube( url_array, textureJSON.mapping, generateTextureCallback( count ) );
+
+ }
+
+ } else {
+
+ var isCompressed = /\.dds$/i.test( textureJSON.url );
+ var fullUrl = get_url( textureJSON.url, data.urlBaseType );
+ var textureCallback = generateTextureCallback( 1 );
+
+ if ( isCompressed ) {
+
+ texture = THREE.ImageUtils.loadCompressedTexture( fullUrl, textureJSON.mapping, textureCallback );
+
+ } else {
+
+ texture = THREE.ImageUtils.loadTexture( fullUrl, textureJSON.mapping, textureCallback );
+
+ }
+
+ if ( THREE[ textureJSON.minFilter ] !== undefined )
+ texture.minFilter = THREE[ textureJSON.minFilter ];
+
+ if ( THREE[ textureJSON.magFilter ] !== undefined )
+ texture.magFilter = THREE[ textureJSON.magFilter ];
+
+ if ( textureJSON.anisotropy ) texture.anisotropy = textureJSON.anisotropy;
+
+ if ( textureJSON.repeat ) {
+
+ texture.repeat.set( textureJSON.repeat[ 0 ], textureJSON.repeat[ 1 ] );
+
+ if ( textureJSON.repeat[ 0 ] !== 1 ) texture.wrapS = THREE.RepeatWrapping;
+ if ( textureJSON.repeat[ 1 ] !== 1 ) texture.wrapT = THREE.RepeatWrapping;
+
+ }
+
+ if ( textureJSON.offset ) {
+
+ texture.offset.set( textureJSON.offset[ 0 ], textureJSON.offset[ 1 ] );
+
+ }
+
+ // handle wrap after repeat so that default repeat can be overriden
+
+ if ( textureJSON.wrap ) {
+
+ var wrapMap = {
+ "repeat": THREE.RepeatWrapping,
+ "mirror": THREE.MirroredRepeatWrapping
+ }
+
+ if ( wrapMap[ textureJSON.wrap[ 0 ] ] !== undefined ) texture.wrapS = wrapMap[ textureJSON.wrap[ 0 ] ];
+ if ( wrapMap[ textureJSON.wrap[ 1 ] ] !== undefined ) texture.wrapT = wrapMap[ textureJSON.wrap[ 1 ] ];
+
+ }
+
+ }
+
+ result.textures[ textureID ] = texture;
+
+ }
+
+ // materials
+
+ var matID, matJSON;
+ var parID;
+
+ for ( matID in data.materials ) {
+
+ matJSON = data.materials[ matID ];
+
+ for ( parID in matJSON.parameters ) {
+
+ if ( parID === "envMap" || parID === "map" || parID === "lightMap" || parID === "bumpMap" ) {
+
+ matJSON.parameters[ parID ] = result.textures[ matJSON.parameters[ parID ] ];
+
+ } else if ( parID === "shading" ) {
+
+ matJSON.parameters[ parID ] = ( matJSON.parameters[ parID ] === "flat" ) ? THREE.FlatShading : THREE.SmoothShading;
+
+ } else if ( parID === "side" ) {
+
+ if ( matJSON.parameters[ parID ] == "double" ) {
+
+ matJSON.parameters[ parID ] = THREE.DoubleSide;
+
+ } else if ( matJSON.parameters[ parID ] == "back" ) {
+
+ matJSON.parameters[ parID ] = THREE.BackSide;
+
+ } else {
+
+ matJSON.parameters[ parID ] = THREE.FrontSide;
+
+ }
+
+ } else if ( parID === "blending" ) {
+
+ matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.NormalBlending;
+
+ } else if ( parID === "combine" ) {
+
+ matJSON.parameters[ parID ] = matJSON.parameters[ parID ] in THREE ? THREE[ matJSON.parameters[ parID ] ] : THREE.MultiplyOperation;
+
+ } else if ( parID === "vertexColors" ) {
+
+ if ( matJSON.parameters[ parID ] == "face" ) {
+
+ matJSON.parameters[ parID ] = THREE.FaceColors;
+
+ // default to vertex colors if "vertexColors" is anything else face colors or 0 / null / false
+
+ } else if ( matJSON.parameters[ parID ] ) {
+
+ matJSON.parameters[ parID ] = THREE.VertexColors;
+
+ }
+
+ } else if ( parID === "wrapRGB" ) {
+
+ var v3 = matJSON.parameters[ parID ];
+ matJSON.parameters[ parID ] = new THREE.Vector3( v3[ 0 ], v3[ 1 ], v3[ 2 ] );
+
+ }
+
+ }
+
+ if ( matJSON.parameters.opacity !== undefined && matJSON.parameters.opacity < 1.0 ) {
+
+ matJSON.parameters.transparent = true;
+
+ }
+
+ if ( matJSON.parameters.normalMap ) {
+
+ var shader = THREE.ShaderLib[ "normalmap" ];
+ var uniforms = THREE.UniformsUtils.clone( shader.uniforms );
+
+ var diffuse = matJSON.parameters.color;
+ var specular = matJSON.parameters.specular;
+ var ambient = matJSON.parameters.ambient;
+ var shininess = matJSON.parameters.shininess;
+
+ uniforms[ "tNormal" ].value = result.textures[ matJSON.parameters.normalMap ];
+
+ if ( matJSON.parameters.normalScale ) {
+
+ uniforms[ "uNormalScale" ].value.set( matJSON.parameters.normalScale[ 0 ], matJSON.parameters.normalScale[ 1 ] );
+
+ }
+
+ if ( matJSON.parameters.map ) {
+
+ uniforms[ "tDiffuse" ].value = matJSON.parameters.map;
+ uniforms[ "enableDiffuse" ].value = true;
+
+ }
+
+ if ( matJSON.parameters.envMap ) {
+
+ uniforms[ "tCube" ].value = matJSON.parameters.envMap;
+ uniforms[ "enableReflection" ].value = true;
+ uniforms[ "uReflectivity" ].value = matJSON.parameters.reflectivity;
+
+ }
+
+ if ( matJSON.parameters.lightMap ) {
+
+ uniforms[ "tAO" ].value = matJSON.parameters.lightMap;
+ uniforms[ "enableAO" ].value = true;
+
+ }
+
+ if ( matJSON.parameters.specularMap ) {
+
+ uniforms[ "tSpecular" ].value = result.textures[ matJSON.parameters.specularMap ];
+ uniforms[ "enableSpecular" ].value = true;
+
+ }
+
+ if ( matJSON.parameters.displacementMap ) {
+
+ uniforms[ "tDisplacement" ].value = result.textures[ matJSON.parameters.displacementMap ];
+ uniforms[ "enableDisplacement" ].value = true;
+
+ uniforms[ "uDisplacementBias" ].value = matJSON.parameters.displacementBias;
+ uniforms[ "uDisplacementScale" ].value = matJSON.parameters.displacementScale;
+
+ }
+
+ uniforms[ "uDiffuseColor" ].value.setHex( diffuse );
+ uniforms[ "uSpecularColor" ].value.setHex( specular );
+ uniforms[ "uAmbientColor" ].value.setHex( ambient );
+
+ uniforms[ "uShininess" ].value = shininess;
+
+ if ( matJSON.parameters.opacity ) {
+
+ uniforms[ "uOpacity" ].value = matJSON.parameters.opacity;
+
+ }
+
+ var parameters = { fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, uniforms: uniforms, lights: true, fog: true };
+
+ material = new THREE.ShaderMaterial( parameters );
+
+ } else {
+
+ material = new THREE[ matJSON.type ]( matJSON.parameters );
+
+ }
+
+ material.name = matID;
+
+ result.materials[ matID ] = material;
+
+ }
+
+ // second pass through all materials to initialize MeshFaceMaterials
+ // that could be referring to other materials out of order
+
+ for ( matID in data.materials ) {
+
+ matJSON = data.materials[ matID ];
+
+ if ( matJSON.parameters.materials ) {
+
+ var materialArray = [];
+
+ for ( var i = 0; i < matJSON.parameters.materials.length; i ++ ) {
+
+ var label = matJSON.parameters.materials[ i ];
+ materialArray.push( result.materials[ label ] );
+
+ }
+
+ result.materials[ matID ].materials = materialArray;
+
+ }
+
+ }
+
+ // objects ( synchronous init of procedural primitives )
+
+ handle_objects();
+
+ // defaults
+
+ if ( result.cameras && data.defaults.camera ) {
+
+ result.currentCamera = result.cameras[ data.defaults.camera ];
+
+ }
+
+ if ( result.fogs && data.defaults.fog ) {
+
+ result.scene.fog = result.fogs[ data.defaults.fog ];
+
+ }
+
+ // synchronous callback
+
+ scope.callbackSync( result );
+
+ // just in case there are no async elements
+
+ async_callback_gate();
+
+ }
+
+}
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.TextureLoader = function ( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.TextureLoader.prototype = {
+
+ constructor: THREE.TextureLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ var scope = this;
+
+ var loader = new THREE.ImageLoader( scope.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.load( url, function ( image ) {
+
+ var texture = new THREE.Texture( image );
+ texture.needsUpdate = true;
+
+ if ( onLoad !== undefined ) {
+
+ onLoad( texture );
+
+ }
+
+ } );
+
+ },
+
+ setCrossOrigin: function ( value ) {
+
+ this.crossOrigin = value;
+
+ }
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Material = function () {
+
+ this.id = THREE.MaterialIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ this.side = THREE.FrontSide;
+
+ this.opacity = 1;
+ this.transparent = false;
+
+ this.blending = THREE.NormalBlending;
+
+ this.blendSrc = THREE.SrcAlphaFactor;
+ this.blendDst = THREE.OneMinusSrcAlphaFactor;
+ this.blendEquation = THREE.AddEquation;
+
+ this.depthTest = true;
+ this.depthWrite = true;
+
+ this.polygonOffset = false;
+ this.polygonOffsetFactor = 0;
+ this.polygonOffsetUnits = 0;
+
+ this.alphaTest = 0;
+
+ this.overdraw = 0; // Overdrawn pixels (typically between 0 and 1) for fixing antialiasing gaps in CanvasRenderer
+
+ this.visible = true;
+
+ this.needsUpdate = true;
+
+};
+
+THREE.Material.prototype = {
+
+ constructor: THREE.Material,
+
+ setValues: function ( values ) {
+
+ if ( values === undefined ) return;
+
+ for ( var key in values ) {
+
+ var newValue = values[ key ];
+
+ if ( newValue === undefined ) {
+
+ console.warn( 'THREE.Material: \'' + key + '\' parameter is undefined.' );
+ continue;
+
+ }
+
+ if ( key in this ) {
+
+ var currentValue = this[ key ];
+
+ if ( currentValue instanceof THREE.Color ) {
+
+ currentValue.set( newValue );
+
+ } else if ( currentValue instanceof THREE.Vector3 && newValue instanceof THREE.Vector3 ) {
+
+ currentValue.copy( newValue );
+
+ } else if ( key == 'overdraw') {
+
+ // ensure overdraw is backwards-compatable with legacy boolean type
+ this[ key ] = Number(newValue);
+
+ } else {
+
+ this[ key ] = newValue;
+
+ }
+
+ }
+
+ }
+
+ },
+
+ clone: function ( material ) {
+
+ if ( material === undefined ) material = new THREE.Material();
+
+ material.name = this.name;
+
+ material.side = this.side;
+
+ material.opacity = this.opacity;
+ material.transparent = this.transparent;
+
+ material.blending = this.blending;
+
+ material.blendSrc = this.blendSrc;
+ material.blendDst = this.blendDst;
+ material.blendEquation = this.blendEquation;
+
+ material.depthTest = this.depthTest;
+ material.depthWrite = this.depthWrite;
+
+ material.polygonOffset = this.polygonOffset;
+ material.polygonOffsetFactor = this.polygonOffsetFactor;
+ material.polygonOffsetUnits = this.polygonOffsetUnits;
+
+ material.alphaTest = this.alphaTest;
+
+ material.overdraw = this.overdraw;
+
+ material.visible = this.visible;
+
+ return material;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Material.prototype );
+
+THREE.MaterialIdCount = 0;
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * linewidth: ,
+ * linecap: "round",
+ * linejoin: "round",
+ *
+ * vertexColors:
+ *
+ * fog:
+ * }
+ */
+
+THREE.LineBasicMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff );
+
+ this.linewidth = 1;
+ this.linecap = 'round';
+ this.linejoin = 'round';
+
+ this.vertexColors = false;
+
+ this.fog = true;
+
+ this.setValues( parameters );
+
+};
+
+THREE.LineBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.LineBasicMaterial.prototype.clone = function () {
+
+ var material = new THREE.LineBasicMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+
+ material.linewidth = this.linewidth;
+ material.linecap = this.linecap;
+ material.linejoin = this.linejoin;
+
+ material.vertexColors = this.vertexColors;
+
+ material.fog = this.fog;
+
+ return material;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * linewidth: ,
+ *
+ * scale: ,
+ * dashSize: ,
+ * gapSize: ,
+ *
+ * vertexColors:
+ *
+ * fog:
+ * }
+ */
+
+THREE.LineDashedMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff );
+
+ this.linewidth = 1;
+
+ this.scale = 1;
+ this.dashSize = 3;
+ this.gapSize = 1;
+
+ this.vertexColors = false;
+
+ this.fog = true;
+
+ this.setValues( parameters );
+
+};
+
+THREE.LineDashedMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.LineDashedMaterial.prototype.clone = function () {
+
+ var material = new THREE.LineDashedMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+
+ material.linewidth = this.linewidth;
+
+ material.scale = this.scale;
+ material.dashSize = this.dashSize;
+ material.gapSize = this.gapSize;
+
+ material.vertexColors = this.vertexColors;
+
+ material.fog = this.fog;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ * map: new THREE.Texture( ),
+ *
+ * lightMap: new THREE.Texture( ),
+ *
+ * specularMap: new THREE.Texture( ),
+ *
+ * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.Multiply,
+ * reflectivity: ,
+ * refractionRatio: ,
+ *
+ * shading: THREE.SmoothShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth: ,
+ *
+ * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ * skinning: ,
+ * morphTargets: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.MeshBasicMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff ); // emissive
+
+ this.map = null;
+
+ this.lightMap = null;
+
+ this.specularMap = null;
+
+ this.envMap = null;
+ this.combine = THREE.MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.fog = true;
+
+ this.shading = THREE.SmoothShading;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.vertexColors = THREE.NoColors;
+
+ this.skinning = false;
+ this.morphTargets = false;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshBasicMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshBasicMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+
+ material.map = this.map;
+
+ material.lightMap = this.lightMap;
+
+ material.specularMap = this.specularMap;
+
+ material.envMap = this.envMap;
+ material.combine = this.combine;
+ material.reflectivity = this.reflectivity;
+ material.refractionRatio = this.refractionRatio;
+
+ material.fog = this.fog;
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+ material.wireframeLinecap = this.wireframeLinecap;
+ material.wireframeLinejoin = this.wireframeLinejoin;
+
+ material.vertexColors = this.vertexColors;
+
+ material.skinning = this.skinning;
+ material.morphTargets = this.morphTargets;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * ambient: ,
+ * emissive: ,
+ * opacity: ,
+ *
+ * map: new THREE.Texture( ),
+ *
+ * lightMap: new THREE.Texture( ),
+ *
+ * specularMap: new THREE.Texture( ),
+ *
+ * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.Multiply,
+ * reflectivity: ,
+ * refractionRatio: ,
+ *
+ * shading: THREE.SmoothShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth: ,
+ *
+ * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ * skinning: ,
+ * morphTargets: ,
+ * morphNormals: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.MeshLambertMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff ); // diffuse
+ this.ambient = new THREE.Color( 0xffffff );
+ this.emissive = new THREE.Color( 0x000000 );
+
+ this.wrapAround = false;
+ this.wrapRGB = new THREE.Vector3( 1, 1, 1 );
+
+ this.map = null;
+
+ this.lightMap = null;
+
+ this.specularMap = null;
+
+ this.envMap = null;
+ this.combine = THREE.MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.fog = true;
+
+ this.shading = THREE.SmoothShading;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.vertexColors = THREE.NoColors;
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshLambertMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshLambertMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshLambertMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+ material.ambient.copy( this.ambient );
+ material.emissive.copy( this.emissive );
+
+ material.wrapAround = this.wrapAround;
+ material.wrapRGB.copy( this.wrapRGB );
+
+ material.map = this.map;
+
+ material.lightMap = this.lightMap;
+
+ material.specularMap = this.specularMap;
+
+ material.envMap = this.envMap;
+ material.combine = this.combine;
+ material.reflectivity = this.reflectivity;
+ material.refractionRatio = this.refractionRatio;
+
+ material.fog = this.fog;
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+ material.wireframeLinecap = this.wireframeLinecap;
+ material.wireframeLinejoin = this.wireframeLinejoin;
+
+ material.vertexColors = this.vertexColors;
+
+ material.skinning = this.skinning;
+ material.morphTargets = this.morphTargets;
+ material.morphNormals = this.morphNormals;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * ambient: ,
+ * emissive: ,
+ * specular: ,
+ * shininess: ,
+ * opacity: ,
+ *
+ * map: new THREE.Texture( ),
+ *
+ * lightMap: new THREE.Texture( ),
+ *
+ * bumpMap: new THREE.Texture( ),
+ * bumpScale: ,
+ *
+ * normalMap: new THREE.Texture( ),
+ * normalScale: ,
+ *
+ * specularMap: new THREE.Texture( ),
+ *
+ * envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.Multiply,
+ * reflectivity: ,
+ * refractionRatio: ,
+ *
+ * shading: THREE.SmoothShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth: ,
+ *
+ * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ * skinning: ,
+ * morphTargets: ,
+ * morphNormals: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.MeshPhongMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff ); // diffuse
+ this.ambient = new THREE.Color( 0xffffff );
+ this.emissive = new THREE.Color( 0x000000 );
+ this.specular = new THREE.Color( 0x111111 );
+ this.shininess = 30;
+
+ this.metal = false;
+ this.perPixel = true;
+
+ this.wrapAround = false;
+ this.wrapRGB = new THREE.Vector3( 1, 1, 1 );
+
+ this.map = null;
+
+ this.lightMap = null;
+
+ this.bumpMap = null;
+ this.bumpScale = 1;
+
+ this.normalMap = null;
+ this.normalScale = new THREE.Vector2( 1, 1 );
+
+ this.specularMap = null;
+
+ this.envMap = null;
+ this.combine = THREE.MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.fog = true;
+
+ this.shading = THREE.SmoothShading;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.vertexColors = THREE.NoColors;
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshPhongMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshPhongMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshPhongMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+ material.ambient.copy( this.ambient );
+ material.emissive.copy( this.emissive );
+ material.specular.copy( this.specular );
+ material.shininess = this.shininess;
+
+ material.metal = this.metal;
+ material.perPixel = this.perPixel;
+
+ material.wrapAround = this.wrapAround;
+ material.wrapRGB.copy( this.wrapRGB );
+
+ material.map = this.map;
+
+ material.lightMap = this.lightMap;
+
+ material.bumpMap = this.bumpMap;
+ material.bumpScale = this.bumpScale;
+
+ material.normalMap = this.normalMap;
+ material.normalScale.copy( this.normalScale );
+
+ material.specularMap = this.specularMap;
+
+ material.envMap = this.envMap;
+ material.combine = this.combine;
+ material.reflectivity = this.reflectivity;
+ material.refractionRatio = this.refractionRatio;
+
+ material.fog = this.fog;
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+ material.wireframeLinecap = this.wireframeLinecap;
+ material.wireframeLinejoin = this.wireframeLinejoin;
+
+ material.vertexColors = this.vertexColors;
+
+ material.skinning = this.skinning;
+ material.morphTargets = this.morphTargets;
+ material.morphNormals = this.morphNormals;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * opacity: ,
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth:
+ * }
+ */
+
+THREE.MeshDepthMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshDepthMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshDepthMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshDepthMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ * opacity: ,
+ *
+ * shading: THREE.FlatShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth:
+ * }
+ */
+
+THREE.MeshNormalMaterial = function ( parameters ) {
+
+ THREE.Material.call( this, parameters );
+
+ this.shading = THREE.FlatShading;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.morphTargets = false;
+
+ this.setValues( parameters );
+
+};
+
+THREE.MeshNormalMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.MeshNormalMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshNormalMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.MeshFaceMaterial = function ( materials ) {
+
+ this.materials = materials instanceof Array ? materials : [];
+
+};
+
+THREE.MeshFaceMaterial.prototype.clone = function () {
+
+ var material = new THREE.MeshFaceMaterial();
+
+ for ( var i = 0; i < this.materials.length; i ++ ) {
+
+ material.materials.push( this.materials[ i ].clone() );
+
+ }
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ * map: new THREE.Texture( ),
+ *
+ * size: ,
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * vertexColors: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.ParticleBasicMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff );
+
+ this.map = null;
+
+ this.size = 1;
+ this.sizeAttenuation = true;
+
+ this.vertexColors = false;
+
+ this.fog = true;
+
+ this.setValues( parameters );
+
+};
+
+THREE.ParticleBasicMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ParticleBasicMaterial.prototype.clone = function () {
+
+ var material = new THREE.ParticleBasicMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+
+ material.map = this.map;
+
+ material.size = this.size;
+ material.sizeAttenuation = this.sizeAttenuation;
+
+ material.vertexColors = this.vertexColors;
+
+ material.fog = this.fog;
+
+ return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ * color: ,
+ * program: ,
+ * opacity: ,
+ * blending: THREE.NormalBlending
+ * }
+ */
+
+THREE.ParticleCanvasMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.color = new THREE.Color( 0xffffff );
+ this.program = function ( context, color ) {};
+
+ this.setValues( parameters );
+
+};
+
+THREE.ParticleCanvasMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ParticleCanvasMaterial.prototype.clone = function () {
+
+ var material = new THREE.ParticleCanvasMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+ material.program = this.program;
+
+ return material;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * fragmentShader: ,
+ * vertexShader: ,
+ *
+ * uniforms: { "parameter1": { type: "f", value: 1.0 }, "parameter2": { type: "i" value2: 2 } },
+ *
+ * defines: { "label" : "value" },
+ *
+ * shading: THREE.SmoothShading,
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * wireframe: ,
+ * wireframeLinewidth: ,
+ *
+ * lights: ,
+ *
+ * vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ * skinning: ,
+ * morphTargets: ,
+ * morphNormals: ,
+ *
+ * fog:
+ * }
+ */
+
+THREE.ShaderMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ this.fragmentShader = "void main() {}";
+ this.vertexShader = "void main() {}";
+ this.uniforms = {};
+ this.defines = {};
+ this.attributes = null;
+
+ this.shading = THREE.SmoothShading;
+
+ this.linewidth = 1;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.fog = false; // set to use scene fog
+
+ this.lights = false; // set to use scene lights
+
+ this.vertexColors = THREE.NoColors; // set to use "color" attribute stream
+
+ this.skinning = false; // set to use skinning attribute streams
+
+ this.morphTargets = false; // set to use morph targets
+ this.morphNormals = false; // set to use morph normals
+
+ // When rendered geometry doesn't include these attributes but the material does,
+ // use these default values in WebGL. This avoids errors when buffer data is missing.
+ this.defaultAttributeValues = {
+ "color" : [ 1, 1, 1],
+ "uv" : [ 0, 0 ],
+ "uv2" : [ 0, 0 ]
+ };
+
+ // By default, bind position to attribute index 0. In WebGL, attribute 0
+ // should always be used to avoid potentially expensive emulation.
+ this.index0AttributeName = "position";
+
+ this.setValues( parameters );
+
+};
+
+THREE.ShaderMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ShaderMaterial.prototype.clone = function () {
+
+ var material = new THREE.ShaderMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.fragmentShader = this.fragmentShader;
+ material.vertexShader = this.vertexShader;
+
+ material.uniforms = THREE.UniformsUtils.clone( this.uniforms );
+
+ material.attributes = this.attributes;
+ material.defines = this.defines;
+
+ material.shading = this.shading;
+
+ material.wireframe = this.wireframe;
+ material.wireframeLinewidth = this.wireframeLinewidth;
+
+ material.fog = this.fog;
+
+ material.lights = this.lights;
+
+ material.vertexColors = this.vertexColors;
+
+ material.skinning = this.skinning;
+
+ material.morphTargets = this.morphTargets;
+ material.morphNormals = this.morphNormals;
+
+ return material;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ * color: ,
+ * opacity: ,
+ * map: new THREE.Texture( ),
+ *
+ * blending: THREE.NormalBlending,
+ * depthTest: ,
+ * depthWrite: ,
+ *
+ * useScreenCoordinates: ,
+ * sizeAttenuation: ,
+ * scaleByViewport: ,
+ * alignment: THREE.SpriteAlignment.center,
+ *
+ * uvOffset: new THREE.Vector2(),
+ * uvScale: new THREE.Vector2(),
+ *
+ * fog:
+ * }
+ */
+
+THREE.SpriteMaterial = function ( parameters ) {
+
+ THREE.Material.call( this );
+
+ // defaults
+
+ this.color = new THREE.Color( 0xffffff );
+ this.map = new THREE.Texture();
+
+ this.useScreenCoordinates = true;
+ this.depthTest = !this.useScreenCoordinates;
+ this.sizeAttenuation = !this.useScreenCoordinates;
+ this.scaleByViewport = !this.sizeAttenuation;
+ this.alignment = THREE.SpriteAlignment.center.clone();
+
+ this.fog = false;
+
+ this.uvOffset = new THREE.Vector2( 0, 0 );
+ this.uvScale = new THREE.Vector2( 1, 1 );
+
+ // set parameters
+
+ this.setValues( parameters );
+
+ // override coupled defaults if not specified explicitly by parameters
+
+ parameters = parameters || {};
+
+ if ( parameters.depthTest === undefined ) this.depthTest = !this.useScreenCoordinates;
+ if ( parameters.sizeAttenuation === undefined ) this.sizeAttenuation = !this.useScreenCoordinates;
+ if ( parameters.scaleByViewport === undefined ) this.scaleByViewport = !this.sizeAttenuation;
+
+};
+
+THREE.SpriteMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.SpriteMaterial.prototype.clone = function () {
+
+ var material = new THREE.SpriteMaterial();
+
+ THREE.Material.prototype.clone.call( this, material );
+
+ material.color.copy( this.color );
+ material.map = this.map;
+
+ material.useScreenCoordinates = this.useScreenCoordinates;
+ material.sizeAttenuation = this.sizeAttenuation;
+ material.scaleByViewport = this.scaleByViewport;
+ material.alignment.copy( this.alignment );
+
+ material.uvOffset.copy( this.uvOffset );
+ material.uvScale.copy( this.uvScale );
+
+ material.fog = this.fog;
+
+ return material;
+
+};
+
+// Alignment enums
+
+THREE.SpriteAlignment = {};
+THREE.SpriteAlignment.topLeft = new THREE.Vector2( 1, -1 );
+THREE.SpriteAlignment.topCenter = new THREE.Vector2( 0, -1 );
+THREE.SpriteAlignment.topRight = new THREE.Vector2( -1, -1 );
+THREE.SpriteAlignment.centerLeft = new THREE.Vector2( 1, 0 );
+THREE.SpriteAlignment.center = new THREE.Vector2( 0, 0 );
+THREE.SpriteAlignment.centerRight = new THREE.Vector2( -1, 0 );
+THREE.SpriteAlignment.bottomLeft = new THREE.Vector2( 1, 1 );
+THREE.SpriteAlignment.bottomCenter = new THREE.Vector2( 0, 1 );
+THREE.SpriteAlignment.bottomRight = new THREE.Vector2( -1, 1 );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ */
+
+THREE.Texture = function ( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+
+ this.id = THREE.TextureIdCount ++;
+ this.uuid = THREE.Math.generateUUID();
+
+ this.name = '';
+
+ this.image = image;
+ this.mipmaps = [];
+
+ this.mapping = mapping !== undefined ? mapping : new THREE.UVMapping();
+
+ this.wrapS = wrapS !== undefined ? wrapS : THREE.ClampToEdgeWrapping;
+ this.wrapT = wrapT !== undefined ? wrapT : THREE.ClampToEdgeWrapping;
+
+ this.magFilter = magFilter !== undefined ? magFilter : THREE.LinearFilter;
+ this.minFilter = minFilter !== undefined ? minFilter : THREE.LinearMipMapLinearFilter;
+
+ this.anisotropy = anisotropy !== undefined ? anisotropy : 1;
+
+ this.format = format !== undefined ? format : THREE.RGBAFormat;
+ this.type = type !== undefined ? type : THREE.UnsignedByteType;
+
+ this.offset = new THREE.Vector2( 0, 0 );
+ this.repeat = new THREE.Vector2( 1, 1 );
+
+ this.generateMipmaps = true;
+ this.premultiplyAlpha = false;
+ this.flipY = true;
+ this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)
+
+ this.needsUpdate = false;
+ this.onUpdate = null;
+
+};
+
+THREE.Texture.prototype = {
+
+ constructor: THREE.Texture,
+
+ clone: function ( texture ) {
+
+ if ( texture === undefined ) texture = new THREE.Texture();
+
+ texture.image = this.image;
+ texture.mipmaps = this.mipmaps.slice(0);
+
+ texture.mapping = this.mapping;
+
+ texture.wrapS = this.wrapS;
+ texture.wrapT = this.wrapT;
+
+ texture.magFilter = this.magFilter;
+ texture.minFilter = this.minFilter;
+
+ texture.anisotropy = this.anisotropy;
+
+ texture.format = this.format;
+ texture.type = this.type;
+
+ texture.offset.copy( this.offset );
+ texture.repeat.copy( this.repeat );
+
+ texture.generateMipmaps = this.generateMipmaps;
+ texture.premultiplyAlpha = this.premultiplyAlpha;
+ texture.flipY = this.flipY;
+ texture.unpackAlignment = this.unpackAlignment;
+
+ return texture;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Texture.prototype );
+
+THREE.TextureIdCount = 0;
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.CompressedTexture = function ( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) {
+
+ THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+ this.image = { width: width, height: height };
+ this.mipmaps = mipmaps;
+
+ this.generateMipmaps = false; // WebGL currently can't generate mipmaps for compressed textures, they must be embedded in DDS file
+
+};
+
+THREE.CompressedTexture.prototype = Object.create( THREE.Texture.prototype );
+
+THREE.CompressedTexture.prototype.clone = function () {
+
+ var texture = new THREE.CompressedTexture();
+
+ THREE.Texture.prototype.clone.call( this, texture );
+
+ return texture;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DataTexture = function ( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy ) {
+
+ THREE.Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+ this.image = { data: data, width: width, height: height };
+
+};
+
+THREE.DataTexture.prototype = Object.create( THREE.Texture.prototype );
+
+THREE.DataTexture.prototype.clone = function () {
+
+ var texture = new THREE.DataTexture();
+
+ THREE.Texture.prototype.clone.call( this, texture );
+
+ return texture;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Particle = function ( material ) {
+
+ THREE.Object3D.call( this );
+
+ this.material = material;
+
+};
+
+THREE.Particle.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Particle.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Particle( this.material );
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ParticleSystem = function ( geometry, material ) {
+
+ THREE.Object3D.call( this );
+
+ this.geometry = geometry !== undefined ? geometry : new THREE.Geometry();
+ this.material = material !== undefined ? material : new THREE.ParticleBasicMaterial( { color: Math.random() * 0xffffff } );
+
+ this.sortParticles = false;
+ this.frustumCulled = false;
+
+};
+
+THREE.ParticleSystem.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.ParticleSystem.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.ParticleSystem( this.geometry, this.material );
+
+ object.sortParticles = this.sortParticles;
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Line = function ( geometry, material, type ) {
+
+ THREE.Object3D.call( this );
+
+ this.geometry = geometry !== undefined ? geometry : new THREE.Geometry();
+ this.material = material !== undefined ? material : new THREE.LineBasicMaterial( { color: Math.random() * 0xffffff } );
+
+ this.type = ( type !== undefined ) ? type : THREE.LineStrip;
+
+};
+
+THREE.LineStrip = 0;
+THREE.LinePieces = 1;
+
+THREE.Line.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Line.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Line( this.geometry, this.material, this.type );
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mikael emtinger / http://gomo.se/
+ * @author jonobr1 / http://jonobr1.com/
+ */
+
+THREE.Mesh = function ( geometry, material ) {
+
+ THREE.Object3D.call( this );
+
+ this.geometry = geometry !== undefined ? geometry : new THREE.Geometry();
+ this.material = material !== undefined ? material : new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff } );
+
+ this.updateMorphTargets();
+
+};
+
+THREE.Mesh.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Mesh.prototype.updateMorphTargets = function () {
+
+ if ( this.geometry.morphTargets.length > 0 ) {
+
+ this.morphTargetBase = -1;
+ this.morphTargetForcedOrder = [];
+ this.morphTargetInfluences = [];
+ this.morphTargetDictionary = {};
+
+ for ( var m = 0, ml = this.geometry.morphTargets.length; m < ml; m ++ ) {
+
+ this.morphTargetInfluences.push( 0 );
+ this.morphTargetDictionary[ this.geometry.morphTargets[ m ].name ] = m;
+
+ }
+
+ }
+
+};
+
+THREE.Mesh.prototype.getMorphTargetIndexByName = function ( name ) {
+
+ if ( this.morphTargetDictionary[ name ] !== undefined ) {
+
+ return this.morphTargetDictionary[ name ];
+
+ }
+
+ console.log( "THREE.Mesh.getMorphTargetIndexByName: morph target " + name + " does not exist. Returning 0." );
+
+ return 0;
+
+};
+
+THREE.Mesh.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Mesh( this.geometry, this.material );
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Bone = function( belongsToSkin ) {
+
+ THREE.Object3D.call( this );
+
+ this.skin = belongsToSkin;
+ this.skinMatrix = new THREE.Matrix4();
+
+};
+
+THREE.Bone.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Bone.prototype.update = function ( parentSkinMatrix, forceUpdate ) {
+
+ // update local
+
+ if ( this.matrixAutoUpdate ) {
+
+ forceUpdate |= this.updateMatrix();
+
+ }
+
+ // update skin matrix
+
+ if ( forceUpdate || this.matrixWorldNeedsUpdate ) {
+
+ if( parentSkinMatrix ) {
+
+ this.skinMatrix.multiplyMatrices( parentSkinMatrix, this.matrix );
+
+ } else {
+
+ this.skinMatrix.copy( this.matrix );
+
+ }
+
+ this.matrixWorldNeedsUpdate = false;
+ forceUpdate = true;
+
+ }
+
+ // update children
+
+ var child, i, l = this.children.length;
+
+ for ( i = 0; i < l; i ++ ) {
+
+ this.children[ i ].update( this.skinMatrix, forceUpdate );
+
+ }
+
+};
+
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SkinnedMesh = function ( geometry, material, useVertexTexture ) {
+
+ THREE.Mesh.call( this, geometry, material );
+
+ //
+
+ this.useVertexTexture = useVertexTexture !== undefined ? useVertexTexture : true;
+
+ // init bones
+
+ this.identityMatrix = new THREE.Matrix4();
+
+ this.bones = [];
+ this.boneMatrices = [];
+
+ var b, bone, gbone, p, q, s;
+
+ if ( this.geometry && this.geometry.bones !== undefined ) {
+
+ for ( b = 0; b < this.geometry.bones.length; b ++ ) {
+
+ gbone = this.geometry.bones[ b ];
+
+ p = gbone.pos;
+ q = gbone.rotq;
+ s = gbone.scl;
+
+ bone = this.addBone();
+
+ bone.name = gbone.name;
+ bone.position.set( p[0], p[1], p[2] );
+ bone.quaternion.set( q[0], q[1], q[2], q[3] );
+
+ if ( s !== undefined ) {
+
+ bone.scale.set( s[0], s[1], s[2] );
+
+ } else {
+
+ bone.scale.set( 1, 1, 1 );
+
+ }
+
+ }
+
+ for ( b = 0; b < this.bones.length; b ++ ) {
+
+ gbone = this.geometry.bones[ b ];
+ bone = this.bones[ b ];
+
+ if ( gbone.parent === -1 ) {
+
+ this.add( bone );
+
+ } else {
+
+ this.bones[ gbone.parent ].add( bone );
+
+ }
+
+ }
+
+ //
+
+ var nBones = this.bones.length;
+
+ if ( this.useVertexTexture ) {
+
+ // layout (1 matrix = 4 pixels)
+ // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
+ // with 8x8 pixel texture max 16 bones (8 * 8 / 4)
+ // 16x16 pixel texture max 64 bones (16 * 16 / 4)
+ // 32x32 pixel texture max 256 bones (32 * 32 / 4)
+ // 64x64 pixel texture max 1024 bones (64 * 64 / 4)
+
+ var size;
+
+ if ( nBones > 256 )
+ size = 64;
+ else if ( nBones > 64 )
+ size = 32;
+ else if ( nBones > 16 )
+ size = 16;
+ else
+ size = 8;
+
+ this.boneTextureWidth = size;
+ this.boneTextureHeight = size;
+
+ this.boneMatrices = new Float32Array( this.boneTextureWidth * this.boneTextureHeight * 4 ); // 4 floats per RGBA pixel
+ this.boneTexture = new THREE.DataTexture( this.boneMatrices, this.boneTextureWidth, this.boneTextureHeight, THREE.RGBAFormat, THREE.FloatType );
+ this.boneTexture.minFilter = THREE.NearestFilter;
+ this.boneTexture.magFilter = THREE.NearestFilter;
+ this.boneTexture.generateMipmaps = false;
+ this.boneTexture.flipY = false;
+
+ } else {
+
+ this.boneMatrices = new Float32Array( 16 * nBones );
+
+ }
+
+ this.pose();
+
+ }
+
+};
+
+THREE.SkinnedMesh.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.SkinnedMesh.prototype.addBone = function( bone ) {
+
+ if ( bone === undefined ) {
+
+ bone = new THREE.Bone( this );
+
+ }
+
+ this.bones.push( bone );
+
+ return bone;
+
+};
+
+THREE.SkinnedMesh.prototype.updateMatrixWorld = function () {
+
+ var offsetMatrix = new THREE.Matrix4();
+
+ return function ( force ) {
+
+ this.matrixAutoUpdate && this.updateMatrix();
+
+ // update matrixWorld
+
+ if ( this.matrixWorldNeedsUpdate || force ) {
+
+ if ( this.parent ) {
+
+ this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+ } else {
+
+ this.matrixWorld.copy( this.matrix );
+
+ }
+
+ this.matrixWorldNeedsUpdate = false;
+
+ force = true;
+
+ }
+
+ // update children
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ var child = this.children[ i ];
+
+ if ( child instanceof THREE.Bone ) {
+
+ child.update( this.identityMatrix, false );
+
+ } else {
+
+ child.updateMatrixWorld( true );
+
+ }
+
+ }
+
+ // make a snapshot of the bones' rest position
+
+ if ( this.boneInverses == undefined ) {
+
+ this.boneInverses = [];
+
+ for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
+
+ var inverse = new THREE.Matrix4();
+
+ inverse.getInverse( this.bones[ b ].skinMatrix );
+
+ this.boneInverses.push( inverse );
+
+ }
+
+ }
+
+ // flatten bone matrices to array
+
+ for ( var b = 0, bl = this.bones.length; b < bl; b ++ ) {
+
+ // compute the offset between the current and the original transform;
+
+ // TODO: we could get rid of this multiplication step if the skinMatrix
+ // was already representing the offset; however, this requires some
+ // major changes to the animation system
+
+ offsetMatrix.multiplyMatrices( this.bones[ b ].skinMatrix, this.boneInverses[ b ] );
+ offsetMatrix.flattenToArrayOffset( this.boneMatrices, b * 16 );
+
+ }
+
+ if ( this.useVertexTexture ) {
+
+ this.boneTexture.needsUpdate = true;
+
+ }
+
+ };
+
+}();
+
+THREE.SkinnedMesh.prototype.pose = function () {
+
+ this.updateMatrixWorld( true );
+
+ this.normalizeSkinWeights();
+
+};
+
+THREE.SkinnedMesh.prototype.normalizeSkinWeights = function () {
+
+ if ( this.geometry instanceof THREE.Geometry ) {
+
+ for ( var i = 0; i < this.geometry.skinIndices.length; i ++ ) {
+
+ var sw = this.geometry.skinWeights[ i ];
+
+ var scale = 1.0 / sw.lengthManhattan();
+
+ if ( scale !== Infinity ) {
+
+ sw.multiplyScalar( scale );
+
+ } else {
+
+ sw.set( 1 ); // this will be normalized by the shader anyway
+
+ }
+
+ }
+
+ } else {
+
+ // skinning weights assumed to be normalized for THREE.BufferGeometry
+
+ }
+
+};
+
+THREE.SkinnedMesh.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) {
+
+ object = new THREE.SkinnedMesh( this.geometry, this.material, this.useVertexTexture );
+
+ }
+
+ THREE.Mesh.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.MorphAnimMesh = function ( geometry, material ) {
+
+ THREE.Mesh.call( this, geometry, material );
+
+ // API
+
+ this.duration = 1000; // milliseconds
+ this.mirroredLoop = false;
+ this.time = 0;
+
+ // internals
+
+ this.lastKeyframe = 0;
+ this.currentKeyframe = 0;
+
+ this.direction = 1;
+ this.directionBackwards = false;
+
+ this.setFrameRange( 0, this.geometry.morphTargets.length - 1 );
+
+};
+
+THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.MorphAnimMesh.prototype.setFrameRange = function ( start, end ) {
+
+ this.startKeyframe = start;
+ this.endKeyframe = end;
+
+ this.length = this.endKeyframe - this.startKeyframe + 1;
+
+};
+
+THREE.MorphAnimMesh.prototype.setDirectionForward = function () {
+
+ this.direction = 1;
+ this.directionBackwards = false;
+
+};
+
+THREE.MorphAnimMesh.prototype.setDirectionBackward = function () {
+
+ this.direction = -1;
+ this.directionBackwards = true;
+
+};
+
+THREE.MorphAnimMesh.prototype.parseAnimations = function () {
+
+ var geometry = this.geometry;
+
+ if ( ! geometry.animations ) geometry.animations = {};
+
+ var firstAnimation, animations = geometry.animations;
+
+ var pattern = /([a-z]+)(\d+)/;
+
+ for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
+
+ var morph = geometry.morphTargets[ i ];
+ var parts = morph.name.match( pattern );
+
+ if ( parts && parts.length > 1 ) {
+
+ var label = parts[ 1 ];
+ var num = parts[ 2 ];
+
+ if ( ! animations[ label ] ) animations[ label ] = { start: Infinity, end: -Infinity };
+
+ var animation = animations[ label ];
+
+ if ( i < animation.start ) animation.start = i;
+ if ( i > animation.end ) animation.end = i;
+
+ if ( ! firstAnimation ) firstAnimation = label;
+
+ }
+
+ }
+
+ geometry.firstAnimation = firstAnimation;
+
+};
+
+THREE.MorphAnimMesh.prototype.setAnimationLabel = function ( label, start, end ) {
+
+ if ( ! this.geometry.animations ) this.geometry.animations = {};
+
+ this.geometry.animations[ label ] = { start: start, end: end };
+
+};
+
+THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
+
+ var animation = this.geometry.animations[ label ];
+
+ if ( animation ) {
+
+ this.setFrameRange( animation.start, animation.end );
+ this.duration = 1000 * ( ( animation.end - animation.start ) / fps );
+ this.time = 0;
+
+ } else {
+
+ console.warn( "animation[" + label + "] undefined" );
+
+ }
+
+};
+
+THREE.MorphAnimMesh.prototype.updateAnimation = function ( delta ) {
+
+ var frameTime = this.duration / this.length;
+
+ this.time += this.direction * delta;
+
+ if ( this.mirroredLoop ) {
+
+ if ( this.time > this.duration || this.time < 0 ) {
+
+ this.direction *= -1;
+
+ if ( this.time > this.duration ) {
+
+ this.time = this.duration;
+ this.directionBackwards = true;
+
+ }
+
+ if ( this.time < 0 ) {
+
+ this.time = 0;
+ this.directionBackwards = false;
+
+ }
+
+ }
+
+ } else {
+
+ this.time = this.time % this.duration;
+
+ if ( this.time < 0 ) this.time += this.duration;
+
+ }
+
+ var keyframe = this.startKeyframe + THREE.Math.clamp( Math.floor( this.time / frameTime ), 0, this.length - 1 );
+
+ if ( keyframe !== this.currentKeyframe ) {
+
+ this.morphTargetInfluences[ this.lastKeyframe ] = 0;
+ this.morphTargetInfluences[ this.currentKeyframe ] = 1;
+
+ this.morphTargetInfluences[ keyframe ] = 0;
+
+ this.lastKeyframe = this.currentKeyframe;
+ this.currentKeyframe = keyframe;
+
+ }
+
+ var mix = ( this.time % frameTime ) / frameTime;
+
+ if ( this.directionBackwards ) {
+
+ mix = 1 - mix;
+
+ }
+
+ this.morphTargetInfluences[ this.currentKeyframe ] = mix;
+ this.morphTargetInfluences[ this.lastKeyframe ] = 1 - mix;
+
+};
+
+THREE.MorphAnimMesh.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.MorphAnimMesh( this.geometry, this.material );
+
+ object.duration = this.duration;
+ object.mirroredLoop = this.mirroredLoop;
+ object.time = this.time;
+
+ object.lastKeyframe = this.lastKeyframe;
+ object.currentKeyframe = this.currentKeyframe;
+
+ object.direction = this.direction;
+ object.directionBackwards = this.directionBackwards;
+
+ THREE.Mesh.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.LOD = function () {
+
+ THREE.Object3D.call( this );
+
+ this.objects = [];
+
+};
+
+
+THREE.LOD.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.LOD.prototype.addLevel = function ( object, distance ) {
+
+ if ( distance === undefined ) distance = 0;
+
+ distance = Math.abs( distance );
+
+ for ( var l = 0; l < this.objects.length; l ++ ) {
+
+ if ( distance < this.objects[ l ].distance ) {
+
+ break;
+
+ }
+
+ }
+
+ this.objects.splice( l, 0, { distance: distance, object: object } );
+ this.add( object );
+
+};
+
+THREE.LOD.prototype.getObjectForDistance = function ( distance ) {
+
+ for ( var i = 1, l = this.objects.length; i < l; i ++ ) {
+
+ if ( distance < this.objects[ i ].distance ) {
+
+ break;
+
+ }
+
+ }
+
+ return this.objects[ i - 1 ].object;
+
+};
+
+THREE.LOD.prototype.update = function () {
+
+ var v1 = new THREE.Vector3();
+ var v2 = new THREE.Vector3();
+
+ return function ( camera ) {
+
+ if ( this.objects.length > 1 ) {
+
+ v1.getPositionFromMatrix( camera.matrixWorld );
+ v2.getPositionFromMatrix( this.matrixWorld );
+
+ var distance = v1.distanceTo( v2 );
+
+ this.objects[ 0 ].object.visible = true;
+
+ for ( var i = 1, l = this.objects.length; i < l; i ++ ) {
+
+ if ( distance >= this.objects[ i ].distance ) {
+
+ this.objects[ i - 1 ].object.visible = false;
+ this.objects[ i ].object.visible = true;
+
+ } else {
+
+ break;
+
+ }
+
+ }
+
+ for( ; i < l; i ++ ) {
+
+ this.objects[ i ].object.visible = false;
+
+ }
+
+ }
+
+ };
+
+}();
+
+THREE.LOD.prototype.clone = function () {
+
+ // TODO
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Sprite = function ( material ) {
+
+ THREE.Object3D.call( this );
+
+ this.material = ( material !== undefined ) ? material : new THREE.SpriteMaterial();
+
+ this.rotation3d = this.rotation;
+ this.rotation = 0;
+
+};
+
+THREE.Sprite.prototype = Object.create( THREE.Object3D.prototype );
+
+/*
+ * Custom update matrix
+ */
+
+THREE.Sprite.prototype.updateMatrix = function () {
+
+ this.rotation3d.set( 0, 0, this.rotation, this.rotation3d.order );
+ this.quaternion.setFromEuler( this.rotation3d );
+ this.matrix.compose( this.position, this.quaternion, this.scale );
+
+ this.matrixWorldNeedsUpdate = true;
+
+};
+
+THREE.Sprite.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Sprite( this.material );
+
+ THREE.Object3D.prototype.clone.call( this, object );
+
+ return object;
+
+};
+
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Scene = function () {
+
+ THREE.Object3D.call( this );
+
+ this.fog = null;
+ this.overrideMaterial = null;
+
+ this.autoUpdate = true; // checked by the renderer
+ this.matrixAutoUpdate = false;
+
+ this.__lights = [];
+
+ this.__objectsAdded = [];
+ this.__objectsRemoved = [];
+
+};
+
+THREE.Scene.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Scene.prototype.__addObject = function ( object ) {
+
+ if ( object instanceof THREE.Light ) {
+
+ if ( this.__lights.indexOf( object ) === - 1 ) {
+
+ this.__lights.push( object );
+
+ }
+
+ if ( object.target && object.target.parent === undefined ) {
+
+ this.add( object.target );
+
+ }
+
+ } else if ( !( object instanceof THREE.Camera || object instanceof THREE.Bone ) ) {
+
+ this.__objectsAdded.push( object );
+
+ // check if previously removed
+
+ var i = this.__objectsRemoved.indexOf( object );
+
+ if ( i !== -1 ) {
+
+ this.__objectsRemoved.splice( i, 1 );
+
+ }
+
+ }
+
+ for ( var c = 0; c < object.children.length; c ++ ) {
+
+ this.__addObject( object.children[ c ] );
+
+ }
+
+};
+
+THREE.Scene.prototype.__removeObject = function ( object ) {
+
+ if ( object instanceof THREE.Light ) {
+
+ var i = this.__lights.indexOf( object );
+
+ if ( i !== -1 ) {
+
+ this.__lights.splice( i, 1 );
+
+ }
+
+ if ( object.shadowCascadeArray ) {
+
+ for ( var x = 0; x < object.shadowCascadeArray.length; x ++ ) {
+
+ this.__removeObject( object.shadowCascadeArray[ x ] );
+
+ }
+
+ }
+
+ } else if ( !( object instanceof THREE.Camera ) ) {
+
+ this.__objectsRemoved.push( object );
+
+ // check if previously added
+
+ var i = this.__objectsAdded.indexOf( object );
+
+ if ( i !== -1 ) {
+
+ this.__objectsAdded.splice( i, 1 );
+
+ }
+
+ }
+
+ for ( var c = 0; c < object.children.length; c ++ ) {
+
+ this.__removeObject( object.children[ c ] );
+
+ }
+
+};
+
+THREE.Scene.prototype.clone = function ( object ) {
+
+ if ( object === undefined ) object = new THREE.Scene();
+
+ THREE.Object3D.prototype.clone.call(this, object);
+
+ if ( this.fog !== null ) object.fog = this.fog.clone();
+ if ( this.overrideMaterial !== null ) object.overrideMaterial = this.overrideMaterial.clone();
+
+ object.autoUpdate = this.autoUpdate;
+ object.matrixAutoUpdate = this.matrixAutoUpdate;
+
+ return object;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Fog = function ( hex, near, far ) {
+
+ this.name = '';
+
+ this.color = new THREE.Color( hex );
+
+ this.near = ( near !== undefined ) ? near : 1;
+ this.far = ( far !== undefined ) ? far : 1000;
+
+};
+
+THREE.Fog.prototype.clone = function () {
+
+ return new THREE.Fog( this.color.getHex(), this.near, this.far );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.FogExp2 = function ( hex, density ) {
+
+ this.name = '';
+
+ this.color = new THREE.Color( hex );
+ this.density = ( density !== undefined ) ? density : 0.00025;
+
+};
+
+THREE.FogExp2.prototype.clone = function () {
+
+ return new THREE.FogExp2( this.color.getHex(), this.density );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.CanvasRenderer = function ( parameters ) {
+
+ console.log( 'THREE.CanvasRenderer', THREE.REVISION );
+
+ var smoothstep = THREE.Math.smoothstep;
+
+ parameters = parameters || {};
+
+ var _this = this,
+ _renderData, _elements, _lights,
+ _projector = new THREE.Projector(),
+
+ _canvas = parameters.canvas !== undefined
+ ? parameters.canvas
+ : document.createElement( 'canvas' ),
+
+ _canvasWidth, _canvasHeight, _canvasWidthHalf, _canvasHeightHalf,
+ _context = _canvas.getContext( '2d' ),
+
+ _clearColor = new THREE.Color( 0x000000 ),
+ _clearAlpha = 0,
+
+ _contextGlobalAlpha = 1,
+ _contextGlobalCompositeOperation = 0,
+ _contextStrokeStyle = null,
+ _contextFillStyle = null,
+ _contextLineWidth = null,
+ _contextLineCap = null,
+ _contextLineJoin = null,
+ _contextDashSize = null,
+ _contextGapSize = 0,
+
+ _camera,
+
+ _v1, _v2, _v3, _v4,
+ _v5 = new THREE.RenderableVertex(),
+ _v6 = new THREE.RenderableVertex(),
+
+ _v1x, _v1y, _v2x, _v2y, _v3x, _v3y,
+ _v4x, _v4y, _v5x, _v5y, _v6x, _v6y,
+
+ _color = new THREE.Color(),
+ _color1 = new THREE.Color(),
+ _color2 = new THREE.Color(),
+ _color3 = new THREE.Color(),
+ _color4 = new THREE.Color(),
+
+ _diffuseColor = new THREE.Color(),
+ _emissiveColor = new THREE.Color(),
+
+ _lightColor = new THREE.Color(),
+
+ _patterns = {}, _imagedatas = {},
+
+ _near, _far,
+
+ _image, _uvs,
+ _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y,
+
+ _clipBox = new THREE.Box2(),
+ _clearBox = new THREE.Box2(),
+ _elemBox = new THREE.Box2(),
+
+ _ambientLight = new THREE.Color(),
+ _directionalLights = new THREE.Color(),
+ _pointLights = new THREE.Color(),
+
+ _vector3 = new THREE.Vector3(), // Needed for PointLight
+
+ _pixelMap, _pixelMapContext, _pixelMapImage, _pixelMapData,
+ _gradientMap, _gradientMapContext, _gradientMapQuality = 16;
+
+ _pixelMap = document.createElement( 'canvas' );
+ _pixelMap.width = _pixelMap.height = 2;
+
+ _pixelMapContext = _pixelMap.getContext( '2d' );
+ _pixelMapContext.fillStyle = 'rgba(0,0,0,1)';
+ _pixelMapContext.fillRect( 0, 0, 2, 2 );
+
+ _pixelMapImage = _pixelMapContext.getImageData( 0, 0, 2, 2 );
+ _pixelMapData = _pixelMapImage.data;
+
+ _gradientMap = document.createElement( 'canvas' );
+ _gradientMap.width = _gradientMap.height = _gradientMapQuality;
+
+ _gradientMapContext = _gradientMap.getContext( '2d' );
+ _gradientMapContext.translate( - _gradientMapQuality / 2, - _gradientMapQuality / 2 );
+ _gradientMapContext.scale( _gradientMapQuality, _gradientMapQuality );
+
+ _gradientMapQuality --; // Fix UVs
+
+ // dash+gap fallbacks for Firefox and everything else
+
+ if ( _context.setLineDash === undefined ) {
+
+ if ( _context.mozDash !== undefined ) {
+
+ _context.setLineDash = function ( values ) {
+
+ _context.mozDash = values[ 0 ] !== null ? values : null;
+
+ }
+
+ } else {
+
+ _context.setLineDash = function () {}
+
+ }
+
+ }
+
+ this.domElement = _canvas;
+
+ this.devicePixelRatio = parameters.devicePixelRatio !== undefined
+ ? parameters.devicePixelRatio
+ : self.devicePixelRatio !== undefined
+ ? self.devicePixelRatio
+ : 1;
+
+ this.autoClear = true;
+ this.sortObjects = true;
+ this.sortElements = true;
+
+ this.info = {
+
+ render: {
+
+ vertices: 0,
+ faces: 0
+
+ }
+
+ }
+
+ // WebGLRenderer compatibility
+
+ this.supportsVertexTextures = function () {};
+ this.setFaceCulling = function () {};
+
+ this.setSize = function ( width, height, updateStyle ) {
+
+ _canvasWidth = width * this.devicePixelRatio;
+ _canvasHeight = height * this.devicePixelRatio;
+
+ _canvasWidthHalf = Math.floor( _canvasWidth / 2 );
+ _canvasHeightHalf = Math.floor( _canvasHeight / 2 );
+
+ _canvas.width = _canvasWidth;
+ _canvas.height = _canvasHeight;
+
+ if ( this.devicePixelRatio !== 1 && updateStyle !== false ) {
+
+ _canvas.style.width = width + 'px';
+ _canvas.style.height = height + 'px';
+
+ }
+
+ _clipBox.set(
+ new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+ new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+ );
+
+ _clearBox.set(
+ new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+ new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+ );
+
+ _contextGlobalAlpha = 1;
+ _contextGlobalCompositeOperation = 0;
+ _contextStrokeStyle = null;
+ _contextFillStyle = null;
+ _contextLineWidth = null;
+ _contextLineCap = null;
+ _contextLineJoin = null;
+
+ };
+
+ this.setClearColor = function ( color, alpha ) {
+
+ _clearColor.set( color );
+ _clearAlpha = alpha !== undefined ? alpha : 1;
+
+ _clearBox.set(
+ new THREE.Vector2( - _canvasWidthHalf, - _canvasHeightHalf ),
+ new THREE.Vector2( _canvasWidthHalf, _canvasHeightHalf )
+ );
+
+ };
+
+ this.setClearColorHex = function ( hex, alpha ) {
+
+ console.warn( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' );
+ this.setClearColor( hex, alpha );
+
+ };
+
+ this.getMaxAnisotropy = function () {
+
+ return 0;
+
+ };
+
+ this.clear = function () {
+
+ _context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf );
+
+ if ( _clearBox.empty() === false ) {
+
+ _clearBox.intersect( _clipBox );
+ _clearBox.expandByScalar( 2 );
+
+ if ( _clearAlpha < 1 ) {
+
+ _context.clearRect(
+ _clearBox.min.x | 0,
+ _clearBox.min.y | 0,
+ ( _clearBox.max.x - _clearBox.min.x ) | 0,
+ ( _clearBox.max.y - _clearBox.min.y ) | 0
+ );
+
+ }
+
+ if ( _clearAlpha > 0 ) {
+
+ setBlending( THREE.NormalBlending );
+ setOpacity( 1 );
+
+ setFillStyle( 'rgba(' + Math.floor( _clearColor.r * 255 ) + ',' + Math.floor( _clearColor.g * 255 ) + ',' + Math.floor( _clearColor.b * 255 ) + ',' + _clearAlpha + ')' );
+
+ _context.fillRect(
+ _clearBox.min.x | 0,
+ _clearBox.min.y | 0,
+ ( _clearBox.max.x - _clearBox.min.x ) | 0,
+ ( _clearBox.max.y - _clearBox.min.y ) | 0
+ );
+
+ }
+
+ _clearBox.makeEmpty();
+
+ }
+
+
+ };
+
+ this.render = function ( scene, camera ) {
+
+ if ( camera instanceof THREE.Camera === false ) {
+
+ console.error( 'THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.' );
+ return;
+
+ }
+
+ if ( this.autoClear === true ) this.clear();
+
+ _context.setTransform( 1, 0, 0, - 1, _canvasWidthHalf, _canvasHeightHalf );
+
+ _this.info.render.vertices = 0;
+ _this.info.render.faces = 0;
+
+ _renderData = _projector.projectScene( scene, camera, this.sortObjects, this.sortElements );
+ _elements = _renderData.elements;
+ _lights = _renderData.lights;
+ _camera = camera;
+
+ /* DEBUG
+ setFillStyle( 'rgba( 0, 255, 255, 0.5 )' );
+ _context.fillRect( _clipBox.min.x, _clipBox.min.y, _clipBox.max.x - _clipBox.min.x, _clipBox.max.y - _clipBox.min.y );
+ */
+
+ calculateLights();
+
+ for ( var e = 0, el = _elements.length; e < el; e++ ) {
+
+ var element = _elements[ e ];
+
+ var material = element.material;
+
+ if ( material === undefined || material.visible === false ) continue;
+
+ _elemBox.makeEmpty();
+
+ if ( element instanceof THREE.RenderableParticle ) {
+
+ _v1 = element;
+ _v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf;
+
+ renderParticle( _v1, element, material );
+
+ } else if ( element instanceof THREE.RenderableLine ) {
+
+ _v1 = element.v1; _v2 = element.v2;
+
+ _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+ _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+
+ _elemBox.setFromPoints( [
+ _v1.positionScreen,
+ _v2.positionScreen
+ ] );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+ renderLine( _v1, _v2, element, material );
+
+ }
+
+ } else if ( element instanceof THREE.RenderableFace3 ) {
+
+ _v1 = element.v1; _v2 = element.v2; _v3 = element.v3;
+
+ if ( _v1.positionScreen.z < -1 || _v1.positionScreen.z > 1 ) continue;
+ if ( _v2.positionScreen.z < -1 || _v2.positionScreen.z > 1 ) continue;
+ if ( _v3.positionScreen.z < -1 || _v3.positionScreen.z > 1 ) continue;
+
+ _v1.positionScreen.x *= _canvasWidthHalf; _v1.positionScreen.y *= _canvasHeightHalf;
+ _v2.positionScreen.x *= _canvasWidthHalf; _v2.positionScreen.y *= _canvasHeightHalf;
+ _v3.positionScreen.x *= _canvasWidthHalf; _v3.positionScreen.y *= _canvasHeightHalf;
+
+ if ( material.overdraw > 0 ) {
+
+ expand( _v1.positionScreen, _v2.positionScreen, material.overdraw );
+ expand( _v2.positionScreen, _v3.positionScreen, material.overdraw );
+ expand( _v3.positionScreen, _v1.positionScreen, material.overdraw );
+
+ }
+
+ _elemBox.setFromPoints( [
+ _v1.positionScreen,
+ _v2.positionScreen,
+ _v3.positionScreen
+ ] );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === true ) {
+
+ renderFace3( _v1, _v2, _v3, 0, 1, 2, element, material );
+
+ }
+
+ }
+
+ /* DEBUG
+ setLineWidth( 1 );
+ setStrokeStyle( 'rgba( 0, 255, 0, 0.5 )' );
+ _context.strokeRect( _elemBox.min.x, _elemBox.min.y, _elemBox.max.x - _elemBox.min.x, _elemBox.max.y - _elemBox.min.y );
+ */
+
+ _clearBox.union( _elemBox );
+
+ }
+
+ /* DEBUG
+ setLineWidth( 1 );
+ setStrokeStyle( 'rgba( 255, 0, 0, 0.5 )' );
+ _context.strokeRect( _clearBox.min.x, _clearBox.min.y, _clearBox.max.x - _clearBox.min.x, _clearBox.max.y - _clearBox.min.y );
+ */
+
+ _context.setTransform( 1, 0, 0, 1, 0, 0 );
+
+ };
+
+ //
+
+ function calculateLights() {
+
+ _ambientLight.setRGB( 0, 0, 0 );
+ _directionalLights.setRGB( 0, 0, 0 );
+ _pointLights.setRGB( 0, 0, 0 );
+
+ for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
+
+ var light = _lights[ l ];
+ var lightColor = light.color;
+
+ if ( light instanceof THREE.AmbientLight ) {
+
+ _ambientLight.add( lightColor );
+
+ } else if ( light instanceof THREE.DirectionalLight ) {
+
+ // for particles
+
+ _directionalLights.add( lightColor );
+
+ } else if ( light instanceof THREE.PointLight ) {
+
+ // for particles
+
+ _pointLights.add( lightColor );
+
+ }
+
+ }
+
+ }
+
+ function calculateLight( position, normal, color ) {
+
+ for ( var l = 0, ll = _lights.length; l < ll; l ++ ) {
+
+ var light = _lights[ l ];
+
+ _lightColor.copy( light.color );
+
+ if ( light instanceof THREE.DirectionalLight ) {
+
+ var lightPosition = _vector3.getPositionFromMatrix( light.matrixWorld ).normalize();
+
+ var amount = normal.dot( lightPosition );
+
+ if ( amount <= 0 ) continue;
+
+ amount *= light.intensity;
+
+ color.add( _lightColor.multiplyScalar( amount ) );
+
+ } else if ( light instanceof THREE.PointLight ) {
+
+ var lightPosition = _vector3.getPositionFromMatrix( light.matrixWorld );
+
+ var amount = normal.dot( _vector3.subVectors( lightPosition, position ).normalize() );
+
+ if ( amount <= 0 ) continue;
+
+ amount *= light.distance == 0 ? 1 : 1 - Math.min( position.distanceTo( lightPosition ) / light.distance, 1 );
+
+ if ( amount == 0 ) continue;
+
+ amount *= light.intensity;
+
+ color.add( _lightColor.multiplyScalar( amount ) );
+
+ }
+
+ }
+
+ }
+
+ function renderParticle( v1, element, material ) {
+
+ setOpacity( material.opacity );
+ setBlending( material.blending );
+
+ var width, height, scaleX, scaleY,
+ bitmap, bitmapWidth, bitmapHeight;
+
+ if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+ if ( material.map === null ) {
+
+ scaleX = element.object.scale.x;
+ scaleY = element.object.scale.y;
+
+ // TODO: Be able to disable this
+
+ scaleX *= element.scale.x * _canvasWidthHalf;
+ scaleY *= element.scale.y * _canvasHeightHalf;
+
+ _elemBox.min.set( v1.x - scaleX, v1.y - scaleY );
+ _elemBox.max.set( v1.x + scaleX, v1.y + scaleY );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+ _elemBox.makeEmpty();
+ return;
+
+ }
+
+ setFillStyle( material.color.getStyle() );
+
+ _context.save();
+ _context.translate( v1.x, v1.y );
+ _context.rotate( - element.rotation );
+ _context.scale( scaleX, scaleY );
+ _context.fillRect( -1, -1, 2, 2 );
+ _context.restore();
+
+ } else {
+
+ bitmap = material.map.image;
+ bitmapWidth = bitmap.width >> 1;
+ bitmapHeight = bitmap.height >> 1;
+
+ scaleX = element.scale.x * _canvasWidthHalf;
+ scaleY = element.scale.y * _canvasHeightHalf;
+
+ width = scaleX * bitmapWidth;
+ height = scaleY * bitmapHeight;
+
+ // TODO: Rotations break this...
+
+ _elemBox.min.set( v1.x - width, v1.y - height );
+ _elemBox.max.set( v1.x + width, v1.y + height );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+ _elemBox.makeEmpty();
+ return;
+
+ }
+
+ _context.save();
+ _context.translate( v1.x, v1.y );
+ _context.rotate( - element.rotation );
+ _context.scale( scaleX, - scaleY );
+
+ _context.translate( - bitmapWidth, - bitmapHeight );
+ _context.drawImage( bitmap, 0, 0 );
+ _context.restore();
+
+ }
+
+ /* DEBUG
+ setStrokeStyle( 'rgb(255,255,0)' );
+ _context.beginPath();
+ _context.moveTo( v1.x - 10, v1.y );
+ _context.lineTo( v1.x + 10, v1.y );
+ _context.moveTo( v1.x, v1.y - 10 );
+ _context.lineTo( v1.x, v1.y + 10 );
+ _context.stroke();
+ */
+
+ } else if ( material instanceof THREE.ParticleCanvasMaterial ) {
+
+ width = element.scale.x * _canvasWidthHalf;
+ height = element.scale.y * _canvasHeightHalf;
+
+ _elemBox.min.set( v1.x - width, v1.y - height );
+ _elemBox.max.set( v1.x + width, v1.y + height );
+
+ if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+ _elemBox.makeEmpty();
+ return;
+
+ }
+
+ setStrokeStyle( material.color.getStyle() );
+ setFillStyle( material.color.getStyle() );
+
+ _context.save();
+ _context.translate( v1.x, v1.y );
+ _context.rotate( - element.rotation );
+ _context.scale( width, height );
+
+ material.program( _context );
+
+ _context.restore();
+
+ }
+
+ }
+
+ function renderLine( v1, v2, element, material ) {
+
+ setOpacity( material.opacity );
+ setBlending( material.blending );
+
+ _context.beginPath();
+ _context.moveTo( v1.positionScreen.x, v1.positionScreen.y );
+ _context.lineTo( v2.positionScreen.x, v2.positionScreen.y );
+
+ if ( material instanceof THREE.LineBasicMaterial ) {
+
+ setLineWidth( material.linewidth );
+ setLineCap( material.linecap );
+ setLineJoin( material.linejoin );
+
+ if ( material.vertexColors !== THREE.VertexColors ) {
+
+ setStrokeStyle( material.color.getStyle() );
+
+ } else {
+
+ var colorStyle1 = element.vertexColors[0].getStyle();
+ var colorStyle2 = element.vertexColors[1].getStyle();
+
+ if ( colorStyle1 === colorStyle2 ) {
+
+ setStrokeStyle( colorStyle1 );
+
+ } else {
+
+ try {
+
+ var grad = _context.createLinearGradient(
+ v1.positionScreen.x,
+ v1.positionScreen.y,
+ v2.positionScreen.x,
+ v2.positionScreen.y
+ );
+ grad.addColorStop( 0, colorStyle1 );
+ grad.addColorStop( 1, colorStyle2 );
+
+ } catch ( exception ) {
+
+ grad = colorStyle1;
+
+ }
+
+ setStrokeStyle( grad );
+
+ }
+
+ }
+
+ _context.stroke();
+ _elemBox.expandByScalar( material.linewidth * 2 );
+
+ } else if ( material instanceof THREE.LineDashedMaterial ) {
+
+ setLineWidth( material.linewidth );
+ setLineCap( material.linecap );
+ setLineJoin( material.linejoin );
+ setStrokeStyle( material.color.getStyle() );
+ setDashAndGap( material.dashSize, material.gapSize );
+
+ _context.stroke();
+
+ _elemBox.expandByScalar( material.linewidth * 2 );
+
+ setDashAndGap( null, null );
+
+ }
+
+ }
+
+ function renderFace3( v1, v2, v3, uv1, uv2, uv3, element, material ) {
+
+ _this.info.render.vertices += 3;
+ _this.info.render.faces ++;
+
+ setOpacity( material.opacity );
+ setBlending( material.blending );
+
+ _v1x = v1.positionScreen.x; _v1y = v1.positionScreen.y;
+ _v2x = v2.positionScreen.x; _v2y = v2.positionScreen.y;
+ _v3x = v3.positionScreen.x; _v3y = v3.positionScreen.y;
+
+ drawTriangle( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y );
+
+ if ( ( material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) && material.map === null ) {
+
+ _diffuseColor.copy( material.color );
+ _emissiveColor.copy( material.emissive );
+
+ if ( material.vertexColors === THREE.FaceColors ) {
+
+ _diffuseColor.multiply( element.color );
+
+ }
+
+ if ( material.wireframe === false && material.shading == THREE.SmoothShading && element.vertexNormalsLength == 3 ) {
+
+ _color1.copy( _ambientLight );
+ _color2.copy( _ambientLight );
+ _color3.copy( _ambientLight );
+
+ calculateLight( element.v1.positionWorld, element.vertexNormalsModel[ 0 ], _color1 );
+ calculateLight( element.v2.positionWorld, element.vertexNormalsModel[ 1 ], _color2 );
+ calculateLight( element.v3.positionWorld, element.vertexNormalsModel[ 2 ], _color3 );
+
+ _color1.multiply( _diffuseColor ).add( _emissiveColor );
+ _color2.multiply( _diffuseColor ).add( _emissiveColor );
+ _color3.multiply( _diffuseColor ).add( _emissiveColor );
+ _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+ _image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+ clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+ } else {
+
+ _color.copy( _ambientLight );
+
+ calculateLight( element.centroidModel, element.normalModel, _color );
+
+ _color.multiply( _diffuseColor ).add( _emissiveColor );
+
+ material.wireframe === true
+ ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+ : fillPath( _color );
+
+ }
+
+ } else if ( material instanceof THREE.MeshBasicMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) {
+
+ if ( material.map !== null ) {
+
+ if ( material.map.mapping instanceof THREE.UVMapping ) {
+
+ _uvs = element.uvs[ 0 ];
+ patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uvs[ uv1 ].x, _uvs[ uv1 ].y, _uvs[ uv2 ].x, _uvs[ uv2 ].y, _uvs[ uv3 ].x, _uvs[ uv3 ].y, material.map );
+
+ }
+
+
+ } else if ( material.envMap !== null ) {
+
+ if ( material.envMap.mapping instanceof THREE.SphericalReflectionMapping ) {
+
+ _vector3.copy( element.vertexNormalsModelView[ uv1 ] );
+ _uv1x = 0.5 * _vector3.x + 0.5;
+ _uv1y = 0.5 * _vector3.y + 0.5;
+
+ _vector3.copy( element.vertexNormalsModelView[ uv2 ] );
+ _uv2x = 0.5 * _vector3.x + 0.5;
+ _uv2y = 0.5 * _vector3.y + 0.5;
+
+ _vector3.copy( element.vertexNormalsModelView[ uv3 ] );
+ _uv3x = 0.5 * _vector3.x + 0.5;
+ _uv3y = 0.5 * _vector3.y + 0.5;
+
+ patternPath( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, _uv1x, _uv1y, _uv2x, _uv2y, _uv3x, _uv3y, material.envMap );
+
+ }/* else if ( material.envMap.mapping == THREE.SphericalRefractionMapping ) {
+
+
+
+ }*/
+
+
+ } else {
+
+ _color.copy( material.color );
+
+ if ( material.vertexColors === THREE.FaceColors ) {
+
+ _color.multiply( element.color );
+
+ }
+
+ material.wireframe === true
+ ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+ : fillPath( _color );
+
+ }
+
+ } else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+ _near = _camera.near;
+ _far = _camera.far;
+
+ _color1.r = _color1.g = _color1.b = 1 - smoothstep( v1.positionScreen.z * v1.positionScreen.w, _near, _far );
+ _color2.r = _color2.g = _color2.b = 1 - smoothstep( v2.positionScreen.z * v2.positionScreen.w, _near, _far );
+ _color3.r = _color3.g = _color3.b = 1 - smoothstep( v3.positionScreen.z * v3.positionScreen.w, _near, _far );
+ _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+ _image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+ clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+ } else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+ var normal;
+
+ if ( material.shading == THREE.FlatShading ) {
+
+ normal = element.normalModelView;
+
+ _color.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+ material.wireframe === true
+ ? strokePath( _color, material.wireframeLinewidth, material.wireframeLinecap, material.wireframeLinejoin )
+ : fillPath( _color );
+
+ } else if ( material.shading == THREE.SmoothShading ) {
+
+ normal = element.vertexNormalsModelView[ uv1 ];
+ _color1.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+ normal = element.vertexNormalsModelView[ uv2 ];
+ _color2.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+ normal = element.vertexNormalsModelView[ uv3 ];
+ _color3.setRGB( normal.x, normal.y, normal.z ).multiplyScalar( 0.5 ).addScalar( 0.5 );
+
+ _color4.addColors( _color2, _color3 ).multiplyScalar( 0.5 );
+
+ _image = getGradientTexture( _color1, _color2, _color3, _color4 );
+
+ clipImage( _v1x, _v1y, _v2x, _v2y, _v3x, _v3y, 0, 0, 1, 0, 0, 1, _image );
+
+ }
+
+ }
+
+ }
+
+ //
+
+ function drawTriangle( x0, y0, x1, y1, x2, y2 ) {
+
+ _context.beginPath();
+ _context.moveTo( x0, y0 );
+ _context.lineTo( x1, y1 );
+ _context.lineTo( x2, y2 );
+ _context.closePath();
+
+ }
+
+ function strokePath( color, linewidth, linecap, linejoin ) {
+
+ setLineWidth( linewidth );
+ setLineCap( linecap );
+ setLineJoin( linejoin );
+ setStrokeStyle( color.getStyle() );
+
+ _context.stroke();
+
+ _elemBox.expandByScalar( linewidth * 2 );
+
+ }
+
+ function fillPath( color ) {
+
+ setFillStyle( color.getStyle() );
+ _context.fill();
+
+ }
+
+ function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) {
+
+ if ( texture instanceof THREE.DataTexture || texture.image === undefined || texture.image.width == 0 ) return;
+
+ if ( texture.needsUpdate === true ) {
+
+ var repeatX = texture.wrapS == THREE.RepeatWrapping;
+ var repeatY = texture.wrapT == THREE.RepeatWrapping;
+
+ _patterns[ texture.id ] = _context.createPattern(
+ texture.image, repeatX === true && repeatY === true
+ ? 'repeat'
+ : repeatX === true && repeatY === false
+ ? 'repeat-x'
+ : repeatX === false && repeatY === true
+ ? 'repeat-y'
+ : 'no-repeat'
+ );
+
+ texture.needsUpdate = false;
+
+ }
+
+ _patterns[ texture.id ] === undefined
+ ? setFillStyle( 'rgba(0,0,0,1)' )
+ : setFillStyle( _patterns[ texture.id ] );
+
+ // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+
+ var a, b, c, d, e, f, det, idet,
+ offsetX = texture.offset.x / texture.repeat.x,
+ offsetY = texture.offset.y / texture.repeat.y,
+ width = texture.image.width * texture.repeat.x,
+ height = texture.image.height * texture.repeat.y;
+
+ u0 = ( u0 + offsetX ) * width;
+ v0 = ( 1.0 - v0 + offsetY ) * height;
+
+ u1 = ( u1 + offsetX ) * width;
+ v1 = ( 1.0 - v1 + offsetY ) * height;
+
+ u2 = ( u2 + offsetX ) * width;
+ v2 = ( 1.0 - v2 + offsetY ) * height;
+
+ x1 -= x0; y1 -= y0;
+ x2 -= x0; y2 -= y0;
+
+ u1 -= u0; v1 -= v0;
+ u2 -= u0; v2 -= v0;
+
+ det = u1 * v2 - u2 * v1;
+
+ if ( det === 0 ) {
+
+ if ( _imagedatas[ texture.id ] === undefined ) {
+
+ var canvas = document.createElement( 'canvas' )
+ canvas.width = texture.image.width;
+ canvas.height = texture.image.height;
+
+ var context = canvas.getContext( '2d' );
+ context.drawImage( texture.image, 0, 0 );
+
+ _imagedatas[ texture.id ] = context.getImageData( 0, 0, texture.image.width, texture.image.height ).data;
+
+ }
+
+ var data = _imagedatas[ texture.id ];
+ var index = ( Math.floor( u0 ) + Math.floor( v0 ) * texture.image.width ) * 4;
+
+ _color.setRGB( data[ index ] / 255, data[ index + 1 ] / 255, data[ index + 2 ] / 255 );
+ fillPath( _color );
+
+ return;
+
+ }
+
+ idet = 1 / det;
+
+ a = ( v2 * x1 - v1 * x2 ) * idet;
+ b = ( v2 * y1 - v1 * y2 ) * idet;
+ c = ( u1 * x2 - u2 * x1 ) * idet;
+ d = ( u1 * y2 - u2 * y1 ) * idet;
+
+ e = x0 - a * u0 - c * v0;
+ f = y0 - b * u0 - d * v0;
+
+ _context.save();
+ _context.transform( a, b, c, d, e, f );
+ _context.fill();
+ _context.restore();
+
+ }
+
+ function clipImage( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, image ) {
+
+ // http://extremelysatisfactorytotalitarianism.com/blog/?p=2120
+
+ var a, b, c, d, e, f, det, idet,
+ width = image.width - 1,
+ height = image.height - 1;
+
+ u0 *= width; v0 *= height;
+ u1 *= width; v1 *= height;
+ u2 *= width; v2 *= height;
+
+ x1 -= x0; y1 -= y0;
+ x2 -= x0; y2 -= y0;
+
+ u1 -= u0; v1 -= v0;
+ u2 -= u0; v2 -= v0;
+
+ det = u1 * v2 - u2 * v1;
+
+ idet = 1 / det;
+
+ a = ( v2 * x1 - v1 * x2 ) * idet;
+ b = ( v2 * y1 - v1 * y2 ) * idet;
+ c = ( u1 * x2 - u2 * x1 ) * idet;
+ d = ( u1 * y2 - u2 * y1 ) * idet;
+
+ e = x0 - a * u0 - c * v0;
+ f = y0 - b * u0 - d * v0;
+
+ _context.save();
+ _context.transform( a, b, c, d, e, f );
+ _context.clip();
+ _context.drawImage( image, 0, 0 );
+ _context.restore();
+
+ }
+
+ function getGradientTexture( color1, color2, color3, color4 ) {
+
+ // http://mrdoob.com/blog/post/710
+
+ _pixelMapData[ 0 ] = ( color1.r * 255 ) | 0;
+ _pixelMapData[ 1 ] = ( color1.g * 255 ) | 0;
+ _pixelMapData[ 2 ] = ( color1.b * 255 ) | 0;
+
+ _pixelMapData[ 4 ] = ( color2.r * 255 ) | 0;
+ _pixelMapData[ 5 ] = ( color2.g * 255 ) | 0;
+ _pixelMapData[ 6 ] = ( color2.b * 255 ) | 0;
+
+ _pixelMapData[ 8 ] = ( color3.r * 255 ) | 0;
+ _pixelMapData[ 9 ] = ( color3.g * 255 ) | 0;
+ _pixelMapData[ 10 ] = ( color3.b * 255 ) | 0;
+
+ _pixelMapData[ 12 ] = ( color4.r * 255 ) | 0;
+ _pixelMapData[ 13 ] = ( color4.g * 255 ) | 0;
+ _pixelMapData[ 14 ] = ( color4.b * 255 ) | 0;
+
+ _pixelMapContext.putImageData( _pixelMapImage, 0, 0 );
+ _gradientMapContext.drawImage( _pixelMap, 0, 0 );
+
+ return _gradientMap;
+
+ }
+
+ // Hide anti-alias gaps
+
+ function expand( v1, v2, pixels ) {
+
+ var x = v2.x - v1.x, y = v2.y - v1.y,
+ det = x * x + y * y, idet;
+
+ if ( det === 0 ) return;
+
+ idet = pixels / Math.sqrt( det );
+
+ x *= idet; y *= idet;
+
+ v2.x += x; v2.y += y;
+ v1.x -= x; v1.y -= y;
+
+ }
+
+ // Context cached methods.
+
+ function setOpacity( value ) {
+
+ if ( _contextGlobalAlpha !== value ) {
+
+ _context.globalAlpha = value;
+ _contextGlobalAlpha = value;
+
+ }
+
+ }
+
+ function setBlending( value ) {
+
+ if ( _contextGlobalCompositeOperation !== value ) {
+
+ if ( value === THREE.NormalBlending ) {
+
+ _context.globalCompositeOperation = 'source-over';
+
+ } else if ( value === THREE.AdditiveBlending ) {
+
+ _context.globalCompositeOperation = 'lighter';
+
+ } else if ( value === THREE.SubtractiveBlending ) {
+
+ _context.globalCompositeOperation = 'darker';
+
+ }
+
+ _contextGlobalCompositeOperation = value;
+
+ }
+
+ }
+
+ function setLineWidth( value ) {
+
+ if ( _contextLineWidth !== value ) {
+
+ _context.lineWidth = value;
+ _contextLineWidth = value;
+
+ }
+
+ }
+
+ function setLineCap( value ) {
+
+ // "butt", "round", "square"
+
+ if ( _contextLineCap !== value ) {
+
+ _context.lineCap = value;
+ _contextLineCap = value;
+
+ }
+
+ }
+
+ function setLineJoin( value ) {
+
+ // "round", "bevel", "miter"
+
+ if ( _contextLineJoin !== value ) {
+
+ _context.lineJoin = value;
+ _contextLineJoin = value;
+
+ }
+
+ }
+
+ function setStrokeStyle( value ) {
+
+ if ( _contextStrokeStyle !== value ) {
+
+ _context.strokeStyle = value;
+ _contextStrokeStyle = value;
+
+ }
+
+ }
+
+ function setFillStyle( value ) {
+
+ if ( _contextFillStyle !== value ) {
+
+ _context.fillStyle = value;
+ _contextFillStyle = value;
+
+ }
+
+ }
+
+ function setDashAndGap( dashSizeValue, gapSizeValue ) {
+
+ if ( _contextDashSize !== dashSizeValue || _contextGapSize !== gapSizeValue ) {
+
+ _context.setLineDash( [ dashSizeValue, gapSizeValue ] );
+ _contextDashSize = dashSizeValue;
+ _contextGapSize = gapSizeValue;
+
+ }
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+THREE.ShaderChunk = {
+
+ // FOG
+
+ fog_pars_fragment: [
+
+ "#ifdef USE_FOG",
+
+ "uniform vec3 fogColor;",
+
+ "#ifdef FOG_EXP2",
+
+ "uniform float fogDensity;",
+
+ "#else",
+
+ "uniform float fogNear;",
+ "uniform float fogFar;",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ fog_fragment: [
+
+ "#ifdef USE_FOG",
+
+ "float depth = gl_FragCoord.z / gl_FragCoord.w;",
+
+ "#ifdef FOG_EXP2",
+
+ "const float LOG2 = 1.442695;",
+ "float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );",
+ "fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );",
+
+ "#else",
+
+ "float fogFactor = smoothstep( fogNear, fogFar, depth );",
+
+ "#endif",
+
+ "gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // ENVIRONMENT MAP
+
+ envmap_pars_fragment: [
+
+ "#ifdef USE_ENVMAP",
+
+ "uniform float reflectivity;",
+ "uniform samplerCube envMap;",
+ "uniform float flipEnvMap;",
+ "uniform int combine;",
+
+ "#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )",
+
+ "uniform bool useRefract;",
+ "uniform float refractionRatio;",
+
+ "#else",
+
+ "varying vec3 vReflect;",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ envmap_fragment: [
+
+ "#ifdef USE_ENVMAP",
+
+ "vec3 reflectVec;",
+
+ "#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )",
+
+ "vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+ "if ( useRefract ) {",
+
+ "reflectVec = refract( cameraToVertex, normal, refractionRatio );",
+
+ "} else { ",
+
+ "reflectVec = reflect( cameraToVertex, normal );",
+
+ "}",
+
+ "#else",
+
+ "reflectVec = vReflect;",
+
+ "#endif",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "float flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );",
+ "vec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );",
+
+ "#else",
+
+ "vec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );",
+
+ "#endif",
+
+ "#ifdef GAMMA_INPUT",
+
+ "cubeColor.xyz *= cubeColor.xyz;",
+
+ "#endif",
+
+ "if ( combine == 1 ) {",
+
+ "gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );",
+
+ "} else if ( combine == 2 ) {",
+
+ "gl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;",
+
+ "} else {",
+
+ "gl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ envmap_pars_vertex: [
+
+ "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )",
+
+ "varying vec3 vReflect;",
+
+ "uniform float refractionRatio;",
+ "uniform bool useRefract;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ worldpos_vertex : [
+
+ "#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )",
+
+ "#ifdef USE_SKINNING",
+
+ "vec4 worldPosition = modelMatrix * skinned;",
+
+ "#endif",
+
+ "#if defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )",
+
+ "vec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );",
+
+ "#endif",
+
+ "#if ! defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )",
+
+ "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ envmap_vertex : [
+
+ "#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )",
+
+ "vec3 worldNormal = mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * objectNormal;",
+ "worldNormal = normalize( worldNormal );",
+
+ "vec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );",
+
+ "if ( useRefract ) {",
+
+ "vReflect = refract( cameraToVertex, worldNormal, refractionRatio );",
+
+ "} else {",
+
+ "vReflect = reflect( cameraToVertex, worldNormal );",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // COLOR MAP (particles)
+
+ map_particle_pars_fragment: [
+
+ "#ifdef USE_MAP",
+
+ "uniform sampler2D map;",
+
+ "#endif"
+
+ ].join("\n"),
+
+
+ map_particle_fragment: [
+
+ "#ifdef USE_MAP",
+
+ "gl_FragColor = gl_FragColor * texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // COLOR MAP (triangles)
+
+ map_pars_vertex: [
+
+ "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+ "varying vec2 vUv;",
+ "uniform vec4 offsetRepeat;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ map_pars_fragment: [
+
+ "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+ "varying vec2 vUv;",
+
+ "#endif",
+
+ "#ifdef USE_MAP",
+
+ "uniform sampler2D map;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ map_vertex: [
+
+ "#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )",
+
+ "vUv = uv * offsetRepeat.zw + offsetRepeat.xy;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ map_fragment: [
+
+ "#ifdef USE_MAP",
+
+ "vec4 texelColor = texture2D( map, vUv );",
+
+ "#ifdef GAMMA_INPUT",
+
+ "texelColor.xyz *= texelColor.xyz;",
+
+ "#endif",
+
+ "gl_FragColor = gl_FragColor * texelColor;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // LIGHT MAP
+
+ lightmap_pars_fragment: [
+
+ "#ifdef USE_LIGHTMAP",
+
+ "varying vec2 vUv2;",
+ "uniform sampler2D lightMap;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lightmap_pars_vertex: [
+
+ "#ifdef USE_LIGHTMAP",
+
+ "varying vec2 vUv2;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lightmap_fragment: [
+
+ "#ifdef USE_LIGHTMAP",
+
+ "gl_FragColor = gl_FragColor * texture2D( lightMap, vUv2 );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lightmap_vertex: [
+
+ "#ifdef USE_LIGHTMAP",
+
+ "vUv2 = uv2;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // BUMP MAP
+
+ bumpmap_pars_fragment: [
+
+ "#ifdef USE_BUMPMAP",
+
+ "uniform sampler2D bumpMap;",
+ "uniform float bumpScale;",
+
+ // Derivative maps - bump mapping unparametrized surfaces by Morten Mikkelsen
+ // http://mmikkelsen3d.blogspot.sk/2011/07/derivative-maps.html
+
+ // Evaluate the derivative of the height w.r.t. screen-space using forward differencing (listing 2)
+
+ "vec2 dHdxy_fwd() {",
+
+ "vec2 dSTdx = dFdx( vUv );",
+ "vec2 dSTdy = dFdy( vUv );",
+
+ "float Hll = bumpScale * texture2D( bumpMap, vUv ).x;",
+ "float dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;",
+ "float dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;",
+
+ "return vec2( dBx, dBy );",
+
+ "}",
+
+ "vec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {",
+
+ "vec3 vSigmaX = dFdx( surf_pos );",
+ "vec3 vSigmaY = dFdy( surf_pos );",
+ "vec3 vN = surf_norm;", // normalized
+
+ "vec3 R1 = cross( vSigmaY, vN );",
+ "vec3 R2 = cross( vN, vSigmaX );",
+
+ "float fDet = dot( vSigmaX, R1 );",
+
+ "vec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );",
+ "return normalize( abs( fDet ) * surf_norm - vGrad );",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // NORMAL MAP
+
+ normalmap_pars_fragment: [
+
+ "#ifdef USE_NORMALMAP",
+
+ "uniform sampler2D normalMap;",
+ "uniform vec2 normalScale;",
+
+ // Per-Pixel Tangent Space Normal Mapping
+ // http://hacksoflife.blogspot.ch/2009/11/per-pixel-tangent-space-normal-mapping.html
+
+ "vec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {",
+
+ "vec3 q0 = dFdx( eye_pos.xyz );",
+ "vec3 q1 = dFdy( eye_pos.xyz );",
+ "vec2 st0 = dFdx( vUv.st );",
+ "vec2 st1 = dFdy( vUv.st );",
+
+ "vec3 S = normalize( q0 * st1.t - q1 * st0.t );",
+ "vec3 T = normalize( -q0 * st1.s + q1 * st0.s );",
+ "vec3 N = normalize( surf_norm );",
+
+ "vec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;",
+ "mapN.xy = normalScale * mapN.xy;",
+ "mat3 tsn = mat3( S, T, N );",
+ "return normalize( tsn * mapN );",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // SPECULAR MAP
+
+ specularmap_pars_fragment: [
+
+ "#ifdef USE_SPECULARMAP",
+
+ "uniform sampler2D specularMap;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ specularmap_fragment: [
+
+ "float specularStrength;",
+
+ "#ifdef USE_SPECULARMAP",
+
+ "vec4 texelSpecular = texture2D( specularMap, vUv );",
+ "specularStrength = texelSpecular.r;",
+
+ "#else",
+
+ "specularStrength = 1.0;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // LIGHTS LAMBERT
+
+ lights_lambert_pars_vertex: [
+
+ "uniform vec3 ambient;",
+ "uniform vec3 diffuse;",
+ "uniform vec3 emissive;",
+
+ "uniform vec3 ambientLightColor;",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+ "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+ "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+ "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "uniform vec3 wrapRGB;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lights_lambert_vertex: [
+
+ "vLightFront = vec3( 0.0 );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack = vec3( 0.0 );",
+
+ "#endif",
+
+ "transformedNormal = normalize( transformedNormal );",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+ "vec3 dirVector = normalize( lDirection.xyz );",
+
+ "float dotProduct = dot( transformedNormal, dirVector );",
+ "vec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+ "#endif",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+ "directionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "directionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );",
+
+ "#endif",
+
+ "#endif",
+
+ "vLightFront += directionalLightColor[ i ] * directionalLightWeighting;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( pointLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+ "lVector = normalize( lVector );",
+ "float dotProduct = dot( transformedNormal, lVector );",
+
+ "vec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+ "#endif",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+ "pointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "pointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );",
+
+ "#endif",
+
+ "#endif",
+
+ "vLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+ "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );",
+
+ "if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+ "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+ "float lDistance = 1.0;",
+ "if ( spotLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+ "lVector = normalize( lVector );",
+
+ "float dotProduct = dot( transformedNormal, lVector );",
+ "vec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );",
+
+ "#endif",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "vec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );",
+ "spotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "spotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );",
+
+ "#endif",
+
+ "#endif",
+
+ "vLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;",
+
+ "#endif",
+
+ "}",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+ "vec3 lVector = normalize( lDirection.xyz );",
+
+ "float dotProduct = dot( transformedNormal, lVector );",
+
+ "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+ "float hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;",
+
+ "vLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "vLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "vLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // LIGHTS PHONG
+
+ lights_phong_pars_vertex: [
+
+ "#ifndef PHONG_PER_PIXEL",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+ "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+ "varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+ "varying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];",
+
+ "#endif",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+ "varying vec3 vWorldPosition;",
+
+ "#endif"
+
+ ].join("\n"),
+
+
+ lights_phong_vertex: [
+
+ "#ifndef PHONG_PER_PIXEL",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( pointLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+ "vPointLight[ i ] = vec4( lVector, lDistance );",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "for( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz - mvPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( spotLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+ "vSpotLight[ i ] = vec4( lVector, lDistance );",
+
+ "}",
+
+ "#endif",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+ "vWorldPosition = worldPosition.xyz;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ lights_phong_pars_fragment: [
+
+ "uniform vec3 ambientLightColor;",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+ "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+
+ "#ifdef PHONG_PER_PIXEL",
+
+ "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+ "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+ "#else",
+
+ "varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
+
+ "#endif",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+
+ "#ifdef PHONG_PER_PIXEL",
+
+ "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+ "#else",
+
+ "varying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];",
+
+ "#endif",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )",
+
+ "varying vec3 vWorldPosition;",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "uniform vec3 wrapRGB;",
+
+ "#endif",
+
+ "varying vec3 vViewPosition;",
+ "varying vec3 vNormal;"
+
+ ].join("\n"),
+
+ lights_phong_fragment: [
+
+ "vec3 normal = normalize( vNormal );",
+ "vec3 viewPosition = normalize( vViewPosition );",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );",
+
+ "#endif",
+
+ "#ifdef USE_NORMALMAP",
+
+ "normal = perturbNormal2Arb( -vViewPosition, normal );",
+
+ "#elif defined( USE_BUMPMAP )",
+
+ "normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "vec3 pointDiffuse = vec3( 0.0 );",
+ "vec3 pointSpecular = vec3( 0.0 );",
+
+ "for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+ "#ifdef PHONG_PER_PIXEL",
+
+ "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz + vViewPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( pointLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+ "lVector = normalize( lVector );",
+
+ "#else",
+
+ "vec3 lVector = normalize( vPointLight[ i ].xyz );",
+ "float lDistance = vPointLight[ i ].w;",
+
+ "#endif",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, lVector );",
+
+ "#ifdef WRAP_AROUND",
+
+ "float pointDiffuseWeightFull = max( dotProduct, 0.0 );",
+ "float pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+ "vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float pointDiffuseWeight = max( dotProduct, 0.0 );",
+
+ "#endif",
+
+ "pointDiffuse += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;",
+
+ // specular
+
+ "vec3 pointHalfVector = normalize( lVector + viewPosition );",
+ "float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+ "float pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, pointHalfVector ), 5.0 );",
+ "pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;",
+
+ "#else",
+
+ "pointSpecular += specular * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "vec3 spotDiffuse = vec3( 0.0 );",
+ "vec3 spotSpecular = vec3( 0.0 );",
+
+ "for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+ "#ifdef PHONG_PER_PIXEL",
+
+ "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+ "vec3 lVector = lPosition.xyz + vViewPosition.xyz;",
+
+ "float lDistance = 1.0;",
+ "if ( spotLightDistance[ i ] > 0.0 )",
+ "lDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+ "lVector = normalize( lVector );",
+
+ "#else",
+
+ "vec3 lVector = normalize( vSpotLight[ i ].xyz );",
+ "float lDistance = vSpotLight[ i ].w;",
+
+ "#endif",
+
+ "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );",
+
+ "if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+ "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, lVector );",
+
+ "#ifdef WRAP_AROUND",
+
+ "float spotDiffuseWeightFull = max( dotProduct, 0.0 );",
+ "float spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+ "vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float spotDiffuseWeight = max( dotProduct, 0.0 );",
+
+ "#endif",
+
+ "spotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;",
+
+ // specular
+
+ "vec3 spotHalfVector = normalize( lVector + viewPosition );",
+ "float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );",
+ "float spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, spotHalfVector ), 5.0 );",
+ "spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;",
+
+ "#else",
+
+ "spotSpecular += specular * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * spotEffect;",
+
+ "#endif",
+
+ "}",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "vec3 dirDiffuse = vec3( 0.0 );",
+ "vec3 dirSpecular = vec3( 0.0 );" ,
+
+ "for( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+ "vec3 dirVector = normalize( lDirection.xyz );",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, dirVector );",
+
+ "#ifdef WRAP_AROUND",
+
+ "float dirDiffuseWeightFull = max( dotProduct, 0.0 );",
+ "float dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );",
+
+ "vec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float dirDiffuseWeight = max( dotProduct, 0.0 );",
+
+ "#endif",
+
+ "dirDiffuse += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;",
+
+ // specular
+
+ "vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+ "float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+ "float dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ /*
+ // fresnel term from skin shader
+ "const float F0 = 0.128;",
+
+ "float base = 1.0 - dot( viewPosition, dirHalfVector );",
+ "float exponential = pow( base, 5.0 );",
+
+ "float fresnel = exponential + F0 * ( 1.0 - exponential );",
+ */
+
+ /*
+ // fresnel term from fresnel shader
+ "const float mFresnelBias = 0.08;",
+ "const float mFresnelScale = 0.3;",
+ "const float mFresnelPower = 5.0;",
+
+ "float fresnel = mFresnelBias + mFresnelScale * pow( 1.0 + dot( normalize( -viewPosition ), normal ), mFresnelPower );",
+ */
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+ //"dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization * fresnel;",
+
+ "vec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );",
+ "dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;",
+
+ "#else",
+
+ "dirSpecular += specular * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "vec3 hemiDiffuse = vec3( 0.0 );",
+ "vec3 hemiSpecular = vec3( 0.0 );" ,
+
+ "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+ "vec3 lVector = normalize( lDirection.xyz );",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, lVector );",
+ "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+
+ "vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+ "hemiDiffuse += diffuse * hemiColor;",
+
+ // specular (sky light)
+
+ "vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );",
+ "float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;",
+ "float hemiSpecularWeightSky = specularStrength * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );",
+
+ // specular (ground light)
+
+ "vec3 lVectorGround = -lVector;",
+
+ "vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );",
+ "float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
+ "float hemiSpecularWeightGround = specularStrength * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ "float dotProductGround = dot( normal, lVectorGround );",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( shininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );",
+ "vec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );",
+ "hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );",
+
+ "#else",
+
+ "hemiSpecular += specular * hemiColor * ( hemiSpecularWeightSky + hemiSpecularWeightGround ) * hemiDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ "vec3 totalDiffuse = vec3( 0.0 );",
+ "vec3 totalSpecular = vec3( 0.0 );",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "totalDiffuse += dirDiffuse;",
+ "totalSpecular += dirSpecular;",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "totalDiffuse += hemiDiffuse;",
+ "totalSpecular += hemiSpecular;",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "totalDiffuse += pointDiffuse;",
+ "totalSpecular += pointSpecular;",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "totalDiffuse += spotDiffuse;",
+ "totalSpecular += spotSpecular;",
+
+ "#endif",
+
+ "#ifdef METAL",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );",
+
+ "#else",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // VERTEX COLORS
+
+ color_pars_fragment: [
+
+ "#ifdef USE_COLOR",
+
+ "varying vec3 vColor;",
+
+ "#endif"
+
+ ].join("\n"),
+
+
+ color_fragment: [
+
+ "#ifdef USE_COLOR",
+
+ "gl_FragColor = gl_FragColor * vec4( vColor, opacity );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ color_pars_vertex: [
+
+ "#ifdef USE_COLOR",
+
+ "varying vec3 vColor;",
+
+ "#endif"
+
+ ].join("\n"),
+
+
+ color_vertex: [
+
+ "#ifdef USE_COLOR",
+
+ "#ifdef GAMMA_INPUT",
+
+ "vColor = color * color;",
+
+ "#else",
+
+ "vColor = color;",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // SKINNING
+
+ skinning_pars_vertex: [
+
+ "#ifdef USE_SKINNING",
+
+ "#ifdef BONE_TEXTURE",
+
+ "uniform sampler2D boneTexture;",
+ "uniform int boneTextureWidth;",
+ "uniform int boneTextureHeight;",
+
+ "mat4 getBoneMatrix( const in float i ) {",
+
+ "float j = i * 4.0;",
+ "float x = mod( j, float( boneTextureWidth ) );",
+ "float y = floor( j / float( boneTextureWidth ) );",
+
+ "float dx = 1.0 / float( boneTextureWidth );",
+ "float dy = 1.0 / float( boneTextureHeight );",
+
+ "y = dy * ( y + 0.5 );",
+
+ "vec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );",
+ "vec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );",
+ "vec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );",
+ "vec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );",
+
+ "mat4 bone = mat4( v1, v2, v3, v4 );",
+
+ "return bone;",
+
+ "}",
+
+ "#else",
+
+ "uniform mat4 boneGlobalMatrices[ MAX_BONES ];",
+
+ "mat4 getBoneMatrix( const in float i ) {",
+
+ "mat4 bone = boneGlobalMatrices[ int(i) ];",
+ "return bone;",
+
+ "}",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ skinbase_vertex: [
+
+ "#ifdef USE_SKINNING",
+
+ "mat4 boneMatX = getBoneMatrix( skinIndex.x );",
+ "mat4 boneMatY = getBoneMatrix( skinIndex.y );",
+
+ "#endif"
+
+ ].join("\n"),
+
+ skinning_vertex: [
+
+ "#ifdef USE_SKINNING",
+
+ "#ifdef USE_MORPHTARGETS",
+
+ "vec4 skinVertex = vec4( morphed, 1.0 );",
+
+ "#else",
+
+ "vec4 skinVertex = vec4( position, 1.0 );",
+
+ "#endif",
+
+ "vec4 skinned = boneMatX * skinVertex * skinWeight.x;",
+ "skinned += boneMatY * skinVertex * skinWeight.y;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // MORPHING
+
+ morphtarget_pars_vertex: [
+
+ "#ifdef USE_MORPHTARGETS",
+
+ "#ifndef USE_MORPHNORMALS",
+
+ "uniform float morphTargetInfluences[ 8 ];",
+
+ "#else",
+
+ "uniform float morphTargetInfluences[ 4 ];",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ morphtarget_vertex: [
+
+ "#ifdef USE_MORPHTARGETS",
+
+ "vec3 morphed = vec3( 0.0 );",
+ "morphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];",
+ "morphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];",
+ "morphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];",
+ "morphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];",
+
+ "#ifndef USE_MORPHNORMALS",
+
+ "morphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];",
+ "morphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];",
+ "morphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];",
+ "morphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];",
+
+ "#endif",
+
+ "morphed += position;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ default_vertex : [
+
+ "vec4 mvPosition;",
+
+ "#ifdef USE_SKINNING",
+
+ "mvPosition = modelViewMatrix * skinned;",
+
+ "#endif",
+
+ "#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )",
+
+ "mvPosition = modelViewMatrix * vec4( morphed, 1.0 );",
+
+ "#endif",
+
+ "#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )",
+
+ "mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+ "#endif",
+
+ "gl_Position = projectionMatrix * mvPosition;"
+
+ ].join("\n"),
+
+ morphnormal_vertex: [
+
+ "#ifdef USE_MORPHNORMALS",
+
+ "vec3 morphedNormal = vec3( 0.0 );",
+
+ "morphedNormal += ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];",
+ "morphedNormal += ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];",
+ "morphedNormal += ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];",
+ "morphedNormal += ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];",
+
+ "morphedNormal += normal;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ skinnormal_vertex: [
+
+ "#ifdef USE_SKINNING",
+
+ "mat4 skinMatrix = skinWeight.x * boneMatX;",
+ "skinMatrix += skinWeight.y * boneMatY;",
+
+ "#ifdef USE_MORPHNORMALS",
+
+ "vec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );",
+
+ "#else",
+
+ "vec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );",
+
+ "#endif",
+
+ "#endif"
+
+ ].join("\n"),
+
+ defaultnormal_vertex: [
+
+ "vec3 objectNormal;",
+
+ "#ifdef USE_SKINNING",
+
+ "objectNormal = skinnedNormal.xyz;",
+
+ "#endif",
+
+ "#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )",
+
+ "objectNormal = morphedNormal;",
+
+ "#endif",
+
+ "#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )",
+
+ "objectNormal = normal;",
+
+ "#endif",
+
+ "#ifdef FLIP_SIDED",
+
+ "objectNormal = -objectNormal;",
+
+ "#endif",
+
+ "vec3 transformedNormal = normalMatrix * objectNormal;"
+
+ ].join("\n"),
+
+ // SHADOW MAP
+
+ // based on SpiderGL shadow map and Fabien Sanglard's GLSL shadow mapping examples
+ // http://spidergl.org/example.php?id=6
+ // http://fabiensanglard.net/shadowmapping
+
+ shadowmap_pars_fragment: [
+
+ "#ifdef USE_SHADOWMAP",
+
+ "uniform sampler2D shadowMap[ MAX_SHADOWS ];",
+ "uniform vec2 shadowMapSize[ MAX_SHADOWS ];",
+
+ "uniform float shadowDarkness[ MAX_SHADOWS ];",
+ "uniform float shadowBias[ MAX_SHADOWS ];",
+
+ "varying vec4 vShadowCoord[ MAX_SHADOWS ];",
+
+ "float unpackDepth( const in vec4 rgba_depth ) {",
+
+ "const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );",
+ "float depth = dot( rgba_depth, bit_shift );",
+ "return depth;",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ shadowmap_fragment: [
+
+ "#ifdef USE_SHADOWMAP",
+
+ "#ifdef SHADOWMAP_DEBUG",
+
+ "vec3 frustumColors[3];",
+ "frustumColors[0] = vec3( 1.0, 0.5, 0.0 );",
+ "frustumColors[1] = vec3( 0.0, 1.0, 0.8 );",
+ "frustumColors[2] = vec3( 0.0, 0.5, 1.0 );",
+
+ "#endif",
+
+ "#ifdef SHADOWMAP_CASCADE",
+
+ "int inFrustumCount = 0;",
+
+ "#endif",
+
+ "float fDepth;",
+ "vec3 shadowColor = vec3( 1.0 );",
+
+ "for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+ "vec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;",
+
+ // "if ( something && something )" breaks ATI OpenGL shader compiler
+ // "if ( all( something, something ) )" using this instead
+
+ "bvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );",
+ "bool inFrustum = all( inFrustumVec );",
+
+ // don't shadow pixels outside of light frustum
+ // use just first frustum (for cascades)
+ // don't shadow pixels behind far plane of light frustum
+
+ "#ifdef SHADOWMAP_CASCADE",
+
+ "inFrustumCount += int( inFrustum );",
+ "bvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );",
+
+ "#else",
+
+ "bvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );",
+
+ "#endif",
+
+ "bool frustumTest = all( frustumTestVec );",
+
+ "if ( frustumTest ) {",
+
+ "shadowCoord.z += shadowBias[ i ];",
+
+ "#if defined( SHADOWMAP_TYPE_PCF )",
+
+ // Percentage-close filtering
+ // (9 pixel kernel)
+ // http://fabiensanglard.net/shadowmappingPCF/
+
+ "float shadow = 0.0;",
+
+ /*
+ // nested loops breaks shader compiler / validator on some ATI cards when using OpenGL
+ // must enroll loop manually
+
+ "for ( float y = -1.25; y <= 1.25; y += 1.25 )",
+ "for ( float x = -1.25; x <= 1.25; x += 1.25 ) {",
+
+ "vec4 rgbaDepth = texture2D( shadowMap[ i ], vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy );",
+
+ // doesn't seem to produce any noticeable visual difference compared to simple "texture2D" lookup
+ //"vec4 rgbaDepth = texture2DProj( shadowMap[ i ], vec4( vShadowCoord[ i ].w * ( vec2( x * xPixelOffset, y * yPixelOffset ) + shadowCoord.xy ), 0.05, vShadowCoord[ i ].w ) );",
+
+ "float fDepth = unpackDepth( rgbaDepth );",
+
+ "if ( fDepth < shadowCoord.z )",
+ "shadow += 1.0;",
+
+ "}",
+
+ "shadow /= 9.0;",
+
+ */
+
+ "const float shadowDelta = 1.0 / 9.0;",
+
+ "float xPixelOffset = 1.0 / shadowMapSize[ i ].x;",
+ "float yPixelOffset = 1.0 / shadowMapSize[ i ].y;",
+
+ "float dx0 = -1.25 * xPixelOffset;",
+ "float dy0 = -1.25 * yPixelOffset;",
+ "float dx1 = 1.25 * xPixelOffset;",
+ "float dy1 = 1.25 * yPixelOffset;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "fDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );",
+ "if ( fDepth < shadowCoord.z ) shadow += shadowDelta;",
+
+ "shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );",
+
+ "#elif defined( SHADOWMAP_TYPE_PCF_SOFT )",
+
+ // Percentage-close filtering
+ // (9 pixel kernel)
+ // http://fabiensanglard.net/shadowmappingPCF/
+
+ "float shadow = 0.0;",
+
+ "float xPixelOffset = 1.0 / shadowMapSize[ i ].x;",
+ "float yPixelOffset = 1.0 / shadowMapSize[ i ].y;",
+
+ "float dx0 = -1.0 * xPixelOffset;",
+ "float dy0 = -1.0 * yPixelOffset;",
+ "float dx1 = 1.0 * xPixelOffset;",
+ "float dy1 = 1.0 * yPixelOffset;",
+
+ "mat3 shadowKernel;",
+ "mat3 depthKernel;",
+
+ "depthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );",
+ "depthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );",
+ "depthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );",
+ "depthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );",
+ "depthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );",
+ "depthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );",
+ "depthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );",
+ "depthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );",
+ "depthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );",
+
+ "vec3 shadowZ = vec3( shadowCoord.z );",
+ "shadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));",
+ "shadowKernel[0] *= vec3(0.25);",
+
+ "shadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));",
+ "shadowKernel[1] *= vec3(0.25);",
+
+ "shadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));",
+ "shadowKernel[2] *= vec3(0.25);",
+
+ "vec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );",
+
+ "shadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );",
+ "shadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );",
+
+ "vec4 shadowValues;",
+ "shadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );",
+ "shadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );",
+ "shadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );",
+ "shadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );",
+
+ "shadow = dot( shadowValues, vec4( 1.0 ) );",
+
+ "shadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );",
+
+ "#else",
+
+ "vec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );",
+ "float fDepth = unpackDepth( rgbaDepth );",
+
+ "if ( fDepth < shadowCoord.z )",
+
+ // spot with multiple shadows is darker
+
+ "shadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );",
+
+ // spot with multiple shadows has the same color as single shadow spot
+
+ //"shadowColor = min( shadowColor, vec3( shadowDarkness[ i ] ) );",
+
+ "#endif",
+
+ "}",
+
+
+ "#ifdef SHADOWMAP_DEBUG",
+
+ "#ifdef SHADOWMAP_CASCADE",
+
+ "if ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];",
+
+ "#else",
+
+ "if ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];",
+
+ "#endif",
+
+ "#endif",
+
+ "}",
+
+ "#ifdef GAMMA_OUTPUT",
+
+ "shadowColor *= shadowColor;",
+
+ "#endif",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * shadowColor;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ shadowmap_pars_vertex: [
+
+ "#ifdef USE_SHADOWMAP",
+
+ "varying vec4 vShadowCoord[ MAX_SHADOWS ];",
+ "uniform mat4 shadowMatrix[ MAX_SHADOWS ];",
+
+ "#endif"
+
+ ].join("\n"),
+
+ shadowmap_vertex: [
+
+ "#ifdef USE_SHADOWMAP",
+
+ "for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+ "vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;",
+
+ "}",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // ALPHATEST
+
+ alphatest_fragment: [
+
+ "#ifdef ALPHATEST",
+
+ "if ( gl_FragColor.a < ALPHATEST ) discard;",
+
+ "#endif"
+
+ ].join("\n"),
+
+ // LINEAR SPACE
+
+ linear_to_gamma_fragment: [
+
+ "#ifdef GAMMA_OUTPUT",
+
+ "gl_FragColor.xyz = sqrt( gl_FragColor.xyz );",
+
+ "#endif"
+
+ ].join("\n")
+
+
+};
+
+THREE.UniformsUtils = {
+
+ merge: function ( uniforms ) {
+
+ var u, p, tmp, merged = {};
+
+ for ( u = 0; u < uniforms.length; u ++ ) {
+
+ tmp = this.clone( uniforms[ u ] );
+
+ for ( p in tmp ) {
+
+ merged[ p ] = tmp[ p ];
+
+ }
+
+ }
+
+ return merged;
+
+ },
+
+ clone: function ( uniforms_src ) {
+
+ var u, p, parameter, parameter_src, uniforms_dst = {};
+
+ for ( u in uniforms_src ) {
+
+ uniforms_dst[ u ] = {};
+
+ for ( p in uniforms_src[ u ] ) {
+
+ parameter_src = uniforms_src[ u ][ p ];
+
+ if ( parameter_src instanceof THREE.Color ||
+ parameter_src instanceof THREE.Vector2 ||
+ parameter_src instanceof THREE.Vector3 ||
+ parameter_src instanceof THREE.Vector4 ||
+ parameter_src instanceof THREE.Matrix4 ||
+ parameter_src instanceof THREE.Texture ) {
+
+ uniforms_dst[ u ][ p ] = parameter_src.clone();
+
+ } else if ( parameter_src instanceof Array ) {
+
+ uniforms_dst[ u ][ p ] = parameter_src.slice();
+
+ } else {
+
+ uniforms_dst[ u ][ p ] = parameter_src;
+
+ }
+
+ }
+
+ }
+
+ return uniforms_dst;
+
+ }
+
+};
+
+THREE.UniformsLib = {
+
+ common: {
+
+ "diffuse" : { type: "c", value: new THREE.Color( 0xeeeeee ) },
+ "opacity" : { type: "f", value: 1.0 },
+
+ "map" : { type: "t", value: null },
+ "offsetRepeat" : { type: "v4", value: new THREE.Vector4( 0, 0, 1, 1 ) },
+
+ "lightMap" : { type: "t", value: null },
+ "specularMap" : { type: "t", value: null },
+
+ "envMap" : { type: "t", value: null },
+ "flipEnvMap" : { type: "f", value: -1 },
+ "useRefract" : { type: "i", value: 0 },
+ "reflectivity" : { type: "f", value: 1.0 },
+ "refractionRatio" : { type: "f", value: 0.98 },
+ "combine" : { type: "i", value: 0 },
+
+ "morphTargetInfluences" : { type: "f", value: 0 }
+
+ },
+
+ bump: {
+
+ "bumpMap" : { type: "t", value: null },
+ "bumpScale" : { type: "f", value: 1 }
+
+ },
+
+ normalmap: {
+
+ "normalMap" : { type: "t", value: null },
+ "normalScale" : { type: "v2", value: new THREE.Vector2( 1, 1 ) }
+ },
+
+ fog : {
+
+ "fogDensity" : { type: "f", value: 0.00025 },
+ "fogNear" : { type: "f", value: 1 },
+ "fogFar" : { type: "f", value: 2000 },
+ "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) }
+
+ },
+
+ lights: {
+
+ "ambientLightColor" : { type: "fv", value: [] },
+
+ "directionalLightDirection" : { type: "fv", value: [] },
+ "directionalLightColor" : { type: "fv", value: [] },
+
+ "hemisphereLightDirection" : { type: "fv", value: [] },
+ "hemisphereLightSkyColor" : { type: "fv", value: [] },
+ "hemisphereLightGroundColor" : { type: "fv", value: [] },
+
+ "pointLightColor" : { type: "fv", value: [] },
+ "pointLightPosition" : { type: "fv", value: [] },
+ "pointLightDistance" : { type: "fv1", value: [] },
+
+ "spotLightColor" : { type: "fv", value: [] },
+ "spotLightPosition" : { type: "fv", value: [] },
+ "spotLightDirection" : { type: "fv", value: [] },
+ "spotLightDistance" : { type: "fv1", value: [] },
+ "spotLightAngleCos" : { type: "fv1", value: [] },
+ "spotLightExponent" : { type: "fv1", value: [] }
+
+ },
+
+ particle: {
+
+ "psColor" : { type: "c", value: new THREE.Color( 0xeeeeee ) },
+ "opacity" : { type: "f", value: 1.0 },
+ "size" : { type: "f", value: 1.0 },
+ "scale" : { type: "f", value: 1.0 },
+ "map" : { type: "t", value: null },
+
+ "fogDensity" : { type: "f", value: 0.00025 },
+ "fogNear" : { type: "f", value: 1 },
+ "fogFar" : { type: "f", value: 2000 },
+ "fogColor" : { type: "c", value: new THREE.Color( 0xffffff ) }
+
+ },
+
+ shadowmap: {
+
+ "shadowMap": { type: "tv", value: [] },
+ "shadowMapSize": { type: "v2v", value: [] },
+
+ "shadowBias" : { type: "fv1", value: [] },
+ "shadowDarkness": { type: "fv1", value: [] },
+
+ "shadowMatrix" : { type: "m4v", value: [] }
+
+ }
+
+};
+
+THREE.ShaderLib = {
+
+ 'basic': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "common" ],
+ THREE.UniformsLib[ "fog" ],
+ THREE.UniformsLib[ "shadowmap" ]
+
+ ] ),
+
+ vertexShader: [
+
+ THREE.ShaderChunk[ "map_pars_vertex" ],
+ THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+ THREE.ShaderChunk[ "envmap_pars_vertex" ],
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "map_vertex" ],
+ THREE.ShaderChunk[ "lightmap_vertex" ],
+ THREE.ShaderChunk[ "color_vertex" ],
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+
+ "#ifdef USE_ENVMAP",
+
+ THREE.ShaderChunk[ "morphnormal_vertex" ],
+ THREE.ShaderChunk[ "skinnormal_vertex" ],
+ THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+ "#endif",
+
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "skinning_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ THREE.ShaderChunk[ "worldpos_vertex" ],
+ THREE.ShaderChunk[ "envmap_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform vec3 diffuse;",
+ "uniform float opacity;",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "map_pars_fragment" ],
+ THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+ THREE.ShaderChunk[ "envmap_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+ THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( diffuse, opacity );",
+
+ THREE.ShaderChunk[ "map_fragment" ],
+ THREE.ShaderChunk[ "alphatest_fragment" ],
+ THREE.ShaderChunk[ "specularmap_fragment" ],
+ THREE.ShaderChunk[ "lightmap_fragment" ],
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "envmap_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+ THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'lambert': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "common" ],
+ THREE.UniformsLib[ "fog" ],
+ THREE.UniformsLib[ "lights" ],
+ THREE.UniformsLib[ "shadowmap" ],
+
+ {
+ "ambient" : { type: "c", value: new THREE.Color( 0xffffff ) },
+ "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) },
+ "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+ }
+
+ ] ),
+
+ vertexShader: [
+
+ "#define LAMBERT",
+
+ "varying vec3 vLightFront;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "varying vec3 vLightBack;",
+
+ "#endif",
+
+ THREE.ShaderChunk[ "map_pars_vertex" ],
+ THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+ THREE.ShaderChunk[ "envmap_pars_vertex" ],
+ THREE.ShaderChunk[ "lights_lambert_pars_vertex" ],
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "map_vertex" ],
+ THREE.ShaderChunk[ "lightmap_vertex" ],
+ THREE.ShaderChunk[ "color_vertex" ],
+
+ THREE.ShaderChunk[ "morphnormal_vertex" ],
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+ THREE.ShaderChunk[ "skinnormal_vertex" ],
+ THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "skinning_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ THREE.ShaderChunk[ "worldpos_vertex" ],
+ THREE.ShaderChunk[ "envmap_vertex" ],
+ THREE.ShaderChunk[ "lights_lambert_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform float opacity;",
+
+ "varying vec3 vLightFront;",
+
+ "#ifdef DOUBLE_SIDED",
+
+ "varying vec3 vLightBack;",
+
+ "#endif",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "map_pars_fragment" ],
+ THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+ THREE.ShaderChunk[ "envmap_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+ THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( vec3 ( 1.0 ), opacity );",
+
+ THREE.ShaderChunk[ "map_fragment" ],
+ THREE.ShaderChunk[ "alphatest_fragment" ],
+ THREE.ShaderChunk[ "specularmap_fragment" ],
+
+ "#ifdef DOUBLE_SIDED",
+
+ //"float isFront = float( gl_FrontFacing );",
+ //"gl_FragColor.xyz *= isFront * vLightFront + ( 1.0 - isFront ) * vLightBack;",
+
+ "if ( gl_FrontFacing )",
+ "gl_FragColor.xyz *= vLightFront;",
+ "else",
+ "gl_FragColor.xyz *= vLightBack;",
+
+ "#else",
+
+ "gl_FragColor.xyz *= vLightFront;",
+
+ "#endif",
+
+ THREE.ShaderChunk[ "lightmap_fragment" ],
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "envmap_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+ THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'phong': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "common" ],
+ THREE.UniformsLib[ "bump" ],
+ THREE.UniformsLib[ "normalmap" ],
+ THREE.UniformsLib[ "fog" ],
+ THREE.UniformsLib[ "lights" ],
+ THREE.UniformsLib[ "shadowmap" ],
+
+ {
+ "ambient" : { type: "c", value: new THREE.Color( 0xffffff ) },
+ "emissive" : { type: "c", value: new THREE.Color( 0x000000 ) },
+ "specular" : { type: "c", value: new THREE.Color( 0x111111 ) },
+ "shininess": { type: "f", value: 30 },
+ "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+ }
+
+ ] ),
+
+ vertexShader: [
+
+ "#define PHONG",
+
+ "varying vec3 vViewPosition;",
+ "varying vec3 vNormal;",
+
+ THREE.ShaderChunk[ "map_pars_vertex" ],
+ THREE.ShaderChunk[ "lightmap_pars_vertex" ],
+ THREE.ShaderChunk[ "envmap_pars_vertex" ],
+ THREE.ShaderChunk[ "lights_phong_pars_vertex" ],
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "map_vertex" ],
+ THREE.ShaderChunk[ "lightmap_vertex" ],
+ THREE.ShaderChunk[ "color_vertex" ],
+
+ THREE.ShaderChunk[ "morphnormal_vertex" ],
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+ THREE.ShaderChunk[ "skinnormal_vertex" ],
+ THREE.ShaderChunk[ "defaultnormal_vertex" ],
+
+ "vNormal = normalize( transformedNormal );",
+
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "skinning_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ "vViewPosition = -mvPosition.xyz;",
+
+ THREE.ShaderChunk[ "worldpos_vertex" ],
+ THREE.ShaderChunk[ "envmap_vertex" ],
+ THREE.ShaderChunk[ "lights_phong_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform vec3 diffuse;",
+ "uniform float opacity;",
+
+ "uniform vec3 ambient;",
+ "uniform vec3 emissive;",
+ "uniform vec3 specular;",
+ "uniform float shininess;",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "map_pars_fragment" ],
+ THREE.ShaderChunk[ "lightmap_pars_fragment" ],
+ THREE.ShaderChunk[ "envmap_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+ THREE.ShaderChunk[ "lights_phong_pars_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+ THREE.ShaderChunk[ "bumpmap_pars_fragment" ],
+ THREE.ShaderChunk[ "normalmap_pars_fragment" ],
+ THREE.ShaderChunk[ "specularmap_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( vec3 ( 1.0 ), opacity );",
+
+ THREE.ShaderChunk[ "map_fragment" ],
+ THREE.ShaderChunk[ "alphatest_fragment" ],
+ THREE.ShaderChunk[ "specularmap_fragment" ],
+
+ THREE.ShaderChunk[ "lights_phong_fragment" ],
+
+ THREE.ShaderChunk[ "lightmap_fragment" ],
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "envmap_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+ THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'particle_basic': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "particle" ],
+ THREE.UniformsLib[ "shadowmap" ]
+
+ ] ),
+
+ vertexShader: [
+
+ "uniform float size;",
+ "uniform float scale;",
+
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "color_vertex" ],
+
+ "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+
+ "#ifdef USE_SIZEATTENUATION",
+ "gl_PointSize = size * ( scale / length( mvPosition.xyz ) );",
+ "#else",
+ "gl_PointSize = size;",
+ "#endif",
+
+ "gl_Position = projectionMatrix * mvPosition;",
+
+ THREE.ShaderChunk[ "worldpos_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform vec3 psColor;",
+ "uniform float opacity;",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "map_particle_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( psColor, opacity );",
+
+ THREE.ShaderChunk[ "map_particle_fragment" ],
+ THREE.ShaderChunk[ "alphatest_fragment" ],
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'dashed': {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "common" ],
+ THREE.UniformsLib[ "fog" ],
+
+ {
+ "scale": { type: "f", value: 1 },
+ "dashSize": { type: "f", value: 1 },
+ "totalSize": { type: "f", value: 2 }
+ }
+
+ ] ),
+
+ vertexShader: [
+
+ "uniform float scale;",
+ "attribute float lineDistance;",
+
+ "varying float vLineDistance;",
+
+ THREE.ShaderChunk[ "color_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "color_vertex" ],
+
+ "vLineDistance = scale * lineDistance;",
+
+ "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );",
+ "gl_Position = projectionMatrix * mvPosition;",
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform vec3 diffuse;",
+ "uniform float opacity;",
+
+ "uniform float dashSize;",
+ "uniform float totalSize;",
+
+ "varying float vLineDistance;",
+
+ THREE.ShaderChunk[ "color_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+ "void main() {",
+
+ "if ( mod( vLineDistance, totalSize ) > dashSize ) {",
+
+ "discard;",
+
+ "}",
+
+ "gl_FragColor = vec4( diffuse, opacity );",
+
+ THREE.ShaderChunk[ "color_fragment" ],
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'depth': {
+
+ uniforms: {
+
+ "mNear": { type: "f", value: 1.0 },
+ "mFar" : { type: "f", value: 2000.0 },
+ "opacity" : { type: "f", value: 1.0 }
+
+ },
+
+ vertexShader: [
+
+ "void main() {",
+
+ "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform float mNear;",
+ "uniform float mFar;",
+ "uniform float opacity;",
+
+ "void main() {",
+
+ "float depth = gl_FragCoord.z / gl_FragCoord.w;",
+ "float color = 1.0 - smoothstep( mNear, mFar, depth );",
+ "gl_FragColor = vec4( vec3( color ), opacity );",
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ 'normal': {
+
+ uniforms: {
+
+ "opacity" : { type: "f", value: 1.0 }
+
+ },
+
+ vertexShader: [
+
+ "varying vec3 vNormal;",
+
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+
+ "void main() {",
+
+ "vNormal = normalize( normalMatrix * normal );",
+
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform float opacity;",
+ "varying vec3 vNormal;",
+
+ "void main() {",
+
+ "gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );",
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ /* -------------------------------------------------------------------------
+ // Normal map shader
+ // - Blinn-Phong
+ // - normal + diffuse + specular + AO + displacement + reflection + shadow maps
+ // - point and directional lights (use with "lights: true" material option)
+ ------------------------------------------------------------------------- */
+
+ 'normalmap' : {
+
+ uniforms: THREE.UniformsUtils.merge( [
+
+ THREE.UniformsLib[ "fog" ],
+ THREE.UniformsLib[ "lights" ],
+ THREE.UniformsLib[ "shadowmap" ],
+
+ {
+
+ "enableAO" : { type: "i", value: 0 },
+ "enableDiffuse" : { type: "i", value: 0 },
+ "enableSpecular" : { type: "i", value: 0 },
+ "enableReflection": { type: "i", value: 0 },
+ "enableDisplacement": { type: "i", value: 0 },
+
+ "tDisplacement": { type: "t", value: null }, // must go first as this is vertex texture
+ "tDiffuse" : { type: "t", value: null },
+ "tCube" : { type: "t", value: null },
+ "tNormal" : { type: "t", value: null },
+ "tSpecular" : { type: "t", value: null },
+ "tAO" : { type: "t", value: null },
+
+ "uNormalScale": { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+
+ "uDisplacementBias": { type: "f", value: 0.0 },
+ "uDisplacementScale": { type: "f", value: 1.0 },
+
+ "uDiffuseColor": { type: "c", value: new THREE.Color( 0xffffff ) },
+ "uSpecularColor": { type: "c", value: new THREE.Color( 0x111111 ) },
+ "uAmbientColor": { type: "c", value: new THREE.Color( 0xffffff ) },
+ "uShininess": { type: "f", value: 30 },
+ "uOpacity": { type: "f", value: 1 },
+
+ "useRefract": { type: "i", value: 0 },
+ "uRefractionRatio": { type: "f", value: 0.98 },
+ "uReflectivity": { type: "f", value: 0.5 },
+
+ "uOffset" : { type: "v2", value: new THREE.Vector2( 0, 0 ) },
+ "uRepeat" : { type: "v2", value: new THREE.Vector2( 1, 1 ) },
+
+ "wrapRGB" : { type: "v3", value: new THREE.Vector3( 1, 1, 1 ) }
+
+ }
+
+ ] ),
+
+ fragmentShader: [
+
+ "uniform vec3 uAmbientColor;",
+ "uniform vec3 uDiffuseColor;",
+ "uniform vec3 uSpecularColor;",
+ "uniform float uShininess;",
+ "uniform float uOpacity;",
+
+ "uniform bool enableDiffuse;",
+ "uniform bool enableSpecular;",
+ "uniform bool enableAO;",
+ "uniform bool enableReflection;",
+
+ "uniform sampler2D tDiffuse;",
+ "uniform sampler2D tNormal;",
+ "uniform sampler2D tSpecular;",
+ "uniform sampler2D tAO;",
+
+ "uniform samplerCube tCube;",
+
+ "uniform vec2 uNormalScale;",
+
+ "uniform bool useRefract;",
+ "uniform float uRefractionRatio;",
+ "uniform float uReflectivity;",
+
+ "varying vec3 vTangent;",
+ "varying vec3 vBinormal;",
+ "varying vec3 vNormal;",
+ "varying vec2 vUv;",
+
+ "uniform vec3 ambientLightColor;",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
+ "uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
+ "uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
+ "uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
+ "uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "uniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];",
+ "uniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightExponent[ MAX_SPOT_LIGHTS ];",
+ "uniform float spotLightDistance[ MAX_SPOT_LIGHTS ];",
+
+ "#endif",
+
+ "#ifdef WRAP_AROUND",
+
+ "uniform vec3 wrapRGB;",
+
+ "#endif",
+
+ "varying vec3 vWorldPosition;",
+ "varying vec3 vViewPosition;",
+
+ THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
+ THREE.ShaderChunk[ "fog_pars_fragment" ],
+
+ "void main() {",
+
+ "gl_FragColor = vec4( vec3( 1.0 ), uOpacity );",
+
+ "vec3 specularTex = vec3( 1.0 );",
+
+ "vec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;",
+ "normalTex.xy *= uNormalScale;",
+ "normalTex = normalize( normalTex );",
+
+ "if( enableDiffuse ) {",
+
+ "#ifdef GAMMA_INPUT",
+
+ "vec4 texelColor = texture2D( tDiffuse, vUv );",
+ "texelColor.xyz *= texelColor.xyz;",
+
+ "gl_FragColor = gl_FragColor * texelColor;",
+
+ "#else",
+
+ "gl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );",
+
+ "#endif",
+
+ "}",
+
+ "if( enableAO ) {",
+
+ "#ifdef GAMMA_INPUT",
+
+ "vec4 aoColor = texture2D( tAO, vUv );",
+ "aoColor.xyz *= aoColor.xyz;",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;",
+
+ "#else",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;",
+
+ "#endif",
+
+ "}",
+
+ "if( enableSpecular )",
+ "specularTex = texture2D( tSpecular, vUv ).xyz;",
+
+ "mat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );",
+ "vec3 finalNormal = tsb * normalTex;",
+
+ "#ifdef FLIP_SIDED",
+
+ "finalNormal = -finalNormal;",
+
+ "#endif",
+
+ "vec3 normal = normalize( finalNormal );",
+ "vec3 viewPosition = normalize( vViewPosition );",
+
+ // point lights
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "vec3 pointDiffuse = vec3( 0.0 );",
+ "vec3 pointSpecular = vec3( 0.0 );",
+
+ "for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );",
+ "vec3 pointVector = lPosition.xyz + vViewPosition.xyz;",
+
+ "float pointDistance = 1.0;",
+ "if ( pointLightDistance[ i ] > 0.0 )",
+ "pointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );",
+
+ "pointVector = normalize( pointVector );",
+
+ // diffuse
+
+ "#ifdef WRAP_AROUND",
+
+ "float pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );",
+ "float pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );",
+
+ "vec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );",
+
+ "#endif",
+
+ "pointDiffuse += pointDistance * pointLightColor[ i ] * uDiffuseColor * pointDiffuseWeight;",
+
+ // specular
+
+ "vec3 pointHalfVector = normalize( pointVector + viewPosition );",
+ "float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+ "float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, uShininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( pointVector, pointHalfVector ), 5.0 );",
+ "pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;",
+
+ "#else",
+
+ "pointSpecular += pointDistance * pointLightColor[ i ] * uSpecularColor * pointSpecularWeight * pointDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ // spot lights
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "vec3 spotDiffuse = vec3( 0.0 );",
+ "vec3 spotSpecular = vec3( 0.0 );",
+
+ "for ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {",
+
+ "vec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );",
+ "vec3 spotVector = lPosition.xyz + vViewPosition.xyz;",
+
+ "float spotDistance = 1.0;",
+ "if ( spotLightDistance[ i ] > 0.0 )",
+ "spotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );",
+
+ "spotVector = normalize( spotVector );",
+
+ "float spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );",
+
+ "if ( spotEffect > spotLightAngleCos[ i ] ) {",
+
+ "spotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );",
+
+ // diffuse
+
+ "#ifdef WRAP_AROUND",
+
+ "float spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );",
+ "float spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );",
+
+ "vec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );",
+
+ "#else",
+
+ "float spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );",
+
+ "#endif",
+
+ "spotDiffuse += spotDistance * spotLightColor[ i ] * uDiffuseColor * spotDiffuseWeight * spotEffect;",
+
+ // specular
+
+ "vec3 spotHalfVector = normalize( spotVector + viewPosition );",
+ "float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );",
+ "float spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, uShininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( spotVector, spotHalfVector ), 5.0 );",
+ "spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;",
+
+ "#else",
+
+ "spotSpecular += spotDistance * spotLightColor[ i ] * uSpecularColor * spotSpecularWeight * spotDiffuseWeight * spotEffect;",
+
+ "#endif",
+
+ "}",
+
+ "}",
+
+ "#endif",
+
+ // directional lights
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "vec3 dirDiffuse = vec3( 0.0 );",
+ "vec3 dirSpecular = vec3( 0.0 );",
+
+ "for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );",
+ "vec3 dirVector = normalize( lDirection.xyz );",
+
+ // diffuse
+
+ "#ifdef WRAP_AROUND",
+
+ "float directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );",
+ "float directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );",
+
+ "vec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );",
+
+ "#else",
+
+ "float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );",
+
+ "#endif",
+
+ "dirDiffuse += directionalLightColor[ i ] * uDiffuseColor * dirDiffuseWeight;",
+
+ // specular
+
+ "vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+ "float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+ "float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, uShininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlick = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );",
+ "dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;",
+
+ "#else",
+
+ "dirSpecular += directionalLightColor[ i ] * uSpecularColor * dirSpecularWeight * dirDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ // hemisphere lights
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "vec3 hemiDiffuse = vec3( 0.0 );",
+ "vec3 hemiSpecular = vec3( 0.0 );" ,
+
+ "for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {",
+
+ "vec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );",
+ "vec3 lVector = normalize( lDirection.xyz );",
+
+ // diffuse
+
+ "float dotProduct = dot( normal, lVector );",
+ "float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
+
+ "vec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+
+ "hemiDiffuse += uDiffuseColor * hemiColor;",
+
+ // specular (sky light)
+
+
+ "vec3 hemiHalfVectorSky = normalize( lVector + viewPosition );",
+ "float hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;",
+ "float hemiSpecularWeightSky = specularTex.r * max( pow( hemiDotNormalHalfSky, uShininess ), 0.0 );",
+
+ // specular (ground light)
+
+ "vec3 lVectorGround = -lVector;",
+
+ "vec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );",
+ "float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
+ "float hemiSpecularWeightGround = specularTex.r * max( pow( hemiDotNormalHalfGround, uShininess ), 0.0 );",
+
+ "#ifdef PHYSICALLY_BASED_SHADING",
+
+ "float dotProductGround = dot( normal, lVectorGround );",
+
+ // 2.0 => 2.0001 is hack to work around ANGLE bug
+
+ "float specularNormalization = ( uShininess + 2.0001 ) / 8.0;",
+
+ "vec3 schlickSky = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );",
+ "vec3 schlickGround = uSpecularColor + vec3( 1.0 - uSpecularColor ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );",
+ "hemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );",
+
+ "#else",
+
+ "hemiSpecular += uSpecularColor * hemiColor * ( hemiSpecularWeightSky + hemiSpecularWeightGround ) * hemiDiffuseWeight;",
+
+ "#endif",
+
+ "}",
+
+ "#endif",
+
+ // all lights contribution summation
+
+ "vec3 totalDiffuse = vec3( 0.0 );",
+ "vec3 totalSpecular = vec3( 0.0 );",
+
+ "#if MAX_DIR_LIGHTS > 0",
+
+ "totalDiffuse += dirDiffuse;",
+ "totalSpecular += dirSpecular;",
+
+ "#endif",
+
+ "#if MAX_HEMI_LIGHTS > 0",
+
+ "totalDiffuse += hemiDiffuse;",
+ "totalSpecular += hemiSpecular;",
+
+ "#endif",
+
+ "#if MAX_POINT_LIGHTS > 0",
+
+ "totalDiffuse += pointDiffuse;",
+ "totalSpecular += pointSpecular;",
+
+ "#endif",
+
+ "#if MAX_SPOT_LIGHTS > 0",
+
+ "totalDiffuse += spotDiffuse;",
+ "totalSpecular += spotSpecular;",
+
+ "#endif",
+
+ "#ifdef METAL",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor + totalSpecular );",
+
+ "#else",
+
+ "gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * uAmbientColor ) + totalSpecular;",
+
+ "#endif",
+
+ "if ( enableReflection ) {",
+
+ "vec3 vReflect;",
+ "vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+ "if ( useRefract ) {",
+
+ "vReflect = refract( cameraToVertex, normal, uRefractionRatio );",
+
+ "} else {",
+
+ "vReflect = reflect( cameraToVertex, normal );",
+
+ "}",
+
+ "vec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );",
+
+ "#ifdef GAMMA_INPUT",
+
+ "cubeColor.xyz *= cubeColor.xyz;",
+
+ "#endif",
+
+ "gl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * uReflectivity );",
+
+ "}",
+
+ THREE.ShaderChunk[ "shadowmap_fragment" ],
+ THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
+ THREE.ShaderChunk[ "fog_fragment" ],
+
+ "}"
+
+ ].join("\n"),
+
+ vertexShader: [
+
+ "attribute vec4 tangent;",
+
+ "uniform vec2 uOffset;",
+ "uniform vec2 uRepeat;",
+
+ "uniform bool enableDisplacement;",
+
+ "#ifdef VERTEX_TEXTURES",
+
+ "uniform sampler2D tDisplacement;",
+ "uniform float uDisplacementScale;",
+ "uniform float uDisplacementBias;",
+
+ "#endif",
+
+ "varying vec3 vTangent;",
+ "varying vec3 vBinormal;",
+ "varying vec3 vNormal;",
+ "varying vec2 vUv;",
+
+ "varying vec3 vWorldPosition;",
+ "varying vec3 vViewPosition;",
+
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+ THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+ THREE.ShaderChunk[ "skinnormal_vertex" ],
+
+ // normal, tangent and binormal vectors
+
+ "#ifdef USE_SKINNING",
+
+ "vNormal = normalize( normalMatrix * skinnedNormal.xyz );",
+
+ "vec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );",
+ "vTangent = normalize( normalMatrix * skinnedTangent.xyz );",
+
+ "#else",
+
+ "vNormal = normalize( normalMatrix * normal );",
+ "vTangent = normalize( normalMatrix * tangent.xyz );",
+
+ "#endif",
+
+ "vBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );",
+
+ "vUv = uv * uRepeat + uOffset;",
+
+ // displacement mapping
+
+ "vec3 displacedPosition;",
+
+ "#ifdef VERTEX_TEXTURES",
+
+ "if ( enableDisplacement ) {",
+
+ "vec3 dv = texture2D( tDisplacement, uv ).xyz;",
+ "float df = uDisplacementScale * dv.x + uDisplacementBias;",
+ "displacedPosition = position + normalize( normal ) * df;",
+
+ "} else {",
+
+ "#ifdef USE_SKINNING",
+
+ "vec4 skinVertex = vec4( position, 1.0 );",
+
+ "vec4 skinned = boneMatX * skinVertex * skinWeight.x;",
+ "skinned += boneMatY * skinVertex * skinWeight.y;",
+
+ "displacedPosition = skinned.xyz;",
+
+ "#else",
+
+ "displacedPosition = position;",
+
+ "#endif",
+
+ "}",
+
+ "#else",
+
+ "#ifdef USE_SKINNING",
+
+ "vec4 skinVertex = vec4( position, 1.0 );",
+
+ "vec4 skinned = boneMatX * skinVertex * skinWeight.x;",
+ "skinned += boneMatY * skinVertex * skinWeight.y;",
+
+ "displacedPosition = skinned.xyz;",
+
+ "#else",
+
+ "displacedPosition = position;",
+
+ "#endif",
+
+ "#endif",
+
+ //
+
+ "vec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );",
+ "vec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );",
+
+ "gl_Position = projectionMatrix * mvPosition;",
+
+ //
+
+ "vWorldPosition = worldPosition.xyz;",
+ "vViewPosition = -mvPosition.xyz;",
+
+ // shadows
+
+ "#ifdef USE_SHADOWMAP",
+
+ "for( int i = 0; i < MAX_SHADOWS; i ++ ) {",
+
+ "vShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;",
+
+ "}",
+
+ "#endif",
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ /* -------------------------------------------------------------------------
+ // Cube map shader
+ ------------------------------------------------------------------------- */
+
+ 'cube': {
+
+ uniforms: { "tCube": { type: "t", value: null },
+ "tFlip": { type: "f", value: -1 } },
+
+ vertexShader: [
+
+ "varying vec3 vWorldPosition;",
+
+ "void main() {",
+
+ "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );",
+ "vWorldPosition = worldPosition.xyz;",
+
+ "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );",
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "uniform samplerCube tCube;",
+ "uniform float tFlip;",
+
+ "varying vec3 vWorldPosition;",
+
+ "void main() {",
+
+ "gl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );",
+
+ "}"
+
+ ].join("\n")
+
+ },
+
+ // Depth encoding into RGBA texture
+ // based on SpiderGL shadow map example
+ // http://spidergl.org/example.php?id=6
+ // originally from
+ // http://www.gamedev.net/topic/442138-packing-a-float-into-a-a8r8g8b8-texture-shader/page__whichpage__1%25EF%25BF%25BD
+ // see also here:
+ // http://aras-p.info/blog/2009/07/30/encoding-floats-to-rgba-the-final/
+
+ 'depthRGBA': {
+
+ uniforms: {},
+
+ vertexShader: [
+
+ THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
+ THREE.ShaderChunk[ "skinning_pars_vertex" ],
+
+ "void main() {",
+
+ THREE.ShaderChunk[ "skinbase_vertex" ],
+ THREE.ShaderChunk[ "morphtarget_vertex" ],
+ THREE.ShaderChunk[ "skinning_vertex" ],
+ THREE.ShaderChunk[ "default_vertex" ],
+
+ "}"
+
+ ].join("\n"),
+
+ fragmentShader: [
+
+ "vec4 pack_depth( const in float depth ) {",
+
+ "const vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );",
+ "const vec4 bit_mask = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );",
+ "vec4 res = fract( depth * bit_shift );",
+ "res -= res.xxyz * bit_mask;",
+ "return res;",
+
+ "}",
+
+ "void main() {",
+
+ "gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );",
+
+ //"gl_FragData[ 0 ] = pack_depth( gl_FragCoord.z / gl_FragCoord.w );",
+ //"float z = ( ( gl_FragCoord.z / gl_FragCoord.w ) - 3.0 ) / ( 4000.0 - 3.0 );",
+ //"gl_FragData[ 0 ] = pack_depth( z );",
+ //"gl_FragData[ 0 ] = vec4( z, z, z, 1.0 );",
+
+ "}"
+
+ ].join("\n")
+
+ }
+
+};
+
+/**
+ * @author supereggbert / http://www.paulbrunt.co.uk/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author szimek / https://github.com/szimek/
+ */
+
+THREE.WebGLRenderer = function ( parameters ) {
+
+ console.log( 'THREE.WebGLRenderer', THREE.REVISION );
+
+ parameters = parameters || {};
+
+ var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement( 'canvas' ),
+
+ _precision = parameters.precision !== undefined ? parameters.precision : 'highp',
+
+ _alpha = parameters.alpha !== undefined ? parameters.alpha : true,
+ _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,
+ _antialias = parameters.antialias !== undefined ? parameters.antialias : false,
+ _stencil = parameters.stencil !== undefined ? parameters.stencil : true,
+ _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false,
+
+ _clearColor = new THREE.Color( 0x000000 ),
+ _clearAlpha = 0;
+
+ // public properties
+
+ this.domElement = _canvas;
+ this.context = null;
+ this.devicePixelRatio = parameters.devicePixelRatio !== undefined
+ ? parameters.devicePixelRatio
+ : self.devicePixelRatio !== undefined
+ ? self.devicePixelRatio
+ : 1;
+
+ // clearing
+
+ this.autoClear = true;
+ this.autoClearColor = true;
+ this.autoClearDepth = true;
+ this.autoClearStencil = true;
+
+ // scene graph
+
+ this.sortObjects = true;
+ this.autoUpdateObjects = true;
+
+ // physically based shading
+
+ this.gammaInput = false;
+ this.gammaOutput = false;
+ this.physicallyBasedShading = false;
+
+ // shadow map
+
+ this.shadowMapEnabled = false;
+ this.shadowMapAutoUpdate = true;
+ this.shadowMapType = THREE.PCFShadowMap;
+ this.shadowMapCullFace = THREE.CullFaceFront;
+ this.shadowMapDebug = false;
+ this.shadowMapCascade = false;
+
+ // morphs
+
+ this.maxMorphTargets = 8;
+ this.maxMorphNormals = 4;
+
+ // flags
+
+ this.autoScaleCubemaps = true;
+
+ // custom render plugins
+
+ this.renderPluginsPre = [];
+ this.renderPluginsPost = [];
+
+ // info
+
+ this.info = {
+
+ memory: {
+
+ programs: 0,
+ geometries: 0,
+ textures: 0
+
+ },
+
+ render: {
+
+ calls: 0,
+ vertices: 0,
+ faces: 0,
+ points: 0
+
+ }
+
+ };
+
+ // internal properties
+
+ var _this = this,
+
+ _programs = [],
+ _programs_counter = 0,
+
+ // internal state cache
+
+ _currentProgram = null,
+ _currentFramebuffer = null,
+ _currentMaterialId = -1,
+ _currentGeometryGroupHash = null,
+ _currentCamera = null,
+ _geometryGroupCounter = 0,
+
+ _usedTextureUnits = 0,
+
+ // GL state cache
+
+ _oldDoubleSided = -1,
+ _oldFlipSided = -1,
+
+ _oldBlending = -1,
+
+ _oldBlendEquation = -1,
+ _oldBlendSrc = -1,
+ _oldBlendDst = -1,
+
+ _oldDepthTest = -1,
+ _oldDepthWrite = -1,
+
+ _oldPolygonOffset = null,
+ _oldPolygonOffsetFactor = null,
+ _oldPolygonOffsetUnits = null,
+
+ _oldLineWidth = null,
+
+ _viewportX = 0,
+ _viewportY = 0,
+ _viewportWidth = 0,
+ _viewportHeight = 0,
+ _currentWidth = 0,
+ _currentHeight = 0,
+
+ _enabledAttributes = {},
+
+ // frustum
+
+ _frustum = new THREE.Frustum(),
+
+ // camera matrices cache
+
+ _projScreenMatrix = new THREE.Matrix4(),
+ _projScreenMatrixPS = new THREE.Matrix4(),
+
+ _vector3 = new THREE.Vector3(),
+
+ // light arrays cache
+
+ _direction = new THREE.Vector3(),
+
+ _lightsNeedUpdate = true,
+
+ _lights = {
+
+ ambient: [ 0, 0, 0 ],
+ directional: { length: 0, colors: new Array(), positions: new Array() },
+ point: { length: 0, colors: new Array(), positions: new Array(), distances: new Array() },
+ spot: { length: 0, colors: new Array(), positions: new Array(), distances: new Array(), directions: new Array(), anglesCos: new Array(), exponents: new Array() },
+ hemi: { length: 0, skyColors: new Array(), groundColors: new Array(), positions: new Array() }
+
+ };
+
+ // initialize
+
+ var _gl;
+
+ var _glExtensionTextureFloat;
+ var _glExtensionTextureFloatLinear;
+ var _glExtensionStandardDerivatives;
+ var _glExtensionTextureFilterAnisotropic;
+ var _glExtensionCompressedTextureS3TC;
+
+ initGL();
+
+ setDefaultGLState();
+
+ this.context = _gl;
+
+ // GPU capabilities
+
+ var _maxTextures = _gl.getParameter( _gl.MAX_TEXTURE_IMAGE_UNITS );
+ var _maxVertexTextures = _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS );
+ var _maxTextureSize = _gl.getParameter( _gl.MAX_TEXTURE_SIZE );
+ var _maxCubemapSize = _gl.getParameter( _gl.MAX_CUBE_MAP_TEXTURE_SIZE );
+
+ var _maxAnisotropy = _glExtensionTextureFilterAnisotropic ? _gl.getParameter( _glExtensionTextureFilterAnisotropic.MAX_TEXTURE_MAX_ANISOTROPY_EXT ) : 0;
+
+ var _supportsVertexTextures = ( _maxVertexTextures > 0 );
+ var _supportsBoneTextures = _supportsVertexTextures && _glExtensionTextureFloat;
+
+ var _compressedTextureFormats = _glExtensionCompressedTextureS3TC ? _gl.getParameter( _gl.COMPRESSED_TEXTURE_FORMATS ) : [];
+
+ //
+
+ var _vertexShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_FLOAT );
+ var _vertexShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_FLOAT );
+ var _vertexShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_FLOAT );
+
+ var _fragmentShaderPrecisionHighpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_FLOAT );
+ var _fragmentShaderPrecisionMediumpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_FLOAT );
+ var _fragmentShaderPrecisionLowpFloat = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_FLOAT );
+
+ var _vertexShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.HIGH_INT );
+ var _vertexShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.MEDIUM_INT );
+ var _vertexShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.VERTEX_SHADER, _gl.LOW_INT );
+
+ var _fragmentShaderPrecisionHighpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.HIGH_INT );
+ var _fragmentShaderPrecisionMediumpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.MEDIUM_INT );
+ var _fragmentShaderPrecisionLowpInt = _gl.getShaderPrecisionFormat( _gl.FRAGMENT_SHADER, _gl.LOW_INT );
+
+ // clamp precision to maximum available
+
+ var highpAvailable = _vertexShaderPrecisionHighpFloat.precision > 0 && _fragmentShaderPrecisionHighpFloat.precision > 0;
+ var mediumpAvailable = _vertexShaderPrecisionMediumpFloat.precision > 0 && _fragmentShaderPrecisionMediumpFloat.precision > 0;
+
+ if ( _precision === "highp" && ! highpAvailable ) {
+
+ if ( mediumpAvailable ) {
+
+ _precision = "mediump";
+ console.warn( "WebGLRenderer: highp not supported, using mediump" );
+
+ } else {
+
+ _precision = "lowp";
+ console.warn( "WebGLRenderer: highp and mediump not supported, using lowp" );
+
+ }
+
+ }
+
+ if ( _precision === "mediump" && ! mediumpAvailable ) {
+
+ _precision = "lowp";
+ console.warn( "WebGLRenderer: mediump not supported, using lowp" );
+
+ }
+
+ // API
+
+ this.getContext = function () {
+
+ return _gl;
+
+ };
+
+ this.supportsVertexTextures = function () {
+
+ return _supportsVertexTextures;
+
+ };
+
+ this.supportsFloatTextures = function () {
+
+ return _glExtensionTextureFloat;
+
+ };
+
+ this.supportsStandardDerivatives = function () {
+
+ return _glExtensionStandardDerivatives;
+
+ };
+
+ this.supportsCompressedTextureS3TC = function () {
+
+ return _glExtensionCompressedTextureS3TC;
+
+ };
+
+ this.getMaxAnisotropy = function () {
+
+ return _maxAnisotropy;
+
+ };
+
+ this.getPrecision = function () {
+
+ return _precision;
+
+ };
+
+ this.setSize = function ( width, height, updateStyle ) {
+
+ _canvas.width = width * this.devicePixelRatio;
+ _canvas.height = height * this.devicePixelRatio;
+
+ if ( this.devicePixelRatio !== 1 && updateStyle !== false ) {
+
+ _canvas.style.width = width + 'px';
+ _canvas.style.height = height + 'px';
+
+ }
+
+ this.setViewport( 0, 0, _canvas.width, _canvas.height );
+
+ };
+
+ this.setViewport = function ( x, y, width, height ) {
+
+ _viewportX = x !== undefined ? x : 0;
+ _viewportY = y !== undefined ? y : 0;
+
+ _viewportWidth = width !== undefined ? width : _canvas.width;
+ _viewportHeight = height !== undefined ? height : _canvas.height;
+
+ _gl.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight );
+
+ };
+
+ this.setScissor = function ( x, y, width, height ) {
+
+ _gl.scissor( x, y, width, height );
+
+ };
+
+ this.enableScissorTest = function ( enable ) {
+
+ enable ? _gl.enable( _gl.SCISSOR_TEST ) : _gl.disable( _gl.SCISSOR_TEST );
+
+ };
+
+ // Clearing
+
+ this.setClearColor = function ( color, alpha ) {
+
+ _clearColor.set( color );
+ _clearAlpha = alpha !== undefined ? alpha : 1;
+
+ _gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
+
+ };
+
+ this.setClearColorHex = function ( hex, alpha ) {
+
+ console.warn( 'DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.' );
+ this.setClearColor( hex, alpha );
+
+ };
+
+ this.getClearColor = function () {
+
+ return _clearColor;
+
+ };
+
+ this.getClearAlpha = function () {
+
+ return _clearAlpha;
+
+ };
+
+ this.clear = function ( color, depth, stencil ) {
+
+ var bits = 0;
+
+ if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT;
+ if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT;
+ if ( stencil === undefined || stencil ) bits |= _gl.STENCIL_BUFFER_BIT;
+
+ _gl.clear( bits );
+
+ };
+
+ this.clearTarget = function ( renderTarget, color, depth, stencil ) {
+
+ this.setRenderTarget( renderTarget );
+ this.clear( color, depth, stencil );
+
+ };
+
+ // Plugins
+
+ this.addPostPlugin = function ( plugin ) {
+
+ plugin.init( this );
+ this.renderPluginsPost.push( plugin );
+
+ };
+
+ this.addPrePlugin = function ( plugin ) {
+
+ plugin.init( this );
+ this.renderPluginsPre.push( plugin );
+
+ };
+
+ // Rendering
+
+ this.updateShadowMap = function ( scene, camera ) {
+
+ _currentProgram = null;
+ _oldBlending = -1;
+ _oldDepthTest = -1;
+ _oldDepthWrite = -1;
+ _currentGeometryGroupHash = -1;
+ _currentMaterialId = -1;
+ _lightsNeedUpdate = true;
+ _oldDoubleSided = -1;
+ _oldFlipSided = -1;
+
+ this.shadowMapPlugin.update( scene, camera );
+
+ };
+
+ // Internal functions
+
+ // Buffer allocation
+
+ function createParticleBuffers ( geometry ) {
+
+ geometry.__webglVertexBuffer = _gl.createBuffer();
+ geometry.__webglColorBuffer = _gl.createBuffer();
+
+ _this.info.memory.geometries ++;
+
+ };
+
+ function createLineBuffers ( geometry ) {
+
+ geometry.__webglVertexBuffer = _gl.createBuffer();
+ geometry.__webglColorBuffer = _gl.createBuffer();
+ geometry.__webglLineDistanceBuffer = _gl.createBuffer();
+
+ _this.info.memory.geometries ++;
+
+ };
+
+ function createMeshBuffers ( geometryGroup ) {
+
+ geometryGroup.__webglVertexBuffer = _gl.createBuffer();
+ geometryGroup.__webglNormalBuffer = _gl.createBuffer();
+ geometryGroup.__webglTangentBuffer = _gl.createBuffer();
+ geometryGroup.__webglColorBuffer = _gl.createBuffer();
+ geometryGroup.__webglUVBuffer = _gl.createBuffer();
+ geometryGroup.__webglUV2Buffer = _gl.createBuffer();
+
+ geometryGroup.__webglSkinIndicesBuffer = _gl.createBuffer();
+ geometryGroup.__webglSkinWeightsBuffer = _gl.createBuffer();
+
+ geometryGroup.__webglFaceBuffer = _gl.createBuffer();
+ geometryGroup.__webglLineBuffer = _gl.createBuffer();
+
+ var m, ml;
+
+ if ( geometryGroup.numMorphTargets ) {
+
+ geometryGroup.__webglMorphTargetsBuffers = [];
+
+ for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+ geometryGroup.__webglMorphTargetsBuffers.push( _gl.createBuffer() );
+
+ }
+
+ }
+
+ if ( geometryGroup.numMorphNormals ) {
+
+ geometryGroup.__webglMorphNormalsBuffers = [];
+
+ for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+ geometryGroup.__webglMorphNormalsBuffers.push( _gl.createBuffer() );
+
+ }
+
+ }
+
+ _this.info.memory.geometries ++;
+
+ };
+
+ // Events
+
+ var onGeometryDispose = function ( event ) {
+
+ var geometry = event.target;
+
+ geometry.removeEventListener( 'dispose', onGeometryDispose );
+
+ deallocateGeometry( geometry );
+
+ };
+
+ var onTextureDispose = function ( event ) {
+
+ var texture = event.target;
+
+ texture.removeEventListener( 'dispose', onTextureDispose );
+
+ deallocateTexture( texture );
+
+ _this.info.memory.textures --;
+
+
+ };
+
+ var onRenderTargetDispose = function ( event ) {
+
+ var renderTarget = event.target;
+
+ renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
+
+ deallocateRenderTarget( renderTarget );
+
+ _this.info.memory.textures --;
+
+ };
+
+ var onMaterialDispose = function ( event ) {
+
+ var material = event.target;
+
+ material.removeEventListener( 'dispose', onMaterialDispose );
+
+ deallocateMaterial( material );
+
+ };
+
+ // Buffer deallocation
+
+ var deleteBuffers = function ( geometry ) {
+
+ if ( geometry.__webglVertexBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglVertexBuffer );
+ if ( geometry.__webglNormalBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglNormalBuffer );
+ if ( geometry.__webglTangentBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglTangentBuffer );
+ if ( geometry.__webglColorBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglColorBuffer );
+ if ( geometry.__webglUVBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglUVBuffer );
+ if ( geometry.__webglUV2Buffer !== undefined ) _gl.deleteBuffer( geometry.__webglUV2Buffer );
+
+ if ( geometry.__webglSkinIndicesBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinIndicesBuffer );
+ if ( geometry.__webglSkinWeightsBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglSkinWeightsBuffer );
+
+ if ( geometry.__webglFaceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglFaceBuffer );
+ if ( geometry.__webglLineBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineBuffer );
+
+ if ( geometry.__webglLineDistanceBuffer !== undefined ) _gl.deleteBuffer( geometry.__webglLineDistanceBuffer );
+ // custom attributes
+
+ if ( geometry.__webglCustomAttributesList !== undefined ) {
+
+ for ( var id in geometry.__webglCustomAttributesList ) {
+
+ _gl.deleteBuffer( geometry.__webglCustomAttributesList[ id ].buffer );
+
+ }
+
+ }
+
+ _this.info.memory.geometries --;
+
+ };
+
+ var deallocateGeometry = function ( geometry ) {
+
+ geometry.__webglInit = undefined;
+
+ if ( geometry instanceof THREE.BufferGeometry ) {
+
+ var attributes = geometry.attributes;
+
+ for ( var key in attributes ) {
+
+ if ( attributes[ key ].buffer !== undefined ) {
+
+ _gl.deleteBuffer( attributes[ key ].buffer );
+
+ }
+
+ }
+
+ _this.info.memory.geometries --;
+
+ } else {
+
+ if ( geometry.geometryGroups !== undefined ) {
+
+ for ( var g in geometry.geometryGroups ) {
+
+ var geometryGroup = geometry.geometryGroups[ g ];
+
+ if ( geometryGroup.numMorphTargets !== undefined ) {
+
+ for ( var m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+ _gl.deleteBuffer( geometryGroup.__webglMorphTargetsBuffers[ m ] );
+
+ }
+
+ }
+
+ if ( geometryGroup.numMorphNormals !== undefined ) {
+
+ for ( var m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+ _gl.deleteBuffer( geometryGroup.__webglMorphNormalsBuffers[ m ] );
+
+ }
+
+ }
+
+ deleteBuffers( geometryGroup );
+
+ }
+
+ } else {
+
+ deleteBuffers( geometry );
+
+ }
+
+ }
+
+ };
+
+ var deallocateTexture = function ( texture ) {
+
+ if ( texture.image && texture.image.__webglTextureCube ) {
+
+ // cube texture
+
+ _gl.deleteTexture( texture.image.__webglTextureCube );
+
+ } else {
+
+ // 2D texture
+
+ if ( ! texture.__webglInit ) return;
+
+ texture.__webglInit = false;
+ _gl.deleteTexture( texture.__webglTexture );
+
+ }
+
+ };
+
+ var deallocateRenderTarget = function ( renderTarget ) {
+
+ if ( !renderTarget || ! renderTarget.__webglTexture ) return;
+
+ _gl.deleteTexture( renderTarget.__webglTexture );
+
+ if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) {
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ _gl.deleteFramebuffer( renderTarget.__webglFramebuffer[ i ] );
+ _gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer[ i ] );
+
+ }
+
+ } else {
+
+ _gl.deleteFramebuffer( renderTarget.__webglFramebuffer );
+ _gl.deleteRenderbuffer( renderTarget.__webglRenderbuffer );
+
+ }
+
+ };
+
+ var deallocateMaterial = function ( material ) {
+
+ var program = material.program;
+
+ if ( program === undefined ) return;
+
+ material.program = undefined;
+
+ // only deallocate GL program if this was the last use of shared program
+ // assumed there is only single copy of any program in the _programs list
+ // (that's how it's constructed)
+
+ var i, il, programInfo;
+ var deleteProgram = false;
+
+ for ( i = 0, il = _programs.length; i < il; i ++ ) {
+
+ programInfo = _programs[ i ];
+
+ if ( programInfo.program === program ) {
+
+ programInfo.usedTimes --;
+
+ if ( programInfo.usedTimes === 0 ) {
+
+ deleteProgram = true;
+
+ }
+
+ break;
+
+ }
+
+ }
+
+ if ( deleteProgram === true ) {
+
+ // avoid using array.splice, this is costlier than creating new array from scratch
+
+ var newPrograms = [];
+
+ for ( i = 0, il = _programs.length; i < il; i ++ ) {
+
+ programInfo = _programs[ i ];
+
+ if ( programInfo.program !== program ) {
+
+ newPrograms.push( programInfo );
+
+ }
+
+ }
+
+ _programs = newPrograms;
+
+ _gl.deleteProgram( program );
+
+ _this.info.memory.programs --;
+
+ }
+
+ };
+
+ // Buffer initialization
+
+ function initCustomAttributes ( geometry, object ) {
+
+ var nvertices = geometry.vertices.length;
+
+ var material = object.material;
+
+ if ( material.attributes ) {
+
+ if ( geometry.__webglCustomAttributesList === undefined ) {
+
+ geometry.__webglCustomAttributesList = [];
+
+ }
+
+ for ( var a in material.attributes ) {
+
+ var attribute = material.attributes[ a ];
+
+ if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) {
+
+ attribute.__webglInitialized = true;
+
+ var size = 1; // "f" and "i"
+
+ if ( attribute.type === "v2" ) size = 2;
+ else if ( attribute.type === "v3" ) size = 3;
+ else if ( attribute.type === "v4" ) size = 4;
+ else if ( attribute.type === "c" ) size = 3;
+
+ attribute.size = size;
+
+ attribute.array = new Float32Array( nvertices * size );
+
+ attribute.buffer = _gl.createBuffer();
+ attribute.buffer.belongsToAttribute = a;
+
+ attribute.needsUpdate = true;
+
+ }
+
+ geometry.__webglCustomAttributesList.push( attribute );
+
+ }
+
+ }
+
+ };
+
+ function initParticleBuffers ( geometry, object ) {
+
+ var nvertices = geometry.vertices.length;
+
+ geometry.__vertexArray = new Float32Array( nvertices * 3 );
+ geometry.__colorArray = new Float32Array( nvertices * 3 );
+
+ geometry.__sortArray = [];
+
+ geometry.__webglParticleCount = nvertices;
+
+ initCustomAttributes ( geometry, object );
+
+ };
+
+ function initLineBuffers ( geometry, object ) {
+
+ var nvertices = geometry.vertices.length;
+
+ geometry.__vertexArray = new Float32Array( nvertices * 3 );
+ geometry.__colorArray = new Float32Array( nvertices * 3 );
+ geometry.__lineDistanceArray = new Float32Array( nvertices * 1 );
+
+ geometry.__webglLineCount = nvertices;
+
+ initCustomAttributes ( geometry, object );
+
+ };
+
+ function initMeshBuffers ( geometryGroup, object ) {
+
+ var geometry = object.geometry,
+ faces3 = geometryGroup.faces3,
+
+ nvertices = faces3.length * 3,
+ ntris = faces3.length * 1,
+ nlines = faces3.length * 3,
+
+ material = getBufferMaterial( object, geometryGroup ),
+
+ uvType = bufferGuessUVType( material ),
+ normalType = bufferGuessNormalType( material ),
+ vertexColorType = bufferGuessVertexColorType( material );
+
+ // console.log( "uvType", uvType, "normalType", normalType, "vertexColorType", vertexColorType, object, geometryGroup, material );
+
+ geometryGroup.__vertexArray = new Float32Array( nvertices * 3 );
+
+ if ( normalType ) {
+
+ geometryGroup.__normalArray = new Float32Array( nvertices * 3 );
+
+ }
+
+ if ( geometry.hasTangents ) {
+
+ geometryGroup.__tangentArray = new Float32Array( nvertices * 4 );
+
+ }
+
+ if ( vertexColorType ) {
+
+ geometryGroup.__colorArray = new Float32Array( nvertices * 3 );
+
+ }
+
+ if ( uvType ) {
+
+ if ( geometry.faceVertexUvs.length > 0 ) {
+
+ geometryGroup.__uvArray = new Float32Array( nvertices * 2 );
+
+ }
+
+ if ( geometry.faceVertexUvs.length > 1 ) {
+
+ geometryGroup.__uv2Array = new Float32Array( nvertices * 2 );
+
+ }
+
+ }
+
+ if ( object.geometry.skinWeights.length && object.geometry.skinIndices.length ) {
+
+ geometryGroup.__skinIndexArray = new Float32Array( nvertices * 4 );
+ geometryGroup.__skinWeightArray = new Float32Array( nvertices * 4 );
+
+ }
+
+ geometryGroup.__faceArray = new Uint16Array( ntris * 3 );
+ geometryGroup.__lineArray = new Uint16Array( nlines * 2 );
+
+ var m, ml;
+
+ if ( geometryGroup.numMorphTargets ) {
+
+ geometryGroup.__morphTargetsArrays = [];
+
+ for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) {
+
+ geometryGroup.__morphTargetsArrays.push( new Float32Array( nvertices * 3 ) );
+
+ }
+
+ }
+
+ if ( geometryGroup.numMorphNormals ) {
+
+ geometryGroup.__morphNormalsArrays = [];
+
+ for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) {
+
+ geometryGroup.__morphNormalsArrays.push( new Float32Array( nvertices * 3 ) );
+
+ }
+
+ }
+
+ geometryGroup.__webglFaceCount = ntris * 3;
+ geometryGroup.__webglLineCount = nlines * 2;
+
+
+ // custom attributes
+
+ if ( material.attributes ) {
+
+ if ( geometryGroup.__webglCustomAttributesList === undefined ) {
+
+ geometryGroup.__webglCustomAttributesList = [];
+
+ }
+
+ for ( var a in material.attributes ) {
+
+ // Do a shallow copy of the attribute object so different geometryGroup chunks use different
+ // attribute buffers which are correctly indexed in the setMeshBuffers function
+
+ var originalAttribute = material.attributes[ a ];
+
+ var attribute = {};
+
+ for ( var property in originalAttribute ) {
+
+ attribute[ property ] = originalAttribute[ property ];
+
+ }
+
+ if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) {
+
+ attribute.__webglInitialized = true;
+
+ var size = 1; // "f" and "i"
+
+ if( attribute.type === "v2" ) size = 2;
+ else if( attribute.type === "v3" ) size = 3;
+ else if( attribute.type === "v4" ) size = 4;
+ else if( attribute.type === "c" ) size = 3;
+
+ attribute.size = size;
+
+ attribute.array = new Float32Array( nvertices * size );
+
+ attribute.buffer = _gl.createBuffer();
+ attribute.buffer.belongsToAttribute = a;
+
+ originalAttribute.needsUpdate = true;
+ attribute.__original = originalAttribute;
+
+ }
+
+ geometryGroup.__webglCustomAttributesList.push( attribute );
+
+ }
+
+ }
+
+ geometryGroup.__inittedArrays = true;
+
+ };
+
+ function getBufferMaterial( object, geometryGroup ) {
+
+ return object.material instanceof THREE.MeshFaceMaterial
+ ? object.material.materials[ geometryGroup.materialIndex ]
+ : object.material;
+
+ };
+
+ function materialNeedsSmoothNormals ( material ) {
+
+ return material && material.shading !== undefined && material.shading === THREE.SmoothShading;
+
+ };
+
+ function bufferGuessNormalType ( material ) {
+
+ // only MeshBasicMaterial and MeshDepthMaterial don't need normals
+
+ if ( ( material instanceof THREE.MeshBasicMaterial && !material.envMap ) || material instanceof THREE.MeshDepthMaterial ) {
+
+ return false;
+
+ }
+
+ if ( materialNeedsSmoothNormals( material ) ) {
+
+ return THREE.SmoothShading;
+
+ } else {
+
+ return THREE.FlatShading;
+
+ }
+
+ };
+
+ function bufferGuessVertexColorType( material ) {
+
+ if ( material.vertexColors ) {
+
+ return material.vertexColors;
+
+ }
+
+ return false;
+
+ };
+
+ function bufferGuessUVType( material ) {
+
+ // material must use some texture to require uvs
+
+ if ( material.map ||
+ material.lightMap ||
+ material.bumpMap ||
+ material.normalMap ||
+ material.specularMap ||
+ material instanceof THREE.ShaderMaterial ) {
+
+ return true;
+
+ }
+
+ return false;
+
+ };
+
+ //
+
+ function initDirectBuffers( geometry ) {
+
+ var a, attribute, type;
+
+ for ( a in geometry.attributes ) {
+
+ if ( a === "index" ) {
+
+ type = _gl.ELEMENT_ARRAY_BUFFER;
+
+ } else {
+
+ type = _gl.ARRAY_BUFFER;
+
+ }
+
+ attribute = geometry.attributes[ a ];
+
+ if ( attribute.numItems === undefined ) {
+
+ attribute.numItems = attribute.array.length;
+
+ }
+
+ attribute.buffer = _gl.createBuffer();
+
+ _gl.bindBuffer( type, attribute.buffer );
+ _gl.bufferData( type, attribute.array, _gl.STATIC_DRAW );
+
+ }
+
+ };
+
+ // Buffer setting
+
+ function setParticleBuffers ( geometry, hint, object ) {
+
+ var v, c, vertex, offset, index, color,
+
+ vertices = geometry.vertices,
+ vl = vertices.length,
+
+ colors = geometry.colors,
+ cl = colors.length,
+
+ vertexArray = geometry.__vertexArray,
+ colorArray = geometry.__colorArray,
+
+ sortArray = geometry.__sortArray,
+
+ dirtyVertices = geometry.verticesNeedUpdate,
+ dirtyElements = geometry.elementsNeedUpdate,
+ dirtyColors = geometry.colorsNeedUpdate,
+
+ customAttributes = geometry.__webglCustomAttributesList,
+ i, il,
+ a, ca, cal, value,
+ customAttribute;
+
+ if ( object.sortParticles ) {
+
+ _projScreenMatrixPS.copy( _projScreenMatrix );
+ _projScreenMatrixPS.multiply( object.matrixWorld );
+
+ for ( v = 0; v < vl; v ++ ) {
+
+ vertex = vertices[ v ];
+
+ _vector3.copy( vertex );
+ _vector3.applyProjection( _projScreenMatrixPS );
+
+ sortArray[ v ] = [ _vector3.z, v ];
+
+ }
+
+ sortArray.sort( numericalSort );
+
+ for ( v = 0; v < vl; v ++ ) {
+
+ vertex = vertices[ sortArray[v][1] ];
+
+ offset = v * 3;
+
+ vertexArray[ offset ] = vertex.x;
+ vertexArray[ offset + 1 ] = vertex.y;
+ vertexArray[ offset + 2 ] = vertex.z;
+
+ }
+
+ for ( c = 0; c < cl; c ++ ) {
+
+ offset = c * 3;
+
+ color = colors[ sortArray[c][1] ];
+
+ colorArray[ offset ] = color.r;
+ colorArray[ offset + 1 ] = color.g;
+ colorArray[ offset + 2 ] = color.b;
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( ! ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) ) continue;
+
+ offset = 0;
+
+ cal = customAttribute.value.length;
+
+ if ( customAttribute.size === 1 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ customAttribute.array[ ca ] = customAttribute.value[ index ];
+
+ }
+
+ } else if ( customAttribute.size === 2 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ value = customAttribute.value[ index ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+
+ offset += 2;
+
+ }
+
+ } else if ( customAttribute.size === 3 ) {
+
+ if ( customAttribute.type === "c" ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ value = customAttribute.value[ index ];
+
+ customAttribute.array[ offset ] = value.r;
+ customAttribute.array[ offset + 1 ] = value.g;
+ customAttribute.array[ offset + 2 ] = value.b;
+
+ offset += 3;
+
+ }
+
+ } else {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ value = customAttribute.value[ index ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+
+ offset += 3;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 4 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ index = sortArray[ ca ][ 1 ];
+
+ value = customAttribute.value[ index ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+ customAttribute.array[ offset + 3 ] = value.w;
+
+ offset += 4;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ } else {
+
+ if ( dirtyVertices ) {
+
+ for ( v = 0; v < vl; v ++ ) {
+
+ vertex = vertices[ v ];
+
+ offset = v * 3;
+
+ vertexArray[ offset ] = vertex.x;
+ vertexArray[ offset + 1 ] = vertex.y;
+ vertexArray[ offset + 2 ] = vertex.z;
+
+ }
+
+ }
+
+ if ( dirtyColors ) {
+
+ for ( c = 0; c < cl; c ++ ) {
+
+ color = colors[ c ];
+
+ offset = c * 3;
+
+ colorArray[ offset ] = color.r;
+ colorArray[ offset + 1 ] = color.g;
+ colorArray[ offset + 2 ] = color.b;
+
+ }
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( customAttribute.needsUpdate &&
+ ( customAttribute.boundTo === undefined ||
+ customAttribute.boundTo === "vertices") ) {
+
+ cal = customAttribute.value.length;
+
+ offset = 0;
+
+ if ( customAttribute.size === 1 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ customAttribute.array[ ca ] = customAttribute.value[ ca ];
+
+ }
+
+ } else if ( customAttribute.size === 2 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+
+ offset += 2;
+
+ }
+
+ } else if ( customAttribute.size === 3 ) {
+
+ if ( customAttribute.type === "c" ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.r;
+ customAttribute.array[ offset + 1 ] = value.g;
+ customAttribute.array[ offset + 2 ] = value.b;
+
+ offset += 3;
+
+ }
+
+ } else {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+
+ offset += 3;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 4 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+ customAttribute.array[ offset + 3 ] = value.w;
+
+ offset += 4;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( dirtyVertices || object.sortParticles ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+ }
+
+ if ( dirtyColors || object.sortParticles ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( customAttribute.needsUpdate || object.sortParticles ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+ }
+
+ }
+
+ }
+
+
+ };
+
+ function setLineBuffers ( geometry, hint ) {
+
+ var v, c, d, vertex, offset, color,
+
+ vertices = geometry.vertices,
+ colors = geometry.colors,
+ lineDistances = geometry.lineDistances,
+
+ vl = vertices.length,
+ cl = colors.length,
+ dl = lineDistances.length,
+
+ vertexArray = geometry.__vertexArray,
+ colorArray = geometry.__colorArray,
+ lineDistanceArray = geometry.__lineDistanceArray,
+
+ dirtyVertices = geometry.verticesNeedUpdate,
+ dirtyColors = geometry.colorsNeedUpdate,
+ dirtyLineDistances = geometry.lineDistancesNeedUpdate,
+
+ customAttributes = geometry.__webglCustomAttributesList,
+
+ i, il,
+ a, ca, cal, value,
+ customAttribute;
+
+ if ( dirtyVertices ) {
+
+ for ( v = 0; v < vl; v ++ ) {
+
+ vertex = vertices[ v ];
+
+ offset = v * 3;
+
+ vertexArray[ offset ] = vertex.x;
+ vertexArray[ offset + 1 ] = vertex.y;
+ vertexArray[ offset + 2 ] = vertex.z;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglVertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+ }
+
+ if ( dirtyColors ) {
+
+ for ( c = 0; c < cl; c ++ ) {
+
+ color = colors[ c ];
+
+ offset = c * 3;
+
+ colorArray[ offset ] = color.r;
+ colorArray[ offset + 1 ] = color.g;
+ colorArray[ offset + 2 ] = color.b;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglColorBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+ }
+
+ if ( dirtyLineDistances ) {
+
+ for ( d = 0; d < dl; d ++ ) {
+
+ lineDistanceArray[ d ] = lineDistances[ d ];
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometry.__webglLineDistanceBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, lineDistanceArray, hint );
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( customAttribute.needsUpdate &&
+ ( customAttribute.boundTo === undefined ||
+ customAttribute.boundTo === "vertices" ) ) {
+
+ offset = 0;
+
+ cal = customAttribute.value.length;
+
+ if ( customAttribute.size === 1 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ customAttribute.array[ ca ] = customAttribute.value[ ca ];
+
+ }
+
+ } else if ( customAttribute.size === 2 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+
+ offset += 2;
+
+ }
+
+ } else if ( customAttribute.size === 3 ) {
+
+ if ( customAttribute.type === "c" ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.r;
+ customAttribute.array[ offset + 1 ] = value.g;
+ customAttribute.array[ offset + 2 ] = value.b;
+
+ offset += 3;
+
+ }
+
+ } else {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+
+ offset += 3;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 4 ) {
+
+ for ( ca = 0; ca < cal; ca ++ ) {
+
+ value = customAttribute.value[ ca ];
+
+ customAttribute.array[ offset ] = value.x;
+ customAttribute.array[ offset + 1 ] = value.y;
+ customAttribute.array[ offset + 2 ] = value.z;
+ customAttribute.array[ offset + 3 ] = value.w;
+
+ offset += 4;
+
+ }
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ function setMeshBuffers( geometryGroup, object, hint, dispose, material ) {
+
+ if ( ! geometryGroup.__inittedArrays ) {
+
+ return;
+
+ }
+
+ var normalType = bufferGuessNormalType( material ),
+ vertexColorType = bufferGuessVertexColorType( material ),
+ uvType = bufferGuessUVType( material ),
+
+ needsSmoothNormals = ( normalType === THREE.SmoothShading );
+
+ var f, fl, fi, face,
+ vertexNormals, faceNormal, normal,
+ vertexColors, faceColor,
+ vertexTangents,
+ uv, uv2, v1, v2, v3, v4, t1, t2, t3, t4, n1, n2, n3, n4,
+ c1, c2, c3, c4,
+ sw1, sw2, sw3, sw4,
+ si1, si2, si3, si4,
+ sa1, sa2, sa3, sa4,
+ sb1, sb2, sb3, sb4,
+ m, ml, i, il,
+ vn, uvi, uv2i,
+ vk, vkl, vka,
+ nka, chf, faceVertexNormals,
+ a,
+
+ vertexIndex = 0,
+
+ offset = 0,
+ offset_uv = 0,
+ offset_uv2 = 0,
+ offset_face = 0,
+ offset_normal = 0,
+ offset_tangent = 0,
+ offset_line = 0,
+ offset_color = 0,
+ offset_skin = 0,
+ offset_morphTarget = 0,
+ offset_custom = 0,
+ offset_customSrc = 0,
+
+ value,
+
+ vertexArray = geometryGroup.__vertexArray,
+ uvArray = geometryGroup.__uvArray,
+ uv2Array = geometryGroup.__uv2Array,
+ normalArray = geometryGroup.__normalArray,
+ tangentArray = geometryGroup.__tangentArray,
+ colorArray = geometryGroup.__colorArray,
+
+ skinIndexArray = geometryGroup.__skinIndexArray,
+ skinWeightArray = geometryGroup.__skinWeightArray,
+
+ morphTargetsArrays = geometryGroup.__morphTargetsArrays,
+ morphNormalsArrays = geometryGroup.__morphNormalsArrays,
+
+ customAttributes = geometryGroup.__webglCustomAttributesList,
+ customAttribute,
+
+ faceArray = geometryGroup.__faceArray,
+ lineArray = geometryGroup.__lineArray,
+
+ geometry = object.geometry, // this is shared for all chunks
+
+ dirtyVertices = geometry.verticesNeedUpdate,
+ dirtyElements = geometry.elementsNeedUpdate,
+ dirtyUvs = geometry.uvsNeedUpdate,
+ dirtyNormals = geometry.normalsNeedUpdate,
+ dirtyTangents = geometry.tangentsNeedUpdate,
+ dirtyColors = geometry.colorsNeedUpdate,
+ dirtyMorphTargets = geometry.morphTargetsNeedUpdate,
+
+ vertices = geometry.vertices,
+ chunk_faces3 = geometryGroup.faces3,
+ obj_faces = geometry.faces,
+
+ obj_uvs = geometry.faceVertexUvs[ 0 ],
+ obj_uvs2 = geometry.faceVertexUvs[ 1 ],
+
+ obj_colors = geometry.colors,
+
+ obj_skinIndices = geometry.skinIndices,
+ obj_skinWeights = geometry.skinWeights,
+
+ morphTargets = geometry.morphTargets,
+ morphNormals = geometry.morphNormals;
+
+ if ( dirtyVertices ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ v1 = vertices[ face.a ];
+ v2 = vertices[ face.b ];
+ v3 = vertices[ face.c ];
+
+ vertexArray[ offset ] = v1.x;
+ vertexArray[ offset + 1 ] = v1.y;
+ vertexArray[ offset + 2 ] = v1.z;
+
+ vertexArray[ offset + 3 ] = v2.x;
+ vertexArray[ offset + 4 ] = v2.y;
+ vertexArray[ offset + 5 ] = v2.z;
+
+ vertexArray[ offset + 6 ] = v3.x;
+ vertexArray[ offset + 7 ] = v3.y;
+ vertexArray[ offset + 8 ] = v3.z;
+
+ offset += 9;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, vertexArray, hint );
+
+ }
+
+ if ( dirtyMorphTargets ) {
+
+ for ( vk = 0, vkl = morphTargets.length; vk < vkl; vk ++ ) {
+
+ offset_morphTarget = 0;
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ chf = chunk_faces3[ f ];
+ face = obj_faces[ chf ];
+
+ // morph positions
+
+ v1 = morphTargets[ vk ].vertices[ face.a ];
+ v2 = morphTargets[ vk ].vertices[ face.b ];
+ v3 = morphTargets[ vk ].vertices[ face.c ];
+
+ vka = morphTargetsArrays[ vk ];
+
+ vka[ offset_morphTarget ] = v1.x;
+ vka[ offset_morphTarget + 1 ] = v1.y;
+ vka[ offset_morphTarget + 2 ] = v1.z;
+
+ vka[ offset_morphTarget + 3 ] = v2.x;
+ vka[ offset_morphTarget + 4 ] = v2.y;
+ vka[ offset_morphTarget + 5 ] = v2.z;
+
+ vka[ offset_morphTarget + 6 ] = v3.x;
+ vka[ offset_morphTarget + 7 ] = v3.y;
+ vka[ offset_morphTarget + 8 ] = v3.z;
+
+ // morph normals
+
+ if ( material.morphNormals ) {
+
+ if ( needsSmoothNormals ) {
+
+ faceVertexNormals = morphNormals[ vk ].vertexNormals[ chf ];
+
+ n1 = faceVertexNormals.a;
+ n2 = faceVertexNormals.b;
+ n3 = faceVertexNormals.c;
+
+ } else {
+
+ n1 = morphNormals[ vk ].faceNormals[ chf ];
+ n2 = n1;
+ n3 = n1;
+
+ }
+
+ nka = morphNormalsArrays[ vk ];
+
+ nka[ offset_morphTarget ] = n1.x;
+ nka[ offset_morphTarget + 1 ] = n1.y;
+ nka[ offset_morphTarget + 2 ] = n1.z;
+
+ nka[ offset_morphTarget + 3 ] = n2.x;
+ nka[ offset_morphTarget + 4 ] = n2.y;
+ nka[ offset_morphTarget + 5 ] = n2.z;
+
+ nka[ offset_morphTarget + 6 ] = n3.x;
+ nka[ offset_morphTarget + 7 ] = n3.y;
+ nka[ offset_morphTarget + 8 ] = n3.z;
+
+ }
+
+ //
+
+ offset_morphTarget += 9;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ vk ] );
+ _gl.bufferData( _gl.ARRAY_BUFFER, morphTargetsArrays[ vk ], hint );
+
+ if ( material.morphNormals ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ vk ] );
+ _gl.bufferData( _gl.ARRAY_BUFFER, morphNormalsArrays[ vk ], hint );
+
+ }
+
+ }
+
+ }
+
+ if ( obj_skinWeights.length ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ // weights
+
+ sw1 = obj_skinWeights[ face.a ];
+ sw2 = obj_skinWeights[ face.b ];
+ sw3 = obj_skinWeights[ face.c ];
+
+ skinWeightArray[ offset_skin ] = sw1.x;
+ skinWeightArray[ offset_skin + 1 ] = sw1.y;
+ skinWeightArray[ offset_skin + 2 ] = sw1.z;
+ skinWeightArray[ offset_skin + 3 ] = sw1.w;
+
+ skinWeightArray[ offset_skin + 4 ] = sw2.x;
+ skinWeightArray[ offset_skin + 5 ] = sw2.y;
+ skinWeightArray[ offset_skin + 6 ] = sw2.z;
+ skinWeightArray[ offset_skin + 7 ] = sw2.w;
+
+ skinWeightArray[ offset_skin + 8 ] = sw3.x;
+ skinWeightArray[ offset_skin + 9 ] = sw3.y;
+ skinWeightArray[ offset_skin + 10 ] = sw3.z;
+ skinWeightArray[ offset_skin + 11 ] = sw3.w;
+
+ // indices
+
+ si1 = obj_skinIndices[ face.a ];
+ si2 = obj_skinIndices[ face.b ];
+ si3 = obj_skinIndices[ face.c ];
+
+ skinIndexArray[ offset_skin ] = si1.x;
+ skinIndexArray[ offset_skin + 1 ] = si1.y;
+ skinIndexArray[ offset_skin + 2 ] = si1.z;
+ skinIndexArray[ offset_skin + 3 ] = si1.w;
+
+ skinIndexArray[ offset_skin + 4 ] = si2.x;
+ skinIndexArray[ offset_skin + 5 ] = si2.y;
+ skinIndexArray[ offset_skin + 6 ] = si2.z;
+ skinIndexArray[ offset_skin + 7 ] = si2.w;
+
+ skinIndexArray[ offset_skin + 8 ] = si3.x;
+ skinIndexArray[ offset_skin + 9 ] = si3.y;
+ skinIndexArray[ offset_skin + 10 ] = si3.z;
+ skinIndexArray[ offset_skin + 11 ] = si3.w;
+
+ offset_skin += 12;
+
+ }
+
+ if ( offset_skin > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, skinIndexArray, hint );
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, skinWeightArray, hint );
+
+ }
+
+ }
+
+ if ( dirtyColors && vertexColorType ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ vertexColors = face.vertexColors;
+ faceColor = face.color;
+
+ if ( vertexColors.length === 3 && vertexColorType === THREE.VertexColors ) {
+
+ c1 = vertexColors[ 0 ];
+ c2 = vertexColors[ 1 ];
+ c3 = vertexColors[ 2 ];
+
+ } else {
+
+ c1 = faceColor;
+ c2 = faceColor;
+ c3 = faceColor;
+
+ }
+
+ colorArray[ offset_color ] = c1.r;
+ colorArray[ offset_color + 1 ] = c1.g;
+ colorArray[ offset_color + 2 ] = c1.b;
+
+ colorArray[ offset_color + 3 ] = c2.r;
+ colorArray[ offset_color + 4 ] = c2.g;
+ colorArray[ offset_color + 5 ] = c2.b;
+
+ colorArray[ offset_color + 6 ] = c3.r;
+ colorArray[ offset_color + 7 ] = c3.g;
+ colorArray[ offset_color + 8 ] = c3.b;
+
+ offset_color += 9;
+
+ }
+
+ if ( offset_color > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, colorArray, hint );
+
+ }
+
+ }
+
+ if ( dirtyTangents && geometry.hasTangents ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ vertexTangents = face.vertexTangents;
+
+ t1 = vertexTangents[ 0 ];
+ t2 = vertexTangents[ 1 ];
+ t3 = vertexTangents[ 2 ];
+
+ tangentArray[ offset_tangent ] = t1.x;
+ tangentArray[ offset_tangent + 1 ] = t1.y;
+ tangentArray[ offset_tangent + 2 ] = t1.z;
+ tangentArray[ offset_tangent + 3 ] = t1.w;
+
+ tangentArray[ offset_tangent + 4 ] = t2.x;
+ tangentArray[ offset_tangent + 5 ] = t2.y;
+ tangentArray[ offset_tangent + 6 ] = t2.z;
+ tangentArray[ offset_tangent + 7 ] = t2.w;
+
+ tangentArray[ offset_tangent + 8 ] = t3.x;
+ tangentArray[ offset_tangent + 9 ] = t3.y;
+ tangentArray[ offset_tangent + 10 ] = t3.z;
+ tangentArray[ offset_tangent + 11 ] = t3.w;
+
+ offset_tangent += 12;
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, tangentArray, hint );
+
+ }
+
+ if ( dirtyNormals && normalType ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ vertexNormals = face.vertexNormals;
+ faceNormal = face.normal;
+
+ if ( vertexNormals.length === 3 && needsSmoothNormals ) {
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ vn = vertexNormals[ i ];
+
+ normalArray[ offset_normal ] = vn.x;
+ normalArray[ offset_normal + 1 ] = vn.y;
+ normalArray[ offset_normal + 2 ] = vn.z;
+
+ offset_normal += 3;
+
+ }
+
+ } else {
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ normalArray[ offset_normal ] = faceNormal.x;
+ normalArray[ offset_normal + 1 ] = faceNormal.y;
+ normalArray[ offset_normal + 2 ] = faceNormal.z;
+
+ offset_normal += 3;
+
+ }
+
+ }
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, normalArray, hint );
+
+ }
+
+ if ( dirtyUvs && obj_uvs && uvType ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ fi = chunk_faces3[ f ];
+
+ uv = obj_uvs[ fi ];
+
+ if ( uv === undefined ) continue;
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ uvi = uv[ i ];
+
+ uvArray[ offset_uv ] = uvi.x;
+ uvArray[ offset_uv + 1 ] = uvi.y;
+
+ offset_uv += 2;
+
+ }
+
+ }
+
+ if ( offset_uv > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, uvArray, hint );
+
+ }
+
+ }
+
+ if ( dirtyUvs && obj_uvs2 && uvType ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ fi = chunk_faces3[ f ];
+
+ uv2 = obj_uvs2[ fi ];
+
+ if ( uv2 === undefined ) continue;
+
+ for ( i = 0; i < 3; i ++ ) {
+
+ uv2i = uv2[ i ];
+
+ uv2Array[ offset_uv2 ] = uv2i.x;
+ uv2Array[ offset_uv2 + 1 ] = uv2i.y;
+
+ offset_uv2 += 2;
+
+ }
+
+ }
+
+ if ( offset_uv2 > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, uv2Array, hint );
+
+ }
+
+ }
+
+ if ( dirtyElements ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ faceArray[ offset_face ] = vertexIndex;
+ faceArray[ offset_face + 1 ] = vertexIndex + 1;
+ faceArray[ offset_face + 2 ] = vertexIndex + 2;
+
+ offset_face += 3;
+
+ lineArray[ offset_line ] = vertexIndex;
+ lineArray[ offset_line + 1 ] = vertexIndex + 1;
+
+ lineArray[ offset_line + 2 ] = vertexIndex;
+ lineArray[ offset_line + 3 ] = vertexIndex + 2;
+
+ lineArray[ offset_line + 4 ] = vertexIndex + 1;
+ lineArray[ offset_line + 5 ] = vertexIndex + 2;
+
+ offset_line += 6;
+
+ vertexIndex += 3;
+
+ }
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer );
+ _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faceArray, hint );
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer );
+ _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, lineArray, hint );
+
+ }
+
+ if ( customAttributes ) {
+
+ for ( i = 0, il = customAttributes.length; i < il; i ++ ) {
+
+ customAttribute = customAttributes[ i ];
+
+ if ( ! customAttribute.__original.needsUpdate ) continue;
+
+ offset_custom = 0;
+ offset_customSrc = 0;
+
+ if ( customAttribute.size === 1 ) {
+
+ if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ customAttribute.array[ offset_custom ] = customAttribute.value[ face.a ];
+ customAttribute.array[ offset_custom + 1 ] = customAttribute.value[ face.b ];
+ customAttribute.array[ offset_custom + 2 ] = customAttribute.value[ face.c ];
+
+ offset_custom += 3;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faces" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ customAttribute.array[ offset_custom ] = value;
+ customAttribute.array[ offset_custom + 1 ] = value;
+ customAttribute.array[ offset_custom + 2 ] = value;
+
+ offset_custom += 3;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 2 ) {
+
+ if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ v1 = customAttribute.value[ face.a ];
+ v2 = customAttribute.value[ face.b ];
+ v3 = customAttribute.value[ face.c ];
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+
+ customAttribute.array[ offset_custom + 2 ] = v2.x;
+ customAttribute.array[ offset_custom + 3 ] = v2.y;
+
+ customAttribute.array[ offset_custom + 4 ] = v3.x;
+ customAttribute.array[ offset_custom + 5 ] = v3.y;
+
+ offset_custom += 6;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faces" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value;
+ v2 = value;
+ v3 = value;
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+
+ customAttribute.array[ offset_custom + 2 ] = v2.x;
+ customAttribute.array[ offset_custom + 3 ] = v2.y;
+
+ customAttribute.array[ offset_custom + 4 ] = v3.x;
+ customAttribute.array[ offset_custom + 5 ] = v3.y;
+
+ offset_custom += 6;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 3 ) {
+
+ var pp;
+
+ if ( customAttribute.type === "c" ) {
+
+ pp = [ "r", "g", "b" ];
+
+ } else {
+
+ pp = [ "x", "y", "z" ];
+
+ }
+
+ if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ v1 = customAttribute.value[ face.a ];
+ v2 = customAttribute.value[ face.b ];
+ v3 = customAttribute.value[ face.c ];
+
+ customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+ offset_custom += 9;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faces" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value;
+ v2 = value;
+ v3 = value;
+
+ customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+ offset_custom += 9;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faceVertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value[ 0 ];
+ v2 = value[ 1 ];
+ v3 = value[ 2 ];
+
+ customAttribute.array[ offset_custom ] = v1[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 1 ] = v1[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 2 ] = v1[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 3 ] = v2[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 4 ] = v2[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 5 ] = v2[ pp[ 2 ] ];
+
+ customAttribute.array[ offset_custom + 6 ] = v3[ pp[ 0 ] ];
+ customAttribute.array[ offset_custom + 7 ] = v3[ pp[ 1 ] ];
+ customAttribute.array[ offset_custom + 8 ] = v3[ pp[ 2 ] ];
+
+ offset_custom += 9;
+
+ }
+
+ }
+
+ } else if ( customAttribute.size === 4 ) {
+
+ if ( customAttribute.boundTo === undefined || customAttribute.boundTo === "vertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ face = obj_faces[ chunk_faces3[ f ] ];
+
+ v1 = customAttribute.value[ face.a ];
+ v2 = customAttribute.value[ face.b ];
+ v3 = customAttribute.value[ face.c ];
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+ customAttribute.array[ offset_custom + 2 ] = v1.z;
+ customAttribute.array[ offset_custom + 3 ] = v1.w;
+
+ customAttribute.array[ offset_custom + 4 ] = v2.x;
+ customAttribute.array[ offset_custom + 5 ] = v2.y;
+ customAttribute.array[ offset_custom + 6 ] = v2.z;
+ customAttribute.array[ offset_custom + 7 ] = v2.w;
+
+ customAttribute.array[ offset_custom + 8 ] = v3.x;
+ customAttribute.array[ offset_custom + 9 ] = v3.y;
+ customAttribute.array[ offset_custom + 10 ] = v3.z;
+ customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+ offset_custom += 12;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faces" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value;
+ v2 = value;
+ v3 = value;
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+ customAttribute.array[ offset_custom + 2 ] = v1.z;
+ customAttribute.array[ offset_custom + 3 ] = v1.w;
+
+ customAttribute.array[ offset_custom + 4 ] = v2.x;
+ customAttribute.array[ offset_custom + 5 ] = v2.y;
+ customAttribute.array[ offset_custom + 6 ] = v2.z;
+ customAttribute.array[ offset_custom + 7 ] = v2.w;
+
+ customAttribute.array[ offset_custom + 8 ] = v3.x;
+ customAttribute.array[ offset_custom + 9 ] = v3.y;
+ customAttribute.array[ offset_custom + 10 ] = v3.z;
+ customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+ offset_custom += 12;
+
+ }
+
+ } else if ( customAttribute.boundTo === "faceVertices" ) {
+
+ for ( f = 0, fl = chunk_faces3.length; f < fl; f ++ ) {
+
+ value = customAttribute.value[ chunk_faces3[ f ] ];
+
+ v1 = value[ 0 ];
+ v2 = value[ 1 ];
+ v3 = value[ 2 ];
+
+ customAttribute.array[ offset_custom ] = v1.x;
+ customAttribute.array[ offset_custom + 1 ] = v1.y;
+ customAttribute.array[ offset_custom + 2 ] = v1.z;
+ customAttribute.array[ offset_custom + 3 ] = v1.w;
+
+ customAttribute.array[ offset_custom + 4 ] = v2.x;
+ customAttribute.array[ offset_custom + 5 ] = v2.y;
+ customAttribute.array[ offset_custom + 6 ] = v2.z;
+ customAttribute.array[ offset_custom + 7 ] = v2.w;
+
+ customAttribute.array[ offset_custom + 8 ] = v3.x;
+ customAttribute.array[ offset_custom + 9 ] = v3.y;
+ customAttribute.array[ offset_custom + 10 ] = v3.z;
+ customAttribute.array[ offset_custom + 11 ] = v3.w;
+
+ offset_custom += 12;
+
+ }
+
+ }
+
+ }
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, customAttribute.buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, customAttribute.array, hint );
+
+ }
+
+ }
+
+ if ( dispose ) {
+
+ delete geometryGroup.__inittedArrays;
+ delete geometryGroup.__colorArray;
+ delete geometryGroup.__normalArray;
+ delete geometryGroup.__tangentArray;
+ delete geometryGroup.__uvArray;
+ delete geometryGroup.__uv2Array;
+ delete geometryGroup.__faceArray;
+ delete geometryGroup.__vertexArray;
+ delete geometryGroup.__lineArray;
+ delete geometryGroup.__skinIndexArray;
+ delete geometryGroup.__skinWeightArray;
+
+ }
+
+ };
+
+ function setDirectBuffers ( geometry, hint, dispose ) {
+
+ var attributes = geometry.attributes;
+
+ var attributeName, attributeItem;
+
+ for ( attributeName in attributes ) {
+
+ attributeItem = attributes[ attributeName ];
+
+ if ( attributeItem.needsUpdate ) {
+
+ if ( attributeName === 'index' ) {
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.buffer );
+ _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, attributeItem.array, hint );
+
+ } else {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, attributeItem.array, hint );
+
+ }
+
+ attributeItem.needsUpdate = false;
+
+ }
+
+ if ( dispose && ! attributeItem.dynamic ) {
+
+ attributeItem.array = null;
+
+ }
+
+ }
+
+ };
+
+ // Buffer rendering
+
+ this.renderBufferImmediate = function ( object, program, material ) {
+
+ if ( object.hasPositions && ! object.__webglVertexBuffer ) object.__webglVertexBuffer = _gl.createBuffer();
+ if ( object.hasNormals && ! object.__webglNormalBuffer ) object.__webglNormalBuffer = _gl.createBuffer();
+ if ( object.hasUvs && ! object.__webglUvBuffer ) object.__webglUvBuffer = _gl.createBuffer();
+ if ( object.hasColors && ! object.__webglColorBuffer ) object.__webglColorBuffer = _gl.createBuffer();
+
+ if ( object.hasPositions ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglVertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW );
+ _gl.enableVertexAttribArray( program.attributes.position );
+ _gl.vertexAttribPointer( program.attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( object.hasNormals ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglNormalBuffer );
+
+ if ( material.shading === THREE.FlatShading ) {
+
+ var nx, ny, nz,
+ nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz,
+ normalArray,
+ i, il = object.count * 3;
+
+ for( i = 0; i < il; i += 9 ) {
+
+ normalArray = object.normalArray;
+
+ nax = normalArray[ i ];
+ nay = normalArray[ i + 1 ];
+ naz = normalArray[ i + 2 ];
+
+ nbx = normalArray[ i + 3 ];
+ nby = normalArray[ i + 4 ];
+ nbz = normalArray[ i + 5 ];
+
+ ncx = normalArray[ i + 6 ];
+ ncy = normalArray[ i + 7 ];
+ ncz = normalArray[ i + 8 ];
+
+ nx = ( nax + nbx + ncx ) / 3;
+ ny = ( nay + nby + ncy ) / 3;
+ nz = ( naz + nbz + ncz ) / 3;
+
+ normalArray[ i ] = nx;
+ normalArray[ i + 1 ] = ny;
+ normalArray[ i + 2 ] = nz;
+
+ normalArray[ i + 3 ] = nx;
+ normalArray[ i + 4 ] = ny;
+ normalArray[ i + 5 ] = nz;
+
+ normalArray[ i + 6 ] = nx;
+ normalArray[ i + 7 ] = ny;
+ normalArray[ i + 8 ] = nz;
+
+ }
+
+ }
+
+ _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW );
+ _gl.enableVertexAttribArray( program.attributes.normal );
+ _gl.vertexAttribPointer( program.attributes.normal, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( object.hasUvs && material.map ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglUvBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW );
+ _gl.enableVertexAttribArray( program.attributes.uv );
+ _gl.vertexAttribPointer( program.attributes.uv, 2, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( object.hasColors && material.vertexColors !== THREE.NoColors ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, object.__webglColorBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW );
+ _gl.enableVertexAttribArray( program.attributes.color );
+ _gl.vertexAttribPointer( program.attributes.color, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ _gl.drawArrays( _gl.TRIANGLES, 0, object.count );
+
+ object.count = 0;
+
+ };
+
+ this.renderBufferDirect = function ( camera, lights, fog, material, geometry, object ) {
+
+ if ( material.visible === false ) return;
+
+ var linewidth, a, attribute;
+ var attributeItem, attributeName, attributePointer, attributeSize;
+
+ var program = setProgram( camera, lights, fog, material, object );
+
+ var programAttributes = program.attributes;
+ var geometryAttributes = geometry.attributes;
+
+ var updateBuffers = false,
+ wireframeBit = material.wireframe ? 1 : 0,
+ geometryHash = ( geometry.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit;
+
+ if ( geometryHash !== _currentGeometryGroupHash ) {
+
+ _currentGeometryGroupHash = geometryHash;
+ updateBuffers = true;
+
+ }
+
+ if ( updateBuffers ) {
+
+ disableAttributes();
+
+ }
+
+ // render mesh
+
+ if ( object instanceof THREE.Mesh ) {
+
+ var index = geometryAttributes[ "index" ];
+
+ // indexed triangles
+
+ if ( index ) {
+
+ var offsets = geometry.offsets;
+
+ // if there is more than 1 chunk
+ // must set attribute pointers to use new offsets for each chunk
+ // even if geometry and materials didn't change
+
+ if ( offsets.length > 1 ) updateBuffers = true;
+
+ for ( var i = 0, il = offsets.length; i < il; i ++ ) {
+
+ var startIndex = offsets[ i ].index;
+
+ if ( updateBuffers ) {
+
+ for ( attributeName in programAttributes ) {
+
+ attributePointer = programAttributes[ attributeName ];
+ attributeItem = geometryAttributes[ attributeName ];
+
+ if ( attributePointer >= 0 ) {
+
+ if ( attributeItem ) {
+
+ attributeSize = attributeItem.itemSize;
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ enableAttribute( attributePointer );
+ _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, startIndex * attributeSize * 4 ); // 4 bytes per Float32
+
+ } else if ( material.defaultAttributeValues ) {
+
+ if ( material.defaultAttributeValues[ attributeName ].length === 2 ) {
+
+ _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) {
+
+ _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ // indices
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, index.buffer );
+
+ }
+
+ // render indexed triangles
+
+ _gl.drawElements( _gl.TRIANGLES, offsets[ i ].count, _gl.UNSIGNED_SHORT, offsets[ i ].start * 2 ); // 2 bytes per Uint16
+
+ _this.info.render.calls ++;
+ _this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared
+ _this.info.render.faces += offsets[ i ].count / 3;
+
+ }
+
+ // non-indexed triangles
+
+ } else {
+
+ if ( updateBuffers ) {
+
+ for ( attributeName in programAttributes ) {
+
+ if ( attributeName === 'index') continue;
+
+ attributePointer = programAttributes[ attributeName ];
+ attributeItem = geometryAttributes[ attributeName ];
+
+ if ( attributePointer >= 0 ) {
+
+ if ( attributeItem ) {
+
+ attributeSize = attributeItem.itemSize;
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ enableAttribute( attributePointer );
+ _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) {
+
+ if ( material.defaultAttributeValues[ attributeName ].length === 2 ) {
+
+ _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) {
+
+ _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ var position = geometry.attributes[ "position" ];
+
+ // render non-indexed triangles
+
+ _gl.drawArrays( _gl.TRIANGLES, 0, position.numItems / 3 );
+
+ _this.info.render.calls ++;
+ _this.info.render.vertices += position.numItems / 3;
+ _this.info.render.faces += position.numItems / 3 / 3;
+
+ }
+
+ // render particles
+
+ } else if ( object instanceof THREE.ParticleSystem ) {
+
+ if ( updateBuffers ) {
+
+ for ( attributeName in programAttributes ) {
+
+ attributePointer = programAttributes[ attributeName ];
+ attributeItem = geometryAttributes[ attributeName ];
+
+ if ( attributePointer >= 0 ) {
+
+ if ( attributeItem ) {
+
+ attributeSize = attributeItem.itemSize;
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ enableAttribute( attributePointer );
+ _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) {
+
+ if ( material.defaultAttributeValues[ attributeName ].length === 2 ) {
+
+ _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) {
+
+ _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ var position = geometryAttributes[ "position" ];
+
+ // render particles
+
+ _gl.drawArrays( _gl.POINTS, 0, position.numItems / 3 );
+
+ _this.info.render.calls ++;
+ _this.info.render.points += position.numItems / 3;
+
+ }
+
+ } else if ( object instanceof THREE.Line ) {
+
+ if ( updateBuffers ) {
+
+ for ( attributeName in programAttributes ) {
+
+ attributePointer = programAttributes[ attributeName ];
+ attributeItem = geometryAttributes[ attributeName ];
+
+ if ( attributePointer >= 0 ) {
+
+ if ( attributeItem ) {
+
+ attributeSize = attributeItem.itemSize;
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attributeItem.buffer );
+ enableAttribute( attributePointer );
+ _gl.vertexAttribPointer( attributePointer, attributeSize, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues && material.defaultAttributeValues[ attributeName ] ) {
+
+ if ( material.defaultAttributeValues[ attributeName ].length === 2 ) {
+
+ _gl.vertexAttrib2fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ } else if ( material.defaultAttributeValues[ attributeName ].length === 3 ) {
+
+ _gl.vertexAttrib3fv( attributePointer, material.defaultAttributeValues[ attributeName ] );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ // render lines
+
+ var primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES;
+
+ setLineWidth( material.linewidth );
+
+ var position = geometryAttributes[ "position" ];
+
+ _gl.drawArrays( primitives, 0, position.numItems / 3 );
+
+ _this.info.render.calls ++;
+ _this.info.render.points += position.numItems;
+
+ }
+
+ }
+
+ };
+
+ this.renderBuffer = function ( camera, lights, fog, material, geometryGroup, object ) {
+
+ if ( material.visible === false ) return;
+
+ var linewidth, a, attribute, i, il;
+
+ var program = setProgram( camera, lights, fog, material, object );
+
+ var attributes = program.attributes;
+
+ var updateBuffers = false,
+ wireframeBit = material.wireframe ? 1 : 0,
+ geometryGroupHash = ( geometryGroup.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit;
+
+ if ( geometryGroupHash !== _currentGeometryGroupHash ) {
+
+ _currentGeometryGroupHash = geometryGroupHash;
+ updateBuffers = true;
+
+ }
+
+ if ( updateBuffers ) {
+
+ disableAttributes();
+
+ }
+
+ // vertices
+
+ if ( !material.morphTargets && attributes.position >= 0 ) {
+
+ if ( updateBuffers ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+ enableAttribute( attributes.position );
+ _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ } else {
+
+ if ( object.morphTargetBase ) {
+
+ setupMorphTargets( material, geometryGroup, object );
+
+ }
+
+ }
+
+
+ if ( updateBuffers ) {
+
+ // custom attributes
+
+ // Use the per-geometryGroup custom attribute arrays which are setup in initMeshBuffers
+
+ if ( geometryGroup.__webglCustomAttributesList ) {
+
+ for ( i = 0, il = geometryGroup.__webglCustomAttributesList.length; i < il; i ++ ) {
+
+ attribute = geometryGroup.__webglCustomAttributesList[ i ];
+
+ if ( attributes[ attribute.buffer.belongsToAttribute ] >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, attribute.buffer );
+ enableAttribute( attributes[ attribute.buffer.belongsToAttribute ] );
+ _gl.vertexAttribPointer( attributes[ attribute.buffer.belongsToAttribute ], attribute.size, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ }
+
+ }
+
+
+ // colors
+
+ if ( attributes.color >= 0 ) {
+
+ if ( object.geometry.colors.length > 0 || object.geometry.faces.length > 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglColorBuffer );
+ enableAttribute( attributes.color );
+ _gl.vertexAttribPointer( attributes.color, 3, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues ) {
+
+
+ _gl.vertexAttrib3fv( attributes.color, material.defaultAttributeValues.color );
+
+ }
+
+ }
+
+ // normals
+
+ if ( attributes.normal >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglNormalBuffer );
+ enableAttribute( attributes.normal );
+ _gl.vertexAttribPointer( attributes.normal, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ // tangents
+
+ if ( attributes.tangent >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglTangentBuffer );
+ enableAttribute( attributes.tangent );
+ _gl.vertexAttribPointer( attributes.tangent, 4, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ // uvs
+
+ if ( attributes.uv >= 0 ) {
+
+ if ( object.geometry.faceVertexUvs[0] ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUVBuffer );
+ enableAttribute( attributes.uv );
+ _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues ) {
+
+
+ _gl.vertexAttrib2fv( attributes.uv, material.defaultAttributeValues.uv );
+
+ }
+
+ }
+
+ if ( attributes.uv2 >= 0 ) {
+
+ if ( object.geometry.faceVertexUvs[1] ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglUV2Buffer );
+ enableAttribute( attributes.uv2 );
+ _gl.vertexAttribPointer( attributes.uv2, 2, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( material.defaultAttributeValues ) {
+
+
+ _gl.vertexAttrib2fv( attributes.uv2, material.defaultAttributeValues.uv2 );
+
+ }
+
+ }
+
+ if ( material.skinning &&
+ attributes.skinIndex >= 0 && attributes.skinWeight >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinIndicesBuffer );
+ enableAttribute( attributes.skinIndex );
+ _gl.vertexAttribPointer( attributes.skinIndex, 4, _gl.FLOAT, false, 0, 0 );
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglSkinWeightsBuffer );
+ enableAttribute( attributes.skinWeight );
+ _gl.vertexAttribPointer( attributes.skinWeight, 4, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ // line distances
+
+ if ( attributes.lineDistance >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglLineDistanceBuffer );
+ enableAttribute( attributes.lineDistance );
+ _gl.vertexAttribPointer( attributes.lineDistance, 1, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ }
+
+ // render mesh
+
+ if ( object instanceof THREE.Mesh ) {
+
+ // wireframe
+
+ if ( material.wireframe ) {
+
+ setLineWidth( material.wireframeLinewidth );
+
+ if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglLineBuffer );
+ _gl.drawElements( _gl.LINES, geometryGroup.__webglLineCount, _gl.UNSIGNED_SHORT, 0 );
+
+ // triangles
+
+ } else {
+
+ if ( updateBuffers ) _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, geometryGroup.__webglFaceBuffer );
+ _gl.drawElements( _gl.TRIANGLES, geometryGroup.__webglFaceCount, _gl.UNSIGNED_SHORT, 0 );
+
+ }
+
+ _this.info.render.calls ++;
+ _this.info.render.vertices += geometryGroup.__webglFaceCount;
+ _this.info.render.faces += geometryGroup.__webglFaceCount / 3;
+
+ // render lines
+
+ } else if ( object instanceof THREE.Line ) {
+
+ var primitives = ( object.type === THREE.LineStrip ) ? _gl.LINE_STRIP : _gl.LINES;
+
+ setLineWidth( material.linewidth );
+
+ _gl.drawArrays( primitives, 0, geometryGroup.__webglLineCount );
+
+ _this.info.render.calls ++;
+
+ // render particles
+
+ } else if ( object instanceof THREE.ParticleSystem ) {
+
+ _gl.drawArrays( _gl.POINTS, 0, geometryGroup.__webglParticleCount );
+
+ _this.info.render.calls ++;
+ _this.info.render.points += geometryGroup.__webglParticleCount;
+
+ }
+
+ };
+
+ function enableAttribute( attribute ) {
+
+ if ( ! _enabledAttributes[ attribute ] ) {
+
+ _gl.enableVertexAttribArray( attribute );
+ _enabledAttributes[ attribute ] = true;
+
+ }
+
+ };
+
+ function disableAttributes() {
+
+ for ( var attribute in _enabledAttributes ) {
+
+ if ( _enabledAttributes[ attribute ] ) {
+
+ _gl.disableVertexAttribArray( attribute );
+ _enabledAttributes[ attribute ] = false;
+
+ }
+
+ }
+
+ };
+
+ function setupMorphTargets ( material, geometryGroup, object ) {
+
+ // set base
+
+ var attributes = material.program.attributes;
+
+ if ( object.morphTargetBase !== -1 && attributes.position >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ object.morphTargetBase ] );
+ enableAttribute( attributes.position );
+ _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+ } else if ( attributes.position >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglVertexBuffer );
+ enableAttribute( attributes.position );
+ _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( object.morphTargetForcedOrder.length ) {
+
+ // set forced order
+
+ var m = 0;
+ var order = object.morphTargetForcedOrder;
+ var influences = object.morphTargetInfluences;
+
+ while ( m < material.numSupportedMorphTargets && m < order.length ) {
+
+ if ( attributes[ "morphTarget" + m ] >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ order[ m ] ] );
+ enableAttribute( attributes[ "morphTarget" + m ] );
+ _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ order[ m ] ] );
+ enableAttribute( attributes[ "morphNormal" + m ] );
+ _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ object.__webglMorphTargetInfluences[ m ] = influences[ order[ m ] ];
+
+ m ++;
+ }
+
+ } else {
+
+ // find the most influencing
+
+ var influence, activeInfluenceIndices = [];
+ var influences = object.morphTargetInfluences;
+ var i, il = influences.length;
+
+ for ( i = 0; i < il; i ++ ) {
+
+ influence = influences[ i ];
+
+ if ( influence > 0 ) {
+
+ activeInfluenceIndices.push( [ influence, i ] );
+
+ }
+
+ }
+
+ if ( activeInfluenceIndices.length > material.numSupportedMorphTargets ) {
+
+ activeInfluenceIndices.sort( numericalSort );
+ activeInfluenceIndices.length = material.numSupportedMorphTargets;
+
+ } else if ( activeInfluenceIndices.length > material.numSupportedMorphNormals ) {
+
+ activeInfluenceIndices.sort( numericalSort );
+
+ } else if ( activeInfluenceIndices.length === 0 ) {
+
+ activeInfluenceIndices.push( [ 0, 0 ] );
+
+ };
+
+ var influenceIndex, m = 0;
+
+ while ( m < material.numSupportedMorphTargets ) {
+
+ if ( activeInfluenceIndices[ m ] ) {
+
+ influenceIndex = activeInfluenceIndices[ m ][ 1 ];
+
+ if ( attributes[ "morphTarget" + m ] >= 0 ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphTargetsBuffers[ influenceIndex ] );
+ enableAttribute( attributes[ "morphTarget" + m ] );
+ _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+
+ if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) {
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, geometryGroup.__webglMorphNormalsBuffers[ influenceIndex ] );
+ enableAttribute( attributes[ "morphNormal" + m ] );
+ _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+
+ }
+
+ object.__webglMorphTargetInfluences[ m ] = influences[ influenceIndex ];
+
+ } else {
+
+ /*
+ _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ if ( material.morphNormals ) {
+
+ _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 );
+
+ }
+ */
+
+ object.__webglMorphTargetInfluences[ m ] = 0;
+
+ }
+
+ m ++;
+
+ }
+
+ }
+
+ // load updated influences uniform
+
+ if ( material.program.uniforms.morphTargetInfluences !== null ) {
+
+ _gl.uniform1fv( material.program.uniforms.morphTargetInfluences, object.__webglMorphTargetInfluences );
+
+ }
+
+ };
+
+ // Sorting
+
+ function painterSortStable ( a, b ) {
+
+ if ( a.z !== b.z ) {
+
+ return b.z - a.z;
+
+ } else {
+
+ return a.id - b.id;
+
+ }
+
+ };
+
+ function numericalSort ( a, b ) {
+
+ return b[ 0 ] - a[ 0 ];
+
+ };
+
+
+ // Rendering
+
+ this.render = function ( scene, camera, renderTarget, forceClear ) {
+
+ if ( camera instanceof THREE.Camera === false ) {
+
+ console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
+ return;
+
+ }
+
+ var i, il,
+
+ webglObject, object,
+ renderList,
+
+ lights = scene.__lights,
+ fog = scene.fog;
+
+ // reset caching for this frame
+
+ _currentMaterialId = -1;
+ _lightsNeedUpdate = true;
+
+ // update scene graph
+
+ if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+ // update camera matrices and frustum
+
+ if ( camera.parent === undefined ) camera.updateMatrixWorld();
+
+ camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+
+ _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+ _frustum.setFromMatrix( _projScreenMatrix );
+
+ // update WebGL objects
+
+ if ( this.autoUpdateObjects ) this.initWebGLObjects( scene );
+
+ // custom render plugins (pre pass)
+
+ renderPlugins( this.renderPluginsPre, scene, camera );
+
+ //
+
+ _this.info.render.calls = 0;
+ _this.info.render.vertices = 0;
+ _this.info.render.faces = 0;
+ _this.info.render.points = 0;
+
+ this.setRenderTarget( renderTarget );
+
+ if ( this.autoClear || forceClear ) {
+
+ this.clear( this.autoClearColor, this.autoClearDepth, this.autoClearStencil );
+
+ }
+
+ // set matrices for regular objects (frustum culled)
+
+ renderList = scene.__webglObjects;
+
+ for ( i = 0, il = renderList.length; i < il; i ++ ) {
+
+ webglObject = renderList[ i ];
+ object = webglObject.object;
+
+ webglObject.id = i;
+ webglObject.render = false;
+
+ if ( object.visible ) {
+
+ if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) {
+
+ setupMatrices( object, camera );
+
+ unrollBufferMaterial( webglObject );
+
+ webglObject.render = true;
+
+ if ( this.sortObjects === true ) {
+
+ if ( object.renderDepth !== null ) {
+
+ webglObject.z = object.renderDepth;
+
+ } else {
+
+ _vector3.getPositionFromMatrix( object.matrixWorld );
+ _vector3.applyProjection( _projScreenMatrix );
+
+ webglObject.z = _vector3.z;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( this.sortObjects ) {
+
+ renderList.sort( painterSortStable );
+
+ }
+
+ // set matrices for immediate objects
+
+ renderList = scene.__webglObjectsImmediate;
+
+ for ( i = 0, il = renderList.length; i < il; i ++ ) {
+
+ webglObject = renderList[ i ];
+ object = webglObject.object;
+
+ if ( object.visible ) {
+
+ setupMatrices( object, camera );
+
+ unrollImmediateBufferMaterial( webglObject );
+
+ }
+
+ }
+
+ if ( scene.overrideMaterial ) {
+
+ var material = scene.overrideMaterial;
+
+ this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+ this.setDepthTest( material.depthTest );
+ this.setDepthWrite( material.depthWrite );
+ setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+ renderObjects( scene.__webglObjects, false, "", camera, lights, fog, true, material );
+ renderObjectsImmediate( scene.__webglObjectsImmediate, "", camera, lights, fog, false, material );
+
+ } else {
+
+ var material = null;
+
+ // opaque pass (front-to-back order)
+
+ this.setBlending( THREE.NoBlending );
+
+ renderObjects( scene.__webglObjects, true, "opaque", camera, lights, fog, false, material );
+ renderObjectsImmediate( scene.__webglObjectsImmediate, "opaque", camera, lights, fog, false, material );
+
+ // transparent pass (back-to-front order)
+
+ renderObjects( scene.__webglObjects, false, "transparent", camera, lights, fog, true, material );
+ renderObjectsImmediate( scene.__webglObjectsImmediate, "transparent", camera, lights, fog, true, material );
+
+ }
+
+ // custom render plugins (post pass)
+
+ renderPlugins( this.renderPluginsPost, scene, camera );
+
+
+ // Generate mipmap if we're using any kind of mipmap filtering
+
+ if ( renderTarget && renderTarget.generateMipmaps && renderTarget.minFilter !== THREE.NearestFilter && renderTarget.minFilter !== THREE.LinearFilter ) {
+
+ updateRenderTargetMipmap( renderTarget );
+
+ }
+
+ // Ensure depth buffer writing is enabled so it can be cleared on next render
+
+ this.setDepthTest( true );
+ this.setDepthWrite( true );
+
+ // _gl.finish();
+
+ };
+
+ function renderPlugins( plugins, scene, camera ) {
+
+ if ( ! plugins.length ) return;
+
+ for ( var i = 0, il = plugins.length; i < il; i ++ ) {
+
+ // reset state for plugin (to start from clean slate)
+
+ _currentProgram = null;
+ _currentCamera = null;
+
+ _oldBlending = -1;
+ _oldDepthTest = -1;
+ _oldDepthWrite = -1;
+ _oldDoubleSided = -1;
+ _oldFlipSided = -1;
+ _currentGeometryGroupHash = -1;
+ _currentMaterialId = -1;
+
+ _lightsNeedUpdate = true;
+
+ plugins[ i ].render( scene, camera, _currentWidth, _currentHeight );
+
+ // reset state after plugin (anything could have changed)
+
+ _currentProgram = null;
+ _currentCamera = null;
+
+ _oldBlending = -1;
+ _oldDepthTest = -1;
+ _oldDepthWrite = -1;
+ _oldDoubleSided = -1;
+ _oldFlipSided = -1;
+ _currentGeometryGroupHash = -1;
+ _currentMaterialId = -1;
+
+ _lightsNeedUpdate = true;
+
+ }
+
+ };
+
+ function renderObjects ( renderList, reverse, materialType, camera, lights, fog, useBlending, overrideMaterial ) {
+
+ var webglObject, object, buffer, material, start, end, delta;
+
+ if ( reverse ) {
+
+ start = renderList.length - 1;
+ end = -1;
+ delta = -1;
+
+ } else {
+
+ start = 0;
+ end = renderList.length;
+ delta = 1;
+ }
+
+ for ( var i = start; i !== end; i += delta ) {
+
+ webglObject = renderList[ i ];
+
+ if ( webglObject.render ) {
+
+ object = webglObject.object;
+ buffer = webglObject.buffer;
+
+ if ( overrideMaterial ) {
+
+ material = overrideMaterial;
+
+ } else {
+
+ material = webglObject[ materialType ];
+
+ if ( ! material ) continue;
+
+ if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+
+ _this.setDepthTest( material.depthTest );
+ _this.setDepthWrite( material.depthWrite );
+ setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+ }
+
+ _this.setMaterialFaces( material );
+
+ if ( buffer instanceof THREE.BufferGeometry ) {
+
+ _this.renderBufferDirect( camera, lights, fog, material, buffer, object );
+
+ } else {
+
+ _this.renderBuffer( camera, lights, fog, material, buffer, object );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ function renderObjectsImmediate ( renderList, materialType, camera, lights, fog, useBlending, overrideMaterial ) {
+
+ var webglObject, object, material, program;
+
+ for ( var i = 0, il = renderList.length; i < il; i ++ ) {
+
+ webglObject = renderList[ i ];
+ object = webglObject.object;
+
+ if ( object.visible ) {
+
+ if ( overrideMaterial ) {
+
+ material = overrideMaterial;
+
+ } else {
+
+ material = webglObject[ materialType ];
+
+ if ( ! material ) continue;
+
+ if ( useBlending ) _this.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+
+ _this.setDepthTest( material.depthTest );
+ _this.setDepthWrite( material.depthWrite );
+ setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+ }
+
+ _this.renderImmediateObject( camera, lights, fog, material, object );
+
+ }
+
+ }
+
+ };
+
+ this.renderImmediateObject = function ( camera, lights, fog, material, object ) {
+
+ var program = setProgram( camera, lights, fog, material, object );
+
+ _currentGeometryGroupHash = -1;
+
+ _this.setMaterialFaces( material );
+
+ if ( object.immediateRenderCallback ) {
+
+ object.immediateRenderCallback( program, _gl, _frustum );
+
+ } else {
+
+ object.render( function( object ) { _this.renderBufferImmediate( object, program, material ); } );
+
+ }
+
+ };
+
+ function unrollImmediateBufferMaterial ( globject ) {
+
+ var object = globject.object,
+ material = object.material;
+
+ if ( material.transparent ) {
+
+ globject.transparent = material;
+ globject.opaque = null;
+
+ } else {
+
+ globject.opaque = material;
+ globject.transparent = null;
+
+ }
+
+ };
+
+ function unrollBufferMaterial ( globject ) {
+
+ var object = globject.object,
+ buffer = globject.buffer,
+ material, materialIndex, meshMaterial;
+
+ meshMaterial = object.material;
+
+ if ( meshMaterial instanceof THREE.MeshFaceMaterial ) {
+
+ materialIndex = buffer.materialIndex;
+
+ material = meshMaterial.materials[ materialIndex ];
+
+ if ( material.transparent ) {
+
+ globject.transparent = material;
+ globject.opaque = null;
+
+ } else {
+
+ globject.opaque = material;
+ globject.transparent = null;
+
+ }
+
+ } else {
+
+ material = meshMaterial;
+
+ if ( material ) {
+
+ if ( material.transparent ) {
+
+ globject.transparent = material;
+ globject.opaque = null;
+
+ } else {
+
+ globject.opaque = material;
+ globject.transparent = null;
+
+ }
+
+ }
+
+ }
+
+ };
+
+ // Geometry splitting
+
+ function sortFacesByMaterial ( geometry, material ) {
+
+ var f, fl, face, materialIndex, vertices,
+ groupHash, hash_map = {};
+
+ var numMorphTargets = geometry.morphTargets.length;
+ var numMorphNormals = geometry.morphNormals.length;
+
+ var usesFaceMaterial = material instanceof THREE.MeshFaceMaterial;
+
+ geometry.geometryGroups = {};
+
+ for ( f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
+
+ face = geometry.faces[ f ];
+ materialIndex = usesFaceMaterial ? face.materialIndex : 0;
+
+ if ( hash_map[ materialIndex ] === undefined ) {
+
+ hash_map[ materialIndex ] = { 'hash': materialIndex, 'counter': 0 };
+
+ }
+
+ groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter;
+
+ if ( geometry.geometryGroups[ groupHash ] === undefined ) {
+
+ geometry.geometryGroups[ groupHash ] = { 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals };
+
+ }
+
+ vertices = 3;
+
+ if ( geometry.geometryGroups[ groupHash ].vertices + vertices > 65535 ) {
+
+ hash_map[ materialIndex ].counter += 1;
+ groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter;
+
+ if ( geometry.geometryGroups[ groupHash ] === undefined ) {
+
+ geometry.geometryGroups[ groupHash ] = { 'faces3': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals };
+
+ }
+
+ }
+
+ geometry.geometryGroups[ groupHash ].faces3.push( f );
+ geometry.geometryGroups[ groupHash ].vertices += vertices;
+
+ }
+
+ geometry.geometryGroupsList = [];
+
+ for ( var g in geometry.geometryGroups ) {
+
+ geometry.geometryGroups[ g ].id = _geometryGroupCounter ++;
+
+ geometry.geometryGroupsList.push( geometry.geometryGroups[ g ] );
+
+ }
+
+ };
+
+ // Objects refresh
+
+ this.initWebGLObjects = function ( scene ) {
+
+ if ( !scene.__webglObjects ) {
+
+ scene.__webglObjects = [];
+ scene.__webglObjectsImmediate = [];
+ scene.__webglSprites = [];
+ scene.__webglFlares = [];
+
+ }
+
+ while ( scene.__objectsAdded.length ) {
+
+ addObject( scene.__objectsAdded[ 0 ], scene );
+ scene.__objectsAdded.splice( 0, 1 );
+
+ }
+
+ while ( scene.__objectsRemoved.length ) {
+
+ removeObject( scene.__objectsRemoved[ 0 ], scene );
+ scene.__objectsRemoved.splice( 0, 1 );
+
+ }
+
+ // update must be called after objects adding / removal
+
+ for ( var o = 0, ol = scene.__webglObjects.length; o < ol; o ++ ) {
+
+ var object = scene.__webglObjects[ o ].object;
+
+ // TODO: Remove this hack (WebGLRenderer refactoring)
+
+ if ( object.__webglInit === undefined ) {
+
+ if ( object.__webglActive !== undefined ) {
+
+ removeObject( object, scene );
+
+ }
+
+ addObject( object, scene );
+
+ }
+
+ updateObject( object );
+
+ }
+
+ };
+
+ // Objects adding
+
+ function addObject( object, scene ) {
+
+ var g, geometry, material, geometryGroup;
+
+ if ( object.__webglInit === undefined ) {
+
+ object.__webglInit = true;
+
+ object._modelViewMatrix = new THREE.Matrix4();
+ object._normalMatrix = new THREE.Matrix3();
+
+ if ( object.geometry !== undefined && object.geometry.__webglInit === undefined ) {
+
+ object.geometry.__webglInit = true;
+ object.geometry.addEventListener( 'dispose', onGeometryDispose );
+
+ }
+
+ geometry = object.geometry;
+
+ if ( geometry === undefined ) {
+
+ // fail silently for now
+
+ } else if ( geometry instanceof THREE.BufferGeometry ) {
+
+ initDirectBuffers( geometry );
+
+ } else if ( object instanceof THREE.Mesh ) {
+
+ material = object.material;
+
+ if ( geometry.geometryGroups === undefined ) {
+
+ sortFacesByMaterial( geometry, material );
+
+ }
+
+ // create separate VBOs per geometry chunk
+
+ for ( g in geometry.geometryGroups ) {
+
+ geometryGroup = geometry.geometryGroups[ g ];
+
+ // initialise VBO on the first access
+
+ if ( ! geometryGroup.__webglVertexBuffer ) {
+
+ createMeshBuffers( geometryGroup );
+ initMeshBuffers( geometryGroup, object );
+
+ geometry.verticesNeedUpdate = true;
+ geometry.morphTargetsNeedUpdate = true;
+ geometry.elementsNeedUpdate = true;
+ geometry.uvsNeedUpdate = true;
+ geometry.normalsNeedUpdate = true;
+ geometry.tangentsNeedUpdate = true;
+ geometry.colorsNeedUpdate = true;
+
+ }
+
+ }
+
+ } else if ( object instanceof THREE.Line ) {
+
+ if ( ! geometry.__webglVertexBuffer ) {
+
+ createLineBuffers( geometry );
+ initLineBuffers( geometry, object );
+
+ geometry.verticesNeedUpdate = true;
+ geometry.colorsNeedUpdate = true;
+ geometry.lineDistancesNeedUpdate = true;
+
+ }
+
+ } else if ( object instanceof THREE.ParticleSystem ) {
+
+ if ( ! geometry.__webglVertexBuffer ) {
+
+ createParticleBuffers( geometry );
+ initParticleBuffers( geometry, object );
+
+ geometry.verticesNeedUpdate = true;
+ geometry.colorsNeedUpdate = true;
+
+ }
+
+ }
+
+ }
+
+ if ( object.__webglActive === undefined ) {
+
+ if ( object instanceof THREE.Mesh ) {
+
+ geometry = object.geometry;
+
+ if ( geometry instanceof THREE.BufferGeometry ) {
+
+ addBuffer( scene.__webglObjects, geometry, object );
+
+ } else if ( geometry instanceof THREE.Geometry ) {
+
+ for ( g in geometry.geometryGroups ) {
+
+ geometryGroup = geometry.geometryGroups[ g ];
+
+ addBuffer( scene.__webglObjects, geometryGroup, object );
+
+ }
+
+ }
+
+ } else if ( object instanceof THREE.Line ||
+ object instanceof THREE.ParticleSystem ) {
+
+ geometry = object.geometry;
+ addBuffer( scene.__webglObjects, geometry, object );
+
+ } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
+
+ addBufferImmediate( scene.__webglObjectsImmediate, object );
+
+ } else if ( object instanceof THREE.Sprite ) {
+
+ scene.__webglSprites.push( object );
+
+ } else if ( object instanceof THREE.LensFlare ) {
+
+ scene.__webglFlares.push( object );
+
+ }
+
+ object.__webglActive = true;
+
+ }
+
+ };
+
+ function addBuffer( objlist, buffer, object ) {
+
+ objlist.push(
+ {
+ id: null,
+ buffer: buffer,
+ object: object,
+ opaque: null,
+ transparent: null,
+ z: 0
+ }
+ );
+
+ };
+
+ function addBufferImmediate( objlist, object ) {
+
+ objlist.push(
+ {
+ id: null,
+ object: object,
+ opaque: null,
+ transparent: null,
+ z: 0
+ }
+ );
+
+ };
+
+ // Objects updates
+
+ function updateObject( object ) {
+
+ var geometry = object.geometry,
+ geometryGroup, customAttributesDirty, material;
+
+ if ( geometry instanceof THREE.BufferGeometry ) {
+
+ setDirectBuffers( geometry, _gl.DYNAMIC_DRAW, !geometry.dynamic );
+
+ } else if ( object instanceof THREE.Mesh ) {
+
+ // check all geometry groups
+
+ for( var i = 0, il = geometry.geometryGroupsList.length; i < il; i ++ ) {
+
+ geometryGroup = geometry.geometryGroupsList[ i ];
+
+ material = getBufferMaterial( object, geometryGroup );
+
+ if ( geometry.buffersNeedUpdate ) {
+
+ initMeshBuffers( geometryGroup, object );
+
+ }
+
+ customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+ if ( geometry.verticesNeedUpdate || geometry.morphTargetsNeedUpdate || geometry.elementsNeedUpdate ||
+ geometry.uvsNeedUpdate || geometry.normalsNeedUpdate ||
+ geometry.colorsNeedUpdate || geometry.tangentsNeedUpdate || customAttributesDirty ) {
+
+ setMeshBuffers( geometryGroup, object, _gl.DYNAMIC_DRAW, !geometry.dynamic, material );
+
+ }
+
+ }
+
+ geometry.verticesNeedUpdate = false;
+ geometry.morphTargetsNeedUpdate = false;
+ geometry.elementsNeedUpdate = false;
+ geometry.uvsNeedUpdate = false;
+ geometry.normalsNeedUpdate = false;
+ geometry.colorsNeedUpdate = false;
+ geometry.tangentsNeedUpdate = false;
+
+ geometry.buffersNeedUpdate = false;
+
+ material.attributes && clearCustomAttributes( material );
+
+ } else if ( object instanceof THREE.Line ) {
+
+ material = getBufferMaterial( object, geometry );
+
+ customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+ if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || geometry.lineDistancesNeedUpdate || customAttributesDirty ) {
+
+ setLineBuffers( geometry, _gl.DYNAMIC_DRAW );
+
+ }
+
+ geometry.verticesNeedUpdate = false;
+ geometry.colorsNeedUpdate = false;
+ geometry.lineDistancesNeedUpdate = false;
+
+ material.attributes && clearCustomAttributes( material );
+
+
+ } else if ( object instanceof THREE.ParticleSystem ) {
+
+ material = getBufferMaterial( object, geometry );
+
+ customAttributesDirty = material.attributes && areCustomAttributesDirty( material );
+
+ if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || object.sortParticles || customAttributesDirty ) {
+
+ setParticleBuffers( geometry, _gl.DYNAMIC_DRAW, object );
+
+ }
+
+ geometry.verticesNeedUpdate = false;
+ geometry.colorsNeedUpdate = false;
+
+ material.attributes && clearCustomAttributes( material );
+
+ }
+
+ };
+
+ // Objects updates - custom attributes check
+
+ function areCustomAttributesDirty( material ) {
+
+ for ( var a in material.attributes ) {
+
+ if ( material.attributes[ a ].needsUpdate ) return true;
+
+ }
+
+ return false;
+
+ };
+
+ function clearCustomAttributes( material ) {
+
+ for ( var a in material.attributes ) {
+
+ material.attributes[ a ].needsUpdate = false;
+
+ }
+
+ };
+
+ // Objects removal
+
+ function removeObject( object, scene ) {
+
+ if ( object instanceof THREE.Mesh ||
+ object instanceof THREE.ParticleSystem ||
+ object instanceof THREE.Line ) {
+
+ removeInstances( scene.__webglObjects, object );
+
+ } else if ( object instanceof THREE.Sprite ) {
+
+ removeInstancesDirect( scene.__webglSprites, object );
+
+ } else if ( object instanceof THREE.LensFlare ) {
+
+ removeInstancesDirect( scene.__webglFlares, object );
+
+ } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) {
+
+ removeInstances( scene.__webglObjectsImmediate, object );
+
+ }
+
+ delete object.__webglActive;
+
+ };
+
+ function removeInstances( objlist, object ) {
+
+ for ( var o = objlist.length - 1; o >= 0; o -- ) {
+
+ if ( objlist[ o ].object === object ) {
+
+ objlist.splice( o, 1 );
+
+ }
+
+ }
+
+ };
+
+ function removeInstancesDirect( objlist, object ) {
+
+ for ( var o = objlist.length - 1; o >= 0; o -- ) {
+
+ if ( objlist[ o ] === object ) {
+
+ objlist.splice( o, 1 );
+
+ }
+
+ }
+
+ };
+
+ // Materials
+
+ this.initMaterial = function ( material, lights, fog, object ) {
+
+ material.addEventListener( 'dispose', onMaterialDispose );
+
+ var u, a, identifiers, i, parameters, maxLightCount, maxBones, maxShadows, shaderID;
+
+ if ( material instanceof THREE.MeshDepthMaterial ) {
+
+ shaderID = 'depth';
+
+ } else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+ shaderID = 'normal';
+
+ } else if ( material instanceof THREE.MeshBasicMaterial ) {
+
+ shaderID = 'basic';
+
+ } else if ( material instanceof THREE.MeshLambertMaterial ) {
+
+ shaderID = 'lambert';
+
+ } else if ( material instanceof THREE.MeshPhongMaterial ) {
+
+ shaderID = 'phong';
+
+ } else if ( material instanceof THREE.LineBasicMaterial ) {
+
+ shaderID = 'basic';
+
+ } else if ( material instanceof THREE.LineDashedMaterial ) {
+
+ shaderID = 'dashed';
+
+ } else if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+ shaderID = 'particle_basic';
+
+ }
+
+ if ( shaderID ) {
+
+ setMaterialShaders( material, THREE.ShaderLib[ shaderID ] );
+
+ }
+
+ // heuristics to create shader parameters according to lights in the scene
+ // (not to blow over maxLights budget)
+
+ maxLightCount = allocateLights( lights );
+
+ maxShadows = allocateShadows( lights );
+
+ maxBones = allocateBones( object );
+
+ parameters = {
+
+ map: !!material.map,
+ envMap: !!material.envMap,
+ lightMap: !!material.lightMap,
+ bumpMap: !!material.bumpMap,
+ normalMap: !!material.normalMap,
+ specularMap: !!material.specularMap,
+
+ vertexColors: material.vertexColors,
+
+ fog: fog,
+ useFog: material.fog,
+ fogExp: fog instanceof THREE.FogExp2,
+
+ sizeAttenuation: material.sizeAttenuation,
+
+ skinning: material.skinning,
+ maxBones: maxBones,
+ useVertexTexture: _supportsBoneTextures && object && object.useVertexTexture,
+
+ morphTargets: material.morphTargets,
+ morphNormals: material.morphNormals,
+ maxMorphTargets: this.maxMorphTargets,
+ maxMorphNormals: this.maxMorphNormals,
+
+ maxDirLights: maxLightCount.directional,
+ maxPointLights: maxLightCount.point,
+ maxSpotLights: maxLightCount.spot,
+ maxHemiLights: maxLightCount.hemi,
+
+ maxShadows: maxShadows,
+ shadowMapEnabled: this.shadowMapEnabled && object.receiveShadow,
+ shadowMapType: this.shadowMapType,
+ shadowMapDebug: this.shadowMapDebug,
+ shadowMapCascade: this.shadowMapCascade,
+
+ alphaTest: material.alphaTest,
+ metal: material.metal,
+ perPixel: material.perPixel,
+ wrapAround: material.wrapAround,
+ doubleSided: material.side === THREE.DoubleSide,
+ flipSided: material.side === THREE.BackSide
+
+ };
+
+ material.program = buildProgram( shaderID, material.fragmentShader, material.vertexShader, material.uniforms, material.attributes, material.defines, parameters, material.index0AttributeName );
+
+ var attributes = material.program.attributes;
+
+ if ( material.morphTargets ) {
+
+ material.numSupportedMorphTargets = 0;
+
+ var id, base = "morphTarget";
+
+ for ( i = 0; i < this.maxMorphTargets; i ++ ) {
+
+ id = base + i;
+
+ if ( attributes[ id ] >= 0 ) {
+
+ material.numSupportedMorphTargets ++;
+
+ }
+
+ }
+
+ }
+
+ if ( material.morphNormals ) {
+
+ material.numSupportedMorphNormals = 0;
+
+ var id, base = "morphNormal";
+
+ for ( i = 0; i < this.maxMorphNormals; i ++ ) {
+
+ id = base + i;
+
+ if ( attributes[ id ] >= 0 ) {
+
+ material.numSupportedMorphNormals ++;
+
+ }
+
+ }
+
+ }
+
+ material.uniformsList = [];
+
+ for ( u in material.uniforms ) {
+
+ material.uniformsList.push( [ material.uniforms[ u ], u ] );
+
+ }
+
+ };
+
+ function setMaterialShaders( material, shaders ) {
+
+ material.uniforms = THREE.UniformsUtils.clone( shaders.uniforms );
+ material.vertexShader = shaders.vertexShader;
+ material.fragmentShader = shaders.fragmentShader;
+
+ };
+
+ function setProgram( camera, lights, fog, material, object ) {
+
+ _usedTextureUnits = 0;
+
+ if ( material.needsUpdate ) {
+
+ if ( material.program ) deallocateMaterial( material );
+
+ _this.initMaterial( material, lights, fog, object );
+ material.needsUpdate = false;
+
+ }
+
+ if ( material.morphTargets ) {
+
+ if ( ! object.__webglMorphTargetInfluences ) {
+
+ object.__webglMorphTargetInfluences = new Float32Array( _this.maxMorphTargets );
+
+ }
+
+ }
+
+ var refreshMaterial = false;
+
+ var program = material.program,
+ p_uniforms = program.uniforms,
+ m_uniforms = material.uniforms;
+
+ if ( program !== _currentProgram ) {
+
+ _gl.useProgram( program );
+ _currentProgram = program;
+
+ refreshMaterial = true;
+
+ }
+
+ if ( material.id !== _currentMaterialId ) {
+
+ _currentMaterialId = material.id;
+ refreshMaterial = true;
+
+ }
+
+ if ( refreshMaterial || camera !== _currentCamera ) {
+
+ _gl.uniformMatrix4fv( p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements );
+
+ if ( camera !== _currentCamera ) _currentCamera = camera;
+
+ }
+
+ // skinning uniforms must be set even if material didn't change
+ // auto-setting of texture unit for bone texture must go before other textures
+ // not sure why, but otherwise weird things happen
+
+ if ( material.skinning ) {
+
+ if ( _supportsBoneTextures && object.useVertexTexture ) {
+
+ if ( p_uniforms.boneTexture !== null ) {
+
+ var textureUnit = getTextureUnit();
+
+ _gl.uniform1i( p_uniforms.boneTexture, textureUnit );
+ _this.setTexture( object.boneTexture, textureUnit );
+
+ }
+
+ if ( p_uniforms.boneTextureWidth !== null ) {
+
+ _gl.uniform1i( p_uniforms.boneTextureWidth, object.boneTextureWidth );
+
+ }
+
+ if ( p_uniforms.boneTextureHeight !== null ) {
+
+ _gl.uniform1i( p_uniforms.boneTextureHeight, object.boneTextureHeight );
+
+ }
+
+ } else {
+
+ if ( p_uniforms.boneGlobalMatrices !== null ) {
+
+ _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.boneMatrices );
+
+ }
+
+ }
+
+ }
+
+ if ( refreshMaterial ) {
+
+ // refresh uniforms common to several materials
+
+ if ( fog && material.fog ) {
+
+ refreshUniformsFog( m_uniforms, fog );
+
+ }
+
+ if ( material instanceof THREE.MeshPhongMaterial ||
+ material instanceof THREE.MeshLambertMaterial ||
+ material.lights ) {
+
+ if ( _lightsNeedUpdate ) {
+
+ setupLights( program, lights );
+ _lightsNeedUpdate = false;
+
+ }
+
+ refreshUniformsLights( m_uniforms, _lights );
+
+ }
+
+ if ( material instanceof THREE.MeshBasicMaterial ||
+ material instanceof THREE.MeshLambertMaterial ||
+ material instanceof THREE.MeshPhongMaterial ) {
+
+ refreshUniformsCommon( m_uniforms, material );
+
+ }
+
+ // refresh single material specific uniforms
+
+ if ( material instanceof THREE.LineBasicMaterial ) {
+
+ refreshUniformsLine( m_uniforms, material );
+
+ } else if ( material instanceof THREE.LineDashedMaterial ) {
+
+ refreshUniformsLine( m_uniforms, material );
+ refreshUniformsDash( m_uniforms, material );
+
+ } else if ( material instanceof THREE.ParticleBasicMaterial ) {
+
+ refreshUniformsParticle( m_uniforms, material );
+
+ } else if ( material instanceof THREE.MeshPhongMaterial ) {
+
+ refreshUniformsPhong( m_uniforms, material );
+
+ } else if ( material instanceof THREE.MeshLambertMaterial ) {
+
+ refreshUniformsLambert( m_uniforms, material );
+
+ } else if ( material instanceof THREE.MeshDepthMaterial ) {
+
+ m_uniforms.mNear.value = camera.near;
+ m_uniforms.mFar.value = camera.far;
+ m_uniforms.opacity.value = material.opacity;
+
+ } else if ( material instanceof THREE.MeshNormalMaterial ) {
+
+ m_uniforms.opacity.value = material.opacity;
+
+ }
+
+ if ( object.receiveShadow && ! material._shadowPass ) {
+
+ refreshUniformsShadow( m_uniforms, lights );
+
+ }
+
+ // load common uniforms
+
+ loadUniformsGeneric( program, material.uniformsList );
+
+ // load material specific uniforms
+ // (shader material also gets them for the sake of genericity)
+
+ if ( material instanceof THREE.ShaderMaterial ||
+ material instanceof THREE.MeshPhongMaterial ||
+ material.envMap ) {
+
+ if ( p_uniforms.cameraPosition !== null ) {
+
+ _vector3.getPositionFromMatrix( camera.matrixWorld );
+ _gl.uniform3f( p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z );
+
+ }
+
+ }
+
+ if ( material instanceof THREE.MeshPhongMaterial ||
+ material instanceof THREE.MeshLambertMaterial ||
+ material instanceof THREE.ShaderMaterial ||
+ material.skinning ) {
+
+ if ( p_uniforms.viewMatrix !== null ) {
+
+ _gl.uniformMatrix4fv( p_uniforms.viewMatrix, false, camera.matrixWorldInverse.elements );
+
+ }
+
+ }
+
+ }
+
+ loadUniformsMatrices( p_uniforms, object );
+
+ if ( p_uniforms.modelMatrix !== null ) {
+
+ _gl.uniformMatrix4fv( p_uniforms.modelMatrix, false, object.matrixWorld.elements );
+
+ }
+
+ return program;
+
+ };
+
+ // Uniforms (refresh uniforms objects)
+
+ function refreshUniformsCommon ( uniforms, material ) {
+
+ uniforms.opacity.value = material.opacity;
+
+ if ( _this.gammaInput ) {
+
+ uniforms.diffuse.value.copyGammaToLinear( material.color );
+
+ } else {
+
+ uniforms.diffuse.value = material.color;
+
+ }
+
+ uniforms.map.value = material.map;
+ uniforms.lightMap.value = material.lightMap;
+ uniforms.specularMap.value = material.specularMap;
+
+ if ( material.bumpMap ) {
+
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+
+ }
+
+ if ( material.normalMap ) {
+
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy( material.normalScale );
+
+ }
+
+ // uv repeat and offset setting priorities
+ // 1. color map
+ // 2. specular map
+ // 3. normal map
+ // 4. bump map
+
+ var uvScaleMap;
+
+ if ( material.map ) {
+
+ uvScaleMap = material.map;
+
+ } else if ( material.specularMap ) {
+
+ uvScaleMap = material.specularMap;
+
+ } else if ( material.normalMap ) {
+
+ uvScaleMap = material.normalMap;
+
+ } else if ( material.bumpMap ) {
+
+ uvScaleMap = material.bumpMap;
+
+ }
+
+ if ( uvScaleMap !== undefined ) {
+
+ var offset = uvScaleMap.offset;
+ var repeat = uvScaleMap.repeat;
+
+ uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y );
+
+ }
+
+ uniforms.envMap.value = material.envMap;
+ uniforms.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : -1;
+
+ if ( _this.gammaInput ) {
+
+ //uniforms.reflectivity.value = material.reflectivity * material.reflectivity;
+ uniforms.reflectivity.value = material.reflectivity;
+
+ } else {
+
+ uniforms.reflectivity.value = material.reflectivity;
+
+ }
+
+ uniforms.refractionRatio.value = material.refractionRatio;
+ uniforms.combine.value = material.combine;
+ uniforms.useRefract.value = material.envMap && material.envMap.mapping instanceof THREE.CubeRefractionMapping;
+
+ };
+
+ function refreshUniformsLine ( uniforms, material ) {
+
+ uniforms.diffuse.value = material.color;
+ uniforms.opacity.value = material.opacity;
+
+ };
+
+ function refreshUniformsDash ( uniforms, material ) {
+
+ uniforms.dashSize.value = material.dashSize;
+ uniforms.totalSize.value = material.dashSize + material.gapSize;
+ uniforms.scale.value = material.scale;
+
+ };
+
+ function refreshUniformsParticle ( uniforms, material ) {
+
+ uniforms.psColor.value = material.color;
+ uniforms.opacity.value = material.opacity;
+ uniforms.size.value = material.size;
+ uniforms.scale.value = _canvas.height / 2.0; // TODO: Cache this.
+
+ uniforms.map.value = material.map;
+
+ };
+
+ function refreshUniformsFog ( uniforms, fog ) {
+
+ uniforms.fogColor.value = fog.color;
+
+ if ( fog instanceof THREE.Fog ) {
+
+ uniforms.fogNear.value = fog.near;
+ uniforms.fogFar.value = fog.far;
+
+ } else if ( fog instanceof THREE.FogExp2 ) {
+
+ uniforms.fogDensity.value = fog.density;
+
+ }
+
+ };
+
+ function refreshUniformsPhong ( uniforms, material ) {
+
+ uniforms.shininess.value = material.shininess;
+
+ if ( _this.gammaInput ) {
+
+ uniforms.ambient.value.copyGammaToLinear( material.ambient );
+ uniforms.emissive.value.copyGammaToLinear( material.emissive );
+ uniforms.specular.value.copyGammaToLinear( material.specular );
+
+ } else {
+
+ uniforms.ambient.value = material.ambient;
+ uniforms.emissive.value = material.emissive;
+ uniforms.specular.value = material.specular;
+
+ }
+
+ if ( material.wrapAround ) {
+
+ uniforms.wrapRGB.value.copy( material.wrapRGB );
+
+ }
+
+ };
+
+ function refreshUniformsLambert ( uniforms, material ) {
+
+ if ( _this.gammaInput ) {
+
+ uniforms.ambient.value.copyGammaToLinear( material.ambient );
+ uniforms.emissive.value.copyGammaToLinear( material.emissive );
+
+ } else {
+
+ uniforms.ambient.value = material.ambient;
+ uniforms.emissive.value = material.emissive;
+
+ }
+
+ if ( material.wrapAround ) {
+
+ uniforms.wrapRGB.value.copy( material.wrapRGB );
+
+ }
+
+ };
+
+ function refreshUniformsLights ( uniforms, lights ) {
+
+ uniforms.ambientLightColor.value = lights.ambient;
+
+ uniforms.directionalLightColor.value = lights.directional.colors;
+ uniforms.directionalLightDirection.value = lights.directional.positions;
+
+ uniforms.pointLightColor.value = lights.point.colors;
+ uniforms.pointLightPosition.value = lights.point.positions;
+ uniforms.pointLightDistance.value = lights.point.distances;
+
+ uniforms.spotLightColor.value = lights.spot.colors;
+ uniforms.spotLightPosition.value = lights.spot.positions;
+ uniforms.spotLightDistance.value = lights.spot.distances;
+ uniforms.spotLightDirection.value = lights.spot.directions;
+ uniforms.spotLightAngleCos.value = lights.spot.anglesCos;
+ uniforms.spotLightExponent.value = lights.spot.exponents;
+
+ uniforms.hemisphereLightSkyColor.value = lights.hemi.skyColors;
+ uniforms.hemisphereLightGroundColor.value = lights.hemi.groundColors;
+ uniforms.hemisphereLightDirection.value = lights.hemi.positions;
+
+ };
+
+ function refreshUniformsShadow ( uniforms, lights ) {
+
+ if ( uniforms.shadowMatrix ) {
+
+ var j = 0;
+
+ for ( var i = 0, il = lights.length; i < il; i ++ ) {
+
+ var light = lights[ i ];
+
+ if ( ! light.castShadow ) continue;
+
+ if ( light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) ) {
+
+ uniforms.shadowMap.value[ j ] = light.shadowMap;
+ uniforms.shadowMapSize.value[ j ] = light.shadowMapSize;
+
+ uniforms.shadowMatrix.value[ j ] = light.shadowMatrix;
+
+ uniforms.shadowDarkness.value[ j ] = light.shadowDarkness;
+ uniforms.shadowBias.value[ j ] = light.shadowBias;
+
+ j ++;
+
+ }
+
+ }
+
+ }
+
+ };
+
+ // Uniforms (load to GPU)
+
+ function loadUniformsMatrices ( uniforms, object ) {
+
+ _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, object._modelViewMatrix.elements );
+
+ if ( uniforms.normalMatrix ) {
+
+ _gl.uniformMatrix3fv( uniforms.normalMatrix, false, object._normalMatrix.elements );
+
+ }
+
+ };
+
+ function getTextureUnit() {
+
+ var textureUnit = _usedTextureUnits;
+
+ if ( textureUnit >= _maxTextures ) {
+
+ console.warn( "WebGLRenderer: trying to use " + textureUnit + " texture units while this GPU supports only " + _maxTextures );
+
+ }
+
+ _usedTextureUnits += 1;
+
+ return textureUnit;
+
+ };
+
+ function loadUniformsGeneric ( program, uniforms ) {
+
+ var uniform, value, type, location, texture, textureUnit, i, il, j, jl, offset;
+
+ for ( j = 0, jl = uniforms.length; j < jl; j ++ ) {
+
+ location = program.uniforms[ uniforms[ j ][ 1 ] ];
+ if ( !location ) continue;
+
+ uniform = uniforms[ j ][ 0 ];
+
+ type = uniform.type;
+ value = uniform.value;
+
+ if ( type === "i" ) { // single integer
+
+ _gl.uniform1i( location, value );
+
+ } else if ( type === "f" ) { // single float
+
+ _gl.uniform1f( location, value );
+
+ } else if ( type === "v2" ) { // single THREE.Vector2
+
+ _gl.uniform2f( location, value.x, value.y );
+
+ } else if ( type === "v3" ) { // single THREE.Vector3
+
+ _gl.uniform3f( location, value.x, value.y, value.z );
+
+ } else if ( type === "v4" ) { // single THREE.Vector4
+
+ _gl.uniform4f( location, value.x, value.y, value.z, value.w );
+
+ } else if ( type === "c" ) { // single THREE.Color
+
+ _gl.uniform3f( location, value.r, value.g, value.b );
+
+ } else if ( type === "iv1" ) { // flat array of integers (JS or typed array)
+
+ _gl.uniform1iv( location, value );
+
+ } else if ( type === "iv" ) { // flat array of integers with 3 x N size (JS or typed array)
+
+ _gl.uniform3iv( location, value );
+
+ } else if ( type === "fv1" ) { // flat array of floats (JS or typed array)
+
+ _gl.uniform1fv( location, value );
+
+ } else if ( type === "fv" ) { // flat array of floats with 3 x N size (JS or typed array)
+
+ _gl.uniform3fv( location, value );
+
+ } else if ( type === "v2v" ) { // array of THREE.Vector2
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 2 * value.length );
+
+ }
+
+ for ( i = 0, il = value.length; i < il; i ++ ) {
+
+ offset = i * 2;
+
+ uniform._array[ offset ] = value[ i ].x;
+ uniform._array[ offset + 1 ] = value[ i ].y;
+
+ }
+
+ _gl.uniform2fv( location, uniform._array );
+
+ } else if ( type === "v3v" ) { // array of THREE.Vector3
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 3 * value.length );
+
+ }
+
+ for ( i = 0, il = value.length; i < il; i ++ ) {
+
+ offset = i * 3;
+
+ uniform._array[ offset ] = value[ i ].x;
+ uniform._array[ offset + 1 ] = value[ i ].y;
+ uniform._array[ offset + 2 ] = value[ i ].z;
+
+ }
+
+ _gl.uniform3fv( location, uniform._array );
+
+ } else if ( type === "v4v" ) { // array of THREE.Vector4
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 4 * value.length );
+
+ }
+
+ for ( i = 0, il = value.length; i < il; i ++ ) {
+
+ offset = i * 4;
+
+ uniform._array[ offset ] = value[ i ].x;
+ uniform._array[ offset + 1 ] = value[ i ].y;
+ uniform._array[ offset + 2 ] = value[ i ].z;
+ uniform._array[ offset + 3 ] = value[ i ].w;
+
+ }
+
+ _gl.uniform4fv( location, uniform._array );
+
+ } else if ( type === "m4") { // single THREE.Matrix4
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 16 );
+
+ }
+
+ value.flattenToArray( uniform._array );
+ _gl.uniformMatrix4fv( location, false, uniform._array );
+
+ } else if ( type === "m4v" ) { // array of THREE.Matrix4
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = new Float32Array( 16 * value.length );
+
+ }
+
+ for ( i = 0, il = value.length; i < il; i ++ ) {
+
+ value[ i ].flattenToArrayOffset( uniform._array, i * 16 );
+
+ }
+
+ _gl.uniformMatrix4fv( location, false, uniform._array );
+
+ } else if ( type === "t" ) { // single THREE.Texture (2d or cube)
+
+ texture = value;
+ textureUnit = getTextureUnit();
+
+ _gl.uniform1i( location, textureUnit );
+
+ if ( !texture ) continue;
+
+ if ( texture.image instanceof Array && texture.image.length === 6 ) {
+
+ setCubeTexture( texture, textureUnit );
+
+ } else if ( texture instanceof THREE.WebGLRenderTargetCube ) {
+
+ setCubeTextureDynamic( texture, textureUnit );
+
+ } else {
+
+ _this.setTexture( texture, textureUnit );
+
+ }
+
+ } else if ( type === "tv" ) { // array of THREE.Texture (2d)
+
+ if ( uniform._array === undefined ) {
+
+ uniform._array = [];
+
+ }
+
+ for( i = 0, il = uniform.value.length; i < il; i ++ ) {
+
+ uniform._array[ i ] = getTextureUnit();
+
+ }
+
+ _gl.uniform1iv( location, uniform._array );
+
+ for( i = 0, il = uniform.value.length; i < il; i ++ ) {
+
+ texture = uniform.value[ i ];
+ textureUnit = uniform._array[ i ];
+
+ if ( !texture ) continue;
+
+ _this.setTexture( texture, textureUnit );
+
+ }
+
+ } else {
+
+ console.warn( 'THREE.WebGLRenderer: Unknown uniform type: ' + type );
+
+ }
+
+ }
+
+ };
+
+ function setupMatrices ( object, camera ) {
+
+ object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+ object._normalMatrix.getNormalMatrix( object._modelViewMatrix );
+
+ };
+
+ //
+
+ function setColorGamma( array, offset, color, intensitySq ) {
+
+ array[ offset ] = color.r * color.r * intensitySq;
+ array[ offset + 1 ] = color.g * color.g * intensitySq;
+ array[ offset + 2 ] = color.b * color.b * intensitySq;
+
+ };
+
+ function setColorLinear( array, offset, color, intensity ) {
+
+ array[ offset ] = color.r * intensity;
+ array[ offset + 1 ] = color.g * intensity;
+ array[ offset + 2 ] = color.b * intensity;
+
+ };
+
+ function setupLights ( program, lights ) {
+
+ var l, ll, light, n,
+ r = 0, g = 0, b = 0,
+ color, skyColor, groundColor,
+ intensity, intensitySq,
+ position,
+ distance,
+
+ zlights = _lights,
+
+ dirColors = zlights.directional.colors,
+ dirPositions = zlights.directional.positions,
+
+ pointColors = zlights.point.colors,
+ pointPositions = zlights.point.positions,
+ pointDistances = zlights.point.distances,
+
+ spotColors = zlights.spot.colors,
+ spotPositions = zlights.spot.positions,
+ spotDistances = zlights.spot.distances,
+ spotDirections = zlights.spot.directions,
+ spotAnglesCos = zlights.spot.anglesCos,
+ spotExponents = zlights.spot.exponents,
+
+ hemiSkyColors = zlights.hemi.skyColors,
+ hemiGroundColors = zlights.hemi.groundColors,
+ hemiPositions = zlights.hemi.positions,
+
+ dirLength = 0,
+ pointLength = 0,
+ spotLength = 0,
+ hemiLength = 0,
+
+ dirCount = 0,
+ pointCount = 0,
+ spotCount = 0,
+ hemiCount = 0,
+
+ dirOffset = 0,
+ pointOffset = 0,
+ spotOffset = 0,
+ hemiOffset = 0;
+
+ for ( l = 0, ll = lights.length; l < ll; l ++ ) {
+
+ light = lights[ l ];
+
+ if ( light.onlyShadow ) continue;
+
+ color = light.color;
+ intensity = light.intensity;
+ distance = light.distance;
+
+ if ( light instanceof THREE.AmbientLight ) {
+
+ if ( ! light.visible ) continue;
+
+ if ( _this.gammaInput ) {
+
+ r += color.r * color.r;
+ g += color.g * color.g;
+ b += color.b * color.b;
+
+ } else {
+
+ r += color.r;
+ g += color.g;
+ b += color.b;
+
+ }
+
+ } else if ( light instanceof THREE.DirectionalLight ) {
+
+ dirCount += 1;
+
+ if ( ! light.visible ) continue;
+
+ _direction.getPositionFromMatrix( light.matrixWorld );
+ _vector3.getPositionFromMatrix( light.target.matrixWorld );
+ _direction.sub( _vector3 );
+ _direction.normalize();
+
+ // skip lights with undefined direction
+ // these create troubles in OpenGL (making pixel black)
+
+ if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue;
+
+ dirOffset = dirLength * 3;
+
+ dirPositions[ dirOffset ] = _direction.x;
+ dirPositions[ dirOffset + 1 ] = _direction.y;
+ dirPositions[ dirOffset + 2 ] = _direction.z;
+
+ if ( _this.gammaInput ) {
+
+ setColorGamma( dirColors, dirOffset, color, intensity * intensity );
+
+ } else {
+
+ setColorLinear( dirColors, dirOffset, color, intensity );
+
+ }
+
+ dirLength += 1;
+
+ } else if ( light instanceof THREE.PointLight ) {
+
+ pointCount += 1;
+
+ if ( ! light.visible ) continue;
+
+ pointOffset = pointLength * 3;
+
+ if ( _this.gammaInput ) {
+
+ setColorGamma( pointColors, pointOffset, color, intensity * intensity );
+
+ } else {
+
+ setColorLinear( pointColors, pointOffset, color, intensity );
+
+ }
+
+ _vector3.getPositionFromMatrix( light.matrixWorld );
+
+ pointPositions[ pointOffset ] = _vector3.x;
+ pointPositions[ pointOffset + 1 ] = _vector3.y;
+ pointPositions[ pointOffset + 2 ] = _vector3.z;
+
+ pointDistances[ pointLength ] = distance;
+
+ pointLength += 1;
+
+ } else if ( light instanceof THREE.SpotLight ) {
+
+ spotCount += 1;
+
+ if ( ! light.visible ) continue;
+
+ spotOffset = spotLength * 3;
+
+ if ( _this.gammaInput ) {
+
+ setColorGamma( spotColors, spotOffset, color, intensity * intensity );
+
+ } else {
+
+ setColorLinear( spotColors, spotOffset, color, intensity );
+
+ }
+
+ _vector3.getPositionFromMatrix( light.matrixWorld );
+
+ spotPositions[ spotOffset ] = _vector3.x;
+ spotPositions[ spotOffset + 1 ] = _vector3.y;
+ spotPositions[ spotOffset + 2 ] = _vector3.z;
+
+ spotDistances[ spotLength ] = distance;
+
+ _direction.copy( _vector3 );
+ _vector3.getPositionFromMatrix( light.target.matrixWorld );
+ _direction.sub( _vector3 );
+ _direction.normalize();
+
+ spotDirections[ spotOffset ] = _direction.x;
+ spotDirections[ spotOffset + 1 ] = _direction.y;
+ spotDirections[ spotOffset + 2 ] = _direction.z;
+
+ spotAnglesCos[ spotLength ] = Math.cos( light.angle );
+ spotExponents[ spotLength ] = light.exponent;
+
+ spotLength += 1;
+
+ } else if ( light instanceof THREE.HemisphereLight ) {
+
+ hemiCount += 1;
+
+ if ( ! light.visible ) continue;
+
+ _direction.getPositionFromMatrix( light.matrixWorld );
+ _direction.normalize();
+
+ // skip lights with undefined direction
+ // these create troubles in OpenGL (making pixel black)
+
+ if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue;
+
+ hemiOffset = hemiLength * 3;
+
+ hemiPositions[ hemiOffset ] = _direction.x;
+ hemiPositions[ hemiOffset + 1 ] = _direction.y;
+ hemiPositions[ hemiOffset + 2 ] = _direction.z;
+
+ skyColor = light.color;
+ groundColor = light.groundColor;
+
+ if ( _this.gammaInput ) {
+
+ intensitySq = intensity * intensity;
+
+ setColorGamma( hemiSkyColors, hemiOffset, skyColor, intensitySq );
+ setColorGamma( hemiGroundColors, hemiOffset, groundColor, intensitySq );
+
+ } else {
+
+ setColorLinear( hemiSkyColors, hemiOffset, skyColor, intensity );
+ setColorLinear( hemiGroundColors, hemiOffset, groundColor, intensity );
+
+ }
+
+ hemiLength += 1;
+
+ }
+
+ }
+
+ // null eventual remains from removed lights
+ // (this is to avoid if in shader)
+
+ for ( l = dirLength * 3, ll = Math.max( dirColors.length, dirCount * 3 ); l < ll; l ++ ) dirColors[ l ] = 0.0;
+ for ( l = pointLength * 3, ll = Math.max( pointColors.length, pointCount * 3 ); l < ll; l ++ ) pointColors[ l ] = 0.0;
+ for ( l = spotLength * 3, ll = Math.max( spotColors.length, spotCount * 3 ); l < ll; l ++ ) spotColors[ l ] = 0.0;
+ for ( l = hemiLength * 3, ll = Math.max( hemiSkyColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiSkyColors[ l ] = 0.0;
+ for ( l = hemiLength * 3, ll = Math.max( hemiGroundColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiGroundColors[ l ] = 0.0;
+
+ zlights.directional.length = dirLength;
+ zlights.point.length = pointLength;
+ zlights.spot.length = spotLength;
+ zlights.hemi.length = hemiLength;
+
+ zlights.ambient[ 0 ] = r;
+ zlights.ambient[ 1 ] = g;
+ zlights.ambient[ 2 ] = b;
+
+ };
+
+ // GL state setting
+
+ this.setFaceCulling = function ( cullFace, frontFaceDirection ) {
+
+ if ( cullFace === THREE.CullFaceNone ) {
+
+ _gl.disable( _gl.CULL_FACE );
+
+ } else {
+
+ if ( frontFaceDirection === THREE.FrontFaceDirectionCW ) {
+
+ _gl.frontFace( _gl.CW );
+
+ } else {
+
+ _gl.frontFace( _gl.CCW );
+
+ }
+
+ if ( cullFace === THREE.CullFaceBack ) {
+
+ _gl.cullFace( _gl.BACK );
+
+ } else if ( cullFace === THREE.CullFaceFront ) {
+
+ _gl.cullFace( _gl.FRONT );
+
+ } else {
+
+ _gl.cullFace( _gl.FRONT_AND_BACK );
+
+ }
+
+ _gl.enable( _gl.CULL_FACE );
+
+ }
+
+ };
+
+ this.setMaterialFaces = function ( material ) {
+
+ var doubleSided = material.side === THREE.DoubleSide;
+ var flipSided = material.side === THREE.BackSide;
+
+ if ( _oldDoubleSided !== doubleSided ) {
+
+ if ( doubleSided ) {
+
+ _gl.disable( _gl.CULL_FACE );
+
+ } else {
+
+ _gl.enable( _gl.CULL_FACE );
+
+ }
+
+ _oldDoubleSided = doubleSided;
+
+ }
+
+ if ( _oldFlipSided !== flipSided ) {
+
+ if ( flipSided ) {
+
+ _gl.frontFace( _gl.CW );
+
+ } else {
+
+ _gl.frontFace( _gl.CCW );
+
+ }
+
+ _oldFlipSided = flipSided;
+
+ }
+
+ };
+
+ this.setDepthTest = function ( depthTest ) {
+
+ if ( _oldDepthTest !== depthTest ) {
+
+ if ( depthTest ) {
+
+ _gl.enable( _gl.DEPTH_TEST );
+
+ } else {
+
+ _gl.disable( _gl.DEPTH_TEST );
+
+ }
+
+ _oldDepthTest = depthTest;
+
+ }
+
+ };
+
+ this.setDepthWrite = function ( depthWrite ) {
+
+ if ( _oldDepthWrite !== depthWrite ) {
+
+ _gl.depthMask( depthWrite );
+ _oldDepthWrite = depthWrite;
+
+ }
+
+ };
+
+ function setLineWidth ( width ) {
+
+ if ( width !== _oldLineWidth ) {
+
+ _gl.lineWidth( width );
+
+ _oldLineWidth = width;
+
+ }
+
+ };
+
+ function setPolygonOffset ( polygonoffset, factor, units ) {
+
+ if ( _oldPolygonOffset !== polygonoffset ) {
+
+ if ( polygonoffset ) {
+
+ _gl.enable( _gl.POLYGON_OFFSET_FILL );
+
+ } else {
+
+ _gl.disable( _gl.POLYGON_OFFSET_FILL );
+
+ }
+
+ _oldPolygonOffset = polygonoffset;
+
+ }
+
+ if ( polygonoffset && ( _oldPolygonOffsetFactor !== factor || _oldPolygonOffsetUnits !== units ) ) {
+
+ _gl.polygonOffset( factor, units );
+
+ _oldPolygonOffsetFactor = factor;
+ _oldPolygonOffsetUnits = units;
+
+ }
+
+ };
+
+ this.setBlending = function ( blending, blendEquation, blendSrc, blendDst ) {
+
+ if ( blending !== _oldBlending ) {
+
+ if ( blending === THREE.NoBlending ) {
+
+ _gl.disable( _gl.BLEND );
+
+ } else if ( blending === THREE.AdditiveBlending ) {
+
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquation( _gl.FUNC_ADD );
+ _gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE );
+
+ } else if ( blending === THREE.SubtractiveBlending ) {
+
+ // TODO: Find blendFuncSeparate() combination
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquation( _gl.FUNC_ADD );
+ _gl.blendFunc( _gl.ZERO, _gl.ONE_MINUS_SRC_COLOR );
+
+ } else if ( blending === THREE.MultiplyBlending ) {
+
+ // TODO: Find blendFuncSeparate() combination
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquation( _gl.FUNC_ADD );
+ _gl.blendFunc( _gl.ZERO, _gl.SRC_COLOR );
+
+ } else if ( blending === THREE.CustomBlending ) {
+
+ _gl.enable( _gl.BLEND );
+
+ } else {
+
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquationSeparate( _gl.FUNC_ADD, _gl.FUNC_ADD );
+ _gl.blendFuncSeparate( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA, _gl.ONE, _gl.ONE_MINUS_SRC_ALPHA );
+
+ }
+
+ _oldBlending = blending;
+
+ }
+
+ if ( blending === THREE.CustomBlending ) {
+
+ if ( blendEquation !== _oldBlendEquation ) {
+
+ _gl.blendEquation( paramThreeToGL( blendEquation ) );
+
+ _oldBlendEquation = blendEquation;
+
+ }
+
+ if ( blendSrc !== _oldBlendSrc || blendDst !== _oldBlendDst ) {
+
+ _gl.blendFunc( paramThreeToGL( blendSrc ), paramThreeToGL( blendDst ) );
+
+ _oldBlendSrc = blendSrc;
+ _oldBlendDst = blendDst;
+
+ }
+
+ } else {
+
+ _oldBlendEquation = null;
+ _oldBlendSrc = null;
+ _oldBlendDst = null;
+
+ }
+
+ };
+
+ // Defines
+
+ function generateDefines ( defines ) {
+
+ var value, chunk, chunks = [];
+
+ for ( var d in defines ) {
+
+ value = defines[ d ];
+ if ( value === false ) continue;
+
+ chunk = "#define " + d + " " + value;
+ chunks.push( chunk );
+
+ }
+
+ return chunks.join( "\n" );
+
+ };
+
+ // Shaders
+
+ function buildProgram ( shaderID, fragmentShader, vertexShader, uniforms, attributes, defines, parameters, index0AttributeName ) {
+
+ var p, pl, d, program, code;
+ var chunks = [];
+
+ // Generate code
+
+ if ( shaderID ) {
+
+ chunks.push( shaderID );
+
+ } else {
+
+ chunks.push( fragmentShader );
+ chunks.push( vertexShader );
+
+ }
+
+ for ( d in defines ) {
+
+ chunks.push( d );
+ chunks.push( defines[ d ] );
+
+ }
+
+ for ( p in parameters ) {
+
+ chunks.push( p );
+ chunks.push( parameters[ p ] );
+
+ }
+
+ code = chunks.join();
+
+ // Check if code has been already compiled
+
+ for ( p = 0, pl = _programs.length; p < pl; p ++ ) {
+
+ var programInfo = _programs[ p ];
+
+ if ( programInfo.code === code ) {
+
+ // console.log( "Code already compiled." /*: \n\n" + code*/ );
+
+ programInfo.usedTimes ++;
+
+ return programInfo.program;
+
+ }
+
+ }
+
+ var shadowMapTypeDefine = "SHADOWMAP_TYPE_BASIC";
+
+ if ( parameters.shadowMapType === THREE.PCFShadowMap ) {
+
+ shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF";
+
+ } else if ( parameters.shadowMapType === THREE.PCFSoftShadowMap ) {
+
+ shadowMapTypeDefine = "SHADOWMAP_TYPE_PCF_SOFT";
+
+ }
+
+ // console.log( "building new program " );
+
+ //
+
+ var customDefines = generateDefines( defines );
+
+ //
+
+ program = _gl.createProgram();
+
+ var prefix_vertex = [
+
+ "precision " + _precision + " float;",
+ "precision " + _precision + " int;",
+
+ customDefines,
+
+ _supportsVertexTextures ? "#define VERTEX_TEXTURES" : "",
+
+ _this.gammaInput ? "#define GAMMA_INPUT" : "",
+ _this.gammaOutput ? "#define GAMMA_OUTPUT" : "",
+ _this.physicallyBasedShading ? "#define PHYSICALLY_BASED_SHADING" : "",
+
+ "#define MAX_DIR_LIGHTS " + parameters.maxDirLights,
+ "#define MAX_POINT_LIGHTS " + parameters.maxPointLights,
+ "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights,
+ "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights,
+
+ "#define MAX_SHADOWS " + parameters.maxShadows,
+
+ "#define MAX_BONES " + parameters.maxBones,
+
+ parameters.map ? "#define USE_MAP" : "",
+ parameters.envMap ? "#define USE_ENVMAP" : "",
+ parameters.lightMap ? "#define USE_LIGHTMAP" : "",
+ parameters.bumpMap ? "#define USE_BUMPMAP" : "",
+ parameters.normalMap ? "#define USE_NORMALMAP" : "",
+ parameters.specularMap ? "#define USE_SPECULARMAP" : "",
+ parameters.vertexColors ? "#define USE_COLOR" : "",
+
+ parameters.skinning ? "#define USE_SKINNING" : "",
+ parameters.useVertexTexture ? "#define BONE_TEXTURE" : "",
+
+ parameters.morphTargets ? "#define USE_MORPHTARGETS" : "",
+ parameters.morphNormals ? "#define USE_MORPHNORMALS" : "",
+ parameters.perPixel ? "#define PHONG_PER_PIXEL" : "",
+ parameters.wrapAround ? "#define WRAP_AROUND" : "",
+ parameters.doubleSided ? "#define DOUBLE_SIDED" : "",
+ parameters.flipSided ? "#define FLIP_SIDED" : "",
+
+ parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "",
+ parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "",
+ parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "",
+ parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "",
+
+ parameters.sizeAttenuation ? "#define USE_SIZEATTENUATION" : "",
+
+ "uniform mat4 modelMatrix;",
+ "uniform mat4 modelViewMatrix;",
+ "uniform mat4 projectionMatrix;",
+ "uniform mat4 viewMatrix;",
+ "uniform mat3 normalMatrix;",
+ "uniform vec3 cameraPosition;",
+
+ "attribute vec3 position;",
+ "attribute vec3 normal;",
+ "attribute vec2 uv;",
+ "attribute vec2 uv2;",
+
+ "#ifdef USE_COLOR",
+
+ "attribute vec3 color;",
+
+ "#endif",
+
+ "#ifdef USE_MORPHTARGETS",
+
+ "attribute vec3 morphTarget0;",
+ "attribute vec3 morphTarget1;",
+ "attribute vec3 morphTarget2;",
+ "attribute vec3 morphTarget3;",
+
+ "#ifdef USE_MORPHNORMALS",
+
+ "attribute vec3 morphNormal0;",
+ "attribute vec3 morphNormal1;",
+ "attribute vec3 morphNormal2;",
+ "attribute vec3 morphNormal3;",
+
+ "#else",
+
+ "attribute vec3 morphTarget4;",
+ "attribute vec3 morphTarget5;",
+ "attribute vec3 morphTarget6;",
+ "attribute vec3 morphTarget7;",
+
+ "#endif",
+
+ "#endif",
+
+ "#ifdef USE_SKINNING",
+
+ "attribute vec4 skinIndex;",
+ "attribute vec4 skinWeight;",
+
+ "#endif",
+
+ ""
+
+ ].join("\n");
+
+ var prefix_fragment = [
+
+ "precision " + _precision + " float;",
+ "precision " + _precision + " int;",
+
+ ( parameters.bumpMap || parameters.normalMap ) ? "#extension GL_OES_standard_derivatives : enable" : "",
+
+ customDefines,
+
+ "#define MAX_DIR_LIGHTS " + parameters.maxDirLights,
+ "#define MAX_POINT_LIGHTS " + parameters.maxPointLights,
+ "#define MAX_SPOT_LIGHTS " + parameters.maxSpotLights,
+ "#define MAX_HEMI_LIGHTS " + parameters.maxHemiLights,
+
+ "#define MAX_SHADOWS " + parameters.maxShadows,
+
+ parameters.alphaTest ? "#define ALPHATEST " + parameters.alphaTest: "",
+
+ _this.gammaInput ? "#define GAMMA_INPUT" : "",
+ _this.gammaOutput ? "#define GAMMA_OUTPUT" : "",
+ _this.physicallyBasedShading ? "#define PHYSICALLY_BASED_SHADING" : "",
+
+ ( parameters.useFog && parameters.fog ) ? "#define USE_FOG" : "",
+ ( parameters.useFog && parameters.fogExp ) ? "#define FOG_EXP2" : "",
+
+ parameters.map ? "#define USE_MAP" : "",
+ parameters.envMap ? "#define USE_ENVMAP" : "",
+ parameters.lightMap ? "#define USE_LIGHTMAP" : "",
+ parameters.bumpMap ? "#define USE_BUMPMAP" : "",
+ parameters.normalMap ? "#define USE_NORMALMAP" : "",
+ parameters.specularMap ? "#define USE_SPECULARMAP" : "",
+ parameters.vertexColors ? "#define USE_COLOR" : "",
+
+ parameters.metal ? "#define METAL" : "",
+ parameters.perPixel ? "#define PHONG_PER_PIXEL" : "",
+ parameters.wrapAround ? "#define WRAP_AROUND" : "",
+ parameters.doubleSided ? "#define DOUBLE_SIDED" : "",
+ parameters.flipSided ? "#define FLIP_SIDED" : "",
+
+ parameters.shadowMapEnabled ? "#define USE_SHADOWMAP" : "",
+ parameters.shadowMapEnabled ? "#define " + shadowMapTypeDefine : "",
+ parameters.shadowMapDebug ? "#define SHADOWMAP_DEBUG" : "",
+ parameters.shadowMapCascade ? "#define SHADOWMAP_CASCADE" : "",
+
+ "uniform mat4 viewMatrix;",
+ "uniform vec3 cameraPosition;",
+ ""
+
+ ].join("\n");
+
+ var glVertexShader = getShader( "vertex", prefix_vertex + vertexShader );
+ var glFragmentShader = getShader( "fragment", prefix_fragment + fragmentShader );
+
+ _gl.attachShader( program, glVertexShader );
+ _gl.attachShader( program, glFragmentShader );
+
+ //Force a particular attribute to index 0.
+ // because potentially expensive emulation is done by browser if attribute 0 is disabled.
+ //And, color, for example is often automatically bound to index 0 so disabling it
+ if ( index0AttributeName ) {
+ _gl.bindAttribLocation( program, 0, index0AttributeName );
+ }
+
+ _gl.linkProgram( program );
+
+ if ( !_gl.getProgramParameter( program, _gl.LINK_STATUS ) ) {
+
+ console.error( "Could not initialise shader\n" + "VALIDATE_STATUS: " + _gl.getProgramParameter( program, _gl.VALIDATE_STATUS ) + ", gl error [" + _gl.getError() + "]" );
+ console.error( "Program Info Log: " + _gl.getProgramInfoLog( program ) );
+ }
+
+ // clean up
+
+ _gl.deleteShader( glFragmentShader );
+ _gl.deleteShader( glVertexShader );
+
+ // console.log( prefix_fragment + fragmentShader );
+ // console.log( prefix_vertex + vertexShader );
+
+ program.uniforms = {};
+ program.attributes = {};
+
+ var identifiers, u, a, i;
+
+ // cache uniform locations
+
+ identifiers = [
+
+ 'viewMatrix', 'modelViewMatrix', 'projectionMatrix', 'normalMatrix', 'modelMatrix', 'cameraPosition',
+ 'morphTargetInfluences'
+
+ ];
+
+ if ( parameters.useVertexTexture ) {
+
+ identifiers.push( 'boneTexture' );
+ identifiers.push( 'boneTextureWidth' );
+ identifiers.push( 'boneTextureHeight' );
+
+ } else {
+
+ identifiers.push( 'boneGlobalMatrices' );
+
+ }
+
+ for ( u in uniforms ) {
+
+ identifiers.push( u );
+
+ }
+
+ cacheUniformLocations( program, identifiers );
+
+ // cache attributes locations
+
+ identifiers = [
+
+ "position", "normal", "uv", "uv2", "tangent", "color",
+ "skinIndex", "skinWeight", "lineDistance"
+
+ ];
+
+ for ( i = 0; i < parameters.maxMorphTargets; i ++ ) {
+
+ identifiers.push( "morphTarget" + i );
+
+ }
+
+ for ( i = 0; i < parameters.maxMorphNormals; i ++ ) {
+
+ identifiers.push( "morphNormal" + i );
+
+ }
+
+ for ( a in attributes ) {
+
+ identifiers.push( a );
+
+ }
+
+ cacheAttributeLocations( program, identifiers );
+
+ program.id = _programs_counter ++;
+
+ _programs.push( { program: program, code: code, usedTimes: 1 } );
+
+ _this.info.memory.programs = _programs.length;
+
+ return program;
+
+ };
+
+ // Shader parameters cache
+
+ function cacheUniformLocations ( program, identifiers ) {
+
+ var i, l, id;
+
+ for( i = 0, l = identifiers.length; i < l; i ++ ) {
+
+ id = identifiers[ i ];
+ program.uniforms[ id ] = _gl.getUniformLocation( program, id );
+
+ }
+
+ };
+
+ function cacheAttributeLocations ( program, identifiers ) {
+
+ var i, l, id;
+
+ for( i = 0, l = identifiers.length; i < l; i ++ ) {
+
+ id = identifiers[ i ];
+ program.attributes[ id ] = _gl.getAttribLocation( program, id );
+
+ }
+
+ };
+
+ function addLineNumbers ( string ) {
+
+ var chunks = string.split( "\n" );
+
+ for ( var i = 0, il = chunks.length; i < il; i ++ ) {
+
+ // Chrome reports shader errors on lines
+ // starting counting from 1
+
+ chunks[ i ] = ( i + 1 ) + ": " + chunks[ i ];
+
+ }
+
+ return chunks.join( "\n" );
+
+ };
+
+ function getShader ( type, string ) {
+
+ var shader;
+
+ if ( type === "fragment" ) {
+
+ shader = _gl.createShader( _gl.FRAGMENT_SHADER );
+
+ } else if ( type === "vertex" ) {
+
+ shader = _gl.createShader( _gl.VERTEX_SHADER );
+
+ }
+
+ _gl.shaderSource( shader, string );
+ _gl.compileShader( shader );
+
+ if ( !_gl.getShaderParameter( shader, _gl.COMPILE_STATUS ) ) {
+
+ console.error( _gl.getShaderInfoLog( shader ) );
+ console.error( addLineNumbers( string ) );
+ return null;
+
+ }
+
+ return shader;
+
+ };
+
+ // Textures
+
+
+ function isPowerOfTwo ( value ) {
+
+ return ( value & ( value - 1 ) ) === 0;
+
+ };
+
+ function setTextureParameters ( textureType, texture, isImagePowerOfTwo ) {
+
+ if ( isImagePowerOfTwo ) {
+
+ _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, paramThreeToGL( texture.wrapS ) );
+ _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, paramThreeToGL( texture.wrapT ) );
+
+ _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, paramThreeToGL( texture.magFilter ) );
+ _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, paramThreeToGL( texture.minFilter ) );
+
+ } else {
+
+ _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE );
+ _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE );
+
+ _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) );
+ _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) );
+
+ }
+
+ if ( _glExtensionTextureFilterAnisotropic && texture.type !== THREE.FloatType ) {
+
+ if ( texture.anisotropy > 1 || texture.__oldAnisotropy ) {
+
+ _gl.texParameterf( textureType, _glExtensionTextureFilterAnisotropic.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, _maxAnisotropy ) );
+ texture.__oldAnisotropy = texture.anisotropy;
+
+ }
+
+ }
+
+ };
+
+ this.setTexture = function ( texture, slot ) {
+
+ if ( texture.needsUpdate ) {
+
+ if ( ! texture.__webglInit ) {
+
+ texture.__webglInit = true;
+
+ texture.addEventListener( 'dispose', onTextureDispose );
+
+ texture.__webglTexture = _gl.createTexture();
+
+ _this.info.memory.textures ++;
+
+ }
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture );
+
+ _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
+ _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha );
+ _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment );
+
+ var image = texture.image,
+ isImagePowerOfTwo = isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ),
+ glFormat = paramThreeToGL( texture.format ),
+ glType = paramThreeToGL( texture.type );
+
+ setTextureParameters( _gl.TEXTURE_2D, texture, isImagePowerOfTwo );
+
+ var mipmap, mipmaps = texture.mipmaps;
+
+ if ( texture instanceof THREE.DataTexture ) {
+
+ // use manually created mipmaps if available
+ // if there are no manual mipmaps
+ // set 0 level mipmap and then use GL to generate other mipmap levels
+
+ if ( mipmaps.length > 0 && isImagePowerOfTwo ) {
+
+ for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+ _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+ }
+
+ texture.generateMipmaps = false;
+
+ } else {
+
+ _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data );
+
+ }
+
+ } else if ( texture instanceof THREE.CompressedTexture ) {
+
+ for( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+ if ( texture.format!==THREE.RGBAFormat ) {
+ _gl.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+ } else {
+ _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+ }
+
+ }
+
+ } else { // regular Texture (image, video, canvas)
+
+ // use manually created mipmaps if available
+ // if there are no manual mipmaps
+ // set 0 level mipmap and then use GL to generate other mipmap levels
+
+ if ( mipmaps.length > 0 && isImagePowerOfTwo ) {
+
+ for ( var i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+ _gl.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap );
+
+ }
+
+ texture.generateMipmaps = false;
+
+ } else {
+
+ _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, texture.image );
+
+ }
+
+ }
+
+ if ( texture.generateMipmaps && isImagePowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D );
+
+ texture.needsUpdate = false;
+
+ if ( texture.onUpdate ) texture.onUpdate();
+
+ } else {
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_2D, texture.__webglTexture );
+
+ }
+
+ };
+
+ function clampToMaxSize ( image, maxSize ) {
+
+ if ( image.width <= maxSize && image.height <= maxSize ) {
+
+ return image;
+
+ }
+
+ // Warning: Scaling through the canvas will only work with images that use
+ // premultiplied alpha.
+
+ var maxDimension = Math.max( image.width, image.height );
+ var newWidth = Math.floor( image.width * maxSize / maxDimension );
+ var newHeight = Math.floor( image.height * maxSize / maxDimension );
+
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = newWidth;
+ canvas.height = newHeight;
+
+ var ctx = canvas.getContext( "2d" );
+ ctx.drawImage( image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight );
+
+ return canvas;
+
+ }
+
+ function setCubeTexture ( texture, slot ) {
+
+ if ( texture.image.length === 6 ) {
+
+ if ( texture.needsUpdate ) {
+
+ if ( ! texture.image.__webglTextureCube ) {
+
+ texture.addEventListener( 'dispose', onTextureDispose );
+
+ texture.image.__webglTextureCube = _gl.createTexture();
+
+ _this.info.memory.textures ++;
+
+ }
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube );
+
+ _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY );
+
+ var isCompressed = texture instanceof THREE.CompressedTexture;
+
+ var cubeImage = [];
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ if ( _this.autoScaleCubemaps && ! isCompressed ) {
+
+ cubeImage[ i ] = clampToMaxSize( texture.image[ i ], _maxCubemapSize );
+
+ } else {
+
+ cubeImage[ i ] = texture.image[ i ];
+
+ }
+
+ }
+
+ var image = cubeImage[ 0 ],
+ isImagePowerOfTwo = isPowerOfTwo( image.width ) && isPowerOfTwo( image.height ),
+ glFormat = paramThreeToGL( texture.format ),
+ glType = paramThreeToGL( texture.type );
+
+ setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isImagePowerOfTwo );
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ if( !isCompressed ) {
+
+ _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] );
+
+ } else {
+
+ var mipmap, mipmaps = cubeImage[ i ].mipmaps;
+
+ for( var j = 0, jl = mipmaps.length; j < jl; j ++ ) {
+
+ mipmap = mipmaps[ j ];
+ if ( texture.format!==THREE.RGBAFormat ) {
+
+ _gl.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+ } else {
+ _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+ }
+
+ }
+ }
+ }
+
+ if ( texture.generateMipmaps && isImagePowerOfTwo ) {
+
+ _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+
+ }
+
+ texture.needsUpdate = false;
+
+ if ( texture.onUpdate ) texture.onUpdate();
+
+ } else {
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.image.__webglTextureCube );
+
+ }
+
+ }
+
+ };
+
+ function setCubeTextureDynamic ( texture, slot ) {
+
+ _gl.activeTexture( _gl.TEXTURE0 + slot );
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, texture.__webglTexture );
+
+ };
+
+ // Render targets
+
+ function setupFrameBuffer ( framebuffer, renderTarget, textureTarget ) {
+
+ _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+ _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureTarget, renderTarget.__webglTexture, 0 );
+
+ };
+
+ function setupRenderBuffer ( renderbuffer, renderTarget ) {
+
+ _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer );
+
+ if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+
+ _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height );
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+
+ /* For some reason this is not working. Defaulting to RGBA4.
+ } else if( ! renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+ _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.STENCIL_INDEX8, renderTarget.width, renderTarget.height );
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+ */
+ } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+ _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height );
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer );
+
+ } else {
+
+ _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height );
+
+ }
+
+ };
+
+ this.setRenderTarget = function ( renderTarget ) {
+
+ var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube );
+
+ if ( renderTarget && ! renderTarget.__webglFramebuffer ) {
+
+ if ( renderTarget.depthBuffer === undefined ) renderTarget.depthBuffer = true;
+ if ( renderTarget.stencilBuffer === undefined ) renderTarget.stencilBuffer = true;
+
+ renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
+
+ renderTarget.__webglTexture = _gl.createTexture();
+
+ _this.info.memory.textures ++;
+
+ // Setup texture, create render and frame buffers
+
+ var isTargetPowerOfTwo = isPowerOfTwo( renderTarget.width ) && isPowerOfTwo( renderTarget.height ),
+ glFormat = paramThreeToGL( renderTarget.format ),
+ glType = paramThreeToGL( renderTarget.type );
+
+ if ( isCube ) {
+
+ renderTarget.__webglFramebuffer = [];
+ renderTarget.__webglRenderbuffer = [];
+
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture );
+ setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget, isTargetPowerOfTwo );
+
+ for ( var i = 0; i < 6; i ++ ) {
+
+ renderTarget.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+ renderTarget.__webglRenderbuffer[ i ] = _gl.createRenderbuffer();
+
+ _gl.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+
+ setupFrameBuffer( renderTarget.__webglFramebuffer[ i ], renderTarget, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );
+ setupRenderBuffer( renderTarget.__webglRenderbuffer[ i ], renderTarget );
+
+ }
+
+ if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+
+ } else {
+
+ renderTarget.__webglFramebuffer = _gl.createFramebuffer();
+
+ if ( renderTarget.shareDepthFrom ) {
+
+ renderTarget.__webglRenderbuffer = renderTarget.shareDepthFrom.__webglRenderbuffer;
+
+ } else {
+
+ renderTarget.__webglRenderbuffer = _gl.createRenderbuffer();
+
+ }
+
+ _gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture );
+ setTextureParameters( _gl.TEXTURE_2D, renderTarget, isTargetPowerOfTwo );
+
+ _gl.texImage2D( _gl.TEXTURE_2D, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+
+ setupFrameBuffer( renderTarget.__webglFramebuffer, renderTarget, _gl.TEXTURE_2D );
+
+ if ( renderTarget.shareDepthFrom ) {
+
+ if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer );
+
+ } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+ _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderTarget.__webglRenderbuffer );
+
+ }
+
+ } else {
+
+ setupRenderBuffer( renderTarget.__webglRenderbuffer, renderTarget );
+
+ }
+
+ if ( isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D );
+
+ }
+
+ // Release everything
+
+ if ( isCube ) {
+
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null );
+
+ } else {
+
+ _gl.bindTexture( _gl.TEXTURE_2D, null );
+
+ }
+
+ _gl.bindRenderbuffer( _gl.RENDERBUFFER, null );
+ _gl.bindFramebuffer( _gl.FRAMEBUFFER, null );
+
+ }
+
+ var framebuffer, width, height, vx, vy;
+
+ if ( renderTarget ) {
+
+ if ( isCube ) {
+
+ framebuffer = renderTarget.__webglFramebuffer[ renderTarget.activeCubeFace ];
+
+ } else {
+
+ framebuffer = renderTarget.__webglFramebuffer;
+
+ }
+
+ width = renderTarget.width;
+ height = renderTarget.height;
+
+ vx = 0;
+ vy = 0;
+
+ } else {
+
+ framebuffer = null;
+
+ width = _viewportWidth;
+ height = _viewportHeight;
+
+ vx = _viewportX;
+ vy = _viewportY;
+
+ }
+
+ if ( framebuffer !== _currentFramebuffer ) {
+
+ _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+ _gl.viewport( vx, vy, width, height );
+
+ _currentFramebuffer = framebuffer;
+
+ }
+
+ _currentWidth = width;
+ _currentHeight = height;
+
+ };
+
+ function updateRenderTargetMipmap ( renderTarget ) {
+
+ if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) {
+
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, renderTarget.__webglTexture );
+ _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP );
+ _gl.bindTexture( _gl.TEXTURE_CUBE_MAP, null );
+
+ } else {
+
+ _gl.bindTexture( _gl.TEXTURE_2D, renderTarget.__webglTexture );
+ _gl.generateMipmap( _gl.TEXTURE_2D );
+ _gl.bindTexture( _gl.TEXTURE_2D, null );
+
+ }
+
+ };
+
+ // Fallback filters for non-power-of-2 textures
+
+ function filterFallback ( f ) {
+
+ if ( f === THREE.NearestFilter || f === THREE.NearestMipMapNearestFilter || f === THREE.NearestMipMapLinearFilter ) {
+
+ return _gl.NEAREST;
+
+ }
+
+ return _gl.LINEAR;
+
+ };
+
+ // Map three.js constants to WebGL constants
+
+ function paramThreeToGL ( p ) {
+
+ if ( p === THREE.RepeatWrapping ) return _gl.REPEAT;
+ if ( p === THREE.ClampToEdgeWrapping ) return _gl.CLAMP_TO_EDGE;
+ if ( p === THREE.MirroredRepeatWrapping ) return _gl.MIRRORED_REPEAT;
+
+ if ( p === THREE.NearestFilter ) return _gl.NEAREST;
+ if ( p === THREE.NearestMipMapNearestFilter ) return _gl.NEAREST_MIPMAP_NEAREST;
+ if ( p === THREE.NearestMipMapLinearFilter ) return _gl.NEAREST_MIPMAP_LINEAR;
+
+ if ( p === THREE.LinearFilter ) return _gl.LINEAR;
+ if ( p === THREE.LinearMipMapNearestFilter ) return _gl.LINEAR_MIPMAP_NEAREST;
+ if ( p === THREE.LinearMipMapLinearFilter ) return _gl.LINEAR_MIPMAP_LINEAR;
+
+ if ( p === THREE.UnsignedByteType ) return _gl.UNSIGNED_BYTE;
+ if ( p === THREE.UnsignedShort4444Type ) return _gl.UNSIGNED_SHORT_4_4_4_4;
+ if ( p === THREE.UnsignedShort5551Type ) return _gl.UNSIGNED_SHORT_5_5_5_1;
+ if ( p === THREE.UnsignedShort565Type ) return _gl.UNSIGNED_SHORT_5_6_5;
+
+ if ( p === THREE.ByteType ) return _gl.BYTE;
+ if ( p === THREE.ShortType ) return _gl.SHORT;
+ if ( p === THREE.UnsignedShortType ) return _gl.UNSIGNED_SHORT;
+ if ( p === THREE.IntType ) return _gl.INT;
+ if ( p === THREE.UnsignedIntType ) return _gl.UNSIGNED_INT;
+ if ( p === THREE.FloatType ) return _gl.FLOAT;
+
+ if ( p === THREE.AlphaFormat ) return _gl.ALPHA;
+ if ( p === THREE.RGBFormat ) return _gl.RGB;
+ if ( p === THREE.RGBAFormat ) return _gl.RGBA;
+ if ( p === THREE.LuminanceFormat ) return _gl.LUMINANCE;
+ if ( p === THREE.LuminanceAlphaFormat ) return _gl.LUMINANCE_ALPHA;
+
+ if ( p === THREE.AddEquation ) return _gl.FUNC_ADD;
+ if ( p === THREE.SubtractEquation ) return _gl.FUNC_SUBTRACT;
+ if ( p === THREE.ReverseSubtractEquation ) return _gl.FUNC_REVERSE_SUBTRACT;
+
+ if ( p === THREE.ZeroFactor ) return _gl.ZERO;
+ if ( p === THREE.OneFactor ) return _gl.ONE;
+ if ( p === THREE.SrcColorFactor ) return _gl.SRC_COLOR;
+ if ( p === THREE.OneMinusSrcColorFactor ) return _gl.ONE_MINUS_SRC_COLOR;
+ if ( p === THREE.SrcAlphaFactor ) return _gl.SRC_ALPHA;
+ if ( p === THREE.OneMinusSrcAlphaFactor ) return _gl.ONE_MINUS_SRC_ALPHA;
+ if ( p === THREE.DstAlphaFactor ) return _gl.DST_ALPHA;
+ if ( p === THREE.OneMinusDstAlphaFactor ) return _gl.ONE_MINUS_DST_ALPHA;
+
+ if ( p === THREE.DstColorFactor ) return _gl.DST_COLOR;
+ if ( p === THREE.OneMinusDstColorFactor ) return _gl.ONE_MINUS_DST_COLOR;
+ if ( p === THREE.SrcAlphaSaturateFactor ) return _gl.SRC_ALPHA_SATURATE;
+
+ if ( _glExtensionCompressedTextureS3TC !== undefined ) {
+
+ if ( p === THREE.RGB_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGB_S3TC_DXT1_EXT;
+ if ( p === THREE.RGBA_S3TC_DXT1_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ if ( p === THREE.RGBA_S3TC_DXT3_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ if ( p === THREE.RGBA_S3TC_DXT5_Format ) return _glExtensionCompressedTextureS3TC.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+ }
+
+ return 0;
+
+ };
+
+ // Allocations
+
+ function allocateBones ( object ) {
+
+ if ( _supportsBoneTextures && object && object.useVertexTexture ) {
+
+ return 1024;
+
+ } else {
+
+ // default for when object is not specified
+ // ( for example when prebuilding shader
+ // to be used with multiple objects )
+ //
+ // - leave some extra space for other uniforms
+ // - limit here is ANGLE's 254 max uniform vectors
+ // (up to 54 should be safe)
+
+ var nVertexUniforms = _gl.getParameter( _gl.MAX_VERTEX_UNIFORM_VECTORS );
+ var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );
+
+ var maxBones = nVertexMatrices;
+
+ if ( object !== undefined && object instanceof THREE.SkinnedMesh ) {
+
+ maxBones = Math.min( object.bones.length, maxBones );
+
+ if ( maxBones < object.bones.length ) {
+
+ console.warn( "WebGLRenderer: too many bones - " + object.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" );
+
+ }
+
+ }
+
+ return maxBones;
+
+ }
+
+ };
+
+ function allocateLights( lights ) {
+
+ var dirLights = 0;
+ var pointLights = 0;
+ var spotLights = 0;
+ var hemiLights = 0;
+
+ for ( var l = 0, ll = lights.length; l < ll; l ++ ) {
+
+ var light = lights[ l ];
+
+ if ( light.onlyShadow ) continue;
+
+ if ( light instanceof THREE.DirectionalLight ) dirLights ++;
+ if ( light instanceof THREE.PointLight ) pointLights ++;
+ if ( light instanceof THREE.SpotLight ) spotLights ++;
+ if ( light instanceof THREE.HemisphereLight ) hemiLights ++;
+
+ }
+
+ return { 'directional' : dirLights, 'point' : pointLights, 'spot': spotLights, 'hemi': hemiLights };
+
+ };
+
+ function allocateShadows( lights ) {
+
+ var maxShadows = 0;
+
+ for ( var l = 0, ll = lights.length; l < ll; l++ ) {
+
+ var light = lights[ l ];
+
+ if ( ! light.castShadow ) continue;
+
+ if ( light instanceof THREE.SpotLight ) maxShadows ++;
+ if ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) maxShadows ++;
+
+ }
+
+ return maxShadows;
+
+ };
+
+ // Initialization
+
+ function initGL() {
+
+ try {
+
+ var attributes = {
+ alpha: _alpha,
+ premultipliedAlpha: _premultipliedAlpha,
+ antialias: _antialias,
+ stencil: _stencil,
+ preserveDrawingBuffer: _preserveDrawingBuffer
+ };
+
+ _gl = _canvas.getContext( 'webgl', attributes ) || _canvas.getContext( 'experimental-webgl', attributes );
+
+ if ( _gl === null ) {
+
+ throw 'Error creating WebGL context.';
+
+ }
+
+ } catch ( error ) {
+
+ console.error( error );
+
+ }
+
+ _glExtensionTextureFloat = _gl.getExtension( 'OES_texture_float' );
+ _glExtensionTextureFloatLinear = _gl.getExtension( 'OES_texture_float_linear' );
+ _glExtensionStandardDerivatives = _gl.getExtension( 'OES_standard_derivatives' );
+
+ _glExtensionTextureFilterAnisotropic = _gl.getExtension( 'EXT_texture_filter_anisotropic' ) || _gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || _gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );
+
+ _glExtensionCompressedTextureS3TC = _gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || _gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || _gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );
+
+ if ( ! _glExtensionTextureFloat ) {
+
+ console.log( 'THREE.WebGLRenderer: Float textures not supported.' );
+
+ }
+
+ if ( ! _glExtensionStandardDerivatives ) {
+
+ console.log( 'THREE.WebGLRenderer: Standard derivatives not supported.' );
+
+ }
+
+ if ( ! _glExtensionTextureFilterAnisotropic ) {
+
+ console.log( 'THREE.WebGLRenderer: Anisotropic texture filtering not supported.' );
+
+ }
+
+ if ( ! _glExtensionCompressedTextureS3TC ) {
+
+ console.log( 'THREE.WebGLRenderer: S3TC compressed textures not supported.' );
+
+ }
+
+ if ( _gl.getShaderPrecisionFormat === undefined ) {
+
+ _gl.getShaderPrecisionFormat = function() {
+
+ return {
+ "rangeMin" : 1,
+ "rangeMax" : 1,
+ "precision" : 1
+ };
+
+ }
+ }
+
+ };
+
+ function setDefaultGLState () {
+
+ _gl.clearColor( 0, 0, 0, 1 );
+ _gl.clearDepth( 1 );
+ _gl.clearStencil( 0 );
+
+ _gl.enable( _gl.DEPTH_TEST );
+ _gl.depthFunc( _gl.LEQUAL );
+
+ _gl.frontFace( _gl.CCW );
+ _gl.cullFace( _gl.BACK );
+ _gl.enable( _gl.CULL_FACE );
+
+ _gl.enable( _gl.BLEND );
+ _gl.blendEquation( _gl.FUNC_ADD );
+ _gl.blendFunc( _gl.SRC_ALPHA, _gl.ONE_MINUS_SRC_ALPHA );
+
+ _gl.clearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha );
+
+ };
+
+ // default plugins (order is important)
+
+ this.shadowMapPlugin = new THREE.ShadowMapPlugin();
+ this.addPrePlugin( this.shadowMapPlugin );
+
+ this.addPostPlugin( new THREE.SpritePlugin() );
+ this.addPostPlugin( new THREE.LensFlarePlugin() );
+
+};
+
+/**
+ * @author szimek / https://github.com/szimek/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.WebGLRenderTarget = function ( width, height, options ) {
+
+ this.width = width;
+ this.height = height;
+
+ options = options || {};
+
+ this.wrapS = options.wrapS !== undefined ? options.wrapS : THREE.ClampToEdgeWrapping;
+ this.wrapT = options.wrapT !== undefined ? options.wrapT : THREE.ClampToEdgeWrapping;
+
+ this.magFilter = options.magFilter !== undefined ? options.magFilter : THREE.LinearFilter;
+ this.minFilter = options.minFilter !== undefined ? options.minFilter : THREE.LinearMipMapLinearFilter;
+
+ this.anisotropy = options.anisotropy !== undefined ? options.anisotropy : 1;
+
+ this.offset = new THREE.Vector2( 0, 0 );
+ this.repeat = new THREE.Vector2( 1, 1 );
+
+ this.format = options.format !== undefined ? options.format : THREE.RGBAFormat;
+ this.type = options.type !== undefined ? options.type : THREE.UnsignedByteType;
+
+ this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
+ this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : true;
+
+ this.generateMipmaps = true;
+
+ this.shareDepthFrom = null;
+
+};
+
+THREE.WebGLRenderTarget.prototype = {
+
+ constructor: THREE.WebGLRenderTarget,
+
+ clone: function () {
+
+ var tmp = new THREE.WebGLRenderTarget( this.width, this.height );
+
+ tmp.wrapS = this.wrapS;
+ tmp.wrapT = this.wrapT;
+
+ tmp.magFilter = this.magFilter;
+ tmp.minFilter = this.minFilter;
+
+ tmp.anisotropy = this.anisotropy;
+
+ tmp.offset.copy( this.offset );
+ tmp.repeat.copy( this.repeat );
+
+ tmp.format = this.format;
+ tmp.type = this.type;
+
+ tmp.depthBuffer = this.depthBuffer;
+ tmp.stencilBuffer = this.stencilBuffer;
+
+ tmp.generateMipmaps = this.generateMipmaps;
+
+ tmp.shareDepthFrom = this.shareDepthFrom;
+
+ return tmp;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.WebGLRenderTarget.prototype );
+
+/**
+ * @author alteredq / http://alteredqualia.com
+ */
+
+THREE.WebGLRenderTargetCube = function ( width, height, options ) {
+
+ THREE.WebGLRenderTarget.call( this, width, height, options );
+
+ this.activeCubeFace = 0; // PX 0, NX 1, PY 2, NY 3, PZ 4, NZ 5
+
+};
+
+THREE.WebGLRenderTargetCube.prototype = Object.create( THREE.WebGLRenderTarget.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableVertex = function () {
+
+ this.positionWorld = new THREE.Vector3();
+ this.positionScreen = new THREE.Vector4();
+
+ this.visible = true;
+
+};
+
+THREE.RenderableVertex.prototype.copy = function ( vertex ) {
+
+ this.positionWorld.copy( vertex.positionWorld );
+ this.positionScreen.copy( vertex.positionScreen );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableFace3 = function () {
+
+ this.id = 0;
+
+ this.v1 = new THREE.RenderableVertex();
+ this.v2 = new THREE.RenderableVertex();
+ this.v3 = new THREE.RenderableVertex();
+
+ this.centroidModel = new THREE.Vector3();
+
+ this.normalModel = new THREE.Vector3();
+ this.normalModelView = new THREE.Vector3();
+
+ this.vertexNormalsLength = 0;
+ this.vertexNormalsModel = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+ this.vertexNormalsModelView = [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ];
+
+ this.color = null;
+ this.material = null;
+ this.uvs = [[]];
+
+ this.z = 0;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableObject = function () {
+
+ this.id = 0;
+
+ this.object = null;
+ this.z = 0;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableParticle = function () {
+
+ this.id = 0;
+
+ this.object = null;
+
+ this.x = 0;
+ this.y = 0;
+ this.z = 0;
+
+ this.rotation = null;
+ this.scale = new THREE.Vector2();
+
+ this.material = null;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableLine = function () {
+
+ this.id = 0;
+
+ this.v1 = new THREE.RenderableVertex();
+ this.v2 = new THREE.RenderableVertex();
+
+ this.vertexColors = [ new THREE.Color(), new THREE.Color() ];
+ this.material = null;
+
+ this.z = 0;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.GeometryUtils = {
+
+ // Merge two geometries or geometry and geometry from object (using object's transform)
+
+ merge: function ( geometry1, object2 /* mesh | geometry */, materialIndexOffset ) {
+
+ var matrix, normalMatrix,
+ vertexOffset = geometry1.vertices.length,
+ uvPosition = geometry1.faceVertexUvs[ 0 ].length,
+ geometry2 = object2 instanceof THREE.Mesh ? object2.geometry : object2,
+ vertices1 = geometry1.vertices,
+ vertices2 = geometry2.vertices,
+ faces1 = geometry1.faces,
+ faces2 = geometry2.faces,
+ uvs1 = geometry1.faceVertexUvs[ 0 ],
+ uvs2 = geometry2.faceVertexUvs[ 0 ];
+
+ if ( materialIndexOffset === undefined ) materialIndexOffset = 0;
+
+ if ( object2 instanceof THREE.Mesh ) {
+
+ object2.matrixAutoUpdate && object2.updateMatrix();
+
+ matrix = object2.matrix;
+
+ normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+ }
+
+ // vertices
+
+ for ( var i = 0, il = vertices2.length; i < il; i ++ ) {
+
+ var vertex = vertices2[ i ];
+
+ var vertexCopy = vertex.clone();
+
+ if ( matrix ) vertexCopy.applyMatrix4( matrix );
+
+ vertices1.push( vertexCopy );
+
+ }
+
+ // faces
+
+ for ( i = 0, il = faces2.length; i < il; i ++ ) {
+
+ var face = faces2[ i ], faceCopy, normal, color,
+ faceVertexNormals = face.vertexNormals,
+ faceVertexColors = face.vertexColors;
+
+ faceCopy = new THREE.Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset );
+ faceCopy.normal.copy( face.normal );
+
+ if ( normalMatrix ) {
+
+ faceCopy.normal.applyMatrix3( normalMatrix ).normalize();
+
+ }
+
+ for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) {
+
+ normal = faceVertexNormals[ j ].clone();
+
+ if ( normalMatrix ) {
+
+ normal.applyMatrix3( normalMatrix ).normalize();
+
+ }
+
+ faceCopy.vertexNormals.push( normal );
+
+ }
+
+ faceCopy.color.copy( face.color );
+
+ for ( var j = 0, jl = faceVertexColors.length; j < jl; j ++ ) {
+
+ color = faceVertexColors[ j ];
+ faceCopy.vertexColors.push( color.clone() );
+
+ }
+
+ faceCopy.materialIndex = face.materialIndex + materialIndexOffset;
+
+ faceCopy.centroid.copy( face.centroid );
+
+ if ( matrix ) {
+
+ faceCopy.centroid.applyMatrix4( matrix );
+
+ }
+
+ faces1.push( faceCopy );
+
+ }
+
+ // uvs
+
+ for ( i = 0, il = uvs2.length; i < il; i ++ ) {
+
+ var uv = uvs2[ i ], uvCopy = [];
+
+ for ( var j = 0, jl = uv.length; j < jl; j ++ ) {
+
+ uvCopy.push( new THREE.Vector2( uv[ j ].x, uv[ j ].y ) );
+
+ }
+
+ uvs1.push( uvCopy );
+
+ }
+
+ },
+
+ // Get random point in triangle (via barycentric coordinates)
+ // (uniform distribution)
+ // http://www.cgafaq.info/wiki/Random_Point_In_Triangle
+
+ randomPointInTriangle: function () {
+
+ var vector = new THREE.Vector3();
+
+ return function ( vectorA, vectorB, vectorC ) {
+
+ var point = new THREE.Vector3();
+
+ var a = THREE.Math.random16();
+ var b = THREE.Math.random16();
+
+ if ( ( a + b ) > 1 ) {
+
+ a = 1 - a;
+ b = 1 - b;
+
+ }
+
+ var c = 1 - a - b;
+
+ point.copy( vectorA );
+ point.multiplyScalar( a );
+
+ vector.copy( vectorB );
+ vector.multiplyScalar( b );
+
+ point.add( vector );
+
+ vector.copy( vectorC );
+ vector.multiplyScalar( c );
+
+ point.add( vector );
+
+ return point;
+
+ };
+
+ }(),
+
+ // Get random point in face (triangle / quad)
+ // (uniform distribution)
+
+ randomPointInFace: function ( face, geometry, useCachedAreas ) {
+
+ var vA, vB, vC, vD;
+
+ vA = geometry.vertices[ face.a ];
+ vB = geometry.vertices[ face.b ];
+ vC = geometry.vertices[ face.c ];
+
+ return THREE.GeometryUtils.randomPointInTriangle( vA, vB, vC );
+
+ },
+
+ // Get uniformly distributed random points in mesh
+ // - create array with cumulative sums of face areas
+ // - pick random number from 0 to total area
+ // - find corresponding place in area array by binary search
+ // - get random point in face
+
+ randomPointsInGeometry: function ( geometry, n ) {
+
+ var face, i,
+ faces = geometry.faces,
+ vertices = geometry.vertices,
+ il = faces.length,
+ totalArea = 0,
+ cumulativeAreas = [],
+ vA, vB, vC, vD;
+
+ // precompute face areas
+
+ for ( i = 0; i < il; i ++ ) {
+
+ face = faces[ i ];
+
+ vA = vertices[ face.a ];
+ vB = vertices[ face.b ];
+ vC = vertices[ face.c ];
+
+ face._area = THREE.GeometryUtils.triangleArea( vA, vB, vC );
+
+ totalArea += face._area;
+
+ cumulativeAreas[ i ] = totalArea;
+
+ }
+
+ // binary search cumulative areas array
+
+ function binarySearchIndices( value ) {
+
+ function binarySearch( start, end ) {
+
+ // return closest larger index
+ // if exact number is not found
+
+ if ( end < start )
+ return start;
+
+ var mid = start + Math.floor( ( end - start ) / 2 );
+
+ if ( cumulativeAreas[ mid ] > value ) {
+
+ return binarySearch( start, mid - 1 );
+
+ } else if ( cumulativeAreas[ mid ] < value ) {
+
+ return binarySearch( mid + 1, end );
+
+ } else {
+
+ return mid;
+
+ }
+
+ }
+
+ var result = binarySearch( 0, cumulativeAreas.length - 1 )
+ return result;
+
+ }
+
+ // pick random face weighted by face area
+
+ var r, index,
+ result = [];
+
+ var stats = {};
+
+ for ( i = 0; i < n; i ++ ) {
+
+ r = THREE.Math.random16() * totalArea;
+
+ index = binarySearchIndices( r );
+
+ result[ i ] = THREE.GeometryUtils.randomPointInFace( faces[ index ], geometry, true );
+
+ if ( ! stats[ index ] ) {
+
+ stats[ index ] = 1;
+
+ } else {
+
+ stats[ index ] += 1;
+
+ }
+
+ }
+
+ return result;
+
+ },
+
+ // Get triangle area (half of parallelogram)
+ // http://mathworld.wolfram.com/TriangleArea.html
+
+ triangleArea: function () {
+
+ var vector1 = new THREE.Vector3();
+ var vector2 = new THREE.Vector3();
+
+ return function ( vectorA, vectorB, vectorC ) {
+
+ vector1.subVectors( vectorB, vectorA );
+ vector2.subVectors( vectorC, vectorA );
+ vector1.cross( vector2 );
+
+ return 0.5 * vector1.length();
+
+ };
+
+ }(),
+
+ // Center geometry so that 0,0,0 is in center of bounding box
+
+ center: function ( geometry ) {
+
+ geometry.computeBoundingBox();
+
+ var bb = geometry.boundingBox;
+
+ var offset = new THREE.Vector3();
+
+ offset.addVectors( bb.min, bb.max );
+ offset.multiplyScalar( -0.5 );
+
+ geometry.applyMatrix( new THREE.Matrix4().makeTranslation( offset.x, offset.y, offset.z ) );
+ geometry.computeBoundingBox();
+
+ return offset;
+
+ },
+
+ triangulateQuads: function ( geometry ) {
+
+ var i, il, j, jl;
+
+ var faces = [];
+ var faceVertexUvs = [];
+
+ for ( i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) {
+
+ faceVertexUvs[ i ] = [];
+
+ }
+
+ for ( i = 0, il = geometry.faces.length; i < il; i ++ ) {
+
+ var face = geometry.faces[ i ];
+
+ faces.push( face );
+
+ for ( j = 0, jl = geometry.faceVertexUvs.length; j < jl; j ++ ) {
+
+ faceVertexUvs[ j ].push( geometry.faceVertexUvs[ j ][ i ] );
+
+ }
+
+ }
+
+ geometry.faces = faces;
+ geometry.faceVertexUvs = faceVertexUvs;
+
+ geometry.computeCentroids();
+ geometry.computeFaceNormals();
+ geometry.computeVertexNormals();
+
+ if ( geometry.hasTangents ) geometry.computeTangents();
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ImageUtils = {
+
+ crossOrigin: 'anonymous',
+
+ loadTexture: function ( url, mapping, onLoad, onError ) {
+
+ var loader = new THREE.ImageLoader();
+ loader.crossOrigin = this.crossOrigin;
+
+ var texture = new THREE.Texture( undefined, mapping );
+
+ var image = loader.load( url, function () {
+
+ texture.needsUpdate = true;
+
+ if ( onLoad ) onLoad( texture );
+
+ } );
+
+ texture.image = image;
+ texture.sourceFile = url;
+
+ return texture;
+
+ },
+
+ loadCompressedTexture: function ( url, mapping, onLoad, onError ) {
+
+ var texture = new THREE.CompressedTexture();
+ texture.mapping = mapping;
+
+ var request = new XMLHttpRequest();
+
+ request.onload = function () {
+
+ var buffer = request.response;
+ var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+ texture.format = dds.format;
+
+ texture.mipmaps = dds.mipmaps;
+ texture.image.width = dds.width;
+ texture.image.height = dds.height;
+
+ // gl.generateMipmap fails for compressed textures
+ // mipmaps must be embedded in the DDS file
+ // or texture filters must not use mipmapping
+
+ texture.generateMipmaps = false;
+
+ texture.needsUpdate = true;
+
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ request.onerror = onError;
+
+ request.open( 'GET', url, true );
+ request.responseType = "arraybuffer";
+ request.send( null );
+
+ return texture;
+
+ },
+
+ loadTextureCube: function ( array, mapping, onLoad, onError ) {
+
+ var images = [];
+ images.loadCount = 0;
+
+ var texture = new THREE.Texture();
+ texture.image = images;
+ if ( mapping !== undefined ) texture.mapping = mapping;
+
+ // no flipping needed for cube textures
+
+ texture.flipY = false;
+
+ for ( var i = 0, il = array.length; i < il; ++ i ) {
+
+ var cubeImage = new Image();
+ images[ i ] = cubeImage;
+
+ cubeImage.onload = function () {
+
+ images.loadCount += 1;
+
+ if ( images.loadCount === 6 ) {
+
+ texture.needsUpdate = true;
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ };
+
+ cubeImage.onerror = onError;
+
+ cubeImage.crossOrigin = this.crossOrigin;
+ cubeImage.src = array[ i ];
+
+ }
+
+ return texture;
+
+ },
+
+ loadCompressedTextureCube: function ( array, mapping, onLoad, onError ) {
+
+ var images = [];
+ images.loadCount = 0;
+
+ var texture = new THREE.CompressedTexture();
+ texture.image = images;
+ if ( mapping !== undefined ) texture.mapping = mapping;
+
+ // no flipping for cube textures
+ // (also flipping doesn't work for compressed textures )
+
+ texture.flipY = false;
+
+ // can't generate mipmaps for compressed textures
+ // mips must be embedded in DDS files
+
+ texture.generateMipmaps = false;
+
+ var generateCubeFaceCallback = function ( rq, img ) {
+
+ return function () {
+
+ var buffer = rq.response;
+ var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+ img.format = dds.format;
+
+ img.mipmaps = dds.mipmaps;
+ img.width = dds.width;
+ img.height = dds.height;
+
+ images.loadCount += 1;
+
+ if ( images.loadCount === 6 ) {
+
+ texture.format = dds.format;
+ texture.needsUpdate = true;
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ }
+
+ }
+
+ // compressed cubemap textures as 6 separate DDS files
+
+ if ( array instanceof Array ) {
+
+ for ( var i = 0, il = array.length; i < il; ++ i ) {
+
+ var cubeImage = {};
+ images[ i ] = cubeImage;
+
+ var request = new XMLHttpRequest();
+
+ request.onload = generateCubeFaceCallback( request, cubeImage );
+ request.onerror = onError;
+
+ var url = array[ i ];
+
+ request.open( 'GET', url, true );
+ request.responseType = "arraybuffer";
+ request.send( null );
+
+ }
+
+ // compressed cubemap texture stored in a single DDS file
+
+ } else {
+
+ var url = array;
+ var request = new XMLHttpRequest();
+
+ request.onload = function( ) {
+
+ var buffer = request.response;
+ var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+ if ( dds.isCubemap ) {
+
+ var faces = dds.mipmaps.length / dds.mipmapCount;
+
+ for ( var f = 0; f < faces; f ++ ) {
+
+ images[ f ] = { mipmaps : [] };
+
+ for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+ images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] );
+ images[ f ].format = dds.format;
+ images[ f ].width = dds.width;
+ images[ f ].height = dds.height;
+
+ }
+
+ }
+
+ texture.format = dds.format;
+ texture.needsUpdate = true;
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ }
+
+ request.onerror = onError;
+
+ request.open( 'GET', url, true );
+ request.responseType = "arraybuffer";
+ request.send( null );
+
+ }
+
+ return texture;
+
+ },
+
+ loadDDSTexture: function ( url, mapping, onLoad, onError ) {
+
+ var images = [];
+ images.loadCount = 0;
+
+ var texture = new THREE.CompressedTexture();
+ texture.image = images;
+ if ( mapping !== undefined ) texture.mapping = mapping;
+
+ // no flipping for cube textures
+ // (also flipping doesn't work for compressed textures )
+
+ texture.flipY = false;
+
+ // can't generate mipmaps for compressed textures
+ // mips must be embedded in DDS files
+
+ texture.generateMipmaps = false;
+
+ {
+ var request = new XMLHttpRequest();
+
+ request.onload = function( ) {
+
+ var buffer = request.response;
+ var dds = THREE.ImageUtils.parseDDS( buffer, true );
+
+ if ( dds.isCubemap ) {
+
+ var faces = dds.mipmaps.length / dds.mipmapCount;
+
+ for ( var f = 0; f < faces; f ++ ) {
+
+ images[ f ] = { mipmaps : [] };
+
+ for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+ images[ f ].mipmaps.push( dds.mipmaps[ f * dds.mipmapCount + i ] );
+ images[ f ].format = dds.format;
+ images[ f ].width = dds.width;
+ images[ f ].height = dds.height;
+
+ }
+
+ }
+
+
+ } else {
+ texture.image.width = dds.width;
+ texture.image.height = dds.height;
+ texture.mipmaps = dds.mipmaps;
+ }
+
+ texture.format = dds.format;
+ texture.needsUpdate = true;
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ request.onerror = onError;
+
+ request.open( 'GET', url, true );
+ request.responseType = "arraybuffer";
+ request.send( null );
+
+ }
+
+ return texture;
+
+ },
+
+ parseDDS: function ( buffer, loadMipmaps ) {
+
+ var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 };
+
+ // Adapted from @toji's DDS utils
+ // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
+
+ // All values and structures referenced from:
+ // http://msdn.microsoft.com/en-us/library/bb943991.aspx/
+
+ var DDS_MAGIC = 0x20534444;
+
+ var DDSD_CAPS = 0x1,
+ DDSD_HEIGHT = 0x2,
+ DDSD_WIDTH = 0x4,
+ DDSD_PITCH = 0x8,
+ DDSD_PIXELFORMAT = 0x1000,
+ DDSD_MIPMAPCOUNT = 0x20000,
+ DDSD_LINEARSIZE = 0x80000,
+ DDSD_DEPTH = 0x800000;
+
+ var DDSCAPS_COMPLEX = 0x8,
+ DDSCAPS_MIPMAP = 0x400000,
+ DDSCAPS_TEXTURE = 0x1000;
+
+ var DDSCAPS2_CUBEMAP = 0x200,
+ DDSCAPS2_CUBEMAP_POSITIVEX = 0x400,
+ DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800,
+ DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000,
+ DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000,
+ DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000,
+ DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000,
+ DDSCAPS2_VOLUME = 0x200000;
+
+ var DDPF_ALPHAPIXELS = 0x1,
+ DDPF_ALPHA = 0x2,
+ DDPF_FOURCC = 0x4,
+ DDPF_RGB = 0x40,
+ DDPF_YUV = 0x200,
+ DDPF_LUMINANCE = 0x20000;
+
+ function fourCCToInt32( value ) {
+
+ return value.charCodeAt(0) +
+ (value.charCodeAt(1) << 8) +
+ (value.charCodeAt(2) << 16) +
+ (value.charCodeAt(3) << 24);
+
+ }
+
+ function int32ToFourCC( value ) {
+
+ return String.fromCharCode(
+ value & 0xff,
+ (value >> 8) & 0xff,
+ (value >> 16) & 0xff,
+ (value >> 24) & 0xff
+ );
+ }
+
+ function loadARGBMip( buffer, dataOffset, width, height ) {
+ var dataLength = width*height*4;
+ var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength );
+ var byteArray = new Uint8Array( dataLength );
+ var dst = 0;
+ var src = 0;
+ for ( var y = 0; y < height; y++ ) {
+ for ( var x = 0; x < width; x++ ) {
+ var b = srcBuffer[src]; src++;
+ var g = srcBuffer[src]; src++;
+ var r = srcBuffer[src]; src++;
+ var a = srcBuffer[src]; src++;
+ byteArray[dst] = r; dst++; //r
+ byteArray[dst] = g; dst++; //g
+ byteArray[dst] = b; dst++; //b
+ byteArray[dst] = a; dst++; //a
+ }
+ }
+ return byteArray;
+ }
+
+ var FOURCC_DXT1 = fourCCToInt32("DXT1");
+ var FOURCC_DXT3 = fourCCToInt32("DXT3");
+ var FOURCC_DXT5 = fourCCToInt32("DXT5");
+
+ var headerLengthInt = 31; // The header length in 32 bit ints
+
+ // Offsets into the header array
+
+ var off_magic = 0;
+
+ var off_size = 1;
+ var off_flags = 2;
+ var off_height = 3;
+ var off_width = 4;
+
+ var off_mipmapCount = 7;
+
+ var off_pfFlags = 20;
+ var off_pfFourCC = 21;
+ var off_RGBBitCount = 22;
+ var off_RBitMask = 23;
+ var off_GBitMask = 24;
+ var off_BBitMask = 25;
+ var off_ABitMask = 26;
+
+ var off_caps = 27;
+ var off_caps2 = 28;
+ var off_caps3 = 29;
+ var off_caps4 = 30;
+
+ // Parse header
+
+ var header = new Int32Array( buffer, 0, headerLengthInt );
+
+ if ( header[ off_magic ] !== DDS_MAGIC ) {
+
+ console.error( "ImageUtils.parseDDS(): Invalid magic number in DDS header" );
+ return dds;
+
+ }
+
+ if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
+
+ console.error( "ImageUtils.parseDDS(): Unsupported format, must contain a FourCC code" );
+ return dds;
+
+ }
+
+ var blockBytes;
+
+ var fourCC = header[ off_pfFourCC ];
+
+ var isRGBAUncompressed = false;
+
+ switch ( fourCC ) {
+
+ case FOURCC_DXT1:
+
+ blockBytes = 8;
+ dds.format = THREE.RGB_S3TC_DXT1_Format;
+ break;
+
+ case FOURCC_DXT3:
+
+ blockBytes = 16;
+ dds.format = THREE.RGBA_S3TC_DXT3_Format;
+ break;
+
+ case FOURCC_DXT5:
+
+ blockBytes = 16;
+ dds.format = THREE.RGBA_S3TC_DXT5_Format;
+ break;
+
+ default:
+
+ if( header[off_RGBBitCount] ==32
+ && header[off_RBitMask]&0xff0000
+ && header[off_GBitMask]&0xff00
+ && header[off_BBitMask]&0xff
+ && header[off_ABitMask]&0xff000000 ) {
+ isRGBAUncompressed = true;
+ blockBytes = 64;
+ dds.format = THREE.RGBAFormat;
+ } else {
+ console.error( "ImageUtils.parseDDS(): Unsupported FourCC code: ", int32ToFourCC( fourCC ) );
+ return dds;
+ }
+ }
+
+ dds.mipmapCount = 1;
+
+ if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
+
+ dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
+
+ }
+
+ //TODO: Verify that all faces of the cubemap are present with DDSCAPS2_CUBEMAP_POSITIVEX, etc.
+
+ dds.isCubemap = header[ off_caps2 ] & DDSCAPS2_CUBEMAP ? true : false;
+
+ dds.width = header[ off_width ];
+ dds.height = header[ off_height ];
+
+ var dataOffset = header[ off_size ] + 4;
+
+ // Extract mipmaps buffers
+
+ var width = dds.width;
+ var height = dds.height;
+
+ var faces = dds.isCubemap ? 6 : 1;
+
+ for ( var face = 0; face < faces; face ++ ) {
+
+ for ( var i = 0; i < dds.mipmapCount; i ++ ) {
+
+ if( isRGBAUncompressed ) {
+ var byteArray = loadARGBMip( buffer, dataOffset, width, height );
+ var dataLength = byteArray.length;
+ } else {
+ var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
+ var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
+ }
+
+ var mipmap = { "data": byteArray, "width": width, "height": height };
+ dds.mipmaps.push( mipmap );
+
+ dataOffset += dataLength;
+
+ width = Math.max( width * 0.5, 1 );
+ height = Math.max( height * 0.5, 1 );
+
+ }
+
+ width = dds.width;
+ height = dds.height;
+
+ }
+
+ return dds;
+
+ },
+
+ getNormalMap: function ( image, depth ) {
+
+ // Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/
+
+ var cross = function ( a, b ) {
+
+ return [ a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ], a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ], a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ] ];
+
+ }
+
+ var subtract = function ( a, b ) {
+
+ return [ a[ 0 ] - b[ 0 ], a[ 1 ] - b[ 1 ], a[ 2 ] - b[ 2 ] ];
+
+ }
+
+ var normalize = function ( a ) {
+
+ var l = Math.sqrt( a[ 0 ] * a[ 0 ] + a[ 1 ] * a[ 1 ] + a[ 2 ] * a[ 2 ] );
+ return [ a[ 0 ] / l, a[ 1 ] / l, a[ 2 ] / l ];
+
+ }
+
+ depth = depth | 1;
+
+ var width = image.width;
+ var height = image.height;
+
+ var canvas = document.createElement( 'canvas' );
+ canvas.width = width;
+ canvas.height = height;
+
+ var context = canvas.getContext( '2d' );
+ context.drawImage( image, 0, 0 );
+
+ var data = context.getImageData( 0, 0, width, height ).data;
+ var imageData = context.createImageData( width, height );
+ var output = imageData.data;
+
+ for ( var x = 0; x < width; x ++ ) {
+
+ for ( var y = 0; y < height; y ++ ) {
+
+ var ly = y - 1 < 0 ? 0 : y - 1;
+ var uy = y + 1 > height - 1 ? height - 1 : y + 1;
+ var lx = x - 1 < 0 ? 0 : x - 1;
+ var ux = x + 1 > width - 1 ? width - 1 : x + 1;
+
+ var points = [];
+ var origin = [ 0, 0, data[ ( y * width + x ) * 4 ] / 255 * depth ];
+ points.push( [ - 1, 0, data[ ( y * width + lx ) * 4 ] / 255 * depth ] );
+ points.push( [ - 1, - 1, data[ ( ly * width + lx ) * 4 ] / 255 * depth ] );
+ points.push( [ 0, - 1, data[ ( ly * width + x ) * 4 ] / 255 * depth ] );
+ points.push( [ 1, - 1, data[ ( ly * width + ux ) * 4 ] / 255 * depth ] );
+ points.push( [ 1, 0, data[ ( y * width + ux ) * 4 ] / 255 * depth ] );
+ points.push( [ 1, 1, data[ ( uy * width + ux ) * 4 ] / 255 * depth ] );
+ points.push( [ 0, 1, data[ ( uy * width + x ) * 4 ] / 255 * depth ] );
+ points.push( [ - 1, 1, data[ ( uy * width + lx ) * 4 ] / 255 * depth ] );
+
+ var normals = [];
+ var num_points = points.length;
+
+ for ( var i = 0; i < num_points; i ++ ) {
+
+ var v1 = points[ i ];
+ var v2 = points[ ( i + 1 ) % num_points ];
+ v1 = subtract( v1, origin );
+ v2 = subtract( v2, origin );
+ normals.push( normalize( cross( v1, v2 ) ) );
+
+ }
+
+ var normal = [ 0, 0, 0 ];
+
+ for ( var i = 0; i < normals.length; i ++ ) {
+
+ normal[ 0 ] += normals[ i ][ 0 ];
+ normal[ 1 ] += normals[ i ][ 1 ];
+ normal[ 2 ] += normals[ i ][ 2 ];
+
+ }
+
+ normal[ 0 ] /= normals.length;
+ normal[ 1 ] /= normals.length;
+ normal[ 2 ] /= normals.length;
+
+ var idx = ( y * width + x ) * 4;
+
+ output[ idx ] = ( ( normal[ 0 ] + 1.0 ) / 2.0 * 255 ) | 0;
+ output[ idx + 1 ] = ( ( normal[ 1 ] + 1.0 ) / 2.0 * 255 ) | 0;
+ output[ idx + 2 ] = ( normal[ 2 ] * 255 ) | 0;
+ output[ idx + 3 ] = 255;
+
+ }
+
+ }
+
+ context.putImageData( imageData, 0, 0 );
+
+ return canvas;
+
+ },
+
+ generateDataTexture: function ( width, height, color ) {
+
+ var size = width * height;
+ var data = new Uint8Array( 3 * size );
+
+ var r = Math.floor( color.r * 255 );
+ var g = Math.floor( color.g * 255 );
+ var b = Math.floor( color.b * 255 );
+
+ for ( var i = 0; i < size; i ++ ) {
+
+ data[ i * 3 ] = r;
+ data[ i * 3 + 1 ] = g;
+ data[ i * 3 + 2 ] = b;
+
+ }
+
+ var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat );
+ texture.needsUpdate = true;
+
+ return texture;
+
+ }
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SceneUtils = {
+
+ createMultiMaterialObject: function ( geometry, materials ) {
+
+ var group = new THREE.Object3D();
+
+ for ( var i = 0, l = materials.length; i < l; i ++ ) {
+
+ group.add( new THREE.Mesh( geometry, materials[ i ] ) );
+
+ }
+
+ return group;
+
+ },
+
+ detach : function ( child, parent, scene ) {
+
+ child.applyMatrix( parent.matrixWorld );
+ parent.remove( child );
+ scene.add( child );
+
+ },
+
+ attach: function ( child, scene, parent ) {
+
+ var matrixWorldInverse = new THREE.Matrix4();
+ matrixWorldInverse.getInverse( parent.matrixWorld );
+ child.applyMatrix( matrixWorldInverse );
+
+ scene.remove( child );
+ parent.add( child );
+
+ }
+
+};
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * For Text operations in three.js (See TextGeometry)
+ *
+ * It uses techniques used in:
+ *
+ * typeface.js and canvastext
+ * For converting fonts and rendering with javascript
+ * http://typeface.neocracy.org
+ *
+ * Triangulation ported from AS3
+ * Simple Polygon Triangulation
+ * http://actionsnippet.com/?p=1462
+ *
+ * A Method to triangulate shapes with holes
+ * http://www.sakri.net/blog/2009/06/12/an-approach-to-triangulating-polygons-with-holes/
+ *
+ */
+
+THREE.FontUtils = {
+
+ faces : {},
+
+ // Just for now. face[weight][style]
+
+ face : "helvetiker",
+ weight: "normal",
+ style : "normal",
+ size : 150,
+ divisions : 10,
+
+ getFace : function() {
+
+ return this.faces[ this.face ][ this.weight ][ this.style ];
+
+ },
+
+ loadFace : function( data ) {
+
+ var family = data.familyName.toLowerCase();
+
+ var ThreeFont = this;
+
+ ThreeFont.faces[ family ] = ThreeFont.faces[ family ] || {};
+
+ ThreeFont.faces[ family ][ data.cssFontWeight ] = ThreeFont.faces[ family ][ data.cssFontWeight ] || {};
+ ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data;
+
+ var face = ThreeFont.faces[ family ][ data.cssFontWeight ][ data.cssFontStyle ] = data;
+
+ return data;
+
+ },
+
+ drawText : function( text ) {
+
+ var characterPts = [], allPts = [];
+
+ // RenderText
+
+ var i, p,
+ face = this.getFace(),
+ scale = this.size / face.resolution,
+ offset = 0,
+ chars = String( text ).split( '' ),
+ length = chars.length;
+
+ var fontPaths = [];
+
+ for ( i = 0; i < length; i ++ ) {
+
+ var path = new THREE.Path();
+
+ var ret = this.extractGlyphPoints( chars[ i ], face, scale, offset, path );
+ offset += ret.offset;
+
+ fontPaths.push( ret.path );
+
+ }
+
+ // get the width
+
+ var width = offset / 2;
+ //
+ // for ( p = 0; p < allPts.length; p++ ) {
+ //
+ // allPts[ p ].x -= width;
+ //
+ // }
+
+ //var extract = this.extractPoints( allPts, characterPts );
+ //extract.contour = allPts;
+
+ //extract.paths = fontPaths;
+ //extract.offset = width;
+
+ return { paths : fontPaths, offset : width };
+
+ },
+
+
+
+
+ extractGlyphPoints : function( c, face, scale, offset, path ) {
+
+ var pts = [];
+
+ var i, i2, divisions,
+ outline, action, length,
+ scaleX, scaleY,
+ x, y, cpx, cpy, cpx0, cpy0, cpx1, cpy1, cpx2, cpy2,
+ laste,
+ glyph = face.glyphs[ c ] || face.glyphs[ '?' ];
+
+ if ( !glyph ) return;
+
+ if ( glyph.o ) {
+
+ outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
+ length = outline.length;
+
+ scaleX = scale;
+ scaleY = scale;
+
+ for ( i = 0; i < length; ) {
+
+ action = outline[ i ++ ];
+
+ //console.log( action );
+
+ switch( action ) {
+
+ case 'm':
+
+ // Move To
+
+ x = outline[ i++ ] * scaleX + offset;
+ y = outline[ i++ ] * scaleY;
+
+ path.moveTo( x, y );
+ break;
+
+ case 'l':
+
+ // Line To
+
+ x = outline[ i++ ] * scaleX + offset;
+ y = outline[ i++ ] * scaleY;
+ path.lineTo(x,y);
+ break;
+
+ case 'q':
+
+ // QuadraticCurveTo
+
+ cpx = outline[ i++ ] * scaleX + offset;
+ cpy = outline[ i++ ] * scaleY;
+ cpx1 = outline[ i++ ] * scaleX + offset;
+ cpy1 = outline[ i++ ] * scaleY;
+
+ path.quadraticCurveTo(cpx1, cpy1, cpx, cpy);
+
+ laste = pts[ pts.length - 1 ];
+
+ if ( laste ) {
+
+ cpx0 = laste.x;
+ cpy0 = laste.y;
+
+ for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) {
+
+ var t = i2 / divisions;
+ var tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx );
+ var ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy );
+ }
+
+ }
+
+ break;
+
+ case 'b':
+
+ // Cubic Bezier Curve
+
+ cpx = outline[ i++ ] * scaleX + offset;
+ cpy = outline[ i++ ] * scaleY;
+ cpx1 = outline[ i++ ] * scaleX + offset;
+ cpy1 = outline[ i++ ] * -scaleY;
+ cpx2 = outline[ i++ ] * scaleX + offset;
+ cpy2 = outline[ i++ ] * -scaleY;
+
+ path.bezierCurveTo( cpx, cpy, cpx1, cpy1, cpx2, cpy2 );
+
+ laste = pts[ pts.length - 1 ];
+
+ if ( laste ) {
+
+ cpx0 = laste.x;
+ cpy0 = laste.y;
+
+ for ( i2 = 1, divisions = this.divisions; i2 <= divisions; i2 ++ ) {
+
+ var t = i2 / divisions;
+ var tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx );
+ var ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy );
+
+ }
+
+ }
+
+ break;
+
+ }
+
+ }
+ }
+
+
+
+ return { offset: glyph.ha*scale, path:path};
+ }
+
+};
+
+
+THREE.FontUtils.generateShapes = function( text, parameters ) {
+
+ // Parameters
+
+ parameters = parameters || {};
+
+ var size = parameters.size !== undefined ? parameters.size : 100;
+ var curveSegments = parameters.curveSegments !== undefined ? parameters.curveSegments: 4;
+
+ var font = parameters.font !== undefined ? parameters.font : "helvetiker";
+ var weight = parameters.weight !== undefined ? parameters.weight : "normal";
+ var style = parameters.style !== undefined ? parameters.style : "normal";
+
+ THREE.FontUtils.size = size;
+ THREE.FontUtils.divisions = curveSegments;
+
+ THREE.FontUtils.face = font;
+ THREE.FontUtils.weight = weight;
+ THREE.FontUtils.style = style;
+
+ // Get a Font data json object
+
+ var data = THREE.FontUtils.drawText( text );
+
+ var paths = data.paths;
+ var shapes = [];
+
+ for ( var p = 0, pl = paths.length; p < pl; p ++ ) {
+
+ Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+
+ }
+
+ return shapes;
+
+};
+
+
+/**
+ * This code is a quick port of code written in C++ which was submitted to
+ * flipcode.com by John W. Ratcliff // July 22, 2000
+ * See original code and more information here:
+ * http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml
+ *
+ * ported to actionscript by Zevan Rosser
+ * www.actionsnippet.com
+ *
+ * ported to javascript by Joshua Koo
+ * http://www.lab4games.net/zz85/blog
+ *
+ */
+
+
+( function( namespace ) {
+
+ var EPSILON = 0.0000000001;
+
+ // takes in an contour array and returns
+
+ var process = function( contour, indices ) {
+
+ var n = contour.length;
+
+ if ( n < 3 ) return null;
+
+ var result = [],
+ verts = [],
+ vertIndices = [];
+
+ /* we want a counter-clockwise polygon in verts */
+
+ var u, v, w;
+
+ if ( area( contour ) > 0.0 ) {
+
+ for ( v = 0; v < n; v++ ) verts[ v ] = v;
+
+ } else {
+
+ for ( v = 0; v < n; v++ ) verts[ v ] = ( n - 1 ) - v;
+
+ }
+
+ var nv = n;
+
+ /* remove nv - 2 vertices, creating 1 triangle every time */
+
+ var count = 2 * nv; /* error detection */
+
+ for( v = nv - 1; nv > 2; ) {
+
+ /* if we loop, it is probably a non-simple polygon */
+
+ if ( ( count-- ) <= 0 ) {
+
+ //** Triangulate: ERROR - probable bad polygon!
+
+ //throw ( "Warning, unable to triangulate polygon!" );
+ //return null;
+ // Sometimes warning is fine, especially polygons are triangulated in reverse.
+ console.log( "Warning, unable to triangulate polygon!" );
+
+ if ( indices ) return vertIndices;
+ return result;
+
+ }
+
+ /* three consecutive vertices in current polygon, */
+
+ u = v; if ( nv <= u ) u = 0; /* previous */
+ v = u + 1; if ( nv <= v ) v = 0; /* new v */
+ w = v + 1; if ( nv <= w ) w = 0; /* next */
+
+ if ( snip( contour, u, v, w, nv, verts ) ) {
+
+ var a, b, c, s, t;
+
+ /* true names of the vertices */
+
+ a = verts[ u ];
+ b = verts[ v ];
+ c = verts[ w ];
+
+ /* output Triangle */
+
+ result.push( [ contour[ a ],
+ contour[ b ],
+ contour[ c ] ] );
+
+
+ vertIndices.push( [ verts[ u ], verts[ v ], verts[ w ] ] );
+
+ /* remove v from the remaining polygon */
+
+ for( s = v, t = v + 1; t < nv; s++, t++ ) {
+
+ verts[ s ] = verts[ t ];
+
+ }
+
+ nv--;
+
+ /* reset error detection counter */
+
+ count = 2 * nv;
+
+ }
+
+ }
+
+ if ( indices ) return vertIndices;
+ return result;
+
+ };
+
+ // calculate area of the contour polygon
+
+ var area = function ( contour ) {
+
+ var n = contour.length;
+ var a = 0.0;
+
+ for( var p = n - 1, q = 0; q < n; p = q++ ) {
+
+ a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
+
+ }
+
+ return a * 0.5;
+
+ };
+
+ var snip = function ( contour, u, v, w, n, verts ) {
+
+ var p;
+ var ax, ay, bx, by;
+ var cx, cy, px, py;
+
+ ax = contour[ verts[ u ] ].x;
+ ay = contour[ verts[ u ] ].y;
+
+ bx = contour[ verts[ v ] ].x;
+ by = contour[ verts[ v ] ].y;
+
+ cx = contour[ verts[ w ] ].x;
+ cy = contour[ verts[ w ] ].y;
+
+ if ( EPSILON > (((bx-ax)*(cy-ay)) - ((by-ay)*(cx-ax))) ) return false;
+
+ var aX, aY, bX, bY, cX, cY;
+ var apx, apy, bpx, bpy, cpx, cpy;
+ var cCROSSap, bCROSScp, aCROSSbp;
+
+ aX = cx - bx; aY = cy - by;
+ bX = ax - cx; bY = ay - cy;
+ cX = bx - ax; cY = by - ay;
+
+ for ( p = 0; p < n; p++ ) {
+
+ if( (p === u) || (p === v) || (p === w) ) continue;
+
+ px = contour[ verts[ p ] ].x
+ py = contour[ verts[ p ] ].y
+
+ apx = px - ax; apy = py - ay;
+ bpx = px - bx; bpy = py - by;
+ cpx = px - cx; cpy = py - cy;
+
+ // see if p is inside triangle abc
+
+ aCROSSbp = aX*bpy - aY*bpx;
+ cCROSSap = cX*apy - cY*apx;
+ bCROSScp = bX*cpy - bY*cpx;
+
+ if ( (aCROSSbp >= -EPSILON) && (bCROSScp >= -EPSILON) && (cCROSSap >= -EPSILON) ) return false;
+
+ }
+
+ return true;
+
+ };
+
+
+ namespace.Triangulate = process;
+ namespace.Triangulate.area = area;
+
+ return namespace;
+
+})(THREE.FontUtils);
+
+// To use the typeface.js face files, hook up the API
+self._typeface_js = { faces: THREE.FontUtils.faces, loadFace: THREE.FontUtils.loadFace };
+THREE.typeface_js = self._typeface_js;
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Extensible curve object
+ *
+ * Some common of Curve methods
+ * .getPoint(t), getTangent(t)
+ * .getPointAt(u), getTagentAt(u)
+ * .getPoints(), .getSpacedPoints()
+ * .getLength()
+ * .updateArcLengths()
+ *
+ * This following classes subclasses THREE.Curve:
+ *
+ * -- 2d classes --
+ * THREE.LineCurve
+ * THREE.QuadraticBezierCurve
+ * THREE.CubicBezierCurve
+ * THREE.SplineCurve
+ * THREE.ArcCurve
+ * THREE.EllipseCurve
+ *
+ * -- 3d classes --
+ * THREE.LineCurve3
+ * THREE.QuadraticBezierCurve3
+ * THREE.CubicBezierCurve3
+ * THREE.SplineCurve3
+ * THREE.ClosedSplineCurve3
+ *
+ * A series of curves can be represented as a THREE.CurvePath
+ *
+ **/
+
+/**************************************************************
+ * Abstract Curve base class
+ **************************************************************/
+
+THREE.Curve = function () {
+
+};
+
+// Virtual base class method to overwrite and implement in subclasses
+// - t [0 .. 1]
+
+THREE.Curve.prototype.getPoint = function ( t ) {
+
+ console.log( "Warning, getPoint() not implemented!" );
+ return null;
+
+};
+
+// Get point at relative position in curve according to arc length
+// - u [0 .. 1]
+
+THREE.Curve.prototype.getPointAt = function ( u ) {
+
+ var t = this.getUtoTmapping( u );
+ return this.getPoint( t );
+
+};
+
+// Get sequence of points using getPoint( t )
+
+THREE.Curve.prototype.getPoints = function ( divisions ) {
+
+ if ( !divisions ) divisions = 5;
+
+ var d, pts = [];
+
+ for ( d = 0; d <= divisions; d ++ ) {
+
+ pts.push( this.getPoint( d / divisions ) );
+
+ }
+
+ return pts;
+
+};
+
+// Get sequence of points using getPointAt( u )
+
+THREE.Curve.prototype.getSpacedPoints = function ( divisions ) {
+
+ if ( !divisions ) divisions = 5;
+
+ var d, pts = [];
+
+ for ( d = 0; d <= divisions; d ++ ) {
+
+ pts.push( this.getPointAt( d / divisions ) );
+
+ }
+
+ return pts;
+
+};
+
+// Get total curve arc length
+
+THREE.Curve.prototype.getLength = function () {
+
+ var lengths = this.getLengths();
+ return lengths[ lengths.length - 1 ];
+
+};
+
+// Get list of cumulative segment lengths
+
+THREE.Curve.prototype.getLengths = function ( divisions ) {
+
+ if ( !divisions ) divisions = (this.__arcLengthDivisions) ? (this.__arcLengthDivisions): 200;
+
+ if ( this.cacheArcLengths
+ && ( this.cacheArcLengths.length == divisions + 1 )
+ && !this.needsUpdate) {
+
+ //console.log( "cached", this.cacheArcLengths );
+ return this.cacheArcLengths;
+
+ }
+
+ this.needsUpdate = false;
+
+ var cache = [];
+ var current, last = this.getPoint( 0 );
+ var p, sum = 0;
+
+ cache.push( 0 );
+
+ for ( p = 1; p <= divisions; p ++ ) {
+
+ current = this.getPoint ( p / divisions );
+ sum += current.distanceTo( last );
+ cache.push( sum );
+ last = current;
+
+ }
+
+ this.cacheArcLengths = cache;
+
+ return cache; // { sums: cache, sum:sum }; Sum is in the last element.
+
+};
+
+
+THREE.Curve.prototype.updateArcLengths = function() {
+ this.needsUpdate = true;
+ this.getLengths();
+};
+
+// Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equi distance
+
+THREE.Curve.prototype.getUtoTmapping = function ( u, distance ) {
+
+ var arcLengths = this.getLengths();
+
+ var i = 0, il = arcLengths.length;
+
+ var targetArcLength; // The targeted u distance value to get
+
+ if ( distance ) {
+
+ targetArcLength = distance;
+
+ } else {
+
+ targetArcLength = u * arcLengths[ il - 1 ];
+
+ }
+
+ //var time = Date.now();
+
+ // binary search for the index with largest value smaller than target u distance
+
+ var low = 0, high = il - 1, comparison;
+
+ while ( low <= high ) {
+
+ i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
+
+ comparison = arcLengths[ i ] - targetArcLength;
+
+ if ( comparison < 0 ) {
+
+ low = i + 1;
+ continue;
+
+ } else if ( comparison > 0 ) {
+
+ high = i - 1;
+ continue;
+
+ } else {
+
+ high = i;
+ break;
+
+ // DONE
+
+ }
+
+ }
+
+ i = high;
+
+ //console.log('b' , i, low, high, Date.now()- time);
+
+ if ( arcLengths[ i ] == targetArcLength ) {
+
+ var t = i / ( il - 1 );
+ return t;
+
+ }
+
+ // we could get finer grain at lengths, or use simple interpolatation between two points
+
+ var lengthBefore = arcLengths[ i ];
+ var lengthAfter = arcLengths[ i + 1 ];
+
+ var segmentLength = lengthAfter - lengthBefore;
+
+ // determine where we are between the 'before' and 'after' points
+
+ var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
+
+ // add that fractional amount to t
+
+ var t = ( i + segmentFraction ) / ( il -1 );
+
+ return t;
+
+};
+
+// Returns a unit vector tangent at t
+// In case any sub curve does not implement its tangent derivation,
+// 2 points a small delta apart will be used to find its gradient
+// which seems to give a reasonable approximation
+
+THREE.Curve.prototype.getTangent = function( t ) {
+
+ var delta = 0.0001;
+ var t1 = t - delta;
+ var t2 = t + delta;
+
+ // Capping in case of danger
+
+ if ( t1 < 0 ) t1 = 0;
+ if ( t2 > 1 ) t2 = 1;
+
+ var pt1 = this.getPoint( t1 );
+ var pt2 = this.getPoint( t2 );
+
+ var vec = pt2.clone().sub(pt1);
+ return vec.normalize();
+
+};
+
+
+THREE.Curve.prototype.getTangentAt = function ( u ) {
+
+ var t = this.getUtoTmapping( u );
+ return this.getTangent( t );
+
+};
+
+
+
+
+
+/**************************************************************
+ * Utils
+ **************************************************************/
+
+THREE.Curve.Utils = {
+
+ tangentQuadraticBezier: function ( t, p0, p1, p2 ) {
+
+ return 2 * ( 1 - t ) * ( p1 - p0 ) + 2 * t * ( p2 - p1 );
+
+ },
+
+ // Puay Bing, thanks for helping with this derivative!
+
+ tangentCubicBezier: function (t, p0, p1, p2, p3 ) {
+
+ return -3 * p0 * (1 - t) * (1 - t) +
+ 3 * p1 * (1 - t) * (1-t) - 6 *t *p1 * (1-t) +
+ 6 * t * p2 * (1-t) - 3 * t * t * p2 +
+ 3 * t * t * p3;
+ },
+
+
+ tangentSpline: function ( t, p0, p1, p2, p3 ) {
+
+ // To check if my formulas are correct
+
+ var h00 = 6 * t * t - 6 * t; // derived from 2t^3 − 3t^2 + 1
+ var h10 = 3 * t * t - 4 * t + 1; // t^3 − 2t^2 + t
+ var h01 = -6 * t * t + 6 * t; // − 2t3 + 3t2
+ var h11 = 3 * t * t - 2 * t; // t3 − t2
+
+ return h00 + h10 + h01 + h11;
+
+ },
+
+ // Catmull-Rom
+
+ interpolate: function( p0, p1, p2, p3, t ) {
+
+ var v0 = ( p2 - p0 ) * 0.5;
+ var v1 = ( p3 - p1 ) * 0.5;
+ var t2 = t * t;
+ var t3 = t * t2;
+ return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+ }
+
+};
+
+
+// TODO: Transformation for Curves?
+
+/**************************************************************
+ * 3D Curves
+ **************************************************************/
+
+// A Factory method for creating new curve subclasses
+
+THREE.Curve.create = function ( constructor, getPointFunc ) {
+
+ constructor.prototype = Object.create( THREE.Curve.prototype );
+ constructor.prototype.getPoint = getPointFunc;
+
+ return constructor;
+
+};
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ *
+ **/
+
+/**************************************************************
+ * Curved Path - a curve path is simply a array of connected
+ * curves, but retains the api of a curve
+ **************************************************************/
+
+THREE.CurvePath = function () {
+
+ this.curves = [];
+ this.bends = [];
+
+ this.autoClose = false; // Automatically closes the path
+};
+
+THREE.CurvePath.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.CurvePath.prototype.add = function ( curve ) {
+
+ this.curves.push( curve );
+
+};
+
+THREE.CurvePath.prototype.checkConnection = function() {
+ // TODO
+ // If the ending of curve is not connected to the starting
+ // or the next curve, then, this is not a real path
+};
+
+THREE.CurvePath.prototype.closePath = function() {
+ // TODO Test
+ // and verify for vector3 (needs to implement equals)
+ // Add a line curve if start and end of lines are not connected
+ var startPoint = this.curves[0].getPoint(0);
+ var endPoint = this.curves[this.curves.length-1].getPoint(1);
+
+ if (!startPoint.equals(endPoint)) {
+ this.curves.push( new THREE.LineCurve(endPoint, startPoint) );
+ }
+
+};
+
+// To get accurate point with reference to
+// entire path distance at time t,
+// following has to be done:
+
+// 1. Length of each sub path have to be known
+// 2. Locate and identify type of curve
+// 3. Get t for the curve
+// 4. Return curve.getPointAt(t')
+
+THREE.CurvePath.prototype.getPoint = function( t ) {
+
+ var d = t * this.getLength();
+ var curveLengths = this.getCurveLengths();
+ var i = 0, diff, curve;
+
+ // To think about boundaries points.
+
+ while ( i < curveLengths.length ) {
+
+ if ( curveLengths[ i ] >= d ) {
+
+ diff = curveLengths[ i ] - d;
+ curve = this.curves[ i ];
+
+ var u = 1 - diff / curve.getLength();
+
+ return curve.getPointAt( u );
+
+ break;
+ }
+
+ i ++;
+
+ }
+
+ return null;
+
+ // loop where sum != 0, sum > d , sum+1 maxX ) maxX = p.x;
+ else if ( p.x < minX ) minX = p.x;
+
+ if ( p.y > maxY ) maxY = p.y;
+ else if ( p.y < minY ) minY = p.y;
+
+ if ( v3 ) {
+
+ if ( p.z > maxZ ) maxZ = p.z;
+ else if ( p.z < minZ ) minZ = p.z;
+
+ }
+
+ sum.add( p );
+
+ }
+
+ var ret = {
+
+ minX: minX,
+ minY: minY,
+ maxX: maxX,
+ maxY: maxY,
+ centroid: sum.divideScalar( il )
+
+ };
+
+ if ( v3 ) {
+
+ ret.maxZ = maxZ;
+ ret.minZ = minZ;
+
+ }
+
+ return ret;
+
+};
+
+/**************************************************************
+ * Create Geometries Helpers
+ **************************************************************/
+
+/// Generate geometry from path points (for Line or ParticleSystem objects)
+
+THREE.CurvePath.prototype.createPointsGeometry = function( divisions ) {
+
+ var pts = this.getPoints( divisions, true );
+ return this.createGeometry( pts );
+
+};
+
+// Generate geometry from equidistance sampling along the path
+
+THREE.CurvePath.prototype.createSpacedPointsGeometry = function( divisions ) {
+
+ var pts = this.getSpacedPoints( divisions, true );
+ return this.createGeometry( pts );
+
+};
+
+THREE.CurvePath.prototype.createGeometry = function( points ) {
+
+ var geometry = new THREE.Geometry();
+
+ for ( var i = 0; i < points.length; i ++ ) {
+
+ geometry.vertices.push( new THREE.Vector3( points[ i ].x, points[ i ].y, points[ i ].z || 0) );
+
+ }
+
+ return geometry;
+
+};
+
+
+/**************************************************************
+ * Bend / Wrap Helper Methods
+ **************************************************************/
+
+// Wrap path / Bend modifiers?
+
+THREE.CurvePath.prototype.addWrapPath = function ( bendpath ) {
+
+ this.bends.push( bendpath );
+
+};
+
+THREE.CurvePath.prototype.getTransformedPoints = function( segments, bends ) {
+
+ var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints
+ var i, il;
+
+ if ( !bends ) {
+
+ bends = this.bends;
+
+ }
+
+ for ( i = 0, il = bends.length; i < il; i ++ ) {
+
+ oldPts = this.getWrapPoints( oldPts, bends[ i ] );
+
+ }
+
+ return oldPts;
+
+};
+
+THREE.CurvePath.prototype.getTransformedSpacedPoints = function( segments, bends ) {
+
+ var oldPts = this.getSpacedPoints( segments );
+
+ var i, il;
+
+ if ( !bends ) {
+
+ bends = this.bends;
+
+ }
+
+ for ( i = 0, il = bends.length; i < il; i ++ ) {
+
+ oldPts = this.getWrapPoints( oldPts, bends[ i ] );
+
+ }
+
+ return oldPts;
+
+};
+
+// This returns getPoints() bend/wrapped around the contour of a path.
+// Read http://www.planetclegg.com/projects/WarpingTextToSplines.html
+
+THREE.CurvePath.prototype.getWrapPoints = function ( oldPts, path ) {
+
+ var bounds = this.getBoundingBox();
+
+ var i, il, p, oldX, oldY, xNorm;
+
+ for ( i = 0, il = oldPts.length; i < il; i ++ ) {
+
+ p = oldPts[ i ];
+
+ oldX = p.x;
+ oldY = p.y;
+
+ xNorm = oldX / bounds.maxX;
+
+ // If using actual distance, for length > path, requires line extrusions
+ //xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance
+
+ xNorm = path.getUtoTmapping( xNorm, oldX );
+
+ // check for out of bounds?
+
+ var pathPt = path.getPoint( xNorm );
+ var normal = path.getNormalVector( xNorm ).multiplyScalar( oldY );
+
+ p.x = pathPt.x + normal.x;
+ p.y = pathPt.y + normal.y;
+
+ }
+
+ return oldPts;
+
+};
+
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Gyroscope = function () {
+
+ THREE.Object3D.call( this );
+
+};
+
+THREE.Gyroscope.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.Gyroscope.prototype.updateMatrixWorld = function ( force ) {
+
+ this.matrixAutoUpdate && this.updateMatrix();
+
+ // update matrixWorld
+
+ if ( this.matrixWorldNeedsUpdate || force ) {
+
+ if ( this.parent ) {
+
+ this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+ this.matrixWorld.decompose( this.translationWorld, this.quaternionWorld, this.scaleWorld );
+ this.matrix.decompose( this.translationObject, this.quaternionObject, this.scaleObject );
+
+ this.matrixWorld.compose( this.translationWorld, this.quaternionObject, this.scaleWorld );
+
+
+ } else {
+
+ this.matrixWorld.copy( this.matrix );
+
+ }
+
+
+ this.matrixWorldNeedsUpdate = false;
+
+ force = true;
+
+ }
+
+ // update children
+
+ for ( var i = 0, l = this.children.length; i < l; i ++ ) {
+
+ this.children[ i ].updateMatrixWorld( force );
+
+ }
+
+};
+
+THREE.Gyroscope.prototype.translationWorld = new THREE.Vector3();
+THREE.Gyroscope.prototype.translationObject = new THREE.Vector3();
+THREE.Gyroscope.prototype.quaternionWorld = new THREE.Quaternion();
+THREE.Gyroscope.prototype.quaternionObject = new THREE.Quaternion();
+THREE.Gyroscope.prototype.scaleWorld = new THREE.Vector3();
+THREE.Gyroscope.prototype.scaleObject = new THREE.Vector3();
+
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Creates free form 2d path using series of points, lines or curves.
+ *
+ **/
+
+THREE.Path = function ( points ) {
+
+ THREE.CurvePath.call(this);
+
+ this.actions = [];
+
+ if ( points ) {
+
+ this.fromPoints( points );
+
+ }
+
+};
+
+THREE.Path.prototype = Object.create( THREE.CurvePath.prototype );
+
+THREE.PathActions = {
+
+ MOVE_TO: 'moveTo',
+ LINE_TO: 'lineTo',
+ QUADRATIC_CURVE_TO: 'quadraticCurveTo', // Bezier quadratic curve
+ BEZIER_CURVE_TO: 'bezierCurveTo', // Bezier cubic curve
+ CSPLINE_THRU: 'splineThru', // Catmull-rom spline
+ ARC: 'arc', // Circle
+ ELLIPSE: 'ellipse'
+};
+
+// TODO Clean up PATH API
+
+// Create path using straight lines to connect all points
+// - vectors: array of Vector2
+
+THREE.Path.prototype.fromPoints = function ( vectors ) {
+
+ this.moveTo( vectors[ 0 ].x, vectors[ 0 ].y );
+
+ for ( var v = 1, vlen = vectors.length; v < vlen; v ++ ) {
+
+ this.lineTo( vectors[ v ].x, vectors[ v ].y );
+
+ };
+
+};
+
+// startPath() endPath()?
+
+THREE.Path.prototype.moveTo = function ( x, y ) {
+
+ var args = Array.prototype.slice.call( arguments );
+ this.actions.push( { action: THREE.PathActions.MOVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.lineTo = function ( x, y ) {
+
+ var args = Array.prototype.slice.call( arguments );
+
+ var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ var curve = new THREE.LineCurve( new THREE.Vector2( x0, y0 ), new THREE.Vector2( x, y ) );
+ this.curves.push( curve );
+
+ this.actions.push( { action: THREE.PathActions.LINE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.quadraticCurveTo = function( aCPx, aCPy, aX, aY ) {
+
+ var args = Array.prototype.slice.call( arguments );
+
+ var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ var curve = new THREE.QuadraticBezierCurve( new THREE.Vector2( x0, y0 ),
+ new THREE.Vector2( aCPx, aCPy ),
+ new THREE.Vector2( aX, aY ) );
+ this.curves.push( curve );
+
+ this.actions.push( { action: THREE.PathActions.QUADRATIC_CURVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.bezierCurveTo = function( aCP1x, aCP1y,
+ aCP2x, aCP2y,
+ aX, aY ) {
+
+ var args = Array.prototype.slice.call( arguments );
+
+ var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ var curve = new THREE.CubicBezierCurve( new THREE.Vector2( x0, y0 ),
+ new THREE.Vector2( aCP1x, aCP1y ),
+ new THREE.Vector2( aCP2x, aCP2y ),
+ new THREE.Vector2( aX, aY ) );
+ this.curves.push( curve );
+
+ this.actions.push( { action: THREE.PathActions.BEZIER_CURVE_TO, args: args } );
+
+};
+
+THREE.Path.prototype.splineThru = function( pts /*Array of Vector*/ ) {
+
+ var args = Array.prototype.slice.call( arguments );
+ var lastargs = this.actions[ this.actions.length - 1 ].args;
+
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+//---
+ var npts = [ new THREE.Vector2( x0, y0 ) ];
+ Array.prototype.push.apply( npts, pts );
+
+ var curve = new THREE.SplineCurve( npts );
+ this.curves.push( curve );
+
+ this.actions.push( { action: THREE.PathActions.CSPLINE_THRU, args: args } );
+
+};
+
+// FUTURE: Change the API or follow canvas API?
+
+THREE.Path.prototype.arc = function ( aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise ) {
+
+ var lastargs = this.actions[ this.actions.length - 1].args;
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ this.absarc(aX + x0, aY + y0, aRadius,
+ aStartAngle, aEndAngle, aClockwise );
+
+ };
+
+ THREE.Path.prototype.absarc = function ( aX, aY, aRadius,
+ aStartAngle, aEndAngle, aClockwise ) {
+ this.absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);
+ };
+
+THREE.Path.prototype.ellipse = function ( aX, aY, xRadius, yRadius,
+ aStartAngle, aEndAngle, aClockwise ) {
+
+ var lastargs = this.actions[ this.actions.length - 1].args;
+ var x0 = lastargs[ lastargs.length - 2 ];
+ var y0 = lastargs[ lastargs.length - 1 ];
+
+ this.absellipse(aX + x0, aY + y0, xRadius, yRadius,
+ aStartAngle, aEndAngle, aClockwise );
+
+ };
+
+
+THREE.Path.prototype.absellipse = function ( aX, aY, xRadius, yRadius,
+ aStartAngle, aEndAngle, aClockwise ) {
+
+ var args = Array.prototype.slice.call( arguments );
+ var curve = new THREE.EllipseCurve( aX, aY, xRadius, yRadius,
+ aStartAngle, aEndAngle, aClockwise );
+ this.curves.push( curve );
+
+ var lastPoint = curve.getPoint(1);
+ args.push(lastPoint.x);
+ args.push(lastPoint.y);
+
+ this.actions.push( { action: THREE.PathActions.ELLIPSE, args: args } );
+
+ };
+
+THREE.Path.prototype.getSpacedPoints = function ( divisions, closedPath ) {
+
+ if ( ! divisions ) divisions = 40;
+
+ var points = [];
+
+ for ( var i = 0; i < divisions; i ++ ) {
+
+ points.push( this.getPoint( i / divisions ) );
+
+ //if( !this.getPoint( i / divisions ) ) throw "DIE";
+
+ }
+
+ // if ( closedPath ) {
+ //
+ // points.push( points[ 0 ] );
+ //
+ // }
+
+ return points;
+
+};
+
+/* Return an array of vectors based on contour of the path */
+
+THREE.Path.prototype.getPoints = function( divisions, closedPath ) {
+
+ if (this.useSpacedPoints) {
+ console.log('tata');
+ return this.getSpacedPoints( divisions, closedPath );
+ }
+
+ divisions = divisions || 12;
+
+ var points = [];
+
+ var i, il, item, action, args;
+ var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
+ laste, j,
+ t, tx, ty;
+
+ for ( i = 0, il = this.actions.length; i < il; i ++ ) {
+
+ item = this.actions[ i ];
+
+ action = item.action;
+ args = item.args;
+
+ switch( action ) {
+
+ case THREE.PathActions.MOVE_TO:
+
+ points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
+
+ break;
+
+ case THREE.PathActions.LINE_TO:
+
+ points.push( new THREE.Vector2( args[ 0 ], args[ 1 ] ) );
+
+ break;
+
+ case THREE.PathActions.QUADRATIC_CURVE_TO:
+
+ cpx = args[ 2 ];
+ cpy = args[ 3 ];
+
+ cpx1 = args[ 0 ];
+ cpy1 = args[ 1 ];
+
+ if ( points.length > 0 ) {
+
+ laste = points[ points.length - 1 ];
+
+ cpx0 = laste.x;
+ cpy0 = laste.y;
+
+ } else {
+
+ laste = this.actions[ i - 1 ].args;
+
+ cpx0 = laste[ laste.length - 2 ];
+ cpy0 = laste[ laste.length - 1 ];
+
+ }
+
+ for ( j = 1; j <= divisions; j ++ ) {
+
+ t = j / divisions;
+
+ tx = THREE.Shape.Utils.b2( t, cpx0, cpx1, cpx );
+ ty = THREE.Shape.Utils.b2( t, cpy0, cpy1, cpy );
+
+ points.push( new THREE.Vector2( tx, ty ) );
+
+ }
+
+ break;
+
+ case THREE.PathActions.BEZIER_CURVE_TO:
+
+ cpx = args[ 4 ];
+ cpy = args[ 5 ];
+
+ cpx1 = args[ 0 ];
+ cpy1 = args[ 1 ];
+
+ cpx2 = args[ 2 ];
+ cpy2 = args[ 3 ];
+
+ if ( points.length > 0 ) {
+
+ laste = points[ points.length - 1 ];
+
+ cpx0 = laste.x;
+ cpy0 = laste.y;
+
+ } else {
+
+ laste = this.actions[ i - 1 ].args;
+
+ cpx0 = laste[ laste.length - 2 ];
+ cpy0 = laste[ laste.length - 1 ];
+
+ }
+
+
+ for ( j = 1; j <= divisions; j ++ ) {
+
+ t = j / divisions;
+
+ tx = THREE.Shape.Utils.b3( t, cpx0, cpx1, cpx2, cpx );
+ ty = THREE.Shape.Utils.b3( t, cpy0, cpy1, cpy2, cpy );
+
+ points.push( new THREE.Vector2( tx, ty ) );
+
+ }
+
+ break;
+
+ case THREE.PathActions.CSPLINE_THRU:
+
+ laste = this.actions[ i - 1 ].args;
+
+ var last = new THREE.Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
+ var spts = [ last ];
+
+ var n = divisions * args[ 0 ].length;
+
+ spts = spts.concat( args[ 0 ] );
+
+ var spline = new THREE.SplineCurve( spts );
+
+ for ( j = 1; j <= n; j ++ ) {
+
+ points.push( spline.getPointAt( j / n ) ) ;
+
+ }
+
+ break;
+
+ case THREE.PathActions.ARC:
+
+ var aX = args[ 0 ], aY = args[ 1 ],
+ aRadius = args[ 2 ],
+ aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
+ aClockwise = !!args[ 5 ];
+
+ var deltaAngle = aEndAngle - aStartAngle;
+ var angle;
+ var tdivisions = divisions * 2;
+
+ for ( j = 1; j <= tdivisions; j ++ ) {
+
+ t = j / tdivisions;
+
+ if ( ! aClockwise ) {
+
+ t = 1 - t;
+
+ }
+
+ angle = aStartAngle + t * deltaAngle;
+
+ tx = aX + aRadius * Math.cos( angle );
+ ty = aY + aRadius * Math.sin( angle );
+
+ //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
+
+ points.push( new THREE.Vector2( tx, ty ) );
+
+ }
+
+ //console.log(points);
+
+ break;
+
+ case THREE.PathActions.ELLIPSE:
+
+ var aX = args[ 0 ], aY = args[ 1 ],
+ xRadius = args[ 2 ],
+ yRadius = args[ 3 ],
+ aStartAngle = args[ 4 ], aEndAngle = args[ 5 ],
+ aClockwise = !!args[ 6 ];
+
+
+ var deltaAngle = aEndAngle - aStartAngle;
+ var angle;
+ var tdivisions = divisions * 2;
+
+ for ( j = 1; j <= tdivisions; j ++ ) {
+
+ t = j / tdivisions;
+
+ if ( ! aClockwise ) {
+
+ t = 1 - t;
+
+ }
+
+ angle = aStartAngle + t * deltaAngle;
+
+ tx = aX + xRadius * Math.cos( angle );
+ ty = aY + yRadius * Math.sin( angle );
+
+ //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);
+
+ points.push( new THREE.Vector2( tx, ty ) );
+
+ }
+
+ //console.log(points);
+
+ break;
+
+ } // end switch
+
+ }
+
+
+
+ // Normalize to remove the closing point by default.
+ var lastPoint = points[ points.length - 1];
+ var EPSILON = 0.0000000001;
+ if ( Math.abs(lastPoint.x - points[ 0 ].x) < EPSILON &&
+ Math.abs(lastPoint.y - points[ 0 ].y) < EPSILON)
+ points.splice( points.length - 1, 1);
+ if ( closedPath ) {
+
+ points.push( points[ 0 ] );
+
+ }
+
+ return points;
+
+};
+
+// Breaks path into shapes
+
+THREE.Path.prototype.toShapes = function( isCCW ) {
+
+ var i, il, item, action, args;
+
+ var subPaths = [], lastPath = new THREE.Path();
+
+ for ( i = 0, il = this.actions.length; i < il; i ++ ) {
+
+ item = this.actions[ i ];
+
+ args = item.args;
+ action = item.action;
+
+ if ( action == THREE.PathActions.MOVE_TO ) {
+
+ if ( lastPath.actions.length != 0 ) {
+
+ subPaths.push( lastPath );
+ lastPath = new THREE.Path();
+
+ }
+
+ }
+
+ lastPath[ action ].apply( lastPath, args );
+
+ }
+
+ if ( lastPath.actions.length != 0 ) {
+
+ subPaths.push( lastPath );
+
+ }
+
+ // console.log(subPaths);
+
+ if ( subPaths.length == 0 ) return [];
+
+ var solid, tmpPath, tmpShape, shapes = [];
+
+ if ( subPaths.length == 1) {
+
+ tmpPath = subPaths[0];
+ tmpShape = new THREE.Shape();
+ tmpShape.actions = tmpPath.actions;
+ tmpShape.curves = tmpPath.curves;
+ shapes.push( tmpShape );
+ return shapes;
+
+ }
+
+ var holesFirst = !THREE.Shape.Utils.isClockWise( subPaths[ 0 ].getPoints() );
+ holesFirst = isCCW ? !holesFirst : holesFirst;
+
+ // console.log("Holes first", holesFirst);
+
+ if ( holesFirst ) {
+
+ tmpShape = new THREE.Shape();
+
+ for ( i = 0, il = subPaths.length; i < il; i ++ ) {
+
+ tmpPath = subPaths[ i ];
+ solid = THREE.Shape.Utils.isClockWise( tmpPath.getPoints() );
+ solid = isCCW ? !solid : solid;
+
+ if ( solid ) {
+
+ tmpShape.actions = tmpPath.actions;
+ tmpShape.curves = tmpPath.curves;
+
+ shapes.push( tmpShape );
+ tmpShape = new THREE.Shape();
+
+ //console.log('cw', i);
+
+ } else {
+
+ tmpShape.holes.push( tmpPath );
+
+ //console.log('ccw', i);
+
+ }
+
+ }
+
+ } else {
+
+ // Shapes first
+ tmpShape = undefined;
+
+ for ( i = 0, il = subPaths.length; i < il; i ++ ) {
+
+ tmpPath = subPaths[ i ];
+ solid = THREE.Shape.Utils.isClockWise( tmpPath.getPoints() );
+ solid = isCCW ? !solid : solid;
+
+ if ( solid ) {
+
+ if ( tmpShape ) shapes.push( tmpShape );
+
+ tmpShape = new THREE.Shape();
+ tmpShape.actions = tmpPath.actions;
+ tmpShape.curves = tmpPath.curves;
+
+ } else {
+
+ tmpShape.holes.push( tmpPath );
+
+ }
+
+ }
+
+ shapes.push( tmpShape );
+
+ }
+
+ //console.log("shape", shapes);
+
+ return shapes;
+
+};
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * Defines a 2d shape plane using paths.
+ **/
+
+// STEP 1 Create a path.
+// STEP 2 Turn path into shape.
+// STEP 3 ExtrudeGeometry takes in Shape/Shapes
+// STEP 3a - Extract points from each shape, turn to vertices
+// STEP 3b - Triangulate each shape, add faces.
+
+THREE.Shape = function () {
+
+ THREE.Path.apply( this, arguments );
+ this.holes = [];
+
+};
+
+THREE.Shape.prototype = Object.create( THREE.Path.prototype );
+
+// Convenience method to return ExtrudeGeometry
+
+THREE.Shape.prototype.extrude = function ( options ) {
+
+ var extruded = new THREE.ExtrudeGeometry( this, options );
+ return extruded;
+
+};
+
+// Convenience method to return ShapeGeometry
+
+THREE.Shape.prototype.makeGeometry = function ( options ) {
+
+ var geometry = new THREE.ShapeGeometry( this, options );
+ return geometry;
+
+};
+
+// Get points of holes
+
+THREE.Shape.prototype.getPointsHoles = function ( divisions ) {
+
+ var i, il = this.holes.length, holesPts = [];
+
+ for ( i = 0; i < il; i ++ ) {
+
+ holesPts[ i ] = this.holes[ i ].getTransformedPoints( divisions, this.bends );
+
+ }
+
+ return holesPts;
+
+};
+
+// Get points of holes (spaced by regular distance)
+
+THREE.Shape.prototype.getSpacedPointsHoles = function ( divisions ) {
+
+ var i, il = this.holes.length, holesPts = [];
+
+ for ( i = 0; i < il; i ++ ) {
+
+ holesPts[ i ] = this.holes[ i ].getTransformedSpacedPoints( divisions, this.bends );
+
+ }
+
+ return holesPts;
+
+};
+
+
+// Get points of shape and holes (keypoints based on segments parameter)
+
+THREE.Shape.prototype.extractAllPoints = function ( divisions ) {
+
+ return {
+
+ shape: this.getTransformedPoints( divisions ),
+ holes: this.getPointsHoles( divisions )
+
+ };
+
+};
+
+THREE.Shape.prototype.extractPoints = function ( divisions ) {
+
+ if (this.useSpacedPoints) {
+ return this.extractAllSpacedPoints(divisions);
+ }
+
+ return this.extractAllPoints(divisions);
+
+};
+
+//
+// THREE.Shape.prototype.extractAllPointsWithBend = function ( divisions, bend ) {
+//
+// return {
+//
+// shape: this.transform( bend, divisions ),
+// holes: this.getPointsHoles( divisions, bend )
+//
+// };
+//
+// };
+
+// Get points of shape and holes (spaced by regular distance)
+
+THREE.Shape.prototype.extractAllSpacedPoints = function ( divisions ) {
+
+ return {
+
+ shape: this.getTransformedSpacedPoints( divisions ),
+ holes: this.getSpacedPointsHoles( divisions )
+
+ };
+
+};
+
+/**************************************************************
+ * Utils
+ **************************************************************/
+
+THREE.Shape.Utils = {
+
+ /*
+ contour - array of vector2 for contour
+ holes - array of array of vector2
+ */
+
+ removeHoles: function ( contour, holes ) {
+
+ var shape = contour.concat(); // work on this shape
+ var allpoints = shape.concat();
+
+ /* For each isolated shape, find the closest points and break to the hole to allow triangulation */
+
+
+ var prevShapeVert, nextShapeVert,
+ prevHoleVert, nextHoleVert,
+ holeIndex, shapeIndex,
+ shapeId, shapeGroup,
+ h, h2,
+ hole, shortest, d,
+ p, pts1, pts2,
+ tmpShape1, tmpShape2,
+ tmpHole1, tmpHole2,
+ verts = [];
+
+ for ( h = 0; h < holes.length; h ++ ) {
+
+ hole = holes[ h ];
+
+ /*
+ shapeholes[ h ].concat(); // preserves original
+ holes.push( hole );
+ */
+
+ Array.prototype.push.apply( allpoints, hole );
+
+ shortest = Number.POSITIVE_INFINITY;
+
+
+ // Find the shortest pair of pts between shape and hole
+
+ // Note: Actually, I'm not sure now if we could optimize this to be faster than O(m*n)
+ // Using distanceToSquared() intead of distanceTo() should speed a little
+ // since running square roots operations are reduced.
+
+ for ( h2 = 0; h2 < hole.length; h2 ++ ) {
+
+ pts1 = hole[ h2 ];
+ var dist = [];
+
+ for ( p = 0; p < shape.length; p++ ) {
+
+ pts2 = shape[ p ];
+ d = pts1.distanceToSquared( pts2 );
+ dist.push( d );
+
+ if ( d < shortest ) {
+
+ shortest = d;
+ holeIndex = h2;
+ shapeIndex = p;
+
+ }
+
+ }
+
+ }
+
+ //console.log("shortest", shortest, dist);
+
+ prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+ prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+ var areaapts = [
+
+ hole[ holeIndex ],
+ shape[ shapeIndex ],
+ shape[ prevShapeVert ]
+
+ ];
+
+ var areaa = THREE.FontUtils.Triangulate.area( areaapts );
+
+ var areabpts = [
+
+ hole[ holeIndex ],
+ hole[ prevHoleVert ],
+ shape[ shapeIndex ]
+
+ ];
+
+ var areab = THREE.FontUtils.Triangulate.area( areabpts );
+
+ var shapeOffset = 1;
+ var holeOffset = -1;
+
+ var oldShapeIndex = shapeIndex, oldHoleIndex = holeIndex;
+ shapeIndex += shapeOffset;
+ holeIndex += holeOffset;
+
+ if ( shapeIndex < 0 ) { shapeIndex += shape.length; }
+ shapeIndex %= shape.length;
+
+ if ( holeIndex < 0 ) { holeIndex += hole.length; }
+ holeIndex %= hole.length;
+
+ prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+ prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+ areaapts = [
+
+ hole[ holeIndex ],
+ shape[ shapeIndex ],
+ shape[ prevShapeVert ]
+
+ ];
+
+ var areaa2 = THREE.FontUtils.Triangulate.area( areaapts );
+
+ areabpts = [
+
+ hole[ holeIndex ],
+ hole[ prevHoleVert ],
+ shape[ shapeIndex ]
+
+ ];
+
+ var areab2 = THREE.FontUtils.Triangulate.area( areabpts );
+ //console.log(areaa,areab ,areaa2,areab2, ( areaa + areab ), ( areaa2 + areab2 ));
+
+ if ( ( areaa + areab ) > ( areaa2 + areab2 ) ) {
+
+ // In case areas are not correct.
+ //console.log("USE THIS");
+
+ shapeIndex = oldShapeIndex;
+ holeIndex = oldHoleIndex ;
+
+ if ( shapeIndex < 0 ) { shapeIndex += shape.length; }
+ shapeIndex %= shape.length;
+
+ if ( holeIndex < 0 ) { holeIndex += hole.length; }
+ holeIndex %= hole.length;
+
+ prevShapeVert = ( shapeIndex - 1 ) >= 0 ? shapeIndex - 1 : shape.length - 1;
+ prevHoleVert = ( holeIndex - 1 ) >= 0 ? holeIndex - 1 : hole.length - 1;
+
+ } else {
+
+ //console.log("USE THAT ")
+
+ }
+
+ tmpShape1 = shape.slice( 0, shapeIndex );
+ tmpShape2 = shape.slice( shapeIndex );
+ tmpHole1 = hole.slice( holeIndex );
+ tmpHole2 = hole.slice( 0, holeIndex );
+
+ // Should check orders here again?
+
+ var trianglea = [
+
+ hole[ holeIndex ],
+ shape[ shapeIndex ],
+ shape[ prevShapeVert ]
+
+ ];
+
+ var triangleb = [
+
+ hole[ holeIndex ] ,
+ hole[ prevHoleVert ],
+ shape[ shapeIndex ]
+
+ ];
+
+ verts.push( trianglea );
+ verts.push( triangleb );
+
+ shape = tmpShape1.concat( tmpHole1 ).concat( tmpHole2 ).concat( tmpShape2 );
+
+ }
+
+ return {
+
+ shape:shape, /* shape with no holes */
+ isolatedPts: verts, /* isolated faces */
+ allpoints: allpoints
+
+ }
+
+
+ },
+
+ triangulateShape: function ( contour, holes ) {
+
+ var shapeWithoutHoles = THREE.Shape.Utils.removeHoles( contour, holes );
+
+ var shape = shapeWithoutHoles.shape,
+ allpoints = shapeWithoutHoles.allpoints,
+ isolatedPts = shapeWithoutHoles.isolatedPts;
+
+ var triangles = THREE.FontUtils.Triangulate( shape, false ); // True returns indices for points of spooled shape
+
+ // To maintain reference to old shape, one must match coordinates, or offset the indices from original arrays. It's probably easier to do the first.
+
+ //console.log( "triangles",triangles, triangles.length );
+ //console.log( "allpoints",allpoints, allpoints.length );
+
+ var i, il, f, face,
+ key, index,
+ allPointsMap = {},
+ isolatedPointsMap = {};
+
+ // prepare all points map
+
+ for ( i = 0, il = allpoints.length; i < il; i ++ ) {
+
+ key = allpoints[ i ].x + ":" + allpoints[ i ].y;
+
+ if ( allPointsMap[ key ] !== undefined ) {
+
+ console.log( "Duplicate point", key );
+
+ }
+
+ allPointsMap[ key ] = i;
+
+ }
+
+ // check all face vertices against all points map
+
+ for ( i = 0, il = triangles.length; i < il; i ++ ) {
+
+ face = triangles[ i ];
+
+ for ( f = 0; f < 3; f ++ ) {
+
+ key = face[ f ].x + ":" + face[ f ].y;
+
+ index = allPointsMap[ key ];
+
+ if ( index !== undefined ) {
+
+ face[ f ] = index;
+
+ }
+
+ }
+
+ }
+
+ // check isolated points vertices against all points map
+
+ for ( i = 0, il = isolatedPts.length; i < il; i ++ ) {
+
+ face = isolatedPts[ i ];
+
+ for ( f = 0; f < 3; f ++ ) {
+
+ key = face[ f ].x + ":" + face[ f ].y;
+
+ index = allPointsMap[ key ];
+
+ if ( index !== undefined ) {
+
+ face[ f ] = index;
+
+ }
+
+ }
+
+ }
+
+ return triangles.concat( isolatedPts );
+
+ }, // end triangulate shapes
+
+ /*
+ triangulate2 : function( pts, holes ) {
+
+ // For use with Poly2Tri.js
+
+ var allpts = pts.concat();
+ var shape = [];
+ for (var p in pts) {
+ shape.push(new js.poly2tri.Point(pts[p].x, pts[p].y));
+ }
+
+ var swctx = new js.poly2tri.SweepContext(shape);
+
+ for (var h in holes) {
+ var aHole = holes[h];
+ var newHole = []
+ for (i in aHole) {
+ newHole.push(new js.poly2tri.Point(aHole[i].x, aHole[i].y));
+ allpts.push(aHole[i]);
+ }
+ swctx.AddHole(newHole);
+ }
+
+ var find;
+ var findIndexForPt = function (pt) {
+ find = new THREE.Vector2(pt.x, pt.y);
+ var p;
+ for (p=0, pl = allpts.length; p points.length - 2 ? points.length -1 : intPoint + 1;
+ c[ 3 ] = intPoint > points.length - 3 ? points.length -1 : intPoint + 2;
+
+ v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight );
+ v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight );
+
+ return v;
+
+};
+/**************************************************************
+ * Ellipse curve
+ **************************************************************/
+
+THREE.EllipseCurve = function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+ this.aX = aX;
+ this.aY = aY;
+
+ this.xRadius = xRadius;
+ this.yRadius = yRadius;
+
+ this.aStartAngle = aStartAngle;
+ this.aEndAngle = aEndAngle;
+
+ this.aClockwise = aClockwise;
+
+};
+
+THREE.EllipseCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.EllipseCurve.prototype.getPoint = function ( t ) {
+
+ var angle;
+ var deltaAngle = this.aEndAngle - this.aStartAngle;
+
+ if ( deltaAngle < 0 ) deltaAngle += Math.PI * 2;
+ if ( deltaAngle > Math.PI * 2 ) deltaAngle -= Math.PI * 2;
+
+ if ( this.aClockwise === true ) {
+
+ angle = this.aEndAngle + ( 1 - t ) * ( Math.PI * 2 - deltaAngle );
+
+ } else {
+
+ angle = this.aStartAngle + t * deltaAngle;
+
+ }
+
+ var tx = this.aX + this.xRadius * Math.cos( angle );
+ var ty = this.aY + this.yRadius * Math.sin( angle );
+
+ return new THREE.Vector2( tx, ty );
+
+};
+
+/**************************************************************
+ * Arc curve
+ **************************************************************/
+
+THREE.ArcCurve = function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+ THREE.EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+};
+
+THREE.ArcCurve.prototype = Object.create( THREE.EllipseCurve.prototype );
+/**************************************************************
+ * Line3D
+ **************************************************************/
+
+THREE.LineCurve3 = THREE.Curve.create(
+
+ function ( v1, v2 ) {
+
+ this.v1 = v1;
+ this.v2 = v2;
+
+ },
+
+ function ( t ) {
+
+ var r = new THREE.Vector3();
+
+
+ r.subVectors( this.v2, this.v1 ); // diff
+ r.multiplyScalar( t );
+ r.add( this.v1 );
+
+ return r;
+
+ }
+
+);
+
+/**************************************************************
+ * Quadratic Bezier 3D curve
+ **************************************************************/
+
+THREE.QuadraticBezierCurve3 = THREE.Curve.create(
+
+ function ( v0, v1, v2 ) {
+
+ this.v0 = v0;
+ this.v1 = v1;
+ this.v2 = v2;
+
+ },
+
+ function ( t ) {
+
+ var tx, ty, tz;
+
+ tx = THREE.Shape.Utils.b2( t, this.v0.x, this.v1.x, this.v2.x );
+ ty = THREE.Shape.Utils.b2( t, this.v0.y, this.v1.y, this.v2.y );
+ tz = THREE.Shape.Utils.b2( t, this.v0.z, this.v1.z, this.v2.z );
+
+ return new THREE.Vector3( tx, ty, tz );
+
+ }
+
+);
+/**************************************************************
+ * Cubic Bezier 3D curve
+ **************************************************************/
+
+THREE.CubicBezierCurve3 = THREE.Curve.create(
+
+ function ( v0, v1, v2, v3 ) {
+
+ this.v0 = v0;
+ this.v1 = v1;
+ this.v2 = v2;
+ this.v3 = v3;
+
+ },
+
+ function ( t ) {
+
+ var tx, ty, tz;
+
+ tx = THREE.Shape.Utils.b3( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x );
+ ty = THREE.Shape.Utils.b3( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y );
+ tz = THREE.Shape.Utils.b3( t, this.v0.z, this.v1.z, this.v2.z, this.v3.z );
+
+ return new THREE.Vector3( tx, ty, tz );
+
+ }
+
+);
+/**************************************************************
+ * Spline 3D curve
+ **************************************************************/
+
+
+THREE.SplineCurve3 = THREE.Curve.create(
+
+ function ( points /* array of Vector3 */) {
+
+ this.points = (points == undefined) ? [] : points;
+
+ },
+
+ function ( t ) {
+
+ var v = new THREE.Vector3();
+ var c = [];
+ var points = this.points, point, intPoint, weight;
+ point = ( points.length - 1 ) * t;
+
+ intPoint = Math.floor( point );
+ weight = point - intPoint;
+
+ c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1;
+ c[ 1 ] = intPoint;
+ c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1;
+ c[ 3 ] = intPoint > points.length - 3 ? points.length - 1 : intPoint + 2;
+
+ var pt0 = points[ c[0] ],
+ pt1 = points[ c[1] ],
+ pt2 = points[ c[2] ],
+ pt3 = points[ c[3] ];
+
+ v.x = THREE.Curve.Utils.interpolate(pt0.x, pt1.x, pt2.x, pt3.x, weight);
+ v.y = THREE.Curve.Utils.interpolate(pt0.y, pt1.y, pt2.y, pt3.y, weight);
+ v.z = THREE.Curve.Utils.interpolate(pt0.z, pt1.z, pt2.z, pt3.z, weight);
+
+ return v;
+
+ }
+
+);
+
+
+// THREE.SplineCurve3.prototype.getTangent = function(t) {
+// var v = new THREE.Vector3();
+// var c = [];
+// var points = this.points, point, intPoint, weight;
+// point = ( points.length - 1 ) * t;
+
+// intPoint = Math.floor( point );
+// weight = point - intPoint;
+
+// c[ 0 ] = intPoint == 0 ? intPoint : intPoint - 1;
+// c[ 1 ] = intPoint;
+// c[ 2 ] = intPoint > points.length - 2 ? points.length - 1 : intPoint + 1;
+// c[ 3 ] = intPoint > points.length - 3 ? points.length - 1 : intPoint + 2;
+
+// var pt0 = points[ c[0] ],
+// pt1 = points[ c[1] ],
+// pt2 = points[ c[2] ],
+// pt3 = points[ c[3] ];
+
+// // t = weight;
+// v.x = THREE.Curve.Utils.tangentSpline( t, pt0.x, pt1.x, pt2.x, pt3.x );
+// v.y = THREE.Curve.Utils.tangentSpline( t, pt0.y, pt1.y, pt2.y, pt3.y );
+// v.z = THREE.Curve.Utils.tangentSpline( t, pt0.z, pt1.z, pt2.z, pt3.z );
+
+// return v;
+
+// }
+/**************************************************************
+ * Closed Spline 3D curve
+ **************************************************************/
+
+
+THREE.ClosedSplineCurve3 = THREE.Curve.create(
+
+ function ( points /* array of Vector3 */) {
+
+ this.points = (points == undefined) ? [] : points;
+
+ },
+
+ function ( t ) {
+
+ var v = new THREE.Vector3();
+ var c = [];
+ var points = this.points, point, intPoint, weight;
+ point = ( points.length - 0 ) * t;
+ // This needs to be from 0-length +1
+
+ intPoint = Math.floor( point );
+ weight = point - intPoint;
+
+ intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / points.length ) + 1 ) * points.length;
+ c[ 0 ] = ( intPoint - 1 ) % points.length;
+ c[ 1 ] = ( intPoint ) % points.length;
+ c[ 2 ] = ( intPoint + 1 ) % points.length;
+ c[ 3 ] = ( intPoint + 2 ) % points.length;
+
+ v.x = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].x, points[ c[ 1 ] ].x, points[ c[ 2 ] ].x, points[ c[ 3 ] ].x, weight );
+ v.y = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].y, points[ c[ 1 ] ].y, points[ c[ 2 ] ].y, points[ c[ 3 ] ].y, weight );
+ v.z = THREE.Curve.Utils.interpolate( points[ c[ 0 ] ].z, points[ c[ 1 ] ].z, points[ c[ 2 ] ].z, points[ c[ 3 ] ].z, weight );
+
+ return v;
+
+ }
+
+);
+/**
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+THREE.AnimationHandler = (function() {
+
+ var playing = [];
+ var library = {};
+ var that = {};
+
+
+ //--- update ---
+
+ that.update = function( deltaTimeMS ) {
+
+ for( var i = 0; i < playing.length; i ++ )
+ playing[ i ].update( deltaTimeMS );
+
+ };
+
+
+ //--- add ---
+
+ that.addToUpdate = function( animation ) {
+
+ if ( playing.indexOf( animation ) === -1 )
+ playing.push( animation );
+
+ };
+
+
+ //--- remove ---
+
+ that.removeFromUpdate = function( animation ) {
+
+ var index = playing.indexOf( animation );
+
+ if( index !== -1 )
+ playing.splice( index, 1 );
+
+ };
+
+
+ //--- add ---
+
+ that.add = function( data ) {
+
+ if ( library[ data.name ] !== undefined )
+ console.log( "THREE.AnimationHandler.add: Warning! " + data.name + " already exists in library. Overwriting." );
+
+ library[ data.name ] = data;
+ initData( data );
+
+ };
+
+
+ //--- get ---
+
+ that.get = function( name ) {
+
+ if ( typeof name === "string" ) {
+
+ if ( library[ name ] ) {
+
+ return library[ name ];
+
+ } else {
+
+ console.log( "THREE.AnimationHandler.get: Couldn't find animation " + name );
+ return null;
+
+ }
+
+ } else {
+
+ // todo: add simple tween library
+
+ }
+
+ };
+
+ //--- parse ---
+
+ that.parse = function( root ) {
+
+ // setup hierarchy
+
+ var hierarchy = [];
+
+ if ( root instanceof THREE.SkinnedMesh ) {
+
+ for( var b = 0; b < root.bones.length; b++ ) {
+
+ hierarchy.push( root.bones[ b ] );
+
+ }
+
+ } else {
+
+ parseRecurseHierarchy( root, hierarchy );
+
+ }
+
+ return hierarchy;
+
+ };
+
+ var parseRecurseHierarchy = function( root, hierarchy ) {
+
+ hierarchy.push( root );
+
+ for( var c = 0; c < root.children.length; c++ )
+ parseRecurseHierarchy( root.children[ c ], hierarchy );
+
+ }
+
+
+ //--- init data ---
+
+ var initData = function( data ) {
+
+ if( data.initialized === true )
+ return;
+
+
+ // loop through all keys
+
+ for( var h = 0; h < data.hierarchy.length; h ++ ) {
+
+ for( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ // remove minus times
+
+ if( data.hierarchy[ h ].keys[ k ].time < 0 )
+ data.hierarchy[ h ].keys[ k ].time = 0;
+
+
+ // create quaternions
+
+ if( data.hierarchy[ h ].keys[ k ].rot !== undefined &&
+ !( data.hierarchy[ h ].keys[ k ].rot instanceof THREE.Quaternion ) ) {
+
+ var quat = data.hierarchy[ h ].keys[ k ].rot;
+ data.hierarchy[ h ].keys[ k ].rot = new THREE.Quaternion( quat[0], quat[1], quat[2], quat[3] );
+
+ }
+
+ }
+
+
+ // prepare morph target keys
+
+ if( data.hierarchy[ h ].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) {
+
+ // get all used
+
+ var usedMorphTargets = {};
+
+ for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) {
+
+ var morphTargetName = data.hierarchy[ h ].keys[ k ].morphTargets[ m ];
+ usedMorphTargets[ morphTargetName ] = -1;
+
+ }
+
+ }
+
+ data.hierarchy[ h ].usedMorphTargets = usedMorphTargets;
+
+
+ // set all used on all frames
+
+ for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ var influences = {};
+
+ for ( var morphTargetName in usedMorphTargets ) {
+
+ for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) {
+
+ if ( data.hierarchy[ h ].keys[ k ].morphTargets[ m ] === morphTargetName ) {
+
+ influences[ morphTargetName ] = data.hierarchy[ h ].keys[ k ].morphTargetsInfluences[ m ];
+ break;
+
+ }
+
+ }
+
+ if ( m === data.hierarchy[ h ].keys[ k ].morphTargets.length ) {
+
+ influences[ morphTargetName ] = 0;
+
+ }
+
+ }
+
+ data.hierarchy[ h ].keys[ k ].morphTargetsInfluences = influences;
+
+ }
+
+ }
+
+
+ // remove all keys that are on the same time
+
+ for ( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ if ( data.hierarchy[ h ].keys[ k ].time === data.hierarchy[ h ].keys[ k - 1 ].time ) {
+
+ data.hierarchy[ h ].keys.splice( k, 1 );
+ k --;
+
+ }
+
+ }
+
+
+ // set index
+
+ for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+
+ data.hierarchy[ h ].keys[ k ].index = k;
+
+ }
+
+ }
+
+
+ // JIT
+
+ var lengthInFrames = parseInt( data.length * data.fps, 10 );
+
+ data.JIT = {};
+ data.JIT.hierarchy = [];
+
+ for( var h = 0; h < data.hierarchy.length; h ++ )
+ data.JIT.hierarchy.push( new Array( lengthInFrames ) );
+
+
+ // done
+
+ data.initialized = true;
+
+ };
+
+
+ // interpolation types
+
+ that.LINEAR = 0;
+ that.CATMULLROM = 1;
+ that.CATMULLROM_FORWARD = 2;
+
+ return that;
+
+}());
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.Animation = function ( root, name, interpolationType ) {
+
+ this.root = root;
+ this.data = THREE.AnimationHandler.get( name );
+ this.hierarchy = THREE.AnimationHandler.parse( root );
+
+ this.currentTime = 0;
+ this.timeScale = 1;
+
+ this.isPlaying = false;
+ this.isPaused = true;
+ this.loop = true;
+
+ this.interpolationType = interpolationType !== undefined ? interpolationType : THREE.AnimationHandler.LINEAR;
+
+ this.points = [];
+ this.target = new THREE.Vector3();
+
+};
+
+THREE.Animation.prototype.play = function ( loop, startTimeMS ) {
+
+ if ( this.isPlaying === false ) {
+
+ this.isPlaying = true;
+ this.loop = loop !== undefined ? loop : true;
+ this.currentTime = startTimeMS !== undefined ? startTimeMS : 0;
+
+ // reset key cache
+
+ var h, hl = this.hierarchy.length,
+ object;
+
+ for ( h = 0; h < hl; h ++ ) {
+
+ object = this.hierarchy[ h ];
+
+ object.matrixAutoUpdate = true;
+
+ if ( object.animationCache === undefined ) {
+
+ object.animationCache = {};
+ object.animationCache.prevKey = { pos: 0, rot: 0, scl: 0 };
+ object.animationCache.nextKey = { pos: 0, rot: 0, scl: 0 };
+ object.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix;
+
+ }
+
+ var prevKey = object.animationCache.prevKey;
+ var nextKey = object.animationCache.nextKey;
+
+ prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ];
+ prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ];
+ prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ];
+
+ nextKey.pos = this.getNextKeyWith( "pos", h, 1 );
+ nextKey.rot = this.getNextKeyWith( "rot", h, 1 );
+ nextKey.scl = this.getNextKeyWith( "scl", h, 1 );
+
+ }
+
+ this.update( 0 );
+
+ }
+
+ this.isPaused = false;
+
+ THREE.AnimationHandler.addToUpdate( this );
+
+};
+
+
+THREE.Animation.prototype.pause = function() {
+
+ if ( this.isPaused === true ) {
+
+ THREE.AnimationHandler.addToUpdate( this );
+
+ } else {
+
+ THREE.AnimationHandler.removeFromUpdate( this );
+
+ }
+
+ this.isPaused = !this.isPaused;
+
+};
+
+
+THREE.Animation.prototype.stop = function() {
+
+ this.isPlaying = false;
+ this.isPaused = false;
+ THREE.AnimationHandler.removeFromUpdate( this );
+
+};
+
+
+THREE.Animation.prototype.update = function ( deltaTimeMS ) {
+
+ // early out
+
+ if ( this.isPlaying === false ) return;
+
+
+ // vars
+
+ var types = [ "pos", "rot", "scl" ];
+ var type;
+ var scale;
+ var vector;
+ var prevXYZ, nextXYZ;
+ var prevKey, nextKey;
+ var object;
+ var animationCache;
+ var frame;
+ var JIThierarchy = this.data.JIT.hierarchy;
+ var currentTime, unloopedCurrentTime;
+ var currentPoint, forwardPoint, angle;
+
+
+ this.currentTime += deltaTimeMS * this.timeScale;
+
+ unloopedCurrentTime = this.currentTime;
+ currentTime = this.currentTime = this.currentTime % this.data.length;
+ frame = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 );
+
+
+ for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) {
+
+ object = this.hierarchy[ h ];
+ animationCache = object.animationCache;
+
+ // loop through pos/rot/scl
+
+ for ( var t = 0; t < 3; t ++ ) {
+
+ // get keys
+
+ type = types[ t ];
+ prevKey = animationCache.prevKey[ type ];
+ nextKey = animationCache.nextKey[ type ];
+
+ // switch keys?
+
+ if ( nextKey.time <= unloopedCurrentTime ) {
+
+ // did we loop?
+
+ if ( currentTime < unloopedCurrentTime ) {
+
+ if ( this.loop ) {
+
+ prevKey = this.data.hierarchy[ h ].keys[ 0 ];
+ nextKey = this.getNextKeyWith( type, h, 1 );
+
+ while( nextKey.time < currentTime ) {
+
+ prevKey = nextKey;
+ nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );
+
+ }
+
+ } else {
+
+ this.stop();
+ return;
+
+ }
+
+ } else {
+
+ do {
+
+ prevKey = nextKey;
+ nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );
+
+ } while( nextKey.time < currentTime )
+
+ }
+
+ animationCache.prevKey[ type ] = prevKey;
+ animationCache.nextKey[ type ] = nextKey;
+
+ }
+
+
+ object.matrixAutoUpdate = true;
+ object.matrixWorldNeedsUpdate = true;
+
+ scale = ( currentTime - prevKey.time ) / ( nextKey.time - prevKey.time );
+ prevXYZ = prevKey[ type ];
+ nextXYZ = nextKey[ type ];
+
+
+ // check scale error
+
+ if ( scale < 0 || scale > 1 ) {
+
+ console.log( "THREE.Animation.update: Warning! Scale out of bounds:" + scale + " on bone " + h );
+ scale = scale < 0 ? 0 : 1;
+
+ }
+
+ // interpolate
+
+ if ( type === "pos" ) {
+
+ vector = object.position;
+
+ if ( this.interpolationType === THREE.AnimationHandler.LINEAR ) {
+
+ vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
+ vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
+ vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;
+
+ } else if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+ this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+ this.points[ 0 ] = this.getPrevKeyWith( "pos", h, prevKey.index - 1 )[ "pos" ];
+ this.points[ 1 ] = prevXYZ;
+ this.points[ 2 ] = nextXYZ;
+ this.points[ 3 ] = this.getNextKeyWith( "pos", h, nextKey.index + 1 )[ "pos" ];
+
+ scale = scale * 0.33 + 0.33;
+
+ currentPoint = this.interpolateCatmullRom( this.points, scale );
+
+ vector.x = currentPoint[ 0 ];
+ vector.y = currentPoint[ 1 ];
+ vector.z = currentPoint[ 2 ];
+
+ if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+ forwardPoint = this.interpolateCatmullRom( this.points, scale * 1.01 );
+
+ this.target.set( forwardPoint[ 0 ], forwardPoint[ 1 ], forwardPoint[ 2 ] );
+ this.target.sub( vector );
+ this.target.y = 0;
+ this.target.normalize();
+
+ angle = Math.atan2( this.target.x, this.target.z );
+ object.rotation.set( 0, angle, 0 );
+
+ }
+
+ }
+
+ } else if ( type === "rot" ) {
+
+ THREE.Quaternion.slerp( prevXYZ, nextXYZ, object.quaternion, scale );
+
+ } else if ( type === "scl" ) {
+
+ vector = object.scale;
+
+ vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
+ vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
+ vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;
+
+ }
+
+ }
+
+ }
+
+};
+
+// Catmull-Rom spline
+
+THREE.Animation.prototype.interpolateCatmullRom = function ( points, scale ) {
+
+ var c = [], v3 = [],
+ point, intPoint, weight, w2, w3,
+ pa, pb, pc, pd;
+
+ point = ( points.length - 1 ) * scale;
+ intPoint = Math.floor( point );
+ weight = point - intPoint;
+
+ c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1;
+ c[ 1 ] = intPoint;
+ c[ 2 ] = intPoint > points.length - 2 ? intPoint : intPoint + 1;
+ c[ 3 ] = intPoint > points.length - 3 ? intPoint : intPoint + 2;
+
+ pa = points[ c[ 0 ] ];
+ pb = points[ c[ 1 ] ];
+ pc = points[ c[ 2 ] ];
+ pd = points[ c[ 3 ] ];
+
+ w2 = weight * weight;
+ w3 = weight * w2;
+
+ v3[ 0 ] = this.interpolate( pa[ 0 ], pb[ 0 ], pc[ 0 ], pd[ 0 ], weight, w2, w3 );
+ v3[ 1 ] = this.interpolate( pa[ 1 ], pb[ 1 ], pc[ 1 ], pd[ 1 ], weight, w2, w3 );
+ v3[ 2 ] = this.interpolate( pa[ 2 ], pb[ 2 ], pc[ 2 ], pd[ 2 ], weight, w2, w3 );
+
+ return v3;
+
+};
+
+THREE.Animation.prototype.interpolate = function ( p0, p1, p2, p3, t, t2, t3 ) {
+
+ var v0 = ( p2 - p0 ) * 0.5,
+ v1 = ( p3 - p1 ) * 0.5;
+
+ return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+};
+
+
+
+// Get next key with
+
+THREE.Animation.prototype.getNextKeyWith = function ( type, h, key ) {
+
+ var keys = this.data.hierarchy[ h ].keys;
+
+ if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+ this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+ key = key < keys.length - 1 ? key : keys.length - 1;
+
+ } else {
+
+ key = key % keys.length;
+
+ }
+
+ for ( ; key < keys.length; key++ ) {
+
+ if ( keys[ key ][ type ] !== undefined ) {
+
+ return keys[ key ];
+
+ }
+
+ }
+
+ return this.data.hierarchy[ h ].keys[ 0 ];
+
+};
+
+// Get previous key with
+
+THREE.Animation.prototype.getPrevKeyWith = function ( type, h, key ) {
+
+ var keys = this.data.hierarchy[ h ].keys;
+
+ if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
+ this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+
+ key = key > 0 ? key : 0;
+
+ } else {
+
+ key = key >= 0 ? key : key + keys.length;
+
+ }
+
+
+ for ( ; key >= 0; key -- ) {
+
+ if ( keys[ key ][ type ] !== undefined ) {
+
+ return keys[ key ];
+
+ }
+
+ }
+
+ return this.data.hierarchy[ h ].keys[ keys.length - 1 ];
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author khang duong
+ * @author erik kitson
+ */
+
+THREE.KeyFrameAnimation = function( root, data, JITCompile ) {
+
+ this.root = root;
+ this.data = THREE.AnimationHandler.get( data );
+ this.hierarchy = THREE.AnimationHandler.parse( root );
+ this.currentTime = 0;
+ this.timeScale = 0.001;
+ this.isPlaying = false;
+ this.isPaused = true;
+ this.loop = true;
+ this.JITCompile = JITCompile !== undefined ? JITCompile : true;
+
+ // initialize to first keyframes
+
+ for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+ var keys = this.data.hierarchy[h].keys,
+ sids = this.data.hierarchy[h].sids,
+ obj = this.hierarchy[h];
+
+ if ( keys.length && sids ) {
+
+ for ( var s = 0; s < sids.length; s++ ) {
+
+ var sid = sids[ s ],
+ next = this.getNextKeyWith( sid, h, 0 );
+
+ if ( next ) {
+
+ next.apply( sid );
+
+ }
+
+ }
+
+ obj.matrixAutoUpdate = false;
+ this.data.hierarchy[h].node.updateMatrix();
+ obj.matrixWorldNeedsUpdate = true;
+
+ }
+
+ }
+
+};
+
+// Play
+
+THREE.KeyFrameAnimation.prototype.play = function( loop, startTimeMS ) {
+
+ if( !this.isPlaying ) {
+
+ this.isPlaying = true;
+ this.loop = loop !== undefined ? loop : true;
+ this.currentTime = startTimeMS !== undefined ? startTimeMS : 0;
+ this.startTimeMs = startTimeMS;
+ this.startTime = 10000000;
+ this.endTime = -this.startTime;
+
+
+ // reset key cache
+
+ var h, hl = this.hierarchy.length,
+ object,
+ node;
+
+ for ( h = 0; h < hl; h++ ) {
+
+ object = this.hierarchy[ h ];
+ node = this.data.hierarchy[ h ];
+
+ if ( node.animationCache === undefined ) {
+
+ node.animationCache = {};
+ node.animationCache.prevKey = null;
+ node.animationCache.nextKey = null;
+ node.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix;
+
+ }
+
+ var keys = this.data.hierarchy[h].keys;
+
+ if (keys.length) {
+
+ node.animationCache.prevKey = keys[ 0 ];
+ node.animationCache.nextKey = keys[ 1 ];
+
+ this.startTime = Math.min( keys[0].time, this.startTime );
+ this.endTime = Math.max( keys[keys.length - 1].time, this.endTime );
+
+ }
+
+ }
+
+ this.update( 0 );
+
+ }
+
+ this.isPaused = false;
+
+ THREE.AnimationHandler.addToUpdate( this );
+
+};
+
+
+
+// Pause
+
+THREE.KeyFrameAnimation.prototype.pause = function() {
+
+ if( this.isPaused ) {
+
+ THREE.AnimationHandler.addToUpdate( this );
+
+ } else {
+
+ THREE.AnimationHandler.removeFromUpdate( this );
+
+ }
+
+ this.isPaused = !this.isPaused;
+
+};
+
+
+// Stop
+
+THREE.KeyFrameAnimation.prototype.stop = function() {
+
+ this.isPlaying = false;
+ this.isPaused = false;
+ THREE.AnimationHandler.removeFromUpdate( this );
+
+
+ // reset JIT matrix and remove cache
+
+ for ( var h = 0; h < this.data.hierarchy.length; h++ ) {
+
+ var obj = this.hierarchy[ h ];
+ var node = this.data.hierarchy[ h ];
+
+ if ( node.animationCache !== undefined ) {
+
+ var original = node.animationCache.originalMatrix;
+
+ if( obj instanceof THREE.Bone ) {
+
+ original.copy( obj.skinMatrix );
+ obj.skinMatrix = original;
+
+ } else {
+
+ original.copy( obj.matrix );
+ obj.matrix = original;
+
+ }
+
+ delete node.animationCache;
+
+ }
+
+ }
+
+};
+
+
+// Update
+
+THREE.KeyFrameAnimation.prototype.update = function( deltaTimeMS ) {
+
+ // early out
+
+ if( !this.isPlaying ) return;
+
+
+ // vars
+
+ var prevKey, nextKey;
+ var object;
+ var node;
+ var frame;
+ var JIThierarchy = this.data.JIT.hierarchy;
+ var currentTime, unloopedCurrentTime;
+ var looped;
+
+
+ // update
+
+ this.currentTime += deltaTimeMS * this.timeScale;
+
+ unloopedCurrentTime = this.currentTime;
+ currentTime = this.currentTime = this.currentTime % this.data.length;
+
+ // if looped around, the current time should be based on the startTime
+ if ( currentTime < this.startTimeMs ) {
+
+ currentTime = this.currentTime = this.startTimeMs + currentTime;
+
+ }
+
+ frame = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 );
+ looped = currentTime < unloopedCurrentTime;
+
+ if ( looped && !this.loop ) {
+
+ // Set the animation to the last keyframes and stop
+ for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+ var keys = this.data.hierarchy[h].keys,
+ sids = this.data.hierarchy[h].sids,
+ end = keys.length-1,
+ obj = this.hierarchy[h];
+
+ if ( keys.length ) {
+
+ for ( var s = 0; s < sids.length; s++ ) {
+
+ var sid = sids[ s ],
+ prev = this.getPrevKeyWith( sid, h, end );
+
+ if ( prev ) {
+ prev.apply( sid );
+
+ }
+
+ }
+
+ this.data.hierarchy[h].node.updateMatrix();
+ obj.matrixWorldNeedsUpdate = true;
+
+ }
+
+ }
+
+ this.stop();
+ return;
+
+ }
+
+ // check pre-infinity
+ if ( currentTime < this.startTime ) {
+
+ return;
+
+ }
+
+ // update
+
+ for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+ object = this.hierarchy[ h ];
+ node = this.data.hierarchy[ h ];
+
+ var keys = node.keys,
+ animationCache = node.animationCache;
+
+ // use JIT?
+
+ if ( this.JITCompile && JIThierarchy[ h ][ frame ] !== undefined ) {
+
+ if( object instanceof THREE.Bone ) {
+
+ object.skinMatrix = JIThierarchy[ h ][ frame ];
+ object.matrixWorldNeedsUpdate = false;
+
+ } else {
+
+ object.matrix = JIThierarchy[ h ][ frame ];
+ object.matrixWorldNeedsUpdate = true;
+
+ }
+
+ // use interpolation
+
+ } else if ( keys.length ) {
+
+ // make sure so original matrix and not JIT matrix is set
+
+ if ( this.JITCompile && animationCache ) {
+
+ if( object instanceof THREE.Bone ) {
+
+ object.skinMatrix = animationCache.originalMatrix;
+
+ } else {
+
+ object.matrix = animationCache.originalMatrix;
+
+ }
+
+ }
+
+ prevKey = animationCache.prevKey;
+ nextKey = animationCache.nextKey;
+
+ if ( prevKey && nextKey ) {
+
+ // switch keys?
+
+ if ( nextKey.time <= unloopedCurrentTime ) {
+
+ // did we loop?
+
+ if ( looped && this.loop ) {
+
+ prevKey = keys[ 0 ];
+ nextKey = keys[ 1 ];
+
+ while ( nextKey.time < currentTime ) {
+
+ prevKey = nextKey;
+ nextKey = keys[ prevKey.index + 1 ];
+
+ }
+
+ } else if ( !looped ) {
+
+ var lastIndex = keys.length - 1;
+
+ while ( nextKey.time < currentTime && nextKey.index !== lastIndex ) {
+
+ prevKey = nextKey;
+ nextKey = keys[ prevKey.index + 1 ];
+
+ }
+
+ }
+
+ animationCache.prevKey = prevKey;
+ animationCache.nextKey = nextKey;
+
+ }
+ if(nextKey.time >= currentTime)
+ prevKey.interpolate( nextKey, currentTime );
+ else
+ prevKey.interpolate( nextKey, nextKey.time);
+
+ }
+
+ this.data.hierarchy[h].node.updateMatrix();
+ object.matrixWorldNeedsUpdate = true;
+
+ }
+
+ }
+
+ // update JIT?
+
+ if ( this.JITCompile ) {
+
+ if ( JIThierarchy[ 0 ][ frame ] === undefined ) {
+
+ this.hierarchy[ 0 ].updateMatrixWorld( true );
+
+ for ( var h = 0; h < this.hierarchy.length; h++ ) {
+
+ if( this.hierarchy[ h ] instanceof THREE.Bone ) {
+
+ JIThierarchy[ h ][ frame ] = this.hierarchy[ h ].skinMatrix.clone();
+
+ } else {
+
+ JIThierarchy[ h ][ frame ] = this.hierarchy[ h ].matrix.clone();
+
+ }
+
+ }
+
+ }
+
+ }
+
+};
+
+// Get next key with
+
+THREE.KeyFrameAnimation.prototype.getNextKeyWith = function( sid, h, key ) {
+
+ var keys = this.data.hierarchy[ h ].keys;
+ key = key % keys.length;
+
+ for ( ; key < keys.length; key++ ) {
+
+ if ( keys[ key ].hasTarget( sid ) ) {
+
+ return keys[ key ];
+
+ }
+
+ }
+
+ return keys[ 0 ];
+
+};
+
+// Get previous key with
+
+THREE.KeyFrameAnimation.prototype.getPrevKeyWith = function( sid, h, key ) {
+
+ var keys = this.data.hierarchy[ h ].keys;
+ key = key >= 0 ? key : key + keys.length;
+
+ for ( ; key >= 0; key-- ) {
+
+ if ( keys[ key ].hasTarget( sid ) ) {
+
+ return keys[ key ];
+
+ }
+
+ }
+
+ return keys[ keys.length - 1 ];
+
+};
+
+/**
+ * Camera for rendering cube maps
+ * - renders scene into axis-aligned cube
+ *
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.CubeCamera = function ( near, far, cubeResolution ) {
+
+ THREE.Object3D.call( this );
+
+ var fov = 90, aspect = 1;
+
+ var cameraPX = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraPX.up.set( 0, -1, 0 );
+ cameraPX.lookAt( new THREE.Vector3( 1, 0, 0 ) );
+ this.add( cameraPX );
+
+ var cameraNX = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraNX.up.set( 0, -1, 0 );
+ cameraNX.lookAt( new THREE.Vector3( -1, 0, 0 ) );
+ this.add( cameraNX );
+
+ var cameraPY = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraPY.up.set( 0, 0, 1 );
+ cameraPY.lookAt( new THREE.Vector3( 0, 1, 0 ) );
+ this.add( cameraPY );
+
+ var cameraNY = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraNY.up.set( 0, 0, -1 );
+ cameraNY.lookAt( new THREE.Vector3( 0, -1, 0 ) );
+ this.add( cameraNY );
+
+ var cameraPZ = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraPZ.up.set( 0, -1, 0 );
+ cameraPZ.lookAt( new THREE.Vector3( 0, 0, 1 ) );
+ this.add( cameraPZ );
+
+ var cameraNZ = new THREE.PerspectiveCamera( fov, aspect, near, far );
+ cameraNZ.up.set( 0, -1, 0 );
+ cameraNZ.lookAt( new THREE.Vector3( 0, 0, -1 ) );
+ this.add( cameraNZ );
+
+ this.renderTarget = new THREE.WebGLRenderTargetCube( cubeResolution, cubeResolution, { format: THREE.RGBFormat, magFilter: THREE.LinearFilter, minFilter: THREE.LinearFilter } );
+
+ this.updateCubeMap = function ( renderer, scene ) {
+
+ var renderTarget = this.renderTarget;
+ var generateMipmaps = renderTarget.generateMipmaps;
+
+ renderTarget.generateMipmaps = false;
+
+ renderTarget.activeCubeFace = 0;
+ renderer.render( scene, cameraPX, renderTarget );
+
+ renderTarget.activeCubeFace = 1;
+ renderer.render( scene, cameraNX, renderTarget );
+
+ renderTarget.activeCubeFace = 2;
+ renderer.render( scene, cameraPY, renderTarget );
+
+ renderTarget.activeCubeFace = 3;
+ renderer.render( scene, cameraNY, renderTarget );
+
+ renderTarget.activeCubeFace = 4;
+ renderer.render( scene, cameraPZ, renderTarget );
+
+ renderTarget.generateMipmaps = generateMipmaps;
+
+ renderTarget.activeCubeFace = 5;
+ renderer.render( scene, cameraNZ, renderTarget );
+
+ };
+
+};
+
+THREE.CubeCamera.prototype = Object.create( THREE.Object3D.prototype );
+
+/*
+ * @author zz85 / http://twitter.com/blurspline / http://www.lab4games.net/zz85/blog
+ *
+ * A general perpose camera, for setting FOV, Lens Focal Length,
+ * and switching between perspective and orthographic views easily.
+ * Use this only if you do not wish to manage
+ * both a Orthographic and Perspective Camera
+ *
+ */
+
+
+THREE.CombinedCamera = function ( width, height, fov, near, far, orthoNear, orthoFar ) {
+
+ THREE.Camera.call( this );
+
+ this.fov = fov;
+
+ this.left = -width / 2;
+ this.right = width / 2
+ this.top = height / 2;
+ this.bottom = -height / 2;
+
+ // We could also handle the projectionMatrix internally, but just wanted to test nested camera objects
+
+ this.cameraO = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, orthoNear, orthoFar );
+ this.cameraP = new THREE.PerspectiveCamera( fov, width / height, near, far );
+
+ this.zoom = 1;
+
+ this.toPerspective();
+
+ var aspect = width/height;
+
+};
+
+THREE.CombinedCamera.prototype = Object.create( THREE.Camera.prototype );
+
+THREE.CombinedCamera.prototype.toPerspective = function () {
+
+ // Switches to the Perspective Camera
+
+ this.near = this.cameraP.near;
+ this.far = this.cameraP.far;
+
+ this.cameraP.fov = this.fov / this.zoom ;
+
+ this.cameraP.updateProjectionMatrix();
+
+ this.projectionMatrix = this.cameraP.projectionMatrix;
+
+ this.inPerspectiveMode = true;
+ this.inOrthographicMode = false;
+
+};
+
+THREE.CombinedCamera.prototype.toOrthographic = function () {
+
+ // Switches to the Orthographic camera estimating viewport from Perspective
+
+ var fov = this.fov;
+ var aspect = this.cameraP.aspect;
+ var near = this.cameraP.near;
+ var far = this.cameraP.far;
+
+ // The size that we set is the mid plane of the viewing frustum
+
+ var hyperfocus = ( near + far ) / 2;
+
+ var halfHeight = Math.tan( fov / 2 ) * hyperfocus;
+ var planeHeight = 2 * halfHeight;
+ var planeWidth = planeHeight * aspect;
+ var halfWidth = planeWidth / 2;
+
+ halfHeight /= this.zoom;
+ halfWidth /= this.zoom;
+
+ this.cameraO.left = -halfWidth;
+ this.cameraO.right = halfWidth;
+ this.cameraO.top = halfHeight;
+ this.cameraO.bottom = -halfHeight;
+
+ // this.cameraO.left = -farHalfWidth;
+ // this.cameraO.right = farHalfWidth;
+ // this.cameraO.top = farHalfHeight;
+ // this.cameraO.bottom = -farHalfHeight;
+
+ // this.cameraO.left = this.left / this.zoom;
+ // this.cameraO.right = this.right / this.zoom;
+ // this.cameraO.top = this.top / this.zoom;
+ // this.cameraO.bottom = this.bottom / this.zoom;
+
+ this.cameraO.updateProjectionMatrix();
+
+ this.near = this.cameraO.near;
+ this.far = this.cameraO.far;
+ this.projectionMatrix = this.cameraO.projectionMatrix;
+
+ this.inPerspectiveMode = false;
+ this.inOrthographicMode = true;
+
+};
+
+
+THREE.CombinedCamera.prototype.setSize = function( width, height ) {
+
+ this.cameraP.aspect = width / height;
+ this.left = -width / 2;
+ this.right = width / 2
+ this.top = height / 2;
+ this.bottom = -height / 2;
+
+};
+
+
+THREE.CombinedCamera.prototype.setFov = function( fov ) {
+
+ this.fov = fov;
+
+ if ( this.inPerspectiveMode ) {
+
+ this.toPerspective();
+
+ } else {
+
+ this.toOrthographic();
+
+ }
+
+};
+
+// For mantaining similar API with PerspectiveCamera
+
+THREE.CombinedCamera.prototype.updateProjectionMatrix = function() {
+
+ if ( this.inPerspectiveMode ) {
+
+ this.toPerspective();
+
+ } else {
+
+ this.toPerspective();
+ this.toOrthographic();
+
+ }
+
+};
+
+/*
+* Uses Focal Length (in mm) to estimate and set FOV
+* 35mm (fullframe) camera is used if frame size is not specified;
+* Formula based on http://www.bobatkins.com/photography/technical/field_of_view.html
+*/
+THREE.CombinedCamera.prototype.setLens = function ( focalLength, frameHeight ) {
+
+ if ( frameHeight === undefined ) frameHeight = 24;
+
+ var fov = 2 * THREE.Math.radToDeg( Math.atan( frameHeight / ( focalLength * 2 ) ) );
+
+ this.setFov( fov );
+
+ return fov;
+};
+
+
+THREE.CombinedCamera.prototype.setZoom = function( zoom ) {
+
+ this.zoom = zoom;
+
+ if ( this.inPerspectiveMode ) {
+
+ this.toPerspective();
+
+ } else {
+
+ this.toOrthographic();
+
+ }
+
+};
+
+THREE.CombinedCamera.prototype.toFrontView = function() {
+
+ this.rotation.x = 0;
+ this.rotation.y = 0;
+ this.rotation.z = 0;
+
+ // should we be modifing the matrix instead?
+
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toBackView = function() {
+
+ this.rotation.x = 0;
+ this.rotation.y = Math.PI;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toLeftView = function() {
+
+ this.rotation.x = 0;
+ this.rotation.y = - Math.PI / 2;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toRightView = function() {
+
+ this.rotation.x = 0;
+ this.rotation.y = Math.PI / 2;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toTopView = function() {
+
+ this.rotation.x = - Math.PI / 2;
+ this.rotation.y = 0;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+THREE.CombinedCamera.prototype.toBottomView = function() {
+
+ this.rotation.x = Math.PI / 2;
+ this.rotation.y = 0;
+ this.rotation.z = 0;
+ this.rotationAutoUpdate = false;
+
+};
+
+
+/**
+ * @author hughes
+ */
+
+THREE.CircleGeometry = function ( radius, segments, thetaStart, thetaLength ) {
+
+ THREE.Geometry.call( this );
+
+ this.radius = radius = radius || 50;
+ this.segments = segments = segments !== undefined ? Math.max( 3, segments ) : 8;
+
+ this.thetaStart = thetaStart = thetaStart !== undefined ? thetaStart : 0;
+ this.thetaLength = thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;
+
+ var i, uvs = [],
+ center = new THREE.Vector3(), centerUV = new THREE.Vector2( 0.5, 0.5 );
+
+ this.vertices.push(center);
+ uvs.push( centerUV );
+
+ for ( i = 0; i <= segments; i ++ ) {
+
+ var vertex = new THREE.Vector3();
+ var segment = thetaStart + i / segments * thetaLength;
+
+ vertex.x = radius * Math.cos( segment );
+ vertex.y = radius * Math.sin( segment );
+
+ this.vertices.push( vertex );
+ uvs.push( new THREE.Vector2( ( vertex.x / radius + 1 ) / 2, ( vertex.y / radius + 1 ) / 2 ) );
+
+ }
+
+ var n = new THREE.Vector3( 0, 0, 1 );
+
+ for ( i = 1; i <= segments; i ++ ) {
+
+ var v1 = i;
+ var v2 = i + 1 ;
+ var v3 = 0;
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n, n, n ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uvs[ i ], uvs[ i + 1 ], centerUV ] );
+
+ }
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+
+ this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Cube.as
+ */
+
+THREE.CubeGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) {
+
+ THREE.Geometry.call( this );
+
+ var scope = this;
+
+ this.width = width;
+ this.height = height;
+ this.depth = depth;
+
+ this.widthSegments = widthSegments || 1;
+ this.heightSegments = heightSegments || 1;
+ this.depthSegments = depthSegments || 1;
+
+ var width_half = this.width / 2;
+ var height_half = this.height / 2;
+ var depth_half = this.depth / 2;
+
+ buildPlane( 'z', 'y', - 1, - 1, this.depth, this.height, width_half, 0 ); // px
+ buildPlane( 'z', 'y', 1, - 1, this.depth, this.height, - width_half, 1 ); // nx
+ buildPlane( 'x', 'z', 1, 1, this.width, this.depth, height_half, 2 ); // py
+ buildPlane( 'x', 'z', 1, - 1, this.width, this.depth, - height_half, 3 ); // ny
+ buildPlane( 'x', 'y', 1, - 1, this.width, this.height, depth_half, 4 ); // pz
+ buildPlane( 'x', 'y', - 1, - 1, this.width, this.height, - depth_half, 5 ); // nz
+
+ function buildPlane( u, v, udir, vdir, width, height, depth, materialIndex ) {
+
+ var w, ix, iy,
+ gridX = scope.widthSegments,
+ gridY = scope.heightSegments,
+ width_half = width / 2,
+ height_half = height / 2,
+ offset = scope.vertices.length;
+
+ if ( ( u === 'x' && v === 'y' ) || ( u === 'y' && v === 'x' ) ) {
+
+ w = 'z';
+
+ } else if ( ( u === 'x' && v === 'z' ) || ( u === 'z' && v === 'x' ) ) {
+
+ w = 'y';
+ gridY = scope.depthSegments;
+
+ } else if ( ( u === 'z' && v === 'y' ) || ( u === 'y' && v === 'z' ) ) {
+
+ w = 'x';
+ gridX = scope.depthSegments;
+
+ }
+
+ var gridX1 = gridX + 1,
+ gridY1 = gridY + 1,
+ segment_width = width / gridX,
+ segment_height = height / gridY,
+ normal = new THREE.Vector3();
+
+ normal[ w ] = depth > 0 ? 1 : - 1;
+
+ for ( iy = 0; iy < gridY1; iy ++ ) {
+
+ for ( ix = 0; ix < gridX1; ix ++ ) {
+
+ var vector = new THREE.Vector3();
+ vector[ u ] = ( ix * segment_width - width_half ) * udir;
+ vector[ v ] = ( iy * segment_height - height_half ) * vdir;
+ vector[ w ] = depth;
+
+ scope.vertices.push( vector );
+
+ }
+
+ }
+
+ for ( iy = 0; iy < gridY; iy++ ) {
+
+ for ( ix = 0; ix < gridX; ix++ ) {
+
+ var a = ix + gridX1 * iy;
+ var b = ix + gridX1 * ( iy + 1 );
+ var c = ( ix + 1 ) + gridX1 * ( iy + 1 );
+ var d = ( ix + 1 ) + gridX1 * iy;
+
+ var uva = new THREE.Vector2( ix / gridX, 1 - iy / gridY );
+ var uvb = new THREE.Vector2( ix / gridX, 1 - ( iy + 1 ) / gridY );
+ var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iy + 1 ) / gridY );
+ var uvd = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iy / gridY );
+
+ var face = new THREE.Face3( a + offset, b + offset, d + offset );
+ face.normal.copy( normal );
+ face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
+ face.materialIndex = materialIndex;
+
+ scope.faces.push( face );
+ scope.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] );
+
+ face = new THREE.Face3( b + offset, c + offset, d + offset );
+ face.normal.copy( normal );
+ face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
+ face.materialIndex = materialIndex;
+
+ scope.faces.push( face );
+ scope.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] );
+
+ }
+
+ }
+
+ }
+
+ this.computeCentroids();
+ this.mergeVertices();
+
+};
+
+THREE.CubeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.CylinderGeometry = function ( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded ) {
+
+ THREE.Geometry.call( this );
+
+ this.radiusTop = radiusTop = radiusTop !== undefined ? radiusTop : 20;
+ this.radiusBottom = radiusBottom = radiusBottom !== undefined ? radiusBottom : 20;
+ this.height = height = height !== undefined ? height : 100;
+
+ this.radialSegments = radialSegments = radialSegments || 8;
+ this.heightSegments = heightSegments = heightSegments || 1;
+
+ this.openEnded = openEnded = openEnded !== undefined ? openEnded : false;
+
+ var heightHalf = height / 2;
+
+ var x, y, vertices = [], uvs = [];
+
+ for ( y = 0; y <= heightSegments; y ++ ) {
+
+ var verticesRow = [];
+ var uvsRow = [];
+
+ var v = y / heightSegments;
+ var radius = v * ( radiusBottom - radiusTop ) + radiusTop;
+
+ for ( x = 0; x <= radialSegments; x ++ ) {
+
+ var u = x / radialSegments;
+
+ var vertex = new THREE.Vector3();
+ vertex.x = radius * Math.sin( u * Math.PI * 2 );
+ vertex.y = - v * height + heightHalf;
+ vertex.z = radius * Math.cos( u * Math.PI * 2 );
+
+ this.vertices.push( vertex );
+
+ verticesRow.push( this.vertices.length - 1 );
+ uvsRow.push( new THREE.Vector2( u, 1 - v ) );
+
+ }
+
+ vertices.push( verticesRow );
+ uvs.push( uvsRow );
+
+ }
+
+ var tanTheta = ( radiusBottom - radiusTop ) / height;
+ var na, nb;
+
+ for ( x = 0; x < radialSegments; x ++ ) {
+
+ if ( radiusTop !== 0 ) {
+
+ na = this.vertices[ vertices[ 0 ][ x ] ].clone();
+ nb = this.vertices[ vertices[ 0 ][ x + 1 ] ].clone();
+
+ } else {
+
+ na = this.vertices[ vertices[ 1 ][ x ] ].clone();
+ nb = this.vertices[ vertices[ 1 ][ x + 1 ] ].clone();
+
+ }
+
+ na.setY( Math.sqrt( na.x * na.x + na.z * na.z ) * tanTheta ).normalize();
+ nb.setY( Math.sqrt( nb.x * nb.x + nb.z * nb.z ) * tanTheta ).normalize();
+
+ for ( y = 0; y < heightSegments; y ++ ) {
+
+ var v1 = vertices[ y ][ x ];
+ var v2 = vertices[ y + 1 ][ x ];
+ var v3 = vertices[ y + 1 ][ x + 1 ];
+ var v4 = vertices[ y ][ x + 1 ];
+
+ var n1 = na.clone();
+ var n2 = na.clone();
+ var n3 = nb.clone();
+ var n4 = nb.clone();
+
+ var uv1 = uvs[ y ][ x ].clone();
+ var uv2 = uvs[ y + 1 ][ x ].clone();
+ var uv3 = uvs[ y + 1 ][ x + 1 ].clone();
+ var uv4 = uvs[ y ][ x + 1 ].clone();
+
+ this.faces.push( new THREE.Face3( v1, v2, v4, [ n1, n2, n4 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv4 ] );
+
+ this.faces.push( new THREE.Face3( v2, v3, v4, [ n2, n3, n4 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv2, uv3, uv4 ] );
+
+ }
+
+ }
+
+ // top cap
+
+ if ( openEnded === false && radiusTop > 0 ) {
+
+ this.vertices.push( new THREE.Vector3( 0, heightHalf, 0 ) );
+
+ for ( x = 0; x < radialSegments; x ++ ) {
+
+ var v1 = vertices[ 0 ][ x ];
+ var v2 = vertices[ 0 ][ x + 1 ];
+ var v3 = this.vertices.length - 1;
+
+ var n1 = new THREE.Vector3( 0, 1, 0 );
+ var n2 = new THREE.Vector3( 0, 1, 0 );
+ var n3 = new THREE.Vector3( 0, 1, 0 );
+
+ var uv1 = uvs[ 0 ][ x ].clone();
+ var uv2 = uvs[ 0 ][ x + 1 ].clone();
+ var uv3 = new THREE.Vector2( uv2.u, 0 );
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] );
+
+ }
+
+ }
+
+ // bottom cap
+
+ if ( openEnded === false && radiusBottom > 0 ) {
+
+ this.vertices.push( new THREE.Vector3( 0, - heightHalf, 0 ) );
+
+ for ( x = 0; x < radialSegments; x ++ ) {
+
+ var v1 = vertices[ y ][ x + 1 ];
+ var v2 = vertices[ y ][ x ];
+ var v3 = this.vertices.length - 1;
+
+ var n1 = new THREE.Vector3( 0, - 1, 0 );
+ var n2 = new THREE.Vector3( 0, - 1, 0 );
+ var n3 = new THREE.Vector3( 0, - 1, 0 );
+
+ var uv1 = uvs[ y ][ x + 1 ].clone();
+ var uv2 = uvs[ y ][ x ].clone();
+ var uv3 = new THREE.Vector2( uv2.u, 1 );
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] );
+
+ }
+
+ }
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+
+}
+
+THREE.CylinderGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ *
+ * Creates extruded geometry from a path shape.
+ *
+ * parameters = {
+ *
+ * size: , // size of the text
+ * height: , // thickness to extrude text
+ * curveSegments: , // number of points on the curves
+ * steps: , // number of points for z-side extrusions / used for subdividing segements of extrude spline too
+ * amount: , // Amount
+ *
+ * bevelEnabled: , // turn on bevel
+ * bevelThickness: , // how deep into text bevel goes
+ * bevelSize: , // how far from text outline is bevel
+ * bevelSegments: , // number of bevel layers
+ *
+ * extrudePath: // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined)
+ * frames: // containing arrays of tangents, normals, binormals
+ *
+ * material: // material index for front and back faces
+ * extrudeMaterial: // material index for extrusion and beveled faces
+ * uvGenerator: // object that provides UV generator functions
+ *
+ * }
+ **/
+
+THREE.ExtrudeGeometry = function ( shapes, options ) {
+
+ if ( typeof( shapes ) === "undefined" ) {
+ shapes = [];
+ return;
+ }
+
+ THREE.Geometry.call( this );
+
+ shapes = shapes instanceof Array ? shapes : [ shapes ];
+
+ this.shapebb = shapes[ shapes.length - 1 ].getBoundingBox();
+
+ this.addShapeList( shapes, options );
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+
+ // can't really use automatic vertex normals
+ // as then front and back sides get smoothed too
+ // should do separate smoothing just for sides
+
+ //this.computeVertexNormals();
+
+ //console.log( "took", ( Date.now() - startTime ) );
+
+};
+
+THREE.ExtrudeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+THREE.ExtrudeGeometry.prototype.addShapeList = function ( shapes, options ) {
+ var sl = shapes.length;
+
+ for ( var s = 0; s < sl; s ++ ) {
+ var shape = shapes[ s ];
+ this.addShape( shape, options );
+ }
+};
+
+THREE.ExtrudeGeometry.prototype.addShape = function ( shape, options ) {
+
+ var amount = options.amount !== undefined ? options.amount : 100;
+
+ var bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6; // 10
+ var bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2; // 8
+ var bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;
+
+ var bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true; // false
+
+ var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
+
+ var steps = options.steps !== undefined ? options.steps : 1;
+
+ var extrudePath = options.extrudePath;
+ var extrudePts, extrudeByPath = false;
+
+ var material = options.material;
+ var extrudeMaterial = options.extrudeMaterial;
+
+ // Use default WorldUVGenerator if no UV generators are specified.
+ var uvgen = options.UVGenerator !== undefined ? options.UVGenerator : THREE.ExtrudeGeometry.WorldUVGenerator;
+
+ var shapebb = this.shapebb;
+ //shapebb = shape.getBoundingBox();
+
+
+
+ var splineTube, binormal, normal, position2;
+ if ( extrudePath ) {
+
+ extrudePts = extrudePath.getSpacedPoints( steps );
+
+ extrudeByPath = true;
+ bevelEnabled = false; // bevels not supported for path extrusion
+
+ // SETUP TNB variables
+
+ // Reuse TNB from TubeGeomtry for now.
+ // TODO1 - have a .isClosed in spline?
+
+ splineTube = options.frames !== undefined ? options.frames : new THREE.TubeGeometry.FrenetFrames(extrudePath, steps, false);
+
+ // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
+
+ binormal = new THREE.Vector3();
+ normal = new THREE.Vector3();
+ position2 = new THREE.Vector3();
+
+ }
+
+ // Safeguards if bevels are not enabled
+
+ if ( ! bevelEnabled ) {
+
+ bevelSegments = 0;
+ bevelThickness = 0;
+ bevelSize = 0;
+
+ }
+
+ // Variables initalization
+
+ var ahole, h, hl; // looping of holes
+ var scope = this;
+ var bevelPoints = [];
+
+ var shapesOffset = this.vertices.length;
+
+ var shapePoints = shape.extractPoints( curveSegments );
+
+ var vertices = shapePoints.shape;
+ var holes = shapePoints.holes;
+
+ var reverse = !THREE.Shape.Utils.isClockWise( vertices ) ;
+
+ if ( reverse ) {
+
+ vertices = vertices.reverse();
+
+ // Maybe we should also check if holes are in the opposite direction, just to be safe ...
+
+ for ( h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ ahole = holes[ h ];
+
+ if ( THREE.Shape.Utils.isClockWise( ahole ) ) {
+
+ holes[ h ] = ahole.reverse();
+
+ }
+
+ }
+
+ reverse = false; // If vertices are in order now, we shouldn't need to worry about them again (hopefully)!
+
+ }
+
+
+ var faces = THREE.Shape.Utils.triangulateShape ( vertices, holes );
+
+ /* Vertices */
+
+ var contour = vertices; // vertices has all points but contour has only points of circumference
+
+ for ( h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ ahole = holes[ h ];
+
+ vertices = vertices.concat( ahole );
+
+ }
+
+
+ function scalePt2 ( pt, vec, size ) {
+
+ if ( !vec ) console.log( "die" );
+
+ return vec.clone().multiplyScalar( size ).add( pt );
+
+ }
+
+ var b, bs, t, z,
+ vert, vlen = vertices.length,
+ face, flen = faces.length,
+ cont, clen = contour.length;
+
+
+ // Find directions for point movement
+
+ var RAD_TO_DEGREES = 180 / Math.PI;
+
+
+ function getBevelVec( pt_i, pt_j, pt_k ) {
+
+ // Algorithm 2
+
+ return getBevelVec2( pt_i, pt_j, pt_k );
+
+ }
+
+ function getBevelVec1( pt_i, pt_j, pt_k ) {
+
+ var anglea = Math.atan2( pt_j.y - pt_i.y, pt_j.x - pt_i.x );
+ var angleb = Math.atan2( pt_k.y - pt_i.y, pt_k.x - pt_i.x );
+
+ if ( anglea > angleb ) {
+
+ angleb += Math.PI * 2;
+
+ }
+
+ var anglec = ( anglea + angleb ) / 2;
+
+
+ //console.log('angle1', anglea * RAD_TO_DEGREES,'angle2', angleb * RAD_TO_DEGREES, 'anglec', anglec *RAD_TO_DEGREES);
+
+ var x = - Math.cos( anglec );
+ var y = - Math.sin( anglec );
+
+ var vec = new THREE.Vector2( x, y ); //.normalize();
+
+ return vec;
+
+ }
+
+ function getBevelVec2( pt_i, pt_j, pt_k ) {
+
+ var a = THREE.ExtrudeGeometry.__v1,
+ b = THREE.ExtrudeGeometry.__v2,
+ v_hat = THREE.ExtrudeGeometry.__v3,
+ w_hat = THREE.ExtrudeGeometry.__v4,
+ p = THREE.ExtrudeGeometry.__v5,
+ q = THREE.ExtrudeGeometry.__v6,
+ v, w,
+ v_dot_w_hat, q_sub_p_dot_w_hat,
+ s, intersection;
+
+ // good reading for line-line intersection
+ // http://sputsoft.com/blog/2010/03/line-line-intersection.html
+
+ // define a as vector j->i
+ // define b as vectot k->i
+
+ a.set( pt_i.x - pt_j.x, pt_i.y - pt_j.y );
+ b.set( pt_i.x - pt_k.x, pt_i.y - pt_k.y );
+
+ // get unit vectors
+
+ v = a.normalize();
+ w = b.normalize();
+
+ // normals from pt i
+
+ v_hat.set( -v.y, v.x );
+ w_hat.set( w.y, -w.x );
+
+ // pts from i
+
+ p.copy( pt_i ).add( v_hat );
+ q.copy( pt_i ).add( w_hat );
+
+ if ( p.equals( q ) ) {
+
+ //console.log("Warning: lines are straight");
+ return w_hat.clone();
+
+ }
+
+ // Points from j, k. helps prevents points cross overover most of the time
+
+ p.copy( pt_j ).add( v_hat );
+ q.copy( pt_k ).add( w_hat );
+
+ v_dot_w_hat = v.dot( w_hat );
+ q_sub_p_dot_w_hat = q.sub( p ).dot( w_hat );
+
+ // We should not reach these conditions
+
+ if ( v_dot_w_hat === 0 ) {
+
+ console.log( "Either infinite or no solutions!" );
+
+ if ( q_sub_p_dot_w_hat === 0 ) {
+
+ console.log( "Its finite solutions." );
+
+ } else {
+
+ console.log( "Too bad, no solutions." );
+
+ }
+
+ }
+
+ s = q_sub_p_dot_w_hat / v_dot_w_hat;
+
+ if ( s < 0 ) {
+
+ // in case of emergecy, revert to algorithm 1.
+
+ return getBevelVec1( pt_i, pt_j, pt_k );
+
+ }
+
+ intersection = v.multiplyScalar( s ).add( p );
+
+ return intersection.sub( pt_i ).clone(); // Don't normalize!, otherwise sharp corners become ugly
+
+ }
+
+ var contourMovements = [];
+
+ for ( var i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+
+ if ( j === il ) j = 0;
+ if ( k === il ) k = 0;
+
+ // (j)---(i)---(k)
+ // console.log('i,j,k', i, j , k)
+
+ var pt_i = contour[ i ];
+ var pt_j = contour[ j ];
+ var pt_k = contour[ k ];
+
+ contourMovements[ i ]= getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
+
+ }
+
+ var holesMovements = [], oneHoleMovements, verticesMovements = contourMovements.concat();
+
+ for ( h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ ahole = holes[ h ];
+
+ oneHoleMovements = [];
+
+ for ( i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+
+ if ( j === il ) j = 0;
+ if ( k === il ) k = 0;
+
+ // (j)---(i)---(k)
+ oneHoleMovements[ i ]= getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
+
+ }
+
+ holesMovements.push( oneHoleMovements );
+ verticesMovements = verticesMovements.concat( oneHoleMovements );
+
+ }
+
+
+ // Loop bevelSegments, 1 for the front, 1 for the back
+
+ for ( b = 0; b < bevelSegments; b ++ ) {
+ //for ( b = bevelSegments; b > 0; b -- ) {
+
+ t = b / bevelSegments;
+ z = bevelThickness * ( 1 - t );
+
+ //z = bevelThickness * t;
+ bs = bevelSize * ( Math.sin ( t * Math.PI/2 ) ) ; // curved
+ //bs = bevelSize * t ; // linear
+
+ // contract shape
+
+ for ( i = 0, il = contour.length; i < il; i ++ ) {
+
+ vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+ //vert = scalePt( contour[ i ], contourCentroid, bs, false );
+ v( vert.x, vert.y, - z );
+
+ }
+
+ // expand holes
+
+ for ( h = 0, hl = holes.length; h < hl; h++ ) {
+
+ ahole = holes[ h ];
+ oneHoleMovements = holesMovements[ h ];
+
+ for ( i = 0, il = ahole.length; i < il; i++ ) {
+
+ vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+ //vert = scalePt( ahole[ i ], holesCentroids[ h ], bs, true );
+
+ v( vert.x, vert.y, -z );
+
+ }
+
+ }
+
+ }
+
+ bs = bevelSize;
+
+ // Back facing vertices
+
+ for ( i = 0; i < vlen; i ++ ) {
+
+ vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+
+ if ( !extrudeByPath ) {
+
+ v( vert.x, vert.y, 0 );
+
+ } else {
+
+ // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
+
+ normal.copy( splineTube.normals[0] ).multiplyScalar(vert.x);
+ binormal.copy( splineTube.binormals[0] ).multiplyScalar(vert.y);
+
+ position2.copy( extrudePts[0] ).add(normal).add(binormal);
+
+ v( position2.x, position2.y, position2.z );
+
+ }
+
+ }
+
+ // Add stepped vertices...
+ // Including front facing vertices
+
+ var s;
+
+ for ( s = 1; s <= steps; s ++ ) {
+
+ for ( i = 0; i < vlen; i ++ ) {
+
+ vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+
+ if ( !extrudeByPath ) {
+
+ v( vert.x, vert.y, amount / steps * s );
+
+ } else {
+
+ // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
+
+ normal.copy( splineTube.normals[s] ).multiplyScalar( vert.x );
+ binormal.copy( splineTube.binormals[s] ).multiplyScalar( vert.y );
+
+ position2.copy( extrudePts[s] ).add( normal ).add( binormal );
+
+ v( position2.x, position2.y, position2.z );
+
+ }
+
+ }
+
+ }
+
+
+ // Add bevel segments planes
+
+ //for ( b = 1; b <= bevelSegments; b ++ ) {
+ for ( b = bevelSegments - 1; b >= 0; b -- ) {
+
+ t = b / bevelSegments;
+ z = bevelThickness * ( 1 - t );
+ //bs = bevelSize * ( 1-Math.sin ( ( 1 - t ) * Math.PI/2 ) );
+ bs = bevelSize * Math.sin ( t * Math.PI/2 ) ;
+
+ // contract shape
+
+ for ( i = 0, il = contour.length; i < il; i ++ ) {
+
+ vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+ v( vert.x, vert.y, amount + z );
+
+ }
+
+ // expand holes
+
+ for ( h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ ahole = holes[ h ];
+ oneHoleMovements = holesMovements[ h ];
+
+ for ( i = 0, il = ahole.length; i < il; i ++ ) {
+
+ vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+
+ if ( !extrudeByPath ) {
+
+ v( vert.x, vert.y, amount + z );
+
+ } else {
+
+ v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ /* Faces */
+
+ // Top and bottom faces
+
+ buildLidFaces();
+
+ // Sides faces
+
+ buildSideFaces();
+
+
+ ///// Internal functions
+
+ function buildLidFaces() {
+
+ if ( bevelEnabled ) {
+
+ var layer = 0 ; // steps + 1
+ var offset = vlen * layer;
+
+ // Bottom faces
+
+ for ( i = 0; i < flen; i ++ ) {
+
+ face = faces[ i ];
+ f3( face[ 2 ]+ offset, face[ 1 ]+ offset, face[ 0 ] + offset, true );
+
+ }
+
+ layer = steps + bevelSegments * 2;
+ offset = vlen * layer;
+
+ // Top faces
+
+ for ( i = 0; i < flen; i ++ ) {
+
+ face = faces[ i ];
+ f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset, false );
+
+ }
+
+ } else {
+
+ // Bottom faces
+
+ for ( i = 0; i < flen; i++ ) {
+
+ face = faces[ i ];
+ f3( face[ 2 ], face[ 1 ], face[ 0 ], true );
+
+ }
+
+ // Top faces
+
+ for ( i = 0; i < flen; i ++ ) {
+
+ face = faces[ i ];
+ f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps, false );
+
+ }
+ }
+
+ }
+
+ // Create faces for the z-sides of the shape
+
+ function buildSideFaces() {
+
+ var layeroffset = 0;
+ sidewalls( contour, layeroffset );
+ layeroffset += contour.length;
+
+ for ( h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ ahole = holes[ h ];
+ sidewalls( ahole, layeroffset );
+
+ //, true
+ layeroffset += ahole.length;
+
+ }
+
+ }
+
+ function sidewalls( contour, layeroffset ) {
+
+ var j, k;
+ i = contour.length;
+
+ while ( --i >= 0 ) {
+
+ j = i;
+ k = i - 1;
+ if ( k < 0 ) k = contour.length - 1;
+
+ //console.log('b', i,j, i-1, k,vertices.length);
+
+ var s = 0, sl = steps + bevelSegments * 2;
+
+ for ( s = 0; s < sl; s ++ ) {
+
+ var slen1 = vlen * s;
+ var slen2 = vlen * ( s + 1 );
+
+ var a = layeroffset + j + slen1,
+ b = layeroffset + k + slen1,
+ c = layeroffset + k + slen2,
+ d = layeroffset + j + slen2;
+
+ f4( a, b, c, d, contour, s, sl, j, k );
+
+ }
+ }
+
+ }
+
+
+ function v( x, y, z ) {
+
+ scope.vertices.push( new THREE.Vector3( x, y, z ) );
+
+ }
+
+ function f3( a, b, c, isBottom ) {
+
+ a += shapesOffset;
+ b += shapesOffset;
+ c += shapesOffset;
+
+ // normal, color, material
+ scope.faces.push( new THREE.Face3( a, b, c, null, null, material ) );
+
+ var uvs = isBottom ? uvgen.generateBottomUV( scope, shape, options, a, b, c ) : uvgen.generateTopUV( scope, shape, options, a, b, c );
+
+ scope.faceVertexUvs[ 0 ].push( uvs );
+
+ }
+
+ function f4( a, b, c, d, wallContour, stepIndex, stepsLength, contourIndex1, contourIndex2 ) {
+
+ a += shapesOffset;
+ b += shapesOffset;
+ c += shapesOffset;
+ d += shapesOffset;
+
+ scope.faces.push( new THREE.Face3( a, b, d, null, null, extrudeMaterial ) );
+ scope.faces.push( new THREE.Face3( b, c, d, null, null, extrudeMaterial ) );
+
+ var uvs = uvgen.generateSideWallUV( scope, shape, wallContour, options, a, b, c, d,
+ stepIndex, stepsLength, contourIndex1, contourIndex2 );
+
+ scope.faceVertexUvs[ 0 ].push( [ uvs[ 0 ], uvs[ 1 ], uvs[ 3 ] ] );
+ scope.faceVertexUvs[ 0 ].push( [ uvs[ 1 ], uvs[ 2 ], uvs[ 3 ] ] );
+
+ }
+
+};
+
+THREE.ExtrudeGeometry.WorldUVGenerator = {
+
+ generateTopUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) {
+ var ax = geometry.vertices[ indexA ].x,
+ ay = geometry.vertices[ indexA ].y,
+
+ bx = geometry.vertices[ indexB ].x,
+ by = geometry.vertices[ indexB ].y,
+
+ cx = geometry.vertices[ indexC ].x,
+ cy = geometry.vertices[ indexC ].y;
+
+ return [
+ new THREE.Vector2( ax, ay ),
+ new THREE.Vector2( bx, by ),
+ new THREE.Vector2( cx, cy )
+ ];
+
+ },
+
+ generateBottomUV: function( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC ) {
+
+ return this.generateTopUV( geometry, extrudedShape, extrudeOptions, indexA, indexB, indexC );
+
+ },
+
+ generateSideWallUV: function( geometry, extrudedShape, wallContour, extrudeOptions,
+ indexA, indexB, indexC, indexD, stepIndex, stepsLength,
+ contourIndex1, contourIndex2 ) {
+
+ var ax = geometry.vertices[ indexA ].x,
+ ay = geometry.vertices[ indexA ].y,
+ az = geometry.vertices[ indexA ].z,
+
+ bx = geometry.vertices[ indexB ].x,
+ by = geometry.vertices[ indexB ].y,
+ bz = geometry.vertices[ indexB ].z,
+
+ cx = geometry.vertices[ indexC ].x,
+ cy = geometry.vertices[ indexC ].y,
+ cz = geometry.vertices[ indexC ].z,
+
+ dx = geometry.vertices[ indexD ].x,
+ dy = geometry.vertices[ indexD ].y,
+ dz = geometry.vertices[ indexD ].z;
+
+ if ( Math.abs( ay - by ) < 0.01 ) {
+ return [
+ new THREE.Vector2( ax, 1 - az ),
+ new THREE.Vector2( bx, 1 - bz ),
+ new THREE.Vector2( cx, 1 - cz ),
+ new THREE.Vector2( dx, 1 - dz )
+ ];
+ } else {
+ return [
+ new THREE.Vector2( ay, 1 - az ),
+ new THREE.Vector2( by, 1 - bz ),
+ new THREE.Vector2( cy, 1 - cz ),
+ new THREE.Vector2( dy, 1 - dz )
+ ];
+ }
+ }
+};
+
+THREE.ExtrudeGeometry.__v1 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v2 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v3 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v4 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v5 = new THREE.Vector2();
+THREE.ExtrudeGeometry.__v6 = new THREE.Vector2();
+
+/**
+ * @author jonobr1 / http://jonobr1.com
+ *
+ * Creates a one-sided polygonal geometry from a path shape. Similar to
+ * ExtrudeGeometry.
+ *
+ * parameters = {
+ *
+ * curveSegments: , // number of points on the curves. NOT USED AT THE MOMENT.
+ *
+ * material: // material index for front and back faces
+ * uvGenerator: // object that provides UV generator functions
+ *
+ * }
+ **/
+
+THREE.ShapeGeometry = function ( shapes, options ) {
+
+ THREE.Geometry.call( this );
+
+ if ( shapes instanceof Array === false ) shapes = [ shapes ];
+
+ this.shapebb = shapes[ shapes.length - 1 ].getBoundingBox();
+
+ this.addShapeList( shapes, options );
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+
+};
+
+THREE.ShapeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * Add an array of shapes to THREE.ShapeGeometry.
+ */
+THREE.ShapeGeometry.prototype.addShapeList = function ( shapes, options ) {
+
+ for ( var i = 0, l = shapes.length; i < l; i++ ) {
+
+ this.addShape( shapes[ i ], options );
+
+ }
+
+ return this;
+
+};
+
+/**
+ * Adds a shape to THREE.ShapeGeometry, based on THREE.ExtrudeGeometry.
+ */
+THREE.ShapeGeometry.prototype.addShape = function ( shape, options ) {
+
+ if ( options === undefined ) options = {};
+ var curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
+
+ var material = options.material;
+ var uvgen = options.UVGenerator === undefined ? THREE.ExtrudeGeometry.WorldUVGenerator : options.UVGenerator;
+
+ var shapebb = this.shapebb;
+
+ //
+
+ var i, l, hole, s;
+
+ var shapesOffset = this.vertices.length;
+ var shapePoints = shape.extractPoints( curveSegments );
+
+ var vertices = shapePoints.shape;
+ var holes = shapePoints.holes;
+
+ var reverse = !THREE.Shape.Utils.isClockWise( vertices );
+
+ if ( reverse ) {
+
+ vertices = vertices.reverse();
+
+ // Maybe we should also check if holes are in the opposite direction, just to be safe...
+
+ for ( i = 0, l = holes.length; i < l; i++ ) {
+
+ hole = holes[ i ];
+
+ if ( THREE.Shape.Utils.isClockWise( hole ) ) {
+
+ holes[ i ] = hole.reverse();
+
+ }
+
+ }
+
+ reverse = false;
+
+ }
+
+ var faces = THREE.Shape.Utils.triangulateShape( vertices, holes );
+
+ // Vertices
+
+ var contour = vertices;
+
+ for ( i = 0, l = holes.length; i < l; i++ ) {
+
+ hole = holes[ i ];
+ vertices = vertices.concat( hole );
+
+ }
+
+ //
+
+ var vert, vlen = vertices.length;
+ var face, flen = faces.length;
+ var cont, clen = contour.length;
+
+ for ( i = 0; i < vlen; i++ ) {
+
+ vert = vertices[ i ];
+
+ this.vertices.push( new THREE.Vector3( vert.x, vert.y, 0 ) );
+
+ }
+
+ for ( i = 0; i < flen; i++ ) {
+
+ face = faces[ i ];
+
+ var a = face[ 0 ] + shapesOffset;
+ var b = face[ 1 ] + shapesOffset;
+ var c = face[ 2 ] + shapesOffset;
+
+ this.faces.push( new THREE.Face3( a, b, c, null, null, material ) );
+ this.faceVertexUvs[ 0 ].push( uvgen.generateBottomUV( this, shape, options, a, b, c ) );
+
+ }
+
+};
+
+/**
+ * @author astrodud / http://astrodud.isgreat.org/
+ * @author zz85 / https://github.com/zz85
+ * @author bhouston / http://exocortex.com
+ */
+
+// points - to create a closed torus, one must use a set of points
+// like so: [ a, b, c, d, a ], see first is the same as last.
+// segments - the number of circumference segments to create
+// phiStart - the starting radian
+// phiLength - the radian (0 to 2*PI) range of the lathed section
+// 2*pi is a closed lathe, less than 2PI is a portion.
+THREE.LatheGeometry = function ( points, segments, phiStart, phiLength ) {
+
+ THREE.Geometry.call( this );
+
+ segments = segments || 12;
+ phiStart = phiStart || 0;
+ phiLength = phiLength || 2 * Math.PI;
+
+ var inversePointLength = 1.0 / ( points.length - 1 );
+ var inverseSegments = 1.0 / segments;
+
+ for ( var i = 0, il = segments; i <= il; i ++ ) {
+
+ var phi = phiStart + i * inverseSegments * phiLength;
+
+ var c = Math.cos( phi ),
+ s = Math.sin( phi );
+
+ for ( var j = 0, jl = points.length; j < jl; j ++ ) {
+
+ var pt = points[ j ];
+
+ var vertex = new THREE.Vector3();
+
+ vertex.x = c * pt.x - s * pt.y;
+ vertex.y = s * pt.x + c * pt.y;
+ vertex.z = pt.z;
+
+ this.vertices.push( vertex );
+
+ }
+
+ }
+
+ var np = points.length;
+
+ for ( var i = 0, il = segments; i < il; i ++ ) {
+
+ for ( var j = 0, jl = points.length - 1; j < jl; j ++ ) {
+
+ var base = j + np * i;
+ var a = base;
+ var b = base + np;
+ var c = base + 1 + np;
+ var d = base + 1;
+
+ var u0 = i * inverseSegments;
+ var v0 = j * inversePointLength;
+ var u1 = u0 + inverseSegments;
+ var v1 = v0 + inversePointLength;
+
+ this.faces.push( new THREE.Face3( a, b, d ) );
+
+ this.faceVertexUvs[ 0 ].push( [
+
+ new THREE.Vector2( u0, v0 ),
+ new THREE.Vector2( u1, v0 ),
+ new THREE.Vector2( u0, v1 )
+
+ ] );
+
+ this.faces.push( new THREE.Face3( b, c, d ) );
+
+ this.faceVertexUvs[ 0 ].push( [
+
+ new THREE.Vector2( u1, v0 ),
+ new THREE.Vector2( u1, v1 ),
+ new THREE.Vector2( u0, v1 )
+
+ ] );
+
+
+ }
+
+ }
+
+ this.mergeVertices();
+ this.computeCentroids();
+ this.computeFaceNormals();
+ this.computeVertexNormals();
+
+};
+
+THREE.LatheGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * based on http://papervision3d.googlecode.com/svn/trunk/as3/trunk/src/org/papervision3d/objects/primitives/Plane.as
+ */
+
+THREE.PlaneGeometry = function ( width, height, widthSegments, heightSegments ) {
+
+ THREE.Geometry.call( this );
+
+ this.width = width;
+ this.height = height;
+
+ this.widthSegments = widthSegments || 1;
+ this.heightSegments = heightSegments || 1;
+
+ var ix, iz;
+ var width_half = width / 2;
+ var height_half = height / 2;
+
+ var gridX = this.widthSegments;
+ var gridZ = this.heightSegments;
+
+ var gridX1 = gridX + 1;
+ var gridZ1 = gridZ + 1;
+
+ var segment_width = this.width / gridX;
+ var segment_height = this.height / gridZ;
+
+ var normal = new THREE.Vector3( 0, 0, 1 );
+
+ for ( iz = 0; iz < gridZ1; iz ++ ) {
+
+ for ( ix = 0; ix < gridX1; ix ++ ) {
+
+ var x = ix * segment_width - width_half;
+ var y = iz * segment_height - height_half;
+
+ this.vertices.push( new THREE.Vector3( x, - y, 0 ) );
+
+ }
+
+ }
+
+ for ( iz = 0; iz < gridZ; iz ++ ) {
+
+ for ( ix = 0; ix < gridX; ix ++ ) {
+
+ var a = ix + gridX1 * iz;
+ var b = ix + gridX1 * ( iz + 1 );
+ var c = ( ix + 1 ) + gridX1 * ( iz + 1 );
+ var d = ( ix + 1 ) + gridX1 * iz;
+
+ var uva = new THREE.Vector2( ix / gridX, 1 - iz / gridZ );
+ var uvb = new THREE.Vector2( ix / gridX, 1 - ( iz + 1 ) / gridZ );
+ var uvc = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - ( iz + 1 ) / gridZ );
+ var uvd = new THREE.Vector2( ( ix + 1 ) / gridX, 1 - iz / gridZ );
+
+ var face = new THREE.Face3( a, b, d );
+ face.normal.copy( normal );
+ face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
+
+ this.faces.push( face );
+ this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] );
+
+ face = new THREE.Face3( b, c, d );
+ face.normal.copy( normal );
+ face.vertexNormals.push( normal.clone(), normal.clone(), normal.clone() );
+
+ this.faces.push( face );
+ this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] );
+
+ }
+
+ }
+
+ this.computeCentroids();
+
+};
+
+THREE.PlaneGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author Kaleb Murphy
+ */
+
+THREE.RingGeometry = function ( innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength ) {
+
+ THREE.Geometry.call( this );
+
+ innerRadius = innerRadius || 0;
+ outerRadius = outerRadius || 50;
+
+ thetaStart = thetaStart !== undefined ? thetaStart : 0;
+ thetaLength = thetaLength !== undefined ? thetaLength : Math.PI * 2;
+
+ thetaSegments = thetaSegments !== undefined ? Math.max( 3, thetaSegments ) : 8;
+ phiSegments = phiSegments !== undefined ? Math.max( 3, phiSegments ) : 8;
+
+ var i, o, uvs = [], radius = innerRadius, radiusStep = ( ( outerRadius - innerRadius ) / phiSegments );
+
+ for ( i = 0; i <= phiSegments; i ++ ) { // concentric circles inside ring
+
+ for ( o = 0; o <= thetaSegments; o ++ ) { // number of segments per circle
+
+ var vertex = new THREE.Vector3();
+ var segment = thetaStart + o / thetaSegments * thetaLength;
+
+ vertex.x = radius * Math.cos( segment );
+ vertex.y = radius * Math.sin( segment );
+
+ this.vertices.push( vertex );
+ uvs.push( new THREE.Vector2( ( vertex.x / radius + 1 ) / 2, - ( vertex.y / radius + 1 ) / 2 + 1 ) );
+ }
+
+ radius += radiusStep;
+
+ }
+
+ var n = new THREE.Vector3( 0, 0, 1 );
+
+ for ( i = 0; i < phiSegments; i ++ ) { // concentric circles inside ring
+
+ var thetaSegment = i * thetaSegments;
+
+ for ( o = 0; o <= thetaSegments; o ++ ) { // number of segments per circle
+
+ var segment = o + thetaSegment;
+
+ var v1 = segment + i;
+ var v2 = segment + thetaSegments + i;
+ var v3 = segment + thetaSegments + 1 + i;
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n, n, n ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ], uvs[ v2 ], uvs[ v3 ] ]);
+
+ v1 = segment + i;
+ v2 = segment + thetaSegments + 1 + i;
+ v3 = segment + 1 + i;
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n, n, n ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ], uvs[ v2 ], uvs[ v3 ] ]);
+
+ }
+ }
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+
+ this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.RingGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.SphereGeometry = function ( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength ) {
+
+ THREE.Geometry.call( this );
+
+ this.radius = radius = radius || 50;
+
+ this.widthSegments = widthSegments = Math.max( 3, Math.floor( widthSegments ) || 8 );
+ this.heightSegments = heightSegments = Math.max( 2, Math.floor( heightSegments ) || 6 );
+
+ this.phiStart = phiStart = phiStart !== undefined ? phiStart : 0;
+ this.phiLength = phiLength = phiLength !== undefined ? phiLength : Math.PI * 2;
+
+ this.thetaStart = thetaStart = thetaStart !== undefined ? thetaStart : 0;
+ this.thetaLength = thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;
+
+ var x, y, vertices = [], uvs = [];
+
+ for ( y = 0; y <= heightSegments; y ++ ) {
+
+ var verticesRow = [];
+ var uvsRow = [];
+
+ for ( x = 0; x <= widthSegments; x ++ ) {
+
+ var u = x / widthSegments;
+ var v = y / heightSegments;
+
+ var vertex = new THREE.Vector3();
+ vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+ vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
+ vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+
+ this.vertices.push( vertex );
+
+ verticesRow.push( this.vertices.length - 1 );
+ uvsRow.push( new THREE.Vector2( u, 1 - v ) );
+
+ }
+
+ vertices.push( verticesRow );
+ uvs.push( uvsRow );
+
+ }
+
+ for ( y = 0; y < this.heightSegments; y ++ ) {
+
+ for ( x = 0; x < this.widthSegments; x ++ ) {
+
+ var v1 = vertices[ y ][ x + 1 ];
+ var v2 = vertices[ y ][ x ];
+ var v3 = vertices[ y + 1 ][ x ];
+ var v4 = vertices[ y + 1 ][ x + 1 ];
+
+ var n1 = this.vertices[ v1 ].clone().normalize();
+ var n2 = this.vertices[ v2 ].clone().normalize();
+ var n3 = this.vertices[ v3 ].clone().normalize();
+ var n4 = this.vertices[ v4 ].clone().normalize();
+
+ var uv1 = uvs[ y ][ x + 1 ].clone();
+ var uv2 = uvs[ y ][ x ].clone();
+ var uv3 = uvs[ y + 1 ][ x ].clone();
+ var uv4 = uvs[ y + 1 ][ x + 1 ].clone();
+
+ if ( Math.abs( this.vertices[ v1 ].y ) === this.radius ) {
+
+ this.faces.push( new THREE.Face3( v1, v3, v4, [ n1, n3, n4 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv3, uv4 ] );
+
+ } else if ( Math.abs( this.vertices[ v3 ].y ) === this.radius ) {
+
+ this.faces.push( new THREE.Face3( v1, v2, v3, [ n1, n2, n3 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv3 ] );
+
+ } else {
+
+ this.faces.push( new THREE.Face3( v1, v2, v4, [ n1, n2, n4 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv1, uv2, uv4 ] );
+
+ this.faces.push( new THREE.Face3( v2, v3, v4, [ n2, n3, n4 ] ) );
+ this.faceVertexUvs[ 0 ].push( [ uv2.clone(), uv3, uv4.clone() ] );
+
+ }
+
+ }
+
+ }
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+
+ this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.SphereGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * For creating 3D text geometry in three.js
+ *
+ * Text = 3D Text
+ *
+ * parameters = {
+ * size: , // size of the text
+ * height: , // thickness to extrude text
+ * curveSegments: , // number of points on the curves
+ *
+ * font: , // font name
+ * weight: , // font weight (normal, bold)
+ * style: , // font style (normal, italics)
+ *
+ * bevelEnabled: , // turn on bevel
+ * bevelThickness: , // how deep into text bevel goes
+ * bevelSize: , // how far from text outline is bevel
+ * }
+ *
+ */
+
+/* Usage Examples
+
+ // TextGeometry wrapper
+
+ var text3d = new TextGeometry( text, options );
+
+ // Complete manner
+
+ var textShapes = THREE.FontUtils.generateShapes( text, options );
+ var text3d = new ExtrudeGeometry( textShapes, options );
+
+*/
+
+
+THREE.TextGeometry = function ( text, parameters ) {
+
+ parameters = parameters || {};
+
+ var textShapes = THREE.FontUtils.generateShapes( text, parameters );
+
+ // translate parameters to ExtrudeGeometry API
+
+ parameters.amount = parameters.height !== undefined ? parameters.height : 50;
+
+ // defaults
+
+ if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10;
+ if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8;
+ if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false;
+
+ THREE.ExtrudeGeometry.call( this, textShapes, parameters );
+
+};
+
+THREE.TextGeometry.prototype = Object.create( THREE.ExtrudeGeometry.prototype );
+
+/**
+ * @author oosmoxiecode
+ * @author mrdoob / http://mrdoob.com/
+ * based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3DLite/src/away3dlite/primitives/Torus.as?r=2888
+ */
+
+THREE.TorusGeometry = function ( radius, tube, radialSegments, tubularSegments, arc ) {
+
+ THREE.Geometry.call( this );
+
+ var scope = this;
+
+ this.radius = radius || 100;
+ this.tube = tube || 40;
+ this.radialSegments = radialSegments || 8;
+ this.tubularSegments = tubularSegments || 6;
+ this.arc = arc || Math.PI * 2;
+
+ var center = new THREE.Vector3(), uvs = [], normals = [];
+
+ for ( var j = 0; j <= this.radialSegments; j ++ ) {
+
+ for ( var i = 0; i <= this.tubularSegments; i ++ ) {
+
+ var u = i / this.tubularSegments * this.arc;
+ var v = j / this.radialSegments * Math.PI * 2;
+
+ center.x = this.radius * Math.cos( u );
+ center.y = this.radius * Math.sin( u );
+
+ var vertex = new THREE.Vector3();
+ vertex.x = ( this.radius + this.tube * Math.cos( v ) ) * Math.cos( u );
+ vertex.y = ( this.radius + this.tube * Math.cos( v ) ) * Math.sin( u );
+ vertex.z = this.tube * Math.sin( v );
+
+ this.vertices.push( vertex );
+
+ uvs.push( new THREE.Vector2( i / this.tubularSegments, j / this.radialSegments ) );
+ normals.push( vertex.clone().sub( center ).normalize() );
+
+ }
+ }
+
+
+ for ( var j = 1; j <= this.radialSegments; j ++ ) {
+
+ for ( var i = 1; i <= this.tubularSegments; i ++ ) {
+
+ var a = ( this.tubularSegments + 1 ) * j + i - 1;
+ var b = ( this.tubularSegments + 1 ) * ( j - 1 ) + i - 1;
+ var c = ( this.tubularSegments + 1 ) * ( j - 1 ) + i;
+ var d = ( this.tubularSegments + 1 ) * j + i;
+
+ var face = new THREE.Face3( a, b, d, [ normals[ a ], normals[ b ], normals[ d ] ] );
+ face.normal.add( normals[ a ] );
+ face.normal.add( normals[ b ] );
+ face.normal.add( normals[ d ] );
+ face.normal.normalize();
+
+ this.faces.push( face );
+
+ this.faceVertexUvs[ 0 ].push( [ uvs[ a ].clone(), uvs[ b ].clone(), uvs[ d ].clone() ] );
+
+ face = new THREE.Face3( b, c, d, [ normals[ b ], normals[ c ], normals[ d ] ] );
+ face.normal.add( normals[ b ] );
+ face.normal.add( normals[ c ] );
+ face.normal.add( normals[ d ] );
+ face.normal.normalize();
+
+ this.faces.push( face );
+
+ this.faceVertexUvs[ 0 ].push( [ uvs[ b ].clone(), uvs[ c ].clone(), uvs[ d ].clone() ] );
+ }
+
+ }
+
+ this.computeCentroids();
+
+};
+
+THREE.TorusGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author oosmoxiecode
+ * based on http://code.google.com/p/away3d/source/browse/trunk/fp10/Away3D/src/away3d/primitives/TorusKnot.as?spec=svn2473&r=2473
+ */
+
+THREE.TorusKnotGeometry = function ( radius, tube, radialSegments, tubularSegments, p, q, heightScale ) {
+
+ THREE.Geometry.call( this );
+
+ var scope = this;
+
+ this.radius = radius || 100;
+ this.tube = tube || 40;
+ this.radialSegments = radialSegments || 64;
+ this.tubularSegments = tubularSegments || 8;
+ this.p = p || 2;
+ this.q = q || 3;
+ this.heightScale = heightScale || 1;
+ this.grid = new Array( this.radialSegments );
+
+ var tang = new THREE.Vector3();
+ var n = new THREE.Vector3();
+ var bitan = new THREE.Vector3();
+
+ for ( var i = 0; i < this.radialSegments; ++ i ) {
+
+ this.grid[ i ] = new Array( this.tubularSegments );
+ var u = i / this.radialSegments * 2 * this.p * Math.PI;
+ var p1 = getPos( u, this.q, this.p, this.radius, this.heightScale );
+ var p2 = getPos( u + 0.01, this.q, this.p, this.radius, this.heightScale );
+ tang.subVectors( p2, p1 );
+ n.addVectors( p2, p1 );
+
+ bitan.crossVectors( tang, n );
+ n.crossVectors( bitan, tang );
+ bitan.normalize();
+ n.normalize();
+
+ for ( var j = 0; j < this.tubularSegments; ++ j ) {
+
+ var v = j / this.tubularSegments * 2 * Math.PI;
+ var cx = - this.tube * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
+ var cy = this.tube * Math.sin( v );
+
+ var pos = new THREE.Vector3();
+ pos.x = p1.x + cx * n.x + cy * bitan.x;
+ pos.y = p1.y + cx * n.y + cy * bitan.y;
+ pos.z = p1.z + cx * n.z + cy * bitan.z;
+
+ this.grid[ i ][ j ] = scope.vertices.push( pos ) - 1;
+
+ }
+
+ }
+
+ for ( var i = 0; i < this.radialSegments; ++ i ) {
+
+ for ( var j = 0; j < this.tubularSegments; ++ j ) {
+
+ var ip = ( i + 1 ) % this.radialSegments;
+ var jp = ( j + 1 ) % this.tubularSegments;
+
+ var a = this.grid[ i ][ j ];
+ var b = this.grid[ ip ][ j ];
+ var c = this.grid[ ip ][ jp ];
+ var d = this.grid[ i ][ jp ];
+
+ var uva = new THREE.Vector2( i / this.radialSegments, j / this.tubularSegments );
+ var uvb = new THREE.Vector2( ( i + 1 ) / this.radialSegments, j / this.tubularSegments );
+ var uvc = new THREE.Vector2( ( i + 1 ) / this.radialSegments, ( j + 1 ) / this.tubularSegments );
+ var uvd = new THREE.Vector2( i / this.radialSegments, ( j + 1 ) / this.tubularSegments );
+
+ this.faces.push( new THREE.Face3( a, b, d ) );
+ this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] );
+
+ this.faces.push( new THREE.Face3( b, c, d ) );
+ this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] );
+
+ }
+ }
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+ this.computeVertexNormals();
+
+ function getPos( u, in_q, in_p, radius, heightScale ) {
+
+ var cu = Math.cos( u );
+ var su = Math.sin( u );
+ var quOverP = in_q / in_p * u;
+ var cs = Math.cos( quOverP );
+
+ var tx = radius * ( 2 + cs ) * 0.5 * cu;
+ var ty = radius * ( 2 + cs ) * su * 0.5;
+ var tz = heightScale * radius * Math.sin( quOverP ) * 0.5;
+
+ return new THREE.Vector3( tx, ty, tz );
+
+ }
+
+};
+
+THREE.TorusKnotGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author WestLangley / https://github.com/WestLangley
+ * @author zz85 / https://github.com/zz85
+ * @author miningold / https://github.com/miningold
+ *
+ * Modified from the TorusKnotGeometry by @oosmoxiecode
+ *
+ * Creates a tube which extrudes along a 3d spline
+ *
+ * Uses parallel transport frames as described in
+ * http://www.cs.indiana.edu/pub/techreports/TR425.pdf
+ */
+
+THREE.TubeGeometry = function( path, segments, radius, radialSegments, closed ) {
+
+ THREE.Geometry.call( this );
+
+ this.path = path;
+ this.segments = segments || 64;
+ this.radius = radius || 1;
+ this.radialSegments = radialSegments || 8;
+ this.closed = closed || false;
+
+ this.grid = [];
+
+ var scope = this,
+
+ tangent,
+ normal,
+ binormal,
+
+ numpoints = this.segments + 1,
+
+ x, y, z,
+ tx, ty, tz,
+ u, v,
+
+ cx, cy,
+ pos, pos2 = new THREE.Vector3(),
+ i, j,
+ ip, jp,
+ a, b, c, d,
+ uva, uvb, uvc, uvd;
+
+ var frames = new THREE.TubeGeometry.FrenetFrames( this.path, this.segments, this.closed ),
+ tangents = frames.tangents,
+ normals = frames.normals,
+ binormals = frames.binormals;
+
+ // proxy internals
+ this.tangents = tangents;
+ this.normals = normals;
+ this.binormals = binormals;
+
+ function vert( x, y, z ) {
+
+ return scope.vertices.push( new THREE.Vector3( x, y, z ) ) - 1;
+
+ }
+
+
+ // consruct the grid
+
+ for ( i = 0; i < numpoints; i++ ) {
+
+ this.grid[ i ] = [];
+
+ u = i / ( numpoints - 1 );
+
+ pos = path.getPointAt( u );
+
+ tangent = tangents[ i ];
+ normal = normals[ i ];
+ binormal = binormals[ i ];
+
+ for ( j = 0; j < this.radialSegments; j++ ) {
+
+ v = j / this.radialSegments * 2 * Math.PI;
+
+ cx = -this.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
+ cy = this.radius * Math.sin( v );
+
+ pos2.copy( pos );
+ pos2.x += cx * normal.x + cy * binormal.x;
+ pos2.y += cx * normal.y + cy * binormal.y;
+ pos2.z += cx * normal.z + cy * binormal.z;
+
+ this.grid[ i ][ j ] = vert( pos2.x, pos2.y, pos2.z );
+
+ }
+ }
+
+
+ // construct the mesh
+
+ for ( i = 0; i < this.segments; i++ ) {
+
+ for ( j = 0; j < this.radialSegments; j++ ) {
+
+ ip = ( this.closed ) ? (i + 1) % this.segments : i + 1;
+ jp = (j + 1) % this.radialSegments;
+
+ a = this.grid[ i ][ j ]; // *** NOT NECESSARILY PLANAR ! ***
+ b = this.grid[ ip ][ j ];
+ c = this.grid[ ip ][ jp ];
+ d = this.grid[ i ][ jp ];
+
+ uva = new THREE.Vector2( i / this.segments, j / this.radialSegments );
+ uvb = new THREE.Vector2( ( i + 1 ) / this.segments, j / this.radialSegments );
+ uvc = new THREE.Vector2( ( i + 1 ) / this.segments, ( j + 1 ) / this.radialSegments );
+ uvd = new THREE.Vector2( i / this.segments, ( j + 1 ) / this.radialSegments );
+
+ this.faces.push( new THREE.Face3( a, b, d ) );
+ this.faceVertexUvs[ 0 ].push( [ uva, uvb, uvd ] );
+
+ this.faces.push( new THREE.Face3( b, c, d ) );
+ this.faceVertexUvs[ 0 ].push( [ uvb.clone(), uvc, uvd.clone() ] );
+
+ }
+ }
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+ this.computeVertexNormals();
+
+};
+
+THREE.TubeGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+
+// For computing of Frenet frames, exposing the tangents, normals and binormals the spline
+THREE.TubeGeometry.FrenetFrames = function(path, segments, closed) {
+
+ var tangent = new THREE.Vector3(),
+ normal = new THREE.Vector3(),
+ binormal = new THREE.Vector3(),
+
+ tangents = [],
+ normals = [],
+ binormals = [],
+
+ vec = new THREE.Vector3(),
+ mat = new THREE.Matrix4(),
+
+ numpoints = segments + 1,
+ theta,
+ epsilon = 0.0001,
+ smallest,
+
+ tx, ty, tz,
+ i, u, v;
+
+
+ // expose internals
+ this.tangents = tangents;
+ this.normals = normals;
+ this.binormals = binormals;
+
+ // compute the tangent vectors for each segment on the path
+
+ for ( i = 0; i < numpoints; i++ ) {
+
+ u = i / ( numpoints - 1 );
+
+ tangents[ i ] = path.getTangentAt( u );
+ tangents[ i ].normalize();
+
+ }
+
+ initialNormal3();
+
+ function initialNormal1(lastBinormal) {
+ // fixed start binormal. Has dangers of 0 vectors
+ normals[ 0 ] = new THREE.Vector3();
+ binormals[ 0 ] = new THREE.Vector3();
+ if (lastBinormal===undefined) lastBinormal = new THREE.Vector3( 0, 0, 1 );
+ normals[ 0 ].crossVectors( lastBinormal, tangents[ 0 ] ).normalize();
+ binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ).normalize();
+ }
+
+ function initialNormal2() {
+
+ // This uses the Frenet-Serret formula for deriving binormal
+ var t2 = path.getTangentAt( epsilon );
+
+ normals[ 0 ] = new THREE.Vector3().subVectors( t2, tangents[ 0 ] ).normalize();
+ binormals[ 0 ] = new THREE.Vector3().crossVectors( tangents[ 0 ], normals[ 0 ] );
+
+ normals[ 0 ].crossVectors( binormals[ 0 ], tangents[ 0 ] ).normalize(); // last binormal x tangent
+ binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] ).normalize();
+
+ }
+
+ function initialNormal3() {
+ // select an initial normal vector perpenicular to the first tangent vector,
+ // and in the direction of the smallest tangent xyz component
+
+ normals[ 0 ] = new THREE.Vector3();
+ binormals[ 0 ] = new THREE.Vector3();
+ smallest = Number.MAX_VALUE;
+ tx = Math.abs( tangents[ 0 ].x );
+ ty = Math.abs( tangents[ 0 ].y );
+ tz = Math.abs( tangents[ 0 ].z );
+
+ if ( tx <= smallest ) {
+ smallest = tx;
+ normal.set( 1, 0, 0 );
+ }
+
+ if ( ty <= smallest ) {
+ smallest = ty;
+ normal.set( 0, 1, 0 );
+ }
+
+ if ( tz <= smallest ) {
+ normal.set( 0, 0, 1 );
+ }
+
+ vec.crossVectors( tangents[ 0 ], normal ).normalize();
+
+ normals[ 0 ].crossVectors( tangents[ 0 ], vec );
+ binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
+ }
+
+
+ // compute the slowly-varying normal and binormal vectors for each segment on the path
+
+ for ( i = 1; i < numpoints; i++ ) {
+
+ normals[ i ] = normals[ i-1 ].clone();
+
+ binormals[ i ] = binormals[ i-1 ].clone();
+
+ vec.crossVectors( tangents[ i-1 ], tangents[ i ] );
+
+ if ( vec.length() > epsilon ) {
+
+ vec.normalize();
+
+ theta = Math.acos( THREE.Math.clamp( tangents[ i-1 ].dot( tangents[ i ] ), -1, 1 ) ); // clamp for floating pt errors
+
+ normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
+
+ }
+
+ binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+
+ }
+
+
+ // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
+
+ if ( closed ) {
+
+ theta = Math.acos( THREE.Math.clamp( normals[ 0 ].dot( normals[ numpoints-1 ] ), -1, 1 ) );
+ theta /= ( numpoints - 1 );
+
+ if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ numpoints-1 ] ) ) > 0 ) {
+
+ theta = -theta;
+
+ }
+
+ for ( i = 1; i < numpoints; i++ ) {
+
+ // twist a little...
+ normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
+ binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+
+ }
+
+ }
+};
+
+/**
+ * @author clockworkgeek / https://github.com/clockworkgeek
+ * @author timothypratley / https://github.com/timothypratley
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.PolyhedronGeometry = function ( vertices, faces, radius, detail ) {
+
+ THREE.Geometry.call( this );
+
+ radius = radius || 1;
+ detail = detail || 0;
+
+ var that = this;
+
+ for ( var i = 0, l = vertices.length; i < l; i ++ ) {
+
+ prepare( new THREE.Vector3( vertices[ i ][ 0 ], vertices[ i ][ 1 ], vertices[ i ][ 2 ] ) );
+
+ }
+
+ var midpoints = [], p = this.vertices;
+
+ var f = [];
+ for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+ var v1 = p[ faces[ i ][ 0 ] ];
+ var v2 = p[ faces[ i ][ 1 ] ];
+ var v3 = p[ faces[ i ][ 2 ] ];
+
+ f[ i ] = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] );
+
+ }
+
+ for ( var i = 0, l = f.length; i < l; i ++ ) {
+
+ subdivide(f[ i ], detail);
+
+ }
+
+
+ // Handle case when face straddles the seam
+
+ for ( var i = 0, l = this.faceVertexUvs[ 0 ].length; i < l; i ++ ) {
+
+ var uvs = this.faceVertexUvs[ 0 ][ i ];
+
+ var x0 = uvs[ 0 ].x;
+ var x1 = uvs[ 1 ].x;
+ var x2 = uvs[ 2 ].x;
+
+ var max = Math.max( x0, Math.max( x1, x2 ) );
+ var min = Math.min( x0, Math.min( x1, x2 ) );
+
+ if ( max > 0.9 && min < 0.1 ) { // 0.9 is somewhat arbitrary
+
+ if ( x0 < 0.2 ) uvs[ 0 ].x += 1;
+ if ( x1 < 0.2 ) uvs[ 1 ].x += 1;
+ if ( x2 < 0.2 ) uvs[ 2 ].x += 1;
+
+ }
+
+ }
+
+
+ // Apply radius
+
+ for ( var i = 0, l = this.vertices.length; i < l; i ++ ) {
+
+ this.vertices[ i ].multiplyScalar( radius );
+
+ }
+
+
+ // Merge vertices
+
+ this.mergeVertices();
+
+ this.computeCentroids();
+
+ this.computeFaceNormals();
+
+ this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+
+ // Project vector onto sphere's surface
+
+ function prepare( vector ) {
+
+ var vertex = vector.normalize().clone();
+ vertex.index = that.vertices.push( vertex ) - 1;
+
+ // Texture coords are equivalent to map coords, calculate angle and convert to fraction of a circle.
+
+ var u = azimuth( vector ) / 2 / Math.PI + 0.5;
+ var v = inclination( vector ) / Math.PI + 0.5;
+ vertex.uv = new THREE.Vector2( u, 1 - v );
+
+ return vertex;
+
+ }
+
+
+ // Approximate a curved face with recursively sub-divided triangles.
+
+ function make( v1, v2, v3 ) {
+
+ var face = new THREE.Face3( v1.index, v2.index, v3.index, [ v1.clone(), v2.clone(), v3.clone() ] );
+ face.centroid.add( v1 ).add( v2 ).add( v3 ).divideScalar( 3 );
+ that.faces.push( face );
+
+ var azi = azimuth( face.centroid );
+
+ that.faceVertexUvs[ 0 ].push( [
+ correctUV( v1.uv, v1, azi ),
+ correctUV( v2.uv, v2, azi ),
+ correctUV( v3.uv, v3, azi )
+ ] );
+
+ }
+
+
+ // Analytically subdivide a face to the required detail level.
+
+ function subdivide(face, detail ) {
+
+ var cols = Math.pow(2, detail);
+ var cells = Math.pow(4, detail);
+ var a = prepare( that.vertices[ face.a ] );
+ var b = prepare( that.vertices[ face.b ] );
+ var c = prepare( that.vertices[ face.c ] );
+ var v = [];
+
+ // Construct all of the vertices for this subdivision.
+
+ for ( var i = 0 ; i <= cols; i ++ ) {
+
+ v[ i ] = [];
+
+ var aj = prepare( a.clone().lerp( c, i / cols ) );
+ var bj = prepare( b.clone().lerp( c, i / cols ) );
+ var rows = cols - i;
+
+ for ( var j = 0; j <= rows; j ++) {
+
+ if ( j == 0 && i == cols ) {
+
+ v[ i ][ j ] = aj;
+
+ } else {
+
+ v[ i ][ j ] = prepare( aj.clone().lerp( bj, j / rows ) );
+
+ }
+
+ }
+
+ }
+
+ // Construct all of the faces.
+
+ for ( var i = 0; i < cols ; i ++ ) {
+
+ for ( var j = 0; j < 2 * (cols - i) - 1; j ++ ) {
+
+ var k = Math.floor( j / 2 );
+
+ if ( j % 2 == 0 ) {
+
+ make(
+ v[ i ][ k + 1],
+ v[ i + 1 ][ k ],
+ v[ i ][ k ]
+ );
+
+ } else {
+
+ make(
+ v[ i ][ k + 1 ],
+ v[ i + 1][ k + 1],
+ v[ i + 1 ][ k ]
+ );
+
+ }
+
+ }
+
+ }
+
+ }
+
+
+ // Angle around the Y axis, counter-clockwise when looking from above.
+
+ function azimuth( vector ) {
+
+ return Math.atan2( vector.z, -vector.x );
+
+ }
+
+
+ // Angle above the XZ plane.
+
+ function inclination( vector ) {
+
+ return Math.atan2( -vector.y, Math.sqrt( ( vector.x * vector.x ) + ( vector.z * vector.z ) ) );
+
+ }
+
+
+ // Texture fixing helper. Spheres have some odd behaviours.
+
+ function correctUV( uv, vector, azimuth ) {
+
+ if ( ( azimuth < 0 ) && ( uv.x === 1 ) ) uv = new THREE.Vector2( uv.x - 1, uv.y );
+ if ( ( vector.x === 0 ) && ( vector.z === 0 ) ) uv = new THREE.Vector2( azimuth / 2 / Math.PI + 0.5, uv.y );
+ return uv.clone();
+
+ }
+
+
+};
+
+THREE.PolyhedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author timothypratley / https://github.com/timothypratley
+ */
+
+THREE.IcosahedronGeometry = function ( radius, detail ) {
+
+ this.radius = radius;
+ this.detail = detail;
+
+ var t = ( 1 + Math.sqrt( 5 ) ) / 2;
+
+ var vertices = [
+ [ -1, t, 0 ], [ 1, t, 0 ], [ -1, -t, 0 ], [ 1, -t, 0 ],
+ [ 0, -1, t ], [ 0, 1, t ], [ 0, -1, -t ], [ 0, 1, -t ],
+ [ t, 0, -1 ], [ t, 0, 1 ], [ -t, 0, -1 ], [ -t, 0, 1 ]
+ ];
+
+ var faces = [
+ [ 0, 11, 5 ], [ 0, 5, 1 ], [ 0, 1, 7 ], [ 0, 7, 10 ], [ 0, 10, 11 ],
+ [ 1, 5, 9 ], [ 5, 11, 4 ], [ 11, 10, 2 ], [ 10, 7, 6 ], [ 7, 1, 8 ],
+ [ 3, 9, 4 ], [ 3, 4, 2 ], [ 3, 2, 6 ], [ 3, 6, 8 ], [ 3, 8, 9 ],
+ [ 4, 9, 5 ], [ 2, 4, 11 ], [ 6, 2, 10 ], [ 8, 6, 7 ], [ 9, 8, 1 ]
+ ];
+
+ THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail );
+
+};
+
+THREE.IcosahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author timothypratley / https://github.com/timothypratley
+ */
+
+THREE.OctahedronGeometry = function ( radius, detail ) {
+
+ var vertices = [
+ [ 1, 0, 0 ], [ -1, 0, 0 ], [ 0, 1, 0 ], [ 0, -1, 0 ], [ 0, 0, 1 ], [ 0, 0, -1 ]
+ ];
+
+ var faces = [
+ [ 0, 2, 4 ], [ 0, 4, 3 ], [ 0, 3, 5 ], [ 0, 5, 2 ], [ 1, 2, 5 ], [ 1, 5, 3 ], [ 1, 3, 4 ], [ 1, 4, 2 ]
+ ];
+
+ THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail );
+};
+
+THREE.OctahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author timothypratley / https://github.com/timothypratley
+ */
+
+THREE.TetrahedronGeometry = function ( radius, detail ) {
+
+ var vertices = [
+ [ 1, 1, 1 ], [ -1, -1, 1 ], [ -1, 1, -1 ], [ 1, -1, -1 ]
+ ];
+
+ var faces = [
+ [ 2, 1, 0 ], [ 0, 3, 2 ], [ 1, 3, 0 ], [ 2, 3, 1 ]
+ ];
+
+ THREE.PolyhedronGeometry.call( this, vertices, faces, radius, detail );
+
+};
+
+THREE.TetrahedronGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author zz85 / https://github.com/zz85
+ * Parametric Surfaces Geometry
+ * based on the brilliant article by @prideout http://prideout.net/blog/?p=44
+ *
+ * new THREE.ParametricGeometry( parametricFunction, uSegments, ySegements );
+ *
+ */
+
+THREE.ParametricGeometry = function ( func, slices, stacks ) {
+
+ THREE.Geometry.call( this );
+
+ var verts = this.vertices;
+ var faces = this.faces;
+ var uvs = this.faceVertexUvs[ 0 ];
+
+ var i, il, j, p;
+ var u, v;
+
+ var stackCount = stacks + 1;
+ var sliceCount = slices + 1;
+
+ for ( i = 0; i <= stacks; i ++ ) {
+
+ v = i / stacks;
+
+ for ( j = 0; j <= slices; j ++ ) {
+
+ u = j / slices;
+
+ p = func( u, v );
+ verts.push( p );
+
+ }
+ }
+
+ var a, b, c, d;
+ var uva, uvb, uvc, uvd;
+
+ for ( i = 0; i < stacks; i ++ ) {
+
+ for ( j = 0; j < slices; j ++ ) {
+
+ a = i * sliceCount + j;
+ b = i * sliceCount + j + 1;
+ c = (i + 1) * sliceCount + j + 1;
+ d = (i + 1) * sliceCount + j;
+
+ uva = new THREE.Vector2( j / slices, i / stacks );
+ uvb = new THREE.Vector2( ( j + 1 ) / slices, i / stacks );
+ uvc = new THREE.Vector2( ( j + 1 ) / slices, ( i + 1 ) / stacks );
+ uvd = new THREE.Vector2( j / slices, ( i + 1 ) / stacks );
+
+ faces.push( new THREE.Face3( a, b, d ) );
+ uvs.push( [ uva, uvb, uvd ] );
+
+ faces.push( new THREE.Face3( b, c, d ) );
+ uvs.push( [ uvb.clone(), uvc, uvd.clone() ] );
+
+ }
+
+ }
+
+ // console.log(this);
+
+ // magic bullet
+ // var diff = this.mergeVertices();
+ // console.log('removed ', diff, ' vertices by merging');
+
+ this.computeCentroids();
+ this.computeFaceNormals();
+ this.computeVertexNormals();
+
+};
+
+THREE.ParametricGeometry.prototype = Object.create( THREE.Geometry.prototype );
+
+/**
+ * @author sroucheray / http://sroucheray.org/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.AxisHelper = function ( size ) {
+
+ size = size || 1;
+
+ var geometry = new THREE.Geometry();
+
+ geometry.vertices.push(
+ new THREE.Vector3(), new THREE.Vector3( size, 0, 0 ),
+ new THREE.Vector3(), new THREE.Vector3( 0, size, 0 ),
+ new THREE.Vector3(), new THREE.Vector3( 0, 0, size )
+ );
+
+ geometry.colors.push(
+ new THREE.Color( 0xff0000 ), new THREE.Color( 0xffaa00 ),
+ new THREE.Color( 0x00ff00 ), new THREE.Color( 0xaaff00 ),
+ new THREE.Color( 0x0000ff ), new THREE.Color( 0x00aaff )
+ );
+
+ var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );
+
+ THREE.Line.call( this, geometry, material, THREE.LinePieces );
+
+};
+
+THREE.AxisHelper.prototype = Object.create( THREE.Line.prototype );
+
+/**
+ * @author WestLangley / http://github.com/WestLangley
+ * @author zz85 / http://github.com/zz85
+ * @author bhouston / http://exocortex.com
+ *
+ * Creates an arrow for visualizing directions
+ *
+ * Parameters:
+ * dir - Vector3
+ * origin - Vector3
+ * length - Number
+ * hex - color in hex value
+ */
+
+THREE.ArrowHelper = function ( dir, origin, length, hex ) {
+
+ // dir is assumed to be normalized
+
+ THREE.Object3D.call( this );
+
+ if ( hex === undefined ) hex = 0xffff00;
+ if ( length === undefined ) length = 1;
+
+ this.position = origin;
+
+ var lineGeometry = new THREE.Geometry();
+ lineGeometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) );
+ lineGeometry.vertices.push( new THREE.Vector3( 0, 1, 0 ) );
+
+ this.line = new THREE.Line( lineGeometry, new THREE.LineBasicMaterial( { color: hex } ) );
+ this.line.matrixAutoUpdate = false;
+ this.add( this.line );
+
+ var coneGeometry = new THREE.CylinderGeometry( 0, 0.05, 0.25, 5, 1 );
+ coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, 0.875, 0 ) );
+
+ this.cone = new THREE.Mesh( coneGeometry, new THREE.MeshBasicMaterial( { color: hex } ) );
+ this.cone.matrixAutoUpdate = false;
+ this.add( this.cone );
+
+ this.setDirection( dir );
+ this.setLength( length );
+
+};
+
+THREE.ArrowHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.ArrowHelper.prototype.setDirection = function () {
+
+ var axis = new THREE.Vector3();
+ var radians;
+
+ return function ( dir ) {
+
+ // dir is assumed to be normalized
+
+ if ( dir.y > 0.99999 ) {
+
+ this.quaternion.set( 0, 0, 0, 1 );
+
+ } else if ( dir.y < - 0.99999 ) {
+
+ this.quaternion.set( 1, 0, 0, 0 );
+
+ } else {
+
+ axis.set( dir.z, 0, - dir.x ).normalize();
+
+ radians = Math.acos( dir.y );
+
+ this.quaternion.setFromAxisAngle( axis, radians );
+
+ }
+
+ };
+
+}();
+
+THREE.ArrowHelper.prototype.setLength = function ( length ) {
+
+ this.scale.set( length, length, length );
+
+};
+
+THREE.ArrowHelper.prototype.setColor = function ( hex ) {
+
+ this.line.material.color.setHex( hex );
+ this.cone.material.color.setHex( hex );
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.BoxHelper = function ( object ) {
+
+ // 5____4
+ // 1/___0/|
+ // | 6__|_7
+ // 2/___3/
+
+ var vertices = [
+ new THREE.Vector3( 1, 1, 1 ),
+ new THREE.Vector3( - 1, 1, 1 ),
+ new THREE.Vector3( - 1, - 1, 1 ),
+ new THREE.Vector3( 1, - 1, 1 ),
+
+ new THREE.Vector3( 1, 1, - 1 ),
+ new THREE.Vector3( - 1, 1, - 1 ),
+ new THREE.Vector3( - 1, - 1, - 1 ),
+ new THREE.Vector3( 1, - 1, - 1 )
+ ];
+
+ this.vertices = vertices;
+
+ // TODO: Wouldn't be nice if Line had .segments?
+
+ var geometry = new THREE.Geometry();
+ geometry.vertices.push(
+ vertices[ 0 ], vertices[ 1 ],
+ vertices[ 1 ], vertices[ 2 ],
+ vertices[ 2 ], vertices[ 3 ],
+ vertices[ 3 ], vertices[ 0 ],
+
+ vertices[ 4 ], vertices[ 5 ],
+ vertices[ 5 ], vertices[ 6 ],
+ vertices[ 6 ], vertices[ 7 ],
+ vertices[ 7 ], vertices[ 4 ],
+
+ vertices[ 0 ], vertices[ 4 ],
+ vertices[ 1 ], vertices[ 5 ],
+ vertices[ 2 ], vertices[ 6 ],
+ vertices[ 3 ], vertices[ 7 ]
+ );
+
+ THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: 0xffff00 } ), THREE.LinePieces );
+
+ if ( object !== undefined ) {
+
+ this.update( object );
+
+ }
+
+};
+
+THREE.BoxHelper.prototype = Object.create( THREE.Line.prototype );
+
+THREE.BoxHelper.prototype.update = function ( object ) {
+
+ var geometry = object.geometry;
+
+ if ( geometry.boundingBox === null ) {
+
+ geometry.computeBoundingBox();
+
+ }
+
+ var min = geometry.boundingBox.min;
+ var max = geometry.boundingBox.max;
+ var vertices = this.vertices;
+
+ vertices[ 0 ].set( max.x, max.y, max.z );
+ vertices[ 1 ].set( min.x, max.y, max.z );
+ vertices[ 2 ].set( min.x, min.y, max.z );
+ vertices[ 3 ].set( max.x, min.y, max.z );
+ vertices[ 4 ].set( max.x, max.y, min.z );
+ vertices[ 5 ].set( min.x, max.y, min.z );
+ vertices[ 6 ].set( min.x, min.y, min.z );
+ vertices[ 7 ].set( max.x, min.y, min.z );
+
+ this.geometry.computeBoundingSphere();
+ this.geometry.verticesNeedUpdate = true;
+
+ this.matrixAutoUpdate = false;
+ this.matrixWorld = object.matrixWorld;
+
+};
+
+/**
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+// a helper to show the world-axis-aligned bounding box for an object
+
+THREE.BoundingBoxHelper = function ( object, hex ) {
+
+ var color = hex || 0x888888;
+
+ this.object = object;
+
+ this.box = new THREE.Box3();
+
+ THREE.Mesh.call( this, new THREE.CubeGeometry( 1, 1, 1 ), new THREE.MeshBasicMaterial( { color: color, wireframe: true } ) );
+
+};
+
+THREE.BoundingBoxHelper.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.BoundingBoxHelper.prototype.update = function () {
+
+ this.box.setFromObject( this.object );
+
+ this.box.size( this.scale );
+
+ this.box.center( this.position );
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * - shows frustum, line of sight and up of the camera
+ * - suitable for fast updates
+ * - based on frustum visualization in lightgl.js shadowmap example
+ * http://evanw.github.com/lightgl.js/tests/shadowmap.html
+ */
+
+THREE.CameraHelper = function ( camera ) {
+
+ var geometry = new THREE.Geometry();
+ var material = new THREE.LineBasicMaterial( { color: 0xffffff, vertexColors: THREE.FaceColors } );
+
+ var pointMap = {};
+
+ // colors
+
+ var hexFrustum = 0xffaa00;
+ var hexCone = 0xff0000;
+ var hexUp = 0x00aaff;
+ var hexTarget = 0xffffff;
+ var hexCross = 0x333333;
+
+ // near
+
+ addLine( "n1", "n2", hexFrustum );
+ addLine( "n2", "n4", hexFrustum );
+ addLine( "n4", "n3", hexFrustum );
+ addLine( "n3", "n1", hexFrustum );
+
+ // far
+
+ addLine( "f1", "f2", hexFrustum );
+ addLine( "f2", "f4", hexFrustum );
+ addLine( "f4", "f3", hexFrustum );
+ addLine( "f3", "f1", hexFrustum );
+
+ // sides
+
+ addLine( "n1", "f1", hexFrustum );
+ addLine( "n2", "f2", hexFrustum );
+ addLine( "n3", "f3", hexFrustum );
+ addLine( "n4", "f4", hexFrustum );
+
+ // cone
+
+ addLine( "p", "n1", hexCone );
+ addLine( "p", "n2", hexCone );
+ addLine( "p", "n3", hexCone );
+ addLine( "p", "n4", hexCone );
+
+ // up
+
+ addLine( "u1", "u2", hexUp );
+ addLine( "u2", "u3", hexUp );
+ addLine( "u3", "u1", hexUp );
+
+ // target
+
+ addLine( "c", "t", hexTarget );
+ addLine( "p", "c", hexCross );
+
+ // cross
+
+ addLine( "cn1", "cn2", hexCross );
+ addLine( "cn3", "cn4", hexCross );
+
+ addLine( "cf1", "cf2", hexCross );
+ addLine( "cf3", "cf4", hexCross );
+
+ function addLine( a, b, hex ) {
+
+ addPoint( a, hex );
+ addPoint( b, hex );
+
+ }
+
+ function addPoint( id, hex ) {
+
+ geometry.vertices.push( new THREE.Vector3() );
+ geometry.colors.push( new THREE.Color( hex ) );
+
+ if ( pointMap[ id ] === undefined ) {
+
+ pointMap[ id ] = [];
+
+ }
+
+ pointMap[ id ].push( geometry.vertices.length - 1 );
+
+ }
+
+ THREE.Line.call( this, geometry, material, THREE.LinePieces );
+
+ this.camera = camera;
+ this.matrixWorld = camera.matrixWorld;
+ this.matrixAutoUpdate = false;
+
+ this.pointMap = pointMap;
+
+ this.update();
+
+};
+
+THREE.CameraHelper.prototype = Object.create( THREE.Line.prototype );
+
+THREE.CameraHelper.prototype.update = function () {
+
+ var vector = new THREE.Vector3();
+ var camera = new THREE.Camera();
+ var projector = new THREE.Projector();
+
+ return function () {
+
+ var scope = this;
+
+ var w = 1, h = 1;
+
+ // we need just camera projection matrix
+ // world matrix must be identity
+
+ camera.projectionMatrix.copy( this.camera.projectionMatrix );
+
+ // center / target
+
+ setPoint( "c", 0, 0, -1 );
+ setPoint( "t", 0, 0, 1 );
+
+ // near
+
+ setPoint( "n1", -w, -h, -1 );
+ setPoint( "n2", w, -h, -1 );
+ setPoint( "n3", -w, h, -1 );
+ setPoint( "n4", w, h, -1 );
+
+ // far
+
+ setPoint( "f1", -w, -h, 1 );
+ setPoint( "f2", w, -h, 1 );
+ setPoint( "f3", -w, h, 1 );
+ setPoint( "f4", w, h, 1 );
+
+ // up
+
+ setPoint( "u1", w * 0.7, h * 1.1, -1 );
+ setPoint( "u2", -w * 0.7, h * 1.1, -1 );
+ setPoint( "u3", 0, h * 2, -1 );
+
+ // cross
+
+ setPoint( "cf1", -w, 0, 1 );
+ setPoint( "cf2", w, 0, 1 );
+ setPoint( "cf3", 0, -h, 1 );
+ setPoint( "cf4", 0, h, 1 );
+
+ setPoint( "cn1", -w, 0, -1 );
+ setPoint( "cn2", w, 0, -1 );
+ setPoint( "cn3", 0, -h, -1 );
+ setPoint( "cn4", 0, h, -1 );
+
+ function setPoint( point, x, y, z ) {
+
+ vector.set( x, y, z );
+ projector.unprojectVector( vector, camera );
+
+ var points = scope.pointMap[ point ];
+
+ if ( points !== undefined ) {
+
+ for ( var i = 0, il = points.length; i < il; i ++ ) {
+
+ scope.geometry.vertices[ points[ i ] ].copy( vector );
+
+ }
+
+ }
+
+ }
+
+ this.geometry.verticesNeedUpdate = true;
+
+ };
+
+}();
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.DirectionalLightHelper = function ( light, size ) {
+
+ THREE.Object3D.call( this );
+
+ this.light = light;
+ this.light.updateMatrixWorld();
+
+ this.matrixWorld = light.matrixWorld;
+ this.matrixAutoUpdate = false;
+
+ var geometry = new THREE.PlaneGeometry( size, size );
+ var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } );
+ material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+ this.lightPlane = new THREE.Mesh( geometry, material );
+ this.add( this.lightPlane );
+
+ geometry = new THREE.Geometry();
+ geometry.vertices.push( new THREE.Vector3() );
+ geometry.vertices.push( new THREE.Vector3() );
+ geometry.computeLineDistances();
+
+ material = new THREE.LineBasicMaterial( { fog: false } );
+ material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+ this.targetLine = new THREE.Line( geometry, material );
+ this.add( this.targetLine );
+
+ this.update();
+
+};
+
+THREE.DirectionalLightHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.DirectionalLightHelper.prototype.dispose = function () {
+
+ this.lightPlane.geometry.dispose();
+ this.lightPlane.material.dispose();
+ this.targetLine.geometry.dispose();
+ this.targetLine.material.dispose();
+};
+
+THREE.DirectionalLightHelper.prototype.update = function () {
+
+ var vector = new THREE.Vector3();
+
+ return function () {
+
+ vector.getPositionFromMatrix( this.light.matrixWorld ).negate();
+
+ this.lightPlane.lookAt( vector );
+ this.lightPlane.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+ this.targetLine.geometry.vertices[ 1 ].copy( vector );
+ this.targetLine.geometry.verticesNeedUpdate = true;
+ this.targetLine.material.color.copy( this.lightPlane.material.color );
+
+ }
+
+}();
+
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.FaceNormalsHelper = function ( object, size, hex, linewidth ) {
+
+ this.object = object;
+
+ this.size = size || 1;
+
+ var color = hex || 0xffff00;
+
+ var width = linewidth || 1;
+
+ var geometry = new THREE.Geometry();
+
+ var faces = this.object.geometry.faces;
+
+ for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+ geometry.vertices.push( new THREE.Vector3() );
+ geometry.vertices.push( new THREE.Vector3() );
+
+ }
+
+ THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces );
+
+ this.matrixAutoUpdate = false;
+
+ this.normalMatrix = new THREE.Matrix3();
+
+ this.update();
+
+};
+
+THREE.FaceNormalsHelper.prototype = Object.create( THREE.Line.prototype );
+
+THREE.FaceNormalsHelper.prototype.update = ( function ( object ) {
+
+ var v1 = new THREE.Vector3();
+
+ return function ( object ) {
+
+ this.object.updateMatrixWorld( true );
+
+ this.normalMatrix.getNormalMatrix( this.object.matrixWorld );
+
+ var vertices = this.geometry.vertices;
+
+ var faces = this.object.geometry.faces;
+
+ var worldMatrix = this.object.matrixWorld;
+
+ for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+ var face = faces[ i ];
+
+ v1.copy( face.normal ).applyMatrix3( this.normalMatrix ).normalize().multiplyScalar( this.size );
+
+ var idx = 2 * i;
+
+ vertices[ idx ].copy( face.centroid ).applyMatrix4( worldMatrix );
+
+ vertices[ idx + 1 ].addVectors( vertices[ idx ], v1 );
+
+ }
+
+ this.geometry.verticesNeedUpdate = true;
+
+ return this;
+
+ }
+
+}());
+
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.GridHelper = function ( size, step ) {
+
+ var geometry = new THREE.Geometry();
+ var material = new THREE.LineBasicMaterial( { vertexColors: THREE.VertexColors } );
+
+ this.color1 = new THREE.Color( 0x444444 );
+ this.color2 = new THREE.Color( 0x888888 );
+
+ for ( var i = - size; i <= size; i += step ) {
+
+ geometry.vertices.push(
+ new THREE.Vector3( - size, 0, i ), new THREE.Vector3( size, 0, i ),
+ new THREE.Vector3( i, 0, - size ), new THREE.Vector3( i, 0, size )
+ );
+
+ var color = i === 0 ? this.color1 : this.color2;
+
+ geometry.colors.push( color, color, color, color );
+
+ }
+
+ THREE.Line.call( this, geometry, material, THREE.LinePieces );
+
+};
+
+THREE.GridHelper.prototype = Object.create( THREE.Line.prototype );
+
+THREE.GridHelper.prototype.setColors = function( colorCenterLine, colorGrid ) {
+
+ this.color1.set( colorCenterLine );
+ this.color2.set( colorGrid );
+
+ this.geometry.colorsNeedUpdate = true;
+
+}
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.HemisphereLightHelper = function ( light, sphereSize, arrowLength, domeSize ) {
+
+ THREE.Object3D.call( this );
+
+ this.light = light;
+ this.light.updateMatrixWorld();
+
+ this.matrixWorld = light.matrixWorld;
+ this.matrixAutoUpdate = false;
+
+ this.colors = [ new THREE.Color(), new THREE.Color() ];
+
+ var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 );
+ geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
+
+ for ( var i = 0, il = 8; i < il; i ++ ) {
+
+ geometry.faces[ i ].color = this.colors[ i < 4 ? 0 : 1 ];
+
+ }
+
+ var material = new THREE.MeshBasicMaterial( { vertexColors: THREE.FaceColors, wireframe: true } );
+
+ this.lightSphere = new THREE.Mesh( geometry, material );
+ this.add( this.lightSphere );
+
+ this.update();
+
+};
+
+THREE.HemisphereLightHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.HemisphereLightHelper.prototype.dispose = function () {
+ this.lightSphere.geometry.dispose();
+ this.lightSphere.material.dispose();
+};
+
+THREE.HemisphereLightHelper.prototype.update = function () {
+
+ var vector = new THREE.Vector3();
+
+ return function () {
+
+ this.colors[ 0 ].copy( this.light.color ).multiplyScalar( this.light.intensity );
+ this.colors[ 1 ].copy( this.light.groundColor ).multiplyScalar( this.light.intensity );
+
+ this.lightSphere.lookAt( vector.getPositionFromMatrix( this.light.matrixWorld ).negate() );
+ this.lightSphere.geometry.colorsNeedUpdate = true;
+
+ }
+
+}();
+
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.PointLightHelper = function ( light, sphereSize ) {
+
+ this.light = light;
+ this.light.updateMatrixWorld();
+
+ var geometry = new THREE.SphereGeometry( sphereSize, 4, 2 );
+ var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } );
+ material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+ THREE.Mesh.call( this, geometry, material );
+
+ this.matrixWorld = this.light.matrixWorld;
+ this.matrixAutoUpdate = false;
+
+ /*
+ var distanceGeometry = new THREE.IcosahedronGeometry( 1, 2 );
+ var distanceMaterial = new THREE.MeshBasicMaterial( { color: hexColor, fog: false, wireframe: true, opacity: 0.1, transparent: true } );
+
+ this.lightSphere = new THREE.Mesh( bulbGeometry, bulbMaterial );
+ this.lightDistance = new THREE.Mesh( distanceGeometry, distanceMaterial );
+
+ var d = light.distance;
+
+ if ( d === 0.0 ) {
+
+ this.lightDistance.visible = false;
+
+ } else {
+
+ this.lightDistance.scale.set( d, d, d );
+
+ }
+
+ this.add( this.lightDistance );
+ */
+
+};
+
+THREE.PointLightHelper.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.PointLightHelper.prototype.dispose = function () {
+
+ this.geometry.dispose();
+ this.material.dispose();
+};
+
+THREE.PointLightHelper.prototype.update = function () {
+
+ this.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+ /*
+ var d = this.light.distance;
+
+ if ( d === 0.0 ) {
+
+ this.lightDistance.visible = false;
+
+ } else {
+
+ this.lightDistance.visible = true;
+ this.lightDistance.scale.set( d, d, d );
+
+ }
+ */
+
+};
+
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.SpotLightHelper = function ( light ) {
+
+ THREE.Object3D.call( this );
+
+ this.light = light;
+ this.light.updateMatrixWorld();
+
+ this.matrixWorld = light.matrixWorld;
+ this.matrixAutoUpdate = false;
+
+ var geometry = new THREE.CylinderGeometry( 0, 1, 1, 8, 1, true );
+
+ geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, -0.5, 0 ) );
+ geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
+
+ var material = new THREE.MeshBasicMaterial( { wireframe: true, fog: false } );
+
+ this.cone = new THREE.Mesh( geometry, material );
+ this.add( this.cone );
+
+ this.update();
+
+};
+
+THREE.SpotLightHelper.prototype = Object.create( THREE.Object3D.prototype );
+
+THREE.SpotLightHelper.prototype.dispose = function () {
+ this.cone.geometry.dispose();
+ this.cone.material.dispose();
+};
+
+THREE.SpotLightHelper.prototype.update = function () {
+
+ var vector = new THREE.Vector3();
+ var vector2 = new THREE.Vector3();
+
+ return function () {
+
+ var coneLength = this.light.distance ? this.light.distance : 10000;
+ var coneWidth = coneLength * Math.tan( this.light.angle );
+
+ this.cone.scale.set( coneWidth, coneWidth, coneLength );
+
+ vector.getPositionFromMatrix( this.light.matrixWorld );
+ vector2.getPositionFromMatrix( this.light.target.matrixWorld );
+
+ this.cone.lookAt( vector2.sub( vector ) );
+
+ this.cone.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+ };
+
+}();
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.VertexNormalsHelper = function ( object, size, hex, linewidth ) {
+
+ this.object = object;
+
+ this.size = size || 1;
+
+ var color = hex || 0xff0000;
+
+ var width = linewidth || 1;
+
+ var geometry = new THREE.Geometry();
+
+ var vertices = object.geometry.vertices;
+
+ var faces = object.geometry.faces;
+
+ for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+ var face = faces[ i ];
+
+ for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
+
+ geometry.vertices.push( new THREE.Vector3() );
+ geometry.vertices.push( new THREE.Vector3() );
+
+ }
+
+ }
+
+ THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces );
+
+ this.matrixAutoUpdate = false;
+
+ this.normalMatrix = new THREE.Matrix3();
+
+ this.update();
+
+};
+
+THREE.VertexNormalsHelper.prototype = Object.create( THREE.Line.prototype );
+
+THREE.VertexNormalsHelper.prototype.update = ( function ( object ) {
+
+ var v1 = new THREE.Vector3();
+
+ return function( object ) {
+
+ var keys = [ 'a', 'b', 'c', 'd' ];
+
+ this.object.updateMatrixWorld( true );
+
+ this.normalMatrix.getNormalMatrix( this.object.matrixWorld );
+
+ var vertices = this.geometry.vertices;
+
+ var verts = this.object.geometry.vertices;
+
+ var faces = this.object.geometry.faces;
+
+ var worldMatrix = this.object.matrixWorld;
+
+ var idx = 0;
+
+ for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+ var face = faces[ i ];
+
+ for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
+
+ var vertexId = face[ keys[ j ] ];
+ var vertex = verts[ vertexId ];
+
+ var normal = face.vertexNormals[ j ];
+
+ vertices[ idx ].copy( vertex ).applyMatrix4( worldMatrix );
+
+ v1.copy( normal ).applyMatrix3( this.normalMatrix ).normalize().multiplyScalar( this.size );
+
+ v1.add( vertices[ idx ] );
+ idx = idx + 1;
+
+ vertices[ idx ].copy( v1 );
+ idx = idx + 1;
+
+ }
+
+ }
+
+ this.geometry.verticesNeedUpdate = true;
+
+ return this;
+
+ }
+
+}());
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.VertexTangentsHelper = function ( object, size, hex, linewidth ) {
+
+ this.object = object;
+
+ this.size = size || 1;
+
+ var color = hex || 0x0000ff;
+
+ var width = linewidth || 1;
+
+ var geometry = new THREE.Geometry();
+
+ var vertices = object.geometry.vertices;
+
+ var faces = object.geometry.faces;
+
+ for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+ var face = faces[ i ];
+
+ for ( var j = 0, jl = face.vertexTangents.length; j < jl; j ++ ) {
+
+ geometry.vertices.push( new THREE.Vector3() );
+ geometry.vertices.push( new THREE.Vector3() );
+
+ }
+
+ }
+
+ THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color, linewidth: width } ), THREE.LinePieces );
+
+ this.matrixAutoUpdate = false;
+
+ this.update();
+
+};
+
+THREE.VertexTangentsHelper.prototype = Object.create( THREE.Line.prototype );
+
+THREE.VertexTangentsHelper.prototype.update = ( function ( object ) {
+
+ var v1 = new THREE.Vector3();
+
+ return function( object ) {
+
+ var keys = [ 'a', 'b', 'c', 'd' ];
+
+ this.object.updateMatrixWorld( true );
+
+ var vertices = this.geometry.vertices;
+
+ var verts = this.object.geometry.vertices;
+
+ var faces = this.object.geometry.faces;
+
+ var worldMatrix = this.object.matrixWorld;
+
+ var idx = 0;
+
+ for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+ var face = faces[ i ];
+
+ for ( var j = 0, jl = face.vertexTangents.length; j < jl; j ++ ) {
+
+ var vertexId = face[ keys[ j ] ];
+ var vertex = verts[ vertexId ];
+
+ var tangent = face.vertexTangents[ j ];
+
+ vertices[ idx ].copy( vertex ).applyMatrix4( worldMatrix );
+
+ v1.copy( tangent ).transformDirection( worldMatrix ).multiplyScalar( this.size );
+
+ v1.add( vertices[ idx ] );
+ idx = idx + 1;
+
+ vertices[ idx ].copy( v1 );
+ idx = idx + 1;
+
+ }
+
+ }
+
+ this.geometry.verticesNeedUpdate = true;
+
+ return this;
+
+ }
+
+}());
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.WireframeHelper = function ( object ) {
+
+ var edge = [ 0, 0 ], hash = {};
+ var sortFunction = function ( a, b ) { return a - b };
+
+ var keys = [ 'a', 'b', 'c', 'd' ];
+ var geometry = new THREE.Geometry();
+
+ var vertices = object.geometry.vertices;
+ var faces = object.geometry.faces;
+
+ for ( var i = 0, l = faces.length; i < l; i ++ ) {
+
+ var face = faces[ i ];
+
+ for ( var j = 0; j < 3; j ++ ) {
+
+ edge[ 0 ] = face[ keys[ j ] ];
+ edge[ 1 ] = face[ keys[ ( j + 1 ) % 3 ] ];
+ edge.sort( sortFunction );
+
+ var key = edge.toString();
+
+ if ( hash[ key ] === undefined ) {
+
+ geometry.vertices.push( vertices[ edge[ 0 ] ] );
+ geometry.vertices.push( vertices[ edge[ 1 ] ] );
+
+ hash[ key ] = true;
+
+ }
+
+ }
+
+ }
+
+ THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: 0xffffff } ), THREE.LinePieces );
+
+ this.matrixAutoUpdate = false;
+ this.matrixWorld = object.matrixWorld;
+
+};
+
+THREE.WireframeHelper.prototype = Object.create( THREE.Line.prototype );
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ImmediateRenderObject = function () {
+
+ THREE.Object3D.call( this );
+
+ this.render = function ( renderCallback ) { };
+
+};
+
+THREE.ImmediateRenderObject.prototype = Object.create( THREE.Object3D.prototype );
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.LensFlare = function ( texture, size, distance, blending, color ) {
+
+ THREE.Object3D.call( this );
+
+ this.lensFlares = [];
+
+ this.positionScreen = new THREE.Vector3();
+ this.customUpdateCallback = undefined;
+
+ if( texture !== undefined ) {
+
+ this.add( texture, size, distance, blending, color );
+
+ }
+
+};
+
+THREE.LensFlare.prototype = Object.create( THREE.Object3D.prototype );
+
+
+/*
+ * Add: adds another flare
+ */
+
+THREE.LensFlare.prototype.add = function ( texture, size, distance, blending, color, opacity ) {
+
+ if( size === undefined ) size = -1;
+ if( distance === undefined ) distance = 0;
+ if( opacity === undefined ) opacity = 1;
+ if( color === undefined ) color = new THREE.Color( 0xffffff );
+ if( blending === undefined ) blending = THREE.NormalBlending;
+
+ distance = Math.min( distance, Math.max( 0, distance ) );
+
+ this.lensFlares.push( { texture: texture, // THREE.Texture
+ size: size, // size in pixels (-1 = use texture.width)
+ distance: distance, // distance (0-1) from light source (0=at light source)
+ x: 0, y: 0, z: 0, // screen position (-1 => 1) z = 0 is ontop z = 1 is back
+ scale: 1, // scale
+ rotation: 1, // rotation
+ opacity: opacity, // opacity
+ color: color, // color
+ blending: blending } ); // blending
+
+};
+
+
+/*
+ * Update lens flares update positions on all flares based on the screen position
+ * Set myLensFlare.customUpdateCallback to alter the flares in your project specific way.
+ */
+
+THREE.LensFlare.prototype.updateLensFlares = function () {
+
+ var f, fl = this.lensFlares.length;
+ var flare;
+ var vecX = -this.positionScreen.x * 2;
+ var vecY = -this.positionScreen.y * 2;
+
+ for( f = 0; f < fl; f ++ ) {
+
+ flare = this.lensFlares[ f ];
+
+ flare.x = this.positionScreen.x + vecX * flare.distance;
+ flare.y = this.positionScreen.y + vecY * flare.distance;
+
+ flare.wantedRotation = flare.x * Math.PI * 0.25;
+ flare.rotation += ( flare.wantedRotation - flare.rotation ) * 0.25;
+
+ }
+
+};
+
+
+
+
+
+
+
+
+
+
+
+
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.MorphBlendMesh = function( geometry, material ) {
+
+ THREE.Mesh.call( this, geometry, material );
+
+ this.animationsMap = {};
+ this.animationsList = [];
+
+ // prepare default animation
+ // (all frames played together in 1 second)
+
+ var numFrames = this.geometry.morphTargets.length;
+
+ var name = "__default";
+
+ var startFrame = 0;
+ var endFrame = numFrames - 1;
+
+ var fps = numFrames / 1;
+
+ this.createAnimation( name, startFrame, endFrame, fps );
+ this.setAnimationWeight( name, 1 );
+
+};
+
+THREE.MorphBlendMesh.prototype = Object.create( THREE.Mesh.prototype );
+
+THREE.MorphBlendMesh.prototype.createAnimation = function ( name, start, end, fps ) {
+
+ var animation = {
+
+ startFrame: start,
+ endFrame: end,
+
+ length: end - start + 1,
+
+ fps: fps,
+ duration: ( end - start ) / fps,
+
+ lastFrame: 0,
+ currentFrame: 0,
+
+ active: false,
+
+ time: 0,
+ direction: 1,
+ weight: 1,
+
+ directionBackwards: false,
+ mirroredLoop: false
+
+ };
+
+ this.animationsMap[ name ] = animation;
+ this.animationsList.push( animation );
+
+};
+
+THREE.MorphBlendMesh.prototype.autoCreateAnimations = function ( fps ) {
+
+ var pattern = /([a-z]+)(\d+)/;
+
+ var firstAnimation, frameRanges = {};
+
+ var geometry = this.geometry;
+
+ for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
+
+ var morph = geometry.morphTargets[ i ];
+ var chunks = morph.name.match( pattern );
+
+ if ( chunks && chunks.length > 1 ) {
+
+ var name = chunks[ 1 ];
+ var num = chunks[ 2 ];
+
+ if ( ! frameRanges[ name ] ) frameRanges[ name ] = { start: Infinity, end: -Infinity };
+
+ var range = frameRanges[ name ];
+
+ if ( i < range.start ) range.start = i;
+ if ( i > range.end ) range.end = i;
+
+ if ( ! firstAnimation ) firstAnimation = name;
+
+ }
+
+ }
+
+ for ( var name in frameRanges ) {
+
+ var range = frameRanges[ name ];
+ this.createAnimation( name, range.start, range.end, fps );
+
+ }
+
+ this.firstAnimation = firstAnimation;
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationDirectionForward = function ( name ) {
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ animation.direction = 1;
+ animation.directionBackwards = false;
+
+ }
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward = function ( name ) {
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ animation.direction = -1;
+ animation.directionBackwards = true;
+
+ }
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationFPS = function ( name, fps ) {
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ animation.fps = fps;
+ animation.duration = ( animation.end - animation.start ) / animation.fps;
+
+ }
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationDuration = function ( name, duration ) {
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ animation.duration = duration;
+ animation.fps = ( animation.end - animation.start ) / animation.duration;
+
+ }
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationWeight = function ( name, weight ) {
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ animation.weight = weight;
+
+ }
+
+};
+
+THREE.MorphBlendMesh.prototype.setAnimationTime = function ( name, time ) {
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ animation.time = time;
+
+ }
+
+};
+
+THREE.MorphBlendMesh.prototype.getAnimationTime = function ( name ) {
+
+ var time = 0;
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ time = animation.time;
+
+ }
+
+ return time;
+
+};
+
+THREE.MorphBlendMesh.prototype.getAnimationDuration = function ( name ) {
+
+ var duration = -1;
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ duration = animation.duration;
+
+ }
+
+ return duration;
+
+};
+
+THREE.MorphBlendMesh.prototype.playAnimation = function ( name ) {
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ animation.time = 0;
+ animation.active = true;
+
+ } else {
+
+ console.warn( "animation[" + name + "] undefined" );
+
+ }
+
+};
+
+THREE.MorphBlendMesh.prototype.stopAnimation = function ( name ) {
+
+ var animation = this.animationsMap[ name ];
+
+ if ( animation ) {
+
+ animation.active = false;
+
+ }
+
+};
+
+THREE.MorphBlendMesh.prototype.update = function ( delta ) {
+
+ for ( var i = 0, il = this.animationsList.length; i < il; i ++ ) {
+
+ var animation = this.animationsList[ i ];
+
+ if ( ! animation.active ) continue;
+
+ var frameTime = animation.duration / animation.length;
+
+ animation.time += animation.direction * delta;
+
+ if ( animation.mirroredLoop ) {
+
+ if ( animation.time > animation.duration || animation.time < 0 ) {
+
+ animation.direction *= -1;
+
+ if ( animation.time > animation.duration ) {
+
+ animation.time = animation.duration;
+ animation.directionBackwards = true;
+
+ }
+
+ if ( animation.time < 0 ) {
+
+ animation.time = 0;
+ animation.directionBackwards = false;
+
+ }
+
+ }
+
+ } else {
+
+ animation.time = animation.time % animation.duration;
+
+ if ( animation.time < 0 ) animation.time += animation.duration;
+
+ }
+
+ var keyframe = animation.startFrame + THREE.Math.clamp( Math.floor( animation.time / frameTime ), 0, animation.length - 1 );
+ var weight = animation.weight;
+
+ if ( keyframe !== animation.currentFrame ) {
+
+ this.morphTargetInfluences[ animation.lastFrame ] = 0;
+ this.morphTargetInfluences[ animation.currentFrame ] = 1 * weight;
+
+ this.morphTargetInfluences[ keyframe ] = 0;
+
+ animation.lastFrame = animation.currentFrame;
+ animation.currentFrame = keyframe;
+
+ }
+
+ var mix = ( animation.time % frameTime ) / frameTime;
+
+ if ( animation.directionBackwards ) mix = 1 - mix;
+
+ this.morphTargetInfluences[ animation.currentFrame ] = mix * weight;
+ this.morphTargetInfluences[ animation.lastFrame ] = ( 1 - mix ) * weight;
+
+ }
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.LensFlarePlugin = function () {
+
+ var _gl, _renderer, _precision, _lensFlare = {};
+
+ this.init = function ( renderer ) {
+
+ _gl = renderer.context;
+ _renderer = renderer;
+
+ _precision = renderer.getPrecision();
+
+ _lensFlare.vertices = new Float32Array( 8 + 8 );
+ _lensFlare.faces = new Uint16Array( 6 );
+
+ var i = 0;
+ _lensFlare.vertices[ i++ ] = -1; _lensFlare.vertices[ i++ ] = -1; // vertex
+ _lensFlare.vertices[ i++ ] = 0; _lensFlare.vertices[ i++ ] = 0; // uv... etc.
+
+ _lensFlare.vertices[ i++ ] = 1; _lensFlare.vertices[ i++ ] = -1;
+ _lensFlare.vertices[ i++ ] = 1; _lensFlare.vertices[ i++ ] = 0;
+
+ _lensFlare.vertices[ i++ ] = 1; _lensFlare.vertices[ i++ ] = 1;
+ _lensFlare.vertices[ i++ ] = 1; _lensFlare.vertices[ i++ ] = 1;
+
+ _lensFlare.vertices[ i++ ] = -1; _lensFlare.vertices[ i++ ] = 1;
+ _lensFlare.vertices[ i++ ] = 0; _lensFlare.vertices[ i++ ] = 1;
+
+ i = 0;
+ _lensFlare.faces[ i++ ] = 0; _lensFlare.faces[ i++ ] = 1; _lensFlare.faces[ i++ ] = 2;
+ _lensFlare.faces[ i++ ] = 0; _lensFlare.faces[ i++ ] = 2; _lensFlare.faces[ i++ ] = 3;
+
+ // buffers
+
+ _lensFlare.vertexBuffer = _gl.createBuffer();
+ _lensFlare.elementBuffer = _gl.createBuffer();
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, _lensFlare.vertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, _lensFlare.vertices, _gl.STATIC_DRAW );
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.elementBuffer );
+ _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.faces, _gl.STATIC_DRAW );
+
+ // textures
+
+ _lensFlare.tempTexture = _gl.createTexture();
+ _lensFlare.occlusionTexture = _gl.createTexture();
+
+ _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture );
+ _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGB, 16, 16, 0, _gl.RGB, _gl.UNSIGNED_BYTE, null );
+ _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 );
+
+ _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.occlusionTexture );
+ _gl.texImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, 16, 16, 0, _gl.RGBA, _gl.UNSIGNED_BYTE, null );
+ _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 ( _gl.getParameter( _gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS ) <= 0 ) {
+
+ _lensFlare.hasVertexTexture = false;
+ _lensFlare.program = createProgram( THREE.ShaderFlares[ "lensFlare" ], _precision );
+
+ } else {
+
+ _lensFlare.hasVertexTexture = true;
+ _lensFlare.program = createProgram( THREE.ShaderFlares[ "lensFlareVertexTexture" ], _precision );
+
+ }
+
+ _lensFlare.attributes = {};
+ _lensFlare.uniforms = {};
+
+ _lensFlare.attributes.vertex = _gl.getAttribLocation ( _lensFlare.program, "position" );
+ _lensFlare.attributes.uv = _gl.getAttribLocation ( _lensFlare.program, "uv" );
+
+ _lensFlare.uniforms.renderType = _gl.getUniformLocation( _lensFlare.program, "renderType" );
+ _lensFlare.uniforms.map = _gl.getUniformLocation( _lensFlare.program, "map" );
+ _lensFlare.uniforms.occlusionMap = _gl.getUniformLocation( _lensFlare.program, "occlusionMap" );
+ _lensFlare.uniforms.opacity = _gl.getUniformLocation( _lensFlare.program, "opacity" );
+ _lensFlare.uniforms.color = _gl.getUniformLocation( _lensFlare.program, "color" );
+ _lensFlare.uniforms.scale = _gl.getUniformLocation( _lensFlare.program, "scale" );
+ _lensFlare.uniforms.rotation = _gl.getUniformLocation( _lensFlare.program, "rotation" );
+ _lensFlare.uniforms.screenPosition = _gl.getUniformLocation( _lensFlare.program, "screenPosition" );
+
+ };
+
+
+ /*
+ * Render lens flares
+ * Method: renders 16x16 0xff00ff-colored points scattered over the light source area,
+ * reads these back and calculates occlusion.
+ * Then _lensFlare.update_lensFlares() is called to re-position and
+ * update transparency of flares. Then they are rendered.
+ *
+ */
+
+ this.render = function ( scene, camera, viewportWidth, viewportHeight ) {
+
+ var flares = scene.__webglFlares,
+ nFlares = flares.length;
+
+ if ( ! nFlares ) return;
+
+ var tempPosition = new THREE.Vector3();
+
+ var invAspect = viewportHeight / viewportWidth,
+ halfViewportWidth = viewportWidth * 0.5,
+ halfViewportHeight = viewportHeight * 0.5;
+
+ var size = 16 / viewportHeight,
+ scale = new THREE.Vector2( size * invAspect, size );
+
+ var screenPosition = new THREE.Vector3( 1, 1, 0 ),
+ screenPositionPixels = new THREE.Vector2( 1, 1 );
+
+ var uniforms = _lensFlare.uniforms,
+ attributes = _lensFlare.attributes;
+
+ // set _lensFlare program and reset blending
+
+ _gl.useProgram( _lensFlare.program );
+
+ _gl.enableVertexAttribArray( _lensFlare.attributes.vertex );
+ _gl.enableVertexAttribArray( _lensFlare.attributes.uv );
+
+ // loop through all lens flares to update their occlusion and positions
+ // setup gl and common used attribs/unforms
+
+ _gl.uniform1i( uniforms.occlusionMap, 0 );
+ _gl.uniform1i( uniforms.map, 1 );
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, _lensFlare.vertexBuffer );
+ _gl.vertexAttribPointer( attributes.vertex, 2, _gl.FLOAT, false, 2 * 8, 0 );
+ _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 2 * 8, 8 );
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _lensFlare.elementBuffer );
+
+ _gl.disable( _gl.CULL_FACE );
+ _gl.depthMask( false );
+
+ var i, j, jl, flare, sprite;
+
+ for ( i = 0; i < nFlares; i ++ ) {
+
+ size = 16 / viewportHeight;
+ scale.set( size * invAspect, size );
+
+ // calc object screen position
+
+ flare = flares[ i ];
+
+ tempPosition.set( flare.matrixWorld.elements[12], flare.matrixWorld.elements[13], flare.matrixWorld.elements[14] );
+
+ tempPosition.applyMatrix4( camera.matrixWorldInverse );
+ tempPosition.applyProjection( camera.projectionMatrix );
+
+ // setup arrays for gl programs
+
+ screenPosition.copy( tempPosition )
+
+ screenPositionPixels.x = screenPosition.x * halfViewportWidth + halfViewportWidth;
+ screenPositionPixels.y = screenPosition.y * halfViewportHeight + halfViewportHeight;
+
+ // screen cull
+
+ if ( _lensFlare.hasVertexTexture || (
+ screenPositionPixels.x > 0 &&
+ screenPositionPixels.x < viewportWidth &&
+ screenPositionPixels.y > 0 &&
+ screenPositionPixels.y < viewportHeight ) ) {
+
+ // save current RGB to temp texture
+
+ _gl.activeTexture( _gl.TEXTURE1 );
+ _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture );
+ _gl.copyTexImage2D( _gl.TEXTURE_2D, 0, _gl.RGB, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 );
+
+
+ // render pink quad
+
+ _gl.uniform1i( uniforms.renderType, 0 );
+ _gl.uniform2f( uniforms.scale, scale.x, scale.y );
+ _gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z );
+
+ _gl.disable( _gl.BLEND );
+ _gl.enable( _gl.DEPTH_TEST );
+
+ _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+
+ // copy result to occlusionMap
+
+ _gl.activeTexture( _gl.TEXTURE0 );
+ _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.occlusionTexture );
+ _gl.copyTexImage2D( _gl.TEXTURE_2D, 0, _gl.RGBA, screenPositionPixels.x - 8, screenPositionPixels.y - 8, 16, 16, 0 );
+
+
+ // restore graphics
+
+ _gl.uniform1i( uniforms.renderType, 1 );
+ _gl.disable( _gl.DEPTH_TEST );
+
+ _gl.activeTexture( _gl.TEXTURE1 );
+ _gl.bindTexture( _gl.TEXTURE_2D, _lensFlare.tempTexture );
+ _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+
+ // update object positions
+
+ flare.positionScreen.copy( screenPosition )
+
+ if ( flare.customUpdateCallback ) {
+
+ flare.customUpdateCallback( flare );
+
+ } else {
+
+ flare.updateLensFlares();
+
+ }
+
+ // render flares
+
+ _gl.uniform1i( uniforms.renderType, 2 );
+ _gl.enable( _gl.BLEND );
+
+ for ( j = 0, jl = flare.lensFlares.length; j < jl; j ++ ) {
+
+ sprite = flare.lensFlares[ j ];
+
+ if ( sprite.opacity > 0.001 && sprite.scale > 0.001 ) {
+
+ screenPosition.x = sprite.x;
+ screenPosition.y = sprite.y;
+ screenPosition.z = sprite.z;
+
+ size = sprite.size * sprite.scale / viewportHeight;
+
+ scale.x = size * invAspect;
+ scale.y = size;
+
+ _gl.uniform3f( uniforms.screenPosition, screenPosition.x, screenPosition.y, screenPosition.z );
+ _gl.uniform2f( uniforms.scale, scale.x, scale.y );
+ _gl.uniform1f( uniforms.rotation, sprite.rotation );
+
+ _gl.uniform1f( uniforms.opacity, sprite.opacity );
+ _gl.uniform3f( uniforms.color, sprite.color.r, sprite.color.g, sprite.color.b );
+
+ _renderer.setBlending( sprite.blending, sprite.blendEquation, sprite.blendSrc, sprite.blendDst );
+ _renderer.setTexture( sprite.texture, 1 );
+
+ _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ // restore gl
+
+ _gl.enable( _gl.CULL_FACE );
+ _gl.enable( _gl.DEPTH_TEST );
+ _gl.depthMask( true );
+
+ };
+
+ function createProgram ( shader, precision ) {
+
+ var program = _gl.createProgram();
+
+ var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER );
+ var vertexShader = _gl.createShader( _gl.VERTEX_SHADER );
+
+ var prefix = "precision " + precision + " float;\n";
+
+ _gl.shaderSource( fragmentShader, prefix + shader.fragmentShader );
+ _gl.shaderSource( vertexShader, prefix + shader.vertexShader );
+
+ _gl.compileShader( fragmentShader );
+ _gl.compileShader( vertexShader );
+
+ _gl.attachShader( program, fragmentShader );
+ _gl.attachShader( program, vertexShader );
+
+ _gl.linkProgram( program );
+
+ return program;
+
+ };
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ShadowMapPlugin = function () {
+
+ var _gl,
+ _renderer,
+ _depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin,
+
+ _frustum = new THREE.Frustum(),
+ _projScreenMatrix = new THREE.Matrix4(),
+
+ _min = new THREE.Vector3(),
+ _max = new THREE.Vector3(),
+
+ _matrixPosition = new THREE.Vector3();
+
+ this.init = function ( renderer ) {
+
+ _gl = renderer.context;
+ _renderer = renderer;
+
+ var depthShader = THREE.ShaderLib[ "depthRGBA" ];
+ var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms );
+
+ _depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } );
+ _depthMaterialMorph = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true } );
+ _depthMaterialSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, skinning: true } );
+ _depthMaterialMorphSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true, skinning: true } );
+
+ _depthMaterial._shadowPass = true;
+ _depthMaterialMorph._shadowPass = true;
+ _depthMaterialSkin._shadowPass = true;
+ _depthMaterialMorphSkin._shadowPass = true;
+
+ };
+
+ this.render = function ( scene, camera ) {
+
+ if ( ! ( _renderer.shadowMapEnabled && _renderer.shadowMapAutoUpdate ) ) return;
+
+ this.update( scene, camera );
+
+ };
+
+ this.update = function ( scene, camera ) {
+
+ var i, il, j, jl, n,
+
+ shadowMap, shadowMatrix, shadowCamera,
+ program, buffer, material,
+ webglObject, object, light,
+ renderList,
+
+ lights = [],
+ k = 0,
+
+ fog = null;
+
+ // set GL state for depth map
+
+ _gl.clearColor( 1, 1, 1, 1 );
+ _gl.disable( _gl.BLEND );
+
+ _gl.enable( _gl.CULL_FACE );
+ _gl.frontFace( _gl.CCW );
+
+ if ( _renderer.shadowMapCullFace === THREE.CullFaceFront ) {
+
+ _gl.cullFace( _gl.FRONT );
+
+ } else {
+
+ _gl.cullFace( _gl.BACK );
+
+ }
+
+ _renderer.setDepthTest( true );
+
+ // preprocess lights
+ // - skip lights that are not casting shadows
+ // - create virtual lights for cascaded shadow maps
+
+ for ( i = 0, il = scene.__lights.length; i < il; i ++ ) {
+
+ light = scene.__lights[ i ];
+
+ if ( ! light.castShadow ) continue;
+
+ if ( ( light instanceof THREE.DirectionalLight ) && light.shadowCascade ) {
+
+ for ( n = 0; n < light.shadowCascadeCount; n ++ ) {
+
+ var virtualLight;
+
+ if ( ! light.shadowCascadeArray[ n ] ) {
+
+ virtualLight = createVirtualLight( light, n );
+ virtualLight.originalCamera = camera;
+
+ var gyro = new THREE.Gyroscope();
+ gyro.position = light.shadowCascadeOffset;
+
+ gyro.add( virtualLight );
+ gyro.add( virtualLight.target );
+
+ camera.add( gyro );
+
+ light.shadowCascadeArray[ n ] = virtualLight;
+
+ console.log( "Created virtualLight", virtualLight );
+
+ } else {
+
+ virtualLight = light.shadowCascadeArray[ n ];
+
+ }
+
+ updateVirtualLight( light, n );
+
+ lights[ k ] = virtualLight;
+ k ++;
+
+ }
+
+ } else {
+
+ lights[ k ] = light;
+ k ++;
+
+ }
+
+ }
+
+ // render depth map
+
+ for ( i = 0, il = lights.length; i < il; i ++ ) {
+
+ light = lights[ i ];
+
+ if ( ! light.shadowMap ) {
+
+ var shadowFilter = THREE.LinearFilter;
+
+ if ( _renderer.shadowMapType === THREE.PCFSoftShadowMap ) {
+
+ shadowFilter = THREE.NearestFilter;
+
+ }
+
+ var pars = { minFilter: shadowFilter, magFilter: shadowFilter, format: THREE.RGBAFormat };
+
+ light.shadowMap = new THREE.WebGLRenderTarget( light.shadowMapWidth, light.shadowMapHeight, pars );
+ light.shadowMapSize = new THREE.Vector2( light.shadowMapWidth, light.shadowMapHeight );
+
+ light.shadowMatrix = new THREE.Matrix4();
+
+ }
+
+ if ( ! light.shadowCamera ) {
+
+ if ( light instanceof THREE.SpotLight ) {
+
+ light.shadowCamera = new THREE.PerspectiveCamera( light.shadowCameraFov, light.shadowMapWidth / light.shadowMapHeight, light.shadowCameraNear, light.shadowCameraFar );
+
+ } else if ( light instanceof THREE.DirectionalLight ) {
+
+ light.shadowCamera = new THREE.OrthographicCamera( light.shadowCameraLeft, light.shadowCameraRight, light.shadowCameraTop, light.shadowCameraBottom, light.shadowCameraNear, light.shadowCameraFar );
+
+ } else {
+
+ console.error( "Unsupported light type for shadow" );
+ continue;
+
+ }
+
+ scene.add( light.shadowCamera );
+
+ if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+ }
+
+ if ( light.shadowCameraVisible && ! light.cameraHelper ) {
+
+ light.cameraHelper = new THREE.CameraHelper( light.shadowCamera );
+ light.shadowCamera.add( light.cameraHelper );
+
+ }
+
+ if ( light.isVirtual && virtualLight.originalCamera == camera ) {
+
+ updateShadowCamera( camera, light );
+
+ }
+
+ shadowMap = light.shadowMap;
+ shadowMatrix = light.shadowMatrix;
+ shadowCamera = light.shadowCamera;
+
+ shadowCamera.position.getPositionFromMatrix( light.matrixWorld );
+ _matrixPosition.getPositionFromMatrix( light.target.matrixWorld );
+ shadowCamera.lookAt( _matrixPosition );
+ shadowCamera.updateMatrixWorld();
+
+ shadowCamera.matrixWorldInverse.getInverse( shadowCamera.matrixWorld );
+
+ if ( light.cameraHelper ) light.cameraHelper.visible = light.shadowCameraVisible;
+ if ( light.shadowCameraVisible ) light.cameraHelper.update();
+
+ // compute shadow matrix
+
+ shadowMatrix.set( 0.5, 0.0, 0.0, 0.5,
+ 0.0, 0.5, 0.0, 0.5,
+ 0.0, 0.0, 0.5, 0.5,
+ 0.0, 0.0, 0.0, 1.0 );
+
+ shadowMatrix.multiply( shadowCamera.projectionMatrix );
+ shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
+
+ // update camera matrices and frustum
+
+ _projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
+ _frustum.setFromMatrix( _projScreenMatrix );
+
+ // render shadow map
+
+ _renderer.setRenderTarget( shadowMap );
+ _renderer.clear();
+
+ // set object matrices & frustum culling
+
+ renderList = scene.__webglObjects;
+
+ for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+ webglObject = renderList[ j ];
+ object = webglObject.object;
+
+ webglObject.render = false;
+
+ if ( object.visible && object.castShadow ) {
+
+ if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) {
+
+ object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+
+ webglObject.render = true;
+
+ }
+
+ }
+
+ }
+
+ // render regular objects
+
+ var objectMaterial, useMorphing, useSkinning;
+
+ for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+ webglObject = renderList[ j ];
+
+ if ( webglObject.render ) {
+
+ object = webglObject.object;
+ buffer = webglObject.buffer;
+
+ // culling is overriden globally for all objects
+ // while rendering depth map
+
+ // need to deal with MeshFaceMaterial somehow
+ // in that case just use the first of material.materials for now
+ // (proper solution would require to break objects by materials
+ // similarly to regular rendering and then set corresponding
+ // depth materials per each chunk instead of just once per object)
+
+ objectMaterial = getObjectMaterial( object );
+
+ useMorphing = object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets;
+ useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning;
+
+ if ( object.customDepthMaterial ) {
+
+ material = object.customDepthMaterial;
+
+ } else if ( useSkinning ) {
+
+ material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin;
+
+ } else if ( useMorphing ) {
+
+ material = _depthMaterialMorph;
+
+ } else {
+
+ material = _depthMaterial;
+
+ }
+
+ if ( buffer instanceof THREE.BufferGeometry ) {
+
+ _renderer.renderBufferDirect( shadowCamera, scene.__lights, fog, material, buffer, object );
+
+ } else {
+
+ _renderer.renderBuffer( shadowCamera, scene.__lights, fog, material, buffer, object );
+
+ }
+
+ }
+
+ }
+
+ // set matrices and render immediate objects
+
+ renderList = scene.__webglObjectsImmediate;
+
+ for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+ webglObject = renderList[ j ];
+ object = webglObject.object;
+
+ if ( object.visible && object.castShadow ) {
+
+ object._modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+
+ _renderer.renderImmediateObject( shadowCamera, scene.__lights, fog, _depthMaterial, object );
+
+ }
+
+ }
+
+ }
+
+ // restore GL state
+
+ var clearColor = _renderer.getClearColor(),
+ clearAlpha = _renderer.getClearAlpha();
+
+ _gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha );
+ _gl.enable( _gl.BLEND );
+
+ if ( _renderer.shadowMapCullFace === THREE.CullFaceFront ) {
+
+ _gl.cullFace( _gl.BACK );
+
+ }
+
+ };
+
+ function createVirtualLight( light, cascade ) {
+
+ var virtualLight = new THREE.DirectionalLight();
+
+ virtualLight.isVirtual = true;
+
+ virtualLight.onlyShadow = true;
+ virtualLight.castShadow = true;
+
+ virtualLight.shadowCameraNear = light.shadowCameraNear;
+ virtualLight.shadowCameraFar = light.shadowCameraFar;
+
+ virtualLight.shadowCameraLeft = light.shadowCameraLeft;
+ virtualLight.shadowCameraRight = light.shadowCameraRight;
+ virtualLight.shadowCameraBottom = light.shadowCameraBottom;
+ virtualLight.shadowCameraTop = light.shadowCameraTop;
+
+ virtualLight.shadowCameraVisible = light.shadowCameraVisible;
+
+ virtualLight.shadowDarkness = light.shadowDarkness;
+
+ virtualLight.shadowBias = light.shadowCascadeBias[ cascade ];
+ virtualLight.shadowMapWidth = light.shadowCascadeWidth[ cascade ];
+ virtualLight.shadowMapHeight = light.shadowCascadeHeight[ cascade ];
+
+ virtualLight.pointsWorld = [];
+ virtualLight.pointsFrustum = [];
+
+ var pointsWorld = virtualLight.pointsWorld,
+ pointsFrustum = virtualLight.pointsFrustum;
+
+ for ( var i = 0; i < 8; i ++ ) {
+
+ pointsWorld[ i ] = new THREE.Vector3();
+ pointsFrustum[ i ] = new THREE.Vector3();
+
+ }
+
+ var nearZ = light.shadowCascadeNearZ[ cascade ];
+ var farZ = light.shadowCascadeFarZ[ cascade ];
+
+ pointsFrustum[ 0 ].set( -1, -1, nearZ );
+ pointsFrustum[ 1 ].set( 1, -1, nearZ );
+ pointsFrustum[ 2 ].set( -1, 1, nearZ );
+ pointsFrustum[ 3 ].set( 1, 1, nearZ );
+
+ pointsFrustum[ 4 ].set( -1, -1, farZ );
+ pointsFrustum[ 5 ].set( 1, -1, farZ );
+ pointsFrustum[ 6 ].set( -1, 1, farZ );
+ pointsFrustum[ 7 ].set( 1, 1, farZ );
+
+ return virtualLight;
+
+ }
+
+ // Synchronize virtual light with the original light
+
+ function updateVirtualLight( light, cascade ) {
+
+ var virtualLight = light.shadowCascadeArray[ cascade ];
+
+ virtualLight.position.copy( light.position );
+ virtualLight.target.position.copy( light.target.position );
+ virtualLight.lookAt( virtualLight.target );
+
+ virtualLight.shadowCameraVisible = light.shadowCameraVisible;
+ virtualLight.shadowDarkness = light.shadowDarkness;
+
+ virtualLight.shadowBias = light.shadowCascadeBias[ cascade ];
+
+ var nearZ = light.shadowCascadeNearZ[ cascade ];
+ var farZ = light.shadowCascadeFarZ[ cascade ];
+
+ var pointsFrustum = virtualLight.pointsFrustum;
+
+ pointsFrustum[ 0 ].z = nearZ;
+ pointsFrustum[ 1 ].z = nearZ;
+ pointsFrustum[ 2 ].z = nearZ;
+ pointsFrustum[ 3 ].z = nearZ;
+
+ pointsFrustum[ 4 ].z = farZ;
+ pointsFrustum[ 5 ].z = farZ;
+ pointsFrustum[ 6 ].z = farZ;
+ pointsFrustum[ 7 ].z = farZ;
+
+ }
+
+ // Fit shadow camera's ortho frustum to camera frustum
+
+ function updateShadowCamera( camera, light ) {
+
+ var shadowCamera = light.shadowCamera,
+ pointsFrustum = light.pointsFrustum,
+ pointsWorld = light.pointsWorld;
+
+ _min.set( Infinity, Infinity, Infinity );
+ _max.set( -Infinity, -Infinity, -Infinity );
+
+ for ( var i = 0; i < 8; i ++ ) {
+
+ var p = pointsWorld[ i ];
+
+ p.copy( pointsFrustum[ i ] );
+ THREE.ShadowMapPlugin.__projector.unprojectVector( p, camera );
+
+ p.applyMatrix4( shadowCamera.matrixWorldInverse );
+
+ if ( p.x < _min.x ) _min.x = p.x;
+ if ( p.x > _max.x ) _max.x = p.x;
+
+ if ( p.y < _min.y ) _min.y = p.y;
+ if ( p.y > _max.y ) _max.y = p.y;
+
+ if ( p.z < _min.z ) _min.z = p.z;
+ if ( p.z > _max.z ) _max.z = p.z;
+
+ }
+
+ shadowCamera.left = _min.x;
+ shadowCamera.right = _max.x;
+ shadowCamera.top = _max.y;
+ shadowCamera.bottom = _min.y;
+
+ // can't really fit near/far
+ //shadowCamera.near = _min.z;
+ //shadowCamera.far = _max.z;
+
+ shadowCamera.updateProjectionMatrix();
+
+ }
+
+ // For the moment just ignore objects that have multiple materials with different animation methods
+ // Only the first material will be taken into account for deciding which depth material to use for shadow maps
+
+ function getObjectMaterial( object ) {
+
+ return object.material instanceof THREE.MeshFaceMaterial
+ ? object.material.materials[ 0 ]
+ : object.material;
+
+ };
+
+};
+
+THREE.ShadowMapPlugin.__projector = new THREE.Projector();
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SpritePlugin = function () {
+
+ var _gl, _renderer, _precision, _sprite = {};
+
+ this.init = function ( renderer ) {
+
+ _gl = renderer.context;
+ _renderer = renderer;
+
+ _precision = renderer.getPrecision();
+
+ _sprite.vertices = new Float32Array( 8 + 8 );
+ _sprite.faces = new Uint16Array( 6 );
+
+ var i = 0;
+
+ _sprite.vertices[ i++ ] = -1; _sprite.vertices[ i++ ] = -1; // vertex 0
+ _sprite.vertices[ i++ ] = 0; _sprite.vertices[ i++ ] = 0; // uv 0
+
+ _sprite.vertices[ i++ ] = 1; _sprite.vertices[ i++ ] = -1; // vertex 1
+ _sprite.vertices[ i++ ] = 1; _sprite.vertices[ i++ ] = 0; // uv 1
+
+ _sprite.vertices[ i++ ] = 1; _sprite.vertices[ i++ ] = 1; // vertex 2
+ _sprite.vertices[ i++ ] = 1; _sprite.vertices[ i++ ] = 1; // uv 2
+
+ _sprite.vertices[ i++ ] = -1; _sprite.vertices[ i++ ] = 1; // vertex 3
+ _sprite.vertices[ i++ ] = 0; _sprite.vertices[ i++ ] = 1; // uv 3
+
+ i = 0;
+
+ _sprite.faces[ i++ ] = 0; _sprite.faces[ i++ ] = 1; _sprite.faces[ i++ ] = 2;
+ _sprite.faces[ i++ ] = 0; _sprite.faces[ i++ ] = 2; _sprite.faces[ i++ ] = 3;
+
+ _sprite.vertexBuffer = _gl.createBuffer();
+ _sprite.elementBuffer = _gl.createBuffer();
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, _sprite.vertexBuffer );
+ _gl.bufferData( _gl.ARRAY_BUFFER, _sprite.vertices, _gl.STATIC_DRAW );
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _sprite.elementBuffer );
+ _gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, _sprite.faces, _gl.STATIC_DRAW );
+
+ _sprite.program = createProgram( THREE.ShaderSprite[ "sprite" ], _precision );
+
+ _sprite.attributes = {};
+ _sprite.uniforms = {};
+
+ _sprite.attributes.position = _gl.getAttribLocation ( _sprite.program, "position" );
+ _sprite.attributes.uv = _gl.getAttribLocation ( _sprite.program, "uv" );
+
+ _sprite.uniforms.uvOffset = _gl.getUniformLocation( _sprite.program, "uvOffset" );
+ _sprite.uniforms.uvScale = _gl.getUniformLocation( _sprite.program, "uvScale" );
+
+ _sprite.uniforms.rotation = _gl.getUniformLocation( _sprite.program, "rotation" );
+ _sprite.uniforms.scale = _gl.getUniformLocation( _sprite.program, "scale" );
+ _sprite.uniforms.alignment = _gl.getUniformLocation( _sprite.program, "alignment" );
+
+ _sprite.uniforms.color = _gl.getUniformLocation( _sprite.program, "color" );
+ _sprite.uniforms.map = _gl.getUniformLocation( _sprite.program, "map" );
+ _sprite.uniforms.opacity = _gl.getUniformLocation( _sprite.program, "opacity" );
+
+ _sprite.uniforms.useScreenCoordinates = _gl.getUniformLocation( _sprite.program, "useScreenCoordinates" );
+ _sprite.uniforms.sizeAttenuation = _gl.getUniformLocation( _sprite.program, "sizeAttenuation" );
+ _sprite.uniforms.screenPosition = _gl.getUniformLocation( _sprite.program, "screenPosition" );
+ _sprite.uniforms.modelViewMatrix = _gl.getUniformLocation( _sprite.program, "modelViewMatrix" );
+ _sprite.uniforms.projectionMatrix = _gl.getUniformLocation( _sprite.program, "projectionMatrix" );
+
+ _sprite.uniforms.fogType = _gl.getUniformLocation( _sprite.program, "fogType" );
+ _sprite.uniforms.fogDensity = _gl.getUniformLocation( _sprite.program, "fogDensity" );
+ _sprite.uniforms.fogNear = _gl.getUniformLocation( _sprite.program, "fogNear" );
+ _sprite.uniforms.fogFar = _gl.getUniformLocation( _sprite.program, "fogFar" );
+ _sprite.uniforms.fogColor = _gl.getUniformLocation( _sprite.program, "fogColor" );
+
+ _sprite.uniforms.alphaTest = _gl.getUniformLocation( _sprite.program, "alphaTest" );
+
+ };
+
+ this.render = function ( scene, camera, viewportWidth, viewportHeight ) {
+
+ var sprites = scene.__webglSprites,
+ nSprites = sprites.length;
+
+ if ( ! nSprites ) return;
+
+ var attributes = _sprite.attributes,
+ uniforms = _sprite.uniforms;
+
+ var invAspect = viewportHeight / viewportWidth;
+
+ var halfViewportWidth = viewportWidth * 0.5,
+ halfViewportHeight = viewportHeight * 0.5;
+
+ // setup gl
+
+ _gl.useProgram( _sprite.program );
+
+ _gl.enableVertexAttribArray( attributes.position );
+ _gl.enableVertexAttribArray( attributes.uv );
+
+ _gl.disable( _gl.CULL_FACE );
+ _gl.enable( _gl.BLEND );
+
+ _gl.bindBuffer( _gl.ARRAY_BUFFER, _sprite.vertexBuffer );
+ _gl.vertexAttribPointer( attributes.position, 2, _gl.FLOAT, false, 2 * 8, 0 );
+ _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 2 * 8, 8 );
+
+ _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, _sprite.elementBuffer );
+
+ _gl.uniformMatrix4fv( uniforms.projectionMatrix, false, camera.projectionMatrix.elements );
+
+ _gl.activeTexture( _gl.TEXTURE0 );
+ _gl.uniform1i( uniforms.map, 0 );
+
+ var oldFogType = 0;
+ var sceneFogType = 0;
+ var fog = scene.fog;
+
+ if ( fog ) {
+
+ _gl.uniform3f( uniforms.fogColor, fog.color.r, fog.color.g, fog.color.b );
+
+ if ( fog instanceof THREE.Fog ) {
+
+ _gl.uniform1f( uniforms.fogNear, fog.near );
+ _gl.uniform1f( uniforms.fogFar, fog.far );
+
+ _gl.uniform1i( uniforms.fogType, 1 );
+ oldFogType = 1;
+ sceneFogType = 1;
+
+ } else if ( fog instanceof THREE.FogExp2 ) {
+
+ _gl.uniform1f( uniforms.fogDensity, fog.density );
+
+ _gl.uniform1i( uniforms.fogType, 2 );
+ oldFogType = 2;
+ sceneFogType = 2;
+
+ }
+
+ } else {
+
+ _gl.uniform1i( uniforms.fogType, 0 );
+ oldFogType = 0;
+ sceneFogType = 0;
+
+ }
+
+
+ // update positions and sort
+
+ var i, sprite, material, screenPosition, size, fogType, scale = [];
+
+ for( i = 0; i < nSprites; i ++ ) {
+
+ sprite = sprites[ i ];
+ material = sprite.material;
+
+ if ( ! sprite.visible || material.opacity === 0 ) continue;
+
+ if ( ! material.useScreenCoordinates ) {
+
+ sprite._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld );
+ sprite.z = - sprite._modelViewMatrix.elements[ 14 ];
+
+ } else {
+
+ sprite.z = - sprite.position.z;
+
+ }
+
+ }
+
+ sprites.sort( painterSortStable );
+
+ // render all sprites
+
+ for( i = 0; i < nSprites; i ++ ) {
+
+ sprite = sprites[ i ];
+ material = sprite.material;
+
+ if ( ! sprite.visible || material.opacity === 0 ) continue;
+
+ if ( material.map && material.map.image && material.map.image.width ) {
+
+ _gl.uniform1f( uniforms.alphaTest, material.alphaTest );
+
+ if ( material.useScreenCoordinates === true ) {
+
+ _gl.uniform1i( uniforms.useScreenCoordinates, 1 );
+ _gl.uniform3f(
+ uniforms.screenPosition,
+ ( ( sprite.position.x * _renderer.devicePixelRatio ) - halfViewportWidth ) / halfViewportWidth,
+ ( halfViewportHeight - ( sprite.position.y * _renderer.devicePixelRatio ) ) / halfViewportHeight,
+ Math.max( 0, Math.min( 1, sprite.position.z ) )
+ );
+
+ scale[ 0 ] = _renderer.devicePixelRatio;
+ scale[ 1 ] = _renderer.devicePixelRatio;
+
+ } else {
+
+ _gl.uniform1i( uniforms.useScreenCoordinates, 0 );
+ _gl.uniform1i( uniforms.sizeAttenuation, material.sizeAttenuation ? 1 : 0 );
+ _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite._modelViewMatrix.elements );
+
+ scale[ 0 ] = 1;
+ scale[ 1 ] = 1;
+
+ }
+
+ if ( scene.fog && material.fog ) {
+
+ fogType = sceneFogType;
+
+ } else {
+
+ fogType = 0;
+
+ }
+
+ if ( oldFogType !== fogType ) {
+
+ _gl.uniform1i( uniforms.fogType, fogType );
+ oldFogType = fogType;
+
+ }
+
+ size = 1 / ( material.scaleByViewport ? viewportHeight : 1 );
+
+ scale[ 0 ] *= size * invAspect * sprite.scale.x
+ scale[ 1 ] *= size * sprite.scale.y;
+
+ _gl.uniform2f( uniforms.uvScale, material.uvScale.x, material.uvScale.y );
+ _gl.uniform2f( uniforms.uvOffset, material.uvOffset.x, material.uvOffset.y );
+ _gl.uniform2f( uniforms.alignment, material.alignment.x, material.alignment.y );
+
+ _gl.uniform1f( uniforms.opacity, material.opacity );
+ _gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b );
+
+ _gl.uniform1f( uniforms.rotation, sprite.rotation );
+ _gl.uniform2fv( uniforms.scale, scale );
+
+ _renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+ _renderer.setDepthTest( material.depthTest );
+ _renderer.setDepthWrite( material.depthWrite );
+ _renderer.setTexture( material.map, 0 );
+
+ _gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+ }
+
+ }
+
+ // restore gl
+
+ _gl.enable( _gl.CULL_FACE );
+
+ };
+
+ function createProgram ( shader, precision ) {
+
+ var program = _gl.createProgram();
+
+ var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER );
+ var vertexShader = _gl.createShader( _gl.VERTEX_SHADER );
+
+ var prefix = "precision " + precision + " float;\n";
+
+ _gl.shaderSource( fragmentShader, prefix + shader.fragmentShader );
+ _gl.shaderSource( vertexShader, prefix + shader.vertexShader );
+
+ _gl.compileShader( fragmentShader );
+ _gl.compileShader( vertexShader );
+
+ _gl.attachShader( program, fragmentShader );
+ _gl.attachShader( program, vertexShader );
+
+ _gl.linkProgram( program );
+
+ return program;
+
+ };
+
+ function painterSortStable ( a, b ) {
+
+ if ( a.z !== b.z ) {
+
+ return b.z - a.z;
+
+ } else {
+
+ return b.id - a.id;
+
+ }
+
+ };
+
+};
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.DepthPassPlugin = function () {
+
+ this.enabled = false;
+ this.renderTarget = null;
+
+ var _gl,
+ _renderer,
+ _depthMaterial, _depthMaterialMorph, _depthMaterialSkin, _depthMaterialMorphSkin,
+
+ _frustum = new THREE.Frustum(),
+ _projScreenMatrix = new THREE.Matrix4();
+
+ this.init = function ( renderer ) {
+
+ _gl = renderer.context;
+ _renderer = renderer;
+
+ var depthShader = THREE.ShaderLib[ "depthRGBA" ];
+ var depthUniforms = THREE.UniformsUtils.clone( depthShader.uniforms );
+
+ _depthMaterial = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms } );
+ _depthMaterialMorph = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true } );
+ _depthMaterialSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, skinning: true } );
+ _depthMaterialMorphSkin = new THREE.ShaderMaterial( { fragmentShader: depthShader.fragmentShader, vertexShader: depthShader.vertexShader, uniforms: depthUniforms, morphTargets: true, skinning: true } );
+
+ _depthMaterial._shadowPass = true;
+ _depthMaterialMorph._shadowPass = true;
+ _depthMaterialSkin._shadowPass = true;
+ _depthMaterialMorphSkin._shadowPass = true;
+
+ };
+
+ this.render = function ( scene, camera ) {
+
+ if ( ! this.enabled ) return;
+
+ this.update( scene, camera );
+
+ };
+
+ this.update = function ( scene, camera ) {
+
+ var i, il, j, jl, n,
+
+ program, buffer, material,
+ webglObject, object, light,
+ renderList,
+
+ fog = null;
+
+ // set GL state for depth map
+
+ _gl.clearColor( 1, 1, 1, 1 );
+ _gl.disable( _gl.BLEND );
+
+ _renderer.setDepthTest( true );
+
+ // update scene
+
+ if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+ // update camera matrices and frustum
+
+ camera.matrixWorldInverse.getInverse( camera.matrixWorld );
+
+ _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+ _frustum.setFromMatrix( _projScreenMatrix );
+
+ // render depth map
+
+ _renderer.setRenderTarget( this.renderTarget );
+ _renderer.clear();
+
+ // set object matrices & frustum culling
+
+ renderList = scene.__webglObjects;
+
+ for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+ webglObject = renderList[ j ];
+ object = webglObject.object;
+
+ webglObject.render = false;
+
+ if ( object.visible ) {
+
+ if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) {
+
+ object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+
+ webglObject.render = true;
+
+ }
+
+ }
+
+ }
+
+ // render regular objects
+
+ var objectMaterial, useMorphing, useSkinning;
+
+ for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+ webglObject = renderList[ j ];
+
+ if ( webglObject.render ) {
+
+ object = webglObject.object;
+ buffer = webglObject.buffer;
+
+ // todo: create proper depth material for particles
+
+ if ( object instanceof THREE.ParticleSystem && !object.customDepthMaterial ) continue;
+
+ objectMaterial = getObjectMaterial( object );
+
+ if ( objectMaterial ) _renderer.setMaterialFaces( object.material );
+
+ useMorphing = object.geometry.morphTargets.length > 0 && objectMaterial.morphTargets;
+ useSkinning = object instanceof THREE.SkinnedMesh && objectMaterial.skinning;
+
+ if ( object.customDepthMaterial ) {
+
+ material = object.customDepthMaterial;
+
+ } else if ( useSkinning ) {
+
+ material = useMorphing ? _depthMaterialMorphSkin : _depthMaterialSkin;
+
+ } else if ( useMorphing ) {
+
+ material = _depthMaterialMorph;
+
+ } else {
+
+ material = _depthMaterial;
+
+ }
+
+ if ( buffer instanceof THREE.BufferGeometry ) {
+
+ _renderer.renderBufferDirect( camera, scene.__lights, fog, material, buffer, object );
+
+ } else {
+
+ _renderer.renderBuffer( camera, scene.__lights, fog, material, buffer, object );
+
+ }
+
+ }
+
+ }
+
+ // set matrices and render immediate objects
+
+ renderList = scene.__webglObjectsImmediate;
+
+ for ( j = 0, jl = renderList.length; j < jl; j ++ ) {
+
+ webglObject = renderList[ j ];
+ object = webglObject.object;
+
+ if ( object.visible ) {
+
+ object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+
+ _renderer.renderImmediateObject( camera, scene.__lights, fog, _depthMaterial, object );
+
+ }
+
+ }
+
+ // restore GL state
+
+ var clearColor = _renderer.getClearColor(),
+ clearAlpha = _renderer.getClearAlpha();
+
+ _gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha );
+ _gl.enable( _gl.BLEND );
+
+ };
+
+ // For the moment just ignore objects that have multiple materials with different animation methods
+ // Only the first material will be taken into account for deciding which depth material to use
+
+ function getObjectMaterial( object ) {
+
+ return object.material instanceof THREE.MeshFaceMaterial
+ ? object.material.materials[ 0 ]
+ : object.material;
+
+ };
+
+};
+
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+THREE.ShaderFlares = {
+
+ 'lensFlareVertexTexture': {
+
+ vertexShader: [
+
+ "uniform lowp int renderType;",
+
+ "uniform vec3 screenPosition;",
+ "uniform vec2 scale;",
+ "uniform float rotation;",
+
+ "uniform sampler2D occlusionMap;",
+
+ "attribute vec2 position;",
+ "attribute vec2 uv;",
+
+ "varying vec2 vUV;",
+ "varying float vVisibility;",
+
+ "void main() {",
+
+ "vUV = uv;",
+
+ "vec2 pos = position;",
+
+ "if( renderType == 2 ) {",
+
+ "vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );",
+ "visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );",
+ "visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );",
+ "visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );",
+ "visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );",
+ "visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );",
+ "visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );",
+ "visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );",
+ "visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );",
+
+ "vVisibility = visibility.r / 9.0;",
+ "vVisibility *= 1.0 - visibility.g / 9.0;",
+ "vVisibility *= visibility.b / 9.0;",
+ "vVisibility *= 1.0 - visibility.a / 9.0;",
+
+ "pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;",
+ "pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;",
+
+ "}",
+
+ "gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );",
+
+ "}"
+
+ ].join( "\n" ),
+
+ fragmentShader: [
+
+ "uniform lowp int renderType;",
+
+ "uniform sampler2D map;",
+ "uniform float opacity;",
+ "uniform vec3 color;",
+
+ "varying vec2 vUV;",
+ "varying float vVisibility;",
+
+ "void main() {",
+
+ // pink square
+
+ "if( renderType == 0 ) {",
+
+ "gl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );",
+
+ // restore
+
+ "} else if( renderType == 1 ) {",
+
+ "gl_FragColor = texture2D( map, vUV );",
+
+ // flare
+
+ "} else {",
+
+ "vec4 texture = texture2D( map, vUV );",
+ "texture.a *= opacity * vVisibility;",
+ "gl_FragColor = texture;",
+ "gl_FragColor.rgb *= color;",
+
+ "}",
+
+ "}"
+ ].join( "\n" )
+
+ },
+
+
+ 'lensFlare': {
+
+ vertexShader: [
+
+ "uniform lowp int renderType;",
+
+ "uniform vec3 screenPosition;",
+ "uniform vec2 scale;",
+ "uniform float rotation;",
+
+ "attribute vec2 position;",
+ "attribute vec2 uv;",
+
+ "varying vec2 vUV;",
+
+ "void main() {",
+
+ "vUV = uv;",
+
+ "vec2 pos = position;",
+
+ "if( renderType == 2 ) {",
+
+ "pos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;",
+ "pos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;",
+
+ "}",
+
+ "gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );",
+
+ "}"
+
+ ].join( "\n" ),
+
+ fragmentShader: [
+
+ "precision mediump float;",
+
+ "uniform lowp int renderType;",
+
+ "uniform sampler2D map;",
+ "uniform sampler2D occlusionMap;",
+ "uniform float opacity;",
+ "uniform vec3 color;",
+
+ "varying vec2 vUV;",
+
+ "void main() {",
+
+ // pink square
+
+ "if( renderType == 0 ) {",
+
+ "gl_FragColor = vec4( texture2D( map, vUV ).rgb, 0.0 );",
+
+ // restore
+
+ "} else if( renderType == 1 ) {",
+
+ "gl_FragColor = texture2D( map, vUV );",
+
+ // flare
+
+ "} else {",
+
+ "float visibility = texture2D( occlusionMap, vec2( 0.5, 0.1 ) ).a;",
+ "visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ).a;",
+ "visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ).a;",
+ "visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ).a;",
+ "visibility = ( 1.0 - visibility / 4.0 );",
+
+ "vec4 texture = texture2D( map, vUV );",
+ "texture.a *= opacity * visibility;",
+ "gl_FragColor = texture;",
+ "gl_FragColor.rgb *= color;",
+
+ "}",
+
+ "}"
+
+ ].join( "\n" )
+
+ }
+
+};
+
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ */
+
+THREE.ShaderSprite = {
+
+ 'sprite': {
+
+ vertexShader: [
+
+ "uniform int useScreenCoordinates;",
+ "uniform int sizeAttenuation;",
+ "uniform vec3 screenPosition;",
+ "uniform mat4 modelViewMatrix;",
+ "uniform mat4 projectionMatrix;",
+ "uniform float rotation;",
+ "uniform vec2 scale;",
+ "uniform vec2 alignment;",
+ "uniform vec2 uvOffset;",
+ "uniform vec2 uvScale;",
+
+ "attribute vec2 position;",
+ "attribute vec2 uv;",
+
+ "varying vec2 vUV;",
+
+ "void main() {",
+
+ "vUV = uvOffset + uv * uvScale;",
+
+ "vec2 alignedPosition = position + alignment;",
+
+ "vec2 rotatedPosition;",
+ "rotatedPosition.x = ( cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y ) * scale.x;",
+ "rotatedPosition.y = ( sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y ) * scale.y;",
+
+ "vec4 finalPosition;",
+
+ "if( useScreenCoordinates != 0 ) {",
+
+ "finalPosition = vec4( screenPosition.xy + rotatedPosition, screenPosition.z, 1.0 );",
+
+ "} else {",
+
+ "finalPosition = projectionMatrix * modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );",
+ "finalPosition.xy += rotatedPosition * ( sizeAttenuation == 1 ? 1.0 : finalPosition.z );",
+
+ "}",
+
+ "gl_Position = finalPosition;",
+
+ "}"
+
+ ].join( "\n" ),
+
+ fragmentShader: [
+
+ "uniform vec3 color;",
+ "uniform sampler2D map;",
+ "uniform float opacity;",
+
+ "uniform int fogType;",
+ "uniform vec3 fogColor;",
+ "uniform float fogDensity;",
+ "uniform float fogNear;",
+ "uniform float fogFar;",
+ "uniform float alphaTest;",
+
+ "varying vec2 vUV;",
+
+ "void main() {",
+
+ "vec4 texture = texture2D( map, vUV );",
+
+ "if ( texture.a < alphaTest ) discard;",
+
+ "gl_FragColor = vec4( color * texture.xyz, texture.a * opacity );",
+
+ "if ( fogType > 0 ) {",
+
+ "float depth = gl_FragCoord.z / gl_FragCoord.w;",
+ "float fogFactor = 0.0;",
+
+ "if ( fogType == 1 ) {",
+
+ "fogFactor = smoothstep( fogNear, fogFar, depth );",
+
+ "} else {",
+
+ "const float LOG2 = 1.442695;",
+ "float fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );",
+ "fogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );",
+
+ "}",
+
+ "gl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );",
+
+ "}",
+
+ "}"
+
+ ].join( "\n" )
+
+ }
+
+};
+
diff --git a/papi/plugin/dpp/add/Add.py b/papi/plugin/dpp/add/Add.py
index 263bb6b4..2bb3437c 100644
--- a/papi/plugin/dpp/add/Add.py
+++ b/papi/plugin/dpp/add/Add.py
@@ -32,6 +32,7 @@
from papi.plugin.base_classes.dpp_base import dpp_base
from papi.data.DPlugin import DBlock
from papi.data.DParameter import DParameter
+from papi.data.DSignal import DSignal
import time
import math
@@ -43,7 +44,7 @@ class Add(dpp_base):
def start_init(self, config=None):
self.t = 0
- print(['ADD: process id: ',os.getpid()] )
+ #print(['ADD: process id: ',os.getpid()] )
self.approx_max = 300
self.fac= 1
self.amax = 20
@@ -52,12 +53,12 @@ def start_init(self, config=None):
self.vec = numpy.zeros((2, self.amax))
- self.block1 = DBlock(None,1,10,'AddOut1',['t','Sum'])
+ self.block1 = DBlock('AddOut1')
+ signal = DSignal('Sum')
+ self.block1.add_signal(signal)
- #self.para1 = DParameter(None,'Count',1, [0, 1] ,1)
self.send_new_block_list([self.block1])
- #self.send_new_parameter_list([self.para1])
return True
@@ -69,7 +70,7 @@ def pause(self):
def resume(self):
pass
- def execute(self, Data=None, block_name = None):
+ def execute(self, Data=None, block_name = None, plugin_uname = None):
#self.approx = round(self.approx_max*self.para1.value)
# self.vec[1] = 0
# self.vec[0] = Data[0]
@@ -107,7 +108,7 @@ def execute(self, Data=None, block_name = None):
vec[0,:] = Data['t']
vec[1,:] = result
- self.send_new_data(Data['t'], [result], 'AddOut1')
+ self.send_new_data('AddOut1', Data['t'], {'Sum':result})
diff --git a/papi/plugin/dpp/add/Add.yapsy-plugin b/papi/plugin/dpp/add/Add.yapsy-plugin
index 48b17f30..b757a2a0 100644
--- a/papi/plugin/dpp/add/Add.yapsy-plugin
+++ b/papi/plugin/dpp/add/Add.yapsy-plugin
@@ -7,3 +7,5 @@ Author = S.K.
Version = 0.1
Website = www
Description = simple sinus io plugin
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/dpp/add/box.png b/papi/plugin/dpp/add/box.png
new file mode 100644
index 00000000..6682eb57
Binary files /dev/null and b/papi/plugin/dpp/add/box.png differ
diff --git a/papi/plugin/dpp/toHDD/ToHDD_CSV.py b/papi/plugin/dpp/toHDD/ToHDD_CSV.py
index 288fa111..dfb9fa9c 100644
--- a/papi/plugin/dpp/toHDD/ToHDD_CSV.py
+++ b/papi/plugin/dpp/toHDD/ToHDD_CSV.py
@@ -63,7 +63,7 @@ def resume(self):
print('toHDD resume')
pass
- def execute(self, Data=None, block_name = None):
+ def execute(self, Data=None, block_name = None, plugin_uname = None):
t = Data['t']
if block_name not in self.known_blocks.keys():
diff --git a/papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin b/papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin
index 1f86d0a2..8d855976 100644
--- a/papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin
+++ b/papi/plugin/dpp/toHDD/ToHDD_CSV.yapsy-plugin
@@ -6,4 +6,6 @@ Module = ToHDD_CSV
Author = S.R.
Version = 0.1
Website = www
-Description = simple write to HDD plugin
\ No newline at end of file
+Description = simple write to HDD plugin
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/dpp/toHDD/box.png b/papi/plugin/dpp/toHDD/box.png
new file mode 100644
index 00000000..06ff1097
Binary files /dev/null and b/papi/plugin/dpp/toHDD/box.png differ
diff --git a/papi/plugin/dpp/trigger/Trigger.py b/papi/plugin/dpp/trigger/Trigger.py
new file mode 100644
index 00000000..e9b13b31
--- /dev/null
+++ b/papi/plugin/dpp/trigger/Trigger.py
@@ -0,0 +1,107 @@
+#!/usr/bin/python3
+#-*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2014 Technische Universität Berlin,
+Fakultät IV - Elektrotechnik und Informatik,
+Fachgebiet Regelungssysteme,
+Einsteinufer 17, D-10587 Berlin, Germany
+
+This file is part of PaPI.
+
+PaPI is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+PaPI is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with PaPI. If not, see .
+
+Contributors:
+Stefan Ruppin
+"""
+
+from papi.data.DPlugin import DBlock
+from papi.data.DSignal import DSignal
+
+from papi.data.DParameter import DParameter
+from papi.plugin.base_classes.dpp_base import dpp_base
+
+import time
+import math
+import numpy
+
+
+class Trigger(dpp_base):
+ def __init__(self):
+ self.initialized = False
+
+ def start_init(self, config=None):
+
+
+
+ self.block1 = DBlock('Progress')
+ signal = DSignal('percent')
+ self.block1.add_signal(signal)
+
+ self.block2 = DBlock('Trigger')
+ signal = DSignal('trigger')
+ self.block2.add_signal(signal)
+
+ self.block3 = DBlock('ResetTrigger')
+ signal = DSignal('reset')
+ self.block3.add_signal(signal)
+
+ blockList = [self.block1, self.block2, self.block3]
+ self.send_new_block_list(blockList)
+
+ self.para3 = DParameter('Choose', default=0, Regex='\d+')
+ para_l = [self.para3]
+
+ self.send_new_parameter_list(para_l)
+
+ self.set_event_trigger_mode(True)
+
+ self.initialized = True
+
+ return True
+
+ def pause(self):
+ pass
+
+ def resume(self):
+ pass
+
+ def execute(self, Data=None, block_name = None, plugin_uname = None):
+ pass
+
+ def set_parameter(self, name, value):
+ if not self.initialized:
+ return
+ value = int(float(value))
+ if name == self.para3.name:
+ if value == 0:
+ self.send_new_data('Progress', [0], {'percent': [20]})
+
+ if value == 1:
+ self.send_new_data('Trigger', [0], {'trigger': [0]})
+
+ if value == 2:
+ self.send_new_data('ResetTrigger', [0], {'reset': [0]})
+
+
+ def get_plugin_configuration(self):
+ config = {
+ }
+ return config
+
+ def quit(self):
+ print('Trigger: will quit')
+
+ def plugin_meta_updated(self):
+ pass
diff --git a/papi/plugin/dpp/trigger/Trigger.yapsy-plugin b/papi/plugin/dpp/trigger/Trigger.yapsy-plugin
new file mode 100644
index 00000000..03c01b64
--- /dev/null
+++ b/papi/plugin/dpp/trigger/Trigger.yapsy-plugin
@@ -0,0 +1,11 @@
+[Core]
+Name = Trigger
+Module = Trigger
+
+[Documentation]
+Author = S.K.
+Version = 0.1
+Website = www
+Description = Used to trigger specific data vectors by parameter events.
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/dpp/trigger/box.png b/papi/plugin/dpp/trigger/box.png
new file mode 100644
index 00000000..949253a4
Binary files /dev/null and b/papi/plugin/dpp/trigger/box.png differ
diff --git a/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json b/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json
deleted file mode 100644
index 1d79619e..00000000
--- a/papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json
+++ /dev/null
@@ -1,5 +0,0 @@
- {"SourcesConfig" : {
- "0" : { "SourceName" : "X" , "NValues_send" : "1", "datatype" : "257" } , "1" : { "SourceName" : "V" , "NValues_send" : "1", "datatype" : "257" } } ,
- "ParametersConfig" : {
- "0" : { "ParameterName" : "Oscillator input" , "NValues" : "1", "datatype" : "257" } , "1" : { "ParameterName" : "A vectorial parameter" , "NValues" : "10", "datatype" : "257" } , "2" : { "ParameterName" : "Test" , "NValues" : "2", "datatype" : "257" } }
-}
\ No newline at end of file
diff --git a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py
index 2c8472d1..b828b8fe 100644
--- a/papi/plugin/io/ORTD_UDP/ORTD_UDP.py
+++ b/papi/plugin/io/ORTD_UDP/ORTD_UDP.py
@@ -25,6 +25,21 @@
Contributors
Christian Klauer
Stefan Ruppin
+
+
+
+
+IDEAS
+-----
+
+- The ProtocolConfig.json may contain information about how to place indivual elements in the GUI
+- How to handle multiple instances and dynamically created datasources?
+- Show a separated screen/page in the gui for each datasource; something like tabs?
+- initial Configuration and later updates via UDP
+
+
+
+
"""
__author__ = 'CK'
@@ -32,12 +47,12 @@
from papi.plugin.base_classes.iop_base import iop_base
from papi.data.DPlugin import DBlock
+from papi.data.DSignal import DSignal
from papi.data.DParameter import DParameter
import numpy as np
import threading
-import pickle
import os
@@ -45,7 +60,10 @@
import struct
import json
+import time
+import pickle
+from threading import Timer
class OptionalObject(object):
def __init__(self, ORTD_par_id, nvalues):
@@ -69,12 +87,12 @@ def get_plugin_configuration(self):
'advanced': '1'
},
'Cfg_Path': {
- 'value': 'papi/plugin/io/ORTD_UDP/DataSourceExample/ProtocollConfig.json',
+ 'value': '/home/control/PycharmProjects/PaPI/data_sources/ORTD/DataSourceExample/ProtocollConfig.json',
'type': 'file',
'advanced': '0'
},
'SeparateSignals': {
- 'value': '1',
+ 'value': '0',
'advanced': '1'
}
}
@@ -88,81 +106,56 @@ def start_init(self, config=None):
self.HOST = config['address']['value']
self.SOURCE_PORT = int(config['source_port']['value'])
self.OUT_PORT = int(config['out_port']['value'])
+
+ self.LOCALBIND_HOST = '' # config['source_address']['value'] #CK
+
self.separate = int(config['SeparateSignals']['value'])
# SOCK_DGRAM is the socket type to use for UDP sockets
self.sock_parameter = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock_parameter.setblocking(1)
- # Load protocol config.
- path = config['Cfg_Path']['value']
- f = open(path, 'r')
- self.ProtocolConfig = json.load(f)
-
- self.Sources = self.ProtocolConfig['SourcesConfig']
- self.Parameters = self.ProtocolConfig['ParametersConfig']
-
- # For each group:: loop through all sources (=signals) in the group and register the signals
- # Register signals
-
-
- if self.separate == 1:
-
- self.blocks = {}
-
- # sort hash keys for usage in right order!
- keys = list(self.Sources.keys())
- keys.sort()
- for key in keys:
- Source = self.Sources[key]
- self.blocks[int(key)] = DBlock(None, 1, 2, 'SourceGroup' + str(key), ['t', Source['SourceName']])
+ self.ControlBlock = DBlock('ControllerSignals')
+ self.ControlBlock.add_signal(DSignal('ControlSignalReset'))
+ self.ControlBlock.add_signal(DSignal('ControlSignalCreate'))
+ self.ControlBlock.add_signal(DSignal('ControlSignalSub'))
+ self.ControlBlock.add_signal(DSignal('ControllerSignalParameter'))
+ self.ControlBlock.add_signal(DSignal('ControllerSignalClose'))
+ self.ControlBlock.add_signal(DSignal('ActiveTab'))
+ self.send_new_block_list([self.ControlBlock])
- self.send_new_block_list(list(self.blocks.values()))
-
- else:
- names = ['t']
-
- keys = list(self.Sources.keys())
- keys.sort()
-
- for key in keys:
- Source = self.Sources[key]
- names.append(Source['SourceName'])
-
- self.block1 = DBlock(None, 1, 2, 'SourceGroup0', names)
- self.send_new_block_list([self.block1])
-
-
-
- # Register parameters
- self.Parameter_List = []
-
- for Pid in self.Parameters:
- Para = self.Parameters[Pid]
- para_name = Para['ParameterName']
- val_count = Para['NValues']
- opt_object = OptionalObject(Pid, val_count)
- Parameter = DParameter('', para_name, 0, 0, OptionalObject=opt_object)
- self.Parameter_List.append(Parameter)
-
- self.send_new_parameter_list(self.Parameter_List)
self.t = 0
self.set_event_trigger_mode(True)
self.sock_recv = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- self.sock_recv.bind((self.HOST, self.SOURCE_PORT))
+ self.sock_recv.bind((self.LOCALBIND_HOST, self.SOURCE_PORT)) # CK
+ print("ORTD listening on: ", self.LOCALBIND_HOST, ":", self.SOURCE_PORT) #CK
self.sock_recv.settimeout(1)
self.thread_goOn = True
self.lock = threading.Lock()
- # self.thread = threading.Thread(target=self.thread_execute, args=(self.HOST, self.SOURCE_PORT) )
self.thread = threading.Thread(target=self.thread_execute)
self.thread.start()
+ self.blocks = {}
+ self.Sources = {}
+
+ self.parameters = {}
+
+ self.signal_values = {}
+
+ self.block_id = 0
+
+ self.config_complete = False
+ self.config_buffer = {}
+
+ self.timer = Timer(3,self.callback_timeout_timer)
+ self.timer_active = False
+
return True
def pause(self):
@@ -177,61 +170,26 @@ def resume(self):
self.thread.start()
def thread_execute(self):
- goOn = True
+ time.sleep(1)
+ self.request_new_config_from_ORTD()
+ goOn = True
+ newData = False
signal_values = {}
while goOn:
try:
- rev = self.sock_recv.recv(1600)
+ rev = self.sock_recv.recv(20000) # not feasible for network connection other than loopback
except socket.timeout:
# print('timeout')
- pass
+ newData = False
+
except socket.error:
print('ORTD got socket error')
else:
- # unpack header
- SenderId, Counter, SourceId = struct.unpack_from(' 0:
+ # data stream finished
+ self.process_finished_action(SourceId, rev)
+ self.signal_values = {}
+
+ if SourceId >= 0 and len(self.blocks) > 0:
+ # got data stream
+ self.process_data_stream(SourceId, rev)
+
+ if SourceId == -2:
+ # new config in ORTD available
+ # send trigger to get new config
+ self.request_new_config_from_ORTD()
+ self.config_complete = False
+ self.config_buffer = {}
+
+ if SourceId == -4:
+ # new configItem
+ # receive new config item and execute cfg in PaPI
+
+ # unpack package
+ i = 16 # Offset: 4 ints
+ unp = ''
+ while i < len(rev):
+ unp = unp + str(struct.unpack_from(' 1:
+ val = val[1:-1]
+ init_value = list(map(float,val.split(',')))
+ else:
+ init_value = float(val)
+ else:
+ init_value = 0
+
+ para_object = DParameter(para_name, default=init_value, OptionalObject=opt_object)
+ self.send_new_parameter_list([para_object])
+
+
+ newList[para_name] = para_object
+
+ toDeleteDict = self.parameters
+ self.parameters = newList
+
+ for par in toDeleteDict:
+ self.send_delete_parameter(par)
+
+
+ def update_block_list(self,ORTDSources):
+ #self.block_id = self.block_id +1
+ #newBlock = DBlock('SourceGroup'+str(self.block_id))
+ #self.blocks['SourceGroup'+str(self.block_id)] = newBlock
+ if 'SourceGroup0' in self.blocks:
+ self.send_delete_block('SourceGroup0')
+ newBlock = DBlock('SourceGroup0')
+ self.blocks['SourceGroup0'] = newBlock
+ self.Sources = ORTDSources
+ keys = list(self.Sources.keys())
+ for key in keys:
+ Source = self.Sources[key]
+ sig_name = Source['SourceName']
+ newBlock.add_signal(DSignal(sig_name))
+
+ self.send_new_block_list([newBlock])
+
+ # Remove BLOCKS
+ #if 'SourceGroup'+str(self.block_id-1) in self.blocks:
+ #self.send_delete_block(self.blocks.pop('SourceGroup'+str(self.block_id-1)).name)
+
+ def process_data_stream(self, SourceId, rev):
+ # Received a data packet
+ # Lookup the Source behind the given SourceId
+ if str(SourceId) in self.Sources:
+ Source = self.Sources[str(SourceId)]
+ NValues = int(Source['NValues_send'])
+
+ # Read NVales from the received packet
+ val = []
+ for i in range(NValues):
+ try:
+ val.append(struct.unpack_from('0:
+ block = list(self.blocks.keys())[0]
+ if len(self.blocks[block].signals) == len(signals_to_send):
+ self.send_new_data(block, [self.t], signals_to_send )
+
+ def execute(self, Data=None, block_name = None, plugin_uname = None):
raise Exception('Should not be called!')
def set_parameter(self, name, value):
- for para in self.Parameter_List:
- if para.name == name:
- Pid = para.OptionalObject.ORTD_par_id
- Counter = 111
- data = struct.pack('.
+
+Contributors
+Sven Knuth
+"""
+
+__author__ = 'knuths'
+
+from papi.plugin.base_classes.pcp_base import pcp_base
+from PySide.QtGui import QSlider, QVBoxLayout, QWidget, QLabel, QRadioButton
+from PySide import QtCore
+
+from papi.data.DPlugin import DBlock
+from papi.data.DParameter import DParameter
+
+import papi.constants as pc
+
+class Radiobutton(pcp_base):
+ def initiate_layer_0(self, config):
+
+ self.block = DBlock('Choice')
+
+ self.send_new_block_list([self.block])
+
+
+ para_list = []
+
+ self.para_texts = DParameter('texts', default=self.config['option_texts']['value'])
+ self.para_values = DParameter('values', default=self.config['option_values']['value'])
+
+ para_list.append(self.para_texts)
+ para_list.append(self.para_values)
+
+ self.send_new_parameter_list(para_list)
+
+ self.central_widget = QWidget()
+
+ self.option_texts = []
+ self.option_values = []
+ self.pre_selected_index = None
+
+ if isinstance(self.config['selected_index']['value'], str):
+ if self.config['selected_index']['value'] != '':
+ self.pre_selected_index = int(self.config['selected_index']['value'])
+
+ self.set_widget_for_internal_usage(self.central_widget)
+ self.layout = QVBoxLayout(self.central_widget)
+
+ self.buttons = []
+
+ self.set_option_texts(self.config['option_texts']['value'])
+ self.set_option_values(self.config['option_values']['value'])
+
+ self.update_widget()
+
+ return True
+
+ def set_option_values(self, values):
+
+ if isinstance(values, str):
+ self.option_values = str.split(values, ',')
+
+ for i in range(len(self.option_values)):
+ self.option_values[i] = self.option_values[i].lstrip().rstrip()
+
+ def set_option_texts(self, texts):
+
+ if isinstance(texts, str):
+ self.option_texts = str.split(texts, ',')
+
+ for i in range(len(self.option_texts)):
+ self.option_texts[i] = self.option_texts[i].lstrip().rstrip()
+
+ def update_widget(self):
+
+ for button in self.buttons:
+ self.layout.removeWidget(button)
+ button.deleteLater()
+
+ self.buttons = []
+
+ for i in range(len(self.option_texts)):
+ button = QRadioButton(self.option_texts[i])
+
+ button.released.connect(self.button_released)
+
+ if i == self.pre_selected_index:
+ button.setChecked(True)
+
+ self.buttons.append(button)
+ self.layout.addWidget(button)
+
+ self.central_widget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.central_widget.customContextMenuRequested.connect(self.show_context_menu)
+
+ return self.central_widget
+
+ def show_context_menu(self, pos):
+ gloPos = self.central_widget.mapToGlobal(pos)
+ self.cmenu = self.create_control_context_menu()
+ self.cmenu.exec_(gloPos)
+
+ def button_released(self):
+ for i in range(len(self.buttons)):
+ if self.buttons[i].isChecked():
+ self.config['selected_index']['value'] = str(i)
+ if len(self.option_values) == len(self.option_texts):
+ self.send_parameter_change(self.option_values[i], self.block.name)
+ else:
+ self.send_parameter_change(self.option_texts[i], self.block.name)
+
+ def set_parameter(self, parameter_name, parameter_value):
+
+
+ if parameter_name == self.para_texts.name:
+ self.set_option_texts(parameter_value)
+ self.update_widget()
+
+ if parameter_name == self.para_values.name:
+ self.set_option_values(parameter_value)
+
+
+ def plugin_meta_updated(self):
+ pass
+
+ def get_plugin_configuration(self):
+ config = {
+ 'option_texts': {
+ 'display_text' : 'Displayed Option',
+ 'value': 'Option Text 1, Option Text 2, Option Text 3',
+ 'tooltip': 'This text is seen by the user. Must be separated by commas.'
+ },
+ 'option_values': {
+ 'display_text' : 'Value per Option',
+ 'value': '',
+ 'tooltip': 'It is possible to set a value for every option. '
+ 'The corresponding value is send instead of the displayed text. '
+ },
+ 'selected_index': {
+ 'display_text' : 'Preselected Option',
+ 'value' : '',
+ 'regex' : pc.REGEX_SINGLE_INT,
+ 'tooltip': 'Preselect an option by its index.',
+ 'advanced' : '1'
+ },
+ 'name': {
+ 'display_text' : 'Plugin Name',
+ 'value': 'RadioButton Label',
+ 'tooltip': 'Used for window title'
+ }}
+ return config
+
+ def quit(self):
+ pass
+
+ def new_parameter_info(self, dparameter_object):
+ if isinstance(dparameter_object, DParameter):
+ value = dparameter_object.default
+ if str(value) in self.option_values:
+ self.pre_selected_index = self.option_values.index(str(value))
+ self.update_widget()
\ No newline at end of file
diff --git a/papi/plugin/pcp/radiobutton/Radiobutton.yapsy-plugin b/papi/plugin/pcp/radiobutton/Radiobutton.yapsy-plugin
new file mode 100644
index 00000000..2ef96fef
--- /dev/null
+++ b/papi/plugin/pcp/radiobutton/Radiobutton.yapsy-plugin
@@ -0,0 +1,13 @@
+[Core]
+Name = Radiobutton
+Module = Radiobutton
+
+[Documentation]
+Author = S.K.
+Version = 0.1
+Website = www
+Description = Enables a user to select between distinct options.
+ It is possible to define an 'option_texts' and an 'option_values'.
+ If the length between both settings differs the selected 'option_texts' will be send.
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/pcp/radiobutton/box.png b/papi/plugin/pcp/radiobutton/box.png
new file mode 100644
index 00000000..9cc3831f
Binary files /dev/null and b/papi/plugin/pcp/radiobutton/box.png differ
diff --git a/papi/plugin/pcp/slider/Slider.py b/papi/plugin/pcp/slider/Slider.py
index 7c42d182..4000692c 100644
--- a/papi/plugin/pcp/slider/Slider.py
+++ b/papi/plugin/pcp/slider/Slider.py
@@ -29,18 +29,29 @@
__author__ = 'knuths'
from papi.plugin.base_classes.pcp_base import pcp_base
-from PySide.QtGui import QSlider, QHBoxLayout, QWidget, QLineEdit
+from PySide.QtGui import QSlider, QHBoxLayout, QWidget, QLabel
from PySide import QtCore
+from PySide.QtCore import Qt
from papi.data.DPlugin import DBlock
+from papi.data.DSignal import DSignal
+from papi.data.DParameter import DParameter
+
+import papi.constants as pc
+
class Slider(pcp_base):
def initiate_layer_0(self, config):
- block = DBlock(self.__id__, 1, 1, 'Parameter_1', ['Parameter'])
+ block = DBlock('SliderBlock')
+ signal = DSignal('SliderParameter1')
+ block.add_signal(signal)
+
self.send_new_block_list([block])
self.set_widget_for_internal_usage(self.create_widget())
+ return True
+
def create_widget(self):
self.central_widget = QWidget()
@@ -48,26 +59,50 @@ def create_widget(self):
self.slider.sliderPressed.connect(self.clicked)
self.slider.valueChanged.connect(self.value_changed)
+
+
+ self.value_max = float(self.config['upper_bound']['value'])
+ self.value_min = float(self.config['lower_bound']['value'])
+ self.tick_count = float(self.config['step_count']['value'])
+ self.init_value = float(self.config['value_init']['value'])
+
+
+ self.tick_width = (self.value_max-self.value_min)/(self.tick_count-1)
+
self.slider.setMinimum(0)
- self.slider.setMaximum(100)
- self.slider.setSingleStep(1)
+ self.slider.setMaximum(self.tick_count-1)
self.slider.setOrientation(QtCore.Qt.Horizontal)
- self.text_field = QLineEdit()
- self.text_field.setReadOnly(True)
- self.text_field.text = self.config['default']['value']
+ self.text_field = QLabel()
+ self.text_field.setMinimumWidth(25)
+ self.text_field.setText(str(self.init_value))
+
+ init_value = (self.init_value - self.value_min)/self.tick_width
+ init_value = round(init_value,0)
+ self.slider.setValue(init_value)
self.layout = QHBoxLayout(self.central_widget)
self.layout.addWidget(self.slider)
self.layout.addWidget(self.text_field)
+ self.slider.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.slider.customContextMenuRequested.connect(self.show_context_menu)
+
+ self.central_widget.keyPressEvent = self.key_event
+
return self.central_widget
+ def show_context_menu(self, pos):
+ gloPos = self.slider.mapToGlobal(pos)
+ self.cmenu = self.create_control_context_menu()
+ self.cmenu.exec_(gloPos)
+
def value_changed(self, change):
- cur_value = change/100
- self.text_field.setText(str(cur_value))
- self.send_parameter_change(cur_value, 'Parameter_1', 'TESTALIAS')
+ val = change * self.tick_width + self.value_min
+ val = round(val, 8)
+ self.text_field.setText(str(val))
+ self.send_parameter_change(val, 'SliderBlock')
def clicked(self):
pass
@@ -77,11 +112,53 @@ def plugin_meta_updated(self):
def get_plugin_configuration(self):
config = {
- 'default': {
- 'value': '1'
- }
- }
+ 'lower_bound': {
+ 'value': '0.0'
+ },
+ 'upper_bound': {
+ 'value': '1.0'
+ },
+ 'step_count': {
+ 'value': '11',
+ 'regex': pc.REGEX_SINGLE_INT,
+ },
+ 'size': {
+ 'value': "(150,75)",
+ 'regex': '\(([0-9]+),([0-9]+)\)',
+ 'advanced': '1',
+ 'tooltip': 'Determine size: (height,width)'
+ },
+ 'value_init': {
+ 'value': 0,
+ 'regex' : pc.REGEX_SIGNED_FLOAT_OR_INT,
+ 'tooltip': 'Used as initial value for the Slider'
+ },
+ 'name': {
+ 'value': 'PaPI Slider',
+ 'tooltip': 'Used for window title'
+ }}
return config
+ def key_event(self, event):
+
+ if event.key() == Qt.Key_Plus:
+ self.slider.setValue(self.slider.value() + 1)
+
+ if event.key() == Qt.Key_Minus:
+ self.slider.setValue(self.slider.value() - 1)
+
def quit(self):
- pass
\ No newline at end of file
+ pass
+
+ def new_parameter_info(self, dparameter_object):
+ if isinstance(dparameter_object, DParameter):
+ value = dparameter_object.default
+ self.text_field.setText(str(value))
+ init_value = (value - self.value_min)/self.tick_width
+ init_value = round(init_value,0)
+
+ self.slider.valueChanged.disconnect()
+ self.slider.sliderPressed.disconnect()
+ self.slider.setValue(init_value)
+ self.slider.valueChanged.connect(self.value_changed)
+ self.slider.sliderPressed.connect(self.clicked)
\ No newline at end of file
diff --git a/papi/plugin/pcp/slider/Slider.yapsy-plugin b/papi/plugin/pcp/slider/Slider.yapsy-plugin
index ff2ff09e..4f02c194 100644
--- a/papi/plugin/pcp/slider/Slider.yapsy-plugin
+++ b/papi/plugin/pcp/slider/Slider.yapsy-plugin
@@ -6,4 +6,6 @@ Module = Slider
Author = S.K.
Version = 0.1
Website = www
-Description = simple slider for parameters
\ No newline at end of file
+Description = simple slider for parameters
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/pcp/slider/box.png b/papi/plugin/pcp/slider/box.png
new file mode 100644
index 00000000..76d1daa3
Binary files /dev/null and b/papi/plugin/pcp/slider/box.png differ
diff --git a/papi/plugin/templates/IOP_DPP_template.py b/papi/plugin/templates/IOP_DPP_template.py
index a8222999..b9b17682 100644
--- a/papi/plugin/templates/IOP_DPP_template.py
+++ b/papi/plugin/templates/IOP_DPP_template.py
@@ -31,6 +31,7 @@
# basic import for block and parameter structure
from papi.data.DPlugin import DBlock
from papi.data.DParameter import DParameter
+from papi.data.DSignal import DSignal
# one of them is not nedded!
# delete line when you decided for iop or dpp
@@ -47,14 +48,19 @@ def start_init(self, config=None):
# define vars, connect to rtai .....
# create a block object
- # self.block1 = DBlockself.__id__.,signal count,frequnce,'Blockname',['Signalname1','Signalname2'])
+ # self.block1 = DBlock('blockName')
+
+ #signal = DSignal('signalName')
+
+ #self.block1.add_signal(signal)
+
# send block list
# self.send_new_block_list([block1, block2, block3])
# create a parameter object
- # self.para1 = DParameter('type','ParameterName',InitWert,RangeArray,1)
- # self.para2 = DParameter('type','ParameterName',InitWert,RangeArray,1)
+ # self.para1 = DParameter('ParameterName',default=0)
+ # self.para2 = DParameter('ParameterName',default=0)
# build parameter list to send to Core
# para_list = [self.para1 self.para2]
@@ -82,7 +88,7 @@ def resume(self):
# e.a. reopen communication ports, files etc.
pass
- def execute(self, Data=None, block_name = None):
+ def execute(self, Data=None, block_name = None, plugin_uname = None):
# Do main work here!
# If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data
# If this plugin is a DPP, then it will get Data with data
@@ -94,8 +100,9 @@ def execute(self, Data=None, block_name = None):
# Data could have multiple types stored in it e.a. Data['d1'] = int, Data['d2'] = []
# implement execute and send new data
- # self.send_new_data(time_vector, [ data1 data2 ... dataN ], 'block_name')
+ # self.send_new_data('blockName', timeVector, signals_to_send )
# Attention: block_name has to match the name defined in start_init for the specific block
+ # signals_to_send need to be a dict with "signalName->values"
pass
@@ -136,7 +143,8 @@ def get_plugin_configuration(self):
def plugin_meta_updated(self):
"""
- Whenever the meta information is updated this function is called (if implemented).
+ Whenever the meta information is updated this function is called.
+ If this function is called there is no guarantee anymore that previous used reference are still used.
:return:
"""
diff --git a/papi/plugin/templates/visual_template.py b/papi/plugin/templates/visual_template.py
index 45e8ec6f..5d3bd5a4 100644
--- a/papi/plugin/templates/visual_template.py
+++ b/papi/plugin/templates/visual_template.py
@@ -1,40 +1,6 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
-"""
-Copyright (C) 2014 Technische Universität Berlin,
-Fakultät IV - Elektrotechnik und Informatik,
-Fachgebiet Regelungssysteme,
-Einsteinufer 17, D-10587 Berlin, Germany
-
-This file is part of PaPI.
-
-PaPI is free software: you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-PaPI is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU Lesser General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with PaPI. If not, see .
-
-Contributors:
-.
+
+Contributors:
+= len(self.boxes) :
+ return
+
+ boxPlugin = self.boxes[boxIndex]['boxPlugin']
+ boxBlock = self.boxes[boxIndex]['boxBlock']
+ boxSignal = self.boxes[boxIndex]['boxSignal']
+ # --------------------
+ # Remove old items
+ # --------------------
+ # for index in range(boxBlock.count()):
+ # print('del index' + str(index))
+ # boxBlock.removeItem(index)
+
+ boxBlock.model().clear()
+ boxSignal.model().clear()
+
+ dplugin_name = boxPlugin.currentText()
+
+ dplugin = self.api.get_dplugin_by_uname(dplugin_name)
+
+ if dplugin is not None:
+
+ for dblock_name in dplugin.get_dblocks():
+ dblock = dplugin.get_dblock_by_name(dblock_name)
+ boxBlock.addItem(dblock.name)
+
+
+ def selection_changed_block(self, index, boxIndex, item):
+ boxPlugin = self.boxes[boxIndex]['boxPlugin']
+ boxBlock = self.boxes[boxIndex]['boxBlock']
+ boxSignal = self.boxes[boxIndex]['boxSignal']
+
+ boxSignal.model().clear()
+
+ dplugin_name = boxPlugin.currentText()
+ dblock_name = boxBlock.currentText()
+
+ dplugin = self.api.get_dplugin_by_uname(dplugin_name)
+
+ if dplugin is not None:
+
+ dblock = dplugin.get_dblock_by_name(dblock_name)
+
+ if dblock is not None:
+
+ if len(dblock.get_signals()):
+ for signal in dblock.get_signals():
+ boxSignal.addItem(signal.uname)
+ else:
+ item.save[boxIndex] = None
+ else:
+ item.save[boxIndex] = None
+ else:
+ item.save[boxIndex] = None
+
+ #item.save[boxIndex] = 'QUARK'
+ def selection_changed_signal(self, index, boxIndex, item):
+ boxPlugin = self.boxes[boxIndex]['boxPlugin']
+ boxBlock = self.boxes[boxIndex]['boxBlock']
+ boxSignal = self.boxes[boxIndex]['boxSignal']
+
+
+ dplugin_name = boxPlugin.currentText()
+ dblock_name = boxBlock.currentText()
+ dsignal_name = boxSignal.currentText()
+
+
+ item.save[boxIndex] = dplugin_name + "::" + dblock_name + "::" + dsignal_name
+
+ print(item.save)
+
+class CRC(vip_base):
+
+
+ def initiate_layer_0(self, config=None):
+
+
+ self.widget = CreateRecordingConfig(self.control_api)
+
+ self.set_widget_for_internal_usage( self.widget )
+
+
+ # ---------------------------
+ # Create Parameters
+ # ---------------------------
+ para_list = []
+
+ # build parameter list to send to Core
+ self.send_new_parameter_list(para_list)
+
+
+ return True
+
+ def show_context_menu(self, pos):
+ gloPos = self.widget.mapToGlobal(pos)
+ self.cmenu = self.create_control_context_menu()
+ self.cmenu.exec_(gloPos)
+
+ def pause(self):
+ # will be called, when plugin gets paused
+ # can be used to get plugin in a defined state before pause
+ # e.a. close communication ports, files etc.
+ pass
+
+ def resume(self):
+ # will be called when plugin gets resumed
+ # can be used to wake up the plugin from defined pause state
+ # e.a. reopen communication ports, files etc.
+ pass
+
+ def execute(self, Data=None, block_name = None):
+ # Do main work here!
+ # If this plugin is an IOP plugin, then there will be no Data parameter because it wont get data
+ # If this plugin is a DPP, then it will get Data with data
+
+ # param: Data is a Data hash and block_name is the block_name of Data origin
+ # Data is a hash, so use ist like: Data['t'] = [t1, t2, ...] where 't' is a signal_name
+ # hash signal_name: value
+
+ # Data could have multiple types stored in it e.a. Data['d1'] = int, Data['d2'] = []
+
+ pass
+
+ def set_parameter(self, name, value):
+ pass
+
+ def quit(self):
+ # do something before plugin will close, e.a. close connections ...
+ pass
+
+
+ def get_plugin_configuration(self):
+ #
+ # Implement a own part of the config
+ # config is a hash of hass object
+ # config_parameter_name : {}
+ # config[config_parameter_name]['value'] NEEDS TO BE IMPLEMENTED
+ # configs can be marked as advanced for create dialog
+ # http://utilitymill.com/utility/Regex_For_Range
+ config = {}
+ return config
+
+ def plugin_meta_updated(self):
+ """
+ Whenever the meta information is updated this function is called (if implemented).
+
+ :return:
+ """
+ pass
+
+class CustomField():
+ def __init__(self, desc='Data::force::sensor', size='3'):
+ self.desc = desc
+ self.size = size
+ if size is not '':
+ self.save = [None] * int(size)
+
+class CustomFieldItem(QStandardItem):
+ def __init__(self, custom_field):
+ super().__init__()
+
+ self.object = custom_field
+
+ def get_decoration(self):
+ return None
+
+ def data(self, role):
+ """
+ For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum'
+ :param role:
+ :return:
+ """
+
+ if role == Qt.ToolTipRole:
+ return None
+
+ if role == Qt.DisplayRole:
+ return None
+
+ if role == Qt.DecorationRole:
+ return self.get_decoration()
+
+ if role == Qt.UserRole:
+ return self.object
+
+ if role == Qt.EditRole:
+ return None
+
+ return None
+
+class CustomTreeItem(QStandardItem):
+ def __init__(self, custom_field):
+ super().__init__()
+
+ self.object = custom_field
+
+ def get_decoration(self):
+ return None
+
+ def data(self, role):
+ """
+ For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum'
+ :param role:
+ :return:
+ """
+
+ if role == Qt.ToolTipRole:
+ return self.tool_tip
+
+ if role == Qt.DisplayRole:
+ return self.object.desc
+
+ if role == Qt.DecorationRole:
+ return self.get_decoration()
+
+ if role == Qt.UserRole:
+ return self.object
+
+ if role == Qt.EditRole:
+ return None
+
+ return None
+
+class CustomFieldModel(QStandardItemModel):
+ def __init__(self, parent=None):
+ super(CustomFieldModel, self).__init__(parent)
+
+ def data(self, index, role):
+ """
+ For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum'
+ :param index:
+ :param role:
+ :return:
+ """
+
+ if not index.isValid():
+ return None
+
+ row = index.row()
+ col = index.column()
+
+ if role == Qt.ToolTipRole:
+ return super(CustomFieldModel, self).data(index, Qt.ToolTipRole)
+
+ if role == Qt.DisplayRole:
+
+ if col == 0:
+ field = super(CustomFieldModel, self).data(index, Qt.UserRole)
+ return field.desc
+ if col == 1:
+ index_sibling = index.sibling(row, col-1)
+ field = super(CustomFieldModel, self).data(index_sibling, Qt.UserRole)
+ return field.size
+ if col == 2:
+ return None
+
+ if role == Qt.DecorationRole:
+ if col == 2:
+ return None
+
+ if role == Qt.UserRole:
+ pass
+
+ if role == Qt.EditRole:
+ if col == 0:
+ field = super(CustomFieldModel, self).data(index, Qt.UserRole)
+ return field.desc
+ if col == 1:
+ index_sibling = index.sibling(row, col-1)
+ field = super(CustomFieldModel, self).data(index_sibling, Qt.UserRole)
+ return field.size
+ if col == 2:
+ return None
+
+ return None
+
+ def setData(self, index, value, role):
+ """
+ This function is called when a content in a row is edited by the user.
+
+ :param index: Current selected index.
+ :param value: New value from user
+ :param role:
+ :return:
+ """
+
+ if not index.isValid():
+ return None
+
+ row = index.row()
+ col = index.column()
+
+ if role == Qt.EditRole:
+
+ if col == 0:
+
+ field = super(CustomFieldModel, self).data(index, Qt.UserRole)
+ field.desc = value
+ self.dataChanged.emit(index, None)
+
+ return True
+
+ if col == 1:
+ index_sibling = index.sibling(row, col-1)
+ field = super(CustomFieldModel, self).data(index_sibling, Qt.UserRole)
+ field.size = value
+ self.dataChanged.emit(index, None)
+
+ return False
+
+ def flags(self, index):
+ row = index.row()
+ col = index.column()
+
+ if col == 2:
+ return ~Qt.ItemIsEditable & ~Qt.ItemIsSelectable
+
+ return Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemIsEditable
+
+class StructTreeModel(PaPITreeModel):
+ """
+ This model is used to handle Plugin objects in TreeView created by the yapsy plugin manager.
+ """
+ def __init__(self, parent=None):
+ super(StructTreeModel, self).__init__(parent)
+
+ def data(self, index, role):
+ """
+ For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum'
+ :param index:
+ :param role:
+ :return:
+ """
+
+ if not index.isValid():
+ return None
+
+ row = index.row()
+ col = index.column()
+
+ if role == Qt.ToolTipRole:
+ return super(StructTreeModel, self).data(index, Qt.ToolTipRole)
+
+ if role == Qt.DisplayRole:
+
+ if col == 0:
+ return super(StructTreeModel, self).data(index, Qt.DisplayRole)
+
+ if col == 1:
+ index_sibling = index.sibling(row, col-1)
+
+ item = super(StructTreeModel, self).data(index_sibling, Qt.UserRole)
+ if item is not None:
+ return item.size
+ if col == 2:
+ return None
+
+ if role == Qt.DecorationRole:
+ pass
+
+ if role == Qt.UserRole:
+ if col == 0:
+ return super(StructTreeModel, self).data(index, Qt.UserRole)
+ if col == 1:
+ index_sibling = index.sibling(row, col-1)
+ return super(StructTreeModel, self).data(index_sibling, Qt.UserRole)
+ if role == Qt.EditRole:
+ pass
+
+ return None
+
+ def flags(self, index):
+ if not index.isValid():
+ return None
+
+ row = index.row()
+ col = index.column()
+ flags = Qt.ItemIsEnabled & ~Qt.ItemIsSelectable
+
+ if col == 0:
+ item = super(StructTreeModel, self).data(index, Qt.UserRole)
+
+ if item is not None:
+ if item.size != '':
+ flags |= Qt.ItemIsSelectable
+
+ if col == 1:
+ index_sibling = index.sibling(row, col-1)
+ item = super(StructTreeModel, self).data(index_sibling, Qt.UserRole)
+ if item is not None:
+ if item.size != '':
+ flags |= Qt.ItemIsSelectable
+
+ if col == 2:
+ return None
+
+ return flags
+
+class StructTreeNode(QStandardItem):
+ """
+ This model is used to handle Plugin objects in TreeView created by the yapsy plugin manager.
+ """
+ def __init__(self, field, level, flags=~Qt.ItemIsEnabled, parent=None):
+
+ elements = str.split(field.desc, "::")
+
+ self.nodes = {}
+ self.level = level
+ self.time_identifier = 'time'
+ self.ptime_identifier = 'ptime'
+ self.size = ''
+ self.field = ''
+ self.object = field
+ self.flags = flags
+
+ super(StructTreeNode, self).__init__(elements[self.level])
+
+ self.appendRow(field)
+
+ def appendRow(self, field):
+
+ elements = str.split(field.desc, "::")
+
+ # Node is responsible for the first element
+
+ if len(elements) > self.level + 1:
+
+ if self.time_identifier not in self.nodes:
+
+ struct_node = QStandardItem(self.time_identifier)
+# struct_node = StructTreeNode(CustomField(desc=self.time_identifier, size=''), 0)
+ struct_node.setColumnCount(2)
+
+ self.nodes[self.time_identifier] = struct_node
+ super(StructTreeNode, self).appendRow(struct_node)
+
+ if self.ptime_identifier not in self.nodes:
+ struct_node = QStandardItem(self.ptime_identifier)
+ #struct_node = StructTreeNode(CustomField(desc=self.ptime_identifier, size=''), 0)
+ struct_node.setColumnCount(2)
+
+ self.nodes[self.ptime_identifier] = struct_node
+ super(StructTreeNode, self).appendRow(struct_node)
+
+ if elements[self.level + 1] not in self.nodes:
+ struct_node = StructTreeNode(field, self.level + 1)
+ struct_node.setColumnCount(2)
+
+ self.nodes[elements[self.level + 1]] = struct_node
+ super(StructTreeNode, self).appendRow(struct_node)
+
+ self.field = elements[self.level]
+
+ if elements[self.level + 1] in self.nodes:
+ self.nodes[elements[self.level + 1]].appendRow(field)
+
+ self.object = None
+ else:
+ self.field = elements[self.level]
+
+ def data(self, role):
+ """
+ For Qt.Role see 'http://qt-project.org/doc/qt-4.8/qt.html#ItemDataRole-enum'
+ :param role:
+ :return:
+ """
+ if role == Qt.ToolTipRole:
+ return None
+
+ if role == Qt.DisplayRole:
+ return self.field
+
+ if role == Qt.DecorationRole:
+ return None
+
+ if role == Qt.UserRole:
+ return self.object
+
+ if role == Qt.EditRole:
+ return None
+
+ return None
+
+ def flags(self, index):
+ return self.flags
+
+class CustomFieldDelegate (QStyledItemDelegate):
+ def paint(self, painter, option, index):
+
+ if index.column() == 2:
+
+ button = QStyleOptionButton()
+ r = option.rect # getting the rect of the cell
+
+ x = r.left()
+ y = r.top()
+ w = r.width()
+ h = r.height()
+ button.rect = QRect(x, y, w, h)
+ button.text = "X"
+ button.state = QStyle.State_Enabled;
+
+ QApplication.style().drawControl(QStyle.CE_PushButton, button, painter)
+ else:
+ QStyledItemDelegate.paint(self, painter, option, index)
\ No newline at end of file
diff --git a/papi/plugin/visual/CRC/CRC.yapsy-plugin b/papi/plugin/visual/CRC/CRC.yapsy-plugin
new file mode 100644
index 00000000..9c20f3a4
--- /dev/null
+++ b/papi/plugin/visual/CRC/CRC.yapsy-plugin
@@ -0,0 +1,9 @@
+[Core]
+Name = CRC
+Module = CRC
+
+[Documentation]
+Author = S. Knuth
+Version = 0.1
+Website =
+Description = Used to create the configuration for the recording tool.
\ No newline at end of file
diff --git a/papi/plugin/visual/HTMLViewer/HTMLViewer.py b/papi/plugin/visual/HTMLViewer/HTMLViewer.py
new file mode 100644
index 00000000..dae00808
--- /dev/null
+++ b/papi/plugin/visual/HTMLViewer/HTMLViewer.py
@@ -0,0 +1,133 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2014 Technische Universität Berlin,
+Fakultät IV - Elektrotechnik und Informatik,
+Fachgebiet Regelungssysteme,
+Einsteinufer 17, D-10587 Berlin, Germany
+
+This file is part of PaPI.
+
+PaPI is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+PaPI is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with PaPI. If not, see .
+
+Contributors:
+ Insert your html code here """,
+ 'display_text' : 'HTML Content',
+ 'advanced' : '0',
+ 'tooltip' : 'Plain html code to be displayed'
+ }}
+
+ return config
+
+ def plugin_meta_updated(self):
+ """
+ Whenever the meta information is updated this function is called (if implemented).
+
+ :return:
+ """
+
+ #dplugin_info = self.dplugin_info
+ pass
diff --git a/papi/plugin/visual/HTMLViewer/HTMLViewer.yapsy-plugin b/papi/plugin/visual/HTMLViewer/HTMLViewer.yapsy-plugin
new file mode 100644
index 00000000..b6dd4b05
--- /dev/null
+++ b/papi/plugin/visual/HTMLViewer/HTMLViewer.yapsy-plugin
@@ -0,0 +1,11 @@
+[Core]
+Name = HTMLViewer
+Module = HTMLViewer
+
+[Documentation]
+Author = S. Ruppin
+Version = 1.0
+Website =
+Description = HTMLViewer for displaying HTML
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/visual/HTMLViewer/box.png b/papi/plugin/visual/HTMLViewer/box.png
new file mode 100644
index 00000000..98cff2a2
Binary files /dev/null and b/papi/plugin/visual/HTMLViewer/box.png differ
diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.py b/papi/plugin/visual/LCDDisplay/LCDDisplay.py
new file mode 100644
index 00000000..48110dc1
--- /dev/null
+++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.py
@@ -0,0 +1,223 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2014 Technische Universität Berlin,
+Fakultät IV - Elektrotechnik und Informatik,
+Fachgebiet Regelungssysteme,
+Einsteinufer 17, D-10587 Berlin, Germany
+
+This file is part of PaPI.
+
+PaPI is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+PaPI is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with PaPI. If not, see .
+
+Contributors:
+ self.time_treshold:
+ self.last_time = cur_time
+ t = Data['t']
+ keys = list(Data.keys())
+ keys.sort()
+ if Data[keys[0]] != 't':
+ y = Data[keys[0]][-1]
+ else:
+ if len(keys) > 1:
+ y = Data[keys[1]][-1]
+
+ y = y*self.value_scale + self.value_offset
+ self.LcdWidget.display(y)
+
+
+
+ def set_parameter(self, name, value):
+ # attetion: value is a string and need to be processed !
+ # if name == 'irgendeinParameter':
+ # do that .... with value
+ if name == self.para_treshold.name:
+ self.time_treshold = int(value)
+ self.config['updateFrequency']['value'] = value
+
+ if name == self.para_value_scale.name:
+ self.config[self.para_value_scale.name]['value'] = value
+ self.value_scale = float(value)
+
+ if name == self.para_value_offset.name:
+ self.config[self.para_value_offset.name]['value'] = value
+ self.value_offset = float(value)
+
+ if name == self.para_digit_count.name:
+ self.config[self.para_digit_count.name]['value'] = value
+ self.digit_count = int(value)
+ self.LcdWidget.setDigitCount(self.digit_count)
+
+
+
+ def quit(self):
+ # do something before plugin will close, e.a. close connections ...
+ pass
+
+
+ def get_plugin_configuration(self):
+ #
+ # Implement a own part of the config
+ # config is a hash of hass object
+ # config_parameter_name : {}
+ # config[config_parameter_name]['value'] NEEDS TO BE IMPLEMENTED
+ # configs can be marked as advanced for create dialog
+ # http://utilitymill.com/utility/Regex_For_Range
+ config = {
+ "updateFrequency": {
+ 'value': '1000',
+ 'regex': '[0-9]+',
+ 'display_text' : 'Minimal time between updates (in ms)',
+ 'advanced' : '1'
+ }, 'size': {
+ 'value': "(150,75)",
+ 'regex': '\(([0-9]+),([0-9]+)\)',
+ 'advanced': '1',
+ 'tooltip': 'Determine size: (height,width)'
+ }, 'name': {
+ 'value': 'LCD',
+ 'tooltip': 'Used for window title'
+ }, 'value_init': {
+ 'value': 0,
+ 'regex' : pc.REGEX_SIGNED_FLOAT_OR_INT,
+ 'tooltip': 'Used as initial value for the LCD-Display'
+ }, 'value_scale': {
+ 'value': '1',
+ 'tooltip': 'Used to scale displayed value',
+ 'regex': '-?[1-9]+[0-9]*(\.?[0-9]+)?',
+ 'advanced': '1'
+ }, 'value_offset': {
+ 'value': '0',
+ 'tooltip': 'Used to offset displayed value',
+ 'regex': '-?\d+(\.?\d+)?',
+ 'advanced': '1'
+ }, 'digit_count': {
+ 'value': '3',
+ 'tooltip': 'Number of digits',
+ 'regex': '[3-9]',
+ 'advanced': '1'
+ }
+ }
+ return config
+
+ def plugin_meta_updated(self):
+ """
+ Whenever the meta information is updated this function is called (if implemented).
+
+ :return:
+ """
+
+ #dplugin_info = self.dplugin_info
+ pass
diff --git a/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin
new file mode 100644
index 00000000..272ea68f
--- /dev/null
+++ b/papi/plugin/visual/LCDDisplay/LCDDisplay.yapsy-plugin
@@ -0,0 +1,11 @@
+[Core]
+Name = LCDDisplay
+Module = LCDDisplay
+
+[Documentation]
+Author = S. Ruppin
+Version = 1.0
+Website =
+Description = LCD Display for displaying floating point numbers up to 5 digits with sign. Refresh rate is user defined.
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/visual/LCDDisplay/box.png b/papi/plugin/visual/LCDDisplay/box.png
new file mode 100644
index 00000000..f0846716
Binary files /dev/null and b/papi/plugin/visual/LCDDisplay/box.png differ
diff --git a/papi/plugin/visual/OrtdController/OrtdController.py b/papi/plugin/visual/OrtdController/OrtdController.py
new file mode 100644
index 00000000..c94dbab8
--- /dev/null
+++ b/papi/plugin/visual/OrtdController/OrtdController.py
@@ -0,0 +1,352 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2014 Technische Universität Berlin,
+Fakultät IV - Elektrotechnik und Informatik,
+Fachgebiet Regelungssysteme,
+Einsteinufer 17, D-10587 Berlin, Germany
+
+This file is part of PaPI.
+
+PaPI is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+PaPI is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with PaPI. If not, see .
+
+Contributors:
+ 0:
+ Data = self.event_list.pop(0)
+ ############################
+ # Reset Signal #
+ ############################
+ if 'ControlSignalReset' in Data:
+ val = Data['ControlSignalReset']
+ if val == 1:
+ while len(self.plugin_started_list):
+ pl_uname = self.plugin_started_list.pop()
+ self.control_api.do_delete_plugin_uname(pl_uname)
+ time.sleep(0.5)
+
+ ############################
+ # Create Plugins #
+ ############################
+ if 'ControlSignalCreate' in Data:
+ cfg = Data['ControlSignalCreate']
+ if cfg is not None:
+ for pl_uname in cfg:
+ if pl_uname not in self.plugin_started_list:
+ pl_cfg = cfg[pl_uname]
+ self.control_api.do_create_plugin(pl_cfg['identifier']['value'],pl_uname, pl_cfg['config'])
+ self.plugin_started_list.append(pl_uname)
+
+
+ ############################
+ # Create Subs #
+ ############################
+ if 'ControlSignalSub' in Data:
+ cfg = Data['ControlSignalSub']
+ if cfg is not None:
+ for pl_uname in cfg:
+ pl_cfg = cfg[pl_uname]
+ sig = []
+ if 'signals' in pl_cfg:
+ sig = pl_cfg['signals']
+ self.control_api.do_subscribe_uname(pl_uname,self.ortd_uname, pl_cfg['block'], signals=sig, sub_alias= None)
+
+ ############################
+ # Set parameter links #
+ ############################
+ if 'ControllerSignalParameter' in Data:
+ cfg = Data['ControllerSignalParameter']
+ if cfg is not None:
+ for pl_uname in cfg:
+ pl_cfg = cfg[pl_uname]
+ para = None
+ if 'parameter' in pl_cfg:
+ para = pl_cfg['parameter']
+ self.control_api.do_subscribe_uname(self.ortd_uname,pl_uname, pl_cfg['block'], signals=[], sub_alias= para)
+
+ ############################
+ # Close plugin #
+ ############################
+ if 'ControllerSignalClose' in Data:
+ cfg = Data['ControllerSignalClose']
+ if cfg is not None:
+ for pl_uname in cfg:
+ if pl_uname in self.plugin_started_list:
+ self.control_api.do_delete_plugin_uname(pl_uname)
+ self.plugin_started_list.remove(pl_uname)
+
+
+ # wait before set active tab
+ time.sleep(1.0)
+ if 'ActiveTab' in Data:
+ cfg = Data['ActiveTab']
+ if cfg is not None:
+ tabName = cfg
+ self.control_api.do_set_tab_active_by_name(tabName)
+
+ self.thread_alive = False
+
+ def set_parameter(self, name, value):
+ # attetion: value is a string and need to be processed !
+ # if name == 'irgendeinParameter':
+ # do that .... with value
+ pass
+
+ def quit(self):
+ # do something before plugin will close, e.a. close connections ...
+ pass
+
+ def get_plugin_configuration(self):
+ #
+ # Implement a own part of the config
+ # config is a hash of hass object
+ # config_parameter_name : {}
+ # config[config_parameter_name]['value'] NEEDS TO BE IMPLEMENTED
+ # configs can be marked as advanced for create dialog
+ # http://utilitymill.com/utility/Regex_For_Range
+ config = {
+ "ORTD_Plugin_uname": {
+ 'value': 'ORTDPlugin1',
+ 'display_text': 'Uname to use for ortd plugin instance',
+ 'advanced': "0"
+ },
+ 'name': {
+ 'value': 'ORTDController'
+ }
+ }
+ return config
+
+ def plugin_meta_updated(self):
+ """
+ Whenever the meta information is updated this function is called (if implemented).
+
+ :return:
+ """
+
+ #dplugin_info = self.dplugin_info
+ pass
+
+
+
+class ControllerIntroPage(QtGui.QWizardPage):
+ def __init__(self,parent = None):
+ QtGui.QWizardPage.__init__(self, parent)
+ self.setTitle("ORTD Controller")
+ label = QtGui.QLabel("This is the ORTD Controller plugin.")
+ label.setWordWrap(True)
+
+ label2 = QtGui.QLabel("Click next to start the configuration")
+
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(label)
+ layout.addWidget(label2)
+
+ self.setLayout(layout)
+
+ def validatePage(self):
+ print('intro: next clicked')
+ return True
+
+
+class ControllerOrtdStart(QtGui.QWizardPage):
+ def __init__(self,api = None, uname= None, parent = None, ortd_uname = None):
+ QtGui.QWizardPage.__init__(self, parent)
+ self.uname = uname
+ self.api = api
+ self.ortd_uname = ortd_uname
+ #self.setTitle("ORTD Controller")
+ label = QtGui.QLabel("Please configure the ORTD plugin.")
+ label.setWordWrap(True)
+
+ # ----------- #
+ # IP line edit#
+ # ----------- #
+ self.ip_line_edit = QtGui.QLineEdit()
+ ip_label = QtGui.QLabel('IP-Address:')
+ ip_label.setBuddy(self.ip_line_edit)
+ regex = '\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}'
+ rx = QtCore.QRegExp(regex)
+ validator = QRegExpValidator(rx, self)
+ self.ip_line_edit.setValidator(validator)
+ self.ip_line_edit.setText('127.0.0.1')
+
+ # ----------- #
+ # Port line edit recv#
+ # ----------- #
+ self.port_line_edit = QtGui.QLineEdit()
+ port_label = QtGui.QLabel('Port:')
+ port_label.setBuddy(self.port_line_edit)
+ regex = '\d{1,5}'
+ rx = QtCore.QRegExp(regex)
+ validator = QRegExpValidator(rx, self)
+ self.port_line_edit.setValidator(validator)
+ self.port_line_edit.setText('20000')
+
+ # ----------- #
+ # Port line edit send#
+ # ----------- #
+ self.port_send_line_edit = QtGui.QLineEdit()
+ port_send_label = QtGui.QLabel('Port Send:')
+ port_send_label.setBuddy(self.port_send_line_edit)
+ regex = '\d{1,5}'
+ rx = QtCore.QRegExp(regex)
+ validator = QRegExpValidator(rx, self)
+ self.port_send_line_edit.setValidator(validator)
+ self.port_send_line_edit.setText('20001')
+
+
+
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(label)
+ layout.addWidget(ip_label)
+ layout.addWidget(self.ip_line_edit)
+ layout.addWidget(port_label)
+ layout.addWidget(self.port_line_edit)
+ layout.addWidget(port_send_label)
+ layout.addWidget(self.port_send_line_edit)
+
+ self.setLayout(layout)
+
+ def validatePage(self):
+ IP = self.ip_line_edit.text()
+ port = self.port_line_edit.text()
+ port_send = self.port_send_line_edit.text()
+ cfg ={
+ 'address': {
+ 'value': IP,
+ 'advanced': '1'
+ },
+ 'source_port': {
+ 'value': port,
+ 'advanced': '1'
+ },
+ 'out_port': {
+ 'value': port_send,
+ 'advanced': '1'
+ }
+ }
+ self.api.do_create_plugin('ORTD_UDP', self.ortd_uname, cfg, True)
+
+ self.thread = threading.Thread(target=self.subscribe_control_signal)
+ self.thread.start()
+
+ return True
+
+ def subscribe_control_signal(self):
+
+ self.api.do_subscribe_uname(self.uname,self.ortd_uname, 'ControllerSignals', signals=['ControlSignalReset',
+ 'ControlSignalCreate',
+ 'ControlSignalSub',
+ 'ControllerSignalParameter',
+ 'ControllerSignalClose',
+ 'ActiveTab'])
+ self.api.do_set_parameter_uname(self.ortd_uname, 'triggerConfiguration', '1')
+
+
+class ControllerWorking(QtGui.QWizardPage):
+ def __init__(self,api = None, uname= None, parent = None):
+ QtGui.QWizardPage.__init__(self, parent)
+ self. api = api
+
+ self.setTitle("ORTD Controller")
+ label = QtGui.QLabel("Controller plugin is working")
+ label.setWordWrap(True)
+
+ label2 = QtGui.QLabel("")
+
+ layout = QtGui.QVBoxLayout()
+ layout.addWidget(label)
+ layout.addWidget(label2)
+
+ self.setLayout(layout)
+
+ def validatePage(self):
+ return False
diff --git a/papi/plugin/visual/OrtdController/OrtdController.yapsy-plugin b/papi/plugin/visual/OrtdController/OrtdController.yapsy-plugin
new file mode 100644
index 00000000..f1d8dcb2
--- /dev/null
+++ b/papi/plugin/visual/OrtdController/OrtdController.yapsy-plugin
@@ -0,0 +1,11 @@
+[Core]
+Name = OrtdController
+Module = OrtdController
+
+[Documentation]
+Author = S.R.
+Version = 0.1
+Website =
+Description = Plugin to setup a ORTD source and controll plugin creation from ortd source.
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/visual/OrtdController/box.png b/papi/plugin/visual/OrtdController/box.png
new file mode 100644
index 00000000..fd281bbc
Binary files /dev/null and b/papi/plugin/visual/OrtdController/box.png differ
diff --git a/papi/plugin/visual/Plot/Plot.py b/papi/plugin/visual/Plot/Plot.py
index f26237c6..6ceb71f4 100644
--- a/papi/plugin/visual/Plot/Plot.py
+++ b/papi/plugin/visual/Plot/Plot.py
@@ -28,19 +28,24 @@
__author__ = 'Stefan'
-import pyqtgraph as pq
+import papi.pyqtgraph as pq
from papi.plugin.base_classes.vip_base import vip_base
from papi.data.DParameter import DParameter
+from papi.data.DSignal import DSignal
import numpy as np
import collections
import re
+import copy
import time
+import papi.pyqtgraph as pg
+import papi.constants as pc
current_milli_time = lambda: int(round(time.time() * 1000))
-from pyqtgraph.Qt import QtCore
-
+from papi.pyqtgraph.Qt import QtCore, QtGui
+from PySide.QtGui import QRegExpValidator
+from PySide.QtCore import *
class Plot(vip_base):
"""
@@ -59,7 +64,7 @@ class Plot(vip_base):
4 : (100, 100, 100)
"""
- def __init__(self):
+ def __init__(self, debug=False):
super(Plot, self).__init__()
"""
Function init
@@ -70,12 +75,13 @@ def __init__(self):
self.signals = {}
+ self.__papi_debug__ = debug
self.__buffer_size__ = None
self.__downsampling_rate__ = 1
self.__tbuffer__ = []
- self.__tdata_old__ = [0]
+
self.__signals_have_same_length = True
- self.__roll_shift__ = None
+
self.__append_at__ = 1
self.__new_added_data__ = 0
self.__input_size__ = 0
@@ -87,11 +93,19 @@ def __init__(self):
self.__parameters__ = {}
self.__update_intervall__ = None
self.__last_time__ = None
+ self.__last_plot_time__ = None
self.__plotWidget__ = None
self.__legend__ = None
- self.__text_item__ = None
- self.__vertical_line__ = None
- self.__offset_line__ = None
+
+ self.__stp_min_x = None
+ self.__stp_max_x = None
+
+ self.__stp_min_y = None
+ self.__stp_max_y = None
+ self.__stp_active__ = None
+
+ self.__downsampling_rate_start__ = None;
+ self.__downsampling_rate__ = None
self.styles = {
0: QtCore.Qt.SolidLine,
@@ -132,50 +146,130 @@ def initiate_layer_0(self, config=None):
self.__buffer_size__ = int(int_re.findall(self.config['buffersize']['value'])[0])
self.__downsampling_rate__ = int(int_re.findall(self.config['downsampling_rate']['value'])[0])
+ self.__downsampling_rate_start__ = 0
# ----------------------------
- # Create internal variables
+ # Set internal variables
# ----------------------------
self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__)
+ # ----------------------------
+ # Set internal variables used for single timestamp plotting (stp)
+ # ----------------------------
+
+ self.__stp_min_x = 0
+ self.__stp_max_x = 0
+
+ self.__stp_min_y = 0
+ self.__stp_max_y = 1
+
+ self.__stp_active__ = False
+
+
# --------------------------------
- # Create PlotWidget
+ # Create Layout and labels
# --------------------------------
- self.__text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0))
- self.__vertical_line__ = pq.InfiniteLine()
+ self.central_widget = QtGui.QWidget()
+ self.central_widget.setContentsMargins(0,0,0,0)
- self.__plotWidget__ = pq.PlotWidget()
- self.__plotWidget__.addItem(self.__text_item__)
+ self.label_widget = QtGui.QWidget()
+ self.label_widget.setContentsMargins(0,0,0,0)
- if self.__rolling_plot__:
- self.__plotWidget__.addItem(self.__vertical_line__)
+ self.verticalLayout = QtGui.QVBoxLayout()
+ self.verticalLayout.setContentsMargins(0,0,0,0)
+ self.verticalLayout.setSpacing(0)
+
+ self.horizontalLayout = QtGui.QHBoxLayout()
+ self.horizontalLayout.setContentsMargins(0,0,0,0)
+ self.horizontalLayout.setSpacing(0)
+
+ self.space_label = QtGui.QLabel()
+ self.space_label.setMargin(0)
+ self.space_label.setAlignment(Qt.AlignJustify)
+ self.space_label.setStyleSheet("QLabel { background-color : black; color : grey;"
+ "border : 0px solid black ; border-bottom-width : 5px }")
+
+ self.time_label = QtGui.QLabel()
+ self.time_label.setMargin(0)
+ self.time_label.setAlignment(Qt.AlignJustify)
+ self.time_label.setStyleSheet("QLabel { background-color : black; color : grey;"
+ "border : 0px solid black ; border-bottom-width : 5px }")
+ self.time_label.setMaximumWidth(55)
+
+
+ self.unit_label = QtGui.QLabel()
+ self.unit_label.setMargin(0)
+ self.unit_label.setAlignment(Qt.AlignLeft)
+ self.unit_label.setStyleSheet("QLabel { background-color : black; color : grey;"
+ "border : 0px solid black ; border-bottom-width : 5px }")
+
+ self.unit_label.setText('[s]')
+
+ self.central_widget.setLayout(self.verticalLayout)
+ self.label_widget.setLayout(self.horizontalLayout)
+
+ # --------------------------------
+ # Create PlotWidget
+ # --------------------------------
+
+ self.__plotWidget__ = PlotWidget()
- self.__text_item__.setPos(0, 0)
self.__plotWidget__.setWindowTitle('PlotPerformanceTitle')
self.__plotWidget__.showGrid(x=self.__show_grid_x__, y=self.__show_grid_y__)
+ self.__plotWidget__.getPlotItem().getViewBox().disableAutoRange()
+ self.__plotWidget__.getPlotItem().getViewBox().setYRange(0,6)
+
+ self.__plotWidget__.getPlotItem().setDownsampling(auto=True)
+
+ # ------------------------------
+ # Add Widget to Layout
+ # ------------------------------
+ self.horizontalLayout.addWidget(self.space_label)
+ self.horizontalLayout.addWidget(self.time_label)
+ self.horizontalLayout.addWidget(self.unit_label)
+
+ self.verticalLayout.addWidget(self.__plotWidget__)
+ self.verticalLayout.addWidget(self.label_widget)
+
- self.set_widget_for_internal_usage(self.__plotWidget__)
+ if not self.__papi_debug__:
+# self.set_widget_for_internal_usage(self.__plotWidget__)
+ self.set_widget_for_internal_usage(self.central_widget)
+
+ self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.YAxis, enable=False)
+ self.__plotWidget__.getPlotItem().getViewBox().enableAutoRange(axis=pq.ViewBox.XAxis, enable=False)
+
+ self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True)
# ---------------------------
# Create Parameters
# ---------------------------
- self.__parameters__['x-grid'] = DParameter(None, 'x-grid', 0, [0, 1], 1, Regex='^(1|0){1}$')
- self.__parameters__['y-grid'] = DParameter(None, 'y-grid', 0, [0, 1], 1, Regex='^(1|0){1}$')
+ self.__parameters__['x-grid'] = \
+ DParameter('x-grid', self.config['x-grid']['value'], Regex='^(1|0){1}$')
+ self.__parameters__['y-grid'] = \
+ DParameter('y-grid', self.config['y-grid']['value'], Regex='^(1|0){1}$')
+
+ self.__parameters__['color'] = \
+ DParameter('color', self.config['color']['value'], Regex='^\[(\s*\d\s*)+\]')
+ self.__parameters__['style'] = \
+ DParameter('style', self.config['style']['value'], Regex='^\[(\s*\d\s*)+\]')
+ self.__parameters__['rolling'] = \
+ DParameter('rolling', self.config['rolling_plot']['value'], Regex='^(1|0){1}')
- self.__parameters__['color'] = DParameter(None, 'color', '[0 1 2 3 4]', [0, 1], 1, Regex='^\[(\s*\d\s*)+\]')
- self.__parameters__['style'] = DParameter(None, 'style', '[0 0 0 0 0]', [0, 1], 1, Regex='^\[(\s*\d\s*)+\]')
- self.__parameters__['rolling'] = DParameter(None, 'rolling', '0', [0, 1], 1, Regex='^(1|0){1}')
+ self.__parameters__['downsampling_rate'] = \
+ DParameter('downsampling_rate', self.__downsampling_rate__, Regex='^\d+$')
+ self.__parameters__['buffersize'] = \
+ DParameter('buffersize', self.__buffer_size__, Regex='^\d+$')
- self.__parameters__['downsampling_rate'] = DParameter(None, 'downsampling_rate', self.__downsampling_rate__,
- [1, 100],
- 1, Regex='^([1-9][0-9]?|100)$')
- self.__parameters__['buffersize'] = DParameter(None, 'buffersize', self.__buffer_size__, [1, 100],
- 1, Regex='^([1-9][0-9]{0,3}|10000)$')
- self.send_new_parameter_list(list(self.__parameters__.values()))
+ self.__parameters__['yRange'] = \
+ DParameter('yRange', self.config['yRange']['value'], Regex='^\[(\d+\.\d+)\s+(\d+\.\d+)\]$')
+
+ if not self.__papi_debug__:
+ self.send_new_parameter_list(list(self.__parameters__.values()))
# ---------------------------
# Create Legend
@@ -186,7 +280,20 @@ def initiate_layer_0(self, config=None):
self.__last_time__ = current_milli_time()
- self.__update_intervall__ = 25 # in milliseconds
+ self.__update_intervall__ = 20 # in milliseconds
+ self.__last_plot_time__ = 0
+
+ self.setup_context_menu()
+ self.__plotWidget__.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.__plotWidget__.customContextMenuRequested.connect(self.showContextMenu)
+
+ self.use_range_for_y(self.config['yRange']['value'])
+
+ # ----------------------------
+ # Initiate for default plotting
+ # ----------------------------
+
+ self.initiate_update_plot()
return True
@@ -196,7 +303,7 @@ def pause(self):
:return:
"""
- print('PlotPerformance paused')
+ self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=True, y=True)
def resume(self):
"""
@@ -204,9 +311,9 @@ def resume(self):
:return:
"""
- print('PlotPerformance resumed')
+ self.__plotWidget__.getPlotItem().getViewBox().setMouseEnabled(x=False, y=True)
- def execute(self, Data=None, block_name=None):
+ def execute(self, Data=None, block_name = None, plugin_uname = None):
"""
Function execute
@@ -214,29 +321,61 @@ def execute(self, Data=None, block_name=None):
:param block_name:
:return:
"""
+
t = Data['t']
self.__input_size__ = len(t)
- self.__tbuffer__.extend(t)
- self.__new_added_data__ += len(t)
+
self.__signals_have_same_length = True
+ now = pg.ptime.time()
+
for key in Data:
if key != 't':
y = Data[key]
if key in self.signals:
- buffer = self.signals[key]['buffer']
- buffer.extend(y)
- self.__signals_have_same_length &= (len(t) == len(y))
+ if self.__downsampling_rate_start__ < len(y):
+ ds_y = y[self.__downsampling_rate_start__::self.__downsampling_rate__]
+
+ self.signals[key].add_data(ds_y)
+
+ self.__signals_have_same_length &= (len(y) == len(t))
+
+ if self.__downsampling_rate_start__ >= len(t):
+ self.__downsampling_rate_start__ -= len(t)
+ else:
+ ds_t = t[self.__downsampling_rate_start__::self.__downsampling_rate__]
+ self.__downsampling_rate_start__ += self.__downsampling_rate__ - len(ds_t)
+ self.__tbuffer__.extend(ds_t)
+
+ self.__new_added_data__ += len(t)
+
+ self.rolling_Checkbox.setDisabled(self.__stp_active__)
if self.__input_size__ > 1 or self.__signals_have_same_length:
- if current_milli_time() - self.__last_time__ > self.__update_intervall__:
+
+ if self.__stp_active__:
+ self.initiate_update_plot()
+
+ if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__:
self.__last_time__ = current_milli_time()
self.update_plot()
self.__last_time__ = current_milli_time()
self.__new_added_data__ = 0
else:
- self.update_plot_single_timestamp(Data)
+
+ if not self.__stp_active__ :
+ self.initiate_update_plot_single_timestamp()
+
+ if current_milli_time() - self.__last_time__ > self.__update_intervall__ - self.__last_plot_time__:
+ self.__last_time__ = current_milli_time()
+
+ self.update_plot_single_timestamp(Data)
+
+ self.__last_time__ = current_milli_time()
+ self.__new_added_data__ = 0
+
+ # print("Plot time: %0.5f sec" % (self.__last_plot_time__) )
def set_parameter(self, name, value):
"""
@@ -249,42 +388,51 @@ def set_parameter(self, name, value):
if name == 'x-grid':
self.config['x-grid']['value'] = value
self.__plotWidget__.showGrid(x=value == '1')
+ self.xGrid_Checkbox.stateChanged.disconnect()
+ self.xGrid_Checkbox.setChecked(value=='1')
+ self.xGrid_Checkbox.stateChanged.connect(self.contextMenu_xGrid_toogle)
if name == 'y-grid':
self.config['y-grid']['value'] = value
self.__plotWidget__.showGrid(y=value == '1')
+ self.yGrid_Checkbox.stateChanged.disconnect()
+ self.yGrid_Checkbox.setChecked(value=='1')
+ self.yGrid_Checkbox.stateChanged.connect(self.contextMenu_yGrid_toogle)
if name == 'downsampling_rate':
self.config['downsampling_rate']['value'] = value
self.__downsampling_rate__ = int(value)
self.__new_added_data__ = 0
+ self.update_downsampling_rate()
if name == 'rolling':
- self.__rolling_plot__ = int(float(value)) == int('1')
+ self.clear()
self.config['rolling_plot']['value'] = value
-
- if self.__rolling_plot__:
- # if self.__vertical_line__ not in self.__plotWidget__.listDataItems():
-
- self.__plotWidget__.addItem(self.__vertical_line__)
-
-
+ self.update_rolling_plot()
if name == 'color':
+ self.clear()
self.config['color']['value'] = value
int_re = re.compile(r'(\d+)')
self.__colors_selected__ = int_re.findall(self.config['color']['value'])
self.update_pens()
+ self.update_legend()
if name == 'style':
+ self.clear()
self.config['style']['value'] = value
int_re = re.compile(r'(\d+)')
self.__styles_selected__ = int_re.findall(self.config['style']['value'])
self.update_pens()
+ self.update_legend()
if name == 'buffersize':
self.config['buffersize']['value'] = value
- self.set_buffer_size(value)
+ self.update_buffer_size(value)
+
+ if name == 'yRange':
+ self.config['yRange']['value'] = value
+ self.use_range_for_y(value)
def update_pens(self):
"""
@@ -294,11 +442,17 @@ def update_pens(self):
"""
for signal_name in self.signals.keys():
- signal_id = self.signals[signal_name]['id']
+ signal_id = self.signals[signal_name].id
new_pen = self.get_pen(signal_id)
+ other_pen = self.get_pen(signal_id)
+
+ o_color = other_pen.color()
+ o_color.setAlpha(100)
+ other_pen.setColor(o_color)
- self.signals[signal_name]['curve'].setPen(new_pen)
+ self.signals[signal_name].pen = new_pen
+ self.signals[signal_name].other_pen = other_pen
def update_plot(self):
"""
@@ -306,36 +460,72 @@ def update_plot(self):
:return:
"""
- shift_data = 0
+ if len(self.__tbuffer__) == 0:
+ return
- for last_tvalue in self.__tdata_old__:
- if last_tvalue in self.__tbuffer__:
- shift_data = list(self.__tbuffer__).index(last_tvalue)
- break
-
- tdata = list(self.__tbuffer__)[shift_data::self.__downsampling_rate__]
+ if not self.__rolling_plot__:
+ tdata = list(self.__tbuffer__)
if self.__rolling_plot__:
- self.__append_at__ += self.__new_added_data__ / self.__downsampling_rate__
+ tdata = list(range(0, len(self.__tbuffer__)))
+ self.__append_at__ += self.signals[list(self.signals.keys())[0]].get_new_added_since_last_drawing()
self.__append_at__ %= len(tdata)
+ tdata = np.roll(tdata, -int(self.__append_at__))
+
+ now = pg.ptime.time()
- # --------------------------
- # iterate over all buffers
- # --------------------------
for signal_name in self.signals:
- data = list(self.signals[signal_name]['buffer'])[shift_data::self.__downsampling_rate__]
- if self.__rolling_plot__:
- data = np.roll(data, int(self.__append_at__))
- self.__vertical_line__.setValue(tdata[int(self.__append_at__)-1])
- else:
- self.__vertical_line__.setValue(tdata[0])
+ # get all no more needed graphic items
+ graphics = self.signals[signal_name].get_old_graphics()
+ for graphic in graphics:
+ self.__plotWidget__.removeItem(graphic)
- curve = self.signals[signal_name]['curve']
- curve.setData(tdata, data, _callSync='off')
+ # Create new new graphic items
+ self.signals[signal_name].create_graphics(tdata)
- self.__tdata_old__ = tdata
+ # Get new created graphic item and paint them
+ graphics = self.signals[signal_name].get_graphics()
+ for graphic in graphics:
+ self.__plotWidget__.addItem(graphic)
+
+ self.__last_plot_time__ = pg.ptime.time()-now
+
+ if self.__rolling_plot__:
+ self.__plotWidget__.getPlotItem().getViewBox().setXRange(0, len(tdata)-1)
+ self.time_label.setNum(self.__tbuffer__[-1])
+ else:
+ self.__plotWidget__.getPlotItem().getViewBox().setXRange(tdata[0], tdata[-1])
+
+ # if self.__papi_debug__:
+ # print("Plot time: %0.5f sec" % (self.__last_plot_time__) )
+
+ def initiate_update_plot(self):
+ """
+ To all needed changes to use default plotting
+
+ :return:
+ """
+ self.__stp_active__ = False
+
+ if self.__rolling_plot__:
+ self.time_label.setHidden(False)
+ else:
+ self.time_label.setHidden(True)
+
+ pass
+
+ def initiate_update_plot_single_timestamp(self):
+ """
+ To all needed changes to use single timestamp plotting
+
+ :return:
+ """
+
+ self.__stp_active__ = True
+
+ self.time_label.setHidden(False)
def update_plot_single_timestamp(self, data):
"""
@@ -344,18 +534,471 @@ def update_plot_single_timestamp(self, data):
:return:
"""
- self.__text_item__.setText("Time " + str(data['t'][0]), color=(200, 200, 200))
+ self.__plotWidget__.clear()
+
+ cur_max_y = 0
+ cur_min_y = 1000
for signal_name in data:
if signal_name != 't':
signal_data = data[signal_name]
if signal_name in self.signals:
+
tdata = np.linspace(1, len(signal_data), len(signal_data))
- curve = self.signals[signal_name]['curve']
- curve.setData(tdata, signal_data, _callSync='off')
+ plot_item = self.signals[signal_name]
- pass
+ if len(tdata) == 1:
+ graphic = GraphicItem(np.array([self.__stp_min_x, self.__stp_max_x]), np.array([signal_data[0], signal_data[0]]), 0, pen=plot_item.pen)
+ else:
+ graphic = GraphicItem(np.array(tdata), np.array(signal_data), 0, pen=plot_item.pen)
+
+ self.__plotWidget__.addItem(graphic)
+
+ self.__stp_max_x = max(self.__stp_max_x, max(tdata))
+ self.__stp_min_x = min(self.__stp_min_x, min(tdata))
+
+ cur_max_y = max(cur_max_y, max(signal_data))
+ cur_min_y = min(cur_min_y, min(signal_data))
+
+ self.__stp_max_y = cur_max_y
+ self.__stp_min_y = cur_min_y
+
+ self.__plotWidget__.getPlotItem().getViewBox().setXRange(self.__stp_min_x, self.__stp_max_x)
+
+ self.time_label.setNum(data['t'][0])
+
+ def update_buffer_size(self, new_size):
+ """
+ Function set buffer size
+
+ :param new_size:
+ :return:
+ """
+
+ self.__buffer_size__ = int(new_size)
+
+ start_size = len(self.__tbuffer__)
+
+ for signal_name in self.signals:
+ self.__tbuffer__ = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION
+
+ plot_item = self.signals[signal_name]
+ plot_item.max_elements = self.__buffer_size__
+ plot_item.clear()
+ self.__plotWidget__.clear()
+
+ self.update_rolling_plot()
+
+ self.__new_added_data__ = 0
+
+ def plugin_meta_updated(self):
+ """
+ This function is called whenever meta information are changed.
+ This enables the plot to handle more than one input for plotting.
+
+ :return:
+ """
+
+ subscriptions = self.dplugin_info.get_subscribtions()
+ changes = False
+ current_signals = {}
+ index = 0
+
+ for dpluginsub_id in subscriptions:
+ for dblock_name in subscriptions[dpluginsub_id]:
+
+ # get subscription for dblock
+ subscription = subscriptions[dpluginsub_id][dblock_name]
+
+ for signal_name in subscription.get_signals():
+
+ signal = subscription.get_dblock().get_signal_by_uname(signal_name)
+ current_signals[signal_name] = {}
+ current_signals[signal_name]['signal'] = signal
+ current_signals[signal_name]['index'] = index
+ index += 1
+
+ # ----------------------------
+ # Add new subscribed signals
+ # ----------------------------
+ for signal_name in sorted(current_signals.keys()):
+ if signal_name not in self.signals:
+ signal = current_signals[signal_name]['signal']
+ self.add_plot_item(signal, current_signals[signal_name]['index'])
+ changes = True
+
+ # -------------------------------
+ # Remove unsubscribed signals
+ # -------------------------------
+ for signal_name in self.signals.copy():
+ if signal_name not in current_signals:
+ self.remove_plot_item(signal_name)
+ changes = True
+
+ if changes:
+ self.update_pens()
+ self.update_signals()
+ self.update_legend()
+ self.update_rolling_plot()
+ self.update_downsampling_rate()
+ else:
+ self.update_signals()
+ self.update_legend()
+
+ def add_plot_item(self, signal, signal_id):
+ """
+ Create a new plot item object for a given signal and internal id
+
+ :param signal: DSignal object
+ :param signal_id: plot internal signal id
+ :return:
+ """
+
+ signal_name = signal.uname
+
+ if signal_name not in self.signals:
+ self.signals[signal_name] = {}
+
+ plot_item = PlotItem(signal, signal_id, self.__buffer_size__)
+ plot_item.set_downsampling_rate(self.__downsampling_rate__)
+
+ self.signals[signal_name] = plot_item
+
+ def remove_plot_item(self, signal_name):
+ """
+ Remove the plot item object for a given signal_name.
+
+ :param signal_name:
+ :return:
+ """
+
+ if signal_name in self.signals:
+ plot_item = self.signals[signal_name]
+
+ # Remove all graphic objects
+
+ for graphic in plot_item.graphics:
+ self.__plotWidget__.removeItem(graphic)
+
+ # Remove from Legend
+ self.__legend__.removeItem(plot_item.signal_name)
+ del self.signals[signal_name]
+
+ def get_pen(self, index):
+ """
+ Function get pen
+
+ :param index:
+ :return:
+ """
+ index = int(index)
+
+ style_index = index % len(self.__styles_selected__)
+ style_code = int(self.__styles_selected__[style_index])
+
+ color_index = index % len(self.__colors_selected__)
+ color_code = int(self.__colors_selected__[color_index])
+
+ if style_code in self.styles:
+ style = self.styles[style_code]
+ else:
+ style = self.styles[0]
+
+ if color_code in self.colors:
+ color = self.colors[color_code]
+ else:
+ color = self.colors[0]
+
+ return pq.mkPen(color=color, style=style)
+
+ def update_rolling_plot(self):
+ """
+ Used to update the rolling plot by resolving all dependencies.
+ The configuration for the rolling plot depends on the current value in self.config['rolling_plot']['value']
+
+ :return:
+ """
+
+ value = self.config['rolling_plot']['value']
+
+ self.__rolling_plot__ = int(float(self.config['rolling_plot']['value'])) == int('1')
+
+ self.rolling_Checkbox.stateChanged.disconnect()
+ self.rolling_Checkbox.setChecked(value == '1')
+ self.rolling_Checkbox.stateChanged.connect(self.contextMenu_rolling_toogled)
+
+ self.clear()
+
+
+ for signal_name in self.signals:
+ self.signals[signal_name].rolling_plot = self.__rolling_plot__
+
+ self.initiate_update_plot()
+
+ def use_range_for_x(self, value):
+ """
+
+ :param value:
+ :return:
+ """
+ reg = re.compile(r'(\d+\.\d+)')
+ range = reg.findall(value)
+ if len(range) == 2:
+ #self.xRange_minEdit.setText(range[0])
+ #self.xRange_maxEdit.setText(range[1])
+ self.__plotWidget__.getPlotItem().getViewBox().setXRange(float(range[0]),float(range[1]))
+
+ def use_range_for_y(self, value):
+ """
+
+ :param value:
+ :return:
+ """
+ reg = re.compile(r'([-]{0,1}\d+\.\d+)')
+ range = reg.findall(value)
+
+ if len(range) == 2:
+ self.yRange_minEdit.setText(range[0])
+ self.yRange_maxEdit.setText(range[1])
+ self.__plotWidget__.getPlotItem().getViewBox().setYRange(float(range[0]), float(range[1]))
+
+ def setup_context_menu(self):
+ """
+
+ :return:
+ """
+
+ self.custMenu = QtGui.QMenu("Options")
+ self.axesMenu = QtGui.QMenu('Y-Axis')
+ self.gridMenu = QtGui.QMenu('Grid')
+
+ ##### Y-Range Actions
+ self.yRange_Widget = QtGui.QWidget()
+ self.yRange_Layout = QtGui.QVBoxLayout(self.yRange_Widget)
+ self.yRange_Layout.setContentsMargins(2, 2, 2, 2)
+ self.yRange_Layout.setSpacing(1)
+
+ self.yAutoRangeButton = QtGui.QPushButton()
+ self.yAutoRangeButton.clicked.connect(self.contextMenu_yAutoRangeButton_clicked)
+ self.yAutoRangeButton.setText('Use autorange')
+ self.yRange_Layout.addWidget(self.yAutoRangeButton)
+
+ ##### Y Line Edits
+ # Layout
+ self.yRange_EditWidget = QtGui.QWidget()
+ self.yRange_EditLayout = QtGui.QHBoxLayout(self.yRange_EditWidget)
+ self.yRange_EditLayout.setContentsMargins(2, 2, 2, 2)
+ self.yRange_EditLayout.setSpacing(1)
+
+ # get old values;
+ reg = re.compile(r'(\d+\.\d+)')
+ range = reg.findall(self.config['yRange']['value'])
+ if len(range) == 2:
+ y_min = range[0]
+ y_max = range[1]
+ else:
+ y_min = '0.0'
+ y_max = '1.0'
+
+ rx = QRegExp(r'([-]{0,1}\d+\.\d+)')
+ validator = QRegExpValidator(rx, self.__plotWidget__)
+
+ # Min
+ self.yRange_minEdit = QtGui.QLineEdit()
+ self.yRange_minEdit.setFixedWidth(80)
+ self.yRange_minEdit.setText(y_min)
+ self.yRange_minEdit.editingFinished.connect(self.contextMenu_yRange_toogle)
+ self.yRange_minEdit.setValidator(validator)
+ # Max
+ self.yRange_maxEdit = QtGui.QLineEdit()
+ self.yRange_maxEdit.setFixedWidth(80)
+ self.yRange_maxEdit.setText(y_max)
+ self.yRange_maxEdit.editingFinished.connect(self.contextMenu_yRange_toogle)
+ self.yRange_maxEdit.setValidator(validator)
+ # addTo Layout
+ self.yRange_EditLayout.addWidget(self.yRange_minEdit)
+ self.yRange_EditLayout.addWidget(QtGui.QLabel('<'))
+ self.yRange_EditLayout.addWidget(self.yRange_maxEdit)
+ self.yRange_Layout.addWidget(self.yRange_EditWidget)
+
+ # build Action
+ self.yRange_Action = QtGui.QWidgetAction(self.__plotWidget__)
+ self.yRange_Action.setDefaultWidget(self.yRange_Widget)
+
+ ##### Rolling Plot
+ self.rolling_Checkbox = QtGui.QCheckBox()
+ self.rolling_Checkbox.setText('Rolling plot')
+ self.rolling_Checkbox.setChecked(self.config['rolling_plot']['value'] == '1')
+ self.rolling_Checkbox.stateChanged.connect(self.contextMenu_rolling_toogled)
+ self.rolling_Checkbox_Action = QtGui.QWidgetAction(self.__plotWidget__)
+ self.rolling_Checkbox_Action.setDefaultWidget(self.rolling_Checkbox)
+ if self.__stp_active__:
+ self.rolling_Checkbox.setDisabled(True)
+
+
+ ##### Build axes menu
+ #self.axesMenu.addAction(self.xRange_Action)
+ self.axesMenu.addSeparator().setText("Y-Range")
+ self.axesMenu.addAction(self.yRange_Action)
+
+ # Grid Menu:
+ # -----------------------------------------------------------
+ # Y-Grid checkbox
+ self.xGrid_Checkbox = QtGui.QCheckBox()
+ self.xGrid_Checkbox.stateChanged.connect(self.contextMenu_xGrid_toogle)
+ self.xGrid_Checkbox.setText('X-Grid')
+ self.xGrid_Action = QtGui.QWidgetAction(self.__plotWidget__)
+ self.xGrid_Action.setDefaultWidget(self.xGrid_Checkbox)
+ self.gridMenu.addAction(self.xGrid_Action)
+ # Check config for startup state
+ if self.__show_grid_x__:
+ self.xGrid_Checkbox.setChecked(True)
+
+ # X-Grid checkbox
+ self.yGrid_Checkbox = QtGui.QCheckBox()
+ self.yGrid_Checkbox.stateChanged.connect(self.contextMenu_yGrid_toogle)
+ self.yGrid_Checkbox.setText('Y-Grid')
+ self.yGrid_Action = QtGui.QWidgetAction(self.__plotWidget__)
+ self.yGrid_Action.setDefaultWidget(self.yGrid_Checkbox)
+ self.gridMenu.addAction(self.yGrid_Action)
+ # Check config for startup state
+ if self.__show_grid_y__:
+ self.yGrid_Checkbox.setChecked(True)
+
+ # add Menus
+ self.custMenu.addMenu(self.axesMenu)
+ self.custMenu.addMenu(self.gridMenu)
+ self.custMenu.addSeparator().setText("Rolling Plot")
+ self.custMenu.addAction(self.rolling_Checkbox_Action)
+ self.__plotWidget__.getPlotItem().getViewBox().menu.clear()
+
+ if not self.__papi_debug__:
+ self.__plotWidget__.getPlotItem().ctrlMenu = [self.create_control_context_menu(), self.custMenu]
+
+ def showContextMenu(self):
+ self.setup_context_menu()
+
+
+ def contextMenu_yAutoRangeButton_clicked(self):
+ mi = None
+ ma = None
+
+ if self.__stp_active__:
+ mi = self.__stp_min_y
+ ma = self.__stp_max_y
+ else:
+ for sig in self.signals:
+ graphics = self.signals[sig].graphics
+ buf = []
+ for graphic in graphics:
+ buf.extend(graphic.y)
+
+ ma_buf = max(buf)
+ mi_buf = min(buf)
+ if ma is not None:
+ if ma_buf > ma:
+ ma = ma_buf
+ else:
+ ma = ma_buf
+
+ if mi is not None:
+ if mi_buf < mi:
+ mi = mi_buf
+ else:
+ mi = mi_buf
+
+ ma = str(ma)
+ mi = str(mi)
+
+ self.yRange_maxEdit.setText(ma)
+ self.yRange_minEdit.setText(mi)
+ self.control_api.do_set_parameter(self.__id__, 'yRange', '[' +mi + ' ' + ma + ']')
+
+ def contextMenu_rolling_toogled(self):
+ if self.rolling_Checkbox.isChecked():
+ self.control_api.do_set_parameter(self.__id__, 'rolling', '1')
+ else:
+ self.control_api.do_set_parameter(self.__id__, 'rolling', '0')
+
+ def contextMenu_xGrid_toogle(self):
+ if self.xGrid_Checkbox.isChecked():
+ self.control_api.do_set_parameter(self.__id__, 'x-grid', '1')
+ else:
+ self.control_api.do_set_parameter(self.__id__, 'x-grid', '0')
+
+ def contextMenu_yGrid_toogle(self):
+ if self.yGrid_Checkbox.isChecked():
+ self.control_api.do_set_parameter(self.__id__, 'y-grid', '1')
+ else:
+ self.control_api.do_set_parameter(self.__id__, 'y-grid', '0')
+
+ def contextMenu_yRange_toogle(self):
+ mi = self.yRange_minEdit.text()
+ ma = self.yRange_maxEdit.text()
+ if float(mi) < float(ma):
+ self.control_api.do_set_parameter(self.__id__, 'yRange', '[' + mi + ' ' + ma + ']')
+
+ def update_signals(self):
+ """
+ Used to update the signals as they are described in self.dplugin_info
+
+ :return:
+ """
+
+ subscriptions = self.dplugin_info.get_subscribtions()
+
+ for dpluginsub_id in subscriptions:
+ for dblock_name in subscriptions[dpluginsub_id]:
+
+ # get subscription for dblock
+ subscription = subscriptions[dpluginsub_id][dblock_name]
+
+ for signal_name in subscription.get_signals():
+ signal = subscription.get_dblock().get_signal_by_uname(signal_name)
+
+ self.signals[signal_name].update_signal(signal)
+
+ def update_legend(self):
+ """
+ Used to update the legend.
+
+ :return:
+ """
+
+ self.__legend__.scene().removeItem(self.__legend__)
+ del self.__legend__
+
+ self.__legend__ = pq.LegendItem((100, 40), offset=(40, 1)) # args are (size, offset)
+ self.__legend__.setParentItem(self.__plotWidget__.graphicsItem())
+
+ if not self.__papi_debug__:
+ self.update_signals()
+
+ for signal_name in sorted(self.signals.keys()):
+
+ graphic = self.signals[signal_name].get_legend_item()
+
+ if graphic is not None:
+ signal = self.signals[signal_name].signal
+ legend_name = signal.dname
+
+ self.__legend__.addItem(graphic, legend_name)
+
+ def update_downsampling_rate(self):
+ """
+ Used to update the downsampling rate by resolving all dependencies.
+ The new downsampling rate is taken by using the private attribute __downsampling_rate__.
+
+ :return:
+ """
+ rate = self.__downsampling_rate__
+
+ self.__downsampling_rate_start__ = 0
+ self.__downsampling_rate__ = rate
+
+ for signal_name in self.signals:
+ self.signals[signal_name].set_downsampling_rate(rate)
def quit(self):
"""
@@ -365,6 +1008,49 @@ def quit(self):
"""
print('PlotPerformance: will quit')
+ def debug_papi(self):
+ config = self.get_plugin_configuration()
+
+ config['yRange'] = {
+ 'value': '[0.0 50.0]',
+ 'regex': '(\d+\.\d+)',
+ 'advanced': '1',
+ 'display_text': 'y: range'
+ }
+ config['buffersize'] = {
+ 'value': '1000',
+ 'regex': '(\d+\.\d+)',
+ 'advanced': '1',
+ 'display_text': 'y: range'
+ }
+ config['downsampling_rate'] = {
+ 'value': '10',
+ 'regex': '(\d+\.\d+)',
+ 'advanced': '1',
+ 'display_text': 'y: range'
+ }
+
+ self.config = config
+ self.__id__ = 0
+ self.initiate_layer_0(config)
+
+ signal_1 = DSignal('signal_1')
+ signal_2 = DSignal('signal_2')
+ signal_3 = DSignal('signal_3')
+ signal_4 = DSignal('signal_4')
+ signal_5 = DSignal('signal_5')
+
+ self.add_plot_item(signal_1, 1)
+ self.add_plot_item(signal_2, 2)
+ self.add_plot_item(signal_3, 3)
+ self.add_plot_item(signal_4, 4)
+ self.add_plot_item(signal_5, 5)
+
+ self.update_pens()
+ self.update_legend()
+
+ pass
+
def get_plugin_configuration(self):
"""
Function get plugin configuration
@@ -372,15 +1058,7 @@ def get_plugin_configuration(self):
:return {}:
"""
config = {
- 'label_y': {
- 'value': "amplitude, V",
- 'regex': '\w+,\s+\w+',
- 'display_text': 'Label-Y'
- }, 'label_x': {
- 'value': "time, s",
- 'regex': '\w+,\s*\w+',
- 'display_text': 'Label-X'
- }, 'x-grid': {
+ 'x-grid': {
'value': "0",
'regex': '^(1|0)$',
'type': 'bool',
@@ -401,148 +1079,283 @@ def get_plugin_configuration(self):
'advanced': '1',
'display_text': 'Style'
}, 'buffersize': {
- 'value': "3000",
- 'regex': '^([1-9][0-9]{0,3}|10000)$',
+ 'value': "100",
+ 'regex': '^(\d+)$',
'advanced': '1',
'display_text': 'Buffersize'
}, 'downsampling_rate': {
- 'value': "10",
+ 'value': "1",
'regex': '(\d+)'
}, 'rolling_plot': {
'value': '0',
'regex': '^(1|0)$',
'type': 'bool',
'display_text': 'Rolling Plot'
+ }, 'yRange': {
+ 'value': '[0.0 1.0]',
+ 'regex': '^\[(\d+\.\d+)\s+(\d+\.\d+)\]$',
+ 'advanced': '1',
+ 'display_text': 'y: range'
}
}
# http://www.regexr.com/
return config
- def set_buffer_size(self, new_size):
+ def clear(self):
"""
- Function set buffer size
- :param new_size:
:return:
"""
+ self.__plotWidget__.clear()
+ self.__tbuffer__.clear()
- self.__buffer_size__ = int(new_size)
+ for signal_name in self.signals:
+ self.signals[signal_name].clear()
- # -------------------------------
- # Change Time Buffer
- # -------------------------------
- self.__tbuffer__ = collections.deque([0.0] * 0, self.__buffer_size__)
+class GraphicItem(pg.QtGui.QGraphicsPathItem):
+ """
+ Represents a single object which is drawn by a plot.
+ """
+ def __init__(self, x, y, counter, pen=pg.mkPen('r')):
+ """
- # -------------------------------
- # Change Buffer of current
- # plotted signals
- # -------------------------------
+ :param x:
+ :param y:
+ :param pen:
+ :return:
+ """
- start_size = len(self.__tbuffer__)
+ x = np.array(x[:])[np.newaxis, :]
- for signal_name in self.signals:
- buffer_new = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION
+ connect = np.ones(x.shape, dtype=bool)
+ connect[:,-1] = 0 # don't draw the segment between each trace
+ self.path = pg.arrayToQPath(x.flatten(), y.flatten(), connect.flatten())
+ pg.QtGui.QGraphicsPathItem.__init__(self, self.path)
+ self.setCacheMode(pg.QtGui.QGraphicsItem.NoCache)
+ self.setPen(pen)
+ self.not_drawn = True
+ self.counter = counter
+ self.y = y
+ self.last_x = x[0][-1]
+ self.last_y = y[-1]
- buffer_old = self.signals[signal_name]['buffer']
- #buffer_new.extend(buffer_old)
+ def shape(self): # override because QGraphicsPathItem.shape is too expensive.
+ return pg.QtGui.QGraphicsItem.shape(self)
- self.signals[signal_name]['buffer'] = buffer_new
+ def length(self):
+ return self.counter
- self.__new_added_data__ = 0
+ def boundingRect(self):
+ return self.path.boundingRect()
- def plugin_meta_updated(self):
+
+class PlotItem(object):
+ """
+ This object is used to manage a single signal object.
+ """
+
+ def __init__(self, signal, signal_id, max_elements):
"""
- By this function the plot is able to handle more than one input for plotting.
+ Initialized by setting a corresponding DSignal object and an internal signal_id
+ :param signal: DSignal object
+ :param signal_id: Plot internal ID
:return:
"""
- subscriptions = self.dplugin_info.get_subscribtions()
+ super(PlotItem,self).__init__()
+ self.signal_name = signal.dname
- current_signals = []
+ self.buffer = collections.deque([0.0] * 0, max_elements)
- for dpluginsub_id in subscriptions:
- for dblock_name in subscriptions[dpluginsub_id]:
- dblocksub = subscriptions[dpluginsub_id][dblock_name]
+ self.id = signal_id
+ self.signal = signal
- for signal in dblocksub.get_signals():
- signal_name = dblocksub.dblock.get_signal_name(signal)
- current_signals.append(signal_name)
+ self.amount_elements = 0
- # Add missing buffers
- for signal_name in current_signals:
- if signal_name not in self.signals:
- self.add_databuffer(signal_name, current_signals.index(signal_name))
+ self.downsampling_rate_start = None;
+ self.downsampling_rate = None
- # Delete old buffers
- for signal_name in self.signals.copy():
- if signal_name not in current_signals:
- self.remove_databuffer(signal_name)
+ self.max_elements = max_elements
+
+ self.pen = None
+ self.other_pen = None
+ self.graphics = []
+ self.rolling_plot = None
+ self.last_x = None
+ self.last_y = None
+ self.new_added_element = 0
- def add_databuffer(self, signal_name, signal_id):
+ def get_legend_item(self):
"""
- Create new buffer for signal_name.
+ Returns an item for a legend with the correct pen style
- :param signal_name:
- :param signal_id:
:return:
"""
- if signal_name not in self.signals:
- self.signals[signal_name] = {}
+ data_item = pg.PlotDataItem()
+ data_item.setPen(self.pen)
+ return data_item
- start_size = len(self.__tbuffer__)
+ def add_data(self, elements):
+ """
+ Used to add data which a sent for a given signal object.
- buffer = collections.deque([0.0] * start_size, self.__buffer_size__) # COLLECTION
+ :param elements:
+ :param tdata:
+ :return:
+ """
- legend_name = str(signal_id) + "# " + signal_name
- curve = self.__plotWidget__.plot([0, 1], [0, 1], name=legend_name, clipToView=True)
+ buffer = self.buffer
- self.signals[signal_name]['buffer'] = buffer
- self.signals[signal_name]['curve'] = curve
- self.signals[signal_name]['id'] = signal_id
+ self.new_added_element += len(elements)
- self.__legend__.addItem(curve, legend_name)
+ buffer.extend(elements)
- self.update_pens()
+ def update_signal(self, new_signal):
+ """
+ Used to update the corresponding signal object.
- def remove_databuffer(self, signal_name):
+ :param new_signal:
+ :return:
"""
- Remove the databuffer for signal_name.
- :param signal_name:
+ self.signal = new_signal
+ self.signal_name = new_signal.dname
+
+ def set_downsampling_rate(self, rate):
+ """
+ Used to set the current downsampling rate.
+
+ :param rate:
:return:
"""
+ self.downsampling_rate = rate
- if signal_name in self.signals:
- curve = self.signals[signal_name]['curve']
- curve.clear()
- self.__legend__.removeItem(signal_name)
- del self.signals[signal_name]
+ self.downsampling_rate_start = 0
- def get_pen(self, index):
+ def create_graphics(self, xdata):
"""
- Function get pen
+ Creates the needed graphics which can be drawn by the plot object for a given x-axis described by xdata.
- :param index:
+ :param xdata:
:return:
"""
- index = int(index)
- style_index = index % len(self.__styles_selected__)
- style_code = int(self.__styles_selected__[style_index])
+ self.new_added_element = 0
- color_index = index % len(self.__colors_selected__)
- color_code = int(self.__colors_selected__[color_index])
+ # get amount of elements in our buffer
+ counter = len(self.buffer)
- if style_code in self.styles:
- style = self.styles[style_code]
- else:
- style = self.styles[1]
+ if not counter > 0:
+ return
- if color_code in self.colors:
- color = self.colors[color_code]
+ self.amount_elements += counter
+
+ self.last_x = xdata[0]
+
+ x_axis = np.array(xdata)
+ y_axis = np.array(list(self.buffer))
+
+ if self.rolling_plot:
+
+ i_max = np.argmax(x_axis)
+ i_min = np.argmin(x_axis)
+
+ x_axis_1 = np.array(x_axis[:i_max+1])
+ x_axis_2 = np.array(x_axis[i_min:])
+
+ y_axis_1 = y_axis[:i_max+1]
+ y_axis_2 = y_axis[i_min:]
+
+ graphic_1 = GraphicItem(x_axis_1, y_axis_1, len(y_axis_1) - 1, self.other_pen)
+
+ graphic_2 = GraphicItem(x_axis_2, y_axis_2, len(y_axis_2), self.pen)
+
+ self.graphics.append(graphic_1)
+ self.graphics.append(graphic_2)
+
+ return
+
+ graphic = GraphicItem(x_axis, y_axis, counter, pen=self.pen)
+ self.graphics.append(graphic)
+
+ def get_graphics(self):
+ """
+ Returns all graphics which still need to be drawn.
+
+ :return:
+ """
+ res_graphics = []
+
+ for graphic in self.graphics:
+
+ if graphic.not_drawn:
+ res_graphics.append(graphic)
+ graphic.not_drawn = False
+
+ return res_graphics
+
+ def get_old_graphics(self):
+ """
+
+ :return:
+ """
+
+ res_graphic = []
+
+ for i in range(len(self.graphics)):
+ graphic = self.graphics[0]
+
+ if graphic.not_drawn:
+ break
+
+ graphic = self.graphics.pop(0)
+
+ res_graphic.append(graphic)
+
+ return res_graphic
+
+ def clear(self):
+ """
+
+ :return:
+ """
+ self.graphics = []
+ self.downsampling_rate_start = 0
+ self.buffer = collections.deque([0.0] * 0, self.max_elements)
+ self.amount_elements = 0
+
+ def get_new_added_since_last_drawing(self):
+ return self.new_added_element
+
+ def get_buffersize(self):
+ """
+
+ :return:
+ """
+
+ if len(self.buffer) < self.max_elements:
+ return len(self.buffer)
else:
- color = self.colors[1]
+ return self.max_elements
+
+
+class PlotWidget(pg.PlotWidget):
+
+ def __init__(self):
+ super(PlotWidget, self).__init__()
+ self.paint = True
+
+ def enablePainting(self):
+ self.paint = True
+
+ def disablePainting(self):
+ self.paint = False
+
+ def paintEvent(self, ev):
+ if self.paint:
+ self.scene().prepareForPaint()
+ return QtGui.QGraphicsView.paintEvent(self, ev)
+
- return pq.mkPen(color=color, style=style)
diff --git a/papi/plugin/visual/Plot/Plot.yapsy-plugin b/papi/plugin/visual/Plot/Plot.yapsy-plugin
index a1f8e24d..23038168 100644
--- a/papi/plugin/visual/Plot/Plot.yapsy-plugin
+++ b/papi/plugin/visual/Plot/Plot.yapsy-plugin
@@ -4,6 +4,8 @@ Module = Plot
[Documentation]
Author = S.R., S.K.
-Version = 0.7
+Version = 1.0
Website =
-Description = Basic plotting plugin. Supports more than one signals which must have the same time vector.
\ No newline at end of file
+Description = Basic plotting plugin. Supports more than one signals which must have the same time vector. Enabled single timestamp plotting and rolling plot.
+
+Icon = License: http://creativecommons.org/licenses/by/3.0/us/, Provided by: http://www.fatcow.com/
\ No newline at end of file
diff --git a/papi/plugin/visual/Plot/box.png b/papi/plugin/visual/Plot/box.png
new file mode 100644
index 00000000..09b7c222
Binary files /dev/null and b/papi/plugin/visual/Plot/box.png differ
diff --git a/papi/plugin/visual/Plot/perf.py b/papi/plugin/visual/Plot/perf.py
new file mode 100644
index 00000000..75816aa5
--- /dev/null
+++ b/papi/plugin/visual/Plot/perf.py
@@ -0,0 +1,114 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2014 Technische Universität Berlin,
+Fakultät IV - Elektrotechnik und Informatik,
+Fachgebiet Regelungssysteme,
+Einsteinufer 17, D-10587 Berlin, Germany
+
+This file is part of PaPI.
+
+PaPI is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+PaPI is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with PaPI. If not, see .
+
+Contributors:
+Sven Knuth
+"""
+
+import sys
+import os
+import time
+import random
+
+sys.path.insert(0,os.path.abspath('../../../../'))
+
+print(sys.path)
+import papi.pyqtgraph as pg
+
+from PySide.QtGui import QApplication, QLabel
+from PySide import QtCore
+
+import importlib.machinery
+
+
+def do_fctn(plugin):
+ t = plugin.__t__
+ data = {}
+
+ now = pg.ptime.time()
+
+ for i in range(10):
+ data['t'] = [t]
+ t += 0.0025
+ data['signal_1'] = [random.randint(0, 5)]
+ data['signal_2'] = [random.randint(10, 15)]
+ data['signal_3'] = [random.randint(20, 25)]
+ data['signal_4'] = [random.randint(30, 35)]
+ data['signal_5'] = [random.randint(40, 45)]
+
+
+ # data['signal_1'] = [1]
+ # data['signal_2'] = [2]
+ # data['signal_3'] = [3]
+ # data['signal_4'] = [4]
+ # data['signal_5'] = [5]
+
+
+
+ plugin.execute(data)
+
+
+
+#
+
+ plugin.__t__ = t
+
+ diff_time = pg.ptime.time()-now
+
+# print("Plot time: %0.5f sec" % (diff_time ) )
+
+ #print(25 - diff_time * 1000)
+
+ if plugin.__t__ < 10:
+ QtCore.QTimer.singleShot(25-diff_time* 1000, lambda : do_fctn(plugin))
+
+
+imp_path = "Plot.py"
+class_name = "Plot"
+
+app = QApplication([])
+
+loader = importlib.machinery.SourceFileLoader(class_name, imp_path)
+current_modul = loader.load_module()
+
+plugin = getattr(current_modul, class_name)(debug=True)
+
+# print('1')
+# __text_item__ = pq.TextItem(text='', color=(200, 200, 200), anchor=(0, 0))
+# print('2')
+
+
+plugin.debug_papi()
+
+plot_widget = plugin.__plotWidget__
+
+plot_widget.show()
+
+plugin.__t__ = 0
+
+QtCore.QTimer.singleShot(50, lambda : do_fctn(plugin))
+
+app.exec_()
+
+
diff --git a/papi/plugin/visual/ProgressBar/ProgressBar.py b/papi/plugin/visual/ProgressBar/ProgressBar.py
new file mode 100644
index 00000000..b7690c27
--- /dev/null
+++ b/papi/plugin/visual/ProgressBar/ProgressBar.py
@@ -0,0 +1,299 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+"""
+Copyright (C) 2014 Technische Universität Berlin,
+Fakultät IV - Elektrotechnik und Informatik,
+Fachgebiet Regelungssysteme,
+Einsteinufer 17, D-10587 Berlin, Germany
+
+This file is part of PaPI.
+
+PaPI is free software: you can redistribute it and/or modify
+it under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+PaPI is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with PaPI. If not, see .
+
+Contributors:
+.
+
+Contributors:
+.
+
+Contributors:
+ 0:
i.setFocus(QtCore.Qt.MouseFocusReason)
break
- #else:
- #addr = sip.unwrapinstance(sip.cast(self.mouseGrabberItem(), QtGui.QGraphicsItem))
- #item = GraphicsScene._addressCache.get(addr, self.mouseGrabberItem())
- #print "click grabbed by:", item
def mouseMoveEvent(self, ev):
self.sigMouseMoved.emit(ev.scenePos())
@@ -201,7 +190,6 @@ def leaveEvent(self, ev): ## inform items that mouse is gone
def mouseReleaseEvent(self, ev):
#print 'sceneRelease'
if self.mouseGrabberItem() is None:
- #print "sending click/drag event"
if ev.button() in self.dragButtons:
if self.sendDragEvent(ev, final=True):
#print "sent drag event"
@@ -243,6 +231,8 @@ def sendHoverEvents(self, ev, exitOnly=False):
prevItems = list(self.hoverItems.keys())
+ #print "hover prev items:", prevItems
+ #print "hover test items:", items
for item in items:
if hasattr(item, 'hoverEvent'):
event.currentItem = item
@@ -260,6 +250,7 @@ def sendHoverEvents(self, ev, exitOnly=False):
event.enter = False
event.exit = True
+ #print "hover exit items:", prevItems
for item in prevItems:
event.currentItem = item
try:
@@ -269,9 +260,13 @@ def sendHoverEvents(self, ev, exitOnly=False):
finally:
del self.hoverItems[item]
- if hasattr(ev, 'buttons') and int(ev.buttons()) == 0:
+ # Update last hover event unless:
+ # - mouse is dragging (move+buttons); in this case we want the dragged
+ # item to continue receiving events until the drag is over
+ # - event is not a mouse event (QEvent.Leave sometimes appears here)
+ if (ev.type() == ev.GraphicsSceneMousePress or
+ (ev.type() == ev.GraphicsSceneMouseMove and int(ev.buttons()) == 0)):
self.lastHoverEvent = event ## save this so we can ask about accepted events later.
-
def sendDragEvent(self, ev, init=False, final=False):
## Send a MouseDragEvent to the current dragItem or to
@@ -335,7 +330,6 @@ def sendClickEvent(self, ev):
acceptedItem = self.lastHoverEvent.clickItems().get(ev.button(), None)
else:
acceptedItem = None
-
if acceptedItem is not None:
ev.currentItem = acceptedItem
try:
@@ -357,22 +351,9 @@ def sendClickEvent(self, ev):
if int(item.flags() & item.ItemIsFocusable) > 0:
item.setFocus(QtCore.Qt.MouseFocusReason)
break
- #if not ev.isAccepted() and ev.button() is QtCore.Qt.RightButton:
- #print "GraphicsScene emitting sigSceneContextMenu"
- #self.sigMouseClicked.emit(ev)
- #ev.accept()
self.sigMouseClicked.emit(ev)
return ev.isAccepted()
- #def claimEvent(self, item, button, eventType):
- #key = (button, eventType)
- #if key in self.claimedEvents:
- #return False
- #self.claimedEvents[key] = item
- #print "event", key, "claimed by", item
- #return True
-
-
def items(self, *args):
#print 'args:', args
items = QtGui.QGraphicsScene.items(self, *args)
@@ -445,10 +426,10 @@ def itemsNearEvent(self, event, selMode=QtCore.Qt.IntersectsItemShape, sortOrder
for item in items:
if hoverable and not hasattr(item, 'hoverEvent'):
continue
- shape = item.shape()
+ shape = item.shape() # Note: default shape() returns boundingRect()
if shape is None:
continue
- if item.mapToScene(shape).contains(point):
+ if shape.contains(item.mapFromScene(point)):
items2.append(item)
## Sort by descending Z-order (don't trust scene.itms() to do this either)
@@ -489,7 +470,7 @@ def getViewWidget(self):
#return v
#else:
#return widget
-
+
def addParentContextMenus(self, item, menu, event):
"""
Can be called by any item in the scene to expand its context menu to include parent context menus.
@@ -519,30 +500,23 @@ def addParentContextMenus(self, item, menu, event):
event The original event that triggered the menu to appear.
============== ==================================================
"""
-
- #items = self.itemsNearEvent(ev)
+
menusToAdd = []
while item is not self:
item = item.parentItem()
-
if item is None:
item = self
-
if not hasattr(item, "getContextMenus"):
continue
-
- subMenus = item.getContextMenus(event)
- if subMenus is None:
- continue
- if type(subMenus) is not list: ## so that some items (like FlowchartViewBox) can return multiple menus
- subMenus = [subMenus]
-
- for sm in subMenus:
- menusToAdd.append(sm)
-
- if len(menusToAdd) > 0:
+ subMenus = item.getContextMenus(event) or []
+ if isinstance(subMenus, list): ## so that some items (like FlowchartViewBox) can return multiple menus
+ menusToAdd.extend(subMenus)
+ else:
+ menusToAdd.append(subMenus)
+
+ if menusToAdd:
menu.addSeparator()
-
+
for m in menusToAdd:
if isinstance(m, QtGui.QMenu):
menu.addMenu(m)
@@ -559,6 +533,7 @@ def getContextMenus(self, event):
def showExportDialog(self):
if self.exportDialog is None:
+ from . import exportDialog
self.exportDialog = exportDialog.ExportDialog(self)
self.exportDialog.show(self.contextMenuItem)
diff --git a/pyqtgraph/GraphicsScene/__init__.py b/papi/pyqtgraph/GraphicsScene/__init__.py
similarity index 100%
rename from pyqtgraph/GraphicsScene/__init__.py
rename to papi/pyqtgraph/GraphicsScene/__init__.py
diff --git a/pyqtgraph/GraphicsScene/exportDialog.py b/papi/pyqtgraph/GraphicsScene/exportDialog.py
similarity index 89%
rename from pyqtgraph/GraphicsScene/exportDialog.py
rename to papi/pyqtgraph/GraphicsScene/exportDialog.py
index 436d5e42..5efb7c44 100644
--- a/pyqtgraph/GraphicsScene/exportDialog.py
+++ b/papi/pyqtgraph/GraphicsScene/exportDialog.py
@@ -1,6 +1,8 @@
-from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
-import pyqtgraph as pg
-import pyqtgraph.exporters as exporters
+from ..Qt import QtCore, QtGui, USE_PYSIDE
+from .. import exporters as exporters
+from .. import functions as fn
+from ..graphicsItems.ViewBox import ViewBox
+from ..graphicsItems.PlotItem import PlotItem
if USE_PYSIDE:
from . import exportDialogTemplate_pyside as exportDialogTemplate
@@ -18,7 +20,7 @@ def __init__(self, scene):
self.scene = scene
self.selectBox = QtGui.QGraphicsRectItem()
- self.selectBox.setPen(pg.mkPen('y', width=3, style=QtCore.Qt.DashLine))
+ self.selectBox.setPen(fn.mkPen('y', width=3, style=QtCore.Qt.DashLine))
self.selectBox.hide()
self.scene.addItem(self.selectBox)
@@ -35,10 +37,10 @@ def __init__(self, scene):
def show(self, item=None):
if item is not None:
## Select next exportable parent of the item originally clicked on
- while not isinstance(item, pg.ViewBox) and not isinstance(item, pg.PlotItem) and item is not None:
+ while not isinstance(item, ViewBox) and not isinstance(item, PlotItem) and item is not None:
item = item.parentItem()
## if this is a ViewBox inside a PlotItem, select the parent instead.
- if isinstance(item, pg.ViewBox) and isinstance(item.parentItem(), pg.PlotItem):
+ if isinstance(item, ViewBox) and isinstance(item.parentItem(), PlotItem):
item = item.parentItem()
self.updateItemList(select=item)
self.setVisible(True)
@@ -64,9 +66,9 @@ def updateItemList(self, select=None):
def updateItemTree(self, item, treeItem, select=None):
si = None
- if isinstance(item, pg.ViewBox):
+ if isinstance(item, ViewBox):
si = QtGui.QTreeWidgetItem(['ViewBox'])
- elif isinstance(item, pg.PlotItem):
+ elif isinstance(item, PlotItem):
si = QtGui.QTreeWidgetItem(['Plot'])
if si is not None:
diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate.ui b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate.ui
similarity index 98%
rename from pyqtgraph/GraphicsScene/exportDialogTemplate.ui
rename to papi/pyqtgraph/GraphicsScene/exportDialogTemplate.ui
index c91fbc3f..eacacd88 100644
--- a/pyqtgraph/GraphicsScene/exportDialogTemplate.ui
+++ b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate.ui
@@ -92,7 +92,7 @@
ParameterTree
QTreeWidget
-
+
diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py
similarity index 68%
rename from pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py
rename to papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py
index c3056d1c..ad7361ab 100644
--- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py
+++ b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyqt.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/GraphicsScene/exportDialogTemplate.ui'
#
-# Created: Wed Jan 30 21:02:28 2013
-# by: PyQt4 UI code generator 4.9.3
+# Created: Mon Dec 23 10:10:52 2013
+# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,16 @@
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
- _fromUtf8 = lambda s: s
+ def _fromUtf8(s):
+ return s
+
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
class Ui_Form(object):
def setupUi(self, Form):
@@ -57,12 +66,12 @@ def setupUi(self, Form):
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
- Form.setWindowTitle(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8))
- self.label.setText(QtGui.QApplication.translate("Form", "Item to export:", None, QtGui.QApplication.UnicodeUTF8))
- self.label_2.setText(QtGui.QApplication.translate("Form", "Export format", None, QtGui.QApplication.UnicodeUTF8))
- self.exportBtn.setText(QtGui.QApplication.translate("Form", "Export", None, QtGui.QApplication.UnicodeUTF8))
- self.closeBtn.setText(QtGui.QApplication.translate("Form", "Close", None, QtGui.QApplication.UnicodeUTF8))
- self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8))
- self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8))
+ Form.setWindowTitle(_translate("Form", "Export", None))
+ self.label.setText(_translate("Form", "Item to export:", None))
+ self.label_2.setText(_translate("Form", "Export format", None))
+ self.exportBtn.setText(_translate("Form", "Export", None))
+ self.closeBtn.setText(_translate("Form", "Close", None))
+ self.label_3.setText(_translate("Form", "Export options", None))
+ self.copyBtn.setText(_translate("Form", "Copy", None))
-from pyqtgraph.parametertree import ParameterTree
+from ..parametertree import ParameterTree
diff --git a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py
similarity index 92%
rename from pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py
rename to papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py
index cf27f60a..f2e8dc70 100644
--- a/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py
+++ b/papi/pyqtgraph/GraphicsScene/exportDialogTemplate_pyside.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './GraphicsScene/exportDialogTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/GraphicsScene/exportDialogTemplate.ui'
#
-# Created: Wed Jan 30 21:02:28 2013
-# by: pyside-uic 0.2.13 running on PySide 1.1.1
+# Created: Mon Dec 23 10:10:53 2013
+# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
@@ -60,4 +60,4 @@ def retranslateUi(self, Form):
self.label_3.setText(QtGui.QApplication.translate("Form", "Export options", None, QtGui.QApplication.UnicodeUTF8))
self.copyBtn.setText(QtGui.QApplication.translate("Form", "Copy", None, QtGui.QApplication.UnicodeUTF8))
-from pyqtgraph.parametertree import ParameterTree
+from ..parametertree import ParameterTree
diff --git a/pyqtgraph/GraphicsScene/mouseEvents.py b/papi/pyqtgraph/GraphicsScene/mouseEvents.py
similarity index 94%
rename from pyqtgraph/GraphicsScene/mouseEvents.py
rename to papi/pyqtgraph/GraphicsScene/mouseEvents.py
index 0b71ac6f..2e472e04 100644
--- a/pyqtgraph/GraphicsScene/mouseEvents.py
+++ b/papi/pyqtgraph/GraphicsScene/mouseEvents.py
@@ -1,7 +1,7 @@
-from pyqtgraph.Point import Point
-from pyqtgraph.Qt import QtCore, QtGui
+from ..Point import Point
+from ..Qt import QtCore, QtGui
import weakref
-import pyqtgraph.ptime as ptime
+from .. import ptime as ptime
class MouseDragEvent(object):
"""
@@ -131,8 +131,12 @@ def isFinish(self):
return self.finish
def __repr__(self):
- lp = self.lastPos()
- p = self.pos()
+ if self.currentItem is None:
+ lp = self._lastScenePos
+ p = self._scenePos
+ else:
+ lp = self.lastPos()
+ p = self.pos()
return "(%g,%g) buttons=%d start=%s finish=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isStart()), str(self.isFinish()))
def modifiers(self):
@@ -221,9 +225,15 @@ def modifiers(self):
return self._modifiers
def __repr__(self):
- p = self.pos()
- return "" % (p.x(), p.y(), int(self.button()))
-
+ try:
+ if self.currentItem is None:
+ p = self._scenePos
+ else:
+ p = self.pos()
+ return "" % (p.x(), p.y(), int(self.button()))
+ except:
+ return "" % (int(self.button()))
+
def time(self):
return self._time
@@ -345,8 +355,15 @@ def lastPos(self):
return Point(self.currentItem.mapFromScene(self._lastScenePos))
def __repr__(self):
- lp = self.lastPos()
- p = self.pos()
+ if self.exit:
+ return ""
+
+ if self.currentItem is None:
+ lp = self._lastScenePos
+ p = self._scenePos
+ else:
+ lp = self.lastPos()
+ p = self.pos()
return "(%g,%g) buttons=%d enter=%s exit=%s>" % (lp.x(), lp.y(), p.x(), p.y(), int(self.buttons()), str(self.isEnter()), str(self.isExit()))
def modifiers(self):
diff --git a/pyqtgraph/PlotData.py b/papi/pyqtgraph/PlotData.py
similarity index 100%
rename from pyqtgraph/PlotData.py
rename to papi/pyqtgraph/PlotData.py
diff --git a/pyqtgraph/Point.py b/papi/pyqtgraph/Point.py
similarity index 100%
rename from pyqtgraph/Point.py
rename to papi/pyqtgraph/Point.py
diff --git a/papi/pyqtgraph/Qt.py b/papi/pyqtgraph/Qt.py
new file mode 100644
index 00000000..efbe66c4
--- /dev/null
+++ b/papi/pyqtgraph/Qt.py
@@ -0,0 +1,135 @@
+"""
+This module exists to smooth out some of the differences between PySide and PyQt4:
+
+* Automatically import either PyQt4 or PySide depending on availability
+* Allow to import QtCore/QtGui pyqtgraph.Qt without specifying which Qt wrapper
+ you want to use.
+* Declare QtCore.Signal, .Slot in PyQt4
+* Declare loadUiType function for Pyside
+
+"""
+
+import sys, re
+
+from .python2_3 import asUnicode
+
+## Automatically determine whether to use PyQt or PySide.
+## This is done by first checking to see whether one of the libraries
+## is already imported. If not, then attempt to import PyQt4, then PySide.
+if 'PyQt4' in sys.modules:
+ USE_PYSIDE = False
+elif 'PySide' in sys.modules:
+ USE_PYSIDE = True
+else:
+ try:
+ import PyQt4
+ USE_PYSIDE = False
+ except ImportError:
+ try:
+ import PySide
+ USE_PYSIDE = True
+ except ImportError:
+ raise Exception("PyQtGraph requires either PyQt4 or PySide; neither package could be imported.")
+
+if USE_PYSIDE:
+ from PySide import QtGui, QtCore, QtOpenGL, QtSvg
+ try:
+ from PySide import QtTest
+ except ImportError:
+ pass
+ import PySide
+ try:
+ from PySide import shiboken
+ isQObjectAlive = shiboken.isValid
+ except ImportError:
+ def isQObjectAlive(obj):
+ try:
+ if hasattr(obj, 'parent'):
+ obj.parent()
+ elif hasattr(obj, 'parentItem'):
+ obj.parentItem()
+ else:
+ raise Exception("Cannot determine whether Qt object %s is still alive." % obj)
+ except RuntimeError:
+ return False
+ else:
+ return True
+
+ VERSION_INFO = 'PySide ' + PySide.__version__
+
+ # Make a loadUiType function like PyQt has
+
+ # Credit:
+ # http://stackoverflow.com/questions/4442286/python-code-genration-with-pyside-uic/14195313#14195313
+
+ class StringIO(object):
+ """Alternative to built-in StringIO needed to circumvent unicode/ascii issues"""
+ def __init__(self):
+ self.data = []
+
+ def write(self, data):
+ self.data.append(data)
+
+ def getvalue(self):
+ return ''.join(map(asUnicode, self.data)).encode('utf8')
+
+ def loadUiType(uiFile):
+ """
+ Pyside "loadUiType" command like PyQt4 has one, so we have to convert the ui file to py code in-memory first and then execute it in a special frame to retrieve the form_class.
+ """
+ import pysideuic
+ import xml.etree.ElementTree as xml
+ #from io import StringIO
+
+ parsed = xml.parse(uiFile)
+ widget_class = parsed.find('widget').get('class')
+ form_class = parsed.find('class').text
+
+ with open(uiFile, 'r') as f:
+ o = StringIO()
+ frame = {}
+
+ pysideuic.compileUi(f, o, indent=0)
+ pyc = compile(o.getvalue(), '', 'exec')
+ exec(pyc, frame)
+
+ #Fetch the base_class and form class based on their type in the xml from designer
+ form_class = frame['Ui_%s'%form_class]
+ base_class = eval('QtGui.%s'%widget_class)
+
+ return form_class, base_class
+
+
+else:
+ from PyQt4 import QtGui, QtCore, uic
+ try:
+ from PyQt4 import QtSvg
+ except ImportError:
+ pass
+ try:
+ from PyQt4 import QtOpenGL
+ except ImportError:
+ pass
+ try:
+ from PyQt4 import QtTest
+ except ImportError:
+ pass
+
+
+ import sip
+ def isQObjectAlive(obj):
+ return not sip.isdeleted(obj)
+ loadUiType = uic.loadUiType
+
+ QtCore.Signal = QtCore.pyqtSignal
+ VERSION_INFO = 'PyQt4 ' + QtCore.PYQT_VERSION_STR + ' Qt ' + QtCore.QT_VERSION_STR
+
+
+## Make sure we have Qt >= 4.7
+versionReq = [4, 7]
+QtVersion = PySide.QtCore.__version__ if USE_PYSIDE else QtCore.QT_VERSION_STR
+m = re.match(r'(\d+)\.(\d+).*', QtVersion)
+if m is not None and list(map(int, m.groups())) < versionReq:
+ print(list(map(int, m.groups())))
+ raise Exception('pyqtgraph requires Qt version >= %d.%d (your version is %s)' % (versionReq[0], versionReq[1], QtVersion))
+
diff --git a/pyqtgraph/SRTTransform.py b/papi/pyqtgraph/SRTTransform.py
similarity index 99%
rename from pyqtgraph/SRTTransform.py
rename to papi/pyqtgraph/SRTTransform.py
index efb24f60..23281343 100644
--- a/pyqtgraph/SRTTransform.py
+++ b/papi/pyqtgraph/SRTTransform.py
@@ -2,7 +2,6 @@
from .Qt import QtCore, QtGui
from .Point import Point
import numpy as np
-import pyqtgraph as pg
class SRTTransform(QtGui.QTransform):
"""Transform that can always be represented as a combination of 3 matrices: scale * rotate * translate
@@ -77,7 +76,7 @@ def setFromQTransform(self, tr):
self.update()
def setFromMatrix4x4(self, m):
- m = pg.SRTTransform3D(m)
+ m = SRTTransform3D(m)
angle, axis = m.getRotation()
if angle != 0 and (axis[0] != 0 or axis[1] != 0 or axis[2] != 1):
print("angle: %s axis: %s" % (str(angle), str(axis)))
@@ -256,4 +255,4 @@ def update():
w1.sigRegionChanged.connect(update)
#w2.sigRegionChanged.connect(update2)
-
\ No newline at end of file
+from .SRTTransform3D import SRTTransform3D
diff --git a/pyqtgraph/SRTTransform3D.py b/papi/pyqtgraph/SRTTransform3D.py
similarity index 93%
rename from pyqtgraph/SRTTransform3D.py
rename to papi/pyqtgraph/SRTTransform3D.py
index 7d87dcb8..9b54843b 100644
--- a/pyqtgraph/SRTTransform3D.py
+++ b/papi/pyqtgraph/SRTTransform3D.py
@@ -1,17 +1,16 @@
# -*- coding: utf-8 -*-
from .Qt import QtCore, QtGui
from .Vector import Vector
-from .SRTTransform import SRTTransform
-import pyqtgraph as pg
+from .Transform3D import Transform3D
+from .Vector import Vector
import numpy as np
-import scipy.linalg
-class SRTTransform3D(pg.Transform3D):
+class SRTTransform3D(Transform3D):
"""4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform has no shear; angles are always preserved.
"""
def __init__(self, init=None):
- pg.Transform3D.__init__(self)
+ Transform3D.__init__(self)
self.reset()
if init is None:
return
@@ -44,14 +43,14 @@ def __init__(self, init=None):
def getScale(self):
- return pg.Vector(self._state['scale'])
+ return Vector(self._state['scale'])
def getRotation(self):
"""Return (angle, axis) of rotation"""
- return self._state['angle'], pg.Vector(self._state['axis'])
+ return self._state['angle'], Vector(self._state['axis'])
def getTranslation(self):
- return pg.Vector(self._state['pos'])
+ return Vector(self._state['pos'])
def reset(self):
self._state = {
@@ -118,11 +117,13 @@ def setFromMatrix(self, m):
The input matrix must be affine AND have no shear,
otherwise the conversion will most likely fail.
"""
+ import numpy.linalg
for i in range(4):
self.setRow(i, m.row(i))
m = self.matrix().reshape(4,4)
## translation is 4th column
- self._state['pos'] = m[:3,3]
+ self._state['pos'] = m[:3,3]
+
## scale is vector-length of first three columns
scale = (m[:3,:3]**2).sum(axis=0)**0.5
## see whether there is an inversion
@@ -132,9 +133,9 @@ def setFromMatrix(self, m):
self._state['scale'] = scale
## rotation axis is the eigenvector with eigenvalue=1
- r = m[:3, :3] / scale[:, np.newaxis]
+ r = m[:3, :3] / scale[np.newaxis, :]
try:
- evals, evecs = scipy.linalg.eig(r)
+ evals, evecs = numpy.linalg.eig(r)
except:
print("Rotation matrix: %s" % str(r))
print("Scale: %s" % str(scale))
@@ -169,7 +170,7 @@ def setFromMatrix(self, m):
def as2D(self):
"""Return a QTransform representing the x,y portion of this transform (if possible)"""
- return pg.SRTTransform(self)
+ return SRTTransform(self)
#def __div__(self, t):
#"""A / B == B^-1 * A"""
@@ -202,11 +203,11 @@ def restoreState(self, state):
self.update()
def update(self):
- pg.Transform3D.setToIdentity(self)
+ Transform3D.setToIdentity(self)
## modifications to the transform are multiplied on the right, so we need to reverse order here.
- pg.Transform3D.translate(self, *self._state['pos'])
- pg.Transform3D.rotate(self, self._state['angle'], *self._state['axis'])
- pg.Transform3D.scale(self, *self._state['scale'])
+ Transform3D.translate(self, *self._state['pos'])
+ Transform3D.rotate(self, self._state['angle'], *self._state['axis'])
+ Transform3D.scale(self, *self._state['scale'])
def __repr__(self):
return str(self.saveState())
@@ -311,4 +312,4 @@ def update():
w1.sigRegionChanged.connect(update)
#w2.sigRegionChanged.connect(update2)
-
\ No newline at end of file
+from .SRTTransform import SRTTransform
diff --git a/pyqtgraph/SignalProxy.py b/papi/pyqtgraph/SignalProxy.py
similarity index 97%
rename from pyqtgraph/SignalProxy.py
rename to papi/pyqtgraph/SignalProxy.py
index 6f9b9112..d36282fa 100644
--- a/pyqtgraph/SignalProxy.py
+++ b/papi/pyqtgraph/SignalProxy.py
@@ -2,6 +2,7 @@
from .Qt import QtCore
from .ptime import time
from . import ThreadsafeTimer
+import weakref
__all__ = ['SignalProxy']
@@ -34,7 +35,7 @@ def __init__(self, signal, delay=0.3, rateLimit=0, slot=None):
self.timer = ThreadsafeTimer.ThreadsafeTimer()
self.timer.timeout.connect(self.flush)
self.block = False
- self.slot = slot
+ self.slot = weakref.ref(slot)
self.lastFlushTime = None
if slot is not None:
self.sigDelayed.connect(slot)
@@ -80,7 +81,7 @@ def disconnect(self):
except:
pass
try:
- self.sigDelayed.disconnect(self.slot)
+ self.sigDelayed.disconnect(self.slot())
except:
pass
diff --git a/pyqtgraph/ThreadsafeTimer.py b/papi/pyqtgraph/ThreadsafeTimer.py
similarity index 97%
rename from pyqtgraph/ThreadsafeTimer.py
rename to papi/pyqtgraph/ThreadsafeTimer.py
index f2de9791..201469de 100644
--- a/pyqtgraph/ThreadsafeTimer.py
+++ b/papi/pyqtgraph/ThreadsafeTimer.py
@@ -1,4 +1,4 @@
-from pyqtgraph.Qt import QtCore, QtGui
+from .Qt import QtCore, QtGui
class ThreadsafeTimer(QtCore.QObject):
"""
diff --git a/pyqtgraph/Transform3D.py b/papi/pyqtgraph/Transform3D.py
similarity index 92%
rename from pyqtgraph/Transform3D.py
rename to papi/pyqtgraph/Transform3D.py
index aa948e28..43b12de3 100644
--- a/pyqtgraph/Transform3D.py
+++ b/papi/pyqtgraph/Transform3D.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from .Qt import QtCore, QtGui
-import pyqtgraph as pg
+from . import functions as fn
import numpy as np
class Transform3D(QtGui.QMatrix4x4):
@@ -26,7 +26,7 @@ def map(self, obj):
Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates
"""
if isinstance(obj, np.ndarray) and obj.ndim >= 2 and obj.shape[0] in (2,3):
- return pg.transformCoordinates(self, obj)
+ return fn.transformCoordinates(self, obj)
else:
return QtGui.QMatrix4x4.map(self, obj)
diff --git a/pyqtgraph/Vector.py b/papi/pyqtgraph/Vector.py
similarity index 79%
rename from pyqtgraph/Vector.py
rename to papi/pyqtgraph/Vector.py
index 4b4fb02f..f2898e80 100644
--- a/pyqtgraph/Vector.py
+++ b/papi/pyqtgraph/Vector.py
@@ -67,4 +67,21 @@ def __iter__(self):
yield(self.x())
yield(self.y())
yield(self.z())
+
+ def angle(self, a):
+ """Returns the angle in degrees between this vector and the vector a."""
+ n1 = self.length()
+ n2 = a.length()
+ if n1 == 0. or n2 == 0.:
+ return None
+ ## Probably this should be done with arctan2 instead..
+ ang = np.arccos(np.clip(QtGui.QVector3D.dotProduct(self, a) / (n1 * n2), -1.0, 1.0)) ### in radians
+# c = self.crossProduct(a)
+# if c > 0:
+# ang *= -1.
+ return ang * 180. / np.pi
+
+ def __abs__(self):
+ return Vector(abs(self.x()), abs(self.y()), abs(self.z()))
+
\ No newline at end of file
diff --git a/pyqtgraph/WidgetGroup.py b/papi/pyqtgraph/WidgetGroup.py
similarity index 100%
rename from pyqtgraph/WidgetGroup.py
rename to papi/pyqtgraph/WidgetGroup.py
diff --git a/pyqtgraph/__init__.py b/papi/pyqtgraph/__init__.py
similarity index 62%
rename from pyqtgraph/__init__.py
rename to papi/pyqtgraph/__init__.py
index f46184b4..1c152d46 100644
--- a/pyqtgraph/__init__.py
+++ b/papi/pyqtgraph/__init__.py
@@ -4,7 +4,7 @@
www.pyqtgraph.org
"""
-__version__ = '0.9.8'
+__version__ = '0.9.10'
### import all the goodies and add some helper functions for easy CLI use
@@ -48,14 +48,15 @@
CONFIG_OPTIONS = {
'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl.
'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox
- 'foreground': (150, 150, 150), ## default foreground color for axes, labels, etc.
- 'background': (0, 0, 0), ## default background for GraphicsWidget
+ 'foreground': 'd', ## default foreground color for axes, labels, etc.
+ 'background': 'k', ## default background for GraphicsWidget
'antialias': False,
'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets
- 'useWeave': True, ## Use weave to speed up some operations, if it is available
+ 'useWeave': False, ## Use weave to speed up some operations, if it is available
'weaveDebug': False, ## Print full error message if weave compile fails
'exitCleanup': True, ## Attempt to work around some exit crash bugs in PyQt and PySide
'enableExperimental': False, ## Enable experimental features (the curious can search for this key in the code)
+ 'crashWarning': False, # If True, print warnings about situations that may result in a crash
}
@@ -130,56 +131,119 @@ def renamePyc(startDir):
## Import almost everything to make it available from a single namespace
## don't import the more complex systems--canvas, parametertree, flowchart, dockarea
## these must be imported separately.
-from . import frozenSupport
-def importModules(path, globals, locals, excludes=()):
- """Import all modules residing within *path*, return a dict of name: module pairs.
+#from . import frozenSupport
+#def importModules(path, globals, locals, excludes=()):
+ #"""Import all modules residing within *path*, return a dict of name: module pairs.
- Note that *path* MUST be relative to the module doing the import.
- """
- d = os.path.join(os.path.split(globals['__file__'])[0], path)
- files = set()
- for f in frozenSupport.listdir(d):
- if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']:
- files.add(f)
- elif f[-3:] == '.py' and f != '__init__.py':
- files.add(f[:-3])
- elif f[-4:] == '.pyc' and f != '__init__.pyc':
- files.add(f[:-4])
+ #Note that *path* MUST be relative to the module doing the import.
+ #"""
+ #d = os.path.join(os.path.split(globals['__file__'])[0], path)
+ #files = set()
+ #for f in frozenSupport.listdir(d):
+ #if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']:
+ #files.add(f)
+ #elif f[-3:] == '.py' and f != '__init__.py':
+ #files.add(f[:-3])
+ #elif f[-4:] == '.pyc' and f != '__init__.pyc':
+ #files.add(f[:-4])
- mods = {}
- path = path.replace(os.sep, '.')
- for modName in files:
- if modName in excludes:
- continue
- try:
- if len(path) > 0:
- modName = path + '.' + modName
- #mod = __import__(modName, globals, locals, fromlist=['*'])
- mod = __import__(modName, globals, locals, ['*'], 1)
- mods[modName] = mod
- except:
- import traceback
- traceback.print_stack()
- sys.excepthook(*sys.exc_info())
- print("[Error importing module: %s]" % modName)
+ #mods = {}
+ #path = path.replace(os.sep, '.')
+ #for modName in files:
+ #if modName in excludes:
+ #continue
+ #try:
+ #if len(path) > 0:
+ #modName = path + '.' + modName
+ #print( "from .%s import * " % modName)
+ #mod = __import__(modName, globals, locals, ['*'], 1)
+ #mods[modName] = mod
+ #except:
+ #import traceback
+ #traceback.print_stack()
+ #sys.excepthook(*sys.exc_info())
+ #print("[Error importing module: %s]" % modName)
- return mods
-
-def importAll(path, globals, locals, excludes=()):
- """Given a list of modules, import all names from each module into the global namespace."""
- mods = importModules(path, globals, locals, excludes)
- for mod in mods.values():
- if hasattr(mod, '__all__'):
- names = mod.__all__
- else:
- names = [n for n in dir(mod) if n[0] != '_']
- for k in names:
- if hasattr(mod, k):
- globals[k] = getattr(mod, k)
-
-importAll('graphicsItems', globals(), locals())
-importAll('widgets', globals(), locals(),
- excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView'])
+ #return mods
+
+#def importAll(path, globals, locals, excludes=()):
+ #"""Given a list of modules, import all names from each module into the global namespace."""
+ #mods = importModules(path, globals, locals, excludes)
+ #for mod in mods.values():
+ #if hasattr(mod, '__all__'):
+ #names = mod.__all__
+ #else:
+ #names = [n for n in dir(mod) if n[0] != '_']
+ #for k in names:
+ #if hasattr(mod, k):
+ #globals[k] = getattr(mod, k)
+
+# Dynamic imports are disabled. This causes too many problems.
+#importAll('graphicsItems', globals(), locals())
+#importAll('widgets', globals(), locals(),
+ #excludes=['MatplotlibWidget', 'RawImageWidget', 'RemoteGraphicsView'])
+
+from .graphicsItems.VTickGroup import *
+from .graphicsItems.GraphicsWidget import *
+from .graphicsItems.ScaleBar import *
+from .graphicsItems.PlotDataItem import *
+from .graphicsItems.GraphItem import *
+from .graphicsItems.TextItem import *
+from .graphicsItems.GraphicsLayout import *
+from .graphicsItems.UIGraphicsItem import *
+from .graphicsItems.GraphicsObject import *
+from .graphicsItems.PlotItem import *
+from .graphicsItems.ROI import *
+from .graphicsItems.InfiniteLine import *
+from .graphicsItems.HistogramLUTItem import *
+from .graphicsItems.GridItem import *
+from .graphicsItems.GradientLegend import *
+from .graphicsItems.GraphicsItem import *
+from .graphicsItems.BarGraphItem import *
+from .graphicsItems.ViewBox import *
+from .graphicsItems.ArrowItem import *
+from .graphicsItems.ImageItem import *
+from .graphicsItems.AxisItem import *
+from .graphicsItems.LabelItem import *
+from .graphicsItems.CurvePoint import *
+from .graphicsItems.GraphicsWidgetAnchor import *
+from .graphicsItems.PlotCurveItem import *
+from .graphicsItems.ButtonItem import *
+from .graphicsItems.GradientEditorItem import *
+from .graphicsItems.MultiPlotItem import *
+from .graphicsItems.ErrorBarItem import *
+from .graphicsItems.IsocurveItem import *
+from .graphicsItems.LinearRegionItem import *
+from .graphicsItems.FillBetweenItem import *
+from .graphicsItems.LegendItem import *
+from .graphicsItems.ScatterPlotItem import *
+from .graphicsItems.ItemGroup import *
+
+from .widgets.MultiPlotWidget import *
+from .widgets.ScatterPlotWidget import *
+from .widgets.ColorMapWidget import *
+from .widgets.FileDialog import *
+from .widgets.ValueLabel import *
+from .widgets.HistogramLUTWidget import *
+from .widgets.CheckTable import *
+from .widgets.BusyCursor import *
+from .widgets.PlotWidget import *
+from .widgets.ComboBox import *
+from .widgets.GradientWidget import *
+from .widgets.DataFilterWidget import *
+from .widgets.SpinBox import *
+from .widgets.JoystickButton import *
+from .widgets.GraphicsLayoutWidget import *
+from .widgets.TreeWidget import *
+from .widgets.PathButton import *
+from .widgets.VerticalLabel import *
+from .widgets.FeedbackButton import *
+from .widgets.ColorButton import *
+from .widgets.DataTreeWidget import *
+from .widgets.GraphicsView import *
+from .widgets.LayoutWidget import *
+from .widgets.TableWidget import *
+from .widgets.ProgressDialog import *
from .imageview import *
from .WidgetGroup import *
@@ -193,6 +257,8 @@ def importAll(path, globals, locals, excludes=()):
from .SignalProxy import *
from .colormap import *
from .ptime import time
+from .Qt import isQObjectAlive
+
##############################################################
## PyQt and PySide both are prone to crashing on exit.
@@ -204,7 +270,12 @@ def importAll(path, globals, locals, excludes=()):
## Attempts to work around exit crashes:
import atexit
+_cleanupCalled = False
def cleanup():
+ global _cleanupCalled
+ if _cleanupCalled:
+ return
+
if not getConfigOption('exitCleanup'):
return
@@ -220,12 +291,31 @@ def cleanup():
s = QtGui.QGraphicsScene()
for o in gc.get_objects():
try:
- if isinstance(o, QtGui.QGraphicsItem) and o.scene() is None:
+ if isinstance(o, QtGui.QGraphicsItem) and isQObjectAlive(o) and o.scene() is None:
+ if getConfigOption('crashWarning'):
+ sys.stderr.write('Error: graphics item without scene. '
+ 'Make sure ViewBox.close() and GraphicsView.close() '
+ 'are properly called before app shutdown (%s)\n' % (o,))
+
s.addItem(o)
except RuntimeError: ## occurs if a python wrapper no longer has its underlying C++ object
continue
+ _cleanupCalled = True
+
atexit.register(cleanup)
+# Call cleanup when QApplication quits. This is necessary because sometimes
+# the QApplication will quit before the atexit callbacks are invoked.
+# Note: cannot connect this function until QApplication has been created, so
+# instead we have GraphicsView.__init__ call this for us.
+_cleanupConnected = False
+def _connectCleanup():
+ global _cleanupConnected
+ if _cleanupConnected:
+ return
+ QtGui.QApplication.instance().aboutToQuit.connect(cleanup)
+ _cleanupConnected = True
+
## Optional function for exiting immediately (with some manual teardown)
def exit():
@@ -254,8 +344,13 @@ def exit():
atexit._run_exitfuncs()
## close file handles
- os.closerange(3, 4096) ## just guessing on the maximum descriptor count..
-
+ if sys.platform == 'darwin':
+ for fd in xrange(3, 4096):
+ if fd not in [7]: # trying to close 7 produces an illegal instruction on the Mac.
+ os.close(fd)
+ else:
+ os.closerange(3, 4096) ## just guessing on the maximum descriptor count..
+
os._exit(0)
@@ -292,7 +387,8 @@ def plot(*args, **kargs):
dataArgs[k] = kargs[k]
w = PlotWindow(**pwArgs)
- w.plot(*args, **dataArgs)
+ if len(args) > 0 or len(dataArgs) > 0:
+ w.plot(*args, **dataArgs)
plots.append(w)
w.show()
return w
@@ -328,6 +424,7 @@ def dbg(*args, **kwds):
consoles.append(c)
except NameError:
consoles = [c]
+ return c
def mkQApp():
diff --git a/pyqtgraph/canvas/Canvas.py b/papi/pyqtgraph/canvas/Canvas.py
similarity index 96%
rename from pyqtgraph/canvas/Canvas.py
rename to papi/pyqtgraph/canvas/Canvas.py
index 17a39c2b..4de891f7 100644
--- a/pyqtgraph/canvas/Canvas.py
+++ b/papi/pyqtgraph/canvas/Canvas.py
@@ -3,29 +3,21 @@
import sys, os
md = os.path.dirname(os.path.abspath(__file__))
sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path
- #print md
-
-#from pyqtgraph.GraphicsView import GraphicsView
-#import pyqtgraph.graphicsItems as graphicsItems
-#from pyqtgraph.PlotWidget import PlotWidget
-from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
-from pyqtgraph.graphicsItems.ROI import ROI
-from pyqtgraph.graphicsItems.ViewBox import ViewBox
-from pyqtgraph.graphicsItems.GridItem import GridItem
+from ..Qt import QtGui, QtCore, USE_PYSIDE
+from ..graphicsItems.ROI import ROI
+from ..graphicsItems.ViewBox import ViewBox
+from ..graphicsItems.GridItem import GridItem
if USE_PYSIDE:
from .CanvasTemplate_pyside import *
else:
from .CanvasTemplate_pyqt import *
-#import DataManager
import numpy as np
-from pyqtgraph import debug
-#import pyqtgraph as pg
+from .. import debug
import weakref
from .CanvasManager import CanvasManager
-#import items
from .CanvasItem import CanvasItem, GroupCanvasItem
class Canvas(QtGui.QWidget):
@@ -75,8 +67,8 @@ def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None)
self.ui.itemList.sigItemMoved.connect(self.treeItemMoved)
self.ui.itemList.itemSelectionChanged.connect(self.treeItemSelected)
self.ui.autoRangeBtn.clicked.connect(self.autoRange)
- self.ui.storeSvgBtn.clicked.connect(self.storeSvg)
- self.ui.storePngBtn.clicked.connect(self.storePng)
+ #self.ui.storeSvgBtn.clicked.connect(self.storeSvg)
+ #self.ui.storePngBtn.clicked.connect(self.storePng)
self.ui.redirectCheck.toggled.connect(self.updateRedirect)
self.ui.redirectCombo.currentIndexChanged.connect(self.updateRedirect)
self.multiSelectBox.sigRegionChanged.connect(self.multiSelectBoxChanged)
@@ -102,11 +94,13 @@ def __init__(self, parent=None, allowTransforms=True, hideCtrl=False, name=None)
self.ui.itemList.contextMenuEvent = self.itemListContextMenuEvent
- def storeSvg(self):
- self.ui.view.writeSvg()
+ #def storeSvg(self):
+ #from pyqtgraph.GraphicsScene.exportDialog import ExportDialog
+ #ex = ExportDialog(self.ui.view)
+ #ex.show()
- def storePng(self):
- self.ui.view.writeImage()
+ #def storePng(self):
+ #self.ui.view.writeImage()
def splitterMoved(self):
self.resizeEvent()
@@ -579,7 +573,9 @@ def itemListContextMenuEvent(self, ev):
self.menu.popup(ev.globalPos())
def removeClicked(self):
- self.removeItem(self.menuItem)
+ #self.removeItem(self.menuItem)
+ for item in self.selectedItems():
+ self.removeItem(item)
self.menuItem = None
import gc
gc.collect()
@@ -605,4 +601,4 @@ def __init__(self, scalable=False):
-
\ No newline at end of file
+
diff --git a/pyqtgraph/canvas/CanvasItem.py b/papi/pyqtgraph/canvas/CanvasItem.py
similarity index 94%
rename from pyqtgraph/canvas/CanvasItem.py
rename to papi/pyqtgraph/canvas/CanvasItem.py
index 81388cb6..b6ecbb39 100644
--- a/pyqtgraph/canvas/CanvasItem.py
+++ b/papi/pyqtgraph/canvas/CanvasItem.py
@@ -1,13 +1,13 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
-from pyqtgraph.graphicsItems.ROI import ROI
-import pyqtgraph as pg
+from ..Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
+from ..graphicsItems.ROI import ROI
+from .. import SRTTransform, ItemGroup
if USE_PYSIDE:
from . import TransformGuiTemplate_pyside as TransformGuiTemplate
else:
from . import TransformGuiTemplate_pyqt as TransformGuiTemplate
-from pyqtgraph import debug
+from .. import debug
class SelectBox(ROI):
def __init__(self, scalable=False, rotatable=True):
@@ -96,7 +96,7 @@ def __init__(self, item, **opts):
if 'transform' in self.opts:
self.baseTransform = self.opts['transform']
else:
- self.baseTransform = pg.SRTTransform()
+ self.baseTransform = SRTTransform()
if 'pos' in self.opts and self.opts['pos'] is not None:
self.baseTransform.translate(self.opts['pos'])
if 'angle' in self.opts and self.opts['angle'] is not None:
@@ -124,8 +124,8 @@ def __init__(self, item, **opts):
self.itemScale = QtGui.QGraphicsScale()
self._graphicsItem.setTransformations([self.itemRotation, self.itemScale])
- self.tempTransform = pg.SRTTransform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done.
- self.userTransform = pg.SRTTransform() ## stores the total transform of the object
+ self.tempTransform = SRTTransform() ## holds the additional transform that happens during a move - gets added to the userTransform when move is done.
+ self.userTransform = SRTTransform() ## stores the total transform of the object
self.resetUserTransform()
## now happens inside resetUserTransform -> selectBoxToItem
@@ -200,7 +200,7 @@ def mirrorY(self):
#flip = self.transformGui.mirrorImageCheck.isChecked()
#tr = self.userTransform.saveState()
- inv = pg.SRTTransform()
+ inv = SRTTransform()
inv.scale(-1, 1)
self.userTransform = self.userTransform * inv
self.updateTransform()
@@ -231,7 +231,7 @@ def mirrorXY(self):
if not self.isMovable():
return
self.rotate(180.)
- # inv = pg.SRTTransform()
+ # inv = SRTTransform()
# inv.scale(-1, -1)
# self.userTransform = self.userTransform * inv #flip lr/ud
# s=self.updateTransform()
@@ -316,7 +316,7 @@ def applyTemporaryTransform(self):
def resetTemporaryTransform(self):
- self.tempTransform = pg.SRTTransform() ## don't use Transform.reset()--this transform might be used elsewhere.
+ self.tempTransform = SRTTransform() ## don't use Transform.reset()--this transform might be used elsewhere.
self.updateTransform()
def transform(self):
@@ -368,7 +368,7 @@ def restoreTransform(self, tr):
try:
#self.userTranslate = pg.Point(tr['trans'])
#self.userRotate = tr['rot']
- self.userTransform = pg.SRTTransform(tr)
+ self.userTransform = SRTTransform(tr)
self.updateTransform()
self.selectBoxFromUser() ## move select box to match
@@ -377,7 +377,7 @@ def restoreTransform(self, tr):
except:
#self.userTranslate = pg.Point([0,0])
#self.userRotate = 0
- self.userTransform = pg.SRTTransform()
+ self.userTransform = SRTTransform()
debug.printExc("Failed to load transform:")
#print "set transform", self, self.userTranslate
@@ -431,9 +431,12 @@ def setZValue(self, z):
def selectionChanged(self, sel, multi):
"""
Inform the item that its selection state has changed.
- Arguments:
- sel: bool, whether the item is currently selected
- multi: bool, whether there are multiple items currently selected
+ ============== =========================================================
+ **Arguments:**
+ sel (bool) whether the item is currently selected
+ multi (bool) whether there are multiple items currently
+ selected
+ ============== =========================================================
"""
self.selectedAlone = sel and not multi
self.showSelectBox()
@@ -504,6 +507,6 @@ class GroupCanvasItem(CanvasItem):
def __init__(self, **opts):
defOpts = {'movable': False, 'scalable': False}
defOpts.update(opts)
- item = pg.ItemGroup()
+ item = ItemGroup()
CanvasItem.__init__(self, item, **defOpts)
diff --git a/pyqtgraph/canvas/CanvasManager.py b/papi/pyqtgraph/canvas/CanvasManager.py
similarity index 98%
rename from pyqtgraph/canvas/CanvasManager.py
rename to papi/pyqtgraph/canvas/CanvasManager.py
index e89ec00f..28188039 100644
--- a/pyqtgraph/canvas/CanvasManager.py
+++ b/papi/pyqtgraph/canvas/CanvasManager.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui
+from ..Qt import QtCore, QtGui
if not hasattr(QtCore, 'Signal'):
QtCore.Signal = QtCore.pyqtSignal
import weakref
diff --git a/pyqtgraph/canvas/CanvasTemplate.ui b/papi/pyqtgraph/canvas/CanvasTemplate.ui
similarity index 82%
rename from pyqtgraph/canvas/CanvasTemplate.ui
rename to papi/pyqtgraph/canvas/CanvasTemplate.ui
index da032906..9bea8f89 100644
--- a/pyqtgraph/canvas/CanvasTemplate.ui
+++ b/papi/pyqtgraph/canvas/CanvasTemplate.ui
@@ -28,21 +28,7 @@
- -
-
-
- Store SVG
-
-
-
- -
-
-
- Store PNG
-
-
-
- -
+
-
@@ -55,7 +41,7 @@
- -
+
-
0
@@ -75,7 +61,7 @@
- -
+
-
@@ -93,28 +79,28 @@
- -
+
-
0
- -
+
-
Reset Transforms
- -
+
-
Mirror Selection
- -
+
-
MirrorXY
@@ -131,12 +117,12 @@
TreeWidget
QTreeWidget
- pyqtgraph.widgets.TreeWidget
+
GraphicsView
QGraphicsView
- pyqtgraph.widgets.GraphicsView
+
CanvasCombo
diff --git a/pyqtgraph/canvas/CanvasTemplate_pyqt.py b/papi/pyqtgraph/canvas/CanvasTemplate_pyqt.py
similarity index 76%
rename from pyqtgraph/canvas/CanvasTemplate_pyqt.py
rename to papi/pyqtgraph/canvas/CanvasTemplate_pyqt.py
index 4d1d8208..557354e0 100644
--- a/pyqtgraph/canvas/CanvasTemplate_pyqt.py
+++ b/papi/pyqtgraph/canvas/CanvasTemplate_pyqt.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './canvas/CanvasTemplate.ui'
+# Form implementation generated from reading ui file 'acq4/pyqtgraph/canvas/CanvasTemplate.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: PyQt4 UI code generator 4.9.1
+# Created: Thu Jan 2 11:13:07 2014
+# by: PyQt4 UI code generator 4.9
#
# WARNING! All changes made in this file will be lost!
@@ -32,12 +32,6 @@ def setupUi(self, Form):
self.gridLayout_2 = QtGui.QGridLayout(self.layoutWidget)
self.gridLayout_2.setMargin(0)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
- self.storeSvgBtn = QtGui.QPushButton(self.layoutWidget)
- self.storeSvgBtn.setObjectName(_fromUtf8("storeSvgBtn"))
- self.gridLayout_2.addWidget(self.storeSvgBtn, 1, 0, 1, 1)
- self.storePngBtn = QtGui.QPushButton(self.layoutWidget)
- self.storePngBtn.setObjectName(_fromUtf8("storePngBtn"))
- self.gridLayout_2.addWidget(self.storePngBtn, 1, 1, 1, 1)
self.autoRangeBtn = QtGui.QPushButton(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
@@ -45,7 +39,7 @@ def setupUi(self, Form):
sizePolicy.setHeightForWidth(self.autoRangeBtn.sizePolicy().hasHeightForWidth())
self.autoRangeBtn.setSizePolicy(sizePolicy)
self.autoRangeBtn.setObjectName(_fromUtf8("autoRangeBtn"))
- self.gridLayout_2.addWidget(self.autoRangeBtn, 3, 0, 1, 2)
+ self.gridLayout_2.addWidget(self.autoRangeBtn, 2, 0, 1, 2)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setSpacing(0)
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
@@ -55,7 +49,7 @@ def setupUi(self, Form):
self.redirectCombo = CanvasCombo(self.layoutWidget)
self.redirectCombo.setObjectName(_fromUtf8("redirectCombo"))
self.horizontalLayout.addWidget(self.redirectCombo)
- self.gridLayout_2.addLayout(self.horizontalLayout, 6, 0, 1, 2)
+ self.gridLayout_2.addLayout(self.horizontalLayout, 5, 0, 1, 2)
self.itemList = TreeWidget(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
sizePolicy.setHorizontalStretch(0)
@@ -65,20 +59,20 @@ def setupUi(self, Form):
self.itemList.setHeaderHidden(True)
self.itemList.setObjectName(_fromUtf8("itemList"))
self.itemList.headerItem().setText(0, _fromUtf8("1"))
- self.gridLayout_2.addWidget(self.itemList, 7, 0, 1, 2)
+ self.gridLayout_2.addWidget(self.itemList, 6, 0, 1, 2)
self.ctrlLayout = QtGui.QGridLayout()
self.ctrlLayout.setSpacing(0)
self.ctrlLayout.setObjectName(_fromUtf8("ctrlLayout"))
- self.gridLayout_2.addLayout(self.ctrlLayout, 11, 0, 1, 2)
+ self.gridLayout_2.addLayout(self.ctrlLayout, 10, 0, 1, 2)
self.resetTransformsBtn = QtGui.QPushButton(self.layoutWidget)
self.resetTransformsBtn.setObjectName(_fromUtf8("resetTransformsBtn"))
- self.gridLayout_2.addWidget(self.resetTransformsBtn, 8, 0, 1, 1)
+ self.gridLayout_2.addWidget(self.resetTransformsBtn, 7, 0, 1, 1)
self.mirrorSelectionBtn = QtGui.QPushButton(self.layoutWidget)
self.mirrorSelectionBtn.setObjectName(_fromUtf8("mirrorSelectionBtn"))
- self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 4, 0, 1, 1)
+ self.gridLayout_2.addWidget(self.mirrorSelectionBtn, 3, 0, 1, 1)
self.reflectSelectionBtn = QtGui.QPushButton(self.layoutWidget)
self.reflectSelectionBtn.setObjectName(_fromUtf8("reflectSelectionBtn"))
- self.gridLayout_2.addWidget(self.reflectSelectionBtn, 4, 1, 1, 1)
+ self.gridLayout_2.addWidget(self.reflectSelectionBtn, 3, 1, 1, 1)
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form)
@@ -86,8 +80,6 @@ def setupUi(self, Form):
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
- self.storeSvgBtn.setText(QtGui.QApplication.translate("Form", "Store SVG", None, QtGui.QApplication.UnicodeUTF8))
- self.storePngBtn.setText(QtGui.QApplication.translate("Form", "Store PNG", None, QtGui.QApplication.UnicodeUTF8))
self.autoRangeBtn.setText(QtGui.QApplication.translate("Form", "Auto Range", None, QtGui.QApplication.UnicodeUTF8))
self.redirectCheck.setToolTip(QtGui.QApplication.translate("Form", "Check to display all local items in a remote canvas.", None, QtGui.QApplication.UnicodeUTF8))
self.redirectCheck.setText(QtGui.QApplication.translate("Form", "Redirect", None, QtGui.QApplication.UnicodeUTF8))
@@ -95,6 +87,6 @@ def retranslateUi(self, Form):
self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8))
self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8))
-from pyqtgraph.widgets.GraphicsView import GraphicsView
+from ..widgets.TreeWidget import TreeWidget
from CanvasManager import CanvasCombo
-from pyqtgraph.widgets.TreeWidget import TreeWidget
+from ..widgets.GraphicsView import GraphicsView
diff --git a/pyqtgraph/canvas/CanvasTemplate_pyside.py b/papi/pyqtgraph/canvas/CanvasTemplate_pyside.py
similarity index 94%
rename from pyqtgraph/canvas/CanvasTemplate_pyside.py
rename to papi/pyqtgraph/canvas/CanvasTemplate_pyside.py
index 12afdf25..56d1ff47 100644
--- a/pyqtgraph/canvas/CanvasTemplate_pyside.py
+++ b/papi/pyqtgraph/canvas/CanvasTemplate_pyside.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './canvas/CanvasTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/canvas/CanvasTemplate.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: pyside-uic 0.2.13 running on PySide 1.1.0
+# Created: Mon Dec 23 10:10:52 2013
+# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
@@ -90,6 +90,6 @@ def retranslateUi(self, Form):
self.mirrorSelectionBtn.setText(QtGui.QApplication.translate("Form", "Mirror Selection", None, QtGui.QApplication.UnicodeUTF8))
self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8))
-from pyqtgraph.widgets.GraphicsView import GraphicsView
+from ..widgets.TreeWidget import TreeWidget
from CanvasManager import CanvasCombo
-from pyqtgraph.widgets.TreeWidget import TreeWidget
+from ..widgets.GraphicsView import GraphicsView
diff --git a/pyqtgraph/canvas/TransformGuiTemplate.ui b/papi/pyqtgraph/canvas/TransformGuiTemplate.ui
similarity index 100%
rename from pyqtgraph/canvas/TransformGuiTemplate.ui
rename to papi/pyqtgraph/canvas/TransformGuiTemplate.ui
diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py b/papi/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py
similarity index 68%
rename from pyqtgraph/canvas/TransformGuiTemplate_pyqt.py
rename to papi/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py
index 1fb86d24..75c694c0 100644
--- a/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py
+++ b/papi/pyqtgraph/canvas/TransformGuiTemplate_pyqt.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './canvas/TransformGuiTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/canvas/TransformGuiTemplate.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: PyQt4 UI code generator 4.9.1
+# Created: Mon Dec 23 10:10:52 2013
+# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,16 @@
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
- _fromUtf8 = lambda s: s
+ def _fromUtf8(s):
+ return s
+
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
class Ui_Form(object):
def setupUi(self, Form):
@@ -51,10 +60,10 @@ def setupUi(self, Form):
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
- Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
- self.translateLabel.setText(QtGui.QApplication.translate("Form", "Translate:", None, QtGui.QApplication.UnicodeUTF8))
- self.rotateLabel.setText(QtGui.QApplication.translate("Form", "Rotate:", None, QtGui.QApplication.UnicodeUTF8))
- self.scaleLabel.setText(QtGui.QApplication.translate("Form", "Scale:", None, QtGui.QApplication.UnicodeUTF8))
- self.mirrorImageBtn.setText(QtGui.QApplication.translate("Form", "Mirror", None, QtGui.QApplication.UnicodeUTF8))
- self.reflectImageBtn.setText(QtGui.QApplication.translate("Form", "Reflect", None, QtGui.QApplication.UnicodeUTF8))
+ Form.setWindowTitle(_translate("Form", "Form", None))
+ self.translateLabel.setText(_translate("Form", "Translate:", None))
+ self.rotateLabel.setText(_translate("Form", "Rotate:", None))
+ self.scaleLabel.setText(_translate("Form", "Scale:", None))
+ self.mirrorImageBtn.setText(_translate("Form", "Mirror", None))
+ self.reflectImageBtn.setText(_translate("Form", "Reflect", None))
diff --git a/pyqtgraph/canvas/TransformGuiTemplate_pyside.py b/papi/pyqtgraph/canvas/TransformGuiTemplate_pyside.py
similarity index 93%
rename from pyqtgraph/canvas/TransformGuiTemplate_pyside.py
rename to papi/pyqtgraph/canvas/TransformGuiTemplate_pyside.py
index 47b23faa..bce7b511 100644
--- a/pyqtgraph/canvas/TransformGuiTemplate_pyside.py
+++ b/papi/pyqtgraph/canvas/TransformGuiTemplate_pyside.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './canvas/TransformGuiTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/canvas/TransformGuiTemplate.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: pyside-uic 0.2.13 running on PySide 1.1.0
+# Created: Mon Dec 23 10:10:52 2013
+# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
diff --git a/pyqtgraph/canvas/__init__.py b/papi/pyqtgraph/canvas/__init__.py
similarity index 100%
rename from pyqtgraph/canvas/__init__.py
rename to papi/pyqtgraph/canvas/__init__.py
diff --git a/pyqtgraph/colormap.py b/papi/pyqtgraph/colormap.py
similarity index 73%
rename from pyqtgraph/colormap.py
rename to papi/pyqtgraph/colormap.py
index d6169209..c0033708 100644
--- a/pyqtgraph/colormap.py
+++ b/papi/pyqtgraph/colormap.py
@@ -1,6 +1,5 @@
import numpy as np
-import scipy.interpolate
-from pyqtgraph.Qt import QtGui, QtCore
+from .Qt import QtGui, QtCore
class ColorMap(object):
"""
@@ -52,20 +51,20 @@ class ColorMap(object):
def __init__(self, pos, color, mode=None):
"""
- ========= ==============================================================
- Arguments
- pos Array of positions where each color is defined
- color Array of RGBA colors.
- Integer data types are interpreted as 0-255; float data types
- are interpreted as 0.0-1.0
- mode Array of color modes (ColorMap.RGB, HSV_POS, or HSV_NEG)
- indicating the color space that should be used when
- interpolating between stops. Note that the last mode value is
- ignored. By default, the mode is entirely RGB.
- ========= ==============================================================
+ =============== ==============================================================
+ **Arguments:**
+ pos Array of positions where each color is defined
+ color Array of RGBA colors.
+ Integer data types are interpreted as 0-255; float data types
+ are interpreted as 0.0-1.0
+ mode Array of color modes (ColorMap.RGB, HSV_POS, or HSV_NEG)
+ indicating the color space that should be used when
+ interpolating between stops. Note that the last mode value is
+ ignored. By default, the mode is entirely RGB.
+ =============== ==============================================================
"""
- self.pos = pos
- self.color = color
+ self.pos = np.array(pos)
+ self.color = np.array(color)
if mode is None:
mode = np.ones(len(pos))
self.mode = mode
@@ -92,15 +91,24 @@ def map(self, data, mode='byte'):
else:
pos, color = self.getStops(mode)
- data = np.clip(data, pos.min(), pos.max())
+ # don't need this--np.interp takes care of it.
+ #data = np.clip(data, pos.min(), pos.max())
- if not isinstance(data, np.ndarray):
- interp = scipy.interpolate.griddata(pos, color, np.array([data]))[0]
+ # Interpolate
+ # TODO: is griddata faster?
+ # interp = scipy.interpolate.griddata(pos, color, data)
+ if np.isscalar(data):
+ interp = np.empty((color.shape[1],), dtype=color.dtype)
else:
- interp = scipy.interpolate.griddata(pos, color, data)
-
- if mode == self.QCOLOR:
if not isinstance(data, np.ndarray):
+ data = np.array(data)
+ interp = np.empty(data.shape + (color.shape[1],), dtype=color.dtype)
+ for i in range(color.shape[1]):
+ interp[...,i] = np.interp(data, pos, color[:,i])
+
+ # Convert to QColor if requested
+ if mode == self.QCOLOR:
+ if np.isscalar(data):
return QtGui.QColor(*interp)
else:
return [QtGui.QColor(*x) for x in interp]
@@ -193,16 +201,16 @@ def getLookupTable(self, start=0.0, stop=1.0, nPts=512, alpha=None, mode='byte')
"""
Return an RGB(A) lookup table (ndarray).
- ============= ============================================================================
- **Arguments**
- start The starting value in the lookup table (default=0.0)
- stop The final value in the lookup table (default=1.0)
- nPts The number of points in the returned lookup table.
- alpha True, False, or None - Specifies whether or not alpha values are included
- in the table. If alpha is None, it will be automatically determined.
- mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'.
- See :func:`map() `.
- ============= ============================================================================
+ =============== =============================================================================
+ **Arguments:**
+ start The starting value in the lookup table (default=0.0)
+ stop The final value in the lookup table (default=1.0)
+ nPts The number of points in the returned lookup table.
+ alpha True, False, or None - Specifies whether or not alpha values are included
+ in the table. If alpha is None, it will be automatically determined.
+ mode Determines return type: 'byte' (0-255), 'float' (0.0-1.0), or 'qcolor'.
+ See :func:`map() `.
+ =============== =============================================================================
"""
if isinstance(mode, basestring):
mode = self.enumMap[mode.lower()]
@@ -236,4 +244,7 @@ def isMapTrivial(self):
else:
return np.all(self.color == np.array([[0,0,0,255], [255,255,255,255]]))
-
+ def __repr__(self):
+ pos = repr(self.pos).replace('\n', '')
+ color = repr(self.color).replace('\n', '')
+ return "ColorMap(%s, %s)" % (pos, color)
diff --git a/pyqtgraph/configfile.py b/papi/pyqtgraph/configfile.py
similarity index 91%
rename from pyqtgraph/configfile.py
rename to papi/pyqtgraph/configfile.py
index f709c786..c095bba3 100644
--- a/pyqtgraph/configfile.py
+++ b/papi/pyqtgraph/configfile.py
@@ -14,6 +14,10 @@
GLOBAL_PATH = None # so not thread safe.
from . import units
from .python2_3 import asUnicode
+from .Qt import QtCore
+from .Point import Point
+from .colormap import ColorMap
+import numpy
class ParseError(Exception):
def __init__(self, message, lineNum, line, fileName=None):
@@ -46,7 +50,7 @@ def readConfigFile(fname):
fname2 = os.path.join(GLOBAL_PATH, fname)
if os.path.exists(fname2):
fname = fname2
-
+
GLOBAL_PATH = os.path.dirname(os.path.abspath(fname))
try:
@@ -135,6 +139,17 @@ def parseString(lines, start=0):
local = units.allUnits.copy()
local['OrderedDict'] = OrderedDict
local['readConfigFile'] = readConfigFile
+ local['Point'] = Point
+ local['QtCore'] = QtCore
+ local['ColorMap'] = ColorMap
+ # Needed for reconstructing numpy arrays
+ local['array'] = numpy.array
+ for dtype in ['int8', 'uint8',
+ 'int16', 'uint16', 'float16',
+ 'int32', 'uint32', 'float32',
+ 'int64', 'uint64', 'float64']:
+ local[dtype] = getattr(numpy, dtype)
+
if len(k) < 1:
raise ParseError('Missing name preceding colon', ln+1, l)
if k[0] == '(' and k[-1] == ')': ## If the key looks like a tuple, try evaluating it.
diff --git a/pyqtgraph/console/CmdInput.py b/papi/pyqtgraph/console/CmdInput.py
similarity index 95%
rename from pyqtgraph/console/CmdInput.py
rename to papi/pyqtgraph/console/CmdInput.py
index 3e9730d6..24a01e89 100644
--- a/pyqtgraph/console/CmdInput.py
+++ b/papi/pyqtgraph/console/CmdInput.py
@@ -1,5 +1,5 @@
-from pyqtgraph.Qt import QtCore, QtGui
-from pyqtgraph.python2_3 import asUnicode
+from ..Qt import QtCore, QtGui
+from ..python2_3 import asUnicode
class CmdInput(QtGui.QLineEdit):
diff --git a/pyqtgraph/console/Console.py b/papi/pyqtgraph/console/Console.py
similarity index 91%
rename from pyqtgraph/console/Console.py
rename to papi/pyqtgraph/console/Console.py
index 982c2424..896de924 100644
--- a/pyqtgraph/console/Console.py
+++ b/papi/pyqtgraph/console/Console.py
@@ -1,14 +1,14 @@
-from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
+from ..Qt import QtCore, QtGui, USE_PYSIDE
import sys, re, os, time, traceback, subprocess
-import pyqtgraph as pg
if USE_PYSIDE:
from . import template_pyside as template
else:
from . import template_pyqt as template
-import pyqtgraph.exceptionHandling as exceptionHandling
+from .. import exceptionHandling as exceptionHandling
import pickle
+from .. import getConfigOption
class ConsoleWidget(QtGui.QWidget):
"""
@@ -31,16 +31,16 @@ class ConsoleWidget(QtGui.QWidget):
def __init__(self, parent=None, namespace=None, historyFile=None, text=None, editor=None):
"""
- ============ ============================================================================
- Arguments:
- namespace dictionary containing the initial variables present in the default namespace
- historyFile optional file for storing command history
- text initial text to display in the console window
- editor optional string for invoking code editor (called when stack trace entries are
- double-clicked). May contain {fileName} and {lineNum} format keys. Example::
+ ============== ============================================================================
+ **Arguments:**
+ namespace dictionary containing the initial variables present in the default namespace
+ historyFile optional file for storing command history
+ text initial text to display in the console window
+ editor optional string for invoking code editor (called when stack trace entries are
+ double-clicked). May contain {fileName} and {lineNum} format keys. Example::
- editorCommand --loadfile {fileName} --gotoline {lineNum}
- ============ =============================================================================
+ editorCommand --loadfile {fileName} --gotoline {lineNum}
+ ============== =============================================================================
"""
QtGui.QWidget.__init__(self, parent)
if namespace is None:
@@ -281,7 +281,7 @@ def stackItemClicked(self, item):
def stackItemDblClicked(self, item):
editor = self.editor
if editor is None:
- editor = pg.getConfigOption('editorCommand')
+ editor = getConfigOption('editorCommand')
if editor is None:
return
tb = self.currentFrame()
@@ -341,6 +341,17 @@ def checkException(self, excType, exc, tb):
filename = tb.tb_frame.f_code.co_filename
function = tb.tb_frame.f_code.co_name
+ filterStr = str(self.ui.filterText.text())
+ if filterStr != '':
+ if isinstance(exc, Exception):
+ msg = exc.message
+ elif isinstance(exc, basestring):
+ msg = exc
+ else:
+ msg = repr(exc)
+ match = re.search(filterStr, "%s:%s:%s" % (filename, function, msg))
+ return match is not None
+
## Go through a list of common exception points we like to ignore:
if excType is GeneratorExit or excType is StopIteration:
return False
diff --git a/pyqtgraph/console/__init__.py b/papi/pyqtgraph/console/__init__.py
similarity index 100%
rename from pyqtgraph/console/__init__.py
rename to papi/pyqtgraph/console/__init__.py
diff --git a/pyqtgraph/console/template.ui b/papi/pyqtgraph/console/template.ui
similarity index 91%
rename from pyqtgraph/console/template.ui
rename to papi/pyqtgraph/console/template.ui
index 6e5c5be3..1a672c5e 100644
--- a/pyqtgraph/console/template.ui
+++ b/papi/pyqtgraph/console/template.ui
@@ -6,7 +6,7 @@
0
0
- 710
+ 694
497
@@ -89,6 +89,16 @@
0
+
-
+
+
+ false
+
+
+ Clear Exception
+
+
+
-
@@ -109,7 +119,7 @@
- -
+
-
Only Uncaught Exceptions
@@ -119,14 +129,14 @@
- -
+
-
true
- -
+
-
Run commands in selected stack frame
@@ -136,24 +146,14 @@
- -
+
-
Exception Info
- -
-
-
- false
-
-
- Clear Exception
-
-
-
- -
+
-
Qt::Horizontal
@@ -166,6 +166,16 @@
+ -
+
+
+ Filter (regex):
+
+
+
+ -
+
+
diff --git a/pyqtgraph/console/template_pyqt.py b/papi/pyqtgraph/console/template_pyqt.py
similarity index 70%
rename from pyqtgraph/console/template_pyqt.py
rename to papi/pyqtgraph/console/template_pyqt.py
index 89ee6cff..354fb1d6 100644
--- a/pyqtgraph/console/template_pyqt.py
+++ b/papi/pyqtgraph/console/template_pyqt.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './console/template.ui'
+# Form implementation generated from reading ui file 'template.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: PyQt4 UI code generator 4.9.1
+# Created: Fri May 02 18:55:28 2014
+# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -12,12 +12,21 @@
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
- _fromUtf8 = lambda s: s
+ def _fromUtf8(s):
+ return s
+
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName(_fromUtf8("Form"))
- Form.resize(710, 497)
+ Form.resize(694, 497)
self.gridLayout = QtGui.QGridLayout(Form)
self.gridLayout.setMargin(0)
self.gridLayout.setSpacing(0)
@@ -62,6 +71,10 @@ def setupUi(self, Form):
self.gridLayout_2.setSpacing(0)
self.gridLayout_2.setContentsMargins(-1, 0, -1, 0)
self.gridLayout_2.setObjectName(_fromUtf8("gridLayout_2"))
+ self.clearExceptionBtn = QtGui.QPushButton(self.exceptionGroup)
+ self.clearExceptionBtn.setEnabled(False)
+ self.clearExceptionBtn.setObjectName(_fromUtf8("clearExceptionBtn"))
+ self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 6, 1, 1)
self.catchAllExceptionsBtn = QtGui.QPushButton(self.exceptionGroup)
self.catchAllExceptionsBtn.setCheckable(True)
self.catchAllExceptionsBtn.setObjectName(_fromUtf8("catchAllExceptionsBtn"))
@@ -73,39 +86,42 @@ def setupUi(self, Form):
self.onlyUncaughtCheck = QtGui.QCheckBox(self.exceptionGroup)
self.onlyUncaughtCheck.setChecked(True)
self.onlyUncaughtCheck.setObjectName(_fromUtf8("onlyUncaughtCheck"))
- self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 2, 1, 1)
+ self.gridLayout_2.addWidget(self.onlyUncaughtCheck, 0, 4, 1, 1)
self.exceptionStackList = QtGui.QListWidget(self.exceptionGroup)
self.exceptionStackList.setAlternatingRowColors(True)
self.exceptionStackList.setObjectName(_fromUtf8("exceptionStackList"))
- self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 5)
+ self.gridLayout_2.addWidget(self.exceptionStackList, 2, 0, 1, 7)
self.runSelectedFrameCheck = QtGui.QCheckBox(self.exceptionGroup)
self.runSelectedFrameCheck.setChecked(True)
self.runSelectedFrameCheck.setObjectName(_fromUtf8("runSelectedFrameCheck"))
- self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 5)
+ self.gridLayout_2.addWidget(self.runSelectedFrameCheck, 3, 0, 1, 7)
self.exceptionInfoLabel = QtGui.QLabel(self.exceptionGroup)
self.exceptionInfoLabel.setObjectName(_fromUtf8("exceptionInfoLabel"))
- self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 5)
- self.clearExceptionBtn = QtGui.QPushButton(self.exceptionGroup)
- self.clearExceptionBtn.setEnabled(False)
- self.clearExceptionBtn.setObjectName(_fromUtf8("clearExceptionBtn"))
- self.gridLayout_2.addWidget(self.clearExceptionBtn, 0, 4, 1, 1)
+ self.gridLayout_2.addWidget(self.exceptionInfoLabel, 1, 0, 1, 7)
spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
- self.gridLayout_2.addItem(spacerItem, 0, 3, 1, 1)
+ self.gridLayout_2.addItem(spacerItem, 0, 5, 1, 1)
+ self.label = QtGui.QLabel(self.exceptionGroup)
+ self.label.setObjectName(_fromUtf8("label"))
+ self.gridLayout_2.addWidget(self.label, 0, 2, 1, 1)
+ self.filterText = QtGui.QLineEdit(self.exceptionGroup)
+ self.filterText.setObjectName(_fromUtf8("filterText"))
+ self.gridLayout_2.addWidget(self.filterText, 0, 3, 1, 1)
self.gridLayout.addWidget(self.splitter, 0, 0, 1, 1)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
- Form.setWindowTitle(QtGui.QApplication.translate("Form", "Console", None, QtGui.QApplication.UnicodeUTF8))
- self.historyBtn.setText(QtGui.QApplication.translate("Form", "History..", None, QtGui.QApplication.UnicodeUTF8))
- self.exceptionBtn.setText(QtGui.QApplication.translate("Form", "Exceptions..", None, QtGui.QApplication.UnicodeUTF8))
- self.exceptionGroup.setTitle(QtGui.QApplication.translate("Form", "Exception Handling", None, QtGui.QApplication.UnicodeUTF8))
- self.catchAllExceptionsBtn.setText(QtGui.QApplication.translate("Form", "Show All Exceptions", None, QtGui.QApplication.UnicodeUTF8))
- self.catchNextExceptionBtn.setText(QtGui.QApplication.translate("Form", "Show Next Exception", None, QtGui.QApplication.UnicodeUTF8))
- self.onlyUncaughtCheck.setText(QtGui.QApplication.translate("Form", "Only Uncaught Exceptions", None, QtGui.QApplication.UnicodeUTF8))
- self.runSelectedFrameCheck.setText(QtGui.QApplication.translate("Form", "Run commands in selected stack frame", None, QtGui.QApplication.UnicodeUTF8))
- self.exceptionInfoLabel.setText(QtGui.QApplication.translate("Form", "Exception Info", None, QtGui.QApplication.UnicodeUTF8))
- self.clearExceptionBtn.setText(QtGui.QApplication.translate("Form", "Clear Exception", None, QtGui.QApplication.UnicodeUTF8))
+ Form.setWindowTitle(_translate("Form", "Console", None))
+ self.historyBtn.setText(_translate("Form", "History..", None))
+ self.exceptionBtn.setText(_translate("Form", "Exceptions..", None))
+ self.exceptionGroup.setTitle(_translate("Form", "Exception Handling", None))
+ self.clearExceptionBtn.setText(_translate("Form", "Clear Exception", None))
+ self.catchAllExceptionsBtn.setText(_translate("Form", "Show All Exceptions", None))
+ self.catchNextExceptionBtn.setText(_translate("Form", "Show Next Exception", None))
+ self.onlyUncaughtCheck.setText(_translate("Form", "Only Uncaught Exceptions", None))
+ self.runSelectedFrameCheck.setText(_translate("Form", "Run commands in selected stack frame", None))
+ self.exceptionInfoLabel.setText(_translate("Form", "Exception Info", None))
+ self.label.setText(_translate("Form", "Filter (regex):", None))
from .CmdInput import CmdInput
diff --git a/pyqtgraph/console/template_pyside.py b/papi/pyqtgraph/console/template_pyside.py
similarity index 97%
rename from pyqtgraph/console/template_pyside.py
rename to papi/pyqtgraph/console/template_pyside.py
index 0493a0fe..2db8ed95 100644
--- a/pyqtgraph/console/template_pyside.py
+++ b/papi/pyqtgraph/console/template_pyside.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './console/template.ui'
+# Form implementation generated from reading ui file './pyqtgraph/console/template.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: pyside-uic 0.2.13 running on PySide 1.1.0
+# Created: Mon Dec 23 10:10:53 2013
+# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
diff --git a/pyqtgraph/debug.py b/papi/pyqtgraph/debug.py
similarity index 73%
rename from pyqtgraph/debug.py
rename to papi/pyqtgraph/debug.py
index a175be9c..57c71bc8 100644
--- a/pyqtgraph/debug.py
+++ b/papi/pyqtgraph/debug.py
@@ -5,10 +5,14 @@
Distributed under MIT/X11 license. See license.txt for more infomation.
"""
-import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile
+from __future__ import print_function
+
+import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile, threading
from . import ptime
from numpy import ndarray
from .Qt import QtCore, QtGui
+from .util.mutex import Mutex
+from .util import cprint
__ftraceDepth = 0
def ftrace(func):
@@ -28,6 +32,57 @@ def w(*args, **kargs):
return rv
return w
+
+class Tracer(object):
+ """
+ Prints every function enter/exit. Useful for debugging crashes / lockups.
+ """
+ def __init__(self):
+ self.count = 0
+ self.stack = []
+
+ def trace(self, frame, event, arg):
+ self.count += 1
+ # If it has been a long time since we saw the top of the stack,
+ # print a reminder
+ if self.count % 1000 == 0:
+ print("----- current stack: -----")
+ for line in self.stack:
+ print(line)
+ if event == 'call':
+ line = " " * len(self.stack) + ">> " + self.frameInfo(frame)
+ print(line)
+ self.stack.append(line)
+ elif event == 'return':
+ self.stack.pop()
+ line = " " * len(self.stack) + "<< " + self.frameInfo(frame)
+ print(line)
+ if len(self.stack) == 0:
+ self.count = 0
+
+ return self.trace
+
+ def stop(self):
+ sys.settrace(None)
+
+ def start(self):
+ sys.settrace(self.trace)
+
+ def frameInfo(self, fr):
+ filename = fr.f_code.co_filename
+ funcname = fr.f_code.co_name
+ lineno = fr.f_lineno
+ callfr = sys._getframe(3)
+ callline = "%s %d" % (callfr.f_code.co_name, callfr.f_lineno)
+ args, _, _, value_dict = inspect.getargvalues(fr)
+ if len(args) and args[0] == 'self':
+ instance = value_dict.get('self', None)
+ if instance is not None:
+ cls = getattr(instance, '__class__', None)
+ if cls is not None:
+ funcname = cls.__name__ + "." + funcname
+ return "%s: %s %s: %s" % (callline, filename, lineno, funcname)
+
def warnOnException(func):
"""Decorator which catches/ignores exceptions and prints a stack trace."""
def w(*args, **kwds):
@@ -37,17 +92,22 @@ def w(*args, **kwds):
printExc('Ignored exception:')
return w
-def getExc(indent=4, prefix='| '):
- tb = traceback.format_exc()
- lines = []
- for l in tb.split('\n'):
- lines.append(" "*indent + prefix + l)
- return '\n'.join(lines)
+def getExc(indent=4, prefix='| ', skip=1):
+ lines = (traceback.format_stack()[:-skip]
+ + [" ---- exception caught ---->\n"]
+ + traceback.format_tb(sys.exc_info()[2])
+ + traceback.format_exception_only(*sys.exc_info()[:2]))
+ lines2 = []
+ for l in lines:
+ lines2.extend(l.strip('\n').split('\n'))
+ lines3 = [" "*indent + prefix + l for l in lines2]
+ return '\n'.join(lines3)
+
def printExc(msg='', indent=4, prefix='|'):
"""Print an error message followed by an indented exception backtrace
(This function is intended to be called within except: blocks)"""
- exc = getExc(indent, prefix + ' ')
+ exc = getExc(indent, prefix + ' ', skip=2)
print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg))
print(" "*indent + prefix + '='*30 + '>>')
print(exc)
@@ -236,7 +296,8 @@ def refPathString(chain):
def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False):
"""Guess how much memory an object is using"""
- ignoreTypes = [types.MethodType, types.UnboundMethodType, types.BuiltinMethodType, types.FunctionType, types.BuiltinFunctionType]
+ ignoreTypes = ['MethodType', 'UnboundMethodType', 'BuiltinMethodType', 'FunctionType', 'BuiltinFunctionType']
+ ignoreTypes = [getattr(types, key) for key in ignoreTypes if hasattr(types, key)]
ignoreRegex = re.compile('(method-wrapper|Flag|ItemChange|Option|Mode)')
@@ -365,84 +426,130 @@ def __getitem__(self, item):
return self.objs[item]
-class Profiler:
+
+
+class Profiler(object):
"""Simple profiler allowing measurement of multiple time intervals.
- Arguments:
- msg: message to print at start and finish of profiling
- disabled: If true, profiler does nothing (so you can leave it in place)
- delayed: If true, all messages are printed after call to finish()
- (this can result in more accurate time step measurements)
- globalDelay: if True, all nested profilers delay printing until the top level finishes
-
+
+ By default, profilers are disabled. To enable profiling, set the
+ environment variable `PYQTGRAPHPROFILE` to a comma-separated list of
+ fully-qualified names of profiled functions.
+
+ Calling a profiler registers a message (defaulting to an increasing
+ counter) that contains the time elapsed since the last call. When the
+ profiler is about to be garbage-collected, the messages are passed to the
+ outer profiler if one is running, or printed to stdout otherwise.
+
+ If `delayed` is set to False, messages are immediately printed instead.
+
Example:
- prof = Profiler('Function')
- ... do stuff ...
- prof.mark('did stuff')
- ... do other stuff ...
- prof.mark('did other stuff')
- prof.finish()
+ def function(...):
+ profiler = Profiler()
+ ... do stuff ...
+ profiler('did stuff')
+ ... do other stuff ...
+ profiler('did other stuff')
+ # profiler is garbage-collected and flushed at function end
+
+ If this function is a method of class C, setting `PYQTGRAPHPROFILE` to
+ "C.function" (without the module name) will enable this profiler.
+
+ For regular functions, use the qualified name of the function, stripping
+ only the initial "pyqtgraph." prefix from the module.
"""
- depth = 0
- msgs = []
+
+ _profilers = os.environ.get("PYQTGRAPHPROFILE", None)
+ _profilers = _profilers.split(",") if _profilers is not None else []
- def __init__(self, msg="Profiler", disabled=False, delayed=True, globalDelay=True):
- self.disabled = disabled
- if disabled:
- return
-
- self.markCount = 0
- self.finished = False
- self.depth = Profiler.depth
- Profiler.depth += 1
- if not globalDelay:
- self.msgs = []
- self.delayed = delayed
- self.msg = " "*self.depth + msg
- msg2 = self.msg + " >>> Started"
- if self.delayed:
- self.msgs.append(msg2)
- else:
- print(msg2)
- self.t0 = ptime.time()
- self.t1 = self.t0
+ _depth = 0
+ _msgs = []
+ disable = False # set this flag to disable all or individual profilers at runtime
- def mark(self, msg=None):
- if self.disabled:
- return
+ class DisabledProfiler(object):
+ def __init__(self, *args, **kwds):
+ pass
+ def __call__(self, *args):
+ pass
+ def finish(self):
+ pass
+ def mark(self, msg=None):
+ pass
+ _disabledProfiler = DisabledProfiler()
+ def __new__(cls, msg=None, disabled='env', delayed=True):
+ """Optionally create a new profiler based on caller's qualname.
+ """
+ if disabled is True or (disabled == 'env' and len(cls._profilers) == 0):
+ return cls._disabledProfiler
+
+ # determine the qualified name of the caller function
+ caller_frame = sys._getframe(1)
+ try:
+ caller_object_type = type(caller_frame.f_locals["self"])
+ except KeyError: # we are in a regular function
+ qualifier = caller_frame.f_globals["__name__"].split(".", 1)[1]
+ else: # we are in a method
+ qualifier = caller_object_type.__name__
+ func_qualname = qualifier + "." + caller_frame.f_code.co_name
+ if disabled == 'env' and func_qualname not in cls._profilers: # don't do anything
+ return cls._disabledProfiler
+ # create an actual profiling object
+ cls._depth += 1
+ obj = super(Profiler, cls).__new__(cls)
+ obj._name = msg or func_qualname
+ obj._delayed = delayed
+ obj._markCount = 0
+ obj._finished = False
+ obj._firstTime = obj._lastTime = ptime.time()
+ obj._newMsg("> Entering " + obj._name)
+ return obj
+
+ def __call__(self, msg=None):
+ """Register or print a new message with timing information.
+ """
+ if self.disable:
+ return
if msg is None:
- msg = str(self.markCount)
- self.markCount += 1
+ msg = str(self._markCount)
+ self._markCount += 1
+ newTime = ptime.time()
+ self._newMsg(" %s: %0.4f ms",
+ msg, (newTime - self._lastTime) * 1000)
+ self._lastTime = newTime
- t1 = ptime.time()
- msg2 = " "+self.msg+" "+msg+" "+"%gms" % ((t1-self.t1)*1000)
- if self.delayed:
- self.msgs.append(msg2)
+ def mark(self, msg=None):
+ self(msg)
+
+ def _newMsg(self, msg, *args):
+ msg = " " * (self._depth - 1) + msg
+ if self._delayed:
+ self._msgs.append((msg, args))
else:
- print(msg2)
- self.t1 = ptime.time() ## don't measure time it took to print
-
+ self.flush()
+ print(msg % args)
+
+ def __del__(self):
+ self.finish()
+
def finish(self, msg=None):
- if self.disabled or self.finished:
- return
-
+ """Add a final message; flush the message list if no parent profiler.
+ """
+ if self._finished or self.disable:
+ return
+ self._finished = True
if msg is not None:
- self.mark(msg)
- t1 = ptime.time()
- msg = self.msg + ' <<< Finished, total time: %gms' % ((t1-self.t0)*1000)
- if self.delayed:
- self.msgs.append(msg)
- if self.depth == 0:
- for line in self.msgs:
- print(line)
- Profiler.msgs = []
- else:
- print(msg)
- Profiler.depth = self.depth
- self.finished = True
-
-
+ self(msg)
+ self._newMsg("< Exiting %s, total time: %0.4f ms",
+ self._name, (ptime.time() - self._firstTime) * 1000)
+ type(self)._depth -= 1
+ if self._depth < 1:
+ self.flush()
+ def flush(self):
+ if self._msgs:
+ print("\n".join([m[0]%m[1] for m in self._msgs]))
+ type(self)._msgs = []
+
def profile(code, name='profile_run', sort='cumulative', num=30):
"""Common-use for cProfile"""
@@ -573,12 +680,12 @@ def diff(self, **kargs):
## Which refs have disappeared since call to start() (these are only displayed once, then forgotten.)
delRefs = {}
- for i in self.startRefs.keys():
+ for i in list(self.startRefs.keys()):
if i not in refs:
delRefs[i] = self.startRefs[i]
del self.startRefs[i]
self.forgetRef(delRefs[i])
- for i in self.newRefs.keys():
+ for i in list(self.newRefs.keys()):
if i not in refs:
delRefs[i] = self.newRefs[i]
del self.newRefs[i]
@@ -616,7 +723,8 @@ def diff(self, **kargs):
for k in self.startCount:
c1[k] = c1.get(k, 0) - self.startCount[k]
typs = list(c1.keys())
- typs.sort(lambda a,b: cmp(c1[a], c1[b]))
+ #typs.sort(lambda a,b: cmp(c1[a], c1[b]))
+ typs.sort(key=lambda a: c1[a])
for t in typs:
if c1[t] == 0:
continue
@@ -716,7 +824,8 @@ def report(self, refs, allobjs=None, showIDs=False):
c = count.get(typ, [0,0])
count[typ] = [c[0]+1, c[1]+objectSize(obj)]
typs = list(count.keys())
- typs.sort(lambda a,b: cmp(count[a][1], count[b][1]))
+ #typs.sort(lambda a,b: cmp(count[a][1], count[b][1]))
+ typs.sort(key=lambda a: count[a][1])
for t in typs:
line = " %d\t%d\t%s" % (count[t][0], count[t][1], t)
@@ -776,14 +885,15 @@ def describeObj(obj, depth=4, path=None, ignore=None):
def typeStr(obj):
"""Create a more useful type string by making types report their class."""
typ = type(obj)
- if typ == types.InstanceType:
+ if typ == getattr(types, 'InstanceType', None):
return "" % obj.__class__.__name__
else:
return str(typ)
def searchRefs(obj, *args):
"""Pseudo-interactive function for tracing references backward.
- Arguments:
+ **Arguments:**
+
obj: The initial object from which to start searching
args: A set of string or int arguments.
each integer selects one of obj's referrers to be the new 'obj'
@@ -795,7 +905,8 @@ def searchRefs(obj, *args):
ro: return obj
rr: return list of obj's referrers
- Examples:
+ Examples::
+
searchRefs(obj, 't') ## Print types of all objects referring to obj
searchRefs(obj, 't', 0, 't') ## ..then select the first referrer and print the types of its referrers
searchRefs(obj, 't', 0, 't', 'l') ## ..also print lengths of the last set of referrers
@@ -928,6 +1039,7 @@ def qObjectReport(verbose=False):
class PrintDetector(object):
+ """Find code locations that print to stdout."""
def __init__(self):
self.stdout = sys.stdout
sys.stdout = self
@@ -943,4 +1055,115 @@ def write(self, x):
traceback.print_stack()
def flush(self):
- self.stdout.flush()
\ No newline at end of file
+ self.stdout.flush()
+
+
+def listQThreads():
+ """Prints Thread IDs (Qt's, not OS's) for all QThreads."""
+ thr = findObj('[Tt]hread')
+ thr = [t for t in thr if isinstance(t, QtCore.QThread)]
+ import sip
+ for t in thr:
+ print("--> ", t)
+ print(" Qt ID: 0x%x" % sip.unwrapinstance(t))
+
+
+def pretty(data, indent=''):
+ """Format nested dict/list/tuple structures into a more human-readable string
+ This function is a bit better than pprint for displaying OrderedDicts.
+ """
+ ret = ""
+ ind2 = indent + " "
+ if isinstance(data, dict):
+ ret = indent+"{\n"
+ for k, v in data.iteritems():
+ ret += ind2 + repr(k) + ": " + pretty(v, ind2).strip() + "\n"
+ ret += indent+"}\n"
+ elif isinstance(data, list) or isinstance(data, tuple):
+ s = repr(data)
+ if len(s) < 40:
+ ret += indent + s
+ else:
+ if isinstance(data, list):
+ d = '[]'
+ else:
+ d = '()'
+ ret = indent+d[0]+"\n"
+ for i, v in enumerate(data):
+ ret += ind2 + str(i) + ": " + pretty(v, ind2).strip() + "\n"
+ ret += indent+d[1]+"\n"
+ else:
+ ret += indent + repr(data)
+ return ret
+
+
+class PeriodicTrace(object):
+ """
+ Used to debug freezing by starting a new thread that reports on the
+ location of the main thread periodically.
+ """
+ class ReportThread(QtCore.QThread):
+ def __init__(self):
+ self.frame = None
+ self.ind = 0
+ self.lastInd = None
+ self.lock = Mutex()
+ QtCore.QThread.__init__(self)
+
+ def notify(self, frame):
+ with self.lock:
+ self.frame = frame
+ self.ind += 1
+
+ def run(self):
+ while True:
+ time.sleep(1)
+ with self.lock:
+ if self.lastInd != self.ind:
+ print("== Trace %d: ==" % self.ind)
+ traceback.print_stack(self.frame)
+ self.lastInd = self.ind
+
+ def __init__(self):
+ self.mainThread = threading.current_thread()
+ self.thread = PeriodicTrace.ReportThread()
+ self.thread.start()
+ sys.settrace(self.trace)
+
+ def trace(self, frame, event, arg):
+ if threading.current_thread() is self.mainThread: # and 'threading' not in frame.f_code.co_filename:
+ self.thread.notify(frame)
+ # print("== Trace ==", event, arg)
+ # traceback.print_stack(frame)
+ return self.trace
+
+
+
+class ThreadColor(object):
+ """
+ Wrapper on stdout/stderr that colors text by the current thread ID.
+
+ *stream* must be 'stdout' or 'stderr'.
+ """
+ colors = {}
+ lock = Mutex()
+
+ def __init__(self, stream):
+ self.stream = getattr(sys, stream)
+ self.err = stream == 'stderr'
+ setattr(sys, stream, self)
+
+ def write(self, msg):
+ with self.lock:
+ cprint.cprint(self.stream, self.color(), msg, -1, stderr=self.err)
+
+ def flush(self):
+ with self.lock:
+ self.stream.flush()
+
+ def color(self):
+ tid = threading.current_thread()
+ if tid not in self.colors:
+ c = (len(self.colors) % 15) + 1
+ self.colors[tid] = c
+ return self.colors[tid]
diff --git a/pyqtgraph/dockarea/Container.py b/papi/pyqtgraph/dockarea/Container.py
similarity index 96%
rename from pyqtgraph/dockarea/Container.py
rename to papi/pyqtgraph/dockarea/Container.py
index 83610937..c3225edf 100644
--- a/pyqtgraph/dockarea/Container.py
+++ b/papi/pyqtgraph/dockarea/Container.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui
+from ..Qt import QtCore, QtGui
import weakref
class Container(object):
@@ -22,6 +22,9 @@ def type(self):
return None
def insert(self, new, pos=None, neighbor=None):
+ # remove from existing parent first
+ new.setParent(None)
+
if not isinstance(new, list):
new = [new]
if neighbor is None:
@@ -241,6 +244,13 @@ def tabClicked(self, tab, ev=None):
else:
w.label.setDim(True)
+ def raiseDock(self, dock):
+ """Move *dock* to the top of the stack"""
+ self.stack.currentWidget().label.setDim(True)
+ self.stack.setCurrentWidget(dock)
+ dock.label.setDim(False)
+
+
def type(self):
return 'tab'
diff --git a/pyqtgraph/dockarea/Dock.py b/papi/pyqtgraph/dockarea/Dock.py
similarity index 76%
rename from pyqtgraph/dockarea/Dock.py
rename to papi/pyqtgraph/dockarea/Dock.py
index 414980ac..28d4244b 100644
--- a/pyqtgraph/dockarea/Dock.py
+++ b/papi/pyqtgraph/dockarea/Dock.py
@@ -1,17 +1,20 @@
-from pyqtgraph.Qt import QtCore, QtGui
+from ..Qt import QtCore, QtGui
from .DockDrop import *
-from pyqtgraph.widgets.VerticalLabel import VerticalLabel
+from ..widgets.VerticalLabel import VerticalLabel
+from ..python2_3 import asUnicode
class Dock(QtGui.QWidget, DockDrop):
sigStretchChanged = QtCore.Signal()
- def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True):
+ def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True, closable=False):
QtGui.QWidget.__init__(self)
DockDrop.__init__(self)
self.area = area
- self.label = DockLabel(name, self)
+ self.label = DockLabel(name, self, closable)
+ if closable:
+ self.label.sigCloseClicked.connect(self.close)
self.labelHidden = False
self.moveLabel = True ## If false, the dock is no longer allowed to move the label.
self.autoOrient = autoOrientation
@@ -34,30 +37,30 @@ def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False,
#self.titlePos = 'top'
self.raiseOverlay()
self.hStyle = """
- Dock > QWidget {
- border: 1px solid #000;
- border-radius: 5px;
- border-top-left-radius: 0px;
- border-top-right-radius: 0px;
+ Dock > QWidget {
+ border: 1px solid #000;
+ border-radius: 5px;
+ border-top-left-radius: 0px;
+ border-top-right-radius: 0px;
border-top-width: 0px;
}"""
self.vStyle = """
- Dock > QWidget {
- border: 1px solid #000;
- border-radius: 5px;
- border-top-left-radius: 0px;
- border-bottom-left-radius: 0px;
+ Dock > QWidget {
+ border: 1px solid #000;
+ border-radius: 5px;
+ border-top-left-radius: 0px;
+ border-bottom-left-radius: 0px;
border-left-width: 0px;
}"""
self.nStyle = """
- Dock > QWidget {
- border: 1px solid #000;
- border-radius: 5px;
+ Dock > QWidget {
+ border: 1px solid #000;
+ border-radius: 5px;
}"""
self.dragStyle = """
- Dock > QWidget {
- border: 4px solid #00F;
- border-radius: 5px;
+ Dock > QWidget {
+ border: 4px solid #00F;
+ border-radius: 5px;
}"""
self.setAutoFillBackground(False)
self.widgetArea.setStyleSheet(self.hStyle)
@@ -78,7 +81,7 @@ def implements(self, name=None):
def setStretch(self, x=None, y=None):
"""
- Set the 'target' size for this Dock.
+ Set the 'target' size for this Dock.
The actual size will be determined by comparing this Dock's
stretch value to the rest of the docks it shares space with.
"""
@@ -129,7 +132,7 @@ def setOrientation(self, o='auto', force=False):
Sets the orientation of the title bar for this Dock.
Must be one of 'auto', 'horizontal', or 'vertical'.
By default ('auto'), the orientation is determined
- based on the aspect ratio of the Dock.
+ based on the aspect ratio of the Dock.
"""
#print self.name(), "setOrientation", o, force
if o == 'auto' and self.autoOrient:
@@ -167,14 +170,14 @@ def resizeEvent(self, ev):
self.resizeOverlay(self.size())
def name(self):
- return str(self.label.text())
+ return asUnicode(self.label.text())
def container(self):
return self._container
def addWidget(self, widget, row=None, col=0, rowspan=1, colspan=1):
"""
- Add a new widget to the interior of this Dock.
+ Add a new widget to the interior of this Dock.
Each Dock uses a QGridLayout to arrange widgets within.
"""
if row is None:
@@ -208,6 +211,11 @@ def containerChanged(self, c):
self.moveLabel = False
self.setOrientation(force=True)
+
+ def raiseDock(self):
+ """If this Dock is stacked underneath others, raise it to the top."""
+ self.container().raiseDock(self)
+
def close(self):
"""Remove this dock from the DockArea it lives inside."""
@@ -233,11 +241,13 @@ def dragLeaveEvent(self, *args):
def dropEvent(self, *args):
DockDrop.dropEvent(self, *args)
+
class DockLabel(VerticalLabel):
sigClicked = QtCore.Signal(object, object)
+ sigCloseClicked = QtCore.Signal()
- def __init__(self, text, dock):
+ def __init__(self, text, dock, showCloseButton):
self.dim = False
self.fixedWidth = False
VerticalLabel.__init__(self, text, orientation='horizontal', forceWidth=False)
@@ -245,10 +255,13 @@ def __init__(self, text, dock):
self.dock = dock
self.updateStyle()
self.setAutoFillBackground(False)
+ self.startedDrag = False
- #def minimumSizeHint(self):
- ##sh = QtGui.QWidget.minimumSizeHint(self)
- #return QtCore.QSize(20, 20)
+ self.closeButton = None
+ if showCloseButton:
+ self.closeButton = QtGui.QToolButton(self)
+ self.closeButton.clicked.connect(self.sigCloseClicked)
+ self.closeButton.setIcon(QtGui.QApplication.style().standardIcon(QtGui.QStyle.SP_TitleBarCloseButton))
def updateStyle(self):
r = '3px'
@@ -262,28 +275,28 @@ def updateStyle(self):
border = '#55B'
if self.orientation == 'vertical':
- self.vStyle = """DockLabel {
- background-color : %s;
- color : %s;
- border-top-right-radius: 0px;
- border-top-left-radius: %s;
- border-bottom-right-radius: 0px;
- border-bottom-left-radius: %s;
- border-width: 0px;
+ self.vStyle = """DockLabel {
+ background-color : %s;
+ color : %s;
+ border-top-right-radius: 0px;
+ border-top-left-radius: %s;
+ border-bottom-right-radius: 0px;
+ border-bottom-left-radius: %s;
+ border-width: 0px;
border-right: 2px solid %s;
padding-top: 3px;
padding-bottom: 3px;
}""" % (bg, fg, r, r, border)
self.setStyleSheet(self.vStyle)
else:
- self.hStyle = """DockLabel {
- background-color : %s;
- color : %s;
- border-top-right-radius: %s;
- border-top-left-radius: %s;
- border-bottom-right-radius: 0px;
- border-bottom-left-radius: 0px;
- border-width: 0px;
+ self.hStyle = """DockLabel {
+ background-color : %s;
+ color : %s;
+ border-top-right-radius: %s;
+ border-top-left-radius: %s;
+ border-bottom-right-radius: 0px;
+ border-bottom-left-radius: 0px;
+ border-width: 0px;
border-bottom: 2px solid %s;
padding-left: 3px;
padding-right: 3px;
@@ -309,11 +322,9 @@ def mouseMoveEvent(self, ev):
if not self.startedDrag and (ev.pos() - self.pressPos).manhattanLength() > QtGui.QApplication.startDragDistance():
self.dock.startDrag()
ev.accept()
- #print ev.pos()
def mouseReleaseEvent(self, ev):
if not self.startedDrag:
- #self.emit(QtCore.SIGNAL('clicked'), self, ev)
self.sigClicked.emit(self, ev)
ev.accept()
@@ -321,13 +332,14 @@ def mouseDoubleClickEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton:
self.dock.float()
- #def paintEvent(self, ev):
- #p = QtGui.QPainter(self)
- ##p.setBrush(QtGui.QBrush(QtGui.QColor(100, 100, 200)))
- #p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 100)))
- #p.drawRect(self.rect().adjusted(0, 0, -1, -1))
-
- #VerticalLabel.paintEvent(self, ev)
-
-
-
+ def resizeEvent (self, ev):
+ if self.closeButton:
+ if self.orientation == 'vertical':
+ size = ev.size().width()
+ pos = QtCore.QPoint(0, 0)
+ else:
+ size = ev.size().height()
+ pos = QtCore.QPoint(ev.size().width() - size, 0)
+ self.closeButton.setFixedSize(QtCore.QSize(size, size))
+ self.closeButton.move(pos)
+ super(DockLabel,self).resizeEvent(ev)
diff --git a/pyqtgraph/dockarea/DockArea.py b/papi/pyqtgraph/dockarea/DockArea.py
similarity index 92%
rename from pyqtgraph/dockarea/DockArea.py
rename to papi/pyqtgraph/dockarea/DockArea.py
index 882b29a3..a75d881d 100644
--- a/pyqtgraph/dockarea/DockArea.py
+++ b/papi/pyqtgraph/dockarea/DockArea.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui
+from ..Qt import QtCore, QtGui
from .Container import *
from .DockDrop import *
from .Dock import Dock
-import pyqtgraph.debug as debug
+from .. import debug as debug
import weakref
## TODO:
@@ -36,16 +36,16 @@ def type(self):
def addDock(self, dock=None, position='bottom', relativeTo=None, **kwds):
"""Adds a dock to this area.
- =========== =================================================================
- Arguments:
- dock The new Dock object to add. If None, then a new Dock will be
- created.
- position 'bottom', 'top', 'left', 'right', 'above', or 'below'
- relativeTo If relativeTo is None, then the new Dock is added to fill an
- entire edge of the window. If relativeTo is another Dock, then
- the new Dock is placed adjacent to it (or in a tabbed
- configuration for 'above' and 'below').
- =========== =================================================================
+ ============== =================================================================
+ **Arguments:**
+ dock The new Dock object to add. If None, then a new Dock will be
+ created.
+ position 'bottom', 'top', 'left', 'right', 'above', or 'below'
+ relativeTo If relativeTo is None, then the new Dock is added to fill an
+ entire edge of the window. If relativeTo is another Dock, then
+ the new Dock is placed adjacent to it (or in a tabbed
+ configuration for 'above' and 'below').
+ ============== =================================================================
All extra keyword arguments are passed to Dock.__init__() if *dock* is
None.
diff --git a/pyqtgraph/dockarea/DockDrop.py b/papi/pyqtgraph/dockarea/DockDrop.py
similarity index 99%
rename from pyqtgraph/dockarea/DockDrop.py
rename to papi/pyqtgraph/dockarea/DockDrop.py
index acab28cd..bd364f50 100644
--- a/pyqtgraph/dockarea/DockDrop.py
+++ b/papi/pyqtgraph/dockarea/DockDrop.py
@@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui
+from ..Qt import QtCore, QtGui
class DockDrop(object):
"""Provides dock-dropping methods"""
diff --git a/pyqtgraph/dockarea/__init__.py b/papi/pyqtgraph/dockarea/__init__.py
similarity index 100%
rename from pyqtgraph/dockarea/__init__.py
rename to papi/pyqtgraph/dockarea/__init__.py
diff --git a/papi/pyqtgraph/dockarea/tests/test_dock.py b/papi/pyqtgraph/dockarea/tests/test_dock.py
new file mode 100644
index 00000000..949f3f0e
--- /dev/null
+++ b/papi/pyqtgraph/dockarea/tests/test_dock.py
@@ -0,0 +1,16 @@
+# -*- coding: utf-8 -*-
+#import sip
+#sip.setapi('QString', 1)
+
+import pyqtgraph as pg
+pg.mkQApp()
+
+import pyqtgraph.dockarea as da
+
+def test_dock():
+ name = pg.asUnicode("évènts_zà héér")
+ dock = da.Dock(name=name)
+ # make sure unicode names work correctly
+ assert dock.name() == name
+ # no surprises in return type.
+ assert type(dock.name()) == type(name)
diff --git a/pyqtgraph/exceptionHandling.py b/papi/pyqtgraph/exceptionHandling.py
similarity index 56%
rename from pyqtgraph/exceptionHandling.py
rename to papi/pyqtgraph/exceptionHandling.py
index daa821b7..3182b7eb 100644
--- a/pyqtgraph/exceptionHandling.py
+++ b/papi/pyqtgraph/exceptionHandling.py
@@ -49,29 +49,45 @@ def setTracebackClearing(clear=True):
class ExceptionHandler(object):
def __call__(self, *args):
- ## call original exception handler first (prints exception)
- global original_excepthook, callbacks, clear_tracebacks
- print("===== %s =====" % str(time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time()))))
- ret = original_excepthook(*args)
+ ## Start by extending recursion depth just a bit.
+ ## If the error we are catching is due to recursion, we don't want to generate another one here.
+ recursionLimit = sys.getrecursionlimit()
+ try:
+ sys.setrecursionlimit(recursionLimit+100)
- for cb in callbacks:
+
+ ## call original exception handler first (prints exception)
+ global original_excepthook, callbacks, clear_tracebacks
try:
- cb(*args)
- except:
- print(" --------------------------------------------------------------")
- print(" Error occurred during exception callback %s" % str(cb))
- print(" --------------------------------------------------------------")
- traceback.print_exception(*sys.exc_info())
+ print("===== %s =====" % str(time.strftime("%Y.%m.%d %H:%m:%S", time.localtime(time.time()))))
+ except Exception:
+ sys.stderr.write("Warning: stdout is broken! Falling back to stderr.\n")
+ sys.stdout = sys.stderr
+
+ ret = original_excepthook(*args)
+
+ for cb in callbacks:
+ try:
+ cb(*args)
+ except Exception:
+ print(" --------------------------------------------------------------")
+ print(" Error occurred during exception callback %s" % str(cb))
+ print(" --------------------------------------------------------------")
+ traceback.print_exception(*sys.exc_info())
+
+ ## Clear long-term storage of last traceback to prevent memory-hogging.
+ ## (If an exception occurs while a lot of data is present on the stack,
+ ## such as when loading large files, the data would ordinarily be kept
+ ## until the next exception occurs. We would rather release this memory
+ ## as soon as possible.)
+ if clear_tracebacks is True:
+ sys.last_traceback = None
- ## Clear long-term storage of last traceback to prevent memory-hogging.
- ## (If an exception occurs while a lot of data is present on the stack,
- ## such as when loading large files, the data would ordinarily be kept
- ## until the next exception occurs. We would rather release this memory
- ## as soon as possible.)
- if clear_tracebacks is True:
- sys.last_traceback = None
-
+ finally:
+ sys.setrecursionlimit(recursionLimit)
+
+
def implements(self, interface=None):
## this just makes it easy for us to detect whether an ExceptionHook is already installed.
if interface is None:
diff --git a/papi/pyqtgraph/exporters/CSVExporter.py b/papi/pyqtgraph/exporters/CSVExporter.py
new file mode 100644
index 00000000..b87f0182
--- /dev/null
+++ b/papi/pyqtgraph/exporters/CSVExporter.py
@@ -0,0 +1,83 @@
+from ..Qt import QtGui, QtCore
+from .Exporter import Exporter
+from ..parametertree import Parameter
+from .. import PlotItem
+
+__all__ = ['CSVExporter']
+
+
+class CSVExporter(Exporter):
+ Name = "CSV from plot data"
+ windows = []
+ def __init__(self, item):
+ Exporter.__init__(self, item)
+ self.params = Parameter(name='params', type='group', children=[
+ {'name': 'separator', 'type': 'list', 'value': 'comma', 'values': ['comma', 'tab']},
+ {'name': 'precision', 'type': 'int', 'value': 10, 'limits': [0, None]},
+ {'name': 'columnMode', 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']}
+ ])
+
+ def parameters(self):
+ return self.params
+
+ def export(self, fileName=None):
+
+ if not isinstance(self.item, PlotItem):
+ raise Exception("Must have a PlotItem selected for CSV export.")
+
+ if fileName is None:
+ self.fileSaveDialog(filter=["*.csv", "*.tsv"])
+ return
+
+ fd = open(fileName, 'w')
+ data = []
+ header = []
+
+ appendAllX = self.params['columnMode'] == '(x,y) per plot'
+
+ for i, c in enumerate(self.item.curves):
+ cd = c.getData()
+ if cd[0] is None:
+ continue
+ data.append(cd)
+ if hasattr(c, 'implements') and c.implements('plotData') and c.name() is not None:
+ name = c.name().replace('"', '""') + '_'
+ xName, yName = '"'+name+'x"', '"'+name+'y"'
+ else:
+ xName = 'x%04d' % i
+ yName = 'y%04d' % i
+ if appendAllX or i == 0:
+ header.extend([xName, yName])
+ else:
+ header.extend([yName])
+
+ if self.params['separator'] == 'comma':
+ sep = ','
+ else:
+ sep = '\t'
+
+ fd.write(sep.join(header) + '\n')
+ i = 0
+ numFormat = '%%0.%dg' % self.params['precision']
+ numRows = max([len(d[0]) for d in data])
+ for i in range(numRows):
+ for j, d in enumerate(data):
+ # write x value if this is the first column, or if we want x
+ # for all rows
+ if appendAllX or j == 0:
+ if d is not None and i < len(d[0]):
+ fd.write(numFormat % d[0][i] + sep)
+ else:
+ fd.write(' %s' % sep)
+
+ # write y value
+ if d is not None and i < len(d[1]):
+ fd.write(numFormat % d[1][i] + sep)
+ else:
+ fd.write(' %s' % sep)
+ fd.write('\n')
+ fd.close()
+
+CSVExporter.register()
+
+
diff --git a/pyqtgraph/exporters/Exporter.py b/papi/pyqtgraph/exporters/Exporter.py
similarity index 72%
rename from pyqtgraph/exporters/Exporter.py
rename to papi/pyqtgraph/exporters/Exporter.py
index 6371a3b9..64a25294 100644
--- a/pyqtgraph/exporters/Exporter.py
+++ b/papi/pyqtgraph/exporters/Exporter.py
@@ -1,7 +1,7 @@
-from pyqtgraph.widgets.FileDialog import FileDialog
-import pyqtgraph as pg
-from pyqtgraph.Qt import QtGui, QtCore, QtSvg
-from pyqtgraph.python2_3 import asUnicode
+from ..widgets.FileDialog import FileDialog
+from ..Qt import QtGui, QtCore, QtSvg
+from ..python2_3 import asUnicode
+from ..GraphicsScene import GraphicsScene
import os, re
LastExportDirectory = None
@@ -11,6 +11,14 @@ class Exporter(object):
Abstract class used for exporting graphics to file / printer / whatever.
"""
allowCopy = False # subclasses set this to True if they can use the copy buffer
+ Exporters = []
+
+ @classmethod
+ def register(cls):
+ """
+ Used to register Exporter classes to appear in the export dialog.
+ """
+ Exporter.Exporters.append(cls)
def __init__(self, item):
"""
@@ -20,9 +28,6 @@ def __init__(self, item):
object.__init__(self)
self.item = item
- #def item(self):
- #return self.item
-
def parameters(self):
"""Return the parameters used to configure this exporter."""
raise Exception("Abstract method must be overridden in subclass.")
@@ -72,20 +77,20 @@ def fileSaveFinished(self, fileName):
self.export(fileName=fileName, **self.fileDialog.opts)
def getScene(self):
- if isinstance(self.item, pg.GraphicsScene):
+ if isinstance(self.item, GraphicsScene):
return self.item
else:
return self.item.scene()
def getSourceRect(self):
- if isinstance(self.item, pg.GraphicsScene):
+ if isinstance(self.item, GraphicsScene):
w = self.item.getViewWidget()
return w.viewportTransform().inverted()[0].mapRect(w.rect())
else:
return self.item.sceneBoundingRect()
def getTargetRect(self):
- if isinstance(self.item, pg.GraphicsScene):
+ if isinstance(self.item, GraphicsScene):
return self.item.getViewWidget().rect()
else:
return self.item.mapRectToDevice(self.item.boundingRect())
@@ -131,45 +136,4 @@ def getPaintItems(self, root=None):
return preItems + rootItem + postItems
def render(self, painter, targetRect, sourceRect, item=None):
-
- #if item is None:
- #item = self.item
- #preItems = []
- #postItems = []
- #if isinstance(item, QtGui.QGraphicsScene):
- #childs = [i for i in item.items() if i.parentItem() is None]
- #rootItem = []
- #else:
- #childs = item.childItems()
- #rootItem = [item]
- #childs.sort(lambda a,b: cmp(a.zValue(), b.zValue()))
- #while len(childs) > 0:
- #ch = childs.pop(0)
- #if int(ch.flags() & ch.ItemStacksBehindParent) > 0 or (ch.zValue() < 0 and int(ch.flags() & ch.ItemNegativeZStacksBehindParent) > 0):
- #preItems.extend(tree)
- #else:
- #postItems.extend(tree)
-
- #for ch in preItems:
- #self.render(painter, sourceRect, targetRect, item=ch)
- ### paint root here
- #for ch in postItems:
- #self.render(painter, sourceRect, targetRect, item=ch)
-
-
self.getScene().render(painter, QtCore.QRectF(targetRect), QtCore.QRectF(sourceRect))
-
- #def writePs(self, fileName=None, item=None):
- #if fileName is None:
- #self.fileSaveDialog(self.writeSvg, filter="PostScript (*.ps)")
- #return
- #if item is None:
- #item = self
- #printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)
- #printer.setOutputFileName(fileName)
- #painter = QtGui.QPainter(printer)
- #self.render(painter)
- #painter.end()
-
- #def writeToPrinter(self):
- #pass
diff --git a/papi/pyqtgraph/exporters/HDF5Exporter.py b/papi/pyqtgraph/exporters/HDF5Exporter.py
new file mode 100644
index 00000000..cc8b5733
--- /dev/null
+++ b/papi/pyqtgraph/exporters/HDF5Exporter.py
@@ -0,0 +1,58 @@
+from ..Qt import QtGui, QtCore
+from .Exporter import Exporter
+from ..parametertree import Parameter
+from .. import PlotItem
+
+import numpy
+try:
+ import h5py
+ HAVE_HDF5 = True
+except ImportError:
+ HAVE_HDF5 = False
+
+__all__ = ['HDF5Exporter']
+
+
+class HDF5Exporter(Exporter):
+ Name = "HDF5 Export: plot (x,y)"
+ windows = []
+ allowCopy = False
+
+ def __init__(self, item):
+ Exporter.__init__(self, item)
+ self.params = Parameter(name='params', type='group', children=[
+ {'name': 'Name', 'type': 'str', 'value': 'Export',},
+ {'name': 'columnMode', 'type': 'list', 'values': ['(x,y) per plot', '(x,y,y,y) for all plots']},
+ ])
+
+ def parameters(self):
+ return self.params
+
+ def export(self, fileName=None):
+ if not HAVE_HDF5:
+ raise RuntimeError("This exporter requires the h5py package, "
+ "but it was not importable.")
+
+ if not isinstance(self.item, PlotItem):
+ raise Exception("Must have a PlotItem selected for HDF5 export.")
+
+ if fileName is None:
+ self.fileSaveDialog(filter=["*.h5", "*.hdf", "*.hd5"])
+ return
+ dsname = self.params['Name']
+ fd = h5py.File(fileName, 'a') # forces append to file... 'w' doesn't seem to "delete/overwrite"
+ data = []
+
+ appendAllX = self.params['columnMode'] == '(x,y) per plot'
+ for i,c in enumerate(self.item.curves):
+ d = c.getData()
+ if appendAllX or i == 0:
+ data.append(d[0])
+ data.append(d[1])
+
+ fdata = numpy.array(data).astype('double')
+ dset = fd.create_dataset(dsname, data=fdata)
+ fd.close()
+
+if HAVE_HDF5:
+ HDF5Exporter.register()
diff --git a/pyqtgraph/exporters/ImageExporter.py b/papi/pyqtgraph/exporters/ImageExporter.py
similarity index 95%
rename from pyqtgraph/exporters/ImageExporter.py
rename to papi/pyqtgraph/exporters/ImageExporter.py
index 9fb77e2a..78d93106 100644
--- a/pyqtgraph/exporters/ImageExporter.py
+++ b/papi/pyqtgraph/exporters/ImageExporter.py
@@ -1,7 +1,7 @@
from .Exporter import Exporter
-from pyqtgraph.parametertree import Parameter
-from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
-import pyqtgraph as pg
+from ..parametertree import Parameter
+from ..Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
+from .. import functions as fn
import numpy as np
__all__ = ['ImageExporter']
@@ -73,7 +73,7 @@ def export(self, fileName=None, toBytes=False, copy=False):
bg[:,:,1] = color.green()
bg[:,:,2] = color.red()
bg[:,:,3] = color.alpha()
- self.png = pg.makeQImage(bg, alpha=True)
+ self.png = fn.makeQImage(bg, alpha=True)
## set resolution of image:
origTargetRect = self.getTargetRect()
@@ -98,4 +98,5 @@ def export(self, fileName=None, toBytes=False, copy=False):
else:
self.png.save(fileName)
-
\ No newline at end of file
+ImageExporter.register()
+
diff --git a/papi/pyqtgraph/exporters/Matplotlib.py b/papi/pyqtgraph/exporters/Matplotlib.py
new file mode 100644
index 00000000..8cec1cef
--- /dev/null
+++ b/papi/pyqtgraph/exporters/Matplotlib.py
@@ -0,0 +1,128 @@
+from ..Qt import QtGui, QtCore
+from .Exporter import Exporter
+from .. import PlotItem
+from .. import functions as fn
+
+__all__ = ['MatplotlibExporter']
+
+"""
+It is helpful when using the matplotlib Exporter if your
+.matplotlib/matplotlibrc file is configured appropriately.
+The following are suggested for getting usable PDF output that
+can be edited in Illustrator, etc.
+
+backend : Qt4Agg
+text.usetex : True # Assumes you have a findable LaTeX installation
+interactive : False
+font.family : sans-serif
+font.sans-serif : 'Arial' # (make first in list)
+mathtext.default : sf
+figure.facecolor : white # personal preference
+# next setting allows pdf font to be readable in Adobe Illustrator
+pdf.fonttype : 42 # set fonts to TrueType (otherwise it will be 3
+ # and the text will be vectorized.
+text.dvipnghack : True # primarily to clean up font appearance on Mac
+
+The advantage is that there is less to do to get an exported file cleaned and ready for
+publication. Fonts are not vectorized (outlined), and window colors are white.
+
+"""
+
+class MatplotlibExporter(Exporter):
+ Name = "Matplotlib Window"
+ windows = []
+ def __init__(self, item):
+ Exporter.__init__(self, item)
+
+ def parameters(self):
+ return None
+
+ def cleanAxes(self, axl):
+ if type(axl) is not list:
+ axl = [axl]
+ for ax in axl:
+ if ax is None:
+ continue
+ for loc, spine in ax.spines.iteritems():
+ if loc in ['left', 'bottom']:
+ pass
+ elif loc in ['right', 'top']:
+ spine.set_color('none')
+ # do not draw the spine
+ else:
+ raise ValueError('Unknown spine location: %s' % loc)
+ # turn off ticks when there is no spine
+ ax.xaxis.set_ticks_position('bottom')
+
+ def export(self, fileName=None):
+
+ if isinstance(self.item, PlotItem):
+ mpw = MatplotlibWindow()
+ MatplotlibExporter.windows.append(mpw)
+
+ stdFont = 'Arial'
+
+ fig = mpw.getFigure()
+
+ # get labels from the graphic item
+ xlabel = self.item.axes['bottom']['item'].label.toPlainText()
+ ylabel = self.item.axes['left']['item'].label.toPlainText()
+ title = self.item.titleLabel.text
+
+ ax = fig.add_subplot(111, title=title)
+ ax.clear()
+ self.cleanAxes(ax)
+ #ax.grid(True)
+ for item in self.item.curves:
+ x, y = item.getData()
+ opts = item.opts
+ pen = fn.mkPen(opts['pen'])
+ if pen.style() == QtCore.Qt.NoPen:
+ linestyle = ''
+ else:
+ linestyle = '-'
+ color = tuple([c/255. for c in fn.colorTuple(pen.color())])
+ symbol = opts['symbol']
+ if symbol == 't':
+ symbol = '^'
+ symbolPen = fn.mkPen(opts['symbolPen'])
+ symbolBrush = fn.mkBrush(opts['symbolBrush'])
+ markeredgecolor = tuple([c/255. for c in fn.colorTuple(symbolPen.color())])
+ markerfacecolor = tuple([c/255. for c in fn.colorTuple(symbolBrush.color())])
+ markersize = opts['symbolSize']
+
+ if opts['fillLevel'] is not None and opts['fillBrush'] is not None:
+ fillBrush = fn.mkBrush(opts['fillBrush'])
+ fillcolor = tuple([c/255. for c in fn.colorTuple(fillBrush.color())])
+ ax.fill_between(x=x, y1=y, y2=opts['fillLevel'], facecolor=fillcolor)
+
+ pl = ax.plot(x, y, marker=symbol, color=color, linewidth=pen.width(),
+ linestyle=linestyle, markeredgecolor=markeredgecolor, markerfacecolor=markerfacecolor,
+ markersize=markersize)
+ xr, yr = self.item.viewRange()
+ ax.set_xbound(*xr)
+ ax.set_ybound(*yr)
+ ax.set_xlabel(xlabel) # place the labels.
+ ax.set_ylabel(ylabel)
+ mpw.draw()
+ else:
+ raise Exception("Matplotlib export currently only works with plot items")
+
+MatplotlibExporter.register()
+
+
+class MatplotlibWindow(QtGui.QMainWindow):
+ def __init__(self):
+ from ..widgets import MatplotlibWidget
+ QtGui.QMainWindow.__init__(self)
+ self.mpl = MatplotlibWidget.MatplotlibWidget()
+ self.setCentralWidget(self.mpl)
+ self.show()
+
+ def __getattr__(self, attr):
+ return getattr(self.mpl, attr)
+
+ def closeEvent(self, ev):
+ MatplotlibExporter.windows.remove(self)
+
+
diff --git a/pyqtgraph/exporters/PrintExporter.py b/papi/pyqtgraph/exporters/PrintExporter.py
similarity index 95%
rename from pyqtgraph/exporters/PrintExporter.py
rename to papi/pyqtgraph/exporters/PrintExporter.py
index 5b31b45d..530a1800 100644
--- a/pyqtgraph/exporters/PrintExporter.py
+++ b/papi/pyqtgraph/exporters/PrintExporter.py
@@ -1,6 +1,6 @@
from .Exporter import Exporter
-from pyqtgraph.parametertree import Parameter
-from pyqtgraph.Qt import QtGui, QtCore, QtSvg
+from ..parametertree import Parameter
+from ..Qt import QtGui, QtCore, QtSvg
import re
__all__ = ['PrintExporter']
@@ -36,7 +36,7 @@ def export(self, fileName=None):
dialog = QtGui.QPrintDialog(printer)
dialog.setWindowTitle("Print Document")
if dialog.exec_() != QtGui.QDialog.Accepted:
- return;
+ return
#dpi = QtGui.QDesktopWidget().physicalDpiX()
@@ -63,3 +63,6 @@ def export(self, fileName=None):
finally:
self.setExportMode(False)
painter.end()
+
+
+#PrintExporter.register()
diff --git a/pyqtgraph/exporters/SVGExporter.py b/papi/pyqtgraph/exporters/SVGExporter.py
similarity index 87%
rename from pyqtgraph/exporters/SVGExporter.py
rename to papi/pyqtgraph/exporters/SVGExporter.py
index 62b49d30..a91466c8 100644
--- a/pyqtgraph/exporters/SVGExporter.py
+++ b/papi/pyqtgraph/exporters/SVGExporter.py
@@ -1,8 +1,9 @@
from .Exporter import Exporter
-from pyqtgraph.python2_3 import asUnicode
-from pyqtgraph.parametertree import Parameter
-from pyqtgraph.Qt import QtGui, QtCore, QtSvg
-import pyqtgraph as pg
+from ..python2_3 import asUnicode
+from ..parametertree import Parameter
+from ..Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
+from .. import debug
+from .. import functions as fn
import re
import xml.dom.minidom as xml
import numpy as np
@@ -101,14 +102,12 @@ def export(self, fileName=None, toBytes=False, copy=False):
\n"
+ defsXml = "\n"
+ for d in defs:
+ defsXml += d.toprettyxml(indent=' ')
+ defsXml += "\n"
+ return xmlHeader + defsXml + node.toprettyxml(indent=' ') + "\n\n"
def _generateItemSvg(item, nodes=None, root=None):
@@ -156,7 +159,7 @@ def _generateItemSvg(item, nodes=None, root=None):
##
## Both 2 and 3 can be addressed by drawing all items in world coordinates.
- prof = pg.debug.Profiler('generateItemSvg %s' % str(item), disabled=True)
+ profiler = debug.Profiler()
if nodes is None: ## nodes maps all node IDs to their XML element.
## this allows us to ensure all elements receive unique names.
@@ -196,17 +199,12 @@ def _generateItemSvg(item, nodes=None, root=None):
tr2 = QtGui.QTransform()
tr2.translate(-rootPos.x(), -rootPos.y())
tr = tr * tr2
- #print item, pg.SRTTransform(tr)
- #tr.translate(item.pos().x(), item.pos().y())
- #tr = tr * item.transform()
arr = QtCore.QByteArray()
buf = QtCore.QBuffer(arr)
svg = QtSvg.QSvgGenerator()
svg.setOutputDevice(buf)
dpi = QtGui.QDesktopWidget().physicalDpiX()
- ### not really sure why this works, but it seems to be important:
- #self.svg.setSize(QtCore.QSize(self.params['width']*dpi/90., self.params['height']*dpi/90.))
svg.setResolution(dpi)
p = QtGui.QPainter()
@@ -223,7 +221,10 @@ def _generateItemSvg(item, nodes=None, root=None):
#if hasattr(item, 'setExportMode'):
#item.setExportMode(False)
- xmlStr = bytes(arr).decode('utf-8')
+ if USE_PYSIDE:
+ xmlStr = str(arr)
+ else:
+ xmlStr = bytes(arr).decode('utf-8')
doc = xml.parseString(xmlStr)
try:
@@ -231,16 +232,20 @@ def _generateItemSvg(item, nodes=None, root=None):
g1 = doc.getElementsByTagName('g')[0]
## get list of sub-groups
g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g']
+
+ defs = doc.getElementsByTagName('defs')
+ if len(defs) > 0:
+ defs = [n for n in defs[0].childNodes if isinstance(n, xml.Element)]
except:
print(doc.toxml())
raise
- prof.mark('render')
+ profiler('render')
## Get rid of group transformation matrices by applying
## transformation to inner coordinates
- correctCoordinates(g1, item)
- prof.mark('correct')
+ correctCoordinates(g1, defs, item)
+ profiler('correct')
## make sure g1 has the transformation matrix
#m = (tr.m11(), tr.m12(), tr.m21(), tr.m22(), tr.m31(), tr.m32())
#g1.setAttribute('transform', "matrix(%f,%f,%f,%f,%f,%f)" % m)
@@ -276,7 +281,9 @@ def _generateItemSvg(item, nodes=None, root=None):
path = QtGui.QGraphicsPathItem(item.mapToScene(item.shape()))
item.scene().addItem(path)
try:
- pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0]
+ #pathNode = _generateItemSvg(path, root=root).getElementsByTagName('path')[0]
+ pathNode = _generateItemSvg(path, root=root)[0].getElementsByTagName('path')[0]
+ # assume for this path is empty.. possibly problematic.
finally:
item.scene().removeItem(path)
@@ -290,20 +297,24 @@ def _generateItemSvg(item, nodes=None, root=None):
childGroup = g1.ownerDocument.createElement('g')
childGroup.setAttribute('clip-path', 'url(#%s)' % clip)
g1.appendChild(childGroup)
- prof.mark('clipping')
+ profiler('clipping')
## Add all child items as sub-elements.
childs.sort(key=lambda c: c.zValue())
for ch in childs:
- cg = _generateItemSvg(ch, nodes, root)
- if cg is None:
+ csvg = _generateItemSvg(ch, nodes, root)
+ if csvg is None:
continue
+ cg, cdefs = csvg
childGroup.appendChild(cg) ### this isn't quite right--some items draw below their parent (good enough for now)
- prof.mark('children')
- prof.finish()
- return g1
+ defs.extend(cdefs)
+
+ profiler('children')
+ return g1, defs
-def correctCoordinates(node, item):
+def correctCoordinates(node, defs, item):
+ # TODO: correct gradient coordinates inside defs
+
## Remove transformation matrices from tags by applying matrix to coordinates inside.
## Each item is represented by a single top-level group with one or more groups inside.
## Each inner group contains one or more drawing primitives, possibly of different types.
@@ -351,7 +362,7 @@ def correctCoordinates(node, item):
if ch.tagName == 'polyline':
removeTransform = True
coords = np.array([[float(a) for a in c.split(',')] for c in ch.getAttribute('points').strip().split(' ')])
- coords = pg.transformCoordinates(tr, coords, transpose=True)
+ coords = fn.transformCoordinates(tr, coords, transpose=True)
ch.setAttribute('points', ' '.join([','.join([str(a) for a in c]) for c in coords]))
elif ch.tagName == 'path':
removeTransform = True
@@ -366,7 +377,7 @@ def correctCoordinates(node, item):
x = x[1:]
else:
t = ''
- nc = pg.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True)
+ nc = fn.transformCoordinates(tr, np.array([[float(x),float(y)]]), transpose=True)
newCoords += t+str(nc[0,0])+','+str(nc[0,1])+' '
ch.setAttribute('d', newCoords)
elif ch.tagName == 'text':
@@ -376,7 +387,7 @@ def correctCoordinates(node, item):
#[float(ch.getAttribute('x')), float(ch.getAttribute('y'))],
#[float(ch.getAttribute('font-size')), 0],
#[0,0]])
- #c = pg.transformCoordinates(tr, c, transpose=True)
+ #c = fn.transformCoordinates(tr, c, transpose=True)
#ch.setAttribute('x', str(c[0,0]))
#ch.setAttribute('y', str(c[0,1]))
#fs = c[1]-c[2]
@@ -398,13 +409,17 @@ def correctCoordinates(node, item):
## correct line widths if needed
if removeTransform and ch.getAttribute('vector-effect') != 'non-scaling-stroke':
w = float(grp.getAttribute('stroke-width'))
- s = pg.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True)
+ s = fn.transformCoordinates(tr, np.array([[w,0], [0,0]]), transpose=True)
w = ((s[0]-s[1])**2).sum()**0.5
ch.setAttribute('stroke-width', str(w))
if removeTransform:
grp.removeAttribute('transform')
+
+SVGExporter.register()
+
+
def itemTransform(item, root):
## Return the transformation mapping item to root
## (actually to parent coordinate system of root)
@@ -440,35 +455,9 @@ def itemTransform(item, root):
tr = item.sceneTransform()
else:
tr = itemTransform(nextRoot, root) * item.itemTransform(nextRoot)[0]
- #pos = QtGui.QTransform()
- #pos.translate(root.pos().x(), root.pos().y())
- #tr = pos * root.transform() * item.itemTransform(root)[0]
-
return tr
-
-#def correctStroke(node, item, root, width=1):
- ##print "==============", item, node
- #if node.hasAttribute('stroke-width'):
- #width = float(node.getAttribute('stroke-width'))
- #if node.getAttribute('vector-effect') == 'non-scaling-stroke':
- #node.removeAttribute('vector-effect')
- #if isinstance(root, QtGui.QGraphicsScene):
- #w = item.mapFromScene(pg.Point(width,0))
- #o = item.mapFromScene(pg.Point(0,0))
- #else:
- #w = item.mapFromItem(root, pg.Point(width,0))
- #o = item.mapFromItem(root, pg.Point(0,0))
- #w = w-o
- ##print " ", w, o, w-o
- #w = (w.x()**2 + w.y()**2) ** 0.5
- ##print " ", w
- #node.setAttribute('stroke-width', str(w))
-
- #for ch in node.childNodes:
- #if isinstance(ch, xml.Element):
- #correctStroke(ch, item, root, width)
def cleanXml(node):
## remove extraneous text; let the xml library do the formatting.
diff --git a/papi/pyqtgraph/exporters/__init__.py b/papi/pyqtgraph/exporters/__init__.py
new file mode 100644
index 00000000..62ab1331
--- /dev/null
+++ b/papi/pyqtgraph/exporters/__init__.py
@@ -0,0 +1,11 @@
+from .Exporter import Exporter
+from .ImageExporter import *
+from .SVGExporter import *
+from .Matplotlib import *
+from .CSVExporter import *
+from .PrintExporter import *
+from .HDF5Exporter import *
+
+def listExporters():
+ return Exporter.Exporters[:]
+
diff --git a/papi/pyqtgraph/exporters/tests/test_csv.py b/papi/pyqtgraph/exporters/tests/test_csv.py
new file mode 100644
index 00000000..a98372ec
--- /dev/null
+++ b/papi/pyqtgraph/exporters/tests/test_csv.py
@@ -0,0 +1,49 @@
+"""
+SVG export test
+"""
+import pyqtgraph as pg
+import pyqtgraph.exporters
+import csv
+
+app = pg.mkQApp()
+
+def approxeq(a, b):
+ return (a-b) <= ((a + b) * 1e-6)
+
+def test_CSVExporter():
+ plt = pg.plot()
+ y1 = [1,3,2,3,1,6,9,8,4,2]
+ plt.plot(y=y1, name='myPlot')
+
+ y2 = [3,4,6,1,2,4,2,3,5,3,5,1,3]
+ x2 = pg.np.linspace(0, 1.0, len(y2))
+ plt.plot(x=x2, y=y2)
+
+ y3 = [1,5,2,3,4,6,1,2,4,2,3,5,3]
+ x3 = pg.np.linspace(0, 1.0, len(y3)+1)
+ plt.plot(x=x3, y=y3, stepMode=True)
+
+ ex = pg.exporters.CSVExporter(plt.plotItem)
+ ex.export(fileName='test.csv')
+
+ r = csv.reader(open('test.csv', 'r'))
+ lines = [line for line in r]
+ header = lines.pop(0)
+ assert header == ['myPlot_x', 'myPlot_y', 'x0001', 'y0001', 'x0002', 'y0002']
+
+ i = 0
+ for vals in lines:
+ vals = list(map(str.strip, vals))
+ assert (i >= len(y1) and vals[0] == '') or approxeq(float(vals[0]), i)
+ assert (i >= len(y1) and vals[1] == '') or approxeq(float(vals[1]), y1[i])
+
+ assert (i >= len(x2) and vals[2] == '') or approxeq(float(vals[2]), x2[i])
+ assert (i >= len(y2) and vals[3] == '') or approxeq(float(vals[3]), y2[i])
+
+ assert (i >= len(x3) and vals[4] == '') or approxeq(float(vals[4]), x3[i])
+ assert (i >= len(y3) and vals[5] == '') or approxeq(float(vals[5]), y3[i])
+ i += 1
+
+if __name__ == '__main__':
+ test_CSVExporter()
+
\ No newline at end of file
diff --git a/papi/pyqtgraph/exporters/tests/test_svg.py b/papi/pyqtgraph/exporters/tests/test_svg.py
new file mode 100644
index 00000000..871f43c2
--- /dev/null
+++ b/papi/pyqtgraph/exporters/tests/test_svg.py
@@ -0,0 +1,67 @@
+"""
+SVG export test
+"""
+import pyqtgraph as pg
+import pyqtgraph.exporters
+app = pg.mkQApp()
+
+def test_plotscene():
+ pg.setConfigOption('foreground', (0,0,0))
+ w = pg.GraphicsWindow()
+ w.show()
+ p1 = w.addPlot()
+ p2 = w.addPlot()
+ p1.plot([1,3,2,3,1,6,9,8,4,2,3,5,3], pen={'color':'k'})
+ p1.setXRange(0,5)
+ p2.plot([1,5,2,3,4,6,1,2,4,2,3,5,3], pen={'color':'k', 'cosmetic':False, 'width': 0.3})
+ app.processEvents()
+ app.processEvents()
+
+ ex = pg.exporters.SVGExporter(w.scene())
+ ex.export(fileName='test.svg')
+
+
+def test_simple():
+ scene = pg.QtGui.QGraphicsScene()
+ #rect = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100)
+ #scene.addItem(rect)
+ #rect.setPos(20,20)
+ #rect.translate(50, 50)
+ #rect.rotate(30)
+ #rect.scale(0.5, 0.5)
+
+ #rect1 = pg.QtGui.QGraphicsRectItem(0, 0, 100, 100)
+ #rect1.setParentItem(rect)
+ #rect1.setFlag(rect1.ItemIgnoresTransformations)
+ #rect1.setPos(20, 20)
+ #rect1.scale(2,2)
+
+ #el1 = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 100)
+ #el1.setParentItem(rect1)
+ ##grp = pg.ItemGroup()
+ #grp.setParentItem(rect)
+ #grp.translate(200,0)
+ ##grp.rotate(30)
+
+ #rect2 = pg.QtGui.QGraphicsRectItem(0, 0, 100, 25)
+ #rect2.setFlag(rect2.ItemClipsChildrenToShape)
+ #rect2.setParentItem(grp)
+ #rect2.setPos(0,25)
+ #rect2.rotate(30)
+ #el = pg.QtGui.QGraphicsEllipseItem(0, 0, 100, 50)
+ #el.translate(10,-5)
+ #el.scale(0.5,2)
+ #el.setParentItem(rect2)
+
+ grp2 = pg.ItemGroup()
+ scene.addItem(grp2)
+ grp2.scale(100,100)
+
+ rect3 = pg.QtGui.QGraphicsRectItem(0,0,2,2)
+ rect3.setPen(pg.mkPen(width=1, cosmetic=False))
+ grp2.addItem(rect3)
+
+ ex = pg.exporters.SVGExporter(scene)
+ ex.export(fileName='test.svg')
+
+
diff --git a/pyqtgraph/flowchart/Flowchart.py b/papi/pyqtgraph/flowchart/Flowchart.py
similarity index 91%
rename from pyqtgraph/flowchart/Flowchart.py
rename to papi/pyqtgraph/flowchart/Flowchart.py
index 81f9e163..ab5f4a82 100644
--- a/pyqtgraph/flowchart/Flowchart.py
+++ b/papi/pyqtgraph/flowchart/Flowchart.py
@@ -1,8 +1,9 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
+from ..Qt import QtCore, QtGui, USE_PYSIDE
from .Node import *
-from pyqtgraph.pgcollections import OrderedDict
-from pyqtgraph.widgets.TreeWidget import *
+from ..pgcollections import OrderedDict
+from ..widgets.TreeWidget import *
+from .. import FileDialog, DataTreeWidget
## pyside and pyqt use incompatible ui files.
if USE_PYSIDE:
@@ -14,60 +15,32 @@
from .Terminal import Terminal
from numpy import ndarray
-from . import library
-from pyqtgraph.debug import printExc
-import pyqtgraph.configfile as configfile
-import pyqtgraph.dockarea as dockarea
-import pyqtgraph as pg
+from .library import LIBRARY
+from ..debug import printExc
+from .. import configfile as configfile
+from .. import dockarea as dockarea
from . import FlowchartGraphicsView
+from .. import functions as fn
def strDict(d):
return dict([(str(k), v) for k, v in d.items()])
-def toposort(deps, nodes=None, seen=None, stack=None, depth=0):
- """Topological sort. Arguments are:
- deps dictionary describing dependencies where a:[b,c] means "a depends on b and c"
- nodes optional, specifies list of starting nodes (these should be the nodes
- which are not depended on by any other nodes)
- """
-
- if nodes is None:
- ## run through deps to find nodes that are not depended upon
- rem = set()
- for dep in deps.values():
- rem |= set(dep)
- nodes = set(deps.keys()) - rem
- if seen is None:
- seen = set()
- stack = []
- sorted = []
- #print " "*depth, "Starting from", nodes
- for n in nodes:
- if n in stack:
- raise Exception("Cyclic dependency detected", stack + [n])
- if n in seen:
- continue
- seen.add(n)
- #print " "*depth, " descending into", n, deps[n]
- sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1))
- #print " "*depth, " Added", n
- sorted.append(n)
- #print " "*depth, " ", sorted
- return sorted
class Flowchart(Node):
-
sigFileLoaded = QtCore.Signal(object)
sigFileSaved = QtCore.Signal(object)
#sigOutputChanged = QtCore.Signal() ## inherited from Node
sigChartLoaded = QtCore.Signal()
- sigStateChanged = QtCore.Signal()
+ sigStateChanged = QtCore.Signal() # called when output is expected to have changed
+ sigChartChanged = QtCore.Signal(object, object, object) # called when nodes are added, removed, or renamed.
+ # (self, action, node)
- def __init__(self, terminals=None, name=None, filePath=None):
+ def __init__(self, terminals=None, name=None, filePath=None, library=None):
+ self.library = library or LIBRARY
if name is None:
name = "Flowchart"
if terminals is None:
@@ -105,6 +78,10 @@ def __init__(self, terminals=None, name=None, filePath=None):
for name, opts in terminals.items():
self.addTerminal(name, **opts)
+ def setLibrary(self, lib):
+ self.library = lib
+ self.widget().chartWidget.buildMenu()
+
def setInput(self, **args):
"""Set the input values of the flowchart. This will automatically propagate
the new values throughout the flowchart, (possibly) causing the output to change.
@@ -194,7 +171,7 @@ def createNode(self, nodeType, name=None, pos=None):
break
n += 1
- node = library.getNodeType(nodeType)(name)
+ node = self.library.getNodeType(nodeType)(name)
self.addNode(node, name, pos)
return node
@@ -213,6 +190,7 @@ def addNode(self, node, name, pos=None):
node.sigClosed.connect(self.nodeClosed)
node.sigRenamed.connect(self.nodeRenamed)
node.sigOutputChanged.connect(self.nodeOutputChanged)
+ self.sigChartChanged.emit(self, 'add', node)
def removeNode(self, node):
node.close()
@@ -220,23 +198,18 @@ def removeNode(self, node):
def nodeClosed(self, node):
del self._nodes[node.name()]
self.widget().removeNode(node)
- try:
- node.sigClosed.disconnect(self.nodeClosed)
- except TypeError:
- pass
- try:
- node.sigRenamed.disconnect(self.nodeRenamed)
- except TypeError:
- pass
- try:
- node.sigOutputChanged.disconnect(self.nodeOutputChanged)
- except TypeError:
- pass
+ for signal in ['sigClosed', 'sigRenamed', 'sigOutputChanged']:
+ try:
+ getattr(node, signal).disconnect(self.nodeClosed)
+ except (TypeError, RuntimeError):
+ pass
+ self.sigChartChanged.emit(self, 'remove', node)
def nodeRenamed(self, node, oldName):
del self._nodes[oldName]
self._nodes[node.name()] = node
self.widget().nodeRenamed(node, oldName)
+ self.sigChartChanged.emit(self, 'rename', node)
def arrangeNodes(self):
pass
@@ -276,9 +249,10 @@ def process(self, **args):
## Record inputs given to process()
for n, t in self.inputNode.outputs().items():
- if n not in args:
- raise Exception("Parameter %s required to process this chart." % n)
- data[t] = args[n]
+ # if n not in args:
+ # raise Exception("Parameter %s required to process this chart." % n)
+ if n in args:
+ data[t] = args[n]
ret = {}
@@ -303,7 +277,7 @@ def process(self, **args):
if len(inputs) == 0:
continue
if inp.isMultiValue(): ## multi-input terminals require a dict of all inputs
- args[inp.name()] = dict([(i, data[i]) for i in inputs])
+ args[inp.name()] = dict([(i, data[i]) for i in inputs if i in data])
else: ## single-inputs terminals only need the single input value available
args[inp.name()] = data[inputs[0]]
@@ -323,9 +297,8 @@ def process(self, **args):
#print out.name()
try:
data[out] = result[out.name()]
- except:
- print(out, out.name())
- raise
+ except KeyError:
+ pass
elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory)
#print "===> delete", arg
if arg in data:
@@ -350,7 +323,7 @@ def processOrder(self):
#print "DEPS:", deps
## determine correct node-processing order
#deps[self] = []
- order = toposort(deps)
+ order = fn.toposort(deps)
#print "ORDER1:", order
## construct list of operations
@@ -399,7 +372,7 @@ def nodeOutputChanged(self, startNode):
deps[node].extend(t.dependentNodes())
## determine order of updates
- order = toposort(deps, nodes=[startNode])
+ order = fn.toposort(deps, nodes=[startNode])
order.reverse()
## keep track of terminals that have been updated
@@ -532,7 +505,7 @@ def loadFile(self, fileName=None, startDir=None):
startDir = self.filePath
if startDir is None:
startDir = '.'
- self.fileDialog = pg.FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)")
+ self.fileDialog = FileDialog(None, "Load Flowchart..", startDir, "Flowchart (*.fc)")
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
#self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
self.fileDialog.show()
@@ -540,7 +513,7 @@ def loadFile(self, fileName=None, startDir=None):
return
## NOTE: was previously using a real widget for the file dialog's parent, but this caused weird mouse event bugs..
#fileName = QtGui.QFileDialog.getOpenFileName(None, "Load Flowchart..", startDir, "Flowchart (*.fc)")
- fileName = str(fileName)
+ fileName = unicode(fileName)
state = configfile.readConfigFile(fileName)
self.restoreState(state, clear=True)
self.viewBox.autoRange()
@@ -553,7 +526,7 @@ def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc
startDir = self.filePath
if startDir is None:
startDir = '.'
- self.fileDialog = pg.FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)")
+ self.fileDialog = FileDialog(None, "Save Flowchart..", startDir, "Flowchart (*.fc)")
#self.fileDialog.setFileMode(QtGui.QFileDialog.AnyFile)
self.fileDialog.setAcceptMode(QtGui.QFileDialog.AcceptSave)
#self.fileDialog.setDirectory(startDir)
@@ -561,7 +534,7 @@ def saveFile(self, fileName=None, startDir=None, suggestedFileName='flowchart.fc
self.fileDialog.fileSelected.connect(self.saveFile)
return
#fileName = QtGui.QFileDialog.getSaveFileName(None, "Save Flowchart..", startDir, "Flowchart (*.fc)")
- fileName = str(fileName)
+ fileName = unicode(fileName)
configfile.writeConfigFile(self.saveState(), fileName)
self.sigFileSaved.emit(fileName)
@@ -683,7 +656,7 @@ def loadClicked(self):
#self.setCurrentFile(newFile)
def fileSaved(self, fileName):
- self.setCurrentFile(str(fileName))
+ self.setCurrentFile(unicode(fileName))
self.ui.saveBtn.success("Saved.")
def saveClicked(self):
@@ -712,7 +685,7 @@ def saveAsClicked(self):
#self.setCurrentFile(newFile)
def setCurrentFile(self, fileName):
- self.currentFileName = str(fileName)
+ self.currentFileName = unicode(fileName)
if fileName is None:
self.ui.fileNameLabel.setText("[ new ]")
else:
@@ -760,7 +733,7 @@ def removeNode(self, node):
#self.disconnect(item.bypassBtn, QtCore.SIGNAL('clicked()'), self.bypassClicked)
try:
item.bypassBtn.clicked.disconnect(self.bypassClicked)
- except TypeError:
+ except (TypeError, RuntimeError):
pass
self.ui.ctrlList.removeTopLevelItem(item)
@@ -816,7 +789,7 @@ def __init__(self, chart, ctrl):
self.selDescLabel = QtGui.QLabel()
self.selNameLabel = QtGui.QLabel()
self.selDescLabel.setWordWrap(True)
- self.selectedTree = pg.DataTreeWidget()
+ self.selectedTree = DataTreeWidget()
#self.selectedTree.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
#self.selInfoLayout.addWidget(self.selNameLabel)
self.selInfoLayout.addWidget(self.selDescLabel)
@@ -846,20 +819,24 @@ def reloadLibrary(self):
self.nodeMenu.triggered.disconnect(self.nodeMenuTriggered)
self.nodeMenu = None
self.subMenus = []
- library.loadLibrary(reloadLibs=True)
+ self.chart.library.reload()
self.buildMenu()
def buildMenu(self, pos=None):
+ def buildSubMenu(node, rootMenu, subMenus, pos=None):
+ for section, node in node.items():
+ menu = QtGui.QMenu(section)
+ rootMenu.addMenu(menu)
+ if isinstance(node, OrderedDict):
+ buildSubMenu(node, menu, subMenus, pos=pos)
+ subMenus.append(menu)
+ else:
+ act = rootMenu.addAction(section)
+ act.nodeType = section
+ act.pos = pos
self.nodeMenu = QtGui.QMenu()
- self.subMenus = []
- for section, nodes in library.getNodeTree().items():
- menu = QtGui.QMenu(section)
- self.nodeMenu.addMenu(menu)
- for name in nodes:
- act = menu.addAction(name)
- act.nodeType = name
- act.pos = pos
- self.subMenus.append(menu)
+ self.subMenus = []
+ buildSubMenu(self.chart.library.getNodeTree(), self.nodeMenu, self.subMenus, pos=pos)
self.nodeMenu.triggered.connect(self.nodeMenuTriggered)
return self.nodeMenu
diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui
similarity index 96%
rename from pyqtgraph/flowchart/FlowchartCtrlTemplate.ui
rename to papi/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui
index 610846b6..0361ad3e 100644
--- a/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui
+++ b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate.ui
@@ -107,12 +107,12 @@
TreeWidget
QTreeWidget
- pyqtgraph.widgets.TreeWidget
+
FeedbackButton
QPushButton
- pyqtgraph.widgets.FeedbackButton
+
diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py
similarity index 69%
rename from pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py
rename to papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py
index 0410cdf3..8afd43f8 100644
--- a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py
+++ b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyqt.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './flowchart/FlowchartCtrlTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartCtrlTemplate.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: PyQt4 UI code generator 4.9.1
+# Created: Mon Dec 23 10:10:50 2013
+# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,16 @@
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
- _fromUtf8 = lambda s: s
+ def _fromUtf8(s):
+ return s
+
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
class Ui_Form(object):
def setupUi(self, Form):
@@ -60,12 +69,12 @@ def setupUi(self, Form):
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
- Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
- self.loadBtn.setText(QtGui.QApplication.translate("Form", "Load..", None, QtGui.QApplication.UnicodeUTF8))
- self.saveBtn.setText(QtGui.QApplication.translate("Form", "Save", None, QtGui.QApplication.UnicodeUTF8))
- self.saveAsBtn.setText(QtGui.QApplication.translate("Form", "As..", None, QtGui.QApplication.UnicodeUTF8))
- self.reloadBtn.setText(QtGui.QApplication.translate("Form", "Reload Libs", None, QtGui.QApplication.UnicodeUTF8))
- self.showChartBtn.setText(QtGui.QApplication.translate("Form", "Flowchart", None, QtGui.QApplication.UnicodeUTF8))
+ Form.setWindowTitle(_translate("Form", "Form", None))
+ self.loadBtn.setText(_translate("Form", "Load..", None))
+ self.saveBtn.setText(_translate("Form", "Save", None))
+ self.saveAsBtn.setText(_translate("Form", "As..", None))
+ self.reloadBtn.setText(_translate("Form", "Reload Libs", None))
+ self.showChartBtn.setText(_translate("Form", "Flowchart", None))
-from pyqtgraph.widgets.FeedbackButton import FeedbackButton
-from pyqtgraph.widgets.TreeWidget import TreeWidget
+from ..widgets.TreeWidget import TreeWidget
+from ..widgets.FeedbackButton import FeedbackButton
diff --git a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py
similarity index 90%
rename from pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py
rename to papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py
index f579c957..b722000e 100644
--- a/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py
+++ b/papi/pyqtgraph/flowchart/FlowchartCtrlTemplate_pyside.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './flowchart/FlowchartCtrlTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartCtrlTemplate.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: pyside-uic 0.2.13 running on PySide 1.1.0
+# Created: Mon Dec 23 10:10:51 2013
+# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
@@ -62,5 +62,5 @@ def retranslateUi(self, Form):
self.reloadBtn.setText(QtGui.QApplication.translate("Form", "Reload Libs", None, QtGui.QApplication.UnicodeUTF8))
self.showChartBtn.setText(QtGui.QApplication.translate("Form", "Flowchart", None, QtGui.QApplication.UnicodeUTF8))
-from pyqtgraph.widgets.FeedbackButton import FeedbackButton
-from pyqtgraph.widgets.TreeWidget import TreeWidget
+from ..widgets.TreeWidget import TreeWidget
+from ..widgets.FeedbackButton import FeedbackButton
diff --git a/pyqtgraph/flowchart/FlowchartGraphicsView.py b/papi/pyqtgraph/flowchart/FlowchartGraphicsView.py
similarity index 94%
rename from pyqtgraph/flowchart/FlowchartGraphicsView.py
rename to papi/pyqtgraph/flowchart/FlowchartGraphicsView.py
index 0ec4d5c8..ab4b2914 100644
--- a/pyqtgraph/flowchart/FlowchartGraphicsView.py
+++ b/papi/pyqtgraph/flowchart/FlowchartGraphicsView.py
@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtGui, QtCore
-from pyqtgraph.widgets.GraphicsView import GraphicsView
-from pyqtgraph.GraphicsScene import GraphicsScene
-from pyqtgraph.graphicsItems.ViewBox import ViewBox
+from ..Qt import QtGui, QtCore
+from ..widgets.GraphicsView import GraphicsView
+from ..GraphicsScene import GraphicsScene
+from ..graphicsItems.ViewBox import ViewBox
#class FlowchartGraphicsView(QtGui.QGraphicsView):
class FlowchartGraphicsView(GraphicsView):
diff --git a/pyqtgraph/flowchart/FlowchartTemplate.ui b/papi/pyqtgraph/flowchart/FlowchartTemplate.ui
similarity index 95%
rename from pyqtgraph/flowchart/FlowchartTemplate.ui
rename to papi/pyqtgraph/flowchart/FlowchartTemplate.ui
index 31b1359c..8b0c19da 100644
--- a/pyqtgraph/flowchart/FlowchartTemplate.ui
+++ b/papi/pyqtgraph/flowchart/FlowchartTemplate.ui
@@ -85,12 +85,12 @@
DataTreeWidget
QTreeWidget
- pyqtgraph.widgets.DataTreeWidget
+
FlowchartGraphicsView
QGraphicsView
- pyqtgraph.flowchart.FlowchartGraphicsView
+ ..flowchart.FlowchartGraphicsView
diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py b/papi/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py
similarity index 75%
rename from pyqtgraph/flowchart/FlowchartTemplate_pyqt.py
rename to papi/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py
index c07dd734..06b10bfe 100644
--- a/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py
+++ b/papi/pyqtgraph/flowchart/FlowchartTemplate_pyqt.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartTemplate.ui'
#
-# Created: Sun Feb 24 19:47:29 2013
-# by: PyQt4 UI code generator 4.9.3
+# Created: Mon Dec 23 10:10:51 2013
+# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,16 @@
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
- _fromUtf8 = lambda s: s
+ def _fromUtf8(s):
+ return s
+
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
class Ui_Form(object):
def setupUi(self, Form):
@@ -53,7 +62,7 @@ def setupUi(self, Form):
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
- Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
+ Form.setWindowTitle(_translate("Form", "Form", None))
-from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget
-from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView
+from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView
+from ..widgets.DataTreeWidget import DataTreeWidget
diff --git a/pyqtgraph/flowchart/FlowchartTemplate_pyside.py b/papi/pyqtgraph/flowchart/FlowchartTemplate_pyside.py
similarity index 86%
rename from pyqtgraph/flowchart/FlowchartTemplate_pyside.py
rename to papi/pyqtgraph/flowchart/FlowchartTemplate_pyside.py
index c73f3c00..2c693c60 100644
--- a/pyqtgraph/flowchart/FlowchartTemplate_pyside.py
+++ b/papi/pyqtgraph/flowchart/FlowchartTemplate_pyside.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './flowchart/FlowchartTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/flowchart/FlowchartTemplate.ui'
#
-# Created: Sun Feb 24 19:47:30 2013
-# by: pyside-uic 0.2.13 running on PySide 1.1.1
+# Created: Mon Dec 23 10:10:51 2013
+# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
@@ -50,5 +50,5 @@ def setupUi(self, Form):
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
-from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget
-from pyqtgraph.flowchart.FlowchartGraphicsView import FlowchartGraphicsView
+from ..flowchart.FlowchartGraphicsView import FlowchartGraphicsView
+from ..widgets.DataTreeWidget import DataTreeWidget
diff --git a/pyqtgraph/flowchart/Node.py b/papi/pyqtgraph/flowchart/Node.py
similarity index 98%
rename from pyqtgraph/flowchart/Node.py
rename to papi/pyqtgraph/flowchart/Node.py
index cd73b42b..fc7b04d3 100644
--- a/pyqtgraph/flowchart/Node.py
+++ b/papi/pyqtgraph/flowchart/Node.py
@@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui
-from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject
-import pyqtgraph.functions as fn
+from ..Qt import QtCore, QtGui
+from ..graphicsItems.GraphicsObject import GraphicsObject
+from .. import functions as fn
from .Terminal import *
-from pyqtgraph.pgcollections import OrderedDict
-from pyqtgraph.debug import *
+from ..pgcollections import OrderedDict
+from ..debug import *
import numpy as np
from .eq import *
@@ -37,7 +37,7 @@ class Node(QtCore.QObject):
def __init__(self, name, terminals=None, allowAddInput=False, allowAddOutput=False, allowRemove=True):
"""
============== ============================================================
- Arguments
+ **Arguments:**
name The name of this specific node instance. It can be any
string, but must be unique within a flowchart. Usually,
we simply let the flowchart decide on a name when calling
@@ -501,8 +501,8 @@ def labelChanged(self):
bounds = self.boundingRect()
self.nameItem.setPos(bounds.width()/2. - self.nameItem.boundingRect().width()/2., 0)
- def setPen(self, pen):
- self.pen = pen
+ def setPen(self, *args, **kwargs):
+ self.pen = fn.mkPen(*args, **kwargs)
self.update()
def setBrush(self, brush):
@@ -617,9 +617,6 @@ def itemChange(self, change, val):
def getMenu(self):
return self.menu
-
- def getContextMenus(self, event):
- return [self.menu]
def raiseContextMenu(self, ev):
menu = self.scene().addParentContextMenus(self, self.getMenu(), ev)
diff --git a/papi/pyqtgraph/flowchart/NodeLibrary.py b/papi/pyqtgraph/flowchart/NodeLibrary.py
new file mode 100644
index 00000000..8e04e97d
--- /dev/null
+++ b/papi/pyqtgraph/flowchart/NodeLibrary.py
@@ -0,0 +1,86 @@
+from ..pgcollections import OrderedDict
+from .Node import Node
+
+def isNodeClass(cls):
+ try:
+ if not issubclass(cls, Node):
+ return False
+ except:
+ return False
+ return hasattr(cls, 'nodeName')
+
+
+
+class NodeLibrary:
+ """
+ A library of flowchart Node types. Custom libraries may be built to provide
+ each flowchart with a specific set of allowed Node types.
+ """
+
+ def __init__(self):
+ self.nodeList = OrderedDict()
+ self.nodeTree = OrderedDict()
+
+ def addNodeType(self, nodeClass, paths, override=False):
+ """
+ Register a new node type. If the type's name is already in use,
+ an exception will be raised (unless override=True).
+
+ ============== =========================================================
+ **Arguments:**
+
+ nodeClass a subclass of Node (must have typ.nodeName)
+ paths list of tuples specifying the location(s) this
+ type will appear in the library tree.
+ override if True, overwrite any class having the same name
+ ============== =========================================================
+ """
+ if not isNodeClass(nodeClass):
+ raise Exception("Object %s is not a Node subclass" % str(nodeClass))
+
+ name = nodeClass.nodeName
+ if not override and name in self.nodeList:
+ raise Exception("Node type name '%s' is already registered." % name)
+
+ self.nodeList[name] = nodeClass
+ for path in paths:
+ root = self.nodeTree
+ for n in path:
+ if n not in root:
+ root[n] = OrderedDict()
+ root = root[n]
+ root[name] = nodeClass
+
+ def getNodeType(self, name):
+ try:
+ return self.nodeList[name]
+ except KeyError:
+ raise Exception("No node type called '%s'" % name)
+
+ def getNodeTree(self):
+ return self.nodeTree
+
+ def copy(self):
+ """
+ Return a copy of this library.
+ """
+ lib = NodeLibrary()
+ lib.nodeList = self.nodeList.copy()
+ lib.nodeTree = self.treeCopy(self.nodeTree)
+ return lib
+
+ @staticmethod
+ def treeCopy(tree):
+ copy = OrderedDict()
+ for k,v in tree.items():
+ if isNodeClass(v):
+ copy[k] = v
+ else:
+ copy[k] = NodeLibrary.treeCopy(v)
+ return copy
+
+ def reload(self):
+ """
+ Reload Node classes in this library.
+ """
+ raise NotImplementedError()
diff --git a/pyqtgraph/flowchart/Terminal.py b/papi/pyqtgraph/flowchart/Terminal.py
similarity index 98%
rename from pyqtgraph/flowchart/Terminal.py
rename to papi/pyqtgraph/flowchart/Terminal.py
index 45805cd8..6a6db62e 100644
--- a/pyqtgraph/flowchart/Terminal.py
+++ b/papi/pyqtgraph/flowchart/Terminal.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui
+from ..Qt import QtCore, QtGui
import weakref
-from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject
-import pyqtgraph.functions as fn
-from pyqtgraph.Point import Point
+from ..graphicsItems.GraphicsObject import GraphicsObject
+from .. import functions as fn
+from ..Point import Point
#from PySide import QtCore, QtGui
from .eq import *
@@ -436,10 +436,6 @@ def getMenu(self):
def toggleMulti(self):
multi = self.menu.multiAct.isChecked()
self.term.setMultiValue(multi)
-
- ## probably never need this
- #def getContextMenus(self, ev):
- #return [self.getMenu()]
def removeSelf(self):
self.term.node().removeTerminal(self.term)
diff --git a/pyqtgraph/flowchart/__init__.py b/papi/pyqtgraph/flowchart/__init__.py
similarity index 100%
rename from pyqtgraph/flowchart/__init__.py
rename to papi/pyqtgraph/flowchart/__init__.py
diff --git a/pyqtgraph/flowchart/eq.py b/papi/pyqtgraph/flowchart/eq.py
similarity index 92%
rename from pyqtgraph/flowchart/eq.py
rename to papi/pyqtgraph/flowchart/eq.py
index 031ebce8..554989b2 100644
--- a/pyqtgraph/flowchart/eq.py
+++ b/papi/pyqtgraph/flowchart/eq.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
from numpy import ndarray, bool_
-from pyqtgraph.metaarray import MetaArray
+from ..metaarray import MetaArray
def eq(a, b):
"""The great missing equivalence function: Guaranteed evaluation to a single bool value."""
@@ -29,7 +29,7 @@ def eq(a, b):
except:
return False
if (hasattr(e, 'implements') and e.implements('MetaArray')):
- return e.asarray().all()
+ return e.asarray().all()
else:
return e.all()
else:
diff --git a/pyqtgraph/flowchart/library/Data.py b/papi/pyqtgraph/flowchart/library/Data.py
similarity index 96%
rename from pyqtgraph/flowchart/library/Data.py
rename to papi/pyqtgraph/flowchart/library/Data.py
index cbef848a..5236de8d 100644
--- a/pyqtgraph/flowchart/library/Data.py
+++ b/papi/pyqtgraph/flowchart/library/Data.py
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
from ..Node import Node
-from pyqtgraph.Qt import QtGui, QtCore
+from ...Qt import QtGui, QtCore
import numpy as np
from .common import *
-from pyqtgraph.SRTTransform import SRTTransform
-from pyqtgraph.Point import Point
-from pyqtgraph.widgets.TreeWidget import TreeWidget
-from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem
+from ...SRTTransform import SRTTransform
+from ...Point import Point
+from ...widgets.TreeWidget import TreeWidget
+from ...graphicsItems.LinearRegionItem import LinearRegionItem
from . import functions
@@ -182,8 +182,8 @@ class EvalNode(Node):
def __init__(self, name):
Node.__init__(self, name,
terminals = {
- 'input': {'io': 'in', 'renamable': True},
- 'output': {'io': 'out', 'renamable': True},
+ 'input': {'io': 'in', 'renamable': True, 'multiable': True},
+ 'output': {'io': 'out', 'renamable': True, 'multiable': True},
},
allowAddInput=True, allowAddOutput=True)
@@ -328,7 +328,7 @@ def restoreState(self, state):
## Node.restoreState should have created all of the terminals we need
## However: to maintain support for some older flowchart files, we need
- ## to manually add any terminals that were not taken care of.
+ ## to manually add any terminals that were not taken care of.
for name in [n for n in state['order'] if n not in inputs]:
Node.addInput(self, name, renamable=True, removable=True, multiable=True)
inputs = self.inputs()
diff --git a/pyqtgraph/flowchart/library/Display.py b/papi/pyqtgraph/flowchart/library/Display.py
similarity index 79%
rename from pyqtgraph/flowchart/library/Display.py
rename to papi/pyqtgraph/flowchart/library/Display.py
index 9068c0ec..642e6491 100644
--- a/pyqtgraph/flowchart/library/Display.py
+++ b/papi/pyqtgraph/flowchart/library/Display.py
@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
from ..Node import Node
import weakref
-#from pyqtgraph import graphicsItems
-from pyqtgraph.Qt import QtCore, QtGui
-from pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem
-from pyqtgraph.graphicsItems.PlotCurveItem import PlotCurveItem
-from pyqtgraph import PlotDataItem
+from ...Qt import QtCore, QtGui
+from ...graphicsItems.ScatterPlotItem import ScatterPlotItem
+from ...graphicsItems.PlotCurveItem import PlotCurveItem
+from ... import PlotDataItem, ComboBox
from .common import *
import numpy as np
@@ -17,7 +16,9 @@ class PlotWidgetNode(Node):
def __init__(self, name):
Node.__init__(self, name, terminals={'In': {'io': 'in', 'multi': True}})
- self.plot = None
+ self.plot = None # currently selected plot
+ self.plots = {} # list of available plots user may select from
+ self.ui = None
self.items = {}
def disconnected(self, localTerm, remoteTerm):
@@ -27,16 +28,27 @@ def disconnected(self, localTerm, remoteTerm):
def setPlot(self, plot):
#print "======set plot"
+ if plot == self.plot:
+ return
+
+ # clear data from previous plot
+ if self.plot is not None:
+ for vid in list(self.items.keys()):
+ self.plot.removeItem(self.items[vid])
+ del self.items[vid]
+
self.plot = plot
+ self.updateUi()
+ self.update()
self.sigPlotChanged.emit(self)
def getPlot(self):
return self.plot
def process(self, In, display=True):
- if display:
- #self.plot.clearPlots()
+ if display and self.plot is not None:
items = set()
+ # Add all new input items to selected plot
for name, vals in In.items():
if vals is None:
continue
@@ -46,14 +58,13 @@ def process(self, In, display=True):
for val in vals:
vid = id(val)
if vid in self.items and self.items[vid].scene() is self.plot.scene():
+ # Item is already added to the correct scene
+ # possible bug: what if two plots occupy the same scene? (should
+ # rarely be a problem because items are removed from a plot before
+ # switching).
items.add(vid)
else:
- #if isinstance(val, PlotCurveItem):
- #self.plot.addItem(val)
- #item = val
- #if isinstance(val, ScatterPlotItem):
- #self.plot.addItem(val)
- #item = val
+ # Add the item to the plot, or generate a new item if needed.
if isinstance(val, QtGui.QGraphicsItem):
self.plot.addItem(val)
item = val
@@ -61,22 +72,48 @@ def process(self, In, display=True):
item = self.plot.plot(val)
self.items[vid] = item
items.add(vid)
+
+ # Any left-over items that did not appear in the input must be removed
for vid in list(self.items.keys()):
if vid not in items:
- #print "remove", self.items[vid]
self.plot.removeItem(self.items[vid])
del self.items[vid]
def processBypassed(self, args):
+ if self.plot is None:
+ return
for item in list(self.items.values()):
self.plot.removeItem(item)
self.items = {}
- #def setInput(self, **args):
- #for k in args:
- #self.plot.plot(args[k])
+ def ctrlWidget(self):
+ if self.ui is None:
+ self.ui = ComboBox()
+ self.ui.currentIndexChanged.connect(self.plotSelected)
+ self.updateUi()
+ return self.ui
-
+ def plotSelected(self, index):
+ self.setPlot(self.ui.value())
+
+ def setPlotList(self, plots):
+ """
+ Specify the set of plots (PlotWidget or PlotItem) that the user may
+ select from.
+
+ *plots* must be a dictionary of {name: plot} pairs.
+ """
+ self.plots = plots
+ self.updateUi()
+
+ def updateUi(self):
+ # sets list and automatically preserves previous selection
+ self.ui.setItems(self.plots)
+ try:
+ self.ui.setValue(self.plot)
+ except ValueError:
+ pass
+
class CanvasNode(Node):
"""Connection to a Canvas widget."""
@@ -272,4 +309,4 @@ def restoreState(self, state):
#pos = file.
-
\ No newline at end of file
+
diff --git a/pyqtgraph/flowchart/library/Filters.py b/papi/pyqtgraph/flowchart/library/Filters.py
similarity index 73%
rename from pyqtgraph/flowchart/library/Filters.py
rename to papi/pyqtgraph/flowchart/library/Filters.py
index 090c261c..88a2f6c5 100644
--- a/pyqtgraph/flowchart/library/Filters.py
+++ b/papi/pyqtgraph/flowchart/library/Filters.py
@@ -1,14 +1,14 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui
+from ...Qt import QtCore, QtGui
from ..Node import Node
-from scipy.signal import detrend
-from scipy.ndimage import median_filter, gaussian_filter
-#from pyqtgraph.SignalProxy import SignalProxy
from . import functions
+from ... import functions as pgfn
from .common import *
import numpy as np
-import pyqtgraph.metaarray as metaarray
+from ... import PolyLineROI
+from ... import Point
+from ... import metaarray as metaarray
class Downsample(CtrlNode):
@@ -119,7 +119,11 @@ class Median(CtrlNode):
@metaArrayWrapper
def processData(self, data):
- return median_filter(data, self.ctrls['n'].value())
+ try:
+ import scipy.ndimage
+ except ImportError:
+ raise Exception("MedianFilter node requires the package scipy.ndimage.")
+ return scipy.ndimage.median_filter(data, self.ctrls['n'].value())
class Mode(CtrlNode):
"""Filters data by taking the mode (histogram-based) of a sliding window"""
@@ -156,7 +160,11 @@ class Gaussian(CtrlNode):
@metaArrayWrapper
def processData(self, data):
- return gaussian_filter(data, self.ctrls['sigma'].value())
+ try:
+ import scipy.ndimage
+ except ImportError:
+ raise Exception("GaussianFilter node requires the package scipy.ndimage.")
+ return pgfn.gaussianFilter(data, self.ctrls['sigma'].value())
class Derivative(CtrlNode):
@@ -189,8 +197,78 @@ class Detrend(CtrlNode):
@metaArrayWrapper
def processData(self, data):
+ try:
+ from scipy.signal import detrend
+ except ImportError:
+ raise Exception("DetrendFilter node requires the package scipy.signal.")
return detrend(data)
+class RemoveBaseline(PlottingCtrlNode):
+ """Remove an arbitrary, graphically defined baseline from the data."""
+ nodeName = 'RemoveBaseline'
+
+ def __init__(self, name):
+ ## define inputs and outputs (one output needs to be a plot)
+ PlottingCtrlNode.__init__(self, name)
+ self.line = PolyLineROI([[0,0],[1,0]])
+ self.line.sigRegionChanged.connect(self.changed)
+
+ ## create a PolyLineROI, add it to a plot -- actually, I think we want to do this after the node is connected to a plot (look at EventDetection.ThresholdEvents node for ideas), and possible after there is data. We will need to update the end positions of the line each time the input data changes
+ #self.line = None ## will become a PolyLineROI
+
+ def connectToPlot(self, node):
+ """Define what happens when the node is connected to a plot"""
+
+ if node.plot is None:
+ return
+ node.getPlot().addItem(self.line)
+
+ def disconnectFromPlot(self, plot):
+ """Define what happens when the node is disconnected from a plot"""
+ plot.removeItem(self.line)
+
+ def processData(self, data):
+ ## get array of baseline (from PolyLineROI)
+ h0 = self.line.getHandles()[0]
+ h1 = self.line.getHandles()[-1]
+
+ timeVals = data.xvals(0)
+ h0.setPos(timeVals[0], h0.pos()[1])
+ h1.setPos(timeVals[-1], h1.pos()[1])
+
+ pts = self.line.listPoints() ## lists line handles in same coordinates as data
+ pts, indices = self.adjustXPositions(pts, timeVals) ## maxe sure x positions match x positions of data points
+
+ ## construct an array that represents the baseline
+ arr = np.zeros(len(data), dtype=float)
+ n = 1
+ arr[0] = pts[0].y()
+ for i in range(len(pts)-1):
+ x1 = pts[i].x()
+ x2 = pts[i+1].x()
+ y1 = pts[i].y()
+ y2 = pts[i+1].y()
+ m = (y2-y1)/(x2-x1)
+ b = y1
+
+ times = timeVals[(timeVals > x1)*(timeVals <= x2)]
+ arr[n:n+len(times)] = (m*(times-times[0]))+b
+ n += len(times)
+
+ return data - arr ## subract baseline from data
+
+ def adjustXPositions(self, pts, data):
+ """Return a list of Point() where the x position is set to the nearest x value in *data* for each point in *pts*."""
+ points = []
+ timeIndices = []
+ for p in pts:
+ x = np.argwhere(abs(data - p.x()) == abs(data - p.x()).min())
+ points.append(Point(data[x], p.y()))
+ timeIndices.append(x)
+
+ return points, timeIndices
+
+
class AdaptiveDetrend(CtrlNode):
"""Removes baseline from data, ignoring anomalous events"""
@@ -265,4 +343,4 @@ def processData(self, data):
return ma
-
\ No newline at end of file
+
diff --git a/pyqtgraph/flowchart/library/Operators.py b/papi/pyqtgraph/flowchart/library/Operators.py
similarity index 100%
rename from pyqtgraph/flowchart/library/Operators.py
rename to papi/pyqtgraph/flowchart/library/Operators.py
diff --git a/papi/pyqtgraph/flowchart/library/__init__.py b/papi/pyqtgraph/flowchart/library/__init__.py
new file mode 100644
index 00000000..d8038aa4
--- /dev/null
+++ b/papi/pyqtgraph/flowchart/library/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+from ...pgcollections import OrderedDict
+import os, types
+from ...debug import printExc
+from ..NodeLibrary import NodeLibrary, isNodeClass
+from ... import reload as reload
+
+
+# Build default library
+LIBRARY = NodeLibrary()
+
+# For backward compatibility, expose the default library's properties here:
+NODE_LIST = LIBRARY.nodeList
+NODE_TREE = LIBRARY.nodeTree
+registerNodeType = LIBRARY.addNodeType
+getNodeTree = LIBRARY.getNodeTree
+getNodeType = LIBRARY.getNodeType
+
+# Add all nodes to the default library
+from . import Data, Display, Filters, Operators
+for mod in [Data, Display, Filters, Operators]:
+ nodes = [getattr(mod, name) for name in dir(mod) if isNodeClass(getattr(mod, name))]
+ for node in nodes:
+ LIBRARY.addNodeType(node, [(mod.__name__.split('.')[-1],)])
+
+
+
+
diff --git a/pyqtgraph/flowchart/library/common.py b/papi/pyqtgraph/flowchart/library/common.py
similarity index 72%
rename from pyqtgraph/flowchart/library/common.py
rename to papi/pyqtgraph/flowchart/library/common.py
index 65f8c1fd..425fe86c 100644
--- a/pyqtgraph/flowchart/library/common.py
+++ b/papi/pyqtgraph/flowchart/library/common.py
@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*-
-from pyqtgraph.Qt import QtCore, QtGui
-from pyqtgraph.widgets.SpinBox import SpinBox
-#from pyqtgraph.SignalProxy import SignalProxy
-from pyqtgraph.WidgetGroup import WidgetGroup
+from ...Qt import QtCore, QtGui
+from ...widgets.SpinBox import SpinBox
+#from ...SignalProxy import SignalProxy
+from ...WidgetGroup import WidgetGroup
#from ColorMapper import ColorMapper
from ..Node import Node
import numpy as np
-from pyqtgraph.widgets.ColorButton import ColorButton
+from ...widgets.ColorButton import ColorButton
try:
import metaarray
HAVE_METAARRAY = True
@@ -131,6 +131,42 @@ def showRow(self, name):
l.show()
+class PlottingCtrlNode(CtrlNode):
+ """Abstract class for CtrlNodes that can connect to plots."""
+
+ def __init__(self, name, ui=None, terminals=None):
+ #print "PlottingCtrlNode.__init__ called."
+ CtrlNode.__init__(self, name, ui=ui, terminals=terminals)
+ self.plotTerminal = self.addOutput('plot', optional=True)
+
+ def connected(self, term, remote):
+ CtrlNode.connected(self, term, remote)
+ if term is not self.plotTerminal:
+ return
+ node = remote.node()
+ node.sigPlotChanged.connect(self.connectToPlot)
+ self.connectToPlot(node)
+
+ def disconnected(self, term, remote):
+ CtrlNode.disconnected(self, term, remote)
+ if term is not self.plotTerminal:
+ return
+ remote.node().sigPlotChanged.disconnect(self.connectToPlot)
+ self.disconnectFromPlot(remote.node().getPlot())
+
+ def connectToPlot(self, node):
+ """Define what happens when the node is connected to a plot"""
+ raise Exception("Must be re-implemented in subclass")
+
+ def disconnectFromPlot(self, plot):
+ """Define what happens when the node is disconnected from a plot"""
+ raise Exception("Must be re-implemented in subclass")
+
+ def process(self, In, display=True):
+ out = CtrlNode.process(self, In, display)
+ out['plot'] = None
+ return out
+
def metaArrayWrapper(fn):
def newFn(self, data, *args, **kargs):
diff --git a/pyqtgraph/flowchart/library/functions.py b/papi/pyqtgraph/flowchart/library/functions.py
similarity index 94%
rename from pyqtgraph/flowchart/library/functions.py
rename to papi/pyqtgraph/flowchart/library/functions.py
index 0476e02f..338d25c4 100644
--- a/pyqtgraph/flowchart/library/functions.py
+++ b/papi/pyqtgraph/flowchart/library/functions.py
@@ -1,6 +1,5 @@
-import scipy
import numpy as np
-from pyqtgraph.metaarray import MetaArray
+from ...metaarray import MetaArray
def downsample(data, n, axis=0, xvals='subsample'):
"""Downsample by averaging points together across axis.
@@ -47,6 +46,11 @@ def downsample(data, n, axis=0, xvals='subsample'):
def applyFilter(data, b, a, padding=100, bidir=True):
"""Apply a linear filter with coefficients a, b. Optionally pad the data before filtering
and/or run the filter in both directions."""
+ try:
+ import scipy.signal
+ except ImportError:
+ raise Exception("applyFilter() requires the package scipy.signal.")
+
d1 = data.view(np.ndarray)
if padding > 0:
@@ -67,6 +71,11 @@ def applyFilter(data, b, a, padding=100, bidir=True):
def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True):
"""return data passed through bessel filter"""
+ try:
+ import scipy.signal
+ except ImportError:
+ raise Exception("besselFilter() requires the package scipy.signal.")
+
if dt is None:
try:
tvals = data.xvals('Time')
@@ -85,6 +94,11 @@ def besselFilter(data, cutoff, order=1, dt=None, btype='low', bidir=True):
def butterworthFilter(data, wPass, wStop=None, gPass=2.0, gStop=20.0, order=1, dt=None, btype='low', bidir=True):
"""return data passed through bessel filter"""
+ try:
+ import scipy.signal
+ except ImportError:
+ raise Exception("butterworthFilter() requires the package scipy.signal.")
+
if dt is None:
try:
tvals = data.xvals('Time')
@@ -175,6 +189,11 @@ def denoise(data, radius=2, threshold=4):
def adaptiveDetrend(data, x=None, threshold=3.0):
"""Return the signal with baseline removed. Discards outliers from baseline measurement."""
+ try:
+ import scipy.signal
+ except ImportError:
+ raise Exception("adaptiveDetrend() requires the package scipy.signal.")
+
if x is None:
x = data.xvals(0)
@@ -187,7 +206,7 @@ def adaptiveDetrend(data, x=None, threshold=3.0):
#d3 = where(mask, 0, d2)
#d4 = d2 - lowPass(d3, cutoffs[1], dt=dt)
- lr = stats.linregress(x[mask], d[mask])
+ lr = scipy.stats.linregress(x[mask], d[mask])
base = lr[1] + lr[0]*x
d4 = d - base
diff --git a/pyqtgraph/frozenSupport.py b/papi/pyqtgraph/frozenSupport.py
similarity index 100%
rename from pyqtgraph/frozenSupport.py
rename to papi/pyqtgraph/frozenSupport.py
diff --git a/pyqtgraph/functions.py b/papi/pyqtgraph/functions.py
similarity index 75%
rename from pyqtgraph/functions.py
rename to papi/pyqtgraph/functions.py
index 337dfb67..897a123d 100644
--- a/pyqtgraph/functions.py
+++ b/papi/pyqtgraph/functions.py
@@ -7,15 +7,19 @@
from __future__ import division
from .python2_3 import asUnicode
+from .Qt import QtGui, QtCore, USE_PYSIDE
Colors = {
- 'b': (0,0,255,255),
- 'g': (0,255,0,255),
- 'r': (255,0,0,255),
- 'c': (0,255,255,255),
- 'm': (255,0,255,255),
- 'y': (255,255,0,255),
- 'k': (0,0,0,255),
- 'w': (255,255,255,255),
+ 'b': QtGui.QColor(0,0,255,255),
+ 'g': QtGui.QColor(0,255,0,255),
+ 'r': QtGui.QColor(255,0,0,255),
+ 'c': QtGui.QColor(0,255,255,255),
+ 'm': QtGui.QColor(255,0,255,255),
+ 'y': QtGui.QColor(255,255,0,255),
+ 'k': QtGui.QColor(0,0,0,255),
+ 'w': QtGui.QColor(255,255,255,255),
+ 'd': QtGui.QColor(150,150,150,255),
+ 'l': QtGui.QColor(200,200,200,255),
+ 's': QtGui.QColor(100,100,150,255),
}
SI_PREFIXES = asUnicode('yzafpnµm kMGTPEZY')
@@ -24,23 +28,12 @@
from .Qt import QtGui, QtCore, USE_PYSIDE
-import pyqtgraph as pg
+from . import getConfigOption, setConfigOptions
import numpy as np
import decimal, re
import ctypes
import sys, struct
-try:
- import scipy.ndimage
- HAVE_SCIPY = True
- if pg.getConfigOption('useWeave'):
- try:
- import scipy.weave
- except ImportError:
- pg.setConfigOptions(useWeave=False)
-except ImportError:
- HAVE_SCIPY = False
-
from . import debug
def siScale(x, minVal=1e-25, allowUnicode=True):
@@ -168,17 +161,15 @@ def mkColor(*args):
"""
err = 'Not sure how to make a color from "%s"' % str(args)
if len(args) == 1:
- if isinstance(args[0], QtGui.QColor):
- return QtGui.QColor(args[0])
- elif isinstance(args[0], float):
- r = g = b = int(args[0] * 255)
- a = 255
- elif isinstance(args[0], basestring):
+ if isinstance(args[0], basestring):
c = args[0]
if c[0] == '#':
c = c[1:]
if len(c) == 1:
- (r, g, b, a) = Colors[c]
+ try:
+ return Colors[c]
+ except KeyError:
+ raise Exception('No color named "%s"' % c)
if len(c) == 3:
r = int(c[0]*2, 16)
g = int(c[1]*2, 16)
@@ -199,6 +190,11 @@ def mkColor(*args):
g = int(c[2:4], 16)
b = int(c[4:6], 16)
a = int(c[6:8], 16)
+ elif isinstance(args[0], QtGui.QColor):
+ return QtGui.QColor(args[0])
+ elif isinstance(args[0], float):
+ r = g = b = int(args[0] * 255)
+ a = 255
elif hasattr(args[0], '__len__'):
if len(args[0]) == 3:
(r, g, b) = args[0]
@@ -282,7 +278,7 @@ def mkPen(*args, **kargs):
color = args
if color is None:
- color = mkColor(200, 200, 200)
+ color = mkColor('l')
if hsv is not None:
color = hsvColor(*hsv)
else:
@@ -376,12 +372,12 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
"""
Take a slice of any orientation through an array. This is useful for extracting sections of multi-dimensional arrays such as MRI images for viewing as 1D or 2D data.
- The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates (see the scipy documentation for more information about this).
+ The slicing axes are aribtrary; they do not need to be orthogonal to the original data or even to each other. It is possible to use this function to extract arbitrary linear, rectangular, or parallelepiped shapes from within larger datasets. The original data is interpolated onto a new array of coordinates using scipy.ndimage.map_coordinates if it is available (see the scipy documentation for more information about this). If scipy is not available, then a slower implementation of map_coordinates is used.
For a graphical interface to this function, see :func:`ROI.getArrayRegion `
============== ====================================================================================================
- Arguments:
+ **Arguments:**
*data* (ndarray) the original dataset
*shape* the shape of the slice to take (Note the return value may have more dimensions than len(shape))
*origin* the location in the original dataset that will become the origin of the sliced data.
@@ -415,8 +411,12 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
affineSlice(data, shape=(20,20), origin=(40,0,0), vectors=((-1, 1, 0), (-1, 0, 1)), axes=(1,2,3))
"""
- if not HAVE_SCIPY:
- raise Exception("This function requires the scipy library, but it does not appear to be importable.")
+ try:
+ import scipy.ndimage
+ have_scipy = True
+ except ImportError:
+ have_scipy = False
+ have_scipy = False
# sanity check
if len(shape) != len(vectors):
@@ -438,7 +438,6 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
#print "tr1:", tr1
## dims are now [(slice axes), (other axes)]
-
## make sure vectors are arrays
if not isinstance(vectors, np.ndarray):
vectors = np.array(vectors)
@@ -454,12 +453,18 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
#print "X values:"
#print x
## iterate manually over unused axes since map_coordinates won't do it for us
- extraShape = data.shape[len(axes):]
- output = np.empty(tuple(shape) + extraShape, dtype=data.dtype)
- for inds in np.ndindex(*extraShape):
- ind = (Ellipsis,) + inds
- #print data[ind].shape, x.shape, output[ind].shape, output.shape
- output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs)
+ if have_scipy:
+ extraShape = data.shape[len(axes):]
+ output = np.empty(tuple(shape) + extraShape, dtype=data.dtype)
+ for inds in np.ndindex(*extraShape):
+ ind = (Ellipsis,) + inds
+ output[ind] = scipy.ndimage.map_coordinates(data[ind], x, order=order, **kargs)
+ else:
+ # map_coordinates expects the indexes as the first axis, whereas
+ # interpolateArray expects indexes at the last axis.
+ tr = tuple(range(1,x.ndim)) + (0,)
+ output = interpolateArray(data, x.transpose(tr))
+
tr = list(range(output.ndim))
trb = []
@@ -476,6 +481,160 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
else:
return output
+def interpolateArray(data, x, default=0.0):
+ """
+ N-dimensional interpolation similar scipy.ndimage.map_coordinates.
+
+ This function returns linearly-interpolated values sampled from a regular
+ grid of data.
+
+ *data* is an array of any shape containing the values to be interpolated.
+ *x* is an array with (shape[-1] <= data.ndim) containing the locations
+ within *data* to interpolate.
+
+ Returns array of shape (x.shape[:-1] + data.shape)
+
+ For example, assume we have the following 2D image data::
+
+ >>> data = np.array([[1, 2, 4 ],
+ [10, 20, 40 ],
+ [100, 200, 400]])
+
+ To compute a single interpolated point from this data::
+
+ >>> x = np.array([(0.5, 0.5)])
+ >>> interpolateArray(data, x)
+ array([ 8.25])
+
+ To compute a 1D list of interpolated locations::
+
+ >>> x = np.array([(0.5, 0.5),
+ (1.0, 1.0),
+ (1.0, 2.0),
+ (1.5, 0.0)])
+ >>> interpolateArray(data, x)
+ array([ 8.25, 20. , 40. , 55. ])
+
+ To compute a 2D array of interpolated locations::
+
+ >>> x = np.array([[(0.5, 0.5), (1.0, 2.0)],
+ [(1.0, 1.0), (1.5, 0.0)]])
+ >>> interpolateArray(data, x)
+ array([[ 8.25, 40. ],
+ [ 20. , 55. ]])
+
+ ..and so on. The *x* argument may have any shape as long as
+ ```x.shape[-1] <= data.ndim```. In the case that
+ ```x.shape[-1] < data.ndim```, then the remaining axes are simply
+ broadcasted as usual. For example, we can interpolate one location
+ from an entire row of the data::
+
+ >>> x = np.array([[0.5]])
+ >>> interpolateArray(data, x)
+ array([[ 5.5, 11. , 22. ]])
+
+ This is useful for interpolating from arrays of colors, vertexes, etc.
+ """
+
+ prof = debug.Profiler()
+
+ nd = data.ndim
+ md = x.shape[-1]
+
+ # First we generate arrays of indexes that are needed to
+ # extract the data surrounding each point
+ fields = np.mgrid[(slice(0,2),) * md]
+ xmin = np.floor(x).astype(int)
+ xmax = xmin + 1
+ indexes = np.concatenate([xmin[np.newaxis, ...], xmax[np.newaxis, ...]])
+ fieldInds = []
+ totalMask = np.ones(x.shape[:-1], dtype=bool) # keep track of out-of-bound indexes
+ for ax in range(md):
+ mask = (xmin[...,ax] >= 0) & (x[...,ax] <= data.shape[ax]-1)
+ # keep track of points that need to be set to default
+ totalMask &= mask
+
+ # ..and keep track of indexes that are out of bounds
+ # (note that when x[...,ax] == data.shape[ax], then xmax[...,ax] will be out
+ # of bounds, but the interpolation will work anyway)
+ mask &= (xmax[...,ax] < data.shape[ax])
+ axisIndex = indexes[...,ax][fields[ax]]
+ #axisMask = mask.astype(np.ubyte).reshape((1,)*(fields.ndim-1) + mask.shape)
+ axisIndex[axisIndex < 0] = 0
+ axisIndex[axisIndex >= data.shape[ax]] = 0
+ fieldInds.append(axisIndex)
+ prof()
+
+ # Get data values surrounding each requested point
+ # fieldData[..., i] contains all 2**nd values needed to interpolate x[i]
+ fieldData = data[tuple(fieldInds)]
+ prof()
+
+ ## Interpolate
+ s = np.empty((md,) + fieldData.shape, dtype=float)
+ dx = x - xmin
+ # reshape fields for arithmetic against dx
+ for ax in range(md):
+ f1 = fields[ax].reshape(fields[ax].shape + (1,)*(dx.ndim-1))
+ sax = f1 * dx[...,ax] + (1-f1) * (1-dx[...,ax])
+ sax = sax.reshape(sax.shape + (1,) * (s.ndim-1-sax.ndim))
+ s[ax] = sax
+ s = np.product(s, axis=0)
+ result = fieldData * s
+ for i in range(md):
+ result = result.sum(axis=0)
+
+ prof()
+ totalMask.shape = totalMask.shape + (1,) * (nd - md)
+ result[~totalMask] = default
+ prof()
+ return result
+
+
+def subArray(data, offset, shape, stride):
+ """
+ Unpack a sub-array from *data* using the specified offset, shape, and stride.
+
+ Note that *stride* is specified in array elements, not bytes.
+ For example, we have a 2x3 array packed in a 1D array as follows::
+
+ data = [_, _, 00, 01, 02, _, 10, 11, 12, _]
+
+ Then we can unpack the sub-array with this call::
+
+ subArray(data, offset=2, shape=(2, 3), stride=(4, 1))
+
+ ..which returns::
+
+ [[00, 01, 02],
+ [10, 11, 12]]
+
+ This function operates only on the first axis of *data*. So changing
+ the input in the example above to have shape (10, 7) would cause the
+ output to have shape (2, 3, 7).
+ """
+ #data = data.flatten()
+ data = data[offset:]
+ shape = tuple(shape)
+ stride = tuple(stride)
+ extraShape = data.shape[1:]
+ #print data.shape, offset, shape, stride
+ for i in range(len(shape)):
+ mask = (slice(None),) * i + (slice(None, shape[i] * stride[i]),)
+ newShape = shape[:i+1]
+ if i < len(shape)-1:
+ newShape += (stride[i],)
+ newShape += extraShape
+ #print i, mask, newShape
+ #print "start:\n", data.shape, data
+ data = data[mask]
+ #print "mask:\n", data.shape, data
+ data = data.reshape(newShape)
+ #print "reshape:\n", data.shape, data
+
+ return data
+
+
def transformToArray(tr):
"""
Given a QTransform, return a 3x3 numpy array.
@@ -570,17 +729,25 @@ def transformCoordinates(tr, coords, transpose=False):
def solve3DTransform(points1, points2):
"""
Find a 3D transformation matrix that maps points1 onto points2.
- Points must be specified as a list of 4 Vectors.
+ Points must be specified as either lists of 4 Vectors or
+ (4, 3) arrays.
"""
- if not HAVE_SCIPY:
- raise Exception("This function depends on the scipy library, but it does not appear to be importable.")
- A = np.array([[points1[i].x(), points1[i].y(), points1[i].z(), 1] for i in range(4)])
- B = np.array([[points2[i].x(), points2[i].y(), points2[i].z(), 1] for i in range(4)])
+ import numpy.linalg
+ pts = []
+ for inp in (points1, points2):
+ if isinstance(inp, np.ndarray):
+ A = np.empty((4,4), dtype=float)
+ A[:,:3] = inp[:,:3]
+ A[:,3] = 1.0
+ else:
+ A = np.array([[inp[i].x(), inp[i].y(), inp[i].z(), 1] for i in range(4)])
+ pts.append(A)
## solve 3 sets of linear equations to determine transformation matrix elements
matrix = np.zeros((4,4))
for i in range(3):
- matrix[i] = scipy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix
+ ## solve Ax = B; x is one row of the desired transformation matrix
+ matrix[i] = numpy.linalg.solve(pts[0], pts[1][:,i])
return matrix
@@ -593,8 +760,7 @@ def solveBilinearTransform(points1, points2):
mapped = np.dot(matrix, [x*y, x, y, 1])
"""
- if not HAVE_SCIPY:
- raise Exception("This function depends on the scipy library, but it does not appear to be importable.")
+ import numpy.linalg
## A is 4 rows (points) x 4 columns (xy, x, y, 1)
## B is 4 rows (points) x 2 columns (x, y)
A = np.array([[points1[i].x()*points1[i].y(), points1[i].x(), points1[i].y(), 1] for i in range(4)])
@@ -603,7 +769,7 @@ def solveBilinearTransform(points1, points2):
## solve 2 sets of linear equations to determine transformation matrix elements
matrix = np.zeros((2,4))
for i in range(2):
- matrix[i] = scipy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix
+ matrix[i] = numpy.linalg.solve(A, B[:,i]) ## solve Ax = B; x is one row of the desired transformation matrix
return matrix
@@ -620,8 +786,12 @@ def rescaleData(data, scale, offset, dtype=None):
dtype = np.dtype(dtype)
try:
- if not pg.getConfigOption('useWeave'):
+ if not getConfigOption('useWeave'):
raise Exception('Weave is disabled; falling back to slower version.')
+ try:
+ import scipy.weave
+ except ImportError:
+ raise Exception('scipy.weave is not importable; falling back to slower version.')
## require native dtype when using weave
if not data.dtype.isnative:
@@ -647,10 +817,10 @@ def rescaleData(data, scale, offset, dtype=None):
newData = newData.astype(dtype)
data = newData.reshape(data.shape)
except:
- if pg.getConfigOption('useWeave'):
- if pg.getConfigOption('weaveDebug'):
+ if getConfigOption('useWeave'):
+ if getConfigOption('weaveDebug'):
debug.printExc("Error; disabling weave.")
- pg.setConfigOption('useWeave', False)
+ setConfigOptions(useWeave=False)
#p = np.poly1d([scale, -offset*scale])
#data = p(data).astype(dtype)
@@ -664,68 +834,13 @@ def applyLookupTable(data, lut):
Uses values in *data* as indexes to select values from *lut*.
The returned data has shape data.shape + lut.shape[1:]
- Uses scipy.weave to improve performance if it is available.
Note: color gradient lookup tables can be generated using GradientWidget.
"""
if data.dtype.kind not in ('i', 'u'):
data = data.astype(int)
- ## using np.take appears to be faster than even the scipy.weave method and takes care of clipping as well.
return np.take(lut, data, axis=0, mode='clip')
- ### old methods:
- #data = np.clip(data, 0, lut.shape[0]-1)
-
- #try:
- #if not USE_WEAVE:
- #raise Exception('Weave is disabled; falling back to slower version.')
-
- ### number of values to copy for each LUT lookup
- #if lut.ndim == 1:
- #ncol = 1
- #else:
- #ncol = sum(lut.shape[1:])
-
- ### output array
- #newData = np.empty((data.size, ncol), dtype=lut.dtype)
-
- ### flattened input arrays
- #flatData = data.flatten()
- #flatLut = lut.reshape((lut.shape[0], ncol))
-
- #dataSize = data.size
-
- ### strides for accessing each item
- #newStride = newData.strides[0] / newData.dtype.itemsize
- #lutStride = flatLut.strides[0] / flatLut.dtype.itemsize
- #dataStride = flatData.strides[0] / flatData.dtype.itemsize
-
- ### strides for accessing individual values within a single LUT lookup
- #newColStride = newData.strides[1] / newData.dtype.itemsize
- #lutColStride = flatLut.strides[1] / flatLut.dtype.itemsize
-
- #code = """
-
- #for( int i=0; i0 and max->*scale*::
-
- rescaled = (clip(data, min, max) - min) * (*scale* / (max - min))
-
- It is also possible to use a 2D (N,2) array of values for levels. In this case,
- it is assumed that each pair of min,max values in the levels array should be
- applied to a different subset of the input data (for example, the input data may
- already have RGB values and the levels are used to independently scale each
- channel). The use of this feature requires that levels.shape[0] == data.shape[-1].
- scale The maximum value to which data will be rescaled before being passed through the
- lookup table (or returned if there is no lookup table). By default this will
- be set to the length of the lookup table, or 256 is no lookup table is provided.
- For OpenGL color specifications (as in GLColor4f) use scale=1.0
- lut Optional lookup table (array with dtype=ubyte).
- Values in data will be converted to color by indexing directly from lut.
- The output data shape will be input.shape + lut.shape[1:].
-
- Note: the output of makeARGB will have the same dtype as the lookup table, so
- for conversion to QImage, the dtype must be ubyte.
-
- Lookup tables can be built using GradientWidget.
- useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures).
- The default is False, which returns in ARGB order for use with QImage
- (Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order
- is BGRA).
- ============ ==================================================================================
+ ============== ==================================================================================
+ **Arguments:**
+ data numpy array of int/float types. If
+ levels List [min, max]; optionally rescale data before converting through the
+ lookup table. The data is rescaled such that min->0 and max->*scale*::
+
+ rescaled = (clip(data, min, max) - min) * (*scale* / (max - min))
+
+ It is also possible to use a 2D (N,2) array of values for levels. In this case,
+ it is assumed that each pair of min,max values in the levels array should be
+ applied to a different subset of the input data (for example, the input data may
+ already have RGB values and the levels are used to independently scale each
+ channel). The use of this feature requires that levels.shape[0] == data.shape[-1].
+ scale The maximum value to which data will be rescaled before being passed through the
+ lookup table (or returned if there is no lookup table). By default this will
+ be set to the length of the lookup table, or 256 is no lookup table is provided.
+ For OpenGL color specifications (as in GLColor4f) use scale=1.0
+ lut Optional lookup table (array with dtype=ubyte).
+ Values in data will be converted to color by indexing directly from lut.
+ The output data shape will be input.shape + lut.shape[1:].
+
+ Note: the output of makeARGB will have the same dtype as the lookup table, so
+ for conversion to QImage, the dtype must be ubyte.
+
+ Lookup tables can be built using GradientWidget.
+ useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures).
+ The default is False, which returns in ARGB order for use with QImage
+ (Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order
+ is BGRA).
+ ============== ==================================================================================
"""
- prof = debug.Profiler('functions.makeARGB', disabled=True)
+ profile = debug.Profiler()
if lut is not None and not isinstance(lut, np.ndarray):
lut = np.array(lut)
if levels is not None and not isinstance(levels, np.ndarray):
levels = np.array(levels)
- ## sanity checks
- #if data.ndim == 3:
- #if data.shape[2] not in (3,4):
- #raise Exception("data.shape[2] must be 3 or 4")
- ##if lut is not None:
- ##raise Exception("can not use lookup table with 3D data")
- #elif data.ndim != 2:
- #raise Exception("data must be 2D or 3D")
-
- #if lut is not None:
- ##if lut.ndim == 2:
- ##if lut.shape[1] :
- ##raise Exception("lut.shape[1] must be 3 or 4")
- ##elif lut.ndim != 1:
- ##raise Exception("lut must be 1D or 2D")
- #if lut.dtype != np.ubyte:
- #raise Exception('lookup table must have dtype=ubyte (got %s instead)' % str(lut.dtype))
-
-
if levels is not None:
if levels.ndim == 1:
if len(levels) != 2:
@@ -813,18 +909,8 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
else:
print(levels)
raise Exception("levels argument must be 1D or 2D.")
- #levels = np.array(levels)
- #if levels.shape == (2,):
- #pass
- #elif levels.shape in [(3,2), (4,2)]:
- #if data.ndim == 3:
- #raise Exception("Can not use 2D levels with 3D data.")
- #if lut is not None:
- #raise Exception('Can not use 2D levels and lookup table together.')
- #else:
- #raise Exception("Levels must have shape (2,) or (3,2) or (4,2)")
-
- prof.mark('1')
+
+ profile()
if scale is None:
if lut is not None:
@@ -850,9 +936,12 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
minVal, maxVal = levels
if minVal == maxVal:
maxVal += 1e-16
- data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int)
- prof.mark('2')
+ if maxVal == minVal:
+ data = rescaleData(data, 1, minVal, dtype=int)
+ else:
+ data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int)
+ profile()
## apply LUT if given
if lut is not None:
@@ -860,41 +949,43 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
else:
if data.dtype is not np.ubyte:
data = np.clip(data, 0, 255).astype(np.ubyte)
- prof.mark('3')
+ profile()
## copy data into ARGB ordered array
imgData = np.empty(data.shape[:2]+(4,), dtype=np.ubyte)
- if data.ndim == 2:
- data = data[..., np.newaxis]
- prof.mark('4')
+ profile()
if useRGBA:
order = [0,1,2,3] ## array comes out RGBA
else:
order = [2,1,0,3] ## for some reason, the colors line up as BGR in the final image.
- if data.shape[2] == 1:
+ if data.ndim == 2:
+ # This is tempting:
+ # imgData[..., :3] = data[..., np.newaxis]
+ # ..but it turns out this is faster:
for i in range(3):
- imgData[..., order[i]] = data[..., 0]
+ imgData[..., i] = data
+ elif data.shape[2] == 1:
+ for i in range(3):
+ imgData[..., i] = data[..., 0]
else:
for i in range(0, data.shape[2]):
- imgData[..., order[i]] = data[..., i]
+ imgData[..., i] = data[..., order[i]]
- prof.mark('5')
+ profile()
- if data.shape[2] == 4:
- alpha = True
- else:
+ if data.ndim == 2 or data.shape[2] == 3:
alpha = False
imgData[..., 3] = 255
+ else:
+ alpha = True
- prof.mark('6')
-
- prof.finish()
+ profile()
return imgData, alpha
-
+
def makeQImage(imgData, alpha=None, copy=True, transpose=True):
"""
@@ -904,26 +995,26 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
pointing to the array which shares its data to prevent python
freeing that memory while the image is in use.
- =========== ===================================================================
- Arguments:
- imgData Array of data to convert. Must have shape (width, height, 3 or 4)
- and dtype=ubyte. The order of values in the 3rd axis must be
- (b, g, r, a).
- alpha If True, the QImage returned will have format ARGB32. If False,
- the format will be RGB32. By default, _alpha_ is True if
- array.shape[2] == 4.
- copy If True, the data is copied before converting to QImage.
- If False, the new QImage points directly to the data in the array.
- Note that the array must be contiguous for this to work
- (see numpy.ascontiguousarray).
- transpose If True (the default), the array x/y axes are transposed before
- creating the image. Note that Qt expects the axes to be in
- (height, width) order whereas pyqtgraph usually prefers the
- opposite.
- =========== ===================================================================
+ ============== ===================================================================
+ **Arguments:**
+ imgData Array of data to convert. Must have shape (width, height, 3 or 4)
+ and dtype=ubyte. The order of values in the 3rd axis must be
+ (b, g, r, a).
+ alpha If True, the QImage returned will have format ARGB32. If False,
+ the format will be RGB32. By default, _alpha_ is True if
+ array.shape[2] == 4.
+ copy If True, the data is copied before converting to QImage.
+ If False, the new QImage points directly to the data in the array.
+ Note that the array must be contiguous for this to work
+ (see numpy.ascontiguousarray).
+ transpose If True (the default), the array x/y axes are transposed before
+ creating the image. Note that Qt expects the axes to be in
+ (height, width) order whereas pyqtgraph usually prefers the
+ opposite.
+ ============== ===================================================================
"""
## create QImage from buffer
- prof = debug.Profiler('functions.makeQImage', disabled=True)
+ profile = debug.Profiler()
## If we didn't explicitly specify alpha, check the array shape.
if alpha is None:
@@ -947,7 +1038,9 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
if transpose:
imgData = imgData.transpose((1, 0, 2)) ## QImage expects the row/column order to be opposite
-
+
+ profile()
+
if not imgData.flags['C_CONTIGUOUS']:
if copy is False:
extra = ' (try setting transpose=False)' if transpose else ''
@@ -988,11 +1081,10 @@ def makeQImage(imgData, alpha=None, copy=True, transpose=True):
#except AttributeError: ## happens when image data is non-contiguous
#buf = imgData.data
- #prof.mark('1')
+ #profiler()
#qimage = QtGui.QImage(buf, imgData.shape[1], imgData.shape[0], imgFormat)
- #prof.mark('2')
+ #profiler()
#qimage.data = imgData
- #prof.finish()
#return qimage
def imageToArray(img, copy=False, transpose=True):
@@ -1009,6 +1101,10 @@ def imageToArray(img, copy=False, transpose=True):
else:
ptr.setsize(img.byteCount())
arr = np.asarray(ptr)
+ if img.byteCount() != arr.size * arr.itemsize:
+ # Required for Python 2.6, PyQt 4.10
+ # If this works on all platforms, then there is no need to use np.asarray..
+ arr = np.frombuffer(ptr, np.ubyte, img.byteCount())
if fmt == img.Format_RGB32:
arr = arr.reshape(img.height(), img.width(), 3)
@@ -1067,7 +1163,88 @@ def colorToAlpha(data, color):
#raise Exception()
return np.clip(output, 0, 255).astype(np.ubyte)
+
+def gaussianFilter(data, sigma):
+ """
+ Drop-in replacement for scipy.ndimage.gaussian_filter.
+
+ (note: results are only approximately equal to the output of
+ gaussian_filter)
+ """
+ if np.isscalar(sigma):
+ sigma = (sigma,) * data.ndim
+
+ baseline = data.mean()
+ filtered = data - baseline
+ for ax in range(data.ndim):
+ s = sigma[ax]
+ if s == 0:
+ continue
+
+ # generate 1D gaussian kernel
+ ksize = int(s * 6)
+ x = np.arange(-ksize, ksize)
+ kernel = np.exp(-x**2 / (2*s**2))
+ kshape = [1,] * data.ndim
+ kshape[ax] = len(kernel)
+ kernel = kernel.reshape(kshape)
+
+ # convolve as product of FFTs
+ shape = data.shape[ax] + ksize
+ scale = 1.0 / (abs(s) * (2*np.pi)**0.5)
+ filtered = scale * np.fft.irfft(np.fft.rfft(filtered, shape, axis=ax) *
+ np.fft.rfft(kernel, shape, axis=ax),
+ axis=ax)
+
+ # clip off extra data
+ sl = [slice(None)] * data.ndim
+ sl[ax] = slice(filtered.shape[ax]-data.shape[ax],None,None)
+ filtered = filtered[sl]
+ return filtered + baseline
+
+
+def downsample(data, n, axis=0, xvals='subsample'):
+ """Downsample by averaging points together across axis.
+ If multiple axes are specified, runs once per axis.
+ If a metaArray is given, then the axis values can be either subsampled
+ or downsampled to match.
+ """
+ ma = None
+ if (hasattr(data, 'implements') and data.implements('MetaArray')):
+ ma = data
+ data = data.view(np.ndarray)
+
+ if hasattr(axis, '__len__'):
+ if not hasattr(n, '__len__'):
+ n = [n]*len(axis)
+ for i in range(len(axis)):
+ data = downsample(data, n[i], axis[i])
+ return data
+
+ if n <= 1:
+ return data
+ nPts = int(data.shape[axis] / n)
+ s = list(data.shape)
+ s[axis] = nPts
+ s.insert(axis+1, n)
+ sl = [slice(None)] * data.ndim
+ sl[axis] = slice(0, nPts*n)
+ d1 = data[tuple(sl)]
+ #print d1.shape, s
+ d1.shape = tuple(s)
+ d2 = d1.mean(axis+1)
+
+ if ma is None:
+ return d2
+ else:
+ info = ma.infoCopy()
+ if 'values' in info[axis]:
+ if xvals == 'subsample':
+ info[axis]['values'] = info[axis]['values'][::n][:nPts]
+ elif xvals == 'downsample':
+ info[axis]['values'] = downsample(info[axis]['values'], n)
+ return MetaArray(d2, info=info)
def arrayToQPath(x, y, connect='all'):
@@ -1112,16 +1289,16 @@ def arrayToQPath(x, y, connect='all'):
path = QtGui.QPainterPath()
- #prof = debug.Profiler('PlotCurveItem.generatePath', disabled=True)
+ #profiler = debug.Profiler()
n = x.shape[0]
# create empty array, pad with extra space on either end
arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')])
# write first two integers
- #prof.mark('allocate empty')
+ #profiler('allocate empty')
byteview = arr.view(dtype=np.ubyte)
byteview[:12] = 0
byteview.data[12:20] = struct.pack('>ii', n, 0)
- #prof.mark('pack header')
+ #profiler('pack header')
# Fill array with vertex values
arr[1:-1]['x'] = x
arr[1:-1]['y'] = y
@@ -1129,6 +1306,8 @@ def arrayToQPath(x, y, connect='all'):
# decide which points are connected by lines
if connect == 'pairs':
connect = np.empty((n/2,2), dtype=np.int32)
+ if connect.size != n:
+ raise Exception("x,y array lengths must be multiple of 2 to use connect='pairs'")
connect[:,0] = 1
connect[:,1] = 0
connect = connect.flatten()
@@ -1142,11 +1321,11 @@ def arrayToQPath(x, y, connect='all'):
else:
raise Exception('connect argument must be "all", "pairs", or array')
- #prof.mark('fill array')
+ #profiler('fill array')
# write last 0
lastInd = 20*(n+1)
byteview.data[lastInd:lastInd+4] = struct.pack('>i', 0)
- #prof.mark('footer')
+ #profiler('footer')
# create datastream object and stream into path
## Avoiding this method because QByteArray(str) leaks memory in PySide
@@ -1157,13 +1336,11 @@ def arrayToQPath(x, y, connect='all'):
buf = QtCore.QByteArray.fromRawData(path.strn)
except TypeError:
buf = QtCore.QByteArray(bytes(path.strn))
- #prof.mark('create buffer')
+ #profiler('create buffer')
ds = QtCore.QDataStream(buf)
ds >> path
- #prof.mark('load')
-
- #prof.finish()
+ #profiler('load')
return path
@@ -1258,19 +1435,19 @@ def isocurve(data, level, connected=False, extendToEdge=False, path=False):
"""
Generate isocurve from 2D data using marching squares algorithm.
- ============= =========================================================
- Arguments
- data 2D numpy array of scalar values
- level The level at which to generate an isosurface
- connected If False, return a single long list of point pairs
- If True, return multiple long lists of connected point
- locations. (This is slower but better for drawing
- continuous lines)
- extendToEdge If True, extend the curves to reach the exact edges of
- the data.
- path if True, return a QPainterPath rather than a list of
- vertex coordinates. This forces connected=True.
- ============= =========================================================
+ ============== =========================================================
+ **Arguments:**
+ data 2D numpy array of scalar values
+ level The level at which to generate an isosurface
+ connected If False, return a single long list of point pairs
+ If True, return multiple long lists of connected point
+ locations. (This is slower but better for drawing
+ continuous lines)
+ extendToEdge If True, extend the curves to reach the exact edges of
+ the data.
+ path if True, return a QPainterPath rather than a list of
+ vertex coordinates. This forces connected=True.
+ ============== =========================================================
This function is SLOW; plenty of room for optimization here.
"""
@@ -1292,30 +1469,30 @@ def isocurve(data, level, connected=False, extendToEdge=False, path=False):
data = d2
sideTable = [
- [],
- [0,1],
- [1,2],
- [0,2],
- [0,3],
- [1,3],
- [0,1,2,3],
- [2,3],
- [2,3],
- [0,1,2,3],
- [1,3],
- [0,3],
- [0,2],
- [1,2],
- [0,1],
- []
- ]
+ [],
+ [0,1],
+ [1,2],
+ [0,2],
+ [0,3],
+ [1,3],
+ [0,1,2,3],
+ [2,3],
+ [2,3],
+ [0,1,2,3],
+ [1,3],
+ [0,3],
+ [0,2],
+ [1,2],
+ [0,1],
+ []
+ ]
edgeKey=[
- [(0,1), (0,0)],
- [(0,0), (1,0)],
- [(1,0), (1,1)],
- [(1,1), (0,1)]
- ]
+ [(0,1), (0,0)],
+ [(0,0), (1,0)],
+ [(1,0), (1,1)],
+ [(1,1), (0,1)]
+ ]
lines = []
@@ -1445,7 +1622,11 @@ def traceImage(image, values, smooth=0.5):
If image is RGB or RGBA, then the shape of values should be (nvals, 3/4)
The parameter *smooth* is expressed in pixels.
"""
- import scipy.ndimage as ndi
+ try:
+ import scipy.ndimage as ndi
+ except ImportError:
+ raise Exception("traceImage() requires the package scipy.ndimage, but it is not importable.")
+
if values.ndim == 2:
values = values.T
values = values[np.newaxis, np.newaxis, ...].astype(float)
@@ -1459,7 +1640,7 @@ def traceImage(image, values, smooth=0.5):
paths = []
for i in range(diff.shape[-1]):
d = (labels==i).astype(float)
- d = ndi.gaussian_filter(d, (smooth, smooth))
+ d = gaussianFilter(d, (smooth, smooth))
lines = isocurve(d, 0.5, connected=True, extendToEdge=True)
path = QtGui.QPainterPath()
for line in lines:
@@ -1499,38 +1680,39 @@ def isosurface(data, level):
## edge index tells us which edges are cut by the isosurface.
## (Data stolen from Bourk; see above.)
edgeTable = np.array([
- 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
- 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
- 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
- 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
- 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
- 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
- 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
- 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
- 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
- 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
- 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
- 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
- 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
- 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
- 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
- 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
- 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
- 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
- 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
- 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
- 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
- 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
- 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
- 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
- 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
- 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
- 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
- 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
- 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
- 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
- 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
- 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0 ], dtype=np.uint16)
+ 0x0 , 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
+ 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
+ 0x190, 0x99 , 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
+ 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
+ 0x230, 0x339, 0x33 , 0x13a, 0x636, 0x73f, 0x435, 0x53c,
+ 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
+ 0x3a0, 0x2a9, 0x1a3, 0xaa , 0x7a6, 0x6af, 0x5a5, 0x4ac,
+ 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
+ 0x460, 0x569, 0x663, 0x76a, 0x66 , 0x16f, 0x265, 0x36c,
+ 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
+ 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0xff , 0x3f5, 0x2fc,
+ 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
+ 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x55 , 0x15c,
+ 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
+ 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0xcc ,
+ 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
+ 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
+ 0xcc , 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
+ 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
+ 0x15c, 0x55 , 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
+ 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
+ 0x2fc, 0x3f5, 0xff , 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
+ 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
+ 0x36c, 0x265, 0x16f, 0x66 , 0x76a, 0x663, 0x569, 0x460,
+ 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
+ 0x4ac, 0x5a5, 0x6af, 0x7a6, 0xaa , 0x1a3, 0x2a9, 0x3a0,
+ 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
+ 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x33 , 0x339, 0x230,
+ 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
+ 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x99 , 0x190,
+ 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
+ 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x0
+ ], dtype=np.uint16)
## Table of triangles to use for filling each grid cell.
## Each set of three integers tells us which three edges to
@@ -1808,7 +1990,7 @@ def isosurface(data, level):
[1, 1, 0, 2],
[0, 1, 0, 2],
#[9, 9, 9, 9] ## fake
- ], dtype=np.ubyte)
+ ], dtype=np.uint16) # don't use ubyte here! This value gets added to cell index later; will need the extra precision.
nTableFaces = np.array([len(f)/3 for f in triTable], dtype=np.ubyte)
faceShiftTables = [None]
for i in range(1,6):
@@ -1890,7 +2072,7 @@ def isosurface(data, level):
faces = np.empty((totFaces, 3), dtype=np.uint32)
ptr = 0
#import debug
- #p = debug.Profiler('isosurface', disabled=False)
+ #p = debug.Profiler()
## this helps speed up an indexing operation later on
cs = np.array(cutEdges.strides)//cutEdges.itemsize
@@ -1902,32 +2084,29 @@ def isosurface(data, level):
for i in range(1,6):
### expensive:
- #p.mark('1')
+ #profiler()
cells = np.argwhere(nFaces == i) ## all cells which require i faces (argwhere is expensive)
- #p.mark('2')
+ #profiler()
if cells.shape[0] == 0:
continue
- #cellInds = index[(cells*ins[np.newaxis,:]).sum(axis=1)]
cellInds = index[cells[:,0], cells[:,1], cells[:,2]] ## index values of cells to process for this round
- #p.mark('3')
+ #profiler()
### expensive:
verts = faceShiftTables[i][cellInds]
- #p.mark('4')
+ #profiler()
verts[...,:3] += cells[:,np.newaxis,np.newaxis,:] ## we now have indexes into cutEdges
verts = verts.reshape((verts.shape[0]*i,)+verts.shape[2:])
- #p.mark('5')
+ #profiler()
### expensive:
- #print verts.shape
verts = (verts * cs[np.newaxis, np.newaxis, :]).sum(axis=2)
- #vertInds = cutEdges[verts[...,0], verts[...,1], verts[...,2], verts[...,3]] ## and these are the vertex indexes we want.
vertInds = cutEdges[verts]
- #p.mark('6')
+ #profiler()
nv = vertInds.shape[0]
- #p.mark('7')
+ #profiler()
faces[ptr:ptr+nv] = vertInds #.reshape((nv, 3))
- #p.mark('8')
+ #profiler()
ptr += nv
return vertexes, faces
@@ -1942,14 +2121,16 @@ def invertQTransform(tr):
bugs in that method. (specifically, Qt has floating-point precision issues
when determining whether a matrix is invertible)
"""
- if not HAVE_SCIPY:
+ try:
+ import numpy.linalg
+ arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]])
+ inv = numpy.linalg.inv(arr)
+ return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1])
+ except ImportError:
inv = tr.inverted()
if inv[1] is False:
raise Exception("Transform is not invertible.")
return inv[0]
- arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]])
- inv = scipy.linalg.inv(arr)
- return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1])
def pseudoScatter(data, spacing=None, shuffle=True, bidir=False):
@@ -2021,3 +2202,51 @@ def pseudoScatter(data, spacing=None, shuffle=True, bidir=False):
yvals[i] = y
return yvals[np.argsort(inds)] ## un-shuffle values before returning
+
+
+
+def toposort(deps, nodes=None, seen=None, stack=None, depth=0):
+ """Topological sort. Arguments are:
+ deps dictionary describing dependencies where a:[b,c] means "a depends on b and c"
+ nodes optional, specifies list of starting nodes (these should be the nodes
+ which are not depended on by any other nodes). Other candidate starting
+ nodes will be ignored.
+
+ Example::
+
+ # Sort the following graph:
+ #
+ # B ──┬─────> C <── D
+ # │ │
+ # E <─┴─> A <─┘
+ #
+ deps = {'a': ['b', 'c'], 'c': ['b', 'd'], 'e': ['b']}
+ toposort(deps)
+ => ['b', 'd', 'c', 'a', 'e']
+ """
+ # fill in empty dep lists
+ deps = deps.copy()
+ for k,v in list(deps.items()):
+ for k in v:
+ if k not in deps:
+ deps[k] = []
+
+ if nodes is None:
+ ## run through deps to find nodes that are not depended upon
+ rem = set()
+ for dep in deps.values():
+ rem |= set(dep)
+ nodes = set(deps.keys()) - rem
+ if seen is None:
+ seen = set()
+ stack = []
+ sorted = []
+ for n in nodes:
+ if n in stack:
+ raise Exception("Cyclic dependency detected", stack + [n])
+ if n in seen:
+ continue
+ seen.add(n)
+ sorted.extend( toposort(deps, deps[n], seen, stack+[n], depth=depth+1))
+ sorted.append(n)
+ return sorted
diff --git a/pyqtgraph/graphicsItems/ArrowItem.py b/papi/pyqtgraph/graphicsItems/ArrowItem.py
similarity index 60%
rename from pyqtgraph/graphicsItems/ArrowItem.py
rename to papi/pyqtgraph/graphicsItems/ArrowItem.py
index dcede02a..77e6195f 100644
--- a/pyqtgraph/graphicsItems/ArrowItem.py
+++ b/papi/pyqtgraph/graphicsItems/ArrowItem.py
@@ -1,5 +1,5 @@
-from pyqtgraph.Qt import QtGui, QtCore
-import pyqtgraph.functions as fn
+from ..Qt import QtGui, QtCore
+from .. import functions as fn
import numpy as np
__all__ = ['ArrowItem']
@@ -16,12 +16,14 @@ def __init__(self, **opts):
Arrows can be initialized with any keyword arguments accepted by
the setStyle() method.
"""
+ self.opts = {}
QtGui.QGraphicsPathItem.__init__(self, opts.get('parent', None))
+
if 'size' in opts:
opts['headLen'] = opts['size']
if 'width' in opts:
opts['headWidth'] = opts['width']
- defOpts = {
+ defaultOpts = {
'pxMode': True,
'angle': -150, ## If the angle is 0, the arrow points left
'pos': (0,0),
@@ -33,12 +35,9 @@ def __init__(self, **opts):
'pen': (200,200,200),
'brush': (50,50,200),
}
- defOpts.update(opts)
-
- self.setStyle(**defOpts)
+ defaultOpts.update(opts)
- self.setPen(fn.mkPen(defOpts['pen']))
- self.setBrush(fn.mkBrush(defOpts['brush']))
+ self.setStyle(**defaultOpts)
self.rotate(self.opts['angle'])
self.moveBy(*self.opts['pos'])
@@ -48,35 +47,38 @@ def setStyle(self, **opts):
Changes the appearance of the arrow.
All arguments are optional:
- ================= =================================================
- Keyword Arguments
- angle Orientation of the arrow in degrees. Default is
- 0; arrow pointing to the left.
- headLen Length of the arrow head, from tip to base.
- default=20
- headWidth Width of the arrow head at its base.
- tipAngle Angle of the tip of the arrow in degrees. Smaller
- values make a 'sharper' arrow. If tipAngle is
- specified, ot overrides headWidth. default=25
- baseAngle Angle of the base of the arrow head. Default is
- 0, which means that the base of the arrow head
- is perpendicular to the arrow shaft.
- tailLen Length of the arrow tail, measured from the base
- of the arrow head to the tip of the tail. If
- this value is None, no tail will be drawn.
- default=None
- tailWidth Width of the tail. default=3
- pen The pen used to draw the outline of the arrow.
- brush The brush used to fill the arrow.
- ================= =================================================
+ ====================== =================================================
+ **Keyword Arguments:**
+ angle Orientation of the arrow in degrees. Default is
+ 0; arrow pointing to the left.
+ headLen Length of the arrow head, from tip to base.
+ default=20
+ headWidth Width of the arrow head at its base.
+ tipAngle Angle of the tip of the arrow in degrees. Smaller
+ values make a 'sharper' arrow. If tipAngle is
+ specified, ot overrides headWidth. default=25
+ baseAngle Angle of the base of the arrow head. Default is
+ 0, which means that the base of the arrow head
+ is perpendicular to the arrow tail.
+ tailLen Length of the arrow tail, measured from the base
+ of the arrow head to the end of the tail. If
+ this value is None, no tail will be drawn.
+ default=None
+ tailWidth Width of the tail. default=3
+ pen The pen used to draw the outline of the arrow.
+ brush The brush used to fill the arrow.
+ ====================== =================================================
"""
- self.opts = opts
+ self.opts.update(opts)
opt = dict([(k,self.opts[k]) for k in ['headLen', 'tipAngle', 'baseAngle', 'tailLen', 'tailWidth']])
self.path = fn.makeArrowPath(**opt)
self.setPath(self.path)
- if opts['pxMode']:
+ self.setPen(fn.mkPen(self.opts['pen']))
+ self.setBrush(fn.mkBrush(self.opts['brush']))
+
+ if self.opts['pxMode']:
self.setFlags(self.flags() | self.ItemIgnoresTransformations)
else:
self.setFlags(self.flags() & ~self.ItemIgnoresTransformations)
@@ -121,4 +123,4 @@ def pixelPadding(self):
return pad
-
\ No newline at end of file
+
diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/papi/pyqtgraph/graphicsItems/AxisItem.py
similarity index 74%
rename from pyqtgraph/graphicsItems/AxisItem.py
rename to papi/pyqtgraph/graphicsItems/AxisItem.py
index 429ff49c..b125cb7e 100644
--- a/pyqtgraph/graphicsItems/AxisItem.py
+++ b/papi/pyqtgraph/graphicsItems/AxisItem.py
@@ -1,11 +1,11 @@
-from pyqtgraph.Qt import QtGui, QtCore
-from pyqtgraph.python2_3 import asUnicode
+from ..Qt import QtGui, QtCore
+from ..python2_3 import asUnicode
import numpy as np
-from pyqtgraph.Point import Point
-import pyqtgraph.debug as debug
+from ..Point import Point
+from .. import debug as debug
import weakref
-import pyqtgraph.functions as fn
-import pyqtgraph as pg
+from .. import functions as fn
+from .. import getConfigOption
from .GraphicsWidget import GraphicsWidget
__all__ = ['AxisItem']
@@ -33,7 +33,6 @@ def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLen
GraphicsWidget.__init__(self, parent)
self.label = QtGui.QGraphicsTextItem(self)
- self.showValues = showValues
self.picture = None
self.orientation = orientation
if orientation not in ['left', 'right', 'top', 'bottom']:
@@ -42,7 +41,7 @@ def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLen
self.label.rotate(-90)
self.style = {
- 'tickTextOffset': (5, 2), ## (horizontal, vertical) spacing between text and axis
+ 'tickTextOffset': [5, 2], ## (horizontal, vertical) spacing between text and axis
'tickTextWidth': 30, ## space reserved for tick text
'tickTextHeight': 18,
'autoExpandTextSpace': True, ## automatically expand text space if needed
@@ -53,12 +52,21 @@ def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLen
(2, 0.6), ## If we already have 2 ticks with text, fill no more than 60% of the axis
(4, 0.4), ## If we already have 4 ticks with text, fill no more than 40% of the axis
(6, 0.2), ## If we already have 6 ticks with text, fill no more than 20% of the axis
- ]
+ ],
+ 'showValues': showValues,
+ 'tickLength': maxTickLength,
+ 'maxTickLevel': 2,
+ 'maxTextLevel': 2,
}
self.textWidth = 30 ## Keeps track of maximum width / height of tick text
self.textHeight = 18
+ # If the user specifies a width / height, remember that setting
+ # indefinitely.
+ self.fixedWidth = None
+ self.fixedHeight = None
+
self.labelText = ''
self.labelUnits = ''
self.labelUnitPrefix=''
@@ -66,15 +74,18 @@ def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLen
self.logMode = False
self.tickFont = None
- self.tickLength = maxTickLength
self._tickLevels = None ## used to override the automatic ticking system with explicit ticks
+ self._tickSpacing = None # used to override default tickSpacing method
self.scale = 1.0
self.autoSIPrefix = True
self.autoSIPrefixScale = 1.0
self.setRange(0, 1)
- self.setPen(pen)
+ if pen is None:
+ self.setPen()
+ else:
+ self.setPen(pen)
self._linkedView = None
if linkView is not None:
@@ -84,6 +95,73 @@ def __init__(self, orientation, pen=None, linkView=None, parent=None, maxTickLen
self.grid = False
#self.setCacheMode(self.DeviceCoordinateCache)
+
+ def setStyle(self, **kwds):
+ """
+ Set various style options.
+
+ =================== =======================================================
+ Keyword Arguments:
+ tickLength (int) The maximum length of ticks in pixels.
+ Positive values point toward the text; negative
+ values point away.
+ tickTextOffset (int) reserved spacing between text and axis in px
+ tickTextWidth (int) Horizontal space reserved for tick text in px
+ tickTextHeight (int) Vertical space reserved for tick text in px
+ autoExpandTextSpace (bool) Automatically expand text space if the tick
+ strings become too long.
+ tickFont (QFont or None) Determines the font used for tick
+ values. Use None for the default font.
+ stopAxisAtTick (tuple: (bool min, bool max)) If True, the axis
+ line is drawn only as far as the last tick.
+ Otherwise, the line is drawn to the edge of the
+ AxisItem boundary.
+ textFillLimits (list of (tick #, % fill) tuples). This structure
+ determines how the AxisItem decides how many ticks
+ should have text appear next to them. Each tuple in
+ the list specifies what fraction of the axis length
+ may be occupied by text, given the number of ticks
+ that already have text displayed. For example::
+
+ [(0, 0.8), # Never fill more than 80% of the axis
+ (2, 0.6), # If we already have 2 ticks with text,
+ # fill no more than 60% of the axis
+ (4, 0.4), # If we already have 4 ticks with text,
+ # fill no more than 40% of the axis
+ (6, 0.2)] # If we already have 6 ticks with text,
+ # fill no more than 20% of the axis
+
+ showValues (bool) indicates whether text is displayed adjacent
+ to ticks.
+ =================== =======================================================
+
+ Added in version 0.9.9
+ """
+ for kwd,value in kwds.items():
+ if kwd not in self.style:
+ raise NameError("%s is not a valid style argument." % kwd)
+
+ if kwd in ('tickLength', 'tickTextOffset', 'tickTextWidth', 'tickTextHeight'):
+ if not isinstance(value, int):
+ raise ValueError("Argument '%s' must be int" % kwd)
+
+ if kwd == 'tickTextOffset':
+ if self.orientation in ('left', 'right'):
+ self.style['tickTextOffset'][0] = value
+ else:
+ self.style['tickTextOffset'][1] = value
+ elif kwd == 'stopAxisAtTick':
+ try:
+ assert len(value) == 2 and isinstance(value[0], bool) and isinstance(value[1], bool)
+ except:
+ raise ValueError("Argument 'stopAxisAtTick' must have type (bool, bool)")
+ self.style[kwd] = value
+ else:
+ self.style[kwd] = value
+
+ self.picture = None
+ self._adjustSize()
+ self.update()
def close(self):
self.scene().removeItem(self.label)
@@ -91,7 +169,11 @@ def close(self):
self.scene().removeItem(self)
def setGrid(self, grid):
- """Set the alpha value for the grid, or False to disable."""
+ """Set the alpha value (0-255) for the grid, or False to disable.
+
+ When grid lines are enabled, the axis tick lines are extended to cover
+ the extent of the linked ViewBox, if any.
+ """
self.grid = grid
self.picture = None
self.prepareGeometryChange()
@@ -125,20 +207,15 @@ def resizeEvent(self, ev=None):
if self.orientation == 'left':
p.setY(int(self.size().height()/2 + br.width()/2))
p.setX(-nudge)
- #s.setWidth(10)
elif self.orientation == 'right':
- #s.setWidth(10)
p.setY(int(self.size().height()/2 + br.width()/2))
p.setX(int(self.size().width()-br.height()+nudge))
elif self.orientation == 'top':
- #s.setHeight(10)
p.setY(-nudge)
p.setX(int(self.size().width()/2. - br.width()/2.))
elif self.orientation == 'bottom':
p.setX(int(self.size().width()/2. - br.width()/2.))
- #s.setHeight(10)
p.setY(int(self.size().height()-br.height()+nudge))
- #self.label.resize(s)
self.label.setPos(p)
self.picture = None
@@ -147,26 +224,26 @@ def showLabel(self, show=True):
#self.drawLabel = show
self.label.setVisible(show)
if self.orientation in ['left', 'right']:
- self.setWidth()
+ self._updateWidth()
else:
- self.setHeight()
+ self._updateHeight()
if self.autoSIPrefix:
self.updateAutoSIPrefix()
def setLabel(self, text=None, units=None, unitPrefix=None, **args):
"""Set the text displayed adjacent to the axis.
- ============= =============================================================
- Arguments
- text The text (excluding units) to display on the label for this
- axis.
- units The units for this axis. Units should generally be given
- without any scaling prefix (eg, 'V' instead of 'mV'). The
- scaling prefix will be automatically prepended based on the
- range of data displayed.
- **args All extra keyword arguments become CSS style options for
- the tag which will surround the axis label and units.
- ============= =============================================================
+ ============== =============================================================
+ **Arguments:**
+ text The text (excluding units) to display on the label for this
+ axis.
+ units The units for this axis. Units should generally be given
+ without any scaling prefix (eg, 'V' instead of 'mV'). The
+ scaling prefix will be automatically prepended based on the
+ range of data displayed.
+ **args All extra keyword arguments become CSS style options for
+ the tag which will surround the axis label and units.
+ ============== =============================================================
The final text generated for the label will look like::
@@ -219,69 +296,101 @@ def _updateMaxTextSize(self, x):
if mx > self.textWidth or mx < self.textWidth-10:
self.textWidth = mx
if self.style['autoExpandTextSpace'] is True:
- self.setWidth()
+ self._updateWidth()
#return True ## size has changed
else:
mx = max(self.textHeight, x)
if mx > self.textHeight or mx < self.textHeight-10:
self.textHeight = mx
if self.style['autoExpandTextSpace'] is True:
- self.setHeight()
+ self._updateHeight()
#return True ## size has changed
def _adjustSize(self):
if self.orientation in ['left', 'right']:
- self.setWidth()
+ self._updateWidth()
else:
- self.setHeight()
+ self._updateHeight()
def setHeight(self, h=None):
"""Set the height of this axis reserved for ticks and tick labels.
- The height of the axis label is automatically added."""
- if h is None:
- if self.style['autoExpandTextSpace'] is True:
- h = self.textHeight
+ The height of the axis label is automatically added.
+
+ If *height* is None, then the value will be determined automatically
+ based on the size of the tick text."""
+ self.fixedHeight = h
+ self._updateHeight()
+
+ def _updateHeight(self):
+ if not self.isVisible():
+ h = 0
+ else:
+ if self.fixedHeight is None:
+ if not self.style['showValues']:
+ h = 0
+ elif self.style['autoExpandTextSpace'] is True:
+ h = self.textHeight
+ else:
+ h = self.style['tickTextHeight']
+ h += self.style['tickTextOffset'][1] if self.style['showValues'] else 0
+ h += max(0, self.style['tickLength'])
+ if self.label.isVisible():
+ h += self.label.boundingRect().height() * 0.8
else:
- h = self.style['tickTextHeight']
- h += max(0, self.tickLength) + self.style['tickTextOffset'][1]
- if self.label.isVisible():
- h += self.label.boundingRect().height() * 0.8
+ h = self.fixedHeight
+
self.setMaximumHeight(h)
self.setMinimumHeight(h)
self.picture = None
-
def setWidth(self, w=None):
"""Set the width of this axis reserved for ticks and tick labels.
- The width of the axis label is automatically added."""
- if w is None:
- if self.style['autoExpandTextSpace'] is True:
- w = self.textWidth
+ The width of the axis label is automatically added.
+
+ If *width* is None, then the value will be determined automatically
+ based on the size of the tick text."""
+ self.fixedWidth = w
+ self._updateWidth()
+
+ def _updateWidth(self):
+ if not self.isVisible():
+ w = 0
+ else:
+ if self.fixedWidth is None:
+ if not self.style['showValues']:
+ w = 0
+ elif self.style['autoExpandTextSpace'] is True:
+ w = self.textWidth
+ else:
+ w = self.style['tickTextWidth']
+ w += self.style['tickTextOffset'][0] if self.style['showValues'] else 0
+ w += max(0, self.style['tickLength'])
+ if self.label.isVisible():
+ w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate
else:
- w = self.style['tickTextWidth']
- w += max(0, self.tickLength) + self.style['tickTextOffset'][0]
- if self.label.isVisible():
- w += self.label.boundingRect().height() * 0.8 ## bounding rect is usually an overestimate
+ w = self.fixedWidth
+
self.setMaximumWidth(w)
self.setMinimumWidth(w)
self.picture = None
def pen(self):
if self._pen is None:
- return fn.mkPen(pg.getConfigOption('foreground'))
- return pg.mkPen(self._pen)
+ return fn.mkPen(getConfigOption('foreground'))
+ return fn.mkPen(self._pen)
- def setPen(self, pen):
+ def setPen(self, *args, **kwargs):
"""
Set the pen used for drawing text, axes, ticks, and grid lines.
- if pen == None, the default will be used (see :func:`setConfigOption
- `)
+ If no arguments are given, the default foreground color will be used
+ (see :func:`setConfigOption `).
"""
- self._pen = pen
self.picture = None
- if pen is None:
- pen = pg.getConfigOption('foreground')
- self.labelStyle['color'] = '#' + pg.colorStr(pg.mkPen(pen).color())[:6]
+ if args or kwargs:
+ self._pen = fn.mkPen(*args, **kwargs)
+ else:
+ self._pen = fn.mkPen(getConfigOption('foreground'))
+ self.labelStyle['color'] = '#' + fn.colorStr(self._pen.color())[:6]
self.setLabel()
self.update()
@@ -383,7 +492,10 @@ def linkedViewChanged(self, view, newRange=None):
else:
if newRange is None:
newRange = view.viewRange()[0]
- self.setRange(*newRange)
+ if view.xInverted():
+ self.setRange(*newRange[::-1])
+ else:
+ self.setRange(*newRange)
def boundingRect(self):
linkedView = self.linkedView()
@@ -391,38 +503,36 @@ def boundingRect(self):
rect = self.mapRectFromParent(self.geometry())
## extend rect if ticks go in negative direction
## also extend to account for text that flows past the edges
+ tl = self.style['tickLength']
if self.orientation == 'left':
- rect = rect.adjusted(0, -15, -min(0,self.tickLength), 15)
+ rect = rect.adjusted(0, -15, -min(0,tl), 15)
elif self.orientation == 'right':
- rect = rect.adjusted(min(0,self.tickLength), -15, 0, 15)
+ rect = rect.adjusted(min(0,tl), -15, 0, 15)
elif self.orientation == 'top':
- rect = rect.adjusted(-15, 0, 15, -min(0,self.tickLength))
+ rect = rect.adjusted(-15, 0, 15, -min(0,tl))
elif self.orientation == 'bottom':
- rect = rect.adjusted(-15, min(0,self.tickLength), 15, 0)
+ rect = rect.adjusted(-15, min(0,tl), 15, 0)
return rect
else:
return self.mapRectFromParent(self.geometry()) | linkedView.mapRectToItem(self, linkedView.boundingRect())
def paint(self, p, opt, widget):
- prof = debug.Profiler('AxisItem.paint', disabled=True)
+ profiler = debug.Profiler()
if self.picture is None:
try:
picture = QtGui.QPicture()
painter = QtGui.QPainter(picture)
specs = self.generateDrawSpecs(painter)
- prof.mark('generate specs')
+ profiler('generate specs')
if specs is not None:
self.drawPicture(painter, *specs)
- prof.mark('draw picture')
+ profiler('draw picture')
finally:
painter.end()
self.picture = picture
#p.setRenderHint(p.Antialiasing, False) ## Sometimes we get a segfault here ???
#p.setRenderHint(p.TextAntialiasing, True)
self.picture.play(p)
- prof.finish()
-
-
def setTicks(self, ticks):
"""Explicitly determine which ticks to display.
@@ -441,6 +551,37 @@ def setTicks(self, ticks):
self.picture = None
self.update()
+ def setTickSpacing(self, major=None, minor=None, levels=None):
+ """
+ Explicitly determine the spacing of major and minor ticks. This
+ overrides the default behavior of the tickSpacing method, and disables
+ the effect of setTicks(). Arguments may be either *major* and *minor*,
+ or *levels* which is a list of (spacing, offset) tuples for each
+ tick level desired.
+
+ If no arguments are given, then the default behavior of tickSpacing
+ is enabled.
+
+ Examples::
+
+ # two levels, all offsets = 0
+ axis.setTickSpacing(5, 1)
+ # three levels, all offsets = 0
+ axis.setTickSpacing([(3, 0), (1, 0), (0.25, 0)])
+ # reset to default
+ axis.setTickSpacing()
+ """
+
+ if levels is None:
+ if major is None:
+ levels = None
+ else:
+ levels = [(major, 0), (minor, 0)]
+ self._tickSpacing = levels
+ self.picture = None
+ self.update()
+
+
def tickSpacing(self, minVal, maxVal, size):
"""Return values describing the desired spacing and offset of ticks.
@@ -456,13 +597,16 @@ def tickSpacing(self, minVal, maxVal, size):
...
]
"""
+ # First check for override tick spacing
+ if self._tickSpacing is not None:
+ return self._tickSpacing
+
dif = abs(maxVal - minVal)
if dif == 0:
return []
## decide optimal minor tick spacing in pixels (this is just aesthetics)
- pixelSpacing = size / np.log(size)
- optimalTickCount = max(2., size / pixelSpacing)
+ optimalTickCount = max(2., np.log(size))
## optimal minor tick spacing
optimalSpacing = dif / optimalTickCount
@@ -482,12 +626,13 @@ def tickSpacing(self, minVal, maxVal, size):
#(intervals[minorIndex], 0) ## Pretty, but eats up CPU
]
- ## decide whether to include the last level of ticks
- minSpacing = min(size / 20., 30.)
- maxTickCount = size / minSpacing
- if dif / intervals[minorIndex] <= maxTickCount:
- levels.append((intervals[minorIndex], 0))
- return levels
+ if self.style['maxTickLevel'] >= 2:
+ ## decide whether to include the last level of ticks
+ minSpacing = min(size / 20., 30.)
+ maxTickCount = size / minSpacing
+ if dif / intervals[minorIndex] <= maxTickCount:
+ levels.append((intervals[minorIndex], 0))
+ return levels
@@ -513,8 +658,6 @@ def tickSpacing(self, minVal, maxVal, size):
#(intervals[intIndexes[0]], 0)
#]
-
-
def tickValues(self, minVal, maxVal, size):
"""
Return the values and spacing of ticks to draw::
@@ -622,12 +765,12 @@ def logTickStrings(self, values, scale, spacing):
def generateDrawSpecs(self, p):
"""
- Calls tickValues() and tickStrings to determine where and how ticks should
+ Calls tickValues() and tickStrings() to determine where and how ticks should
be drawn, then generates from this a set of drawing commands to be
interpreted by drawPicture().
"""
- prof = debug.Profiler("AxisItem.generateDrawSpecs", disabled=True)
-
+ profiler = debug.Profiler()
+
#bounds = self.boundingRect()
bounds = self.mapRectFromParent(self.geometry())
@@ -671,6 +814,7 @@ def generateDrawSpecs(self, p):
if lengthInPixels == 0:
return
+ # Determine major / minor / subminor axis ticks
if self._tickLevels is None:
tickLevels = self.tickValues(self.range[0], self.range[1], lengthInPixels)
tickStrings = None
@@ -687,12 +831,10 @@ def generateDrawSpecs(self, p):
values.append(val)
strings.append(strn)
- textLevel = 1 ## draw text at this scale level
-
## determine mapping between tick values and local coordinates
dif = self.range[1] - self.range[0]
if dif == 0:
- xscale = 1
+ xScale = 1
offset = 0
else:
if axis == 0:
@@ -706,12 +848,11 @@ def generateDrawSpecs(self, p):
xMin = min(xRange)
xMax = max(xRange)
- prof.mark('init')
+ profiler('init')
tickPositions = [] # remembers positions of previously drawn ticks
- ## draw ticks
- ## (to improve performance, we do not interleave line and text drawing, since this causes unnecessary pipeline switching)
+ ## compute coordinates to draw ticks
## draw three different intervals, long ticks first
tickSpecs = []
for i in range(len(tickLevels)):
@@ -719,7 +860,7 @@ def generateDrawSpecs(self, p):
ticks = tickLevels[i][1]
## length of tick
- tickLength = self.tickLength / ((i*0.5)+1.0)
+ tickLength = self.style['tickLength'] / ((i*0.5)+1.0)
lineAlpha = 255 / (i+1)
if self.grid is not False:
@@ -744,9 +885,8 @@ def generateDrawSpecs(self, p):
color.setAlpha(lineAlpha)
tickPen.setColor(color)
tickSpecs.append((tickPen, Point(p1), Point(p2)))
- prof.mark('compute ticks')
+ profiler('compute ticks')
- ## This is where the long axis line should be drawn
if self.style['stopAxisAtTick'][0] is True:
stop = max(span[0].y(), min(map(min, tickPositions)))
@@ -763,7 +903,6 @@ def generateDrawSpecs(self, p):
axisSpec = (self.pen(), span[0], span[1])
-
textOffset = self.style['tickTextOffset'][axis] ## spacing between axis and text
#if self.style['autoExpandTextSpace'] is True:
#textWidth = self.textWidth
@@ -775,8 +914,12 @@ def generateDrawSpecs(self, p):
textSize2 = 0
textRects = []
textSpecs = [] ## list of draw
- textSize2 = 0
- for i in range(len(tickLevels)):
+
+ # If values are hidden, return early
+ if not self.style['showValues']:
+ return (axisSpec, tickSpecs, textSpecs)
+
+ for i in range(min(len(tickLevels), self.style['maxTextLevel']+1)):
## Get the list of strings to display for this level
if tickStrings is None:
spacing, values = tickLevels[i]
@@ -798,7 +941,7 @@ def generateDrawSpecs(self, p):
if s is None:
rects.append(None)
else:
- br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, str(s))
+ br = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, asUnicode(s))
## boundingRect is usually just a bit too large
## (but this probably depends on per-font metrics?)
br.setHeight(br.height() * 0.8)
@@ -806,7 +949,7 @@ def generateDrawSpecs(self, p):
rects.append(br)
textRects.append(rects[-1])
- if i > 0: ## always draw top level
+ if len(textRects) > 0:
## measure all text, make sure there's enough room
if axis == 0:
textSize = np.sum([r.height() for r in textRects])
@@ -814,7 +957,11 @@ def generateDrawSpecs(self, p):
else:
textSize = np.sum([r.width() for r in textRects])
textSize2 = np.max([r.height() for r in textRects])
+ else:
+ textSize = 0
+ textSize2 = 0
+ if i > 0: ## always draw top level
## If the strings are too crowded, stop drawing text now.
## We use three different crowding limits based on the number
## of texts drawn so far.
@@ -829,18 +976,19 @@ def generateDrawSpecs(self, p):
#spacing, values = tickLevels[best]
#strings = self.tickStrings(values, self.scale, spacing)
+ # Determine exactly where tick text should be drawn
for j in range(len(strings)):
vstr = strings[j]
if vstr is None: ## this tick was ignored because it is out of bounds
continue
- vstr = str(vstr)
+ vstr = asUnicode(vstr)
x = tickPositions[i][j]
#textRect = p.boundingRect(QtCore.QRectF(0, 0, 100, 100), QtCore.Qt.AlignCenter, vstr)
textRect = rects[j]
height = textRect.height()
width = textRect.width()
#self.textHeight = height
- offset = max(0,self.tickLength) + textOffset
+ offset = max(0,self.style['tickLength']) + textOffset
if self.orientation == 'left':
textFlags = QtCore.Qt.TextDontClip|QtCore.Qt.AlignRight|QtCore.Qt.AlignVCenter
rect = QtCore.QRectF(tickStop-offset-width, x-(height/2), width, height)
@@ -857,16 +1005,16 @@ def generateDrawSpecs(self, p):
#p.setPen(self.pen())
#p.drawText(rect, textFlags, vstr)
textSpecs.append((rect, textFlags, vstr))
- prof.mark('compute text')
-
+ profiler('compute text')
+
## update max text size if needed.
self._updateMaxTextSize(textSize2)
return (axisSpec, tickSpecs, textSpecs)
def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
- prof = debug.Profiler("AxisItem.drawPicture", disabled=True)
-
+ profiler = debug.Profiler()
+
p.setRenderHint(p.Antialiasing, False)
p.setRenderHint(p.TextAntialiasing, True)
@@ -880,8 +1028,8 @@ def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
for pen, p1, p2 in tickSpecs:
p.setPen(pen)
p.drawLine(p1, p2)
- prof.mark('draw ticks')
-
+ profiler('draw ticks')
+
## Draw all text
if self.tickFont is not None:
p.setFont(self.tickFont)
@@ -889,24 +1037,21 @@ def drawPicture(self, p, axisSpec, tickSpecs, textSpecs):
for rect, flags, text in textSpecs:
p.drawText(rect, flags, text)
#p.drawRect(rect)
-
- prof.mark('draw text')
- prof.finish()
-
+ profiler('draw text')
+
def show(self):
-
+ GraphicsWidget.show(self)
if self.orientation in ['left', 'right']:
- self.setWidth()
+ self._updateWidth()
else:
- self.setHeight()
- GraphicsWidget.show(self)
+ self._updateHeight()
def hide(self):
+ GraphicsWidget.hide(self)
if self.orientation in ['left', 'right']:
- self.setWidth(0)
+ self._updateWidth()
else:
- self.setHeight(0)
- GraphicsWidget.hide(self)
+ self._updateHeight()
def wheelEvent(self, ev):
if self.linkedView() is None:
diff --git a/pyqtgraph/graphicsItems/BarGraphItem.py b/papi/pyqtgraph/graphicsItems/BarGraphItem.py
similarity index 81%
rename from pyqtgraph/graphicsItems/BarGraphItem.py
rename to papi/pyqtgraph/graphicsItems/BarGraphItem.py
index 0527e9f1..a1d5d029 100644
--- a/pyqtgraph/graphicsItems/BarGraphItem.py
+++ b/papi/pyqtgraph/graphicsItems/BarGraphItem.py
@@ -1,8 +1,10 @@
-import pyqtgraph as pg
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
from .GraphicsObject import GraphicsObject
+from .. import getConfigOption
+from .. import functions as fn
import numpy as np
+
__all__ = ['BarGraphItem']
class BarGraphItem(GraphicsObject):
@@ -45,23 +47,27 @@ def __init__(self, **opts):
pens=None,
brushes=None,
)
+ self._shape = None
+ self.picture = None
self.setOpts(**opts)
def setOpts(self, **opts):
self.opts.update(opts)
self.picture = None
+ self._shape = None
self.update()
self.informViewBoundsChanged()
def drawPicture(self):
self.picture = QtGui.QPicture()
+ self._shape = QtGui.QPainterPath()
p = QtGui.QPainter(self.picture)
pen = self.opts['pen']
pens = self.opts['pens']
if pen is None and pens is None:
- pen = pg.getConfigOption('foreground')
+ pen = getConfigOption('foreground')
brush = self.opts['brush']
brushes = self.opts['brushes']
@@ -112,14 +118,18 @@ def asarray(x):
raise Exception('must specify either y1 or height')
height = y1 - y0
- p.setPen(pg.mkPen(pen))
- p.setBrush(pg.mkBrush(brush))
+ p.setPen(fn.mkPen(pen))
+ p.setBrush(fn.mkBrush(brush))
for i in range(len(x0)):
if pens is not None:
- p.setPen(pg.mkPen(pens[i]))
+ p.setPen(fn.mkPen(pens[i]))
if brushes is not None:
- p.setBrush(pg.mkBrush(brushes[i]))
+ p.setBrush(fn.mkBrush(brushes[i]))
+ if np.isscalar(x0):
+ x = x0
+ else:
+ x = x0[i]
if np.isscalar(y0):
y = y0
else:
@@ -128,9 +138,15 @@ def asarray(x):
w = width
else:
w = width[i]
-
- p.drawRect(QtCore.QRectF(x0[i], y, w, height[i]))
-
+ if np.isscalar(height):
+ h = height
+ else:
+ h = height[i]
+
+
+ rect = QtCore.QRectF(x, y, w, h)
+ p.drawRect(rect)
+ self._shape.addRect(rect)
p.end()
self.prepareGeometryChange()
@@ -146,4 +162,7 @@ def boundingRect(self):
self.drawPicture()
return QtCore.QRectF(self.picture.boundingRect())
-
\ No newline at end of file
+ def shape(self):
+ if self.picture is None:
+ self.drawPicture()
+ return self._shape
diff --git a/pyqtgraph/graphicsItems/ButtonItem.py b/papi/pyqtgraph/graphicsItems/ButtonItem.py
similarity index 97%
rename from pyqtgraph/graphicsItems/ButtonItem.py
rename to papi/pyqtgraph/graphicsItems/ButtonItem.py
index 741f2666..1c796823 100644
--- a/pyqtgraph/graphicsItems/ButtonItem.py
+++ b/papi/pyqtgraph/graphicsItems/ButtonItem.py
@@ -1,4 +1,4 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
from .GraphicsObject import GraphicsObject
__all__ = ['ButtonItem']
diff --git a/pyqtgraph/graphicsItems/CurvePoint.py b/papi/pyqtgraph/graphicsItems/CurvePoint.py
similarity index 97%
rename from pyqtgraph/graphicsItems/CurvePoint.py
rename to papi/pyqtgraph/graphicsItems/CurvePoint.py
index 668830f7..bb6beebc 100644
--- a/pyqtgraph/graphicsItems/CurvePoint.py
+++ b/papi/pyqtgraph/graphicsItems/CurvePoint.py
@@ -1,7 +1,7 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
from . import ArrowItem
import numpy as np
-from pyqtgraph.Point import Point
+from ..Point import Point
import weakref
from .GraphicsObject import GraphicsObject
@@ -112,6 +112,6 @@ def __init__(self, curve, index=0, pos=None, **opts):
self.arrow = ArrowItem.ArrowItem(**opts)
self.arrow.setParentItem(self)
- def setStyle(**opts):
+ def setStyle(self, **opts):
return self.arrow.setStyle(**opts)
diff --git a/pyqtgraph/graphicsItems/ErrorBarItem.py b/papi/pyqtgraph/graphicsItems/ErrorBarItem.py
similarity index 76%
rename from pyqtgraph/graphicsItems/ErrorBarItem.py
rename to papi/pyqtgraph/graphicsItems/ErrorBarItem.py
index 656b9e2e..986c5140 100644
--- a/pyqtgraph/graphicsItems/ErrorBarItem.py
+++ b/papi/pyqtgraph/graphicsItems/ErrorBarItem.py
@@ -1,21 +1,14 @@
-import pyqtgraph as pg
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
from .GraphicsObject import GraphicsObject
+from .. import getConfigOption
+from .. import functions as fn
__all__ = ['ErrorBarItem']
class ErrorBarItem(GraphicsObject):
def __init__(self, **opts):
"""
- Valid keyword options are:
- x, y, height, width, top, bottom, left, right, beam, pen
-
- x and y must be numpy arrays specifying the coordinates of data points.
- height, width, top, bottom, left, right, and beam may be numpy arrays,
- single values, or None to disable. All values should be positive.
-
- If height is specified, it overrides top and bottom.
- If width is specified, it overrides left and right.
+ All keyword arguments are passed to setData().
"""
GraphicsObject.__init__(self)
self.opts = dict(
@@ -30,14 +23,37 @@ def __init__(self, **opts):
beam=None,
pen=None
)
- self.setOpts(**opts)
+ self.setData(**opts)
+
+ def setData(self, **opts):
+ """
+ Update the data in the item. All arguments are optional.
- def setOpts(self, **opts):
+ Valid keyword options are:
+ x, y, height, width, top, bottom, left, right, beam, pen
+
+ * x and y must be numpy arrays specifying the coordinates of data points.
+ * height, width, top, bottom, left, right, and beam may be numpy arrays,
+ single values, or None to disable. All values should be positive.
+ * top, bottom, left, and right specify the lengths of bars extending
+ in each direction.
+ * If height is specified, it overrides top and bottom.
+ * If width is specified, it overrides left and right.
+ * beam specifies the width of the beam at the end of each bar.
+ * pen may be any single argument accepted by pg.mkPen().
+
+ This method was added in version 0.9.9. For prior versions, use setOpts.
+ """
self.opts.update(opts)
self.path = None
self.update()
+ self.prepareGeometryChange()
self.informViewBoundsChanged()
+ def setOpts(self, **opts):
+ # for backward compatibility
+ self.setData(**opts)
+
def drawPath(self):
p = QtGui.QPainterPath()
@@ -121,8 +137,8 @@ def paint(self, p, *args):
self.drawPath()
pen = self.opts['pen']
if pen is None:
- pen = pg.getConfigOption('foreground')
- p.setPen(pg.mkPen(pen))
+ pen = getConfigOption('foreground')
+ p.setPen(fn.mkPen(pen))
p.drawPath(self.path)
def boundingRect(self):
diff --git a/papi/pyqtgraph/graphicsItems/FillBetweenItem.py b/papi/pyqtgraph/graphicsItems/FillBetweenItem.py
new file mode 100644
index 00000000..15a14f86
--- /dev/null
+++ b/papi/pyqtgraph/graphicsItems/FillBetweenItem.py
@@ -0,0 +1,73 @@
+from ..Qt import QtGui
+from .. import functions as fn
+from .PlotDataItem import PlotDataItem
+from .PlotCurveItem import PlotCurveItem
+
+class FillBetweenItem(QtGui.QGraphicsPathItem):
+ """
+ GraphicsItem filling the space between two PlotDataItems.
+ """
+ def __init__(self, curve1=None, curve2=None, brush=None):
+ QtGui.QGraphicsPathItem.__init__(self)
+ self.curves = None
+ if curve1 is not None and curve2 is not None:
+ self.setCurves(curve1, curve2)
+ elif curve1 is not None or curve2 is not None:
+ raise Exception("Must specify two curves to fill between.")
+
+ if brush is not None:
+ self.setBrush(fn.mkBrush(brush))
+ self.updatePath()
+
+ def setCurves(self, curve1, curve2):
+ """Set the curves to fill between.
+
+ Arguments must be instances of PlotDataItem or PlotCurveItem.
+
+ Added in version 0.9.9
+ """
+
+ if self.curves is not None:
+ for c in self.curves:
+ try:
+ c.sigPlotChanged.disconnect(self.curveChanged)
+ except (TypeError, RuntimeError):
+ pass
+
+ curves = [curve1, curve2]
+ for c in curves:
+ if not isinstance(c, PlotDataItem) and not isinstance(c, PlotCurveItem):
+ raise TypeError("Curves must be PlotDataItem or PlotCurveItem.")
+ self.curves = curves
+ curve1.sigPlotChanged.connect(self.curveChanged)
+ curve2.sigPlotChanged.connect(self.curveChanged)
+ self.setZValue(min(curve1.zValue(), curve2.zValue())-1)
+ self.curveChanged()
+
+ def setBrush(self, *args, **kwds):
+ """Change the fill brush. Acceps the same arguments as pg.mkBrush()"""
+ QtGui.QGraphicsPathItem.setBrush(self, fn.mkBrush(*args, **kwds))
+
+ def curveChanged(self):
+ self.updatePath()
+
+ def updatePath(self):
+ if self.curves is None:
+ self.setPath(QtGui.QPainterPath())
+ return
+ paths = []
+ for c in self.curves:
+ if isinstance(c, PlotDataItem):
+ paths.append(c.curve.getPath())
+ elif isinstance(c, PlotCurveItem):
+ paths.append(c.getPath())
+
+ path = QtGui.QPainterPath()
+ p1 = paths[0].toSubpathPolygons()
+ p2 = paths[1].toReversed().toSubpathPolygons()
+ if len(p1) == 0 or len(p2) == 0:
+ self.setPath(QtGui.QPainterPath())
+ return
+
+ path.addPolygon(p1[0] + p2[0])
+ self.setPath(path)
diff --git a/pyqtgraph/graphicsItems/GradientEditorItem.py b/papi/pyqtgraph/graphicsItems/GradientEditorItem.py
similarity index 72%
rename from pyqtgraph/graphicsItems/GradientEditorItem.py
rename to papi/pyqtgraph/graphicsItems/GradientEditorItem.py
index 955106d8..a151798a 100644
--- a/pyqtgraph/graphicsItems/GradientEditorItem.py
+++ b/papi/pyqtgraph/graphicsItems/GradientEditorItem.py
@@ -1,11 +1,12 @@
-from pyqtgraph.Qt import QtGui, QtCore
-from pyqtgraph.python2_3 import sortList
-import pyqtgraph.functions as fn
+from ..Qt import QtGui, QtCore
+from ..python2_3 import sortList
+from .. import functions as fn
from .GraphicsObject import GraphicsObject
from .GraphicsWidget import GraphicsWidget
+from ..widgets.SpinBox import SpinBox
import weakref
-from pyqtgraph.pgcollections import OrderedDict
-from pyqtgraph.colormap import ColorMap
+from ..pgcollections import OrderedDict
+from ..colormap import ColorMap
import numpy as np
@@ -35,14 +36,14 @@ class TickSliderItem(GraphicsWidget):
def __init__(self, orientation='bottom', allowAdd=True, **kargs):
"""
- ============= =================================================================================
- **Arguments**
- orientation Set the orientation of the gradient. Options are: 'left', 'right'
- 'top', and 'bottom'.
- allowAdd Specifies whether ticks can be added to the item by the user.
- tickPen Default is white. Specifies the color of the outline of the ticks.
- Can be any of the valid arguments for :func:`mkPen `
- ============= =================================================================================
+ ============== =================================================================================
+ **Arguments:**
+ orientation Set the orientation of the gradient. Options are: 'left', 'right'
+ 'top', and 'bottom'.
+ allowAdd Specifies whether ticks can be added to the item by the user.
+ tickPen Default is white. Specifies the color of the outline of the ticks.
+ Can be any of the valid arguments for :func:`mkPen `
+ ============== =================================================================================
"""
## public
GraphicsWidget.__init__(self)
@@ -103,13 +104,13 @@ def setOrientation(self, orientation):
## public
"""Set the orientation of the TickSliderItem.
- ============= ===================================================================
- **Arguments**
- orientation Options are: 'left', 'right', 'top', 'bottom'
- The orientation option specifies which side of the slider the
- ticks are on, as well as whether the slider is vertical ('right'
- and 'left') or horizontal ('top' and 'bottom').
- ============= ===================================================================
+ ============== ===================================================================
+ **Arguments:**
+ orientation Options are: 'left', 'right', 'top', 'bottom'
+ The orientation option specifies which side of the slider the
+ ticks are on, as well as whether the slider is vertical ('right'
+ and 'left') or horizontal ('top' and 'bottom').
+ ============== ===================================================================
"""
self.orientation = orientation
self.setMaxDim()
@@ -136,13 +137,13 @@ def addTick(self, x, color=None, movable=True):
"""
Add a tick to the item.
- ============= ==================================================================
- **Arguments**
- x Position where tick should be added.
- color Color of added tick. If color is not specified, the color will be
- white.
- movable Specifies whether the tick is movable with the mouse.
- ============= ==================================================================
+ ============== ==================================================================
+ **Arguments:**
+ x Position where tick should be added.
+ color Color of added tick. If color is not specified, the color will be
+ white.
+ movable Specifies whether the tick is movable with the mouse.
+ ============== ==================================================================
"""
if color is None:
@@ -265,14 +266,14 @@ def showMenu(self, ev):
def setTickColor(self, tick, color):
"""Set the color of the specified tick.
- ============= ==================================================================
- **Arguments**
- tick Can be either an integer corresponding to the index of the tick
- or a Tick object. Ex: if you had a slider with 3 ticks and you
- wanted to change the middle tick, the index would be 1.
- color The color to make the tick. Can be any argument that is valid for
- :func:`mkBrush `
- ============= ==================================================================
+ ============== ==================================================================
+ **Arguments:**
+ tick Can be either an integer corresponding to the index of the tick
+ or a Tick object. Ex: if you had a slider with 3 ticks and you
+ wanted to change the middle tick, the index would be 1.
+ color The color to make the tick. Can be any argument that is valid for
+ :func:`mkBrush `
+ ============== ==================================================================
"""
tick = self.getTick(tick)
tick.color = color
@@ -284,14 +285,14 @@ def setTickValue(self, tick, val):
"""
Set the position (along the slider) of the tick.
- ============= ==================================================================
- **Arguments**
- tick Can be either an integer corresponding to the index of the tick
- or a Tick object. Ex: if you had a slider with 3 ticks and you
- wanted to change the middle tick, the index would be 1.
- val The desired position of the tick. If val is < 0, position will be
- set to 0. If val is > 1, position will be set to 1.
- ============= ==================================================================
+ ============== ==================================================================
+ **Arguments:**
+ tick Can be either an integer corresponding to the index of the tick
+ or a Tick object. Ex: if you had a slider with 3 ticks and you
+ wanted to change the middle tick, the index would be 1.
+ val The desired position of the tick. If val is < 0, position will be
+ set to 0. If val is > 1, position will be set to 1.
+ ============== ==================================================================
"""
tick = self.getTick(tick)
val = min(max(0.0, val), 1.0)
@@ -300,17 +301,18 @@ def setTickValue(self, tick, val):
pos.setX(x)
tick.setPos(pos)
self.ticks[tick] = val
+ self.updateGradient()
def tickValue(self, tick):
## public
"""Return the value (from 0.0 to 1.0) of the specified tick.
- ============= ==================================================================
- **Arguments**
- tick Can be either an integer corresponding to the index of the tick
- or a Tick object. Ex: if you had a slider with 3 ticks and you
- wanted the value of the middle tick, the index would be 1.
- ============= ==================================================================
+ ============== ==================================================================
+ **Arguments:**
+ tick Can be either an integer corresponding to the index of the tick
+ or a Tick object. Ex: if you had a slider with 3 ticks and you
+ wanted the value of the middle tick, the index would be 1.
+ ============== ==================================================================
"""
tick = self.getTick(tick)
return self.ticks[tick]
@@ -319,11 +321,11 @@ def getTick(self, tick):
## public
"""Return the Tick object at the specified index.
- ============= ==================================================================
- **Arguments**
- tick An integer corresponding to the index of the desired tick. If the
- argument is not an integer it will be returned unchanged.
- ============= ==================================================================
+ ============== ==================================================================
+ **Arguments:**
+ tick An integer corresponding to the index of the desired tick. If the
+ argument is not an integer it will be returned unchanged.
+ ============== ==================================================================
"""
if type(tick) is int:
tick = self.listTicks()[tick][0]
@@ -349,7 +351,7 @@ class GradientEditorItem(TickSliderItem):
with a GradientEditorItem that can be added to a GUI.
================================ ===========================================================
- **Signals**
+ **Signals:**
sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal
is emitted in real time while ticks are being dragged or
colors are being changed.
@@ -366,14 +368,14 @@ def __init__(self, *args, **kargs):
Create a new GradientEditorItem.
All arguments are passed to :func:`TickSliderItem.__init__ `
- ============= =================================================================================
- **Arguments**
- orientation Set the orientation of the gradient. Options are: 'left', 'right'
- 'top', and 'bottom'.
- allowAdd Default is True. Specifies whether ticks can be added to the item.
- tickPen Default is white. Specifies the color of the outline of the ticks.
- Can be any of the valid arguments for :func:`mkPen `
- ============= =================================================================================
+ =============== =================================================================================
+ **Arguments:**
+ orientation Set the orientation of the gradient. Options are: 'left', 'right'
+ 'top', and 'bottom'.
+ allowAdd Default is True. Specifies whether ticks can be added to the item.
+ tickPen Default is white. Specifies the color of the outline of the ticks.
+ Can be any of the valid arguments for :func:`mkPen `
+ =============== =================================================================================
"""
self.currentTick = None
self.currentTickColor = None
@@ -445,13 +447,13 @@ def setOrientation(self, orientation):
"""
Set the orientation of the GradientEditorItem.
- ============= ===================================================================
- **Arguments**
- orientation Options are: 'left', 'right', 'top', 'bottom'
- The orientation option specifies which side of the gradient the
- ticks are on, as well as whether the gradient is vertical ('right'
- and 'left') or horizontal ('top' and 'bottom').
- ============= ===================================================================
+ ============== ===================================================================
+ **Arguments:**
+ orientation Options are: 'left', 'right', 'top', 'bottom'
+ The orientation option specifies which side of the gradient the
+ ticks are on, as well as whether the gradient is vertical ('right'
+ and 'left') or horizontal ('top' and 'bottom').
+ ============== ===================================================================
"""
TickSliderItem.setOrientation(self, orientation)
self.translate(0, self.rectSize)
@@ -537,23 +539,22 @@ def currentColorAccepted(self):
def tickClicked(self, tick, ev):
#private
if ev.button() == QtCore.Qt.LeftButton:
- if not tick.colorChangeAllowed:
- return
- self.currentTick = tick
- self.currentTickColor = tick.color
- self.colorDialog.setCurrentColor(tick.color)
- self.colorDialog.open()
- #color = QtGui.QColorDialog.getColor(tick.color, self, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
- #if color.isValid():
- #self.setTickColor(tick, color)
- #self.updateGradient()
+ self.raiseColorDialog(tick)
elif ev.button() == QtCore.Qt.RightButton:
- if not tick.removeAllowed:
- return
- if len(self.ticks) > 2:
- self.removeTick(tick)
- self.updateGradient()
-
+ self.raiseTickContextMenu(tick, ev)
+
+ def raiseColorDialog(self, tick):
+ if not tick.colorChangeAllowed:
+ return
+ self.currentTick = tick
+ self.currentTickColor = tick.color
+ self.colorDialog.setCurrentColor(tick.color)
+ self.colorDialog.open()
+
+ def raiseTickContextMenu(self, tick, ev):
+ self.tickMenu = TickMenu(tick, self)
+ self.tickMenu.popup(ev.screenPos().toQPoint())
+
def tickMoved(self, tick, pos):
#private
TickSliderItem.tickMoved(self, tick, pos)
@@ -588,11 +589,11 @@ def getColor(self, x, toQColor=True):
"""
Return a color for a given value.
- ============= ==================================================================
- **Arguments**
- x Value (position on gradient) of requested color.
- toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple.
- ============= ==================================================================
+ ============== ==================================================================
+ **Arguments:**
+ x Value (position on gradient) of requested color.
+ toQColor If true, returns a QColor object, else returns a (r,g,b,a) tuple.
+ ============== ==================================================================
"""
ticks = self.listTicks()
if x <= ticks[0][1]:
@@ -648,12 +649,12 @@ def getLookupTable(self, nPts, alpha=None):
"""
Return an RGB(A) lookup table (ndarray).
- ============= ============================================================================
- **Arguments**
- nPts The number of points in the returned lookup table.
- alpha True, False, or None - Specifies whether or not alpha values are included
- in the table.If alpha is None, alpha will be automatically determined.
- ============= ============================================================================
+ ============== ============================================================================
+ **Arguments:**
+ nPts The number of points in the returned lookup table.
+ alpha True, False, or None - Specifies whether or not alpha values are included
+ in the table.If alpha is None, alpha will be automatically determined.
+ ============== ============================================================================
"""
if alpha is None:
alpha = self.usesAlpha()
@@ -702,13 +703,13 @@ def addTick(self, x, color=None, movable=True, finish=True):
"""
Add a tick to the gradient. Return the tick.
- ============= ==================================================================
- **Arguments**
- x Position where tick should be added.
- color Color of added tick. If color is not specified, the color will be
- the color of the gradient at the specified position.
- movable Specifies whether the tick is movable with the mouse.
- ============= ==================================================================
+ ============== ==================================================================
+ **Arguments:**
+ x Position where tick should be added.
+ color Color of added tick. If color is not specified, the color will be
+ the color of the gradient at the specified position.
+ movable Specifies whether the tick is movable with the mouse.
+ ============== ==================================================================
"""
@@ -726,6 +727,7 @@ def addTick(self, x, color=None, movable=True, finish=True):
def removeTick(self, tick, finish=True):
TickSliderItem.removeTick(self, tick)
if finish:
+ self.updateGradient()
self.sigGradientChangeFinished.emit(self)
@@ -748,16 +750,16 @@ def restoreState(self, state):
"""
Restore the gradient specified in state.
- ============= ====================================================================
- **Arguments**
- state A dictionary with same structure as those returned by
- :func:`saveState `
+ ============== ====================================================================
+ **Arguments:**
+ state A dictionary with same structure as those returned by
+ :func:`saveState `
- Keys must include:
+ Keys must include:
- - 'mode': hsv or rgb
- - 'ticks': a list of tuples (pos, (r,g,b,a))
- ============= ====================================================================
+ - 'mode': hsv or rgb
+ - 'ticks': a list of tuples (pos, (r,g,b,a))
+ ============== ====================================================================
"""
## public
self.setColorMode(state['mode'])
@@ -867,44 +869,59 @@ def hoverEvent(self, ev):
self.currentPen = self.pen
self.update()
- #def mouseMoveEvent(self, ev):
- ##print self, "move", ev.scenePos()
- #if not self.movable:
- #return
- #if not ev.buttons() & QtCore.Qt.LeftButton:
- #return
-
+
+class TickMenu(QtGui.QMenu):
+
+ def __init__(self, tick, sliderItem):
+ QtGui.QMenu.__init__(self)
+
+ self.tick = weakref.ref(tick)
+ self.sliderItem = weakref.ref(sliderItem)
+
+ self.removeAct = self.addAction("Remove Tick", lambda: self.sliderItem().removeTick(tick))
+ if (not self.tick().removeAllowed) or len(self.sliderItem().ticks) < 3:
+ self.removeAct.setEnabled(False)
- #newPos = ev.scenePos() + self.mouseOffset
- #newPos.setY(self.pos().y())
- ##newPos.setX(min(max(newPos.x(), 0), 100))
- #self.setPos(newPos)
- #self.view().tickMoved(self, newPos)
- #self.movedSincePress = True
- ##self.emit(QtCore.SIGNAL('tickChanged'), self)
- #ev.accept()
+ positionMenu = self.addMenu("Set Position")
+ w = QtGui.QWidget()
+ l = QtGui.QGridLayout()
+ w.setLayout(l)
+
+ value = sliderItem.tickValue(tick)
+ self.fracPosSpin = SpinBox()
+ self.fracPosSpin.setOpts(value=value, bounds=(0.0, 1.0), step=0.01, decimals=2)
+ #self.dataPosSpin = SpinBox(value=dataVal)
+ #self.dataPosSpin.setOpts(decimals=3, siPrefix=True)
+
+ l.addWidget(QtGui.QLabel("Position:"), 0,0)
+ l.addWidget(self.fracPosSpin, 0, 1)
+ #l.addWidget(QtGui.QLabel("Position (data units):"), 1, 0)
+ #l.addWidget(self.dataPosSpin, 1,1)
+
+ #if self.sliderItem().dataParent is None:
+ # self.dataPosSpin.setEnabled(False)
+
+ a = QtGui.QWidgetAction(self)
+ a.setDefaultWidget(w)
+ positionMenu.addAction(a)
+
+ self.fracPosSpin.sigValueChanging.connect(self.fractionalValueChanged)
+ #self.dataPosSpin.valueChanged.connect(self.dataValueChanged)
+
+ colorAct = self.addAction("Set Color", lambda: self.sliderItem().raiseColorDialog(self.tick()))
+ if not self.tick().colorChangeAllowed:
+ colorAct.setEnabled(False)
- #def mousePressEvent(self, ev):
- #self.movedSincePress = False
- #if ev.button() == QtCore.Qt.LeftButton:
- #ev.accept()
- #self.mouseOffset = self.pos() - ev.scenePos()
- #self.pressPos = ev.scenePos()
- #elif ev.button() == QtCore.Qt.RightButton:
- #ev.accept()
- ##if self.endTick:
- ##return
- ##self.view.tickChanged(self, delete=True)
+ def fractionalValueChanged(self, x):
+ self.sliderItem().setTickValue(self.tick(), self.fracPosSpin.value())
+ #if self.sliderItem().dataParent is not None:
+ # self.dataPosSpin.blockSignals(True)
+ # self.dataPosSpin.setValue(self.sliderItem().tickDataValue(self.tick()))
+ # self.dataPosSpin.blockSignals(False)
- #def mouseReleaseEvent(self, ev):
- ##print self, "release", ev.scenePos()
- #if not self.movedSincePress:
- #self.view().tickClicked(self, ev)
-
- ##if ev.button() == QtCore.Qt.LeftButton and ev.scenePos() == self.pressPos:
- ##color = QtGui.QColorDialog.getColor(self.color, None, "Select Color", QtGui.QColorDialog.ShowAlphaChannel)
- ##if color.isValid():
- ##self.color = color
- ##self.setBrush(QtGui.QBrush(QtGui.QColor(self.color)))
- ###self.emit(QtCore.SIGNAL('tickChanged'), self)
- ##self.view.tickChanged(self)
+ #def dataValueChanged(self, val):
+ # self.sliderItem().setTickValue(self.tick(), val, dataUnits=True)
+ # self.fracPosSpin.blockSignals(True)
+ # self.fracPosSpin.setValue(self.sliderItem().tickValue(self.tick()))
+ # self.fracPosSpin.blockSignals(False)
+
diff --git a/pyqtgraph/graphicsItems/GradientLegend.py b/papi/pyqtgraph/graphicsItems/GradientLegend.py
similarity index 98%
rename from pyqtgraph/graphicsItems/GradientLegend.py
rename to papi/pyqtgraph/graphicsItems/GradientLegend.py
index 4528b7ed..28c2cd63 100644
--- a/pyqtgraph/graphicsItems/GradientLegend.py
+++ b/papi/pyqtgraph/graphicsItems/GradientLegend.py
@@ -1,6 +1,6 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
from .UIGraphicsItem import *
-import pyqtgraph.functions as fn
+from .. import functions as fn
__all__ = ['GradientLegend']
diff --git a/papi/pyqtgraph/graphicsItems/GraphItem.py b/papi/pyqtgraph/graphicsItems/GraphItem.py
new file mode 100644
index 00000000..c80138fb
--- /dev/null
+++ b/papi/pyqtgraph/graphicsItems/GraphItem.py
@@ -0,0 +1,147 @@
+from .. import functions as fn
+from .GraphicsObject import GraphicsObject
+from .ScatterPlotItem import ScatterPlotItem
+from ..Qt import QtGui, QtCore
+import numpy as np
+from .. import getConfigOption
+
+__all__ = ['GraphItem']
+
+
+class GraphItem(GraphicsObject):
+ """A GraphItem displays graph information as
+ a set of nodes connected by lines (as in 'graph theory', not 'graphics').
+ Useful for drawing networks, trees, etc.
+ """
+
+ def __init__(self, **kwds):
+ GraphicsObject.__init__(self)
+ self.scatter = ScatterPlotItem()
+ self.scatter.setParentItem(self)
+ self.adjacency = None
+ self.pos = None
+ self.picture = None
+ self.pen = 'default'
+ self.setData(**kwds)
+
+ def setData(self, **kwds):
+ """
+ Change the data displayed by the graph.
+
+ ============== =======================================================================
+ **Arguments:**
+ pos (N,2) array of the positions of each node in the graph.
+ adj (M,2) array of connection data. Each row contains indexes
+ of two nodes that are connected.
+ pen The pen to use when drawing lines between connected
+ nodes. May be one of:
+
+ * QPen
+ * a single argument to pass to pg.mkPen
+ * a record array of length M
+ with fields (red, green, blue, alpha, width). Note
+ that using this option may have a significant performance
+ cost.
+ * None (to disable connection drawing)
+ * 'default' to use the default foreground color.
+
+ symbolPen The pen(s) used for drawing nodes.
+ symbolBrush The brush(es) used for drawing nodes.
+ ``**opts`` All other keyword arguments are given to
+ :func:`ScatterPlotItem.setData() `
+ to affect the appearance of nodes (symbol, size, brush,
+ etc.)
+ ============== =======================================================================
+ """
+ if 'adj' in kwds:
+ self.adjacency = kwds.pop('adj')
+ if self.adjacency.dtype.kind not in 'iu':
+ raise Exception("adjacency array must have int or unsigned type.")
+ self._update()
+ if 'pos' in kwds:
+ self.pos = kwds['pos']
+ self._update()
+ if 'pen' in kwds:
+ self.setPen(kwds.pop('pen'))
+ self._update()
+
+ if 'symbolPen' in kwds:
+ kwds['pen'] = kwds.pop('symbolPen')
+ if 'symbolBrush' in kwds:
+ kwds['brush'] = kwds.pop('symbolBrush')
+ self.scatter.setData(**kwds)
+ self.informViewBoundsChanged()
+
+ def _update(self):
+ self.picture = None
+ self.prepareGeometryChange()
+ self.update()
+
+ def setPen(self, *args, **kwargs):
+ """
+ Set the pen used to draw graph lines.
+ May be:
+
+ * None to disable line drawing
+ * Record array with fields (red, green, blue, alpha, width)
+ * Any set of arguments and keyword arguments accepted by
+ :func:`mkPen `.
+ * 'default' to use the default foreground color.
+ """
+ if len(args) == 1 and len(kwargs) == 0:
+ self.pen = args[0]
+ else:
+ self.pen = fn.mkPen(*args, **kwargs)
+ self.picture = None
+ self.update()
+
+ def generatePicture(self):
+ self.picture = QtGui.QPicture()
+ if self.pen is None or self.pos is None or self.adjacency is None:
+ return
+
+ p = QtGui.QPainter(self.picture)
+ try:
+ pts = self.pos[self.adjacency]
+ pen = self.pen
+ if isinstance(pen, np.ndarray):
+ lastPen = None
+ for i in range(pts.shape[0]):
+ pen = self.pen[i]
+ if np.any(pen != lastPen):
+ lastPen = pen
+ if pen.dtype.fields is None:
+ p.setPen(fn.mkPen(color=(pen[0], pen[1], pen[2], pen[3]), width=1))
+ else:
+ p.setPen(fn.mkPen(color=(pen['red'], pen['green'], pen['blue'], pen['alpha']), width=pen['width']))
+ p.drawLine(QtCore.QPointF(*pts[i][0]), QtCore.QPointF(*pts[i][1]))
+ else:
+ if pen == 'default':
+ pen = getConfigOption('foreground')
+ p.setPen(fn.mkPen(pen))
+ pts = pts.reshape((pts.shape[0]*pts.shape[1], pts.shape[2]))
+ path = fn.arrayToQPath(x=pts[:,0], y=pts[:,1], connect='pairs')
+ p.drawPath(path)
+ finally:
+ p.end()
+
+ def paint(self, p, *args):
+ if self.picture == None:
+ self.generatePicture()
+ if getConfigOption('antialias') is True:
+ p.setRenderHint(p.Antialiasing)
+ self.picture.play(p)
+
+ def boundingRect(self):
+ return self.scatter.boundingRect()
+
+ def dataBounds(self, *args, **kwds):
+ return self.scatter.dataBounds(*args, **kwds)
+
+ def pixelPadding(self):
+ return self.scatter.pixelPadding()
+
+
+
+
+
diff --git a/pyqtgraph/graphicsItems/GraphicsItem.py b/papi/pyqtgraph/graphicsItems/GraphicsItem.py
similarity index 92%
rename from pyqtgraph/graphicsItems/GraphicsItem.py
rename to papi/pyqtgraph/graphicsItems/GraphicsItem.py
index a129436e..2ca35193 100644
--- a/pyqtgraph/graphicsItems/GraphicsItem.py
+++ b/papi/pyqtgraph/graphicsItems/GraphicsItem.py
@@ -1,31 +1,11 @@
-from pyqtgraph.Qt import QtGui, QtCore
-from pyqtgraph.GraphicsScene import GraphicsScene
-from pyqtgraph.Point import Point
-import pyqtgraph.functions as fn
+from ..Qt import QtGui, QtCore, isQObjectAlive
+from ..GraphicsScene import GraphicsScene
+from ..Point import Point
+from .. import functions as fn
import weakref
-from pyqtgraph.pgcollections import OrderedDict
-import operator, sys
+import operator
+from ..util.lru_cache import LRUCache
-class FiniteCache(OrderedDict):
- """Caches a finite number of objects, removing
- least-frequently used items."""
- def __init__(self, length):
- self._length = length
- OrderedDict.__init__(self)
-
- def __setitem__(self, item, val):
- self.pop(item, None) # make sure item is added to end
- OrderedDict.__setitem__(self, item, val)
- while len(self) > self._length:
- del self[list(self.keys())[0]]
-
- def __getitem__(self, item):
- val = OrderedDict.__getitem__(self, item)
- del self[item]
- self[item] = val ## promote this key
- return val
-
-
class GraphicsItem(object):
"""
@@ -38,7 +18,7 @@ class GraphicsItem(object):
The GraphicsView system places a lot of emphasis on the notion that the graphics within the scene should be device independent--you should be able to take the same graphics and display them on screens of different resolutions, printers, export to SVG, etc. This is nice in principle, but causes me a lot of headache in practice. It means that I have to circumvent all the device-independent expectations any time I want to operate in pixel coordinates rather than arbitrary scene coordinates. A lot of the code in GraphicsItem is devoted to this task--keeping track of view widgets and device transforms, computing the size and shape of a pixel in local item coordinates, etc. Note that in item coordinates, a pixel does not have to be square or even rectangular, so just asking how to increase a bounding rect by 2px can be a rather complex task.
"""
- _pixelVectorGlobalCache = FiniteCache(100)
+ _pixelVectorGlobalCache = LRUCache(100, 70)
def __init__(self, register=True):
if not hasattr(self, '_qtBaseClass'):
@@ -62,8 +42,11 @@ def __init__(self, register=True):
def getViewWidget(self):
"""
- Return the view widget for this item. If the scene has multiple views, only the first view is returned.
- The return value is cached; clear the cached value with forgetViewWidget()
+ Return the view widget for this item.
+
+ If the scene has multiple views, only the first view is returned.
+ The return value is cached; clear the cached value with forgetViewWidget().
+ If the view has been deleted by Qt, return None.
"""
if self._viewWidget is None:
scene = self.scene()
@@ -73,7 +56,12 @@ def getViewWidget(self):
if len(views) < 1:
return None
self._viewWidget = weakref.ref(self.scene().views()[0])
- return self._viewWidget()
+
+ v = self._viewWidget()
+ if v is not None and not isQObjectAlive(v):
+ return None
+
+ return v
def forgetViewWidget(self):
self._viewWidget = None
@@ -114,7 +102,7 @@ def deviceTransform(self, viewportTransform=None):
Extends deviceTransform to automatically determine the viewportTransform.
"""
if self._exportOpts is not False and 'painter' in self._exportOpts: ## currently exporting; device transform may be different.
- return self._exportOpts['painter'].deviceTransform()
+ return self._exportOpts['painter'].deviceTransform() * self.sceneTransform()
if viewportTransform is None:
view = self.getViewWidget()
@@ -330,6 +318,8 @@ def mapFromDevice(self, obj):
vt = self.deviceTransform()
if vt is None:
return None
+ if isinstance(obj, QtCore.QPoint):
+ obj = QtCore.QPointF(obj)
vt = fn.invertQTransform(vt)
return vt.map(obj)
@@ -479,24 +469,29 @@ def _updateView(self):
## disconnect from previous view
if oldView is not None:
- #print "disconnect:", self, oldView
- try:
- oldView.sigRangeChanged.disconnect(self.viewRangeChanged)
- except TypeError:
- pass
-
- try:
- oldView.sigTransformChanged.disconnect(self.viewTransformChanged)
- except TypeError:
- pass
+ for signal, slot in [('sigRangeChanged', self.viewRangeChanged),
+ ('sigDeviceRangeChanged', self.viewRangeChanged),
+ ('sigTransformChanged', self.viewTransformChanged),
+ ('sigDeviceTransformChanged', self.viewTransformChanged)]:
+ try:
+ getattr(oldView, signal).disconnect(slot)
+ except (TypeError, AttributeError, RuntimeError):
+ # TypeError and RuntimeError are from pyqt and pyside, respectively
+ pass
self._connectedView = None
## connect to new view
if view is not None:
#print "connect:", self, view
- view.sigRangeChanged.connect(self.viewRangeChanged)
- view.sigTransformChanged.connect(self.viewTransformChanged)
+ if hasattr(view, 'sigDeviceRangeChanged'):
+ # connect signals from GraphicsView
+ view.sigDeviceRangeChanged.connect(self.viewRangeChanged)
+ view.sigDeviceTransformChanged.connect(self.viewTransformChanged)
+ else:
+ # connect signals from ViewBox
+ view.sigRangeChanged.connect(self.viewRangeChanged)
+ view.sigTransformChanged.connect(self.viewTransformChanged)
self._connectedView = weakref.ref(view)
self.viewRangeChanged()
self.viewTransformChanged()
@@ -585,3 +580,6 @@ def setExportMode(self, export, opts=None):
#def update(self):
#self._qtBaseClass.update(self)
#print "Update:", self
+
+ def getContextMenus(self, event):
+ return [self.getMenu()] if hasattr(self, "getMenu") else []
diff --git a/pyqtgraph/graphicsItems/GraphicsLayout.py b/papi/pyqtgraph/graphicsItems/GraphicsLayout.py
similarity index 89%
rename from pyqtgraph/graphicsItems/GraphicsLayout.py
rename to papi/pyqtgraph/graphicsItems/GraphicsLayout.py
index 9d48e627..6ec38fb5 100644
--- a/pyqtgraph/graphicsItems/GraphicsLayout.py
+++ b/papi/pyqtgraph/graphicsItems/GraphicsLayout.py
@@ -1,5 +1,5 @@
-from pyqtgraph.Qt import QtGui, QtCore
-import pyqtgraph.functions as fn
+from ..Qt import QtGui, QtCore
+from .. import functions as fn
from .GraphicsWidget import GraphicsWidget
## Must be imported at the end to avoid cyclic-dependency hell:
from .ViewBox import ViewBox
@@ -31,6 +31,15 @@ def __init__(self, parent=None, border=None):
#ret = GraphicsWidget.resizeEvent(self, ev)
#print self.pos(), self.mapToDevice(self.rect().topLeft())
#return ret
+
+ def setBorder(self, *args, **kwds):
+ """
+ Set the pen used to draw border between cells.
+
+ See :func:`mkPen ` for arguments.
+ """
+ self.border = fn.mkPen(*args, **kwds)
+ self.update()
def nextRow(self):
"""Advance to next row for automatic item placement"""
@@ -151,4 +160,12 @@ def clear(self):
for i in list(self.items.keys()):
self.removeItem(i)
+ def setContentsMargins(self, *args):
+ # Wrap calls to layout. This should happen automatically, but there
+ # seems to be a Qt bug:
+ # http://stackoverflow.com/questions/27092164/margins-in-pyqtgraphs-graphicslayout
+ self.layout.setContentsMargins(*args)
+ def setSpacing(self, *args):
+ self.layout.setSpacing(*args)
+
\ No newline at end of file
diff --git a/pyqtgraph/graphicsItems/GraphicsObject.py b/papi/pyqtgraph/graphicsItems/GraphicsObject.py
similarity index 70%
rename from pyqtgraph/graphicsItems/GraphicsObject.py
rename to papi/pyqtgraph/graphicsItems/GraphicsObject.py
index d8f55d27..015a78c6 100644
--- a/pyqtgraph/graphicsItems/GraphicsObject.py
+++ b/papi/pyqtgraph/graphicsItems/GraphicsObject.py
@@ -1,4 +1,4 @@
-from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
+from ..Qt import QtGui, QtCore, USE_PYSIDE
if not USE_PYSIDE:
import sip
from .GraphicsItem import GraphicsItem
@@ -21,8 +21,15 @@ def itemChange(self, change, value):
ret = QtGui.QGraphicsObject.itemChange(self, change, value)
if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
self.parentChanged()
- if self.__inform_view_on_changes and change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
- self.informViewBoundsChanged()
+ try:
+ inform_view_on_change = self.__inform_view_on_changes
+ except AttributeError:
+ # It's possible that the attribute was already collected when the itemChange happened
+ # (if it was triggered during the gc of the object).
+ pass
+ else:
+ if inform_view_on_change and change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
+ self.informViewBoundsChanged()
## workaround for pyqt bug:
## http://www.riverbankcomputing.com/pipermail/pyqt/2012-August/031818.html
diff --git a/pyqtgraph/graphicsItems/GraphicsWidget.py b/papi/pyqtgraph/graphicsItems/GraphicsWidget.py
similarity index 95%
rename from pyqtgraph/graphicsItems/GraphicsWidget.py
rename to papi/pyqtgraph/graphicsItems/GraphicsWidget.py
index 7650b125..c379ce8e 100644
--- a/pyqtgraph/graphicsItems/GraphicsWidget.py
+++ b/papi/pyqtgraph/graphicsItems/GraphicsWidget.py
@@ -1,5 +1,5 @@
-from pyqtgraph.Qt import QtGui, QtCore
-from pyqtgraph.GraphicsScene import GraphicsScene
+from ..Qt import QtGui, QtCore
+from ..GraphicsScene import GraphicsScene
from .GraphicsItem import GraphicsItem
__all__ = ['GraphicsWidget']
diff --git a/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py b/papi/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py
similarity index 100%
rename from pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py
rename to papi/pyqtgraph/graphicsItems/GraphicsWidgetAnchor.py
diff --git a/pyqtgraph/graphicsItems/GridItem.py b/papi/pyqtgraph/graphicsItems/GridItem.py
similarity index 97%
rename from pyqtgraph/graphicsItems/GridItem.py
rename to papi/pyqtgraph/graphicsItems/GridItem.py
index 29b0aa2c..87f90a62 100644
--- a/pyqtgraph/graphicsItems/GridItem.py
+++ b/papi/pyqtgraph/graphicsItems/GridItem.py
@@ -1,8 +1,8 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
from .UIGraphicsItem import *
import numpy as np
-from pyqtgraph.Point import Point
-import pyqtgraph.functions as fn
+from ..Point import Point
+from .. import functions as fn
__all__ = ['GridItem']
class GridItem(UIGraphicsItem):
diff --git a/pyqtgraph/graphicsItems/HistogramLUTItem.py b/papi/pyqtgraph/graphicsItems/HistogramLUTItem.py
similarity index 88%
rename from pyqtgraph/graphicsItems/HistogramLUTItem.py
rename to papi/pyqtgraph/graphicsItems/HistogramLUTItem.py
index 5a3b63d6..89ebef3e 100644
--- a/pyqtgraph/graphicsItems/HistogramLUTItem.py
+++ b/papi/pyqtgraph/graphicsItems/HistogramLUTItem.py
@@ -3,8 +3,8 @@
"""
-from pyqtgraph.Qt import QtGui, QtCore
-import pyqtgraph.functions as fn
+from ..Qt import QtGui, QtCore
+from .. import functions as fn
from .GraphicsWidget import GraphicsWidget
from .ViewBox import *
from .GradientEditorItem import *
@@ -12,11 +12,12 @@
from .PlotDataItem import *
from .AxisItem import *
from .GridItem import *
-from pyqtgraph.Point import Point
-import pyqtgraph.functions as fn
+from ..Point import Point
+from .. import functions as fn
import numpy as np
-import pyqtgraph.debug as debug
+from .. import debug as debug
+import weakref
__all__ = ['HistogramLUTItem']
@@ -42,13 +43,13 @@ def __init__(self, image=None, fillHistogram=True):
"""
GraphicsWidget.__init__(self)
self.lut = None
- self.imageItem = None
+ self.imageItem = lambda: None # fake a dead weakref
self.layout = QtGui.QGraphicsGridLayout()
self.setLayout(self.layout)
self.layout.setContentsMargins(1,1,1,1)
self.layout.setSpacing(0)
- self.vb = ViewBox()
+ self.vb = ViewBox(parent=self)
self.vb.setMaximumWidth(152)
self.vb.setMinimumWidth(45)
self.vb.setMouseEnabled(x=False, y=True)
@@ -58,7 +59,7 @@ def __init__(self, image=None, fillHistogram=True):
self.region = LinearRegionItem([0, 1], LinearRegionItem.Horizontal)
self.region.setZValue(1000)
self.vb.addItem(self.region)
- self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, showValues=False)
+ self.axis = AxisItem('left', linkView=self.vb, maxTickLength=-10, parent=self)
self.layout.addItem(self.axis, 0, 0)
self.layout.addItem(self.vb, 0, 1)
self.layout.addItem(self.gradient, 0, 2)
@@ -138,7 +139,7 @@ def autoHistogramRange(self):
#self.region.setBounds([vr.top(), vr.bottom()])
def setImageItem(self, img):
- self.imageItem = img
+ self.imageItem = weakref.ref(img)
img.sigImageChanged.connect(self.imageChanged)
img.setLookupTable(self.getLookupTable) ## send function pointer, not the result
#self.gradientChanged()
@@ -150,11 +151,11 @@ def viewRangeChanged(self):
self.update()
def gradientChanged(self):
- if self.imageItem is not None:
+ if self.imageItem() is not None:
if self.gradient.isLookupTrivial():
- self.imageItem.setLookupTable(None) #lambda x: x.astype(np.uint8))
+ self.imageItem().setLookupTable(None) #lambda x: x.astype(np.uint8))
else:
- self.imageItem.setLookupTable(self.getLookupTable) ## send function pointer, not the result
+ self.imageItem().setLookupTable(self.getLookupTable) ## send function pointer, not the result
self.lut = None
#if self.imageItem is not None:
@@ -178,25 +179,24 @@ def regionChanged(self):
#self.update()
def regionChanging(self):
- if self.imageItem is not None:
- self.imageItem.setLevels(self.region.getRegion())
+ if self.imageItem() is not None:
+ self.imageItem().setLevels(self.region.getRegion())
self.sigLevelsChanged.emit(self)
self.update()
def imageChanged(self, autoLevel=False, autoRange=False):
- prof = debug.Profiler('HistogramLUTItem.imageChanged', disabled=True)
- h = self.imageItem.getHistogram()
- prof.mark('get histogram')
+ profiler = debug.Profiler()
+ h = self.imageItem().getHistogram()
+ profiler('get histogram')
if h[0] is None:
return
self.plot.setData(*h)
- prof.mark('set plot')
+ profiler('set plot')
if autoLevel:
mn = h[0][0]
mx = h[0][-1]
self.region.setRegion([mn, mx])
- prof.mark('set region')
- prof.finish()
+ profiler('set region')
def getLevels(self):
return self.region.getRegion()
diff --git a/pyqtgraph/graphicsItems/ImageItem.py b/papi/pyqtgraph/graphicsItems/ImageItem.py
similarity index 78%
rename from pyqtgraph/graphicsItems/ImageItem.py
rename to papi/pyqtgraph/graphicsItems/ImageItem.py
index 530db7fb..5b041433 100644
--- a/pyqtgraph/graphicsItems/ImageItem.py
+++ b/papi/pyqtgraph/graphicsItems/ImageItem.py
@@ -1,11 +1,16 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from __future__ import division
+
+from ..Qt import QtGui, QtCore
import numpy as np
import collections
-import pyqtgraph.functions as fn
-import pyqtgraph.debug as debug
+from .. import functions as fn
+from .. import debug as debug
from .GraphicsObject import GraphicsObject
+from ..Point import Point
__all__ = ['ImageItem']
+
+
class ImageItem(GraphicsObject):
"""
**Bases:** :class:`GraphicsObject `
@@ -32,20 +37,16 @@ def __init__(self, image=None, **kargs):
See :func:`setImage ` for all allowed initialization arguments.
"""
GraphicsObject.__init__(self)
- #self.pixmapItem = QtGui.QGraphicsPixmapItem(self)
- #self.qimage = QtGui.QImage()
- #self._pixmap = None
self.menu = None
self.image = None ## original image data
self.qimage = None ## rendered image for display
- #self.clipMask = None
self.paintMode = None
self.levels = None ## [min, max] or [[redMin, redMax], ...]
self.lut = None
+ self.autoDownsample = False
- #self.clipLevel = None
self.drawKernel = None
self.border = None
self.removable = False
@@ -140,7 +141,18 @@ def setLookupTable(self, lut, update=True):
if update:
self.updateImage()
+ def setAutoDownsample(self, ads):
+ """
+ Set the automatic downsampling mode for this ImageItem.
+
+ Added in version 0.9.9
+ """
+ self.autoDownsample = ads
+ self.qimage = None
+ self.update()
+
def setOpts(self, update=True, **kargs):
+
if 'lut' in kargs:
self.setLookupTable(kargs['lut'], update=update)
if 'levels' in kargs:
@@ -156,6 +168,10 @@ def setOpts(self, update=True, **kargs):
if 'removable' in kargs:
self.removable = kargs['removable']
self.menu = None
+ if 'autoDownsample' in kargs:
+ self.setAutoDownsample(kargs['autoDownsample'])
+ if update:
+ self.update()
def setRect(self, rect):
"""Scale and translate the image to fit within rect (must be a QRect or QRectF)."""
@@ -163,6 +179,12 @@ def setRect(self, rect):
self.translate(rect.left(), rect.top())
self.scale(rect.width() / self.width(), rect.height() / self.height())
+ def clear(self):
+ self.image = None
+ self.prepareGeometryChange()
+ self.informViewBoundsChanged()
+ self.update()
+
def setImage(self, image=None, autoLevels=None, **kargs):
"""
Update the image displayed by this item. For more information on how the image
@@ -186,10 +208,13 @@ def setImage(self, image=None, autoLevels=None, **kargs):
opacity (float 0.0-1.0)
compositionMode see :func:`setCompositionMode `
border Sets the pen used when drawing the image border. Default is None.
+ autoDownsample (bool) If True, the image is automatically downsampled to match the
+ screen resolution. This improves performance for large images and
+ reduces aliasing.
================= =========================================================================
"""
- prof = debug.Profiler('ImageItem.setImage', disabled=True)
-
+ profile = debug.Profiler()
+
gotNewData = False
if image is None:
if self.image is None:
@@ -198,12 +223,15 @@ def setImage(self, image=None, autoLevels=None, **kargs):
gotNewData = True
shapeChanged = (self.image is None or image.shape != self.image.shape)
self.image = image.view(np.ndarray)
+ if self.image.shape[0] > 2**15-1 or self.image.shape[1] > 2**15-1:
+ if 'autoDownsample' not in kargs:
+ kargs['autoDownsample'] = True
if shapeChanged:
self.prepareGeometryChange()
self.informViewBoundsChanged()
-
- prof.mark('1')
-
+
+ profile()
+
if autoLevels is None:
if 'levels' in kargs:
autoLevels = False
@@ -218,23 +246,22 @@ def setImage(self, image=None, autoLevels=None, **kargs):
mn = 0
mx = 255
kargs['levels'] = [mn,mx]
- prof.mark('2')
-
+
+ profile()
+
self.setOpts(update=False, **kargs)
- prof.mark('3')
-
+
+ profile()
+
self.qimage = None
self.update()
- prof.mark('4')
+
+ profile()
if gotNewData:
self.sigImageChanged.emit()
- prof.finish()
-
-
-
def updateImage(self, *args, **kargs):
## used for re-rendering qimage from self.image.
@@ -245,45 +272,53 @@ def updateImage(self, *args, **kargs):
}
defaults.update(kargs)
return self.setImage(*args, **defaults)
-
-
-
def render(self):
- prof = debug.Profiler('ImageItem.render', disabled=True)
+ # Convert data to QImage for display.
+
+ profile = debug.Profiler()
if self.image is None or self.image.size == 0:
return
if isinstance(self.lut, collections.Callable):
lut = self.lut(self.image)
else:
lut = self.lut
- #print lut.shape
- #print self.lut
-
- argb, alpha = fn.makeARGB(self.image, lut=lut, levels=self.levels)
- self.qimage = fn.makeQImage(argb, alpha)
- prof.finish()
-
+
+ if self.autoDownsample:
+ # reduce dimensions of image based on screen resolution
+ o = self.mapToDevice(QtCore.QPointF(0,0))
+ x = self.mapToDevice(QtCore.QPointF(1,0))
+ y = self.mapToDevice(QtCore.QPointF(0,1))
+ w = Point(x-o).length()
+ h = Point(y-o).length()
+ xds = max(1, int(1/w))
+ yds = max(1, int(1/h))
+ image = fn.downsample(self.image, xds, axis=0)
+ image = fn.downsample(image, yds, axis=1)
+ else:
+ image = self.image
+
+ argb, alpha = fn.makeARGB(image.transpose((1, 0, 2)[:image.ndim]), lut=lut, levels=self.levels)
+ self.qimage = fn.makeQImage(argb, alpha, transpose=False)
def paint(self, p, *args):
- prof = debug.Profiler('ImageItem.paint', disabled=True)
+ profile = debug.Profiler()
if self.image is None:
return
if self.qimage is None:
self.render()
if self.qimage is None:
return
- prof.mark('render QImage')
+ profile('render QImage')
if self.paintMode is not None:
p.setCompositionMode(self.paintMode)
- prof.mark('set comp mode')
-
- p.drawImage(QtCore.QPointF(0,0), self.qimage)
- prof.mark('p.drawImage')
+ profile('set comp mode')
+
+ p.drawImage(QtCore.QRectF(0,0,self.image.shape[0],self.image.shape[1]), self.qimage)
+ profile('p.drawImage')
if self.border is not None:
p.setPen(self.border)
p.drawRect(self.boundingRect())
- prof.finish()
def save(self, fileName, *args):
"""Save this image to file. Note that this saves the visible image (after scale/color changes), not the original data."""
@@ -291,15 +326,47 @@ def save(self, fileName, *args):
self.render()
self.qimage.save(fileName, *args)
- def getHistogram(self, bins=500, step=3):
+ def getHistogram(self, bins='auto', step='auto', targetImageSize=200, targetHistogramSize=500, **kwds):
"""Returns x and y arrays containing the histogram values for the current image.
- The step argument causes pixels to be skipped when computing the histogram to save time.
+ For an explanation of the return format, see numpy.histogram().
+
+ The *step* argument causes pixels to be skipped when computing the histogram to save time.
+ If *step* is 'auto', then a step is chosen such that the analyzed data has
+ dimensions roughly *targetImageSize* for each axis.
+
+ The *bins* argument and any extra keyword arguments are passed to
+ np.histogram(). If *bins* is 'auto', then a bin number is automatically
+ chosen based on the image characteristics:
+
+ * Integer images will have approximately *targetHistogramSize* bins,
+ with each bin having an integer width.
+ * All other types will have *targetHistogramSize* bins.
+
This method is also used when automatically computing levels.
"""
if self.image is None:
return None,None
- stepData = self.image[::step, ::step]
- hist = np.histogram(stepData, bins=bins)
+ if step == 'auto':
+ step = (np.ceil(self.image.shape[0] / targetImageSize),
+ np.ceil(self.image.shape[1] / targetImageSize))
+ if np.isscalar(step):
+ step = (step, step)
+ stepData = self.image[::step[0], ::step[1]]
+
+ if bins == 'auto':
+ if stepData.dtype.kind in "ui":
+ mn = stepData.min()
+ mx = stepData.max()
+ step = np.ceil((mx-mn) / 500.)
+ bins = np.arange(mn, mx+1.01*step, step, dtype=np.int)
+ if len(bins) == 0:
+ bins = [mn, mx]
+ else:
+ bins = 500
+
+ kwds['bins'] = bins
+ hist = np.histogram(stepData, **kwds)
+
return hist[1][:-1], hist[0]
def setPxMode(self, b):
@@ -327,6 +394,11 @@ def pixelSize(self):
if self.image is None:
return 1,1
return br.width()/self.width(), br.height()/self.height()
+
+ def viewTransformChanged(self):
+ if self.autoDownsample:
+ self.qimage = None
+ self.update()
#def mousePressEvent(self, ev):
#if self.drawKernel is not None and ev.button() == QtCore.Qt.LeftButton:
@@ -448,6 +520,9 @@ def setDrawKernel(self, kernel=None, mask=None, center=(0,0), mode='set'):
def removeClicked(self):
## Send remove event only after we have exited the menu event handler
self.removeTimer = QtCore.QTimer()
- self.removeTimer.timeout.connect(lambda: self.sigRemoveRequested.emit(self))
+ self.removeTimer.timeout.connect(self.emitRemoveRequested)
self.removeTimer.start(0)
+ def emitRemoveRequested(self):
+ self.removeTimer.timeout.disconnect(self.emitRemoveRequested)
+ self.sigRemoveRequested.emit(self)
diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/papi/pyqtgraph/graphicsItems/InfiniteLine.py
similarity index 79%
rename from pyqtgraph/graphicsItems/InfiniteLine.py
rename to papi/pyqtgraph/graphicsItems/InfiniteLine.py
index 4f0df863..8108c3cf 100644
--- a/pyqtgraph/graphicsItems/InfiniteLine.py
+++ b/papi/pyqtgraph/graphicsItems/InfiniteLine.py
@@ -1,7 +1,7 @@
-from pyqtgraph.Qt import QtGui, QtCore
-from pyqtgraph.Point import Point
+from ..Qt import QtGui, QtCore
+from ..Point import Point
from .GraphicsObject import GraphicsObject
-import pyqtgraph.functions as fn
+from .. import functions as fn
import numpy as np
import weakref
@@ -15,7 +15,7 @@ class InfiniteLine(GraphicsObject):
This line may be dragged to indicate a position in data coordinates.
=============================== ===================================================
- **Signals**
+ **Signals:**
sigDragged(self)
sigPositionChangeFinished(self)
sigPositionChanged(self)
@@ -28,18 +28,18 @@ class InfiniteLine(GraphicsObject):
def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None):
"""
- ============= ==================================================================
- **Arguments**
- pos Position of the line. This can be a QPointF or a single value for
- vertical/horizontal lines.
- angle Angle of line in degrees. 0 is horizontal, 90 is vertical.
- pen Pen to use when drawing line. Can be any arguments that are valid
- for :func:`mkPen `. Default pen is transparent
- yellow.
- movable If True, the line can be dragged to a new position by the user.
- bounds Optional [min, max] bounding values. Bounds are only valid if the
- line is vertical or horizontal.
- ============= ==================================================================
+ =============== ==================================================================
+ **Arguments:**
+ pos Position of the line. This can be a QPointF or a single value for
+ vertical/horizontal lines.
+ angle Angle of line in degrees. 0 is horizontal, 90 is vertical.
+ pen Pen to use when drawing line. Can be any arguments that are valid
+ for :func:`mkPen `. Default pen is transparent
+ yellow.
+ movable If True, the line can be dragged to a new position by the user.
+ bounds Optional [min, max] bounding values. Bounds are only valid if the
+ line is vertical or horizontal.
+ =============== ==================================================================
"""
GraphicsObject.__init__(self)
@@ -59,7 +59,9 @@ def __init__(self, pos=None, angle=90, pen=None, movable=False, bounds=None):
if pen is None:
pen = (200, 200, 100)
+
self.setPen(pen)
+ self.setHoverPen(color=(255,0,0), width=self.pen.width())
self.currentPen = self.pen
#self.setFlag(self.ItemSendsScenePositionChanges)
@@ -73,12 +75,26 @@ def setBounds(self, bounds):
self.maxRange = bounds
self.setValue(self.value())
- def setPen(self, pen):
+ def setPen(self, *args, **kwargs):
"""Set the pen for drawing the line. Allowable arguments are any that are valid
for :func:`mkPen `."""
- self.pen = fn.mkPen(pen)
- self.currentPen = self.pen
- self.update()
+ self.pen = fn.mkPen(*args, **kwargs)
+ if not self.mouseHovering:
+ self.currentPen = self.pen
+ self.update()
+
+ def setHoverPen(self, *args, **kwargs):
+ """Set the pen for drawing the line while the mouse hovers over it.
+ Allowable arguments are any that are valid
+ for :func:`mkPen `.
+
+ If the line is not movable, then hovering is also disabled.
+
+ Added in version 0.9.9."""
+ self.hoverPen = fn.mkPen(*args, **kwargs)
+ if self.mouseHovering:
+ self.currentPen = self.hoverPen
+ self.update()
def setAngle(self, angle):
"""
@@ -168,8 +184,9 @@ def boundingRect(self):
px = self.pixelLength(direction=Point(1,0), ortho=True) ## get pixel length orthogonal to the line
if px is None:
px = 0
- br.setBottom(-px*4)
- br.setTop(px*4)
+ w = (max(4, self.pen.width()/2, self.hoverPen.width()/2)+1) * px
+ br.setBottom(-w)
+ br.setTop(w)
return br.normalized()
def paint(self, p, *args):
@@ -183,25 +200,6 @@ def dataBounds(self, axis, frac=1.0, orthoRange=None):
return None ## x axis should never be auto-scaled
else:
return (0,0)
-
- #def mousePressEvent(self, ev):
- #if self.movable and ev.button() == QtCore.Qt.LeftButton:
- #ev.accept()
- #self.pressDelta = self.mapToParent(ev.pos()) - QtCore.QPointF(*self.p)
- #else:
- #ev.ignore()
-
- #def mouseMoveEvent(self, ev):
- #self.setPos(self.mapToParent(ev.pos()) - self.pressDelta)
- ##self.emit(QtCore.SIGNAL('dragged'), self)
- #self.sigDragged.emit(self)
- #self.hasMoved = True
-
- #def mouseReleaseEvent(self, ev):
- #if self.hasMoved and ev.button() == QtCore.Qt.LeftButton:
- #self.hasMoved = False
- ##self.emit(QtCore.SIGNAL('positionChangeFinished'), self)
- #self.sigPositionChangeFinished.emit(self)
def mouseDragEvent(self, ev):
if self.movable and ev.button() == QtCore.Qt.LeftButton:
@@ -239,12 +237,12 @@ def hoverEvent(self, ev):
self.setMouseHover(False)
def setMouseHover(self, hover):
- ## Inform the item that the mouse is(not) hovering over it
+ ## Inform the item that the mouse is (not) hovering over it
if self.mouseHovering == hover:
return
self.mouseHovering = hover
if hover:
- self.currentPen = fn.mkPen(255, 0,0)
+ self.currentPen = self.hoverPen
else:
self.currentPen = self.pen
self.update()
diff --git a/pyqtgraph/graphicsItems/IsocurveItem.py b/papi/pyqtgraph/graphicsItems/IsocurveItem.py
similarity index 70%
rename from pyqtgraph/graphicsItems/IsocurveItem.py
rename to papi/pyqtgraph/graphicsItems/IsocurveItem.py
index 01ef57b6..4474e29a 100644
--- a/pyqtgraph/graphicsItems/IsocurveItem.py
+++ b/papi/pyqtgraph/graphicsItems/IsocurveItem.py
@@ -1,8 +1,8 @@
from .GraphicsObject import *
-import pyqtgraph.functions as fn
-from pyqtgraph.Qt import QtGui, QtCore
+from .. import functions as fn
+from ..Qt import QtGui, QtCore
class IsocurveItem(GraphicsObject):
@@ -18,14 +18,14 @@ def __init__(self, data=None, level=0, pen='w'):
"""
Create a new isocurve item.
- ============= ===============================================================
- **Arguments**
- data A 2-dimensional ndarray. Can be initialized as None, and set
- later using :func:`setData `
- level The cutoff value at which to draw the isocurve.
- pen The color of the curve item. Can be anything valid for
- :func:`mkPen `
- ============= ===============================================================
+ ============== ===============================================================
+ **Arguments:**
+ data A 2-dimensional ndarray. Can be initialized as None, and set
+ later using :func:`setData `
+ level The cutoff value at which to draw the isocurve.
+ pen The color of the curve item. Can be anything valid for
+ :func:`mkPen `
+ ============== ===============================================================
"""
GraphicsObject.__init__(self)
@@ -35,22 +35,17 @@ def __init__(self, data=None, level=0, pen='w'):
self.setPen(pen)
self.setData(data, level)
-
-
- #if data is not None and level is not None:
- #self.updateLines(data, level)
-
def setData(self, data, level=None):
"""
Set the data/image to draw isocurves for.
- ============= ========================================================================
- **Arguments**
- data A 2-dimensional ndarray.
- level The cutoff value at which to draw the curve. If level is not specified,
- the previously set level is used.
- ============= ========================================================================
+ ============== ========================================================================
+ **Arguments:**
+ data A 2-dimensional ndarray.
+ level The cutoff value at which to draw the curve. If level is not specified,
+ the previously set level is used.
+ ============== ========================================================================
"""
if level is None:
level = self.level
@@ -65,6 +60,7 @@ def setLevel(self, level):
"""Set the level at which the isocurve is drawn."""
self.level = level
self.path = None
+ self.prepareGeometryChange()
self.update()
diff --git a/pyqtgraph/graphicsItems/ItemGroup.py b/papi/pyqtgraph/graphicsItems/ItemGroup.py
similarity index 92%
rename from pyqtgraph/graphicsItems/ItemGroup.py
rename to papi/pyqtgraph/graphicsItems/ItemGroup.py
index 930fdf80..4eb0ee0d 100644
--- a/pyqtgraph/graphicsItems/ItemGroup.py
+++ b/papi/pyqtgraph/graphicsItems/ItemGroup.py
@@ -1,4 +1,4 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
from .GraphicsObject import GraphicsObject
__all__ = ['ItemGroup']
diff --git a/pyqtgraph/graphicsItems/LabelItem.py b/papi/pyqtgraph/graphicsItems/LabelItem.py
similarity index 97%
rename from pyqtgraph/graphicsItems/LabelItem.py
rename to papi/pyqtgraph/graphicsItems/LabelItem.py
index 6101c4bc..37980ee3 100644
--- a/pyqtgraph/graphicsItems/LabelItem.py
+++ b/papi/pyqtgraph/graphicsItems/LabelItem.py
@@ -1,8 +1,8 @@
-from pyqtgraph.Qt import QtGui, QtCore
-import pyqtgraph.functions as fn
-import pyqtgraph as pg
+from ..Qt import QtGui, QtCore
+from .. import functions as fn
from .GraphicsWidget import GraphicsWidget
from .GraphicsWidgetAnchor import GraphicsWidgetAnchor
+from .. import getConfigOption
__all__ = ['LabelItem']
@@ -54,7 +54,7 @@ def setText(self, text, **args):
color = self.opts['color']
if color is None:
- color = pg.getConfigOption('foreground')
+ color = getConfigOption('foreground')
color = fn.mkColor(color)
optlist.append('color: #' + fn.colorStr(color)[:6])
if 'size' in opts:
diff --git a/pyqtgraph/graphicsItems/LegendItem.py b/papi/pyqtgraph/graphicsItems/LegendItem.py
similarity index 70%
rename from pyqtgraph/graphicsItems/LegendItem.py
rename to papi/pyqtgraph/graphicsItems/LegendItem.py
index 69ddffea..20d6416e 100644
--- a/pyqtgraph/graphicsItems/LegendItem.py
+++ b/papi/pyqtgraph/graphicsItems/LegendItem.py
@@ -3,8 +3,9 @@
from ..Qt import QtGui, QtCore
from .. import functions as fn
from ..Point import Point
+from .ScatterPlotItem import ScatterPlotItem, drawSymbol
+from .PlotDataItem import PlotDataItem
from .GraphicsWidgetAnchor import GraphicsWidgetAnchor
-import pyqtgraph as pg
__all__ = ['LegendItem']
class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
@@ -20,17 +21,17 @@ class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
"""
def __init__(self, size=None, offset=None):
"""
- ========== ===============================================================
- Arguments
- size Specifies the fixed size (width, height) of the legend. If
- this argument is omitted, the legend will autimatically resize
- to fit its contents.
- offset Specifies the offset position relative to the legend's parent.
- Positive values offset from the left or top; negative values
- offset from the right or bottom. If offset is None, the
- legend must be anchored manually by calling anchor() or
- positioned by calling setPos().
- ========== ===============================================================
+ ============== ===============================================================
+ **Arguments:**
+ size Specifies the fixed size (width, height) of the legend. If
+ this argument is omitted, the legend will autimatically resize
+ to fit its contents.
+ offset Specifies the offset position relative to the legend's parent.
+ Positive values offset from the left or top; negative values
+ offset from the right or bottom. If offset is None, the
+ legend must be anchored manually by calling anchor() or
+ positioned by calling setPos().
+ ============== ===============================================================
"""
@@ -60,21 +61,21 @@ def addItem(self, item, name):
"""
Add a new entry to the legend.
- =========== ========================================================
- Arguments
- item A PlotDataItem from which the line and point style
- of the item will be determined or an instance of
- ItemSample (or a subclass), allowing the item display
- to be customized.
- title The title to display for this item. Simple HTML allowed.
- =========== ========================================================
+ ============== ========================================================
+ **Arguments:**
+ item A PlotDataItem from which the line and point style
+ of the item will be determined or an instance of
+ ItemSample (or a subclass), allowing the item display
+ to be customized.
+ title The title to display for this item. Simple HTML allowed.
+ ============== ========================================================
"""
label = LabelItem(name)
if isinstance(item, ItemSample):
sample = item
else:
sample = ItemSample(item)
- row = len(self.items)
+ row = self.layout.rowCount()
self.items.append((sample, label))
self.layout.addItem(sample, row, 0)
self.layout.addItem(label, row, 1)
@@ -84,10 +85,10 @@ def removeItem(self, name):
"""
Removes one item from the legend.
- =========== ========================================================
- Arguments
- title The title displayed for this item.
- =========== ========================================================
+ ============== ========================================================
+ **Arguments:**
+ title The title displayed for this item.
+ ============== ========================================================
"""
# Thanks, Ulrich!
# cycle for a match
@@ -152,21 +153,21 @@ def paint(self, p, *args):
p.setPen(fn.mkPen(None))
p.drawPolygon(QtGui.QPolygonF([QtCore.QPointF(2,18), QtCore.QPointF(18,2), QtCore.QPointF(18,18)]))
- if not isinstance(self.item, pg.ScatterPlotItem):
+ if not isinstance(self.item, ScatterPlotItem):
p.setPen(fn.mkPen(opts['pen']))
p.drawLine(2, 18, 18, 2)
symbol = opts.get('symbol', None)
if symbol is not None:
- if isinstance(self.item, pg.PlotDataItem):
+ if isinstance(self.item, PlotDataItem):
opts = self.item.scatter.opts
- pen = pg.mkPen(opts['pen'])
- brush = pg.mkBrush(opts['brush'])
+ pen = fn.mkPen(opts['pen'])
+ brush = fn.mkBrush(opts['brush'])
size = opts['size']
p.translate(10,10)
- path = pg.graphicsItems.ScatterPlotItem.drawSymbol(p, symbol, size, pen, brush)
+ path = drawSymbol(p, symbol, size, pen, brush)
diff --git a/pyqtgraph/graphicsItems/LinearRegionItem.py b/papi/pyqtgraph/graphicsItems/LinearRegionItem.py
similarity index 87%
rename from pyqtgraph/graphicsItems/LinearRegionItem.py
rename to papi/pyqtgraph/graphicsItems/LinearRegionItem.py
index a35e8efc..e139190b 100644
--- a/pyqtgraph/graphicsItems/LinearRegionItem.py
+++ b/papi/pyqtgraph/graphicsItems/LinearRegionItem.py
@@ -1,8 +1,8 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
from .UIGraphicsItem import UIGraphicsItem
from .InfiniteLine import InfiniteLine
-import pyqtgraph.functions as fn
-import pyqtgraph.debug as debug
+from .. import functions as fn
+from .. import debug as debug
__all__ = ['LinearRegionItem']
@@ -30,19 +30,19 @@ class LinearRegionItem(UIGraphicsItem):
def __init__(self, values=[0,1], orientation=None, brush=None, movable=True, bounds=None):
"""Create a new LinearRegionItem.
- ============= =====================================================================
- **Arguments**
- values A list of the positions of the lines in the region. These are not
- limits; limits can be set by specifying bounds.
- orientation Options are LinearRegionItem.Vertical or LinearRegionItem.Horizontal.
- If not specified it will be vertical.
- brush Defines the brush that fills the region. Can be any arguments that
- are valid for :func:`mkBrush `. Default is
- transparent blue.
- movable If True, the region and individual lines are movable by the user; if
- False, they are static.
- bounds Optional [min, max] bounding values for the region
- ============= =====================================================================
+ ============== =====================================================================
+ **Arguments:**
+ values A list of the positions of the lines in the region. These are not
+ limits; limits can be set by specifying bounds.
+ orientation Options are LinearRegionItem.Vertical or LinearRegionItem.Horizontal.
+ If not specified it will be vertical.
+ brush Defines the brush that fills the region. Can be any arguments that
+ are valid for :func:`mkBrush `. Default is
+ transparent blue.
+ movable If True, the region and individual lines are movable by the user; if
+ False, they are static.
+ bounds Optional [min, max] bounding values for the region
+ ============== =====================================================================
"""
UIGraphicsItem.__init__(self)
@@ -89,10 +89,10 @@ def getRegion(self):
def setRegion(self, rgn):
"""Set the values for the edges of the region.
- ============= ==============================================
- **Arguments**
- rgn A list or tuple of the lower and upper values.
- ============= ==============================================
+ ============== ==============================================
+ **Arguments:**
+ rgn A list or tuple of the lower and upper values.
+ ============== ==============================================
"""
if self.lines[0].value() == rgn[0] and self.lines[1].value() == rgn[1]:
return
@@ -140,12 +140,11 @@ def boundingRect(self):
return br.normalized()
def paint(self, p, *args):
- #prof = debug.Profiler('LinearRegionItem.paint')
+ profiler = debug.Profiler()
UIGraphicsItem.paint(self, p, *args)
p.setBrush(self.currentBrush)
p.setPen(fn.mkPen(None))
p.drawRect(self.boundingRect())
- #prof.finish()
def dataBounds(self, axis, frac=1.0, orthoRange=None):
if axis == self.orientation:
diff --git a/pyqtgraph/graphicsItems/MultiPlotItem.py b/papi/pyqtgraph/graphicsItems/MultiPlotItem.py
similarity index 69%
rename from pyqtgraph/graphicsItems/MultiPlotItem.py
rename to papi/pyqtgraph/graphicsItems/MultiPlotItem.py
index d20467a9..be775d4a 100644
--- a/pyqtgraph/graphicsItems/MultiPlotItem.py
+++ b/papi/pyqtgraph/graphicsItems/MultiPlotItem.py
@@ -7,26 +7,23 @@
from numpy import ndarray
from . import GraphicsLayout
+from ..metaarray import *
-try:
- from metaarray import *
- HAVE_METAARRAY = True
-except:
- #raise
- HAVE_METAARRAY = False
-
__all__ = ['MultiPlotItem']
class MultiPlotItem(GraphicsLayout.GraphicsLayout):
"""
- Automaticaly generates a grid of plots from a multi-dimensional array
+ Automatically generates a grid of plots from a multi-dimensional array
"""
-
+ def __init__(self, *args, **kwds):
+ GraphicsLayout.GraphicsLayout.__init__(self, *args, **kwds)
+ self.plots = []
+
+
def plot(self, data):
#self.layout.clear()
- self.plots = []
-
- if HAVE_METAARRAY and (hasattr(data, 'implements') and data.implements('MetaArray')):
+
+ if hasattr(data, 'implements') and data.implements('MetaArray'):
if data.ndim != 2:
raise Exception("MultiPlot currently only accepts 2D MetaArray.")
ic = data.infoCopy()
@@ -44,21 +41,17 @@ def plot(self, data):
pi.plot(data[tuple(sl)])
#self.layout.addItem(pi, i, 0)
self.plots.append((pi, i, 0))
- title = None
- units = None
info = ic[ax]['cols'][i]
- if 'title' in info:
- title = info['title']
- elif 'name' in info:
- title = info['name']
- if 'units' in info:
- units = info['units']
-
+ title = info.get('title', info.get('name', None))
+ units = info.get('units', None)
pi.setLabel('left', text=title, units=units)
-
+ info = ic[1-ax]
+ title = info.get('title', info.get('name', None))
+ units = info.get('units', None)
+ pi.setLabel('bottom', text=title, units=units)
else:
raise Exception("Data type %s not (yet?) supported for MultiPlot." % type(data))
-
+
def close(self):
for p in self.plots:
p[0].close()
diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/papi/pyqtgraph/graphicsItems/PlotCurveItem.py
similarity index 87%
rename from pyqtgraph/graphicsItems/PlotCurveItem.py
rename to papi/pyqtgraph/graphicsItems/PlotCurveItem.py
index 28214552..3d3e969d 100644
--- a/pyqtgraph/graphicsItems/PlotCurveItem.py
+++ b/papi/pyqtgraph/graphicsItems/PlotCurveItem.py
@@ -1,17 +1,17 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
try:
- from pyqtgraph.Qt import QtOpenGL
+ from ..Qt import QtOpenGL
HAVE_OPENGL = True
except:
HAVE_OPENGL = False
import numpy as np
from .GraphicsObject import GraphicsObject
-import pyqtgraph.functions as fn
-from pyqtgraph import debug
-from pyqtgraph.Point import Point
-import pyqtgraph as pg
+from .. import functions as fn
+from ..Point import Point
import struct, sys
+from .. import getConfigOption
+from .. import debug
__all__ = ['PlotCurveItem']
class PlotCurveItem(GraphicsObject):
@@ -53,9 +53,6 @@ def __init__(self, *args, **kargs):
"""
GraphicsObject.__init__(self, kargs.get('parent', None))
self.clear()
- self.path = None
- self.fillPath = None
- self._boundsCache = [None, None]
## this is disastrous for performance.
#self.setCacheMode(QtGui.QGraphicsItem.DeviceCoordinateCache)
@@ -68,8 +65,9 @@ def __init__(self, *args, **kargs):
'brush': None,
'stepMode': False,
'name': None,
- 'antialias': pg.getConfigOption('antialias'),\
+ 'antialias': getConfigOption('antialias'),
'connect': 'all',
+ 'mouseWidth': 8, # width of shape responding to mouse click
}
self.setClickable(kargs.get('clickable', False))
self.setData(*args, **kargs)
@@ -80,9 +78,20 @@ def implements(self, interface=None):
return ints
return interface in ints
- def setClickable(self, s):
- """Sets whether the item responds to mouse clicks."""
+ def name(self):
+ return self.opts.get('name', None)
+
+ def setClickable(self, s, width=None):
+ """Sets whether the item responds to mouse clicks.
+
+ The *width* argument specifies the width in pixels orthogonal to the
+ curve that will respond to a mouse click.
+ """
self.clickable = s
+ if width is not None:
+ self.opts['mouseWidth'] = width
+ self._mouseShape = None
+ self._boundingRect = None
def getData(self):
@@ -148,6 +157,8 @@ def pixelPadding(self):
w += pen.widthF()*0.7072
if spen is not None and spen.isCosmetic() and spen.style() != QtCore.Qt.NoPen:
w = max(w, spen.widthF()*0.7072)
+ if self.clickable:
+ w = max(w, self.opts['mouseWidth']//2 + 1)
return w
def boundingRect(self):
@@ -162,8 +173,14 @@ def boundingRect(self):
if pxPad > 0:
# determine length of pixel in local x, y directions
px, py = self.pixelVectors()
- px = 0 if px is None else px.length()
- py = 0 if py is None else py.length()
+ try:
+ px = 0 if px is None else px.length()
+ except OverflowError:
+ px = 0
+ try:
+ py = 0 if py is None else py.length()
+ except OverflowError:
+ py = 0
# return bounds expanded by pixel size
px *= pxPad
@@ -171,6 +188,7 @@ def boundingRect(self):
#px += self._maxSpotWidth * 0.5
#py += self._maxSpotWidth * 0.5
self._boundingRect = QtCore.QRectF(xmn-px, ymn-py, (2*px)+xmx-xmn, (2*py)+ymx-ymn)
+
return self._boundingRect
def viewTransformChanged(self):
@@ -281,7 +299,7 @@ def setData(self, *args, **kargs):
self.updateData(*args, **kargs)
def updateData(self, *args, **kargs):
- prof = debug.Profiler('PlotCurveItem.updateData', disabled=True)
+ profiler = debug.Profiler()
if len(args) == 1:
kargs['y'] = args[0]
@@ -304,7 +322,7 @@ def updateData(self, *args, **kargs):
if 'complex' in str(data.dtype):
raise Exception("Can not plot complex data types.")
- prof.mark("data checks")
+ profiler("data checks")
#self.setCacheMode(QtGui.QGraphicsItem.NoCache) ## Disabling and re-enabling the cache works around a bug in Qt 4.6 causing the cached results to display incorrectly
## Test this bug with test_PlotWidget and zoom in on the animated plot
@@ -314,7 +332,7 @@ def updateData(self, *args, **kargs):
self.yData = kargs['y'].view(np.ndarray)
self.xData = kargs['x'].view(np.ndarray)
- prof.mark('copy')
+ profiler('copy')
if 'stepMode' in kargs:
self.opts['stepMode'] = kargs['stepMode']
@@ -328,6 +346,7 @@ def updateData(self, *args, **kargs):
self.path = None
self.fillPath = None
+ self._mouseShape = None
#self.xDisp = self.yDisp = None
if 'name' in kargs:
@@ -346,12 +365,11 @@ def updateData(self, *args, **kargs):
self.opts['antialias'] = kargs['antialias']
- prof.mark('set')
+ profiler('set')
self.update()
- prof.mark('update')
+ profiler('update')
self.sigPlotChanged.emit(self)
- prof.mark('emit')
- prof.finish()
+ profiler('emit')
def generatePath(self, x, y):
if self.opts['stepMode']:
@@ -377,35 +395,33 @@ def generatePath(self, x, y):
return path
- def shape(self):
+ def getPath(self):
if self.path is None:
- try:
+ x,y = self.getData()
+ if x is None or len(x) == 0 or y is None or len(y) == 0:
+ self.path = QtGui.QPainterPath()
+ else:
self.path = self.generatePath(*self.getData())
- except:
- return QtGui.QPainterPath()
+ self.fillPath = None
+ self._mouseShape = None
+
return self.path
- @pg.debug.warnOnException ## raising an exception here causes crash
+ @debug.warnOnException ## raising an exception here causes crash
def paint(self, p, opt, widget):
- prof = debug.Profiler('PlotCurveItem.paint '+str(id(self)), disabled=True)
- if self.xData is None:
+ profiler = debug.Profiler()
+ if self.xData is None or len(self.xData) == 0:
return
- if HAVE_OPENGL and pg.getConfigOption('enableExperimental') and isinstance(widget, QtOpenGL.QGLWidget):
+ if HAVE_OPENGL and getConfigOption('enableExperimental') and isinstance(widget, QtOpenGL.QGLWidget):
self.paintGL(p, opt, widget)
return
x = None
y = None
- if self.path is None:
- x,y = self.getData()
- if x is None or len(x) == 0 or y is None or len(y) == 0:
- return
- self.path = self.generatePath(x,y)
- self.fillPath = None
-
- path = self.path
- prof.mark('generate path')
+ path = self.getPath()
+
+ profiler('generate path')
if self._exportOpts is not False:
aa = self._exportOpts.get('antialias', True)
@@ -426,9 +442,9 @@ def paint(self, p, opt, widget):
p2.closeSubpath()
self.fillPath = p2
- prof.mark('generate fill path')
+ profiler('generate fill path')
p.fillPath(self.fillPath, self.opts['brush'])
- prof.mark('draw fill path')
+ profiler('draw fill path')
sp = fn.mkPen(self.opts['shadowPen'])
cp = fn.mkPen(self.opts['pen'])
@@ -451,10 +467,9 @@ def paint(self, p, opt, widget):
p.drawPath(path)
p.setPen(cp)
p.drawPath(path)
- prof.mark('drawPath')
+ profiler('drawPath')
#print "Render hints:", int(p.renderHints())
- prof.finish()
#p.setPen(QtGui.QPen(QtGui.QColor(255,0,0)))
#p.drawRect(self.boundingRect())
@@ -477,7 +492,7 @@ def paintGL(self, p, opt, widget):
gl.glStencilOp(gl.GL_REPLACE, gl.GL_KEEP, gl.GL_KEEP)
## draw stencil pattern
- gl.glStencilMask(0xFF);
+ gl.glStencilMask(0xFF)
gl.glClear(gl.GL_STENCIL_BUFFER_BIT)
gl.glBegin(gl.GL_TRIANGLES)
gl.glVertex2f(rect.x(), rect.y())
@@ -511,7 +526,7 @@ def paintGL(self, p, opt, widget):
gl.glEnable(gl.GL_LINE_SMOOTH)
gl.glEnable(gl.GL_BLEND)
gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
- gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST);
+ gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST)
gl.glDrawArrays(gl.GL_LINE_STRIP, 0, pos.size / pos.shape[-1])
finally:
gl.glDisableClientState(gl.GL_VERTEX_ARRAY)
@@ -524,13 +539,36 @@ def clear(self):
self.xDisp = None ## display values (after log / fft)
self.yDisp = None
self.path = None
+ self.fillPath = None
+ self._mouseShape = None
+ self._mouseBounds = None
+ self._boundsCache = [None, None]
#del self.xData, self.yData, self.xDisp, self.yDisp, self.path
+
+ def mouseShape(self):
+ """
+ Return a QPainterPath representing the clickable shape of the curve
+
+ """
+ if self._mouseShape is None:
+ view = self.getViewBox()
+ if view is None:
+ return QtGui.QPainterPath()
+ stroker = QtGui.QPainterPathStroker()
+ path = self.getPath()
+ path = self.mapToItem(view, path)
+ stroker.setWidth(self.opts['mouseWidth'])
+ mousePath = stroker.createStroke(path)
+ self._mouseShape = self.mapFromItem(view, mousePath)
+ return self._mouseShape
def mouseClickEvent(self, ev):
if not self.clickable or ev.button() != QtCore.Qt.LeftButton:
return
- ev.accept()
- self.sigClicked.emit(self)
+ if self.mouseShape().contains(ev.pos()):
+ ev.accept()
+ self.sigClicked.emit(self)
+
class ROIPlotItem(PlotCurveItem):
diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/papi/pyqtgraph/graphicsItems/PlotDataItem.py
similarity index 88%
rename from pyqtgraph/graphicsItems/PlotDataItem.py
rename to papi/pyqtgraph/graphicsItems/PlotDataItem.py
index 87b47227..6148989d 100644
--- a/pyqtgraph/graphicsItems/PlotDataItem.py
+++ b/papi/pyqtgraph/graphicsItems/PlotDataItem.py
@@ -1,12 +1,12 @@
-import pyqtgraph.metaarray as metaarray
-from pyqtgraph.Qt import QtCore
+from .. import metaarray as metaarray
+from ..Qt import QtCore
from .GraphicsObject import GraphicsObject
from .PlotCurveItem import PlotCurveItem
from .ScatterPlotItem import ScatterPlotItem
import numpy as np
-import pyqtgraph.functions as fn
-import pyqtgraph.debug as debug
-import pyqtgraph as pg
+from .. import functions as fn
+from .. import debug as debug
+from .. import getConfigOption
class PlotDataItem(GraphicsObject):
"""
@@ -15,7 +15,7 @@ class PlotDataItem(GraphicsObject):
GraphicsItem for displaying plot curves, scatter plots, or both.
While it is possible to use :class:`PlotCurveItem ` or
:class:`ScatterPlotItem ` individually, this class
- provides a unified interface to both. Inspances of :class:`PlotDataItem` are
+ provides a unified interface to both. Instances of :class:`PlotDataItem` are
usually created by plot() methods such as :func:`pyqtgraph.plot` and
:func:`PlotItem.plot() `.
@@ -56,10 +56,11 @@ def __init__(self, *args, **kargs):
=========================== =========================================
**Line style keyword arguments:**
- ========== ================================================
- connect Specifies how / whether vertexes should be connected.
- See :func:`arrayToQPath() `
- pen Pen to use for drawing line between points.
+
+ ========== ==============================================================================
+ connect Specifies how / whether vertexes should be connected. See
+ :func:`arrayToQPath() `
+ pen Pen to use for drawing line between points.
Default is solid grey, 1px width. Use None to disable line drawing.
May be any single argument accepted by :func:`mkPen() `
shadowPen Pen for secondary line to draw behind the primary line. disabled by default.
@@ -67,21 +68,29 @@ def __init__(self, *args, **kargs):
fillLevel Fill the area between the curve and fillLevel
fillBrush Fill to use when fillLevel is specified.
May be any single argument accepted by :func:`mkBrush() `
- ========== ================================================
+ stepMode If True, two orthogonal lines are drawn for each sample
+ as steps. This is commonly used when drawing histograms.
+ Note that in this case, `len(x) == len(y) + 1`
+ (added in version 0.9.9)
+ ========== ==============================================================================
**Point style keyword arguments:** (see :func:`ScatterPlotItem.setData() ` for more information)
- ============ ================================================
- symbol Symbol to use for drawing points OR list of symbols, one per point. Default is no symbol.
+ ============ =====================================================
+ symbol Symbol to use for drawing points OR list of symbols,
+ one per point. Default is no symbol.
Options are o, s, t, d, +, or any QPainterPath
- symbolPen Outline pen for drawing points OR list of pens, one per point.
- May be any single argument accepted by :func:`mkPen() `
- symbolBrush Brush for filling points OR list of brushes, one per point.
- May be any single argument accepted by :func:`mkBrush() `
+ symbolPen Outline pen for drawing points OR list of pens, one
+ per point. May be any single argument accepted by
+ :func:`mkPen() `
+ symbolBrush Brush for filling points OR list of brushes, one per
+ point. May be any single argument accepted by
+ :func:`mkBrush() `
symbolSize Diameter of symbols OR list of diameters.
- pxMode (bool) If True, then symbolSize is specified in pixels. If False, then symbolSize is
+ pxMode (bool) If True, then symbolSize is specified in
+ pixels. If False, then symbolSize is
specified in data coordinates.
- ============ ================================================
+ ============ =====================================================
**Optimization keyword arguments:**
@@ -92,11 +101,11 @@ def __init__(self, *args, **kargs):
decimate deprecated.
downsample (int) Reduce the number of samples displayed by this value
downsampleMethod 'subsample': Downsample by taking the first of N samples.
- This method is fastest and least accurate.
+ This method is fastest and least accurate.
'mean': Downsample by taking the mean of N samples.
'peak': Downsample by drawing a saw wave that follows the min
- and max of the original data. This method produces the best
- visual representation of the data but is slower.
+ and max of the original data. This method produces the best
+ visual representation of the data but is slower.
autoDownsample (bool) If True, resample the data before plotting to avoid plotting
multiple line segments per pixel. This can improve performance when
viewing very high-density data, but increases the initial overhead
@@ -145,6 +154,7 @@ def __init__(self, *args, **kargs):
'shadowPen': None,
'fillLevel': None,
'fillBrush': None,
+ 'stepMode': None,
'symbol': None,
'symbolSize': 10,
@@ -152,12 +162,13 @@ def __init__(self, *args, **kargs):
'symbolBrush': (50, 50, 150),
'pxMode': True,
- 'antialias': pg.getConfigOption('antialias'),
+ 'antialias': getConfigOption('antialias'),
'pointMode': None,
'downsample': 1,
'autoDownsample': False,
'downsampleMethod': 'peak',
+ 'autoDownsampleFactor': 5., # draw ~5 samples per pixel
'clipToView': False,
'data': None,
@@ -170,6 +181,9 @@ def implements(self, interface=None):
return ints
return interface in ints
+ def name(self):
+ return self.opts.get('name', None)
+
def boundingRect(self):
return QtCore.QRectF() ## let child items handle this
@@ -287,18 +301,18 @@ def setDownsampling(self, ds=None, auto=None, method=None):
Set the downsampling mode of this item. Downsampling reduces the number
of samples drawn to increase performance.
- =========== =================================================================
- Arguments
- ds (int) Reduce visible plot samples by this factor. To disable,
- set ds=1.
- auto (bool) If True, automatically pick *ds* based on visible range
- mode 'subsample': Downsample by taking the first of N samples.
- This method is fastest and least accurate.
- 'mean': Downsample by taking the mean of N samples.
- 'peak': Downsample by drawing a saw wave that follows the min
- and max of the original data. This method produces the best
- visual representation of the data but is slower.
- =========== =================================================================
+ ============== =================================================================
+ **Arguments:**
+ ds (int) Reduce visible plot samples by this factor. To disable,
+ set ds=1.
+ auto (bool) If True, automatically pick *ds* based on visible range
+ mode 'subsample': Downsample by taking the first of N samples.
+ This method is fastest and least accurate.
+ 'mean': Downsample by taking the mean of N samples.
+ 'peak': Downsample by drawing a saw wave that follows the min
+ and max of the original data. This method produces the best
+ visual representation of the data but is slower.
+ ============== =================================================================
"""
changed = False
if ds is not None:
@@ -333,7 +347,7 @@ def setData(self, *args, **kargs):
See :func:`__init__() ` for details; it accepts the same arguments.
"""
#self.clear()
- prof = debug.Profiler('PlotDataItem.setData (0x%x)' % id(self), disabled=True)
+ profiler = debug.Profiler()
y = None
x = None
if len(args) == 1:
@@ -367,14 +381,23 @@ def setData(self, *args, **kargs):
elif len(args) == 2:
seq = ('listOfValues', 'MetaArray', 'empty')
- if dataType(args[0]) not in seq or dataType(args[1]) not in seq:
+ dtyp = dataType(args[0]), dataType(args[1])
+ if dtyp[0] not in seq or dtyp[1] not in seq:
raise Exception('When passing two unnamed arguments, both must be a list or array of values. (got %s, %s)' % (str(type(args[0])), str(type(args[1]))))
if not isinstance(args[0], np.ndarray):
- x = np.array(args[0])
+ #x = np.array(args[0])
+ if dtyp[0] == 'MetaArray':
+ x = args[0].asarray()
+ else:
+ x = np.array(args[0])
else:
x = args[0].view(np.ndarray)
if not isinstance(args[1], np.ndarray):
- y = np.array(args[1])
+ #y = np.array(args[1])
+ if dtyp[1] == 'MetaArray':
+ y = args[1].asarray()
+ else:
+ y = np.array(args[1])
else:
y = args[1].view(np.ndarray)
@@ -383,7 +406,7 @@ def setData(self, *args, **kargs):
if 'y' in kargs:
y = kargs['y']
- prof.mark('interpret data')
+ profiler('interpret data')
## pull in all style arguments.
## Use self.opts to fill in anything not present in kargs.
@@ -432,10 +455,10 @@ def setData(self, *args, **kargs):
self.xClean = self.yClean = None
self.xDisp = None
self.yDisp = None
- prof.mark('set data')
+ profiler('set data')
self.updateItems()
- prof.mark('update items')
+ profiler('update items')
self.informViewBoundsChanged()
#view = self.getViewBox()
@@ -443,14 +466,12 @@ def setData(self, *args, **kargs):
#view.itemBoundsChanged(self) ## inform view so it can update its range if it wants
self.sigPlotChanged.emit(self)
- prof.mark('emit')
- prof.finish()
-
+ profiler('emit')
def updateItems(self):
curveArgs = {}
- for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect')]:
+ for k,v in [('pen','pen'), ('shadowPen','shadowPen'), ('fillLevel','fillLevel'), ('fillBrush', 'brush'), ('antialias', 'antialias'), ('connect', 'connect'), ('stepMode', 'stepMode')]:
curveArgs[v] = self.opts[k]
scatterArgs = {}
@@ -526,19 +547,22 @@ def getData(self):
x0 = (range.left()-x[0]) / dx
x1 = (range.right()-x[0]) / dx
width = self.getViewBox().width()
- ds = int(max(1, int(0.2 * (x1-x0) / width)))
+ if width != 0.0:
+ ds = int(max(1, int((x1-x0) / (width*self.opts['autoDownsampleFactor']))))
## downsampling is expensive; delay until after clipping.
if self.opts['clipToView']:
- # this option presumes that x-values have uniform spacing
- range = self.viewRect()
- if range is not None:
- dx = float(x[-1]-x[0]) / (len(x)-1)
- # clip to visible region extended by downsampling value
- x0 = np.clip(int((range.left()-x[0])/dx)-1*ds , 0, len(x)-1)
- x1 = np.clip(int((range.right()-x[0])/dx)+2*ds , 0, len(x)-1)
- x = x[x0:x1]
- y = y[x0:x1]
+ view = self.getViewBox()
+ if view is None or not view.autoRangeEnabled()[0]:
+ # this option presumes that x-values have uniform spacing
+ range = self.viewRect()
+ if range is not None and len(x) > 1:
+ dx = float(x[-1]-x[0]) / (len(x)-1)
+ # clip to visible region extended by downsampling value
+ x0 = np.clip(int((range.left()-x[0])/dx)-1*ds , 0, len(x)-1)
+ x1 = np.clip(int((range.right()-x[0])/dx)+2*ds , 0, len(x)-1)
+ x = x[x0:x1]
+ y = y[x0:x1]
if ds > 1:
if self.opts['downsampleMethod'] == 'subsample':
@@ -643,13 +667,12 @@ def viewRangeChanged(self):
def _fourierTransform(self, x, y):
## Perform fourier transform. If x values are not sampled uniformly,
- ## then use interpolate.griddata to resample before taking fft.
+ ## then use np.interp to resample before taking fft.
dx = np.diff(x)
uniform = not np.any(np.abs(dx-dx[0]) > (abs(dx[0]) / 1000.))
if not uniform:
- import scipy.interpolate as interp
x2 = np.linspace(x[0], x[-1], len(x))
- y = interp.griddata(x, y, x2, method='linear')
+ y = np.interp(x2, x, y)
x = x2
f = np.fft.fft(y) / len(y)
y = abs(f[1:len(f)/2])
diff --git a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/papi/pyqtgraph/graphicsItems/PlotItem/PlotItem.py
similarity index 92%
rename from pyqtgraph/graphicsItems/PlotItem/PlotItem.py
rename to papi/pyqtgraph/graphicsItems/PlotItem/PlotItem.py
index ec0960ba..4f10b0e3 100644
--- a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py
+++ b/papi/pyqtgraph/graphicsItems/PlotItem/PlotItem.py
@@ -16,16 +16,17 @@
- Control panel with a huge feature set including averaging, decimation,
display, power spectrum, svg/png export, plot linking, and more.
"""
-from pyqtgraph.Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
-import pyqtgraph.pixmaps
+from ...Qt import QtGui, QtCore, QtSvg, USE_PYSIDE
+from ... import pixmaps
+import sys
if USE_PYSIDE:
from .plotConfigTemplate_pyside import *
else:
from .plotConfigTemplate_pyqt import *
-import pyqtgraph.functions as fn
-from pyqtgraph.widgets.FileDialog import FileDialog
+from ... import functions as fn
+from ...widgets.FileDialog import FileDialog
import weakref
import numpy as np
import os
@@ -37,7 +38,7 @@
from .. GraphicsWidget import GraphicsWidget
from .. ButtonItem import ButtonItem
from .. InfiniteLine import InfiniteLine
-from pyqtgraph.WidgetGroup import WidgetGroup
+from ...WidgetGroup import WidgetGroup
__all__ = ['PlotItem']
@@ -69,6 +70,7 @@ class PlotItem(GraphicsWidget):
:func:`setYLink `,
:func:`setAutoPan `,
:func:`setAutoVisible `,
+ :func:`setLimits `,
:func:`viewRect `,
:func:`viewRange `,
:func:`setMouseEnabled `,
@@ -76,13 +78,14 @@ class PlotItem(GraphicsWidget):
:func:`disableAutoRange `,
:func:`setAspectLocked `,
:func:`invertY `,
+ :func:`invertX `,
:func:`register `,
:func:`unregister `
The ViewBox itself can be accessed by calling :func:`getViewBox() `
==================== =======================================================================
- **Signals**
+ **Signals:**
sigYRangeChanged wrapped from :class:`ViewBox `
sigXRangeChanged wrapped from :class:`ViewBox `
sigRangeChanged wrapped from :class:`ViewBox `
@@ -95,7 +98,6 @@ class PlotItem(GraphicsWidget):
lastFileDir = None
- managers = {}
def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None, axisItems=None, enableMenu=True, **kargs):
"""
@@ -103,7 +105,7 @@ def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None
Any extra keyword arguments are passed to PlotItem.plot().
============== ==========================================================================================
- **Arguments**
+ **Arguments:**
*title* Title to display at the top of the item. Html is allowed.
*labels* A dictionary specifying the axis labels to display::
@@ -129,7 +131,7 @@ def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None
path = os.path.dirname(__file__)
#self.autoImageFile = os.path.join(path, 'auto.png')
#self.lockImageFile = os.path.join(path, 'lock.png')
- self.autoBtn = ButtonItem(pyqtgraph.pixmaps.getPixmap('auto'), 14, self)
+ self.autoBtn = ButtonItem(pixmaps.getPixmap('auto'), 14, self)
self.autoBtn.mode = 'auto'
self.autoBtn.clicked.connect(self.autoBtnClicked)
#self.autoBtn.hide()
@@ -143,7 +145,7 @@ def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None
self.layout.setVerticalSpacing(0)
if viewBox is None:
- viewBox = ViewBox()
+ viewBox = ViewBox(parent=self)
self.vb = viewBox
self.vb.sigStateChanged.connect(self.viewStateChanged)
self.setMenuEnabled(enableMenu, enableMenu) ## en/disable plotitem and viewbox menus
@@ -166,14 +168,14 @@ def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None
axisItems = {}
self.axes = {}
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
- axis = axisItems.get(k, AxisItem(orientation=k))
+ axis = axisItems.get(k, AxisItem(orientation=k, parent=self))
axis.linkToView(self.vb)
self.axes[k] = {'item': axis, 'pos': pos}
self.layout.addItem(axis, *pos)
axis.setZValue(-1000)
axis.setFlag(axis.ItemNegativeZStacksBehindParent)
- self.titleLabel = LabelItem('', size='11pt')
+ self.titleLabel = LabelItem('', size='11pt', parent=self)
self.layout.addItem(self.titleLabel, 0, 1)
self.setTitle(None) ## hide
@@ -193,14 +195,6 @@ def __init__(self, parent=None, name=None, labels=None, title=None, viewBox=None
self.layout.setColumnStretchFactor(1, 100)
- ## Wrap a few methods from viewBox
- for m in [
- 'setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', 'setAutoVisible',
- 'setRange', 'autoRange', 'viewRect', 'viewRange', 'setMouseEnabled',
- 'enableAutoRange', 'disableAutoRange', 'setAspectLocked', 'invertY',
- 'register', 'unregister']: ## NOTE: If you update this list, please update the class docstring as well.
- setattr(self, m, getattr(self.vb, m))
-
self.items = []
self.curves = []
self.itemMeta = weakref.WeakKeyDictionary()
@@ -297,7 +291,26 @@ def implements(self, interface=None):
def getViewBox(self):
"""Return the :class:`ViewBox ` contained within."""
return self.vb
+
+
+ ## Wrap a few methods from viewBox.
+ #Important: don't use a settattr(m, getattr(self.vb, m)) as we'd be leaving the viebox alive
+ #because we had a reference to an instance method (creating wrapper methods at runtime instead).
+ for m in ['setXRange', 'setYRange', 'setXLink', 'setYLink', 'setAutoPan', # NOTE:
+ 'setAutoVisible', 'setRange', 'autoRange', 'viewRect', 'viewRange', # If you update this list, please
+ 'setMouseEnabled', 'setLimits', 'enableAutoRange', 'disableAutoRange', # update the class docstring
+ 'setAspectLocked', 'invertY', 'invertX', 'register', 'unregister']: # as well.
+
+ def _create_method(name):
+ def method(self, *args, **kwargs):
+ return getattr(self.vb, name)(*args, **kwargs)
+ method.__name__ = name
+ return method
+
+ locals()[m] = _create_method(m)
+
+ del _create_method
def setLogMode(self, x=None, y=None):
@@ -339,9 +352,8 @@ def showGrid(self, x=None, y=None, alpha=None):
self.ctrl.gridAlphaSlider.setValue(v)
#def paint(self, *args):
- #prof = debug.Profiler('PlotItem.paint', disabled=True)
+ #prof = debug.Profiler()
#QtGui.QGraphicsWidget.paint(self, *args)
- #prof.finish()
## bad idea.
#def __getattr__(self, attr): ## wrap ms
@@ -357,10 +369,8 @@ def close(self):
self.ctrlMenu.setParent(None)
self.ctrlMenu = None
- #self.ctrlBtn.setParent(None)
- #self.ctrlBtn = None
- #self.autoBtn.setParent(None)
- #self.autoBtn = None
+ self.autoBtn.setParent(None)
+ self.autoBtn = None
for k in self.axes:
i = self.axes[k]['item']
@@ -370,28 +380,6 @@ def close(self):
self.scene().removeItem(self.vb)
self.vb = None
- ## causes invalid index errors:
- #for i in range(self.layout.count()):
- #self.layout.removeAt(i)
-
- #for p in self.proxies:
- #try:
- #p.setWidget(None)
- #except RuntimeError:
- #break
- #self.scene().removeItem(p)
- #self.proxies = []
-
- #self.menuAction.releaseWidget(self.menuAction.defaultWidget())
- #self.menuAction.setParent(None)
- #self.menuAction = None
-
- #if self.manager is not None:
- #self.manager.sigWidgetListChanged.disconnect(self.updatePlotList)
- #self.manager.removeWidget(self.name)
- #else:
- #print "no manager"
-
def registerPlot(self, name): ## for backward compatibility
self.vb.register(name)
@@ -481,7 +469,8 @@ def addAvgCurve(self, curve):
### Average data together
(x, y) = curve.getData()
- if plot.yData is not None:
+ if plot.yData is not None and y.shape == plot.yData.shape:
+ # note that if shapes do not match, then the average resets.
newData = plot.yData * (n-1) / float(n) + y * 1.0 / float(n)
plot.setData(plot.xData, newData)
else:
@@ -515,7 +504,9 @@ def addItem(self, item, *args, **kargs):
if 'ignoreBounds' in kargs:
vbargs['ignoreBounds'] = kargs['ignoreBounds']
self.vb.addItem(item, *args, **vbargs)
+ name = None
if hasattr(item, 'implements') and item.implements('plotData'):
+ name = item.name()
self.dataItems.append(item)
#self.plotChanged()
@@ -548,7 +539,7 @@ def addItem(self, item, *args, **kargs):
#c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged)
#item.sigPlotChanged.connect(self.plotChanged)
#self.plotChanged()
- name = kargs.get('name', getattr(item, 'opts', {}).get('name', None))
+ #name = kargs.get('name', getattr(item, 'opts', {}).get('name', None))
if name is not None and hasattr(self, 'legend') and self.legend is not None:
self.legend.addItem(item, name=name)
@@ -952,18 +943,18 @@ def updateLogMode(self):
def setDownsampling(self, ds=None, auto=None, mode=None):
"""Change the default downsampling mode for all PlotDataItems managed by this plot.
- =========== =================================================================
- Arguments
- ds (int) Reduce visible plot samples by this factor, or
- (bool) To enable/disable downsampling without changing the value.
- auto (bool) If True, automatically pick *ds* based on visible range
- mode 'subsample': Downsample by taking the first of N samples.
- This method is fastest and least accurate.
- 'mean': Downsample by taking the mean of N samples.
- 'peak': Downsample by drawing a saw wave that follows the min
- and max of the original data. This method produces the best
- visual representation of the data but is slower.
- =========== =================================================================
+ =============== =================================================================
+ **Arguments:**
+ ds (int) Reduce visible plot samples by this factor, or
+ (bool) To enable/disable downsampling without changing the value.
+ auto (bool) If True, automatically pick *ds* based on visible range
+ mode 'subsample': Downsample by taking the first of N samples.
+ This method is fastest and least accurate.
+ 'mean': Downsample by taking the mean of N samples.
+ 'peak': Downsample by drawing a saw wave that follows the min
+ and max of the original data. This method produces the best
+ visual representation of the data but is slower.
+ =============== =================================================================
"""
if ds is not None:
if ds is False:
@@ -1134,15 +1125,15 @@ def setLabel(self, axis, text=None, units=None, unitPrefix=None, **args):
"""
Set the label for an axis. Basic HTML formatting is allowed.
- ============= =================================================================
- **Arguments**
- axis must be one of 'left', 'bottom', 'right', or 'top'
- text text to display along the axis. HTML allowed.
- units units to display after the title. If units are given,
- then an SI prefix will be automatically appended
- and the axis values will be scaled accordingly.
- (ie, use 'V' instead of 'mV'; 'm' will be added automatically)
- ============= =================================================================
+ ============== =================================================================
+ **Arguments:**
+ axis must be one of 'left', 'bottom', 'right', or 'top'
+ text text to display along the axis. HTML allowed.
+ units units to display after the title. If units are given,
+ then an SI prefix will be automatically appended
+ and the axis values will be scaled accordingly.
+ (ie, use 'V' instead of 'mV'; 'm' will be added automatically)
+ ============== =================================================================
"""
self.getAxis(axis).setLabel(text=text, units=units, **args)
self.showAxis(axis)
@@ -1217,10 +1208,13 @@ def showButtons(self):
self.updateButtons()
def updateButtons(self):
- if self._exportOpts is False and self.mouseHovering and not self.buttonsHidden and not all(self.vb.autoRangeEnabled()):
- self.autoBtn.show()
- else:
- self.autoBtn.hide()
+ try:
+ if self._exportOpts is False and self.mouseHovering and not self.buttonsHidden and not all(self.vb.autoRangeEnabled()):
+ self.autoBtn.show()
+ else:
+ self.autoBtn.hide()
+ except RuntimeError:
+ pass # this can happen if the plot has been deleted.
def _plotArray(self, arr, x=None, **kargs):
if arr.ndim != 1:
diff --git a/pyqtgraph/graphicsItems/PlotItem/__init__.py b/papi/pyqtgraph/graphicsItems/PlotItem/__init__.py
similarity index 100%
rename from pyqtgraph/graphicsItems/PlotItem/__init__.py
rename to papi/pyqtgraph/graphicsItems/PlotItem/__init__.py
diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui
similarity index 100%
rename from pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui
rename to papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui
diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py
similarity index 61%
rename from pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py
rename to papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py
index 5335ee76..e09c9978 100644
--- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py
+++ b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyqt.py
@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui'
#
-# Created: Mon Jul 1 23:21:08 2013
-# by: PyQt4 UI code generator 4.9.3
+# Created: Mon Dec 23 10:10:51 2013
+# by: PyQt4 UI code generator 4.10
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,16 @@
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
- _fromUtf8 = lambda s: s
+ def _fromUtf8(s):
+ return s
+
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
class Ui_Form(object):
def setupUi(self, Form):
@@ -139,35 +148,35 @@ def setupUi(self, Form):
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
- Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
- self.averageGroup.setToolTip(QtGui.QApplication.translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None, QtGui.QApplication.UnicodeUTF8))
- self.averageGroup.setTitle(QtGui.QApplication.translate("Form", "Average", None, QtGui.QApplication.UnicodeUTF8))
- self.clipToViewCheck.setToolTip(QtGui.QApplication.translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8))
- self.clipToViewCheck.setText(QtGui.QApplication.translate("Form", "Clip to View", None, QtGui.QApplication.UnicodeUTF8))
- self.maxTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8))
- self.maxTracesCheck.setText(QtGui.QApplication.translate("Form", "Max Traces:", None, QtGui.QApplication.UnicodeUTF8))
- self.downsampleCheck.setText(QtGui.QApplication.translate("Form", "Downsample", None, QtGui.QApplication.UnicodeUTF8))
- self.peakRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None, QtGui.QApplication.UnicodeUTF8))
- self.peakRadio.setText(QtGui.QApplication.translate("Form", "Peak", None, QtGui.QApplication.UnicodeUTF8))
- self.maxTracesSpin.setToolTip(QtGui.QApplication.translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None, QtGui.QApplication.UnicodeUTF8))
- self.forgetTracesCheck.setToolTip(QtGui.QApplication.translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None, QtGui.QApplication.UnicodeUTF8))
- self.forgetTracesCheck.setText(QtGui.QApplication.translate("Form", "Forget hidden traces", None, QtGui.QApplication.UnicodeUTF8))
- self.meanRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the mean of N samples.", None, QtGui.QApplication.UnicodeUTF8))
- self.meanRadio.setText(QtGui.QApplication.translate("Form", "Mean", None, QtGui.QApplication.UnicodeUTF8))
- self.subsampleRadio.setToolTip(QtGui.QApplication.translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None, QtGui.QApplication.UnicodeUTF8))
- self.subsampleRadio.setText(QtGui.QApplication.translate("Form", "Subsample", None, QtGui.QApplication.UnicodeUTF8))
- self.autoDownsampleCheck.setToolTip(QtGui.QApplication.translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None, QtGui.QApplication.UnicodeUTF8))
- self.autoDownsampleCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
- self.downsampleSpin.setToolTip(QtGui.QApplication.translate("Form", "Downsample data before plotting. (plot every Nth sample)", None, QtGui.QApplication.UnicodeUTF8))
- self.downsampleSpin.setSuffix(QtGui.QApplication.translate("Form", "x", None, QtGui.QApplication.UnicodeUTF8))
- self.fftCheck.setText(QtGui.QApplication.translate("Form", "Power Spectrum (FFT)", None, QtGui.QApplication.UnicodeUTF8))
- self.logXCheck.setText(QtGui.QApplication.translate("Form", "Log X", None, QtGui.QApplication.UnicodeUTF8))
- self.logYCheck.setText(QtGui.QApplication.translate("Form", "Log Y", None, QtGui.QApplication.UnicodeUTF8))
- self.pointsGroup.setTitle(QtGui.QApplication.translate("Form", "Points", None, QtGui.QApplication.UnicodeUTF8))
- self.autoPointsCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
- self.xGridCheck.setText(QtGui.QApplication.translate("Form", "Show X Grid", None, QtGui.QApplication.UnicodeUTF8))
- self.yGridCheck.setText(QtGui.QApplication.translate("Form", "Show Y Grid", None, QtGui.QApplication.UnicodeUTF8))
- self.label.setText(QtGui.QApplication.translate("Form", "Opacity", None, QtGui.QApplication.UnicodeUTF8))
- self.alphaGroup.setTitle(QtGui.QApplication.translate("Form", "Alpha", None, QtGui.QApplication.UnicodeUTF8))
- self.autoAlphaCheck.setText(QtGui.QApplication.translate("Form", "Auto", None, QtGui.QApplication.UnicodeUTF8))
+ Form.setWindowTitle(_translate("Form", "Form", None))
+ self.averageGroup.setToolTip(_translate("Form", "Display averages of the curves displayed in this plot. The parameter list allows you to choose parameters to average over (if any are available).", None))
+ self.averageGroup.setTitle(_translate("Form", "Average", None))
+ self.clipToViewCheck.setToolTip(_translate("Form", "Plot only the portion of each curve that is visible. This assumes X values are uniformly spaced.", None))
+ self.clipToViewCheck.setText(_translate("Form", "Clip to View", None))
+ self.maxTracesCheck.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check this box to limit the number of traces that are displayed.", None))
+ self.maxTracesCheck.setText(_translate("Form", "Max Traces:", None))
+ self.downsampleCheck.setText(_translate("Form", "Downsample", None))
+ self.peakRadio.setToolTip(_translate("Form", "Downsample by drawing a saw wave that follows the min and max of the original data. This method produces the best visual representation of the data but is slower.", None))
+ self.peakRadio.setText(_translate("Form", "Peak", None))
+ self.maxTracesSpin.setToolTip(_translate("Form", "If multiple curves are displayed in this plot, check \"Max Traces\" and set this value to limit the number of traces that are displayed.", None))
+ self.forgetTracesCheck.setToolTip(_translate("Form", "If MaxTraces is checked, remove curves from memory after they are hidden (saves memory, but traces can not be un-hidden).", None))
+ self.forgetTracesCheck.setText(_translate("Form", "Forget hidden traces", None))
+ self.meanRadio.setToolTip(_translate("Form", "Downsample by taking the mean of N samples.", None))
+ self.meanRadio.setText(_translate("Form", "Mean", None))
+ self.subsampleRadio.setToolTip(_translate("Form", "Downsample by taking the first of N samples. This method is fastest and least accurate.", None))
+ self.subsampleRadio.setText(_translate("Form", "Subsample", None))
+ self.autoDownsampleCheck.setToolTip(_translate("Form", "Automatically downsample data based on the visible range. This assumes X values are uniformly spaced.", None))
+ self.autoDownsampleCheck.setText(_translate("Form", "Auto", None))
+ self.downsampleSpin.setToolTip(_translate("Form", "Downsample data before plotting. (plot every Nth sample)", None))
+ self.downsampleSpin.setSuffix(_translate("Form", "x", None))
+ self.fftCheck.setText(_translate("Form", "Power Spectrum (FFT)", None))
+ self.logXCheck.setText(_translate("Form", "Log X", None))
+ self.logYCheck.setText(_translate("Form", "Log Y", None))
+ self.pointsGroup.setTitle(_translate("Form", "Points", None))
+ self.autoPointsCheck.setText(_translate("Form", "Auto", None))
+ self.xGridCheck.setText(_translate("Form", "Show X Grid", None))
+ self.yGridCheck.setText(_translate("Form", "Show Y Grid", None))
+ self.label.setText(_translate("Form", "Opacity", None))
+ self.alphaGroup.setTitle(_translate("Form", "Alpha", None))
+ self.autoAlphaCheck.setText(_translate("Form", "Auto", None))
diff --git a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py
similarity index 99%
rename from pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py
rename to papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py
index b8e0b19e..aff31211 100644
--- a/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py
+++ b/papi/pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate_pyside.py
@@ -2,8 +2,8 @@
# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/PlotItem/plotConfigTemplate.ui'
#
-# Created: Mon Jul 1 23:21:08 2013
-# by: pyside-uic 0.2.13 running on PySide 1.1.2
+# Created: Mon Dec 23 10:10:52 2013
+# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
diff --git a/pyqtgraph/graphicsItems/ROI.py b/papi/pyqtgraph/graphicsItems/ROI.py
similarity index 72%
rename from pyqtgraph/graphicsItems/ROI.py
rename to papi/pyqtgraph/graphicsItems/ROI.py
index f6ce4680..7707466a 100644
--- a/pyqtgraph/graphicsItems/ROI.py
+++ b/papi/pyqtgraph/graphicsItems/ROI.py
@@ -12,23 +12,20 @@
of how to build an ROI at the bottom of the file.
"""
-from pyqtgraph.Qt import QtCore, QtGui
-#if not hasattr(QtCore, 'Signal'):
- #QtCore.Signal = QtCore.pyqtSignal
+from ..Qt import QtCore, QtGui
import numpy as np
-from numpy.linalg import norm
-import scipy.ndimage as ndimage
-from pyqtgraph.Point import *
-from pyqtgraph.SRTTransform import SRTTransform
+#from numpy.linalg import norm
+from ..Point import *
+from ..SRTTransform import SRTTransform
from math import cos, sin
-import pyqtgraph.functions as fn
+from .. import functions as fn
from .GraphicsObject import GraphicsObject
from .UIGraphicsItem import UIGraphicsItem
__all__ = [
'ROI',
'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI',
- 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI',
+ 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI', 'CrosshairROI',
]
@@ -36,11 +33,56 @@ def rectStr(r):
return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height())
class ROI(GraphicsObject):
- """Generic region-of-interest widget.
- Can be used for implementing many types of selection box with rotate/translate/scale handles.
+ """
+ Generic region-of-interest widget.
+
+ Can be used for implementing many types of selection box with
+ rotate/translate/scale handles.
+ ROIs can be customized to have a variety of shapes (by subclassing or using
+ any of the built-in subclasses) and any combination of draggable handles
+ that allow the user to manipulate the ROI.
+
+
+
+ ================ ===========================================================
+ **Arguments**
+ pos (length-2 sequence) Indicates the position of the ROI's
+ origin. For most ROIs, this is the lower-left corner of
+ its bounding rectangle.
+ size (length-2 sequence) Indicates the width and height of the
+ ROI.
+ angle (float) The rotation of the ROI in degrees. Default is 0.
+ invertible (bool) If True, the user may resize the ROI to have
+ negative width or height (assuming the ROI has scale
+ handles). Default is False.
+ maxBounds (QRect, QRectF, or None) Specifies boundaries that the ROI
+ cannot be dragged outside of by the user. Default is None.
+ snapSize (float) The spacing of snap positions used when *scaleSnap*
+ or *translateSnap* are enabled. Default is 1.0.
+ scaleSnap (bool) If True, the width and height of the ROI are forced
+ to be integer multiples of *snapSize* when being resized
+ by the user. Default is False.
+ translateSnap (bool) If True, the x and y positions of the ROI are forced
+ to be integer multiples of *snapSize* when being resized
+ by the user. Default is False.
+ rotateSnap (bool) If True, the ROI angle is forced to a multiple of
+ 15 degrees when rotated by the user. Default is False.
+ parent (QGraphicsItem) The graphics item parent of this ROI. It
+ is generally not necessary to specify the parent.
+ pen (QPen or argument to pg.mkPen) The pen to use when drawing
+ the shape of the ROI.
+ movable (bool) If True, the ROI can be moved by dragging anywhere
+ inside the ROI. Default is True.
+ removable (bool) If True, the ROI will be given a context menu with
+ an option to remove the ROI. The ROI emits
+ sigRemoveRequested when this menu action is selected.
+ Default is False.
+ ================ ===========================================================
+
- Signals
- ----------------------- ----------------------------------------------------
+
+ ======================= ====================================================
+ **Signals**
sigRegionChangeFinished Emitted when the user stops dragging the ROI (or
one of its handles) or if the ROI is changed
programatically.
@@ -58,7 +100,7 @@ class ROI(GraphicsObject):
details.
sigRemoveRequested Emitted when the user selects 'remove' from the
ROI's context menu (if available).
- ----------------------- ----------------------------------------------------
+ ======================= ====================================================
"""
sigRegionChangeFinished = QtCore.Signal(object)
@@ -117,7 +159,11 @@ def stateCopy(self):
return sc
def saveState(self):
- """Return the state of the widget in a format suitable for storing to disk. (Points are converted to tuple)"""
+ """Return the state of the widget in a format suitable for storing to
+ disk. (Points are converted to tuple)
+
+ Combined with setState(), this allows ROIs to be easily saved and
+ restored."""
state = {}
state['pos'] = tuple(self.state['pos'])
state['size'] = tuple(self.state['size'])
@@ -125,6 +171,10 @@ def saveState(self):
return state
def setState(self, state, update=True):
+ """
+ Set the state of the ROI from a structure generated by saveState() or
+ getState().
+ """
self.setPos(state['pos'], update=False)
self.setSize(state['size'], update=False)
self.setAngle(state['angle'], update=update)
@@ -135,20 +185,32 @@ def setZValue(self, z):
h['item'].setZValue(z+1)
def parentBounds(self):
+ """
+ Return the bounding rectangle of this ROI in the coordinate system
+ of its parent.
+ """
return self.mapToParent(self.boundingRect()).boundingRect()
- def setPen(self, pen):
- self.pen = fn.mkPen(pen)
+ def setPen(self, *args, **kwargs):
+ """
+ Set the pen to use when drawing the ROI shape.
+ For arguments, see :func:`mkPen `.
+ """
+ self.pen = fn.mkPen(*args, **kwargs)
self.currentPen = self.pen
self.update()
def size(self):
+ """Return the size (w,h) of the ROI."""
return self.getState()['size']
def pos(self):
+ """Return the position (x,y) of the ROI's origin.
+ For most ROIs, this will be the lower-left corner."""
return self.getState()['pos']
def angle(self):
+ """Return the angle of the ROI in degrees."""
return self.getState()['angle']
def setPos(self, pos, update=True, finish=True):
@@ -214,11 +276,14 @@ def translate(self, *args, **kargs):
If the ROI is bounded and the move would exceed boundaries, then the ROI
is moved to the nearest acceptable position instead.
- snap can be:
- None (default): use self.translateSnap and self.snapSize to determine whether/how to snap
- False: do not snap
- Point(w,h) snap to rectangular grid with spacing (w,h)
- True: snap using self.snapSize (and ignoring self.translateSnap)
+ *snap* can be:
+
+ =============== ==========================================================================
+ None (default) use self.translateSnap and self.snapSize to determine whether/how to snap
+ False do not snap
+ Point(w,h) snap to rectangular grid with spacing (w,h)
+ True snap using self.snapSize (and ignoring self.translateSnap)
+ =============== ==========================================================================
Also accepts *update* and *finish* arguments (see setPos() for a description of these).
"""
@@ -264,21 +329,86 @@ def translate(self, *args, **kargs):
#self.stateChanged()
def rotate(self, angle, update=True, finish=True):
+ """
+ Rotate the ROI by *angle* degrees.
+
+ Also accepts *update* and *finish* arguments (see setPos() for a
+ description of these).
+ """
self.setAngle(self.angle()+angle, update=update, finish=finish)
def handleMoveStarted(self):
self.preMoveState = self.getState()
def addTranslateHandle(self, pos, axes=None, item=None, name=None, index=None):
+ """
+ Add a new translation handle to the ROI. Dragging the handle will move
+ the entire ROI without changing its angle or shape.
+
+ Note that, by default, ROIs may be moved by dragging anywhere inside the
+ ROI. However, for larger ROIs it may be desirable to disable this and
+ instead provide one or more translation handles.
+
+ =================== ====================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the handle
+ relative to the shape of the ROI. A value of (0,0)
+ indicates the origin, whereas (1, 1) indicates the
+ upper-right corner, regardless of the ROI's size.
+ item The Handle instance to add. If None, a new handle
+ will be created.
+ name The name of this handle (optional). Handles are
+ identified by name when calling
+ getLocalHandlePositions and getSceneHandlePositions.
+ =================== ====================================================
+ """
pos = Point(pos)
return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}, index=index)
def addFreeHandle(self, pos=None, axes=None, item=None, name=None, index=None):
+ """
+ Add a new free handle to the ROI. Dragging free handles has no effect
+ on the position or shape of the ROI.
+
+ =================== ====================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the handle
+ relative to the shape of the ROI. A value of (0,0)
+ indicates the origin, whereas (1, 1) indicates the
+ upper-right corner, regardless of the ROI's size.
+ item The Handle instance to add. If None, a new handle
+ will be created.
+ name The name of this handle (optional). Handles are
+ identified by name when calling
+ getLocalHandlePositions and getSceneHandlePositions.
+ =================== ====================================================
+ """
if pos is not None:
pos = Point(pos)
return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}, index=index)
def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False, index=None):
+ """
+ Add a new scale handle to the ROI. Dragging a scale handle allows the
+ user to change the height and/or width of the ROI.
+
+ =================== ====================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the handle
+ relative to the shape of the ROI. A value of (0,0)
+ indicates the origin, whereas (1, 1) indicates the
+ upper-right corner, regardless of the ROI's size.
+ center (length-2 sequence) The center point around which
+ scaling takes place. If the center point has the
+ same x or y value as the handle position, then
+ scaling will be disabled for that axis.
+ item The Handle instance to add. If None, a new handle
+ will be created.
+ name The name of this handle (optional). Handles are
+ identified by name when calling
+ getLocalHandlePositions and getSceneHandlePositions.
+ =================== ====================================================
+ """
pos = Point(pos)
center = Point(center)
info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect}
@@ -289,11 +419,51 @@ def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspec
return self.addHandle(info, index=index)
def addRotateHandle(self, pos, center, item=None, name=None, index=None):
+ """
+ Add a new rotation handle to the ROI. Dragging a rotation handle allows
+ the user to change the angle of the ROI.
+
+ =================== ====================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the handle
+ relative to the shape of the ROI. A value of (0,0)
+ indicates the origin, whereas (1, 1) indicates the
+ upper-right corner, regardless of the ROI's size.
+ center (length-2 sequence) The center point around which
+ rotation takes place.
+ item The Handle instance to add. If None, a new handle
+ will be created.
+ name The name of this handle (optional). Handles are
+ identified by name when calling
+ getLocalHandlePositions and getSceneHandlePositions.
+ =================== ====================================================
+ """
pos = Point(pos)
center = Point(center)
return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}, index=index)
def addScaleRotateHandle(self, pos, center, item=None, name=None, index=None):
+ """
+ Add a new scale+rotation handle to the ROI. When dragging a handle of
+ this type, the user can simultaneously rotate the ROI around an
+ arbitrary center point as well as scale the ROI by dragging the handle
+ toward or away from the center point.
+
+ =================== ====================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the handle
+ relative to the shape of the ROI. A value of (0,0)
+ indicates the origin, whereas (1, 1) indicates the
+ upper-right corner, regardless of the ROI's size.
+ center (length-2 sequence) The center point around which
+ scaling and rotation take place.
+ item The Handle instance to add. If None, a new handle
+ will be created.
+ name The name of this handle (optional). Handles are
+ identified by name when calling
+ getLocalHandlePositions and getSceneHandlePositions.
+ =================== ====================================================
+ """
pos = Point(pos)
center = Point(center)
if pos[0] != center[0] and pos[1] != center[1]:
@@ -301,6 +471,27 @@ def addScaleRotateHandle(self, pos, center, item=None, name=None, index=None):
return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}, index=index)
def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None, index=None):
+ """
+ Add a new rotation+free handle to the ROI. When dragging a handle of
+ this type, the user can rotate the ROI around an
+ arbitrary center point, while moving toward or away from the center
+ point has no effect on the shape of the ROI.
+
+ =================== ====================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the handle
+ relative to the shape of the ROI. A value of (0,0)
+ indicates the origin, whereas (1, 1) indicates the
+ upper-right corner, regardless of the ROI's size.
+ center (length-2 sequence) The center point around which
+ rotation takes place.
+ item The Handle instance to add. If None, a new handle
+ will be created.
+ name The name of this handle (optional). Handles are
+ identified by name when calling
+ getLocalHandlePositions and getSceneHandlePositions.
+ =================== ====================================================
+ """
pos = Point(pos)
center = Point(center)
return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}, index=index)
@@ -329,6 +520,9 @@ def addHandle(self, info, index=None):
return h
def indexOfHandle(self, handle):
+ """
+ Return the index of *handle* in the list of this ROI's handles.
+ """
if isinstance(handle, Handle):
index = [i for i, info in enumerate(self.handles) if info['item'] is handle]
if len(index) == 0:
@@ -338,7 +532,8 @@ def indexOfHandle(self, handle):
return handle
def removeHandle(self, handle):
- """Remove a handle from this ROI. Argument may be either a Handle instance or the integer index of the handle."""
+ """Remove a handle from this ROI. Argument may be either a Handle
+ instance or the integer index of the handle."""
index = self.indexOfHandle(handle)
handle = self.handles[index]['item']
@@ -349,20 +544,17 @@ def removeHandle(self, handle):
self.stateChanged()
def replaceHandle(self, oldHandle, newHandle):
- """Replace one handle in the ROI for another. This is useful when connecting multiple ROIs together.
- *oldHandle* may be a Handle instance or the index of a handle."""
- #print "========================="
- #print "replace", oldHandle, newHandle
- #print self
- #print self.handles
- #print "-----------------"
+ """Replace one handle in the ROI for another. This is useful when
+ connecting multiple ROIs together.
+
+ *oldHandle* may be a Handle instance or the index of a handle to be
+ replaced."""
index = self.indexOfHandle(oldHandle)
info = self.handles[index]
self.removeHandle(index)
info['item'] = newHandle
info['pos'] = newHandle.pos()
self.addHandle(info, index=index)
- #print self.handles
def checkRemoveHandle(self, handle):
## This is used when displaying a Handle's context menu to determine
@@ -373,7 +565,10 @@ def checkRemoveHandle(self, handle):
def getLocalHandlePositions(self, index=None):
- """Returns the position of a handle in ROI coordinates"""
+ """Returns the position of handles in the ROI's coordinate system.
+
+ The format returned is a list of (name, pos) tuples.
+ """
if index == None:
positions = []
for h in self.handles:
@@ -383,6 +578,10 @@ def getLocalHandlePositions(self, index=None):
return (self.handles[index]['name'], self.handles[index]['pos'])
def getSceneHandlePositions(self, index=None):
+ """Returns the position of handles in the scene coordinate system.
+
+ The format returned is a list of (name, pos) tuples.
+ """
if index == None:
positions = []
for h in self.handles:
@@ -392,6 +591,9 @@ def getSceneHandlePositions(self, index=None):
return (self.handles[index]['name'], self.handles[index]['item'].scenePos())
def getHandles(self):
+ """
+ Return a list of this ROI's Handles.
+ """
return [h['item'] for h in self.handles]
def mapSceneToParent(self, pt):
@@ -463,12 +665,8 @@ def getMenu(self):
def removeClicked(self):
## Send remove event only after we have exited the menu event handler
- self.removeTimer = QtCore.QTimer()
- self.removeTimer.timeout.connect(lambda: self.sigRemoveRequested.emit(self))
- self.removeTimer.start(0)
+ QtCore.QTimer.singleShot(0, lambda: self.sigRemoveRequested.emit(self))
-
-
def mouseDragEvent(self, ev):
if ev.isStart():
#p = ev.pos()
@@ -510,56 +708,16 @@ def mouseClickEvent(self, ev):
self.sigClicked.emit(self, ev)
else:
ev.ignore()
-
-
-
def cancelMove(self):
self.isMoving = False
self.setState(self.preMoveState)
-
- #def pointDragEvent(self, pt, ev):
- ### just for handling drag start/stop.
- ### drag moves are handled through movePoint()
-
- #if ev.isStart():
- #self.isMoving = True
- #self.preMoveState = self.getState()
-
- #self.sigRegionChangeStarted.emit(self)
- #elif ev.isFinish():
- #self.isMoving = False
- #self.sigRegionChangeFinished.emit(self)
- #return
-
-
- #def pointPressEvent(self, pt, ev):
- ##print "press"
- #self.isMoving = True
- #self.preMoveState = self.getState()
-
- ##self.emit(QtCore.SIGNAL('regionChangeStarted'), self)
- #self.sigRegionChangeStarted.emit(self)
- ##self.pressPos = self.mapFromScene(ev.scenePos())
- ##self.pressHandlePos = self.handles[pt]['item'].pos()
-
- #def pointReleaseEvent(self, pt, ev):
- ##print "release"
- #self.isMoving = False
- ##self.emit(QtCore.SIGNAL('regionChangeFinished'), self)
- #self.sigRegionChangeFinished.emit(self)
-
- #def pointMoveEvent(self, pt, ev):
- #self.movePoint(pt, ev.scenePos(), ev.modifiers())
-
-
def checkPointMove(self, handle, pos, modifiers):
"""When handles move, they must ask the ROI if the move is acceptable.
By default, this always returns True. Subclasses may wish override.
"""
return True
-
def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True, coords='parent'):
## called by Handles when they are moved.
@@ -664,7 +822,10 @@ def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=
if not self.rotateAllowed:
return
## If the handle is directly over its center point, we can't compute an angle.
- if lp1.length() == 0 or lp0.length() == 0:
+ try:
+ if lp1.length() == 0 or lp0.length() == 0:
+ return
+ except OverflowError:
return
## determine new rotation angle, constrained if necessary
@@ -701,10 +862,15 @@ def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=
elif h['type'] == 'sr':
if h['center'][0] == h['pos'][0]:
scaleAxis = 1
+ nonScaleAxis=0
else:
scaleAxis = 0
+ nonScaleAxis=1
- if lp1.length() == 0 or lp0.length() == 0:
+ try:
+ if lp1.length() == 0 or lp0.length() == 0:
+ return
+ except OverflowError:
return
ang = newState['angle'] - lp0.angle(lp1)
@@ -721,6 +887,8 @@ def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=
newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize
if newState['size'][scaleAxis] == 0:
newState['size'][scaleAxis] = 1
+ if self.aspectLocked:
+ newState['size'][nonScaleAxis] = newState['size'][scaleAxis]
c1 = c * newState['size']
tr = QtGui.QTransform()
@@ -804,19 +972,20 @@ def getSnapPosition(self, pos, snap=None):
round(pos[1] / snap[1]) * snap[1]
)
-
def boundingRect(self):
return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized()
def paint(self, p, opt, widget):
- p.save()
- r = self.boundingRect()
+ # p.save()
+ # Note: don't use self.boundingRect here, because subclasses may need to redefine it.
+ r = QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized()
+
p.setRenderHint(QtGui.QPainter.Antialiasing)
p.setPen(self.currentPen)
p.translate(r.left(), r.top())
p.scale(r.width(), r.height())
p.drawRect(0, 0, 1, 1)
- p.restore()
+ # p.restore()
def getArraySlice(self, data, img, axes=(0,1), returnSlice=True):
"""Return a tuple of slice objects that can be used to slice the region from data covered by this ROI.
@@ -871,7 +1040,25 @@ def getArraySlice(self, data, img, axes=(0,1), returnSlice=True):
return bounds, tr
def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds):
- """Use the position and orientation of this ROI relative to an imageItem to pull a slice from an array.
+ """Use the position and orientation of this ROI relative to an imageItem
+ to pull a slice from an array.
+
+ =================== ====================================================
+ **Arguments**
+ data The array to slice from. Note that this array does
+ *not* have to be the same data that is represented
+ in *img*.
+ img (ImageItem or other suitable QGraphicsItem)
+ Used to determine the relationship between the
+ ROI and the boundaries of *data*.
+ axes (length-2 tuple) Specifies the axes in *data* that
+ correspond to the x and y axes of *img*.
+ returnMappedCoords (bool) If True, the array slice is returned along
+ with a corresponding array of coordinates that were
+ used to extract data from the original array.
+ \**kwds All keyword arguments are passed to
+ :func:`affineSlice `.
+ =================== ====================================================
This method uses :func:`affineSlice ` to generate
the slice from *data* and uses :func:`getAffineSliceParams ` to determine the parameters to
@@ -905,105 +1092,6 @@ def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds
#mapped += translate.reshape((2,1,1))
mapped = fn.transformCoordinates(img.transform(), coords)
return result, mapped
-
-
- ### transpose data so x and y are the first 2 axes
- #trAx = range(0, data.ndim)
- #trAx.remove(axes[0])
- #trAx.remove(axes[1])
- #tr1 = tuple(axes) + tuple(trAx)
- #arr = data.transpose(tr1)
-
- ### Determine the minimal area of the data we will need
- #(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes)
-
- ### Pad data boundaries by 1px if possible
- #dataBounds = (
- #(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])),
- #(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1]))
- #)
-
- ### Extract minimal data from array
- #arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]]
-
- ### Update roiDataTransform to reflect this extraction
- #roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0])
- #### (roiDataTransform now maps from ROI coords to extracted data coords)
-
-
- ### Rotate array
- #if abs(self.state['angle']) > 1e-5:
- #arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1)
-
- ### update data transforms to reflect this rotation
- #rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi)
- #roiDataTransform *= rot
-
- ### The rotation also causes a shift which must be accounted for:
- #dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1])
- #rotBound = rot.mapRect(dataBound)
- #roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top())
-
- #else:
- #arr2 = arr1
-
-
-
- #### Shift off partial pixels
- ## 1. map ROI into current data space
- #roiBounds = roiDataTransform.mapRect(self.boundingRect())
-
- ## 2. Determine amount to shift data
- #shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom())
- #if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6:
- ## 3. pad array with 0s before shifting
- #arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype)
- #arr2a[1:-1, 1:-1] = arr2
-
- ## 4. shift array and udpate transforms
- #arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1)
- #roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1])
- #else:
- #arr3 = arr2
-
-
- #### Extract needed region from rotated/shifted array
- ## 1. map ROI into current data space (round these values off--they should be exact integer values at this point)
- #roiBounds = roiDataTransform.mapRect(self.boundingRect())
- ##print self, roiBounds.height()
- ##import traceback
- ##traceback.print_stack()
-
- #roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height()))
-
- ##2. intersect ROI with data bounds
- #dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1]))
-
- ##3. Extract data from array
- #db = dataBounds
- #bounds = (
- #(db.left(), db.right()+1),
- #(db.top(), db.bottom()+1)
- #)
- #arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]]
-
- #### Create zero array in size of ROI
- #arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype)
-
- ### Fill array with ROI data
- #orig = Point(dataBounds.topLeft() - roiBounds.topLeft())
- #subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]]
- #subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]]
-
-
- ### figure out the reverse transpose order
- #tr2 = np.array(tr1)
- #for i in range(0, len(tr2)):
- #tr2[tr1[i]] = i
- #tr2 = tuple(tr2)
-
- ### Untranspose array before returning
- #return arr5.transpose(tr2)
def getAffineSliceParams(self, data, img, axes=(0,1)):
"""
@@ -1088,7 +1176,18 @@ def applyGlobalTransform(self, tr):
class Handle(UIGraphicsItem):
+ """
+ Handle represents a single user-interactable point attached to an ROI. They
+ are usually created by a call to one of the ROI.add___Handle() methods.
+
+ Handles are represented as a square, diamond, or circle, and are drawn with
+ fixed pixel size regardless of the scaling of the view they are displayed in.
+
+ Handles may be dragged to change the position, size, orientation, or other
+ properties of the ROI they are attached to.
+
+ """
types = { ## defines number of sides, start angle for each handle type
't': (4, np.pi/4),
'f': (4, np.pi/4),
@@ -1202,11 +1301,7 @@ def buildMenu(self):
def getMenu(self):
return self.menu
-
-
- def getContextMenus(self, event):
- return [self.menu]
-
+
def raiseContextMenu(self, ev):
menu = self.scene().addParentContextMenus(self, self.getMenu(), ev)
@@ -1364,6 +1459,22 @@ def __init__(self, pos, size, **args):
class RectROI(ROI):
+ """
+ Rectangular ROI subclass with a single scale handle at the top-right corner.
+
+ ============== =============================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the ROI origin.
+ See ROI().
+ size (length-2 sequence) The size of the ROI. See ROI().
+ centered (bool) If True, scale handles affect the ROI relative to its
+ center, rather than its origin.
+ sideScalers (bool) If True, extra scale handles are added at the top and
+ right edges.
+ \**args All extra keyword arguments are passed to ROI()
+ ============== =============================================================
+
+ """
def __init__(self, pos, size, centered=False, sideScalers=False, **args):
#QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
ROI.__init__(self, pos, size, **args)
@@ -1379,6 +1490,22 @@ def __init__(self, pos, size, centered=False, sideScalers=False, **args):
self.addScaleHandle([0.5, 1], [0.5, center[1]])
class LineROI(ROI):
+ """
+ Rectangular ROI subclass with scale-rotate handles on either side. This
+ allows the ROI to be positioned as if moving the ends of a line segment.
+ A third handle controls the width of the ROI orthogonal to its "line" axis.
+
+ ============== =============================================================
+ **Arguments**
+ pos1 (length-2 sequence) The position of the center of the ROI's
+ left edge.
+ pos2 (length-2 sequence) The position of the center of the ROI's
+ right edge.
+ width (float) The width of the ROI.
+ \**args All extra keyword arguments are passed to ROI()
+ ============== =============================================================
+
+ """
def __init__(self, pos1, pos2, width, **args):
pos1 = Point(pos1)
pos2 = Point(pos2)
@@ -1403,6 +1530,13 @@ class MultiRectROI(QtGui.QGraphicsObject):
This is generally used to mark a curved path through
an image similarly to PolyLineROI. It differs in that each segment
of the chain is rectangular instead of linear and thus has width.
+
+ ============== =============================================================
+ **Arguments**
+ points (list of length-2 sequences) The list of points in the path.
+ width (float) The width of the ROIs orthogonal to the path.
+ \**args All extra keyword arguments are passed to ROI()
+ ============== =============================================================
"""
sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChangeStarted = QtCore.Signal(object)
@@ -1527,6 +1661,18 @@ def __init__(self, *args, **kwds):
print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)")
class EllipseROI(ROI):
+ """
+ Elliptical ROI subclass with one scale handle and one rotation handle.
+
+
+ ============== =============================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the ROI's origin.
+ size (length-2 sequence) The size of the ROI's bounding rectangle.
+ \**args All extra keyword arguments are passed to ROI()
+ ============== =============================================================
+
+ """
def __init__(self, pos, size, **args):
#QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1])
ROI.__init__(self, pos, size, **args)
@@ -1544,6 +1690,10 @@ def paint(self, p, opt, widget):
p.drawEllipse(r)
def getArrayRegion(self, arr, img=None):
+ """
+ Return the result of ROI.getArrayRegion() masked by the elliptical shape
+ of the ROI. Regions outside the ellipse are set to 0.
+ """
arr = ROI.getArrayRegion(self, arr, img)
if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0:
return None
@@ -1561,12 +1711,25 @@ def shape(self):
class CircleROI(EllipseROI):
+ """
+ Circular ROI subclass. Behaves exactly as EllipseROI, but may only be scaled
+ proportionally to maintain its aspect ratio.
+
+ ============== =============================================================
+ **Arguments**
+ pos (length-2 sequence) The position of the ROI's origin.
+ size (length-2 sequence) The size of the ROI's bounding rectangle.
+ \**args All extra keyword arguments are passed to ROI()
+ ============== =============================================================
+
+ """
def __init__(self, pos, size, **args):
ROI.__init__(self, pos, size, **args)
self.aspectLocked = True
#self.addTranslateHandle([0.5, 0.5])
self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5])
-
+
+
class PolygonROI(ROI):
## deprecated. Use PloyLineROI instead.
@@ -1620,24 +1783,83 @@ def stateCopy(self):
return sc
class PolyLineROI(ROI):
- """Container class for multiple connected LineSegmentROIs. Responsible for adding new
- line segments, and for translation/(rotation?) of multiple lines together."""
+ """
+ Container class for multiple connected LineSegmentROIs.
+
+ This class allows the user to draw paths of multiple line segments.
+
+ ============== =============================================================
+ **Arguments**
+ positions (list of length-2 sequences) The list of points in the path.
+ Note that, unlike the handle positions specified in other
+ ROIs, these positions must be expressed in the normal
+ coordinate system of the ROI, rather than (0 to 1) relative
+ to the size of the ROI.
+ closed (bool) if True, an extra LineSegmentROI is added connecting
+ the beginning and end points.
+ \**args All extra keyword arguments are passed to ROI()
+ ============== =============================================================
+
+ """
def __init__(self, positions, closed=False, pos=None, **args):
if pos is None:
pos = [0,0]
- ROI.__init__(self, pos, size=[1,1], **args)
self.closed = closed
self.segments = []
+ ROI.__init__(self, pos, size=[1,1], **args)
- for p in positions:
- self.addFreeHandle(p)
+ self.setPoints(positions)
+ #for p in positions:
+ #self.addFreeHandle(p)
+ #start = -1 if self.closed else 0
+ #for i in range(start, len(self.handles)-1):
+ #self.addSegment(self.handles[i]['item'], self.handles[i+1]['item'])
+
+ def setPoints(self, points, closed=None):
+ """
+ Set the complete sequence of points displayed by this ROI.
+
+ ============= =========================================================
+ **Arguments**
+ points List of (x,y) tuples specifying handle locations to set.
+ closed If bool, then this will set whether the ROI is closed
+ (the last point is connected to the first point). If
+ None, then the closed mode is left unchanged.
+ ============= =========================================================
+
+ """
+ if closed is not None:
+ self.closed = closed
+
+ for p in points:
+ self.addFreeHandle(p)
+
start = -1 if self.closed else 0
for i in range(start, len(self.handles)-1):
self.addSegment(self.handles[i]['item'], self.handles[i+1]['item'])
+
+
+ def clearPoints(self):
+ """
+ Remove all handles and segments.
+ """
+ while len(self.handles) > 0:
+ self.removeHandle(self.handles[0]['item'])
+
+ def saveState(self):
+ state = ROI.saveState(self)
+ state['closed'] = self.closed
+ state['points'] = [tuple(h.pos()) for h in self.getHandles()]
+ return state
+ def setState(self, state):
+ ROI.setState(self, state)
+ self.clearPoints()
+ self.setPoints(state['points'], closed=state['closed'])
+
def addSegment(self, h1, h2, index=None):
seg = LineSegmentROI(handles=(h1, h2), pen=self.pen, parent=self, movable=False)
if index is None:
@@ -1734,6 +1956,10 @@ def shape(self):
return p
def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds):
+ """
+ Return the result of ROI.getArrayRegion(), masked by the shape of the
+ ROI. Values outside the ROI shape are set to 0.
+ """
sl = self.getArraySlice(data, img, axes=(0,1))
if sl is None:
return None
@@ -1754,10 +1980,26 @@ def getArrayRegion(self, data, img, axes=(0,1), returnMappedCoords=False, **kwds
shape[axes[1]] = sliced.shape[axes[1]]
return sliced * mask.reshape(shape)
+ def setPen(self, *args, **kwds):
+ ROI.setPen(self, *args, **kwds)
+ for seg in self.segments:
+ seg.setPen(*args, **kwds)
+
+
class LineSegmentROI(ROI):
"""
ROI subclass with two freely-moving handles defining a line.
+
+ ============== =============================================================
+ **Arguments**
+ positions (list of two length-2 sequences) The endpoints of the line
+ segment. Note that, unlike the handle positions specified in
+ other ROIs, these positions must be expressed in the normal
+ coordinate system of the ROI, rather than (0 to 1) relative
+ to the size of the ROI.
+ \**args All extra keyword arguments are passed to ROI()
+ ============== =============================================================
"""
def __init__(self, positions=(None, None), pos=None, handles=(None,None), **args):
@@ -1810,8 +2052,13 @@ def shape(self):
def getArrayRegion(self, data, img, axes=(0,1)):
"""
- Use the position of this ROI relative to an imageItem to pull a slice from an array.
- Since this pulls 1D data from a 2D coordinate system, the return value will have ndim = data.ndim-1
+ Use the position of this ROI relative to an imageItem to pull a slice
+ from an array.
+
+ Since this pulls 1D data from a 2D coordinate system, the return value
+ will have ndim = data.ndim-1
+
+ See ROI.getArrayRegion() for a description of the arguments.
"""
imgPts = [self.mapToItem(img, h['item'].pos()) for h in self.handles]
@@ -1898,6 +2145,102 @@ def paint(self, p, *args):
p.drawRect(self.boundingRect())
+class CrosshairROI(ROI):
+ """A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable."""
+
+ def __init__(self, pos=None, size=None, **kargs):
+ if size == None:
+ #size = [100e-6,100e-6]
+ size=[1,1]
+ if pos == None:
+ pos = [0,0]
+ self._shape = None
+ ROI.__init__(self, pos, size, **kargs)
+
+ self.sigRegionChanged.connect(self.invalidate)
+ self.addScaleRotateHandle(Point(1, 0), Point(0, 0))
+ self.aspectLocked = True
+ def invalidate(self):
+ self._shape = None
+ self.prepareGeometryChange()
+
+ def boundingRect(self):
+ #size = self.size()
+ #return QtCore.QRectF(-size[0]/2., -size[1]/2., size[0], size[1]).normalized()
+ return self.shape().boundingRect()
+
+ #def getRect(self):
+ ### same as boundingRect -- for internal use so that boundingRect can be re-implemented in subclasses
+ #size = self.size()
+ #return QtCore.QRectF(-size[0]/2., -size[1]/2., size[0], size[1]).normalized()
+
+
+ def shape(self):
+ if self._shape is None:
+ radius = self.getState()['size'][1]
+ p = QtGui.QPainterPath()
+ p.moveTo(Point(0, -radius))
+ p.lineTo(Point(0, radius))
+ p.moveTo(Point(-radius, 0))
+ p.lineTo(Point(radius, 0))
+ p = self.mapToDevice(p)
+ stroker = QtGui.QPainterPathStroker()
+ stroker.setWidth(10)
+ outline = stroker.createStroke(p)
+ self._shape = self.mapFromDevice(outline)
+
+
+ ##h1 = self.handles[0]['item'].pos()
+ ##h2 = self.handles[1]['item'].pos()
+ #w1 = Point(-0.5, 0)*self.size()
+ #w2 = Point(0.5, 0)*self.size()
+ #h1 = Point(0, -0.5)*self.size()
+ #h2 = Point(0, 0.5)*self.size()
+
+ #dh = h2-h1
+ #dw = w2-w1
+ #if dh.length() == 0 or dw.length() == 0:
+ #return p
+ #pxv = self.pixelVectors(dh)[1]
+ #if pxv is None:
+ #return p
+
+ #pxv *= 4
-
+ #p.moveTo(h1+pxv)
+ #p.lineTo(h2+pxv)
+ #p.lineTo(h2-pxv)
+ #p.lineTo(h1-pxv)
+ #p.lineTo(h1+pxv)
+
+ #pxv = self.pixelVectors(dw)[1]
+ #if pxv is None:
+ #return p
+
+ #pxv *= 4
+
+ #p.moveTo(w1+pxv)
+ #p.lineTo(w2+pxv)
+ #p.lineTo(w2-pxv)
+ #p.lineTo(w1-pxv)
+ #p.lineTo(w1+pxv)
+
+ return self._shape
+
+ def paint(self, p, *args):
+ #p.save()
+ #r = self.getRect()
+ radius = self.getState()['size'][1]
+ p.setRenderHint(QtGui.QPainter.Antialiasing)
+ p.setPen(self.currentPen)
+ #p.translate(r.left(), r.top())
+ #p.scale(r.width()/10., r.height()/10.) ## need to scale up a little because drawLine has trouble dealing with 0.5
+ #p.drawLine(0,5, 10,5)
+ #p.drawLine(5,0, 5,10)
+ #p.restore()
+
+ p.drawLine(Point(0, -radius), Point(0, radius))
+ p.drawLine(Point(-radius, 0), Point(radius, 0))
+
+
diff --git a/papi/pyqtgraph/graphicsItems/ScaleBar.py b/papi/pyqtgraph/graphicsItems/ScaleBar.py
new file mode 100644
index 00000000..8ba546f7
--- /dev/null
+++ b/papi/pyqtgraph/graphicsItems/ScaleBar.py
@@ -0,0 +1,71 @@
+from ..Qt import QtGui, QtCore
+from .GraphicsObject import *
+from .GraphicsWidgetAnchor import *
+from .TextItem import TextItem
+import numpy as np
+from .. import functions as fn
+from .. import getConfigOption
+from ..Point import Point
+
+__all__ = ['ScaleBar']
+
+class ScaleBar(GraphicsObject, GraphicsWidgetAnchor):
+ """
+ Displays a rectangular bar to indicate the relative scale of objects on the view.
+ """
+ def __init__(self, size, width=5, brush=None, pen=None, suffix='m', offset=None):
+ GraphicsObject.__init__(self)
+ GraphicsWidgetAnchor.__init__(self)
+ self.setFlag(self.ItemHasNoContents)
+ self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
+
+ if brush is None:
+ brush = getConfigOption('foreground')
+ self.brush = fn.mkBrush(brush)
+ self.pen = fn.mkPen(pen)
+ self._width = width
+ self.size = size
+ if offset == None:
+ offset = (0,0)
+ self.offset = offset
+
+ self.bar = QtGui.QGraphicsRectItem()
+ self.bar.setPen(self.pen)
+ self.bar.setBrush(self.brush)
+ self.bar.setParentItem(self)
+
+ self.text = TextItem(text=fn.siFormat(size, suffix=suffix), anchor=(0.5,1))
+ self.text.setParentItem(self)
+
+ def parentChanged(self):
+ view = self.parentItem()
+ if view is None:
+ return
+ view.sigRangeChanged.connect(self.updateBar)
+ self.updateBar()
+
+
+ def updateBar(self):
+ view = self.parentItem()
+ if view is None:
+ return
+ p1 = view.mapFromViewToItem(self, QtCore.QPointF(0,0))
+ p2 = view.mapFromViewToItem(self, QtCore.QPointF(self.size,0))
+ w = (p2-p1).x()
+ self.bar.setRect(QtCore.QRectF(-w, 0, w, self._width))
+ self.text.setPos(-w/2., 0)
+
+ def boundingRect(self):
+ return QtCore.QRectF()
+
+ def setParentItem(self, p):
+ ret = GraphicsObject.setParentItem(self, p)
+ if self.offset is not None:
+ offset = Point(self.offset)
+ anchorx = 1 if offset[0] <= 0 else 0
+ anchory = 1 if offset[1] <= 0 else 0
+ anchor = (anchorx, anchory)
+ self.anchor(itemPos=anchor, parentPos=anchor, offset=offset)
+ return ret
+
+
diff --git a/pyqtgraph/graphicsItems/ScatterPlotItem.py b/papi/pyqtgraph/graphicsItems/ScatterPlotItem.py
similarity index 82%
rename from pyqtgraph/graphicsItems/ScatterPlotItem.py
rename to papi/pyqtgraph/graphicsItems/ScatterPlotItem.py
index f1a5201d..faae8632 100644
--- a/pyqtgraph/graphicsItems/ScatterPlotItem.py
+++ b/papi/pyqtgraph/graphicsItems/ScatterPlotItem.py
@@ -1,14 +1,19 @@
-from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
-from pyqtgraph.Point import Point
-import pyqtgraph.functions as fn
+from ..Qt import QtGui, QtCore, USE_PYSIDE
+from ..Point import Point
+from .. import functions as fn
from .GraphicsItem import GraphicsItem
from .GraphicsObject import GraphicsObject
+from itertools import starmap, repeat
+try:
+ from itertools import imap
+except ImportError:
+ imap = map
import numpy as np
import weakref
-import pyqtgraph.debug as debug
-from pyqtgraph.pgcollections import OrderedDict
-import pyqtgraph as pg
-#import pyqtgraph as pg
+from .. import getConfigOption
+from .. import debug as debug
+from ..pgcollections import OrderedDict
+from .. import debug
__all__ = ['ScatterPlotItem', 'SpotItem']
@@ -63,10 +68,12 @@ def renderSymbol(symbol, size, pen, brush, device=None):
device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32)
device.fill(0)
p = QtGui.QPainter(device)
- p.setRenderHint(p.Antialiasing)
- p.translate(device.width()*0.5, device.height()*0.5)
- drawSymbol(p, symbol, size, pen, brush)
- p.end()
+ try:
+ p.setRenderHint(p.Antialiasing)
+ p.translate(device.width()*0.5, device.height()*0.5)
+ drawSymbol(p, symbol, size, pen, brush)
+ finally:
+ p.end()
return device
def makeSymbolPixmap(size, pen, brush, symbol):
@@ -86,11 +93,8 @@ class SymbolAtlas(object):
pm = atlas.getAtlas()
"""
- class SymbolCoords(list): ## needed because lists are not allowed in weak references.
- pass
-
def __init__(self):
- # symbol key : [x, y, w, h] atlas coordinates
+ # symbol key : QRect(...) coordinates where symbol can be found in atlas.
# note that the coordinate list will always be the same list object as
# long as the symbol is in the atlas, but the coordinates may
# change if the atlas is rebuilt.
@@ -101,28 +105,32 @@ def __init__(self):
self.atlasData = None # numpy array of atlas image
self.atlas = None # atlas as QPixmap
self.atlasValid = False
+ self.max_width=0
def getSymbolCoords(self, opts):
"""
Given a list of spot records, return an object representing the coordinates of that symbol within the atlas
"""
- coords = np.empty(len(opts), dtype=object)
+ sourceRect = np.empty(len(opts), dtype=object)
+ keyi = None
+ sourceRecti = None
for i, rec in enumerate(opts):
- symbol, size, pen, brush = rec['symbol'], rec['size'], rec['pen'], rec['brush']
- pen = fn.mkPen(pen) if not isinstance(pen, QtGui.QPen) else pen
- brush = fn.mkBrush(brush) if not isinstance(pen, QtGui.QBrush) else brush
- key = (symbol, size, fn.colorTuple(pen.color()), pen.widthF(), pen.style(), fn.colorTuple(brush.color()))
- if key not in self.symbolMap:
- newCoords = SymbolAtlas.SymbolCoords()
- self.symbolMap[key] = newCoords
- self.atlasValid = False
- #try:
- #self.addToAtlas(key) ## squeeze this into the atlas if there is room
- #except:
- #self.buildAtlas() ## otherwise, we need to rebuild
-
- coords[i] = self.symbolMap[key]
- return coords
+ key = (rec[3], rec[2], id(rec[4]), id(rec[5])) # TODO: use string indexes?
+ if key == keyi:
+ sourceRect[i] = sourceRecti
+ else:
+ try:
+ sourceRect[i] = self.symbolMap[key]
+ except KeyError:
+ newRectSrc = QtCore.QRectF()
+ newRectSrc.pen = rec['pen']
+ newRectSrc.brush = rec['brush']
+ self.symbolMap[key] = newRectSrc
+ self.atlasValid = False
+ sourceRect[i] = newRectSrc
+ keyi = key
+ sourceRecti = newRectSrc
+ return sourceRect
def buildAtlas(self):
# get rendered array for all symbols, keep track of avg/max width
@@ -130,15 +138,13 @@ def buildAtlas(self):
avgWidth = 0.0
maxWidth = 0
images = []
- for key, coords in self.symbolMap.items():
- if len(coords) == 0:
- pen = fn.mkPen(color=key[2], width=key[3], style=key[4])
- brush = fn.mkBrush(color=key[5])
- img = renderSymbol(key[0], key[1], pen, brush)
+ for key, sourceRect in self.symbolMap.items():
+ if sourceRect.width() == 0:
+ img = renderSymbol(key[0], key[1], sourceRect.pen, sourceRect.brush)
images.append(img) ## we only need this to prevent the images being garbage collected immediately
arr = fn.imageToArray(img, copy=False, transpose=False)
else:
- (x,y,w,h) = self.symbolMap[key]
+ (y,x,h,w) = sourceRect.getRect()
arr = self.atlasData[x:x+w, y:y+w]
rendered[key] = arr
w = arr.shape[0]
@@ -169,17 +175,18 @@ def buildAtlas(self):
x = 0
rowheight = h
self.atlasRows.append([y, rowheight, 0])
- self.symbolMap[key][:] = x, y, w, h
+ self.symbolMap[key].setRect(y, x, h, w)
x += w
self.atlasRows[-1][2] = x
height = y + rowheight
self.atlasData = np.zeros((width, height, 4), dtype=np.ubyte)
for key in symbols:
- x, y, w, h = self.symbolMap[key]
+ y, x, h, w = self.symbolMap[key].getRect()
self.atlasData[x:x+w, y:y+h] = rendered[key]
self.atlas = None
self.atlasValid = True
+ self.max_width = maxWidth
def getAtlas(self):
if not self.atlasValid:
@@ -219,32 +226,31 @@ def __init__(self, *args, **kargs):
"""
Accepts the same arguments as setData()
"""
- prof = debug.Profiler('ScatterPlotItem.__init__', disabled=True)
+ profiler = debug.Profiler()
GraphicsObject.__init__(self)
self.picture = None # QPicture used for rendering when pxmode==False
- self.fragments = None # fragment specification for pxmode; updated every time the view changes.
self.fragmentAtlas = SymbolAtlas()
- self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('data', object), ('fragCoords', object), ('item', object)])
+ self.data = np.empty(0, dtype=[('x', float), ('y', float), ('size', float), ('symbol', object), ('pen', object), ('brush', object), ('data', object), ('item', object), ('sourceRect', object), ('targetRect', object), ('width', float)])
self.bounds = [None, None] ## caches data bounds
self._maxSpotWidth = 0 ## maximum size of the scale-variant portion of all spots
self._maxSpotPxWidth = 0 ## maximum size of the scale-invariant portion of all spots
self.opts = {
'pxMode': True,
'useCache': True, ## If useCache is False, symbols are re-drawn on every paint.
- 'antialias': pg.getConfigOption('antialias'),
- }
-
- self.setPen(200,200,200, update=False)
- self.setBrush(100,100,150, update=False)
+ 'antialias': getConfigOption('antialias'),
+ 'name': None,
+ }
+
+ self.setPen(fn.mkPen(getConfigOption('foreground')), update=False)
+ self.setBrush(fn.mkBrush(100,100,150), update=False)
self.setSymbol('o', update=False)
self.setSize(7, update=False)
- prof.mark('1')
+ profiler()
self.setData(*args, **kargs)
- prof.mark('setData')
- prof.finish()
-
+ profiler('setData')
+
#self.setCacheMode(self.DeviceCoordinateCache)
def setData(self, *args, **kargs):
@@ -282,6 +288,8 @@ def setData(self, *args, **kargs):
*antialias* Whether to draw symbols with antialiasing. Note that if pxMode is True, symbols are
always rendered with antialiasing (since the rendered symbols can be cached, this
incurs very little performance cost)
+ *name* The name of this item. Names are used for automatically
+ generating LegendItem entries and by some exporters.
====================== ===============================================================================================
"""
oldData = self.data ## this causes cached pixmaps to be preserved while new data is registered.
@@ -343,16 +351,12 @@ def addPoints(self, *args, **kargs):
newData = self.data[len(oldData):]
newData['size'] = -1 ## indicates to use default size
-
+
if 'spots' in kargs:
spots = kargs['spots']
for i in range(len(spots)):
spot = spots[i]
for k in spot:
- #if k == 'pen':
- #newData[k] = fn.mkPen(spot[k])
- #elif k == 'brush':
- #newData[k] = fn.mkBrush(spot[k])
if k == 'pos':
pos = spot[k]
if isinstance(pos, QtCore.QPointF):
@@ -361,10 +365,12 @@ def addPoints(self, *args, **kargs):
x,y = pos[0], pos[1]
newData[i]['x'] = x
newData[i]['y'] = y
- elif k in ['x', 'y', 'size', 'symbol', 'pen', 'brush', 'data']:
+ elif k == 'pen':
+ newData[i][k] = fn.mkPen(spot[k])
+ elif k == 'brush':
+ newData[i][k] = fn.mkBrush(spot[k])
+ elif k in ['x', 'y', 'size', 'symbol', 'brush', 'data']:
newData[i][k] = spot[k]
- #elif k == 'data':
- #self.pointData[i] = spot[k]
else:
raise Exception("Unknown spot parameter: %s" % k)
elif 'y' in kargs:
@@ -381,11 +387,12 @@ def addPoints(self, *args, **kargs):
if k in kargs:
setMethod = getattr(self, 'set' + k[0].upper() + k[1:])
setMethod(kargs[k], update=False, dataSet=newData, mask=kargs.get('mask', None))
-
+
if 'data' in kargs:
self.setPointData(kargs['data'], dataSet=newData)
-
+
self.prepareGeometryChange()
+ self.informViewBoundsChanged()
self.bounds = [None, None]
self.invalidate()
self.updateSpots(newData)
@@ -394,12 +401,10 @@ def addPoints(self, *args, **kargs):
def invalidate(self):
## clear any cached drawing state
self.picture = None
- self.fragments = None
self.update()
def getData(self):
- return self.data['x'], self.data['y']
-
+ return self.data['x'], self.data['y']
def setPoints(self, *args, **kargs):
##Deprecated; use setData
@@ -411,6 +416,9 @@ def implements(self, interface=None):
return ints
return interface in ints
+ def name(self):
+ return self.opts.get('name', None)
+
def setPen(self, *args, **kargs):
"""Set the pen(s) used to draw the outline around each spot.
If a list or array is provided, then the pen for each spot will be set separately.
@@ -418,10 +426,10 @@ def setPen(self, *args, **kargs):
all spots which do not have a pen explicitly set."""
update = kargs.pop('update', True)
dataSet = kargs.pop('dataSet', self.data)
-
+
if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)):
pens = args[0]
- if kargs['mask'] is not None:
+ if 'mask' in kargs and kargs['mask'] is not None:
pens = pens[kargs['mask']]
if len(pens) != len(dataSet):
raise Exception("Number of pens does not match number of points (%d != %d)" % (len(pens), len(dataSet)))
@@ -429,7 +437,7 @@ def setPen(self, *args, **kargs):
else:
self.opts['pen'] = fn.mkPen(*args, **kargs)
- dataSet['fragCoords'] = None
+ dataSet['sourceRect'] = None
if update:
self.updateSpots(dataSet)
@@ -443,7 +451,7 @@ def setBrush(self, *args, **kargs):
if len(args) == 1 and (isinstance(args[0], np.ndarray) or isinstance(args[0], list)):
brushes = args[0]
- if kargs['mask'] is not None:
+ if 'mask' in kargs and kargs['mask'] is not None:
brushes = brushes[kargs['mask']]
if len(brushes) != len(dataSet):
raise Exception("Number of brushes does not match number of points (%d != %d)" % (len(brushes), len(dataSet)))
@@ -454,7 +462,7 @@ def setBrush(self, *args, **kargs):
self.opts['brush'] = fn.mkBrush(*args, **kargs)
#self._spotPixmap = None
- dataSet['fragCoords'] = None
+ dataSet['sourceRect'] = None
if update:
self.updateSpots(dataSet)
@@ -477,7 +485,7 @@ def setSymbol(self, symbol, update=True, dataSet=None, mask=None):
self.opts['symbol'] = symbol
self._spotPixmap = None
- dataSet['fragCoords'] = None
+ dataSet['sourceRect'] = None
if update:
self.updateSpots(dataSet)
@@ -500,7 +508,7 @@ def setSize(self, size, update=True, dataSet=None, mask=None):
self.opts['size'] = size
self._spotPixmap = None
- dataSet['fragCoords'] = None
+ dataSet['sourceRect'] = None
if update:
self.updateSpots(dataSet)
@@ -532,22 +540,26 @@ def setPxMode(self, mode):
def updateSpots(self, dataSet=None):
if dataSet is None:
dataSet = self.data
- self._maxSpotWidth = 0
- self._maxSpotPxWidth = 0
+
invalidate = False
- self.measureSpotSizes(dataSet)
if self.opts['pxMode']:
- mask = np.equal(dataSet['fragCoords'], None)
+ mask = np.equal(dataSet['sourceRect'], None)
if np.any(mask):
invalidate = True
opts = self.getSpotOpts(dataSet[mask])
- coords = self.fragmentAtlas.getSymbolCoords(opts)
- dataSet['fragCoords'][mask] = coords
+ sourceRect = self.fragmentAtlas.getSymbolCoords(opts)
+ dataSet['sourceRect'][mask] = sourceRect
- #for rec in dataSet:
- #if rec['fragCoords'] is None:
- #invalidate = True
- #rec['fragCoords'] = self.fragmentAtlas.getSymbolCoords(*self.getSpotOpts(rec))
+ self.fragmentAtlas.getAtlas() # generate atlas so source widths are available.
+
+ dataSet['width'] = np.array(list(imap(QtCore.QRectF.width, dataSet['sourceRect'])))/2
+ dataSet['targetRect'] = None
+ self._maxSpotPxWidth = self.fragmentAtlas.max_width
+ else:
+ self._maxSpotWidth = 0
+ self._maxSpotPxWidth = 0
+ self.measureSpotSizes(dataSet)
+
if invalidate:
self.invalidate()
@@ -652,8 +664,14 @@ def boundingRect(self):
if pxPad > 0:
# determine length of pixel in local x, y directions
px, py = self.pixelVectors()
- px = 0 if px is None else px.length()
- py = 0 if py is None else py.length()
+ try:
+ px = 0 if px is None else px.length()
+ except OverflowError:
+ px = 0
+ try:
+ py = 0 if py is None else py.length()
+ except OverflowError:
+ py = 0
# return bounds expanded by pixel size
px *= pxPad
@@ -664,31 +682,44 @@ def viewTransformChanged(self):
self.prepareGeometryChange()
GraphicsObject.viewTransformChanged(self)
self.bounds = [None, None]
- self.fragments = None
-
- def generateFragments(self):
+ self.data['targetRect'] = None
+
+ def setExportMode(self, *args, **kwds):
+ GraphicsObject.setExportMode(self, *args, **kwds)
+ self.invalidate()
+
+
+ def mapPointsToDevice(self, pts):
+ # Map point locations to device
tr = self.deviceTransform()
if tr is None:
- return
- pts = np.empty((2,len(self.data['x'])))
- pts[0] = self.data['x']
- pts[1] = self.data['y']
+ return None
+
+ #pts = np.empty((2,len(self.data['x'])))
+ #pts[0] = self.data['x']
+ #pts[1] = self.data['y']
pts = fn.transformCoordinates(tr, pts)
- self.fragments = []
+ pts -= self.data['width']
pts = np.clip(pts, -2**30, 2**30) ## prevent Qt segmentation fault.
- ## Still won't be able to render correctly, though.
- for i in xrange(len(self.data)):
- rec = self.data[i]
- pos = QtCore.QPointF(pts[0,i], pts[1,i])
- x,y,w,h = rec['fragCoords']
- rect = QtCore.QRectF(y, x, h, w)
- self.fragments.append(QtGui.QPainter.PixmapFragment.create(pos, rect))
-
- def setExportMode(self, *args, **kwds):
- GraphicsObject.setExportMode(self, *args, **kwds)
- self.invalidate()
- @pg.debug.warnOnException ## raising an exception here causes crash
+ return pts
+
+ def getViewMask(self, pts):
+ # Return bool mask indicating all points that are within viewbox
+ # pts is expressed in *device coordiantes*
+ vb = self.getViewBox()
+ if vb is None:
+ return None
+ viewBounds = vb.mapRectToDevice(vb.boundingRect())
+ w = self.data['width']
+ mask = ((pts[0] + w > viewBounds.left()) &
+ (pts[0] - w < viewBounds.right()) &
+ (pts[1] + w > viewBounds.top()) &
+ (pts[1] - w < viewBounds.bottom())) ## remove out of view points
+ return mask
+
+
+ @debug.warnOnException ## raising an exception here causes crash
def paint(self, p, *args):
#p.setPen(fn.mkPen('r'))
@@ -702,29 +733,44 @@ def paint(self, p, *args):
scale = 1.0
if self.opts['pxMode'] is True:
- atlas = self.fragmentAtlas.getAtlas()
- #arr = fn.imageToArray(atlas.toImage(), copy=True)
- #if hasattr(self, 'lastAtlas'):
- #if np.any(self.lastAtlas != arr):
- #print "Atlas changed:", arr
- #self.lastAtlas = arr
-
- if self.fragments is None:
- self.updateSpots()
- self.generateFragments()
-
p.resetTransform()
- if not USE_PYSIDE and self.opts['useCache'] and self._exportOpts is False:
- p.drawPixmapFragments(self.fragments, atlas)
+ # Map point coordinates to device
+ pts = np.vstack([self.data['x'], self.data['y']])
+ pts = self.mapPointsToDevice(pts)
+ if pts is None:
+ return
+
+ # Cull points that are outside view
+ viewMask = self.getViewMask(pts)
+ #pts = pts[:,mask]
+ #data = self.data[mask]
+
+ if self.opts['useCache'] and self._exportOpts is False:
+ # Draw symbols from pre-rendered atlas
+ atlas = self.fragmentAtlas.getAtlas()
+
+ # Update targetRects if necessary
+ updateMask = viewMask & np.equal(self.data['targetRect'], None)
+ if np.any(updateMask):
+ updatePts = pts[:,updateMask]
+ width = self.data[updateMask]['width']*2
+ self.data['targetRect'][updateMask] = list(imap(QtCore.QRectF, updatePts[0,:], updatePts[1,:], width, width))
+
+ data = self.data[viewMask]
+ if USE_PYSIDE:
+ list(imap(p.drawPixmap, data['targetRect'], repeat(atlas), data['sourceRect']))
+ else:
+ p.drawPixmapFragments(data['targetRect'].tolist(), data['sourceRect'].tolist(), atlas)
else:
+ # render each symbol individually
p.setRenderHint(p.Antialiasing, aa)
-
- for i in range(len(self.data)):
- rec = self.data[i]
- frag = self.fragments[i]
+
+ data = self.data[viewMask]
+ pts = pts[:,viewMask]
+ for i, rec in enumerate(data):
p.resetTransform()
- p.translate(frag.x, frag.y)
+ p.translate(pts[0,i] + rec['width'], pts[1,i] + rec['width'])
drawSymbol(p, *self.getSpotOpts(rec, scale))
else:
if self.picture is None:
@@ -886,7 +932,7 @@ def setData(self, data):
self._data['data'] = data
def updateItem(self):
- self._data['fragCoords'] = None
+ self._data['sourceRect'] = None
self._plot.updateSpots(self._data.reshape(1))
self._plot.invalidate()
diff --git a/pyqtgraph/graphicsItems/TextItem.py b/papi/pyqtgraph/graphicsItems/TextItem.py
similarity index 64%
rename from pyqtgraph/graphicsItems/TextItem.py
rename to papi/pyqtgraph/graphicsItems/TextItem.py
index 911057f4..d3c98006 100644
--- a/pyqtgraph/graphicsItems/TextItem.py
+++ b/papi/pyqtgraph/graphicsItems/TextItem.py
@@ -1,7 +1,7 @@
-from pyqtgraph.Qt import QtCore, QtGui
-import pyqtgraph as pg
+from ..Qt import QtCore, QtGui
+from ..Point import Point
from .UIGraphicsItem import *
-import pyqtgraph.functions as fn
+from .. import functions as fn
class TextItem(UIGraphicsItem):
"""
@@ -9,25 +9,25 @@ class TextItem(UIGraphicsItem):
"""
def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border=None, fill=None, angle=0):
"""
- =========== =================================================================================
- Arguments:
- *text* The text to display
- *color* The color of the text (any format accepted by pg.mkColor)
- *html* If specified, this overrides both *text* and *color*
- *anchor* A QPointF or (x,y) sequence indicating what region of the text box will
- be anchored to the item's position. A value of (0,0) sets the upper-left corner
- of the text box to be at the position specified by setPos(), while a value of (1,1)
- sets the lower-right corner.
- *border* A pen to use when drawing the border
- *fill* A brush to use when filling within the border
- =========== =================================================================================
+ ============== =================================================================================
+ **Arguments:**
+ *text* The text to display
+ *color* The color of the text (any format accepted by pg.mkColor)
+ *html* If specified, this overrides both *text* and *color*
+ *anchor* A QPointF or (x,y) sequence indicating what region of the text box will
+ be anchored to the item's position. A value of (0,0) sets the upper-left corner
+ of the text box to be at the position specified by setPos(), while a value of (1,1)
+ sets the lower-right corner.
+ *border* A pen to use when drawing the border
+ *fill* A brush to use when filling within the border
+ ============== =================================================================================
"""
## not working yet
#*angle* Angle in degrees to rotate text (note that the rotation assigned in this item's
#transformation will be ignored)
- self.anchor = pg.Point(anchor)
+ self.anchor = Point(anchor)
#self.angle = 0
UIGraphicsItem.__init__(self)
self.textItem = QtGui.QGraphicsTextItem()
@@ -38,13 +38,18 @@ def __init__(self, text='', color=(200,200,200), html=None, anchor=(0,0), border
self.setText(text, color)
else:
self.setHtml(html)
- self.fill = pg.mkBrush(fill)
- self.border = pg.mkPen(border)
+ self.fill = fn.mkBrush(fill)
+ self.border = fn.mkPen(border)
self.rotate(angle)
self.setFlag(self.ItemIgnoresTransformations) ## This is required to keep the text unscaled inside the viewport
def setText(self, text, color=(200,200,200)):
- color = pg.mkColor(color)
+ """
+ Set the text and color of this item.
+
+ This method sets the plain text of the item; see also setHtml().
+ """
+ color = fn.mkColor(color)
self.textItem.setDefaultTextColor(color)
self.textItem.setPlainText(text)
self.updateText()
@@ -57,18 +62,41 @@ def updateAnchor(self):
#self.translate(0, 20)
def setPlainText(self, *args):
+ """
+ Set the plain text to be rendered by this item.
+
+ See QtGui.QGraphicsTextItem.setPlainText().
+ """
self.textItem.setPlainText(*args)
self.updateText()
def setHtml(self, *args):
+ """
+ Set the HTML code to be rendered by this item.
+
+ See QtGui.QGraphicsTextItem.setHtml().
+ """
self.textItem.setHtml(*args)
self.updateText()
def setTextWidth(self, *args):
+ """
+ Set the width of the text.
+
+ If the text requires more space than the width limit, then it will be
+ wrapped into multiple lines.
+
+ See QtGui.QGraphicsTextItem.setTextWidth().
+ """
self.textItem.setTextWidth(*args)
self.updateText()
def setFont(self, *args):
+ """
+ Set the font for this text.
+
+ See QtGui.QGraphicsTextItem.setFont().
+ """
self.textItem.setFont(*args)
self.updateText()
@@ -89,7 +117,7 @@ def updateText(self):
#br = self.textItem.mapRectToParent(self.textItem.boundingRect())
self.textItem.setPos(0,0)
br = self.textItem.boundingRect()
- apos = self.textItem.mapToParent(pg.Point(br.width()*self.anchor.x(), br.height()*self.anchor.y()))
+ apos = self.textItem.mapToParent(Point(br.width()*self.anchor.x(), br.height()*self.anchor.y()))
#print br, apos
self.textItem.setPos(-apos.x(), -apos.y())
diff --git a/pyqtgraph/graphicsItems/UIGraphicsItem.py b/papi/pyqtgraph/graphicsItems/UIGraphicsItem.py
similarity index 98%
rename from pyqtgraph/graphicsItems/UIGraphicsItem.py
rename to papi/pyqtgraph/graphicsItems/UIGraphicsItem.py
index 19fda424..6f756334 100644
--- a/pyqtgraph/graphicsItems/UIGraphicsItem.py
+++ b/papi/pyqtgraph/graphicsItems/UIGraphicsItem.py
@@ -1,4 +1,4 @@
-from pyqtgraph.Qt import QtGui, QtCore, USE_PYSIDE
+from ..Qt import QtGui, QtCore, USE_PYSIDE
import weakref
from .GraphicsObject import GraphicsObject
if not USE_PYSIDE:
diff --git a/pyqtgraph/graphicsItems/VTickGroup.py b/papi/pyqtgraph/graphicsItems/VTickGroup.py
similarity index 67%
rename from pyqtgraph/graphicsItems/VTickGroup.py
rename to papi/pyqtgraph/graphicsItems/VTickGroup.py
index c6880f91..1db4a4a2 100644
--- a/pyqtgraph/graphicsItems/VTickGroup.py
+++ b/papi/pyqtgraph/graphicsItems/VTickGroup.py
@@ -3,8 +3,8 @@
path = os.path.abspath(os.path.dirname(__file__))
sys.path.insert(0, os.path.join(path, '..', '..'))
-from pyqtgraph.Qt import QtGui, QtCore
-import pyqtgraph.functions as fn
+from ..Qt import QtGui, QtCore
+from .. import functions as fn
import weakref
from .UIGraphicsItem import UIGraphicsItem
@@ -19,15 +19,15 @@ class VTickGroup(UIGraphicsItem):
"""
def __init__(self, xvals=None, yrange=None, pen=None):
"""
- ============= ===================================================================
- **Arguments**
- xvals A list of x values (in data coordinates) at which to draw ticks.
- yrange A list of [low, high] limits for the tick. 0 is the bottom of
- the view, 1 is the top. [0.8, 1] would draw ticks in the top
- fifth of the view.
- pen The pen to use for drawing ticks. Default is grey. Can be specified
- as any argument valid for :func:`mkPen`
- ============= ===================================================================
+ ============== ===================================================================
+ **Arguments:**
+ xvals A list of x values (in data coordinates) at which to draw ticks.
+ yrange A list of [low, high] limits for the tick. 0 is the bottom of
+ the view, 1 is the top. [0.8, 1] would draw ticks in the top
+ fifth of the view.
+ pen The pen to use for drawing ticks. Default is grey. Can be specified
+ as any argument valid for :func:`mkPen`
+ ============== ===================================================================
"""
if yrange is None:
yrange = [0, 1]
@@ -56,10 +56,10 @@ def setPen(self, *args, **kwargs):
def setXVals(self, vals):
"""Set the x values for the ticks.
- ============= =====================================================================
- **Arguments**
- vals A list of x values (in data/plot coordinates) at which to draw ticks.
- ============= =====================================================================
+ ============== =====================================================================
+ **Arguments:**
+ vals A list of x values (in data/plot coordinates) at which to draw ticks.
+ ============== =====================================================================
"""
self.xvals = vals
self.rebuildTicks()
@@ -96,18 +96,4 @@ def paint(self, p, *args):
p.setPen(self.pen)
p.drawPath(self.path)
-
-if __name__ == '__main__':
- app = QtGui.QApplication([])
- import pyqtgraph as pg
- vt = VTickGroup([1,3,4,7,9], [0.8, 1.0])
- p = pg.plot()
- p.addItem(vt)
-
- if sys.flags.interactive == 0:
- app.exec_()
-
-
-
-
\ No newline at end of file
diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/papi/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
similarity index 82%
rename from pyqtgraph/graphicsItems/ViewBox/ViewBox.py
rename to papi/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
index 3cbb1ea2..900c2038 100644
--- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
+++ b/papi/pyqtgraph/graphicsItems/ViewBox/ViewBox.py
@@ -1,32 +1,67 @@
-from pyqtgraph.Qt import QtGui, QtCore
-from pyqtgraph.python2_3 import sortList
+from ...Qt import QtGui, QtCore
+from ...python2_3 import sortList
import numpy as np
-from pyqtgraph.Point import Point
-import pyqtgraph.functions as fn
+from ...Point import Point
+from ... import functions as fn
from .. ItemGroup import ItemGroup
from .. GraphicsWidget import GraphicsWidget
-from pyqtgraph.GraphicsScene import GraphicsScene
-import pyqtgraph
import weakref
from copy import deepcopy
-import pyqtgraph.debug as debug
+from ... import debug as debug
+from ... import getConfigOption
+import sys
+from ...Qt import isQObjectAlive
__all__ = ['ViewBox']
+class WeakList(object):
+
+ def __init__(self):
+ self._items = []
+
+ def append(self, obj):
+ #Add backwards to iterate backwards (to make iterating more efficient on removal).
+ self._items.insert(0, weakref.ref(obj))
+
+ def __iter__(self):
+ i = len(self._items)-1
+ while i >= 0:
+ ref = self._items[i]
+ d = ref()
+ if d is None:
+ del self._items[i]
+ else:
+ yield d
+ i -= 1
class ChildGroup(ItemGroup):
- sigItemsChanged = QtCore.Signal()
def __init__(self, parent):
ItemGroup.__init__(self, parent)
+
+ # Used as callback to inform ViewBox when items are added/removed from
+ # the group.
+ # Note 1: We would prefer to override itemChange directly on the
+ # ViewBox, but this causes crashes on PySide.
+ # Note 2: We might also like to use a signal rather than this callback
+ # mechanism, but this causes a different PySide crash.
+ self.itemsChangedListeners = WeakList()
+
# excempt from telling view when transform changes
self._GraphicsObject__inform_view_on_change = False
def itemChange(self, change, value):
ret = ItemGroup.itemChange(self, change, value)
if change == self.ItemChildAddedChange or change == self.ItemChildRemovedChange:
- self.sigItemsChanged.emit()
-
+ try:
+ itemsChangedListeners = self.itemsChangedListeners
+ except AttributeError:
+ # It's possible that the attribute was already collected when the itemChange happened
+ # (if it was triggered during the gc of the object).
+ pass
+ else:
+ for listener in itemsChangedListeners:
+ listener.itemsChanged()
return ret
@@ -39,12 +74,11 @@ class ViewBox(GraphicsWidget):
Features:
- - Scaling contents by mouse or auto-scale when contents change
- - View linking--multiple views display the same data ranges
- - Configurable by context menu
- - Item coordinate mapping methods
+ * Scaling contents by mouse or auto-scale when contents change
+ * View linking--multiple views display the same data ranges
+ * Configurable by context menu
+ * Item coordinate mapping methods
- Not really compatible with GraphicsView having the same functionality.
"""
sigYRangeChanged = QtCore.Signal(object, object)
@@ -69,22 +103,27 @@ class ViewBox(GraphicsWidget):
NamedViews = weakref.WeakValueDictionary() # name: ViewBox
AllViews = weakref.WeakKeyDictionary() # ViewBox: None
- def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None):
+ def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True, invertY=False, enableMenu=True, name=None, invertX=False):
"""
- ============= =============================================================
- **Arguments**
- *parent* (QGraphicsWidget) Optional parent widget
- *border* (QPen) Do draw a border around the view, give any
- single argument accepted by :func:`mkPen `
- *lockAspect* (False or float) The aspect ratio to lock the view
- coorinates to. (or False to allow the ratio to change)
- *enableMouse* (bool) Whether mouse can be used to scale/pan the view
- *invertY* (bool) See :func:`invertY `
- ============= =============================================================
+ ============== =============================================================
+ **Arguments:**
+ *parent* (QGraphicsWidget) Optional parent widget
+ *border* (QPen) Do draw a border around the view, give any
+ single argument accepted by :func:`mkPen `
+ *lockAspect* (False or float) The aspect ratio to lock the view
+ coorinates to. (or False to allow the ratio to change)
+ *enableMouse* (bool) Whether mouse can be used to scale/pan the view
+ *invertY* (bool) See :func:`invertY `
+ *invertX* (bool) See :func:`invertX `
+ *enableMenu* (bool) Whether to display a context menu when
+ right-clicking on the ViewBox background.
+ *name* (str) Used to register this ViewBox so that it appears
+ in the "Link axis" dropdown inside other ViewBox
+ context menus. This allows the user to manually link
+ the axes of any other view to this one.
+ ============== =============================================================
"""
-
-
GraphicsWidget.__init__(self, parent)
self.name = None
self.linksBlocked = False
@@ -104,6 +143,7 @@ def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True,
'viewRange': [[0,1], [0,1]], ## actual range viewed
'yInverted': invertY,
+ 'xInverted': invertX,
'aspectLocked': False, ## False if aspect is unlocked, otherwise float specifies the locked ratio.
'autoRange': [True, True], ## False if auto range is disabled,
## otherwise float gives the fraction of data that is visible
@@ -113,11 +153,20 @@ def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True,
## a name string indicates that the view *should* link to another, but no view with that name exists yet.
'mouseEnabled': [enableMouse, enableMouse],
- 'mouseMode': ViewBox.PanMode if pyqtgraph.getConfigOption('leftButtonPan') else ViewBox.RectMode,
+ 'mouseMode': ViewBox.PanMode if getConfigOption('leftButtonPan') else ViewBox.RectMode,
'enableMenu': enableMenu,
'wheelScaleFactor': -1.0 / 8.0,
'background': None,
+
+ # Limits
+ 'limits': {
+ 'xLimits': [None, None], # Maximum and minimum visible X values
+ 'yLimits': [None, None], # Maximum and minimum visible Y values
+ 'xRange': [None, None], # Maximum and minimum X range
+ 'yRange': [None, None], # Maximum and minimum Y range
+ }
+
}
self._updatingRange = False ## Used to break recursive loops. See updateAutoRange.
self._itemBoundsCache = weakref.WeakKeyDictionary()
@@ -131,7 +180,7 @@ def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True,
## this is a workaround for a Qt + OpenGL bug that causes improper clipping
## https://bugreports.qt.nokia.com/browse/QTBUG-23723
self.childGroup = ChildGroup(self)
- self.childGroup.sigItemsChanged.connect(self.itemsChanged)
+ self.childGroup.itemsChangedListeners.append(self)
self.background = QtGui.QGraphicsRectItem(self.rect())
self.background.setParentItem(self)
@@ -174,7 +223,11 @@ def __init__(self, parent=None, border=None, lockAspect=False, enableMouse=True,
def register(self, name):
"""
Add this ViewBox to the registered list of views.
- *name* will appear in the drop-down lists for axis linking in all other views.
+
+ This allows users to manually link the axes of any other ViewBox to
+ this one. The specified *name* will appear in the drop-down lists for
+ axis linking in the context menus of all other views.
+
The same can be accomplished by initializing the ViewBox with the *name* attribute.
"""
ViewBox.AllViews[self] = None
@@ -197,6 +250,7 @@ def unregister(self):
del ViewBox.NamedViews[self.name]
def close(self):
+ self.clear()
self.unregister()
def implements(self, interface):
@@ -276,6 +330,17 @@ def setState(self, state):
self.updateViewRange()
self.sigStateChanged.emit(self)
+ def setBackgroundColor(self, color):
+ """
+ Set the background color of the ViewBox.
+
+ If color is None, then no background will be drawn.
+
+ Added in version 0.9.9
+ """
+ self.background.setVisible(color is not None)
+ self.state['background'] = color
+ self.updateBackground()
def setMouseMode(self, mode):
"""
@@ -362,11 +427,11 @@ def resizeEvent(self, ev):
self.linkedYChanged()
self.updateAutoRange()
self.updateViewRange()
+ self._matrixNeedsUpdate = True
self.sigStateChanged.emit(self)
self.background.setRect(self.rect())
self.sigResized.emit(self)
-
def viewRange(self):
"""Return a the view's visible range as a list: [[xmin, xmax], [ymin, ymax]]"""
return [x[:] for x in self.state['viewRange']] ## return copy
@@ -398,13 +463,20 @@ def targetRect(self):
print("make qrectf failed:", self.state['targetRange'])
raise
+ def _resetTarget(self):
+ # Reset target range to exactly match current view range.
+ # This is used during mouse interaction to prevent unpredictable
+ # behavior (because the user is unaware of targetRange).
+ if self.state['aspectLocked'] is False: # (interferes with aspect locking)
+ self.state['targetRange'] = [self.state['viewRange'][0][:], self.state['viewRange'][1][:]]
+
def setRange(self, rect=None, xRange=None, yRange=None, padding=None, update=True, disableAutoRange=True):
"""
Set the visible range of the ViewBox.
Must specify at least one of *rect*, *xRange*, or *yRange*.
================== =====================================================================
- **Arguments**
+ **Arguments:**
*rect* (QRectF) The full range that should be visible in the view box.
*xRange* (min,max) The range that should be visible along the x-axis.
*yRange* (min,max) The range that should be visible along the y-axis.
@@ -546,14 +618,14 @@ def autoRange(self, padding=None, items=None, item=None):
Note that this is not the same as enableAutoRange, which causes the view to
automatically auto-range whenever its contents are changed.
- =========== ============================================================
- Arguments
- padding The fraction of the total data range to add on to the final
- visible range. By default, this value is set between 0.02
- and 0.1 depending on the size of the ViewBox.
- items If specified, this is a list of items to consider when
- determining the visible range.
- =========== ============================================================
+ ============== ============================================================
+ **Arguments:**
+ padding The fraction of the total data range to add on to the final
+ visible range. By default, this value is set between 0.02
+ and 0.1 depending on the size of the ViewBox.
+ items If specified, this is a list of items to consider when
+ determining the visible range.
+ ============== ============================================================
"""
if item is None:
bounds = self.childrenBoundingRect(items=items)
@@ -571,6 +643,60 @@ def suggestPadding(self, axis):
else:
padding = 0.02
return padding
+
+ def setLimits(self, **kwds):
+ """
+ Set limits that constrain the possible view ranges.
+
+ **Panning limits**. The following arguments define the region within the
+ viewbox coordinate system that may be accessed by panning the view.
+
+ =========== ============================================================
+ xMin Minimum allowed x-axis value
+ xMax Maximum allowed x-axis value
+ yMin Minimum allowed y-axis value
+ yMax Maximum allowed y-axis value
+ =========== ============================================================
+
+ **Scaling limits**. These arguments prevent the view being zoomed in or
+ out too far.
+
+ =========== ============================================================
+ minXRange Minimum allowed left-to-right span across the view.
+ maxXRange Maximum allowed left-to-right span across the view.
+ minYRange Minimum allowed top-to-bottom span across the view.
+ maxYRange Maximum allowed top-to-bottom span across the view.
+ =========== ============================================================
+
+ Added in version 0.9.9
+ """
+ update = False
+ allowed = ['xMin', 'xMax', 'yMin', 'yMax', 'minXRange', 'maxXRange', 'minYRange', 'maxYRange']
+ for kwd in kwds:
+ if kwd not in allowed:
+ raise ValueError("Invalid keyword argument '%s'." % kwd)
+ #for kwd in ['xLimits', 'yLimits', 'minRange', 'maxRange']:
+ #if kwd in kwds and self.state['limits'][kwd] != kwds[kwd]:
+ #self.state['limits'][kwd] = kwds[kwd]
+ #update = True
+ for axis in [0,1]:
+ for mnmx in [0,1]:
+ kwd = [['xMin', 'xMax'], ['yMin', 'yMax']][axis][mnmx]
+ lname = ['xLimits', 'yLimits'][axis]
+ if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]:
+ self.state['limits'][lname][mnmx] = kwds[kwd]
+ update = True
+ kwd = [['minXRange', 'maxXRange'], ['minYRange', 'maxYRange']][axis][mnmx]
+ lname = ['xRange', 'yRange'][axis]
+ if kwd in kwds and self.state['limits'][lname][mnmx] != kwds[kwd]:
+ self.state['limits'][lname][mnmx] = kwds[kwd]
+ update = True
+
+ if update:
+ self.updateViewRange()
+
+
+
def scaleBy(self, s=None, center=None, x=None, y=None):
"""
@@ -634,7 +760,8 @@ def translateBy(self, t=None, x=None, y=None):
x = vr.left()+x, vr.right()+x
if y is not None:
y = vr.top()+y, vr.bottom()+y
- self.setRange(xRange=x, yRange=y, padding=0)
+ if x is not None or y is not None:
+ self.setRange(xRange=x, yRange=y, padding=0)
@@ -776,6 +903,14 @@ def updateAutoRange(self):
return
args['padding'] = 0
args['disableAutoRange'] = False
+
+ # check for and ignore bad ranges
+ for k in ['xRange', 'yRange']:
+ if k in args:
+ if not np.all(np.isfinite(args[k])):
+ r = args.pop(k)
+ #print("Warning: %s is invalid: %s" % (k, str(r))
+
self.setRange(**args)
finally:
self._autoRangeNeedsUpdate = False
@@ -818,7 +953,7 @@ def linkView(self, axis, view):
try:
getattr(oldLink, signal).disconnect(slot)
oldLink.sigResized.disconnect(slot)
- except TypeError:
+ except (TypeError, RuntimeError):
## This can occur if the view has been deleted already
pass
@@ -882,7 +1017,10 @@ def linkedViewChanged(self, view, axis):
x2 = vr.right()
else: ## views overlap; line them up
upp = float(vr.width()) / vg.width()
- x1 = vr.left() + (sg.x()-vg.x()) * upp
+ if self.xInverted():
+ x1 = vr.left() + (sg.right()-vg.right()) * upp
+ else:
+ x1 = vr.left() + (sg.x()-vg.x()) * upp
x2 = x1 + sg.width() * upp
self.enableAutoRange(ViewBox.XAxis, False)
self.setXRange(x1, x2, padding=0)
@@ -937,13 +1075,30 @@ def invertY(self, b=True):
return
self.state['yInverted'] = b
- #self.updateMatrix(changed=(False, True))
+ self._matrixNeedsUpdate = True # updateViewRange won't detect this for us
self.updateViewRange()
self.sigStateChanged.emit(self)
+ self.sigYRangeChanged.emit(self, tuple(self.state['viewRange'][1]))
def yInverted(self):
return self.state['yInverted']
+ def invertX(self, b=True):
+ """
+ By default, the positive x-axis points rightward on the screen. Use invertX(True) to reverse the x-axis.
+ """
+ if self.state['xInverted'] == b:
+ return
+
+ self.state['xInverted'] = b
+ #self.updateMatrix(changed=(False, True))
+ self.updateViewRange()
+ self.sigStateChanged.emit(self)
+ self.sigXRangeChanged.emit(self, tuple(self.state['viewRange'][0]))
+
+ def xInverted(self):
+ return self.state['xInverted']
+
def setAspectLocked(self, lock=True, ratio=1):
"""
If the aspect ratio is locked, view scaling must always preserve the aspect ratio.
@@ -980,6 +1135,8 @@ def childTransform(self):
Return the transform that maps from child(item in the childGroup) coordinates to local coordinates.
(This maps from inside the viewbox to outside)
"""
+ if self._matrixNeedsUpdate:
+ self.updateMatrix()
m = self.childGroup.transform()
#m1 = QtGui.QTransform()
#m1.translate(self.childGroup.pos().x(), self.childGroup.pos().y())
@@ -1056,6 +1213,7 @@ def wheelEvent(self, ev, axis=None):
center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos()))
#center = ev.pos()
+ self._resetTarget()
self.scaleBy(s, center)
self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
ev.accept()
@@ -1065,33 +1223,17 @@ def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton and self.menuEnabled():
ev.accept()
self.raiseContextMenu(ev)
-
+
def raiseContextMenu(self, ev):
- #print "viewbox.raiseContextMenu called."
-
- #menu = self.getMenu(ev)
menu = self.getMenu(ev)
self.scene().addParentContextMenus(self, menu, ev)
- #print "2:", [str(a.text()) for a in self.menu.actions()]
- pos = ev.screenPos()
- #pos2 = ev.scenePos()
- #print "3:", [str(a.text()) for a in self.menu.actions()]
- #self.sigActionPositionChanged.emit(pos2)
+ menu.popup(ev.screenPos().toPoint())
- menu.popup(QtCore.QPoint(pos.x(), pos.y()))
- #print "4:", [str(a.text()) for a in self.menu.actions()]
-
def getMenu(self, ev):
- self._menuCopy = self.menu.copy() ## temporary storage to prevent menu disappearing
- return self._menuCopy
-
+ return self.menu
+
def getContextMenus(self, event):
- if self.menuEnabled():
- return self.menu.subMenus()
- else:
- return None
- #return [self.getMenu(event)]
-
+ return self.menu.actions() if self.menuEnabled() else []
def mouseDragEvent(self, ev, axis=None):
## if axis is specified, event will only affect that axis.
@@ -1129,7 +1271,9 @@ def mouseDragEvent(self, ev, axis=None):
x = tr.x() if mask[0] == 1 else None
y = tr.y() if mask[1] == 1 else None
- self.translateBy(x=x, y=y)
+ self._resetTarget()
+ if x is not None or y is not None:
+ self.translateBy(x=x, y=y)
self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
elif ev.button() & QtCore.Qt.RightButton:
#print "vb.rightDrag"
@@ -1148,6 +1292,7 @@ def mouseDragEvent(self, ev, axis=None):
y = s[1] if mouseEnabled[1] == 1 else None
center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton)))
+ self._resetTarget()
self.scaleBy(x=x, y=y, center=center)
self.sigRangeChangedManually.emit(self.state['mouseEnabled'])
@@ -1178,6 +1323,8 @@ def keyPressEvent(self, ev):
ev.ignore()
def scaleHistory(self, d):
+ if len(self.axHistory) == 0:
+ return
ptr = max(0, min(len(self.axHistory)-1, self.axHistoryPointer+d))
if ptr != self.axHistoryPointer:
self.axHistoryPointer = ptr
@@ -1221,7 +1368,7 @@ def childrenBounds(self, frac=None, orthoRange=(None,None), items=None):
[[xmin, xmax], [ymin, ymax]]
Values may be None if there are no specific bounds for an axis.
"""
- prof = debug.Profiler('updateAutoRange', disabled=True)
+ profiler = debug.Profiler()
if items is None:
items = self.addedItems
@@ -1298,7 +1445,7 @@ def childrenBounds(self, frac=None, orthoRange=(None,None), items=None):
range[0] = [min(bounds.left(), range[0][0]), max(bounds.right(), range[0][1])]
else:
range[0] = [bounds.left(), bounds.right()]
- prof.mark('2')
+ profiler()
#print "range", range
@@ -1322,10 +1469,7 @@ def childrenBounds(self, frac=None, orthoRange=(None,None), items=None):
continue
range[1][0] = min(range[1][0], bounds.top() - px*pxSize)
range[1][1] = max(range[1][1], bounds.bottom() + px*pxSize)
-
- #print "final range", range
-
- prof.finish()
+
return range
def childrenBoundingRect(self, *args, **kwds):
@@ -1346,18 +1490,19 @@ def updateViewRange(self, forceX=False, forceY=False):
viewRange = [self.state['targetRange'][0][:], self.state['targetRange'][1][:]]
changed = [False, False]
- # Make correction for aspect ratio constraint
+ #-------- Make correction for aspect ratio constraint ----------
- ## aspect is (widget w/h) / (view range w/h)
+ # aspect is (widget w/h) / (view range w/h)
aspect = self.state['aspectLocked'] # size ratio / view ratio
tr = self.targetRect()
bounds = self.rect()
- if aspect is not False and aspect != 0 and tr.height() != 0 and bounds.height() != 0:
+ if aspect is not False and 0 not in [aspect, tr.height(), bounds.height(), bounds.width()]:
## This is the view range aspect ratio we have requested
- targetRatio = tr.width() / tr.height()
+ targetRatio = tr.width() / tr.height() if tr.height() != 0 else 1
## This is the view range aspect ratio we need to obey aspect constraint
- viewRatio = (bounds.width() / bounds.height()) / aspect
+ viewRatio = (bounds.width() / bounds.height() if bounds.height() != 0 else 1) / aspect
+ viewRatio = 1 if viewRatio == 0 else viewRatio
# Decide which range to keep unchanged
#print self.name, "aspect:", aspect, "changed:", changed, "auto:", self.state['autoRange']
@@ -1370,7 +1515,6 @@ def updateViewRange(self, forceX=False, forceY=False):
# then make the entire target range visible
ax = 0 if targetRatio > viewRatio else 1
- #### these should affect viewRange, not targetRange!
if ax == 0:
## view range needs to be taller than target
dy = 0.5 * (tr.width() / viewRatio - tr.height())
@@ -1383,8 +1527,60 @@ def updateViewRange(self, forceX=False, forceY=False):
if dx != 0:
changed[0] = True
viewRange[0] = [self.state['targetRange'][0][0] - dx, self.state['targetRange'][0][1] + dx]
+
+
+ # ----------- Make corrections for view limits -----------
+
+ limits = (self.state['limits']['xLimits'], self.state['limits']['yLimits'])
+ minRng = [self.state['limits']['xRange'][0], self.state['limits']['yRange'][0]]
+ maxRng = [self.state['limits']['xRange'][1], self.state['limits']['yRange'][1]]
+
+ for axis in [0, 1]:
+ if limits[axis][0] is None and limits[axis][1] is None and minRng[axis] is None and maxRng[axis] is None:
+ continue
+
+ # max range cannot be larger than bounds, if they are given
+ if limits[axis][0] is not None and limits[axis][1] is not None:
+ if maxRng[axis] is not None:
+ maxRng[axis] = min(maxRng[axis], limits[axis][1]-limits[axis][0])
+ else:
+ maxRng[axis] = limits[axis][1]-limits[axis][0]
+
+ #print "\nLimits for axis %d: range=%s min=%s max=%s" % (axis, limits[axis], minRng[axis], maxRng[axis])
+ #print "Starting range:", viewRange[axis]
+
+ # Apply xRange, yRange
+ diff = viewRange[axis][1] - viewRange[axis][0]
+ if maxRng[axis] is not None and diff > maxRng[axis]:
+ delta = maxRng[axis] - diff
+ changed[axis] = True
+ elif minRng[axis] is not None and diff < minRng[axis]:
+ delta = minRng[axis] - diff
+ changed[axis] = True
+ else:
+ delta = 0
- changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) and (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)]
+ viewRange[axis][0] -= delta/2.
+ viewRange[axis][1] += delta/2.
+
+ #print "after applying min/max:", viewRange[axis]
+
+ # Apply xLimits, yLimits
+ mn, mx = limits[axis]
+ if mn is not None and viewRange[axis][0] < mn:
+ delta = mn - viewRange[axis][0]
+ viewRange[axis][0] += delta
+ viewRange[axis][1] += delta
+ changed[axis] = True
+ elif mx is not None and viewRange[axis][1] > mx:
+ delta = mx - viewRange[axis][1]
+ viewRange[axis][0] += delta
+ viewRange[axis][1] += delta
+ changed[axis] = True
+
+ #print "after applying edge limits:", viewRange[axis]
+
+ changed = [(viewRange[i][0] != self.state['viewRange'][i][0]) or (viewRange[i][1] != self.state['viewRange'][i][1]) for i in (0,1)]
self.state['viewRange'] = viewRange
# emit range change signals
@@ -1396,17 +1592,16 @@ def updateViewRange(self, forceX=False, forceY=False):
if any(changed):
self.sigRangeChanged.emit(self, self.state['viewRange'])
self.update()
+ self._matrixNeedsUpdate = True
- # Inform linked views that the range has changed
- for ax in [0, 1]:
- if not changed[ax]:
- continue
- link = self.linkedView(ax)
- if link is not None:
- link.linkedViewChanged(self, ax)
+ # Inform linked views that the range has changed
+ for ax in [0, 1]:
+ if not changed[ax]:
+ continue
+ link = self.linkedView(ax)
+ if link is not None:
+ link.linkedViewChanged(self, ax)
- self._matrixNeedsUpdate = True
-
def updateMatrix(self, changed=None):
## Make the childGroup's transform match the requested viewRange.
bounds = self.rect()
@@ -1417,6 +1612,8 @@ def updateMatrix(self, changed=None):
scale = Point(bounds.width()/vr.width(), bounds.height()/vr.height())
if not self.state['yInverted']:
scale = scale * Point(1, -1)
+ if self.state['xInverted']:
+ scale = scale * Point(-1, 1)
m = QtGui.QTransform()
## First center the viewport at 0
@@ -1499,6 +1696,8 @@ def updateAllViewLists():
def forgetView(vid, name):
if ViewBox is None: ## can happen as python is shutting down
return
+ if QtGui.QApplication.instance() is None:
+ return
## Called with ID and name of view (the view itself is no longer available)
for v in list(ViewBox.AllViews.keys()):
if id(v) == vid:
@@ -1512,12 +1711,17 @@ def quit():
## called when the application is about to exit.
## this disables all callbacks, which might otherwise generate errors if invoked during exit.
for k in ViewBox.AllViews:
+ if isQObjectAlive(k) and getConfigOption('crashWarning'):
+ sys.stderr.write('Warning: ViewBox should be closed before application exit.\n')
+
try:
k.destroyed.disconnect()
except RuntimeError: ## signal is already disconnected.
pass
except TypeError: ## view has already been deleted (?)
pass
+ except AttributeError: # PySide has deleted signal
+ pass
def locate(self, item, timeout=3.0, children=False):
"""
diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py b/papi/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py
similarity index 93%
rename from pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py
rename to papi/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py
index 5242ecdd..0e7d7912 100644
--- a/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py
+++ b/papi/pyqtgraph/graphicsItems/ViewBox/ViewBoxMenu.py
@@ -1,6 +1,6 @@
-from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
-from pyqtgraph.python2_3 import asUnicode
-from pyqtgraph.WidgetGroup import WidgetGroup
+from ...Qt import QtCore, QtGui, USE_PYSIDE
+from ...python2_3 import asUnicode
+from ...WidgetGroup import WidgetGroup
if USE_PYSIDE:
from .axisCtrlTemplate_pyside import Ui_Form as AxisCtrlTemplate
@@ -56,7 +56,7 @@ def __init__(self, view):
for sig, fn in connects:
sig.connect(getattr(self, axis.lower()+fn))
- self.ctrl[0].invertCheck.hide() ## no invert for x-axis
+ self.ctrl[0].invertCheck.toggled.connect(self.xInvertToggled)
self.ctrl[1].invertCheck.toggled.connect(self.yInvertToggled)
## exporting is handled by GraphicsScene now
#self.export = QtGui.QMenu("Export")
@@ -88,22 +88,6 @@ def __init__(self, view):
self.updateState()
- def copy(self):
- m = QtGui.QMenu()
- for sm in self.subMenus():
- if isinstance(sm, QtGui.QMenu):
- m.addMenu(sm)
- else:
- m.addAction(sm)
- m.setTitle(self.title())
- return m
-
- def subMenus(self):
- if not self.valid:
- self.updateState()
- return [self.viewAll] + self.axes + [self.leftMenu]
-
-
def setExportMethods(self, methods):
self.exportMethods = methods
self.export.clear()
@@ -155,10 +139,15 @@ def updateState(self):
self.ctrl[i].autoPanCheck.setChecked(state['autoPan'][i])
self.ctrl[i].visibleOnlyCheck.setChecked(state['autoVisibleOnly'][i])
-
- self.ctrl[1].invertCheck.setChecked(state['yInverted'])
+ xy = ['x', 'y'][i]
+ self.ctrl[i].invertCheck.setChecked(state.get(xy+'Inverted', False))
+
self.valid = True
+ def popup(self, *args):
+ if not self.valid:
+ self.updateState()
+ QtGui.QMenu.popup(self, *args)
def autoRange(self):
self.view().autoRange() ## don't let signal call this directly--it'll add an unwanted argument
@@ -229,19 +218,19 @@ def yVisibleOnlyToggled(self, b):
def yInvertToggled(self, b):
self.view().invertY(b)
+ def xInvertToggled(self, b):
+ self.view().invertX(b)
def exportMethod(self):
act = self.sender()
self.exportMethods[str(act.text())]()
-
def set3ButtonMode(self):
self.view().setLeftButtonAction('pan')
def set1ButtonMode(self):
self.view().setLeftButtonAction('rect')
-
def setViewList(self, views):
names = ['']
self.viewMap.clear()
@@ -275,4 +264,4 @@ def setViewList(self, views):
from .ViewBox import ViewBox
-
\ No newline at end of file
+
diff --git a/pyqtgraph/graphicsItems/ViewBox/__init__.py b/papi/pyqtgraph/graphicsItems/ViewBox/__init__.py
similarity index 100%
rename from pyqtgraph/graphicsItems/ViewBox/__init__.py
rename to papi/pyqtgraph/graphicsItems/ViewBox/__init__.py
diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui
similarity index 100%
rename from pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui
rename to papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui
diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py
new file mode 100644
index 00000000..d8ef1925
--- /dev/null
+++ b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyqt.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+
+# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui'
+#
+# Created: Mon Dec 23 10:10:51 2013
+# by: PyQt4 UI code generator 4.10
+#
+# WARNING! All changes made in this file will be lost!
+
+from PyQt4 import QtCore, QtGui
+
+try:
+ _fromUtf8 = QtCore.QString.fromUtf8
+except AttributeError:
+ def _fromUtf8(s):
+ return s
+
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
+
+class Ui_Form(object):
+ def setupUi(self, Form):
+ Form.setObjectName(_fromUtf8("Form"))
+ Form.resize(186, 154)
+ Form.setMaximumSize(QtCore.QSize(200, 16777215))
+ self.gridLayout = QtGui.QGridLayout(Form)
+ self.gridLayout.setMargin(0)
+ self.gridLayout.setSpacing(0)
+ self.gridLayout.setObjectName(_fromUtf8("gridLayout"))
+ self.label = QtGui.QLabel(Form)
+ self.label.setObjectName(_fromUtf8("label"))
+ self.gridLayout.addWidget(self.label, 7, 0, 1, 2)
+ self.linkCombo = QtGui.QComboBox(Form)
+ self.linkCombo.setSizeAdjustPolicy(QtGui.QComboBox.AdjustToContents)
+ self.linkCombo.setObjectName(_fromUtf8("linkCombo"))
+ self.gridLayout.addWidget(self.linkCombo, 7, 2, 1, 2)
+ self.autoPercentSpin = QtGui.QSpinBox(Form)
+ self.autoPercentSpin.setEnabled(True)
+ self.autoPercentSpin.setMinimum(1)
+ self.autoPercentSpin.setMaximum(100)
+ self.autoPercentSpin.setSingleStep(1)
+ self.autoPercentSpin.setProperty("value", 100)
+ self.autoPercentSpin.setObjectName(_fromUtf8("autoPercentSpin"))
+ self.gridLayout.addWidget(self.autoPercentSpin, 2, 2, 1, 2)
+ self.autoRadio = QtGui.QRadioButton(Form)
+ self.autoRadio.setChecked(True)
+ self.autoRadio.setObjectName(_fromUtf8("autoRadio"))
+ self.gridLayout.addWidget(self.autoRadio, 2, 0, 1, 2)
+ self.manualRadio = QtGui.QRadioButton(Form)
+ self.manualRadio.setObjectName(_fromUtf8("manualRadio"))
+ self.gridLayout.addWidget(self.manualRadio, 1, 0, 1, 2)
+ self.minText = QtGui.QLineEdit(Form)
+ self.minText.setObjectName(_fromUtf8("minText"))
+ self.gridLayout.addWidget(self.minText, 1, 2, 1, 1)
+ self.maxText = QtGui.QLineEdit(Form)
+ self.maxText.setObjectName(_fromUtf8("maxText"))
+ self.gridLayout.addWidget(self.maxText, 1, 3, 1, 1)
+ self.invertCheck = QtGui.QCheckBox(Form)
+ self.invertCheck.setObjectName(_fromUtf8("invertCheck"))
+ self.gridLayout.addWidget(self.invertCheck, 5, 0, 1, 4)
+ self.mouseCheck = QtGui.QCheckBox(Form)
+ self.mouseCheck.setChecked(True)
+ self.mouseCheck.setObjectName(_fromUtf8("mouseCheck"))
+ self.gridLayout.addWidget(self.mouseCheck, 6, 0, 1, 4)
+ self.visibleOnlyCheck = QtGui.QCheckBox(Form)
+ self.visibleOnlyCheck.setObjectName(_fromUtf8("visibleOnlyCheck"))
+ self.gridLayout.addWidget(self.visibleOnlyCheck, 3, 2, 1, 2)
+ self.autoPanCheck = QtGui.QCheckBox(Form)
+ self.autoPanCheck.setObjectName(_fromUtf8("autoPanCheck"))
+ self.gridLayout.addWidget(self.autoPanCheck, 4, 2, 1, 2)
+
+ self.retranslateUi(Form)
+ QtCore.QMetaObject.connectSlotsByName(Form)
+
+ def retranslateUi(self, Form):
+ Form.setWindowTitle(_translate("Form", "Form", None))
+ self.label.setText(_translate("Form", "Link Axis:", None))
+ self.linkCombo.setToolTip(_translate("Form", "Links this axis with another view. When linked, both views will display the same data range.
", None))
+ self.autoPercentSpin.setToolTip(_translate("Form", "Percent of data to be visible when auto-scaling. It may be useful to decrease this value for data with spiky noise.
", None))
+ self.autoPercentSpin.setSuffix(_translate("Form", "%", None))
+ self.autoRadio.setToolTip(_translate("Form", "Automatically resize this axis whenever the displayed data is changed.
", None))
+ self.autoRadio.setText(_translate("Form", "Auto", None))
+ self.manualRadio.setToolTip(_translate("Form", "Set the range for this axis manually. This disables automatic scaling.
", None))
+ self.manualRadio.setText(_translate("Form", "Manual", None))
+ self.minText.setToolTip(_translate("Form", "Minimum value to display for this axis.
", None))
+ self.minText.setText(_translate("Form", "0", None))
+ self.maxText.setToolTip(_translate("Form", "Maximum value to display for this axis.
", None))
+ self.maxText.setText(_translate("Form", "0", None))
+ self.invertCheck.setToolTip(_translate("Form", "Inverts the display of this axis. (+y points downward instead of upward)
", None))
+ self.invertCheck.setText(_translate("Form", "Invert Axis", None))
+ self.mouseCheck.setToolTip(_translate("Form", "Enables mouse interaction (panning, scaling) for this axis.
", None))
+ self.mouseCheck.setText(_translate("Form", "Mouse Enabled", None))
+ self.visibleOnlyCheck.setToolTip(_translate("Form", "When checked, the axis will only auto-scale to data that is visible along the orthogonal axis.
", None))
+ self.visibleOnlyCheck.setText(_translate("Form", "Visible Data Only", None))
+ self.autoPanCheck.setToolTip(_translate("Form", "When checked, the axis will automatically pan to center on the current data, but the scale along this axis will not change.
", None))
+ self.autoPanCheck.setText(_translate("Form", "Auto Pan Only", None))
+
diff --git a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py
similarity index 97%
rename from pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py
rename to papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py
index 18510bc2..9ddeb5d1 100644
--- a/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py
+++ b/papi/pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate_pyside.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './graphicsItems/ViewBox/axisCtrlTemplate.ui'
+# Form implementation generated from reading ui file './pyqtgraph/graphicsItems/ViewBox/axisCtrlTemplate.ui'
#
-# Created: Sun Sep 9 14:41:32 2012
-# by: pyside-uic 0.2.13 running on PySide 1.1.0
+# Created: Mon Dec 23 10:10:51 2013
+# by: pyside-uic 0.2.14 running on PySide 1.1.2
#
# WARNING! All changes made in this file will be lost!
diff --git a/papi/pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBox.py b/papi/pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBox.py
new file mode 100644
index 00000000..f1063e7f
--- /dev/null
+++ b/papi/pyqtgraph/graphicsItems/ViewBox/tests/test_ViewBox.py
@@ -0,0 +1,85 @@
+#import PySide
+import pyqtgraph as pg
+
+app = pg.mkQApp()
+qtest = pg.Qt.QtTest.QTest
+
+def assertMapping(vb, r1, r2):
+ assert vb.mapFromView(r1.topLeft()) == r2.topLeft()
+ assert vb.mapFromView(r1.bottomLeft()) == r2.bottomLeft()
+ assert vb.mapFromView(r1.topRight()) == r2.topRight()
+ assert vb.mapFromView(r1.bottomRight()) == r2.bottomRight()
+
+def test_ViewBox():
+ global app, win, vb
+ QRectF = pg.QtCore.QRectF
+
+ win = pg.GraphicsWindow()
+ win.ci.layout.setContentsMargins(0,0,0,0)
+ win.resize(200, 200)
+ win.show()
+ vb = win.addViewBox()
+
+ # set range before viewbox is shown
+ vb.setRange(xRange=[0, 10], yRange=[0, 10], padding=0)
+
+ # required to make mapFromView work properly.
+ qtest.qWaitForWindowShown(win)
+
+ g = pg.GridItem()
+ vb.addItem(g)
+
+ app.processEvents()
+
+ w = vb.geometry().width()
+ h = vb.geometry().height()
+ view1 = QRectF(0, 0, 10, 10)
+ size1 = QRectF(0, h, w, -h)
+ assertMapping(vb, view1, size1)
+
+ # test resize
+ win.resize(400, 400)
+ app.processEvents()
+ w = vb.geometry().width()
+ h = vb.geometry().height()
+ size1 = QRectF(0, h, w, -h)
+ assertMapping(vb, view1, size1)
+
+ # now lock aspect
+ vb.setAspectLocked()
+
+ # test wide resize
+ win.resize(800, 400)
+ app.processEvents()
+ w = vb.geometry().width()
+ h = vb.geometry().height()
+ view1 = QRectF(-5, 0, 20, 10)
+ size1 = QRectF(0, h, w, -h)
+ assertMapping(vb, view1, size1)
+
+ # test tall resize
+ win.resize(400, 800)
+ app.processEvents()
+ w = vb.geometry().width()
+ h = vb.geometry().height()
+ view1 = QRectF(0, -5, 10, 20)
+ size1 = QRectF(0, h, w, -h)
+ assertMapping(vb, view1, size1)
+
+ # test limits + resize (aspect ratio constraint has priority over limits
+ win.resize(400, 400)
+ app.processEvents()
+ vb.setLimits(xMin=0, xMax=10, yMin=0, yMax=10)
+ win.resize(800, 400)
+ app.processEvents()
+ w = vb.geometry().width()
+ h = vb.geometry().height()
+ view1 = QRectF(-5, 0, 20, 10)
+ size1 = QRectF(0, h, w, -h)
+ assertMapping(vb, view1, size1)
+
+
+if __name__ == '__main__':
+ import user,sys
+ test_ViewBox()
+
\ No newline at end of file
diff --git a/pyqtgraph/graphicsItems/__init__.py b/papi/pyqtgraph/graphicsItems/__init__.py
similarity index 100%
rename from pyqtgraph/graphicsItems/__init__.py
rename to papi/pyqtgraph/graphicsItems/__init__.py
diff --git a/papi/pyqtgraph/graphicsItems/tests/test_GraphicsItem.py b/papi/pyqtgraph/graphicsItems/tests/test_GraphicsItem.py
new file mode 100644
index 00000000..112dd4d5
--- /dev/null
+++ b/papi/pyqtgraph/graphicsItems/tests/test_GraphicsItem.py
@@ -0,0 +1,47 @@
+import gc
+import weakref
+try:
+ import faulthandler
+ faulthandler.enable()
+except ImportError:
+ pass
+
+import pyqtgraph as pg
+pg.mkQApp()
+
+def test_getViewWidget():
+ view = pg.PlotWidget()
+ vref = weakref.ref(view)
+ item = pg.InfiniteLine()
+ view.addItem(item)
+ assert item.getViewWidget() is view
+ del view
+ gc.collect()
+ assert vref() is None
+ assert item.getViewWidget() is None
+
+def test_getViewWidget_deleted():
+ view = pg.PlotWidget()
+ item = pg.InfiniteLine()
+ view.addItem(item)
+ assert item.getViewWidget() is view
+
+ # Arrange to have Qt automatically delete the view widget
+ obj = pg.QtGui.QWidget()
+ view.setParent(obj)
+ del obj
+ gc.collect()
+
+ assert not pg.Qt.isQObjectAlive(view)
+ assert item.getViewWidget() is None
+
+
+#if __name__ == '__main__':
+ #view = pg.PlotItem()
+ #vref = weakref.ref(view)
+ #item = pg.InfiniteLine()
+ #view.addItem(item)
+ #del view
+ #gc.collect()
+
+
\ No newline at end of file
diff --git a/papi/pyqtgraph/graphicsItems/tests/test_ScatterPlotItem.py b/papi/pyqtgraph/graphicsItems/tests/test_ScatterPlotItem.py
new file mode 100644
index 00000000..8b0ebc8f
--- /dev/null
+++ b/papi/pyqtgraph/graphicsItems/tests/test_ScatterPlotItem.py
@@ -0,0 +1,86 @@
+import pyqtgraph as pg
+import numpy as np
+app = pg.mkQApp()
+plot = pg.plot()
+app.processEvents()
+
+# set view range equal to its bounding rect.
+# This causes plots to look the same regardless of pxMode.
+plot.setRange(rect=plot.boundingRect())
+
+
+def test_scatterplotitem():
+ for i, pxMode in enumerate([True, False]):
+ for j, useCache in enumerate([True, False]):
+ s = pg.ScatterPlotItem()
+ s.opts['useCache'] = useCache
+ plot.addItem(s)
+ s.setData(x=np.array([10,40,20,30])+i*100, y=np.array([40,60,10,30])+j*100, pxMode=pxMode)
+ s.addPoints(x=np.array([60, 70])+i*100, y=np.array([60, 70])+j*100, size=[20, 30])
+
+ # Test uniform spot updates
+ s.setSize(10)
+ s.setBrush('r')
+ s.setPen('g')
+ s.setSymbol('+')
+ app.processEvents()
+
+ # Test list spot updates
+ s.setSize([10] * 6)
+ s.setBrush([pg.mkBrush('r')] * 6)
+ s.setPen([pg.mkPen('g')] * 6)
+ s.setSymbol(['+'] * 6)
+ s.setPointData([s] * 6)
+ app.processEvents()
+
+ # Test array spot updates
+ s.setSize(np.array([10] * 6))
+ s.setBrush(np.array([pg.mkBrush('r')] * 6))
+ s.setPen(np.array([pg.mkPen('g')] * 6))
+ s.setSymbol(np.array(['+'] * 6))
+ s.setPointData(np.array([s] * 6))
+ app.processEvents()
+
+ # Test per-spot updates
+ spot = s.points()[0]
+ spot.setSize(20)
+ spot.setBrush('b')
+ spot.setPen('g')
+ spot.setSymbol('o')
+ spot.setData(None)
+ app.processEvents()
+
+ plot.clear()
+
+
+def test_init_spots():
+ spots = [
+ {'x': 0, 'y': 1},
+ {'pos': (1, 2), 'pen': None, 'brush': None, 'data': 'zzz'},
+ ]
+ s = pg.ScatterPlotItem(spots=spots)
+
+ # Check we can display without errors
+ plot.addItem(s)
+ app.processEvents()
+ plot.clear()
+
+ # check data is correct
+ spots = s.points()
+
+ defPen = pg.mkPen(pg.getConfigOption('foreground'))
+
+ assert spots[0].pos().x() == 0
+ assert spots[0].pos().y() == 1
+ assert spots[0].pen() == defPen
+ assert spots[0].data() is None
+
+ assert spots[1].pos().x() == 1
+ assert spots[1].pos().y() == 2
+ assert spots[1].pen() == pg.mkPen(None)
+ assert spots[1].brush() == pg.mkBrush(None)
+ assert spots[1].data() == 'zzz'
+
+
+if __name__ == '__main__':
+ test_scatterplotitem()
diff --git a/pyqtgraph/graphicsWindows.py b/papi/pyqtgraph/graphicsWindows.py
similarity index 93%
rename from pyqtgraph/graphicsWindows.py
rename to papi/pyqtgraph/graphicsWindows.py
index 6e7d6305..1aa3f3f4 100644
--- a/pyqtgraph/graphicsWindows.py
+++ b/papi/pyqtgraph/graphicsWindows.py
@@ -19,11 +19,14 @@ def mkQApp():
class GraphicsWindow(GraphicsLayoutWidget):
+ """
+ Convenience subclass of :class:`GraphicsLayoutWidget
+ `. This class is intended for use from
+ the interactive python prompt.
+ """
def __init__(self, title=None, size=(800,600), **kargs):
mkQApp()
- #self.win = QtGui.QMainWindow()
GraphicsLayoutWidget.__init__(self, **kargs)
- #self.win.setCentralWidget(self)
self.resize(*size)
if title is not None:
self.setWindowTitle(title)
diff --git a/pyqtgraph/imageview/ImageView.py b/papi/pyqtgraph/imageview/ImageView.py
similarity index 81%
rename from pyqtgraph/imageview/ImageView.py
rename to papi/pyqtgraph/imageview/ImageView.py
index 77f34419..65252cfe 100644
--- a/pyqtgraph/imageview/ImageView.py
+++ b/papi/pyqtgraph/imageview/ImageView.py
@@ -12,32 +12,28 @@
- ROI plotting
- Image normalization through a variety of methods
"""
-from pyqtgraph.Qt import QtCore, QtGui, USE_PYSIDE
+import os, sys
+import numpy as np
+from ..Qt import QtCore, QtGui, USE_PYSIDE
if USE_PYSIDE:
from .ImageViewTemplate_pyside import *
else:
from .ImageViewTemplate_pyqt import *
-from pyqtgraph.graphicsItems.ImageItem import *
-from pyqtgraph.graphicsItems.ROI import *
-from pyqtgraph.graphicsItems.LinearRegionItem import *
-from pyqtgraph.graphicsItems.InfiniteLine import *
-from pyqtgraph.graphicsItems.ViewBox import *
-#from widgets import ROI
-import sys
-#from numpy import ndarray
-import pyqtgraph.ptime as ptime
-import numpy as np
-import pyqtgraph.debug as debug
-
-from pyqtgraph.SignalProxy import SignalProxy
+from ..graphicsItems.ImageItem import *
+from ..graphicsItems.ROI import *
+from ..graphicsItems.LinearRegionItem import *
+from ..graphicsItems.InfiniteLine import *
+from ..graphicsItems.ViewBox import *
+from .. import ptime as ptime
+from .. import debug as debug
+from ..SignalProxy import SignalProxy
-#try:
- #import pyqtgraph.metaarray as metaarray
- #HAVE_METAARRAY = True
-#except:
- #HAVE_METAARRAY = False
+try:
+ from bottleneck import nanmin, nanmax
+except ImportError:
+ from numpy import nanmin, nanmax
class PlotROI(ROI):
@@ -67,6 +63,16 @@ class ImageView(QtGui.QWidget):
imv = pg.ImageView()
imv.show()
imv.setImage(data)
+
+ **Keyboard interaction**
+
+ * left/right arrows step forward/backward 1 frame when pressed,
+ seek at 20fps when held.
+ * up/down arrows seek at 100fps
+ * pgup/pgdn seek at 1000fps
+ * home/end seek immediately to the first/last frame
+ * space begins playing frames. If time values (in seconds) are given
+ for each frame, then playback is in realtime.
"""
sigTimeChanged = QtCore.Signal(object, object)
sigProcessingChanged = QtCore.Signal(object)
@@ -74,8 +80,31 @@ class ImageView(QtGui.QWidget):
def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *args):
"""
By default, this class creates an :class:`ImageItem ` to display image data
- and a :class:`ViewBox ` to contain the ImageItem. Custom items may be given instead
- by specifying the *view* and/or *imageItem* arguments.
+ and a :class:`ViewBox ` to contain the ImageItem.
+
+ ============= =========================================================
+ **Arguments**
+ parent (QWidget) Specifies the parent widget to which
+ this ImageView will belong. If None, then the ImageView
+ is created with no parent.
+ name (str) The name used to register both the internal ViewBox
+ and the PlotItem used to display ROI data. See the *name*
+ argument to :func:`ViewBox.__init__()
+ `.
+ view (ViewBox or PlotItem) If specified, this will be used
+ as the display area that contains the displayed image.
+ Any :class:`ViewBox `,
+ :class:`PlotItem `, or other
+ compatible object is acceptable.
+ imageItem (ImageItem) If specified, this object will be used to
+ display the image. Must be an instance of ImageItem
+ or other compatible object.
+ ============= =========================================================
+
+ Note: to display axis ticks inside the ImageView, instantiate it
+ with a PlotItem instance as its view::
+
+ pg.ImageView(view=pg.PlotItem())
"""
QtGui.QWidget.__init__(self, parent, *args)
self.levelMax = 4096
@@ -107,6 +136,8 @@ def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *ar
self.ui.histogram.setImageItem(self.imageItem)
+ self.menu = None
+
self.ui.normGroup.hide()
self.roi = PlotROI(10)
@@ -147,7 +178,8 @@ def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *ar
self.timeLine.sigPositionChanged.connect(self.timeLineChanged)
self.ui.roiBtn.clicked.connect(self.roiClicked)
self.roi.sigRegionChanged.connect(self.roiChanged)
- self.ui.normBtn.toggled.connect(self.normToggled)
+ #self.ui.normBtn.toggled.connect(self.normToggled)
+ self.ui.menuBtn.clicked.connect(self.menuClicked)
self.ui.normDivideRadio.clicked.connect(self.normRadioChanged)
self.ui.normSubtractRadio.clicked.connect(self.normRadioChanged)
self.ui.normOffRadio.clicked.connect(self.normRadioChanged)
@@ -160,6 +192,7 @@ def __init__(self, parent=None, name="ImageView", view=None, imageItem=None, *ar
self.normRoi.sigRegionChangeFinished.connect(self.updateNorm)
self.ui.roiPlot.registerPlot(self.name + '_ROI')
+ self.view.register(self.name)
self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]
@@ -190,14 +223,20 @@ def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None,
image data.
================== =======================================================================
"""
- prof = debug.Profiler('ImageView.setImage', disabled=True)
+ profiler = debug.Profiler()
if hasattr(img, 'implements') and img.implements('MetaArray'):
img = img.asarray()
if not isinstance(img, np.ndarray):
- raise Exception("Image must be specified as ndarray.")
+ required = ['dtype', 'max', 'min', 'ndim', 'shape', 'size']
+ if not all([hasattr(img, attr) for attr in required]):
+ raise TypeError("Image must be NumPy array or any object "
+ "that provides compatible attributes/methods:\n"
+ " %s" % str(required))
+
self.image = img
+ self.imageDisp = None
if xvals is not None:
self.tVals = xvals
@@ -209,7 +248,7 @@ def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None,
else:
self.tVals = np.arange(img.shape[0])
- prof.mark('1')
+ profiler()
if axes is None:
if img.ndim == 2:
@@ -234,13 +273,9 @@ def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None,
for x in ['t', 'x', 'y', 'c']:
self.axes[x] = self.axes.get(x, None)
- prof.mark('2')
-
- self.imageDisp = None
-
-
- prof.mark('3')
-
+
+ profiler()
+
self.currentIndex = 0
self.updateImage(autoHistogramRange=autoHistogramRange)
if levels is None and autoLevels:
@@ -250,9 +285,9 @@ def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None,
if self.ui.roiBtn.isChecked():
self.roiChanged()
- prof.mark('4')
-
-
+
+ profiler()
+
if self.axes['t'] is not None:
#self.ui.roiPlot.show()
self.ui.roiPlot.setXRange(self.tVals.min(), self.tVals.max())
@@ -271,8 +306,8 @@ def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None,
s.setBounds([start, stop])
#else:
#self.ui.roiPlot.hide()
- prof.mark('5')
-
+ profiler()
+
self.imageItem.resetTransform()
if scale is not None:
self.imageItem.scale(*scale)
@@ -280,14 +315,18 @@ def setImage(self, img, autoRange=True, autoLevels=True, levels=None, axes=None,
self.imageItem.setPos(*pos)
if transform is not None:
self.imageItem.setTransform(transform)
- prof.mark('6')
-
+
+ profiler()
+
if autoRange:
self.autoRange()
self.roiClicked()
- prof.mark('7')
- prof.finish()
+ profiler()
+
+ def clear(self):
+ self.image = None
+ self.imageItem.clear()
def play(self, rate):
"""Begin automatically stepping frames forward at the given rate (in fps).
@@ -311,7 +350,7 @@ def setLevels(self, min, max):
self.ui.histogram.setLevels(min, max)
def autoRange(self):
- """Auto scale and pan the view around the image."""
+ """Auto scale and pan the view around the image such that the image fills the view."""
image = self.getProcessedImage()
self.view.autoRange()
@@ -322,11 +361,10 @@ def getProcessedImage(self):
if self.imageDisp is None:
image = self.normalize(self.image)
self.imageDisp = image
- self.levelMin, self.levelMax = list(map(float, ImageView.quickMinMax(self.imageDisp)))
+ self.levelMin, self.levelMax = list(map(float, self.quickMinMax(self.imageDisp)))
return self.imageDisp
-
def close(self):
"""Closes the widget nicely, making sure to clear the graphics scene and release memory."""
self.ui.roiPlot.close()
@@ -378,7 +416,6 @@ def keyReleaseEvent(self, ev):
else:
QtGui.QWidget.keyReleaseEvent(self, ev)
-
def evalKeyState(self):
if len(self.keysPressed) == 1:
key = list(self.keysPressed.keys())[0]
@@ -402,16 +439,13 @@ def evalKeyState(self):
else:
self.play(0)
-
def timeout(self):
now = ptime.time()
dt = now - self.lastPlayTime
if dt < 0:
return
n = int(self.playRate * dt)
- #print n, dt
if n != 0:
- #print n, dt, self.lastPlayTime
self.lastPlayTime += (float(n)/self.playRate)
if self.currentIndex+n > self.image.shape[0]:
self.play(0)
@@ -436,17 +470,14 @@ def normRadioChanged(self):
self.autoLevels()
self.roiChanged()
self.sigProcessingChanged.emit(self)
-
def updateNorm(self):
if self.ui.normTimeRangeCheck.isChecked():
- #print "show!"
self.normRgn.show()
else:
self.normRgn.hide()
if self.ui.normROICheck.isChecked():
- #print "show!"
self.normRoi.show()
else:
self.normRoi.hide()
@@ -522,21 +553,25 @@ def roiChanged(self):
coords = coords - coords[:,0,np.newaxis]
xvals = (coords**2).sum(axis=0) ** 0.5
self.roiCurve.setData(y=data, x=xvals)
-
- #self.ui.roiPlot.replot()
-
- @staticmethod
- def quickMinMax(data):
+ def quickMinMax(self, data):
+ """
+ Estimate the min/max values of *data* by subsampling.
+ """
while data.size > 1e6:
ax = np.argmax(data.shape)
sl = [slice(None)] * data.ndim
sl[ax] = slice(None, None, 2)
data = data[sl]
- return data.min(), data.max()
+ return nanmin(data), nanmax(data)
def normalize(self, image):
+ """
+ Process *image* using the normalization options configured in the
+ control panel.
+ This can be repurposed to process any data through the same filter.
+ """
if self.ui.normOffRadio.isChecked():
return image
@@ -643,3 +678,43 @@ def getRoiPlot(self):
def getHistogramWidget(self):
"""Return the HistogramLUTWidget for this ImageView"""
return self.ui.histogram
+
+ def export(self, fileName):
+ """
+ Export data from the ImageView to a file, or to a stack of files if
+ the data is 3D. Saving an image stack will result in index numbers
+ being added to the file name. Images are saved as they would appear
+ onscreen, with levels and lookup table applied.
+ """
+ img = self.getProcessedImage()
+ if self.hasTimeAxis():
+ base, ext = os.path.splitext(fileName)
+ fmt = "%%s%%0%dd%%s" % int(np.log10(img.shape[0])+1)
+ for i in range(img.shape[0]):
+ self.imageItem.setImage(img[i], autoLevels=False)
+ self.imageItem.save(fmt % (base, i, ext))
+ self.updateImage()
+ else:
+ self.imageItem.save(fileName)
+
+ def exportClicked(self):
+ fileName = QtGui.QFileDialog.getSaveFileName()
+ if fileName == '':
+ return
+ self.export(fileName)
+
+ def buildMenu(self):
+ self.menu = QtGui.QMenu()
+ self.normAction = QtGui.QAction("Normalization", self.menu)
+ self.normAction.setCheckable(True)
+ self.normAction.toggled.connect(self.normToggled)
+ self.menu.addAction(self.normAction)
+ self.exportAction = QtGui.QAction("Export", self.menu)
+ self.exportAction.triggered.connect(self.exportClicked)
+ self.menu.addAction(self.exportAction)
+
+ def menuClicked(self):
+ if self.menu is None:
+ self.buildMenu()
+ self.menu.popup(QtGui.QCursor.pos())
+
diff --git a/pyqtgraph/imageview/ImageViewTemplate.ui b/papi/pyqtgraph/imageview/ImageViewTemplate.ui
similarity index 95%
rename from pyqtgraph/imageview/ImageViewTemplate.ui
rename to papi/pyqtgraph/imageview/ImageViewTemplate.ui
index 497c0c59..927bda30 100644
--- a/pyqtgraph/imageview/ImageViewTemplate.ui
+++ b/papi/pyqtgraph/imageview/ImageViewTemplate.ui
@@ -53,7 +53,7 @@
-
-
+
0
@@ -61,10 +61,7 @@
- Norm
-
-
- true
+ Menu
@@ -233,18 +230,18 @@
PlotWidget
QWidget
- pyqtgraph.widgets.PlotWidget
+
1
GraphicsView
QGraphicsView
- pyqtgraph.widgets.GraphicsView
+
HistogramLUTWidget
QGraphicsView
- pyqtgraph.widgets.HistogramLUTWidget
+ ..widgets.HistogramLUTWidget
diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyqt.py b/papi/pyqtgraph/imageview/ImageViewTemplate_pyqt.py
similarity index 72%
rename from pyqtgraph/imageview/ImageViewTemplate_pyqt.py
rename to papi/pyqtgraph/imageview/ImageViewTemplate_pyqt.py
index e6423276..e728b265 100644
--- a/pyqtgraph/imageview/ImageViewTemplate_pyqt.py
+++ b/papi/pyqtgraph/imageview/ImageViewTemplate_pyqt.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './imageview/ImageViewTemplate.ui'
+# Form implementation generated from reading ui file 'ImageViewTemplate.ui'
#
-# Created: Sun Sep 9 14:41:30 2012
-# by: PyQt4 UI code generator 4.9.1
+# Created: Thu May 1 15:20:40 2014
+# by: PyQt4 UI code generator 4.10.4
#
# WARNING! All changes made in this file will be lost!
@@ -12,7 +12,16 @@
try:
_fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
- _fromUtf8 = lambda s: s
+ def _fromUtf8(s):
+ return s
+
+try:
+ _encoding = QtGui.QApplication.UnicodeUTF8
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig, _encoding)
+except AttributeError:
+ def _translate(context, text, disambig):
+ return QtGui.QApplication.translate(context, text, disambig)
class Ui_Form(object):
def setupUi(self, Form):
@@ -46,15 +55,14 @@ def setupUi(self, Form):
self.roiBtn.setCheckable(True)
self.roiBtn.setObjectName(_fromUtf8("roiBtn"))
self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1)
- self.normBtn = QtGui.QPushButton(self.layoutWidget)
+ self.menuBtn = QtGui.QPushButton(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1)
- sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth())
- self.normBtn.setSizePolicy(sizePolicy)
- self.normBtn.setCheckable(True)
- self.normBtn.setObjectName(_fromUtf8("normBtn"))
- self.gridLayout.addWidget(self.normBtn, 1, 2, 1, 1)
+ sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth())
+ self.menuBtn.setSizePolicy(sizePolicy)
+ self.menuBtn.setObjectName(_fromUtf8("menuBtn"))
+ self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1)
self.roiPlot = PlotWidget(self.splitter)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -138,23 +146,23 @@ def setupUi(self, Form):
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
- Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
- self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8))
- self.normBtn.setText(QtGui.QApplication.translate("Form", "Norm", None, QtGui.QApplication.UnicodeUTF8))
- self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8))
- self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8))
- self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8))
- self.label_5.setText(QtGui.QApplication.translate("Form", "Operation:", None, QtGui.QApplication.UnicodeUTF8))
- self.label_3.setText(QtGui.QApplication.translate("Form", "Mean:", None, QtGui.QApplication.UnicodeUTF8))
- self.label_4.setText(QtGui.QApplication.translate("Form", "Blur:", None, QtGui.QApplication.UnicodeUTF8))
- self.normROICheck.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8))
- self.label_8.setText(QtGui.QApplication.translate("Form", "X", None, QtGui.QApplication.UnicodeUTF8))
- self.label_9.setText(QtGui.QApplication.translate("Form", "Y", None, QtGui.QApplication.UnicodeUTF8))
- self.label_10.setText(QtGui.QApplication.translate("Form", "T", None, QtGui.QApplication.UnicodeUTF8))
- self.normOffRadio.setText(QtGui.QApplication.translate("Form", "Off", None, QtGui.QApplication.UnicodeUTF8))
- self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8))
- self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8))
+ Form.setWindowTitle(_translate("Form", "Form", None))
+ self.roiBtn.setText(_translate("Form", "ROI", None))
+ self.menuBtn.setText(_translate("Form", "Menu", None))
+ self.normGroup.setTitle(_translate("Form", "Normalization", None))
+ self.normSubtractRadio.setText(_translate("Form", "Subtract", None))
+ self.normDivideRadio.setText(_translate("Form", "Divide", None))
+ self.label_5.setText(_translate("Form", "Operation:", None))
+ self.label_3.setText(_translate("Form", "Mean:", None))
+ self.label_4.setText(_translate("Form", "Blur:", None))
+ self.normROICheck.setText(_translate("Form", "ROI", None))
+ self.label_8.setText(_translate("Form", "X", None))
+ self.label_9.setText(_translate("Form", "Y", None))
+ self.label_10.setText(_translate("Form", "T", None))
+ self.normOffRadio.setText(_translate("Form", "Off", None))
+ self.normTimeRangeCheck.setText(_translate("Form", "Time range", None))
+ self.normFrameCheck.setText(_translate("Form", "Frame", None))
-from pyqtgraph.widgets.GraphicsView import GraphicsView
-from pyqtgraph.widgets.PlotWidget import PlotWidget
-from pyqtgraph.widgets.HistogramLUTWidget import HistogramLUTWidget
+from ..widgets.HistogramLUTWidget import HistogramLUTWidget
+from ..widgets.GraphicsView import GraphicsView
+from ..widgets.PlotWidget import PlotWidget
diff --git a/pyqtgraph/imageview/ImageViewTemplate_pyside.py b/papi/pyqtgraph/imageview/ImageViewTemplate_pyside.py
similarity index 91%
rename from pyqtgraph/imageview/ImageViewTemplate_pyside.py
rename to papi/pyqtgraph/imageview/ImageViewTemplate_pyside.py
index c17bbfe1..6d6c9632 100644
--- a/pyqtgraph/imageview/ImageViewTemplate_pyside.py
+++ b/papi/pyqtgraph/imageview/ImageViewTemplate_pyside.py
@@ -1,9 +1,9 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file './imageview/ImageViewTemplate.ui'
+# Form implementation generated from reading ui file 'ImageViewTemplate.ui'
#
-# Created: Sun Sep 9 14:41:31 2012
-# by: pyside-uic 0.2.13 running on PySide 1.1.0
+# Created: Thu May 1 15:20:42 2014
+# by: pyside-uic 0.2.15 running on PySide 1.2.1
#
# WARNING! All changes made in this file will be lost!
@@ -41,15 +41,14 @@ def setupUi(self, Form):
self.roiBtn.setCheckable(True)
self.roiBtn.setObjectName("roiBtn")
self.gridLayout.addWidget(self.roiBtn, 1, 1, 1, 1)
- self.normBtn = QtGui.QPushButton(self.layoutWidget)
+ self.menuBtn = QtGui.QPushButton(self.layoutWidget)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Fixed)
sizePolicy.setHorizontalStretch(0)
sizePolicy.setVerticalStretch(1)
- sizePolicy.setHeightForWidth(self.normBtn.sizePolicy().hasHeightForWidth())
- self.normBtn.setSizePolicy(sizePolicy)
- self.normBtn.setCheckable(True)
- self.normBtn.setObjectName("normBtn")
- self.gridLayout.addWidget(self.normBtn, 1, 2, 1, 1)
+ sizePolicy.setHeightForWidth(self.menuBtn.sizePolicy().hasHeightForWidth())
+ self.menuBtn.setSizePolicy(sizePolicy)
+ self.menuBtn.setObjectName("menuBtn")
+ self.gridLayout.addWidget(self.menuBtn, 1, 2, 1, 1)
self.roiPlot = PlotWidget(self.splitter)
sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Preferred)
sizePolicy.setHorizontalStretch(0)
@@ -135,7 +134,7 @@ def setupUi(self, Form):
def retranslateUi(self, Form):
Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8))
self.roiBtn.setText(QtGui.QApplication.translate("Form", "ROI", None, QtGui.QApplication.UnicodeUTF8))
- self.normBtn.setText(QtGui.QApplication.translate("Form", "Norm", None, QtGui.QApplication.UnicodeUTF8))
+ self.menuBtn.setText(QtGui.QApplication.translate("Form", "Menu", None, QtGui.QApplication.UnicodeUTF8))
self.normGroup.setTitle(QtGui.QApplication.translate("Form", "Normalization", None, QtGui.QApplication.UnicodeUTF8))
self.normSubtractRadio.setText(QtGui.QApplication.translate("Form", "Subtract", None, QtGui.QApplication.UnicodeUTF8))
self.normDivideRadio.setText(QtGui.QApplication.translate("Form", "Divide", None, QtGui.QApplication.UnicodeUTF8))
@@ -150,6 +149,6 @@ def retranslateUi(self, Form):
self.normTimeRangeCheck.setText(QtGui.QApplication.translate("Form", "Time range", None, QtGui.QApplication.UnicodeUTF8))
self.normFrameCheck.setText(QtGui.QApplication.translate("Form", "Frame", None, QtGui.QApplication.UnicodeUTF8))
-from pyqtgraph.widgets.GraphicsView import GraphicsView
-from pyqtgraph.widgets.PlotWidget import PlotWidget
-from pyqtgraph.widgets.HistogramLUTWidget import HistogramLUTWidget
+from ..widgets.HistogramLUTWidget import HistogramLUTWidget
+from ..widgets.GraphicsView import GraphicsView
+from ..widgets.PlotWidget import PlotWidget
diff --git a/pyqtgraph/imageview/__init__.py b/papi/pyqtgraph/imageview/__init__.py
similarity index 100%
rename from pyqtgraph/imageview/__init__.py
rename to papi/pyqtgraph/imageview/__init__.py
diff --git a/papi/pyqtgraph/imageview/tests/test_imageview.py b/papi/pyqtgraph/imageview/tests/test_imageview.py
new file mode 100644
index 00000000..2ca1712c
--- /dev/null
+++ b/papi/pyqtgraph/imageview/tests/test_imageview.py
@@ -0,0 +1,11 @@
+import pyqtgraph as pg
+import numpy as np
+
+app = pg.mkQApp()
+
+def test_nan_image():
+ img = np.ones((10,10))
+ img[0,0] = np.nan
+ v = pg.image(img)
+ app.processEvents()
+ v.window().close()
diff --git a/pyqtgraph/metaarray/MetaArray.py b/papi/pyqtgraph/metaarray/MetaArray.py
similarity index 96%
rename from pyqtgraph/metaarray/MetaArray.py
rename to papi/pyqtgraph/metaarray/MetaArray.py
index f55c60dc..9c3f5b8a 100644
--- a/pyqtgraph/metaarray/MetaArray.py
+++ b/papi/pyqtgraph/metaarray/MetaArray.py
@@ -103,6 +103,14 @@ class MetaArray(object):
"""
version = '2'
+
+ # Default hdf5 compression to use when writing
+ # 'gzip' is widely available and somewhat slow
+ # 'lzf' is faster, but generally not available outside h5py
+ # 'szip' is also faster, but lacks write support on windows
+ # (so by default, we use no compression)
+ # May also be a tuple (filter, opts), such as ('gzip', 3)
+ defaultCompression = None
## Types allowed as axis or column names
nameTypes = [basestring, tuple]
@@ -122,7 +130,7 @@ def __init__(self, data=None, info=None, dtype=None, file=None, copy=False, **kw
if file is not None:
self._data = None
self.readFile(file, **kwargs)
- if self._data is None:
+ if kwargs.get("readAllData", True) and self._data is None:
raise Exception("File read failed: %s" % file)
else:
self._info = info
@@ -720,25 +728,28 @@ def readFile(self, filename, **kwargs):
"""
## decide which read function to use
- fd = open(filename, 'rb')
- magic = fd.read(8)
- if magic == '\x89HDF\r\n\x1a\n':
- fd.close()
- self._readHDF5(filename, **kwargs)
- self._isHDF = True
- else:
- fd.seek(0)
- meta = MetaArray._readMeta(fd)
- if 'version' in meta:
- ver = meta['version']
+ with open(filename, 'rb') as fd:
+ magic = fd.read(8)
+ if magic == '\x89HDF\r\n\x1a\n':
+ fd.close()
+ self._readHDF5(filename, **kwargs)
+ self._isHDF = True
else:
- ver = 1
- rFuncName = '_readData%s' % str(ver)
- if not hasattr(MetaArray, rFuncName):
- raise Exception("This MetaArray library does not support array version '%s'" % ver)
- rFunc = getattr(self, rFuncName)
- rFunc(fd, meta, **kwargs)
- self._isHDF = False
+ fd.seek(0)
+ meta = MetaArray._readMeta(fd)
+
+ if not kwargs.get("readAllData", True):
+ self._data = np.empty(meta['shape'], dtype=meta['type'])
+ if 'version' in meta:
+ ver = meta['version']
+ else:
+ ver = 1
+ rFuncName = '_readData%s' % str(ver)
+ if not hasattr(MetaArray, rFuncName):
+ raise Exception("This MetaArray library does not support array version '%s'" % ver)
+ rFunc = getattr(self, rFuncName)
+ rFunc(fd, meta, **kwargs)
+ self._isHDF = False
@staticmethod
def _readMeta(fd):
@@ -756,7 +767,7 @@ def _readMeta(fd):
#print ret
return ret
- def _readData1(self, fd, meta, mmap=False):
+ def _readData1(self, fd, meta, mmap=False, **kwds):
## Read array data from the file descriptor for MetaArray v1 files
## read in axis values for any axis that specifies a length
frameSize = 1
@@ -766,16 +777,18 @@ def _readData1(self, fd, meta, mmap=False):
frameSize *= ax['values_len']
del ax['values_len']
del ax['values_type']
+ self._info = meta['info']
+ if not kwds.get("readAllData", True):
+ return
## the remaining data is the actual array
if mmap:
subarr = np.memmap(fd, dtype=meta['type'], mode='r', shape=meta['shape'])
else:
subarr = np.fromstring(fd.read(), dtype=meta['type'])
subarr.shape = meta['shape']
- self._info = meta['info']
self._data = subarr
- def _readData2(self, fd, meta, mmap=False, subset=None):
+ def _readData2(self, fd, meta, mmap=False, subset=None, **kwds):
## read in axis values
dynAxis = None
frameSize = 1
@@ -792,7 +805,10 @@ def _readData2(self, fd, meta, mmap=False, subset=None):
frameSize *= ax['values_len']
del ax['values_len']
del ax['values_type']
-
+ self._info = meta['info']
+ if not kwds.get("readAllData", True):
+ return
+
## No axes are dynamic, just read the entire array in at once
if dynAxis is None:
#if rewriteDynamic is not None:
@@ -929,7 +945,7 @@ def _readHDF5Remote(self, fileName):
if proc == False:
raise Exception('remote read failed')
if proc == None:
- import pyqtgraph.multiprocess as mp
+ from .. import multiprocess as mp
#print "new process"
proc = mp.Process(executable='/usr/bin/python')
proc.setProxyOptions(deferGetattr=True)
@@ -1027,10 +1043,18 @@ def writeMeta(self, fileName):
def writeHDF5(self, fileName, **opts):
## default options for writing datasets
+ comp = self.defaultCompression
+ if isinstance(comp, tuple):
+ comp, copts = comp
+ else:
+ copts = None
+
dsOpts = {
- 'compression': 'lzf',
+ 'compression': comp,
'chunks': True,
}
+ if copts is not None:
+ dsOpts['compression_opts'] = copts
## if there is an appendable axis, then we can guess the desired chunk shape (optimized for appending)
appAxis = opts.get('appendAxis', None)
@@ -1471,4 +1495,4 @@ def writeCsv(self, fileName=None):
ma2 = MetaArray(file=tf, mmap=True)
print("\nArrays are equivalent:", (ma == ma2).all())
os.remove(tf)
-
\ No newline at end of file
+
diff --git a/pyqtgraph/metaarray/__init__.py b/papi/pyqtgraph/metaarray/__init__.py
similarity index 100%
rename from pyqtgraph/metaarray/__init__.py
rename to papi/pyqtgraph/metaarray/__init__.py
diff --git a/pyqtgraph/metaarray/license.txt b/papi/pyqtgraph/metaarray/license.txt
similarity index 100%
rename from pyqtgraph/metaarray/license.txt
rename to papi/pyqtgraph/metaarray/license.txt
diff --git a/pyqtgraph/metaarray/readMeta.m b/papi/pyqtgraph/metaarray/readMeta.m
similarity index 100%
rename from pyqtgraph/metaarray/readMeta.m
rename to papi/pyqtgraph/metaarray/readMeta.m
diff --git a/pyqtgraph/multiprocess/__init__.py b/papi/pyqtgraph/multiprocess/__init__.py
similarity index 100%
rename from pyqtgraph/multiprocess/__init__.py
rename to papi/pyqtgraph/multiprocess/__init__.py
diff --git a/pyqtgraph/multiprocess/bootstrap.py b/papi/pyqtgraph/multiprocess/bootstrap.py
similarity index 100%
rename from pyqtgraph/multiprocess/bootstrap.py
rename to papi/pyqtgraph/multiprocess/bootstrap.py
diff --git a/pyqtgraph/multiprocess/parallelizer.py b/papi/pyqtgraph/multiprocess/parallelizer.py
similarity index 98%
rename from pyqtgraph/multiprocess/parallelizer.py
rename to papi/pyqtgraph/multiprocess/parallelizer.py
index 659b5efc..f4ddd95c 100644
--- a/pyqtgraph/multiprocess/parallelizer.py
+++ b/papi/pyqtgraph/multiprocess/parallelizer.py
@@ -40,7 +40,7 @@ class Parallelize(object):
def __init__(self, tasks=None, workers=None, block=True, progressDialog=None, randomReseed=True, **kwds):
"""
=============== ===================================================================
- Arguments:
+ **Arguments:**
tasks list of objects to be processed (Parallelize will determine how to
distribute the tasks). If unspecified, then each worker will receive
a single task with a unique id number.
@@ -63,8 +63,8 @@ def __init__(self, tasks=None, workers=None, block=True, progressDialog=None, ra
self.showProgress = True
if isinstance(progressDialog, basestring):
progressDialog = {'labelText': progressDialog}
- import pyqtgraph as pg
- self.progressDlg = pg.ProgressDialog(**progressDialog)
+ from ..widgets.ProgressDialog import ProgressDialog
+ self.progressDlg = ProgressDialog(**progressDialog)
if workers is None:
workers = self.suggestedWorkerCount()
@@ -304,7 +304,7 @@ def numWorkers(self):
#def __enter__(self):
#self.childs = []
#for i in range(1, self.n):
- #c1, c2 = multiprocessingTest.Pipe()
+ #c1, c2 = multiprocessing.Pipe()
#pid = os.fork()
#if pid == 0: ## child
#self.par.i = i
diff --git a/pyqtgraph/multiprocess/processes.py b/papi/pyqtgraph/multiprocess/processes.py
similarity index 83%
rename from pyqtgraph/multiprocess/processes.py
rename to papi/pyqtgraph/multiprocess/processes.py
index 6d32a5a1..0dfb80b9 100644
--- a/pyqtgraph/multiprocess/processes.py
+++ b/papi/pyqtgraph/multiprocess/processes.py
@@ -1,12 +1,15 @@
-from .remoteproxy import RemoteEventHandler, ClosedError, NoResultError, LocalObjectProxy, ObjectProxy
import subprocess, atexit, os, sys, time, random, socket, signal
import multiprocessing.connection
-import pyqtgraph as pg
try:
import cPickle as pickle
except ImportError:
import pickle
+from .remoteproxy import RemoteEventHandler, ClosedError, NoResultError, LocalObjectProxy, ObjectProxy
+from ..Qt import USE_PYSIDE
+from ..util import cprint # color printing for debugging
+
+
__all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError']
class Process(RemoteEventHandler):
@@ -15,7 +18,7 @@ class Process(RemoteEventHandler):
This class is used to spawn and control a new python interpreter.
It uses subprocess.Popen to start the new process and communicates with it
- using multiprocessingTest.Connection objects over a network socket.
+ using multiprocessing.Connection objects over a network socket.
By default, the remote process will immediately enter an event-processing
loop that carries out requests send from the parent process.
@@ -34,28 +37,29 @@ class Process(RemoteEventHandler):
return objects either by proxy or by value (if they are picklable). See
ProxyObject for more information.
"""
-
+ _process_count = 1 # just used for assigning colors to each process for debugging
+
def __init__(self, name=None, target=None, executable=None, copySysPath=True, debug=False, timeout=20, wrapStdout=None):
"""
- ============ =============================================================
- Arguments:
- name Optional name for this process used when printing messages
- from the remote process.
- target Optional function to call after starting remote process.
- By default, this is startEventLoop(), which causes the remote
- process to process requests from the parent process until it
- is asked to quit. If you wish to specify a different target,
- it must be picklable (bound methods are not).
- copySysPath If True, copy the contents of sys.path to the remote process
- debug If True, print detailed information about communication
- with the child process.
- wrapStdout If True (default on windows) then stdout and stderr from the
- child process will be caught by the parent process and
- forwarded to its stdout/stderr. This provides a workaround
- for a python bug: http://bugs.python.org/issue3905
- but has the side effect that child output is significantly
- delayed relative to the parent output.
- ============ =============================================================
+ ============== =============================================================
+ **Arguments:**
+ name Optional name for this process used when printing messages
+ from the remote process.
+ target Optional function to call after starting remote process.
+ By default, this is startEventLoop(), which causes the remote
+ process to process requests from the parent process until it
+ is asked to quit. If you wish to specify a different target,
+ it must be picklable (bound methods are not).
+ copySysPath If True, copy the contents of sys.path to the remote process
+ debug If True, print detailed information about communication
+ with the child process.
+ wrapStdout If True (default on windows) then stdout and stderr from the
+ child process will be caught by the parent process and
+ forwarded to its stdout/stderr. This provides a workaround
+ for a python bug: http://bugs.python.org/issue3905
+ but has the side effect that child output is significantly
+ delayed relative to the parent output.
+ ============== =============================================================
"""
if target is None:
target = startEventLoop
@@ -63,7 +67,7 @@ def __init__(self, name=None, target=None, executable=None, copySysPath=True, de
name = str(self)
if executable is None:
executable = sys.executable
- self.debug = debug
+ self.debug = 7 if debug is True else False # 7 causes printing in white
## random authentication key
authkey = os.urandom(20)
@@ -74,21 +78,20 @@ def __init__(self, name=None, target=None, executable=None, copySysPath=True, de
#print "key:", ' '.join([str(ord(x)) for x in authkey])
## Listen for connection from remote process (and find free port number)
- port = 10000
- while True:
- try:
- l = multiprocessing.connection.Listener(('localhost', int(port)), authkey=authkey)
- break
- except socket.error as ex:
- if ex.errno != 98 and ex.errno != 10048: # unix=98, win=10048
- raise
- port += 1
-
+ l = multiprocessing.connection.Listener(('localhost', 0), authkey=authkey)
+ port = l.address[1]
## start remote process, instruct it to run target function
sysPath = sys.path if copySysPath else None
bootstrap = os.path.abspath(os.path.join(os.path.dirname(__file__), 'bootstrap.py'))
self.debugMsg('Starting child process (%s %s)' % (executable, bootstrap))
+
+ # Decide on printing color for this process
+ if debug:
+ procDebug = (Process._process_count%6) + 1 # pick a color for this process to print in
+ Process._process_count += 1
+ else:
+ procDebug = False
if wrapStdout is None:
wrapStdout = sys.platform.startswith('win')
@@ -101,8 +104,8 @@ def __init__(self, name=None, target=None, executable=None, copySysPath=True, de
self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE, stdout=stdout, stderr=stderr)
## to circumvent the bug and still make the output visible, we use
## background threads to pass data from pipes to stdout/stderr
- self._stdoutForwarder = FileForwarder(self.proc.stdout, "stdout")
- self._stderrForwarder = FileForwarder(self.proc.stderr, "stderr")
+ self._stdoutForwarder = FileForwarder(self.proc.stdout, "stdout", procDebug)
+ self._stderrForwarder = FileForwarder(self.proc.stderr, "stderr", procDebug)
else:
self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE)
@@ -118,8 +121,8 @@ def __init__(self, name=None, target=None, executable=None, copySysPath=True, de
ppid=pid,
targetStr=targetStr,
path=sysPath,
- pyside=pg.Qt.USE_PYSIDE,
- debug=debug
+ pyside=USE_PYSIDE,
+ debug=procDebug
)
pickle.dump(data, self.proc.stdin)
self.proc.stdin.close()
@@ -135,8 +138,8 @@ def __init__(self, name=None, target=None, executable=None, copySysPath=True, de
continue
else:
raise
-
- RemoteEventHandler.__init__(self, conn, name+'_parent', pid=self.proc.pid, debug=debug)
+
+ RemoteEventHandler.__init__(self, conn, name+'_parent', pid=self.proc.pid, debug=self.debug)
self.debugMsg('Connected to child process.')
atexit.register(self.join)
@@ -166,10 +169,11 @@ def debugMsg(self, msg):
def startEventLoop(name, port, authkey, ppid, debug=False):
if debug:
import os
- print('[%d] connecting to server at port localhost:%d, authkey=%s..' % (os.getpid(), port, repr(authkey)))
+ cprint.cout(debug, '[%d] connecting to server at port localhost:%d, authkey=%s..\n'
+ % (os.getpid(), port, repr(authkey)), -1)
conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey)
if debug:
- print('[%d] connected; starting remote proxy.' % os.getpid())
+ cprint.cout(debug, '[%d] connected; starting remote proxy.\n' % os.getpid(), -1)
global HANDLER
#ppid = 0 if not hasattr(os, 'getppid') else os.getppid()
HANDLER = RemoteEventHandler(conn, name, ppid, debug=debug)
@@ -337,7 +341,7 @@ def __init__(self, *args, **kwds):
RemoteEventHandler.__init__(self, *args, **kwds)
def startEventTimer(self):
- from pyqtgraph.Qt import QtGui, QtCore
+ from ..Qt import QtGui, QtCore
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.processRequests)
self.timer.start(10)
@@ -346,7 +350,7 @@ def processRequests(self):
try:
RemoteEventHandler.processRequests(self)
except ClosedError:
- from pyqtgraph.Qt import QtGui, QtCore
+ from ..Qt import QtGui, QtCore
QtGui.QApplication.instance().quit()
self.timer.stop()
#raise SystemExit
@@ -379,17 +383,17 @@ def slot():
def __init__(self, **kwds):
if 'target' not in kwds:
kwds['target'] = startQtEventLoop
+ from ..Qt import QtGui ## avoid module-level import to keep bootstrap snappy.
self._processRequests = kwds.pop('processRequests', True)
+ if self._processRequests and QtGui.QApplication.instance() is None:
+ raise Exception("Must create QApplication before starting QtProcess, or use QtProcess(processRequests=False)")
Process.__init__(self, **kwds)
self.startEventTimer()
def startEventTimer(self):
- from pyqtgraph.Qt import QtGui, QtCore ## avoid module-level import to keep bootstrap snappy.
+ from ..Qt import QtCore ## avoid module-level import to keep bootstrap snappy.
self.timer = QtCore.QTimer()
if self._processRequests:
- app = QtGui.QApplication.instance()
- if app is None:
- raise Exception("Must create QApplication before starting QtProcess, or use QtProcess(processRequests=False)")
self.startRequestProcessing()
def startRequestProcessing(self, interval=0.01):
@@ -411,11 +415,11 @@ def processRequests(self):
def startQtEventLoop(name, port, authkey, ppid, debug=False):
if debug:
import os
- print('[%d] connecting to server at port localhost:%d, authkey=%s..' % (os.getpid(), port, repr(authkey)))
+ cprint.cout(debug, '[%d] connecting to server at port localhost:%d, authkey=%s..\n' % (os.getpid(), port, repr(authkey)), -1)
conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey)
if debug:
- print('[%d] connected; starting remote proxy.' % os.getpid())
- from pyqtgraph.Qt import QtGui, QtCore
+ cprint.cout(debug, '[%d] connected; starting remote proxy.\n' % os.getpid(), -1)
+ from ..Qt import QtGui, QtCore
#from PyQt4 import QtGui, QtCore
app = QtGui.QApplication.instance()
#print app
@@ -444,11 +448,13 @@ class FileForwarder(threading.Thread):
which ensures that the correct behavior is achieved even if
sys.stdout/stderr are replaced at runtime.
"""
- def __init__(self, input, output):
+ def __init__(self, input, output, color):
threading.Thread.__init__(self)
self.input = input
self.output = output
self.lock = threading.Lock()
+ self.daemon = True
+ self.color = color
self.start()
def run(self):
@@ -456,12 +462,12 @@ def run(self):
while True:
line = self.input.readline()
with self.lock:
- sys.stdout.write(line)
+ cprint.cout(self.color, line, -1)
elif self.output == 'stderr':
while True:
line = self.input.readline()
with self.lock:
- sys.stderr.write(line)
+ cprint.cerr(self.color, line, -1)
else:
while True:
line = self.input.readline()
diff --git a/pyqtgraph/multiprocess/remoteproxy.py b/papi/pyqtgraph/multiprocess/remoteproxy.py
similarity index 85%
rename from pyqtgraph/multiprocess/remoteproxy.py
rename to papi/pyqtgraph/multiprocess/remoteproxy.py
index eba42ef3..8be2add0 100644
--- a/pyqtgraph/multiprocess/remoteproxy.py
+++ b/papi/pyqtgraph/multiprocess/remoteproxy.py
@@ -1,5 +1,6 @@
import os, time, sys, traceback, weakref
import numpy as np
+import threading
try:
import __builtin__ as builtins
import cPickle as pickle
@@ -7,6 +8,9 @@
import builtins
import pickle
+# color printing for debugging
+from ..util import cprint
+
class ClosedError(Exception):
"""Raised when an event handler receives a request to close the connection
or discovers that the connection has been closed."""
@@ -50,8 +54,10 @@ def __init__(self, connection, name, pid, debug=False):
## status is either 'result' or 'error'
## if 'error', then result will be (exception, formatted exceprion)
## where exception may be None if it could not be passed through the Connection.
+ self.resultLock = threading.RLock()
self.proxies = {} ## maps {weakref(proxy): proxyId}; used to inform the remote process when a proxy has been deleted.
+ self.proxyLock = threading.RLock()
## attributes that affect the behavior of the proxy.
## See ObjectProxy._setProxyOptions for description
@@ -63,10 +69,15 @@ def __init__(self, connection, name, pid, debug=False):
'deferGetattr': False, ## True, False
'noProxyTypes': [ type(None), str, int, float, tuple, list, dict, LocalObjectProxy, ObjectProxy ],
}
+ self.optsLock = threading.RLock()
self.nextRequestId = 0
self.exited = False
+ # Mutexes to help prevent issues when multiple threads access the same RemoteEventHandler
+ self.processLock = threading.RLock()
+ self.sendLock = threading.RLock()
+
RemoteEventHandler.handlers[pid] = self ## register this handler as the one communicating with pid
@classmethod
@@ -80,49 +91,62 @@ def getHandler(cls, pid):
def debugMsg(self, msg):
if not self.debug:
return
- print("[%d] %s" % (os.getpid(), str(msg)))
+ cprint.cout(self.debug, "[%d] %s\n" % (os.getpid(), str(msg)), -1)
def getProxyOption(self, opt):
- return self.proxyOptions[opt]
+ with self.optsLock:
+ return self.proxyOptions[opt]
def setProxyOptions(self, **kwds):
"""
Set the default behavior options for object proxies.
See ObjectProxy._setProxyOptions for more info.
"""
- self.proxyOptions.update(kwds)
+ with self.optsLock:
+ self.proxyOptions.update(kwds)
def processRequests(self):
"""Process all pending requests from the pipe, return
after no more events are immediately available. (non-blocking)
Returns the number of events processed.
"""
- if self.exited:
- self.debugMsg(' processRequests: exited already; raise ClosedError.')
- raise ClosedError()
-
- numProcessed = 0
- while self.conn.poll():
- try:
- self.handleRequest()
- numProcessed += 1
- except ClosedError:
- self.debugMsg('processRequests: got ClosedError from handleRequest; setting exited=True.')
- self.exited = True
- raise
- #except IOError as err: ## let handleRequest take care of this.
- #self.debugMsg(' got IOError from handleRequest; try again.')
- #if err.errno == 4: ## interrupted system call; try again
- #continue
- #else:
- #raise
- except:
- print("Error in process %s" % self.name)
- sys.excepthook(*sys.exc_info())
-
- if numProcessed > 0:
- self.debugMsg('processRequests: finished %d requests' % numProcessed)
- return numProcessed
+ with self.processLock:
+
+ if self.exited:
+ self.debugMsg(' processRequests: exited already; raise ClosedError.')
+ raise ClosedError()
+
+ numProcessed = 0
+
+ while self.conn.poll():
+ #try:
+ #poll = self.conn.poll()
+ #if not poll:
+ #break
+ #except IOError: # this can happen if the remote process dies.
+ ## might it also happen in other circumstances?
+ #raise ClosedError()
+
+ try:
+ self.handleRequest()
+ numProcessed += 1
+ except ClosedError:
+ self.debugMsg('processRequests: got ClosedError from handleRequest; setting exited=True.')
+ self.exited = True
+ raise
+ #except IOError as err: ## let handleRequest take care of this.
+ #self.debugMsg(' got IOError from handleRequest; try again.')
+ #if err.errno == 4: ## interrupted system call; try again
+ #continue
+ #else:
+ #raise
+ except:
+ print("Error in process %s" % self.name)
+ sys.excepthook(*sys.exc_info())
+
+ if numProcessed > 0:
+ self.debugMsg('processRequests: finished %d requests' % numProcessed)
+ return numProcessed
def handleRequest(self):
"""Handle a single request from the remote process.
@@ -180,9 +204,11 @@ def handleRequest(self):
returnType = opts.get('returnType', 'auto')
if cmd == 'result':
- self.results[resultId] = ('result', opts['result'])
+ with self.resultLock:
+ self.results[resultId] = ('result', opts['result'])
elif cmd == 'error':
- self.results[resultId] = ('error', (opts['exception'], opts['excString']))
+ with self.resultLock:
+ self.results[resultId] = ('error', (opts['exception'], opts['excString']))
elif cmd == 'getObjAttr':
result = getattr(opts['obj'], opts['attr'])
elif cmd == 'callObj':
@@ -226,6 +252,8 @@ def handleRequest(self):
elif cmd == 'import':
name = opts['module']
fromlist = opts.get('fromlist', [])
+
+ name = "papi." + name
mod = builtins.__import__(name, fromlist=fromlist)
if len(fromlist) == 0:
@@ -256,7 +284,9 @@ def handleRequest(self):
self.debugMsg(" handleRequest: sending return value for %d: %s" % (reqId, str(result)))
#print "returnValue:", returnValue, result
if returnType == 'auto':
- result = self.autoProxy(result, self.proxyOptions['noProxyTypes'])
+ with self.optsLock:
+ noProxyTypes = self.proxyOptions['noProxyTypes']
+ result = self.autoProxy(result, noProxyTypes)
elif returnType == 'proxy':
result = LocalObjectProxy(result)
@@ -299,23 +329,23 @@ def send(self, request, opts=None, reqId=None, callSync='sync', timeout=10, retu
(The docstring has information that is nevertheless useful to the programmer
as it describes the internal protocol used to communicate between processes)
- ========== ====================================================================
- Arguments:
- request String describing the type of request being sent (see below)
- reqId Integer uniquely linking a result back to the request that generated
- it. (most requests leave this blank)
- callSync 'sync': return the actual result of the request
- 'async': return a Request object which can be used to look up the
- result later
- 'off': return no result
- timeout Time in seconds to wait for a response when callSync=='sync'
- opts Extra arguments sent to the remote process that determine the way
- the request will be handled (see below)
- returnType 'proxy', 'value', or 'auto'
- byteData If specified, this is a list of objects to be sent as byte messages
- to the remote process.
- This is used to send large arrays without the cost of pickling.
- ========== ====================================================================
+ ============== ====================================================================
+ **Arguments:**
+ request String describing the type of request being sent (see below)
+ reqId Integer uniquely linking a result back to the request that generated
+ it. (most requests leave this blank)
+ callSync 'sync': return the actual result of the request
+ 'async': return a Request object which can be used to look up the
+ result later
+ 'off': return no result
+ timeout Time in seconds to wait for a response when callSync=='sync'
+ opts Extra arguments sent to the remote process that determine the way
+ the request will be handled (see below)
+ returnType 'proxy', 'value', or 'auto'
+ byteData If specified, this is a list of objects to be sent as byte messages
+ to the remote process.
+ This is used to send large arrays without the cost of pickling.
+ ============== ====================================================================
Description of request strings and options allowed for each:
@@ -375,54 +405,59 @@ def send(self, request, opts=None, reqId=None, callSync='sync', timeout=10, retu
traceback
============= =====================================================================
"""
- #if len(kwds) > 0:
- #print "Warning: send() ignored args:", kwds
-
- if opts is None:
- opts = {}
-
- assert callSync in ['off', 'sync', 'async'], 'callSync must be one of "off", "sync", or "async"'
- if reqId is None:
- if callSync != 'off': ## requested return value; use the next available request ID
- reqId = self.nextRequestId
- self.nextRequestId += 1
- else:
- ## If requestId is provided, this _must_ be a response to a previously received request.
- assert request in ['result', 'error']
+ if self.exited:
+ self.debugMsg(' send: exited already; raise ClosedError.')
+ raise ClosedError()
- if returnType is not None:
- opts['returnType'] = returnType
+ with self.sendLock:
+ #if len(kwds) > 0:
+ #print "Warning: send() ignored args:", kwds
+
+ if opts is None:
+ opts = {}
- #print os.getpid(), "send request:", request, reqId, opts
-
- ## double-pickle args to ensure that at least status and request ID get through
- try:
- optStr = pickle.dumps(opts)
- except:
- print("==== Error pickling this object: ====")
- print(opts)
- print("=======================================")
- raise
-
- nByteMsgs = 0
- if byteData is not None:
- nByteMsgs = len(byteData)
+ assert callSync in ['off', 'sync', 'async'], 'callSync must be one of "off", "sync", or "async"'
+ if reqId is None:
+ if callSync != 'off': ## requested return value; use the next available request ID
+ reqId = self.nextRequestId
+ self.nextRequestId += 1
+ else:
+ ## If requestId is provided, this _must_ be a response to a previously received request.
+ assert request in ['result', 'error']
+
+ if returnType is not None:
+ opts['returnType'] = returnType
+
+ #print os.getpid(), "send request:", request, reqId, opts
+
+ ## double-pickle args to ensure that at least status and request ID get through
+ try:
+ optStr = pickle.dumps(opts)
+ except:
+ print("==== Error pickling this object: ====")
+ print(opts)
+ print("=======================================")
+ raise
+
+ nByteMsgs = 0
+ if byteData is not None:
+ nByteMsgs = len(byteData)
+
+ ## Send primary request
+ request = (request, reqId, nByteMsgs, optStr)
+ self.debugMsg('send request: cmd=%s nByteMsgs=%d id=%s opts=%s' % (str(request[0]), nByteMsgs, str(reqId), str(opts)))
+ self.conn.send(request)
+
+ ## follow up by sending byte messages
+ if byteData is not None:
+ for obj in byteData: ## Remote process _must_ be prepared to read the same number of byte messages!
+ self.conn.send_bytes(obj)
+ self.debugMsg(' sent %d byte messages' % len(byteData))
+
+ self.debugMsg(' call sync: %s' % callSync)
+ if callSync == 'off':
+ return
- ## Send primary request
- request = (request, reqId, nByteMsgs, optStr)
- self.debugMsg('send request: cmd=%s nByteMsgs=%d id=%s opts=%s' % (str(request[0]), nByteMsgs, str(reqId), str(opts)))
- self.conn.send(request)
-
- ## follow up by sending byte messages
- if byteData is not None:
- for obj in byteData: ## Remote process _must_ be prepared to read the same number of byte messages!
- self.conn.send_bytes(obj)
- self.debugMsg(' sent %d byte messages' % len(byteData))
-
- self.debugMsg(' call sync: %s' % callSync)
- if callSync == 'off':
- return
-
req = Request(self, reqId, description=str(request), timeout=timeout)
if callSync == 'async':
return req
@@ -434,20 +469,30 @@ def send(self, request, opts=None, reqId=None, callSync='sync', timeout=10, retu
return req
def close(self, callSync='off', noCleanup=False, **kwds):
- self.send(request='close', opts=dict(noCleanup=noCleanup), callSync=callSync, **kwds)
+ try:
+ self.send(request='close', opts=dict(noCleanup=noCleanup), callSync=callSync, **kwds)
+ self.exited = True
+ except ClosedError:
+ pass
def getResult(self, reqId):
## raises NoResultError if the result is not available yet
#print self.results.keys(), os.getpid()
- if reqId not in self.results:
+ with self.resultLock:
+ haveResult = reqId in self.results
+
+ if not haveResult:
try:
self.processRequests()
except ClosedError: ## even if remote connection has closed, we may have
## received new data during this call to processRequests()
pass
- if reqId not in self.results:
- raise NoResultError()
- status, result = self.results.pop(reqId)
+
+ with self.resultLock:
+ if reqId not in self.results:
+ raise NoResultError()
+ status, result = self.results.pop(reqId)
+
if status == 'result':
return result
elif status == 'error':
@@ -491,11 +536,13 @@ def callObj(self, obj, args, kwds, **opts):
args = list(args)
## Decide whether to send arguments by value or by proxy
- noProxyTypes = opts.pop('noProxyTypes', None)
- if noProxyTypes is None:
- noProxyTypes = self.proxyOptions['noProxyTypes']
-
- autoProxy = opts.pop('autoProxy', self.proxyOptions['autoProxy'])
+ with self.optsLock:
+ noProxyTypes = opts.pop('noProxyTypes', None)
+ if noProxyTypes is None:
+ noProxyTypes = self.proxyOptions['noProxyTypes']
+
+ autoProxy = opts.pop('autoProxy', self.proxyOptions['autoProxy'])
+
if autoProxy is True:
args = [self.autoProxy(v, noProxyTypes) for v in args]
for k, v in kwds.iteritems():
@@ -517,11 +564,14 @@ def callObj(self, obj, args, kwds, **opts):
return self.send(request='callObj', opts=dict(obj=obj, args=args, kwds=kwds), byteData=byteMsgs, **opts)
def registerProxy(self, proxy):
- ref = weakref.ref(proxy, self.deleteProxy)
- self.proxies[ref] = proxy._proxyId
+ with self.proxyLock:
+ ref = weakref.ref(proxy, self.deleteProxy)
+ self.proxies[ref] = proxy._proxyId
def deleteProxy(self, ref):
- proxyId = self.proxies.pop(ref)
+ with self.proxyLock:
+ proxyId = self.proxies.pop(ref)
+
try:
self.send(request='del', opts=dict(proxyId=proxyId), callSync='off')
except IOError: ## if remote process has closed down, there is no need to send delete requests anymore
@@ -576,7 +626,7 @@ def result(self, block=True, timeout=None):
return self._result
if timeout is None:
- timeout = self.timeout
+ timeout = self.timeout
if block:
start = time.time()
diff --git a/pyqtgraph/numpy_fix.py b/papi/pyqtgraph/numpy_fix.py
similarity index 100%
rename from pyqtgraph/numpy_fix.py
rename to papi/pyqtgraph/numpy_fix.py
diff --git a/pyqtgraph/opengl/GLGraphicsItem.py b/papi/pyqtgraph/opengl/GLGraphicsItem.py
similarity index 98%
rename from pyqtgraph/opengl/GLGraphicsItem.py
rename to papi/pyqtgraph/opengl/GLGraphicsItem.py
index 9680fba7..12c5b707 100644
--- a/pyqtgraph/opengl/GLGraphicsItem.py
+++ b/papi/pyqtgraph/opengl/GLGraphicsItem.py
@@ -1,5 +1,5 @@
-from pyqtgraph.Qt import QtGui, QtCore
-from pyqtgraph import Transform3D
+from ..Qt import QtGui, QtCore
+from .. import Transform3D
from OpenGL.GL import *
from OpenGL import GL
@@ -28,8 +28,13 @@
class GLGraphicsItem(QtCore.QObject):
+ _nextId = 0
+
def __init__(self, parentItem=None):
QtCore.QObject.__init__(self)
+ self._id = GLGraphicsItem._nextId
+ GLGraphicsItem._nextId += 1
+
self.__parent = None
self.__view = None
self.__children = set()
diff --git a/pyqtgraph/opengl/GLViewWidget.py b/papi/pyqtgraph/opengl/GLViewWidget.py
similarity index 91%
rename from pyqtgraph/opengl/GLViewWidget.py
rename to papi/pyqtgraph/opengl/GLViewWidget.py
index 89fef92e..992aa73e 100644
--- a/pyqtgraph/opengl/GLViewWidget.py
+++ b/papi/pyqtgraph/opengl/GLViewWidget.py
@@ -1,12 +1,14 @@
-from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL
+from ..Qt import QtCore, QtGui, QtOpenGL
from OpenGL.GL import *
import OpenGL.GL.framebufferobjects as glfbo
import numpy as np
-from pyqtgraph import Vector
-import pyqtgraph.functions as fn
+from .. import Vector
+from .. import functions as fn
##Vector = QtGui.QVector3D
+ShareWidget = None
+
class GLViewWidget(QtOpenGL.QGLWidget):
"""
Basic widget for displaying 3D data
@@ -16,14 +18,14 @@ class GLViewWidget(QtOpenGL.QGLWidget):
"""
- ShareWidget = None
-
def __init__(self, parent=None):
- if GLViewWidget.ShareWidget is None:
+ global ShareWidget
+
+ if ShareWidget is None:
## create a dummy widget to allow sharing objects (textures, shaders, etc) between views
- GLViewWidget.ShareWidget = QtOpenGL.QGLWidget()
+ ShareWidget = QtOpenGL.QGLWidget()
- QtOpenGL.QGLWidget.__init__(self, parent, GLViewWidget.ShareWidget)
+ QtOpenGL.QGLWidget.__init__(self, parent, ShareWidget)
self.setFocusPolicy(QtCore.Qt.ClickFocus)
@@ -36,6 +38,7 @@ def __init__(self, parent=None):
## (rotation around z-axis 0 points along x-axis)
'viewport': None, ## glViewport params; None == whole widget
}
+ self.setBackgroundColor('k')
self.items = []
self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]
self.keysPressed = {}
@@ -64,9 +67,16 @@ def removeItem(self, item):
def initializeGL(self):
- glClearColor(0.0, 0.0, 0.0, 0.0)
self.resizeGL(self.width(), self.height())
+ def setBackgroundColor(self, *args, **kwds):
+ """
+ Set the background color of the widget. Accepts the same arguments as
+ pg.mkColor().
+ """
+ self.opts['bgcolor'] = fn.mkColor(*args, **kwds)
+ self.update()
+
def getViewport(self):
vp = self.opts['viewport']
if vp is None:
@@ -129,6 +139,12 @@ def viewMatrix(self):
return tr
def itemsAt(self, region=None):
+ """
+ Return a list of the items displayed in the region (x, y, w, h)
+ relative to the widget.
+ """
+ region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3])
+
#buf = np.zeros(100000, dtype=np.uint)
buf = glSelectBuffer(100000)
try:
@@ -140,12 +156,11 @@ def itemsAt(self, region=None):
finally:
hits = glRenderMode(GL_RENDER)
-
+
items = [(h.near, h.names[0]) for h in hits]
items.sort(key=lambda i: i[0])
-
return [self._itemNames[i[1]] for i in items]
-
+
def paintGL(self, region=None, viewport=None, useItemNames=False):
"""
viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport']
@@ -158,6 +173,8 @@ def paintGL(self, region=None, viewport=None, useItemNames=False):
glViewport(*viewport)
self.setProjection(region=region)
self.setModelview()
+ bgcolor = self.opts['bgcolor']
+ glClearColor(bgcolor.red(), bgcolor.green(), bgcolor.blue(), 1.0)
glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
self.drawItemTree(useItemNames=useItemNames)
@@ -175,12 +192,12 @@ def drawItemTree(self, item=None, useItemNames=False):
try:
glPushAttrib(GL_ALL_ATTRIB_BITS)
if useItemNames:
- glLoadName(id(i))
- self._itemNames[id(i)] = i
+ glLoadName(i._id)
+ self._itemNames[i._id] = i
i.paint()
except:
- import pyqtgraph.debug
- pyqtgraph.debug.printExc()
+ from .. import debug
+ debug.printExc()
msg = "Error while drawing item %s." % str(item)
ver = glGetString(GL_VERSION)
if ver is not None:
@@ -294,6 +311,17 @@ def mouseMoveEvent(self, ev):
def mouseReleaseEvent(self, ev):
pass
+ # Example item selection code:
+ #region = (ev.pos().x()-5, ev.pos().y()-5, 10, 10)
+ #print(self.itemsAt(region))
+
+ ## debugging code: draw the picking region
+ #glViewport(*self.getViewport())
+ #glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
+ #region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3])
+ #self.paintGL(region=region)
+ #self.swapBuffers()
+
def wheelEvent(self, ev):
if (ev.modifiers() & QtCore.Qt.ControlModifier):
@@ -345,7 +373,7 @@ def checkOpenGLVersion(self, msg):
## Only to be called from within exception handler.
ver = glGetString(GL_VERSION).split()[0]
if int(ver.split('.')[0]) < 2:
- import pyqtgraph.debug
+ from .. import debug
pyqtgraph.debug.printExc()
raise Exception(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver)
else:
diff --git a/pyqtgraph/opengl/MeshData.py b/papi/pyqtgraph/opengl/MeshData.py
similarity index 81%
rename from pyqtgraph/opengl/MeshData.py
rename to papi/pyqtgraph/opengl/MeshData.py
index 71e566c9..5adf4b64 100644
--- a/pyqtgraph/opengl/MeshData.py
+++ b/papi/pyqtgraph/opengl/MeshData.py
@@ -1,5 +1,5 @@
-from pyqtgraph.Qt import QtGui
-import pyqtgraph.functions as fn
+from ..Qt import QtGui
+from .. import functions as fn
import numpy as np
class MeshData(object):
@@ -23,18 +23,18 @@ class MeshData(object):
def __init__(self, vertexes=None, faces=None, edges=None, vertexColors=None, faceColors=None):
"""
- ============= =====================================================
- Arguments
- vertexes (Nv, 3) array of vertex coordinates.
- If faces is not specified, then this will instead be
- interpreted as (Nf, 3, 3) array of coordinates.
- faces (Nf, 3) array of indexes into the vertex array.
- edges [not available yet]
- vertexColors (Nv, 4) array of vertex colors.
- If faces is not specified, then this will instead be
- interpreted as (Nf, 3, 4) array of colors.
- faceColors (Nf, 4) array of face colors.
- ============= =====================================================
+ ============== =====================================================
+ **Arguments:**
+ vertexes (Nv, 3) array of vertex coordinates.
+ If faces is not specified, then this will instead be
+ interpreted as (Nf, 3, 3) array of coordinates.
+ faces (Nf, 3) array of indexes into the vertex array.
+ edges [not available yet]
+ vertexColors (Nv, 4) array of vertex colors.
+ If faces is not specified, then this will instead be
+ interpreted as (Nf, 3, 4) array of colors.
+ faceColors (Nf, 4) array of face colors.
+ ============== =====================================================
All arguments are optional.
"""
@@ -84,64 +84,11 @@ def __init__(self, vertexes=None, faces=None, edges=None, vertexColors=None, fac
if faceColors is not None:
self.setFaceColors(faceColors)
- #self.setFaces(vertexes=vertexes, faces=faces, vertexColors=vertexColors, faceColors=faceColors)
-
-
- #def setFaces(self, vertexes=None, faces=None, vertexColors=None, faceColors=None):
- #"""
- #Set the faces in this data set.
- #Data may be provided either as an Nx3x3 array of floats (9 float coordinate values per face)::
-
- #faces = [ [(x, y, z), (x, y, z), (x, y, z)], ... ]
-
- #or as an Nx3 array of ints (vertex integers) AND an Mx3 array of floats (3 float coordinate values per vertex)::
-
- #faces = [ (p1, p2, p3), ... ]
- #vertexes = [ (x, y, z), ... ]
-
- #"""
- #if not isinstance(vertexes, np.ndarray):
- #vertexes = np.array(vertexes)
- #if vertexes.dtype != np.float:
- #vertexes = vertexes.astype(float)
- #if faces is None:
- #self._setIndexedFaces(vertexes, vertexColors, faceColors)
- #else:
- #self._setUnindexedFaces(faces, vertexes, vertexColors, faceColors)
- ##print self.vertexes().shape
- ##print self.faces().shape
-
-
- #def setMeshColor(self, color):
- #"""Set the color of the entire mesh. This removes any per-face or per-vertex colors."""
- #color = fn.Color(color)
- #self._meshColor = color.glColor()
- #self._vertexColors = None
- #self._faceColors = None
-
-
- #def __iter__(self):
- #"""Iterate over all faces, yielding a list of three tuples [(position, normal, color), ...] for each face."""
- #vnorms = self.vertexNormals()
- #vcolors = self.vertexColors()
- #for i in range(self._faces.shape[0]):
- #face = []
- #for j in [0,1,2]:
- #vind = self._faces[i,j]
- #pos = self._vertexes[vind]
- #norm = vnorms[vind]
- #if vcolors is None:
- #color = self._meshColor
- #else:
- #color = vcolors[vind]
- #face.append((pos, norm, color))
- #yield face
-
- #def __len__(self):
- #return len(self._faces)
-
def faces(self):
- """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh."""
+ """Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh.
+
+ If faces have not been computed for this mesh, the function returns None.
+ """
return self._faces
def edges(self):
@@ -161,8 +108,6 @@ def setFaces(self, faces):
self.resetNormals()
self._vertexColorsIndexedByFaces = None
self._faceColorsIndexedByFaces = None
-
-
def vertexes(self, indexed=None):
"""Return an array (N,3) of the positions of vertexes in the mesh.
@@ -207,7 +152,6 @@ def resetNormals(self):
self._vertexNormalsIndexedByFaces = None
self._faceNormals = None
self._faceNormalsIndexedByFaces = None
-
def hasFaceIndexedData(self):
"""Return True if this object already has vertex positions indexed by face"""
@@ -229,7 +173,6 @@ def hasFaceColor(self):
if v is not None:
return True
return False
-
def faceNormals(self, indexed=None):
"""
@@ -242,7 +185,6 @@ def faceNormals(self, indexed=None):
v = self.vertexes(indexed='faces')
self._faceNormals = np.cross(v[:,1]-v[:,0], v[:,2]-v[:,0])
-
if indexed is None:
return self._faceNormals
elif indexed == 'faces':
@@ -266,7 +208,11 @@ def vertexNormals(self, indexed=None):
vertFaces = self.vertexFaces()
self._vertexNormals = np.empty(self._vertexes.shape, dtype=float)
for vindex in xrange(self._vertexes.shape[0]):
- norms = faceNorms[vertFaces[vindex]] ## get all face normals
+ faces = vertFaces[vindex]
+ if len(faces) == 0:
+ self._vertexNormals[vindex] = (0,0,0)
+ continue
+ norms = faceNorms[faces] ## get all face normals
norm = norms.sum(axis=0) ## sum normals
norm /= (norm**2).sum()**0.5 ## and re-normalize
self._vertexNormals[vindex] = norm
@@ -363,7 +309,6 @@ def _computeUnindexedVertexes(self):
## This is done by collapsing into a list of 'unique' vertexes (difference < 1e-14)
## I think generally this should be discouraged..
-
faces = self._vertexesIndexedByFaces
verts = {} ## used to remember the index of each vertex position
self._faces = np.empty(faces.shape[:2], dtype=np.uint)
@@ -403,12 +348,10 @@ def vertexFaces(self):
Return list mapping each vertex index to a list of face indexes that use the vertex.
"""
if self._vertexFaces is None:
- self._vertexFaces = [None] * len(self.vertexes())
+ self._vertexFaces = [[] for i in xrange(len(self.vertexes()))]
for i in xrange(self._faces.shape[0]):
face = self._faces[i]
for ind in face:
- if self._vertexFaces[ind] is None:
- self._vertexFaces[ind] = [] ## need a unique/empty list to fill
self._vertexFaces[ind].append(i)
return self._vertexFaces
@@ -426,22 +369,35 @@ def vertexFaces(self):
#pass
def _computeEdges(self):
- ## generate self._edges from self._faces
- #print self._faces
- nf = len(self._faces)
- edges = np.empty(nf*3, dtype=[('i', np.uint, 2)])
- edges['i'][0:nf] = self._faces[:,:2]
- edges['i'][nf:2*nf] = self._faces[:,1:3]
- edges['i'][-nf:,0] = self._faces[:,2]
- edges['i'][-nf:,1] = self._faces[:,0]
-
- # sort per-edge
- mask = edges['i'][:,0] > edges['i'][:,1]
- edges['i'][mask] = edges['i'][mask][:,::-1]
-
- # remove duplicate entries
- self._edges = np.unique(edges)['i']
- #print self._edges
+ if not self.hasFaceIndexedData:
+ ## generate self._edges from self._faces
+ nf = len(self._faces)
+ edges = np.empty(nf*3, dtype=[('i', np.uint, 2)])
+ edges['i'][0:nf] = self._faces[:,:2]
+ edges['i'][nf:2*nf] = self._faces[:,1:3]
+ edges['i'][-nf:,0] = self._faces[:,2]
+ edges['i'][-nf:,1] = self._faces[:,0]
+
+ # sort per-edge
+ mask = edges['i'][:,0] > edges['i'][:,1]
+ edges['i'][mask] = edges['i'][mask][:,::-1]
+
+ # remove duplicate entries
+ self._edges = np.unique(edges)['i']
+ #print self._edges
+ elif self._vertexesIndexedByFaces is not None:
+ verts = self._vertexesIndexedByFaces
+ edges = np.empty((verts.shape[0], 3, 2), dtype=np.uint)
+ nf = verts.shape[0]
+ edges[:,0,0] = np.arange(nf) * 3
+ edges[:,0,1] = edges[:,0,0] + 1
+ edges[:,1,0] = edges[:,0,1]
+ edges[:,1,1] = edges[:,1,0] + 1
+ edges[:,2,0] = edges[:,1,1]
+ edges[:,2,1] = edges[:,0,0]
+ self._edges = edges
+ else:
+ raise Exception("MeshData cannot generate edges--no faces in this data.")
def save(self):
@@ -516,4 +472,33 @@ def sphere(rows, cols, radius=1.0, offset=True):
return MeshData(vertexes=verts, faces=faces)
-
\ No newline at end of file
+ @staticmethod
+ def cylinder(rows, cols, radius=[1.0, 1.0], length=1.0, offset=False):
+ """
+ Return a MeshData instance with vertexes and faces computed
+ for a cylindrical surface.
+ The cylinder may be tapered with different radii at each end (truncated cone)
+ """
+ verts = np.empty((rows+1, cols, 3), dtype=float)
+ if isinstance(radius, int):
+ radius = [radius, radius] # convert to list
+ ## compute vertexes
+ th = np.linspace(2 * np.pi, 0, cols).reshape(1, cols)
+ r = np.linspace(radius[0],radius[1],num=rows+1, endpoint=True).reshape(rows+1, 1) # radius as a function of z
+ verts[...,2] = np.linspace(0, length, num=rows+1, endpoint=True).reshape(rows+1, 1) # z
+ if offset:
+ th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column
+ verts[...,0] = r * np.cos(th) # x = r cos(th)
+ verts[...,1] = r * np.sin(th) # y = r sin(th)
+ verts = verts.reshape((rows+1)*cols, 3) # just reshape: no redundant vertices...
+ ## compute faces
+ faces = np.empty((rows*cols*2, 3), dtype=np.uint)
+ rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]])
+ rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]])
+ for row in range(rows):
+ start = row * cols * 2
+ faces[start:start+cols] = rowtemplate1 + row * cols
+ faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols
+
+ return MeshData(vertexes=verts, faces=faces)
+
diff --git a/papi/pyqtgraph/opengl/__init__.py b/papi/pyqtgraph/opengl/__init__.py
new file mode 100644
index 00000000..931003e4
--- /dev/null
+++ b/papi/pyqtgraph/opengl/__init__.py
@@ -0,0 +1,22 @@
+from .GLViewWidget import GLViewWidget
+
+## dynamic imports cause too many problems.
+#from .. import importAll
+#importAll('items', globals(), locals())
+
+from .items.GLGridItem import *
+from .items.GLBarGraphItem import *
+from .items.GLScatterPlotItem import *
+from .items.GLMeshItem import *
+from .items.GLLinePlotItem import *
+from .items.GLAxisItem import *
+from .items.GLImageItem import *
+from .items.GLSurfacePlotItem import *
+from .items.GLBoxItem import *
+from .items.GLVolumeItem import *
+
+from .MeshData import MeshData
+## for backward compatibility:
+#MeshData.MeshData = MeshData ## breaks autodoc.
+
+from . import shaders
diff --git a/pyqtgraph/opengl/glInfo.py b/papi/pyqtgraph/opengl/glInfo.py
similarity index 90%
rename from pyqtgraph/opengl/glInfo.py
rename to papi/pyqtgraph/opengl/glInfo.py
index 28da1f69..84346d81 100644
--- a/pyqtgraph/opengl/glInfo.py
+++ b/papi/pyqtgraph/opengl/glInfo.py
@@ -1,4 +1,4 @@
-from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL
+from ..Qt import QtCore, QtGui, QtOpenGL
from OpenGL.GL import *
app = QtGui.QApplication([])
diff --git a/pyqtgraph/opengl/items/GLAxisItem.py b/papi/pyqtgraph/opengl/items/GLAxisItem.py
similarity index 95%
rename from pyqtgraph/opengl/items/GLAxisItem.py
rename to papi/pyqtgraph/opengl/items/GLAxisItem.py
index 860ac497..989a44ca 100644
--- a/pyqtgraph/opengl/items/GLAxisItem.py
+++ b/papi/pyqtgraph/opengl/items/GLAxisItem.py
@@ -1,6 +1,6 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
-from pyqtgraph import QtGui
+from ... import QtGui
__all__ = ['GLAxisItem']
@@ -45,7 +45,7 @@ def paint(self):
if self.antialias:
glEnable(GL_LINE_SMOOTH)
- glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
+ glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
glBegin( GL_LINES )
diff --git a/pyqtgraph/opengl/items/GLBarGraphItem.py b/papi/pyqtgraph/opengl/items/GLBarGraphItem.py
similarity index 100%
rename from pyqtgraph/opengl/items/GLBarGraphItem.py
rename to papi/pyqtgraph/opengl/items/GLBarGraphItem.py
diff --git a/pyqtgraph/opengl/items/GLBoxItem.py b/papi/pyqtgraph/opengl/items/GLBoxItem.py
similarity index 96%
rename from pyqtgraph/opengl/items/GLBoxItem.py
rename to papi/pyqtgraph/opengl/items/GLBoxItem.py
index bc25afd1..f0a6ae6c 100644
--- a/pyqtgraph/opengl/items/GLBoxItem.py
+++ b/papi/pyqtgraph/opengl/items/GLBoxItem.py
@@ -1,7 +1,7 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
-from pyqtgraph.Qt import QtGui
-import pyqtgraph as pg
+from ...Qt import QtGui
+from ... import functions as fn
__all__ = ['GLBoxItem']
@@ -38,7 +38,7 @@ def size(self):
def setColor(self, *args):
"""Set the color of the box. Arguments are the same as those accepted by functions.mkColor()"""
- self.__color = pg.Color(*args)
+ self.__color = fn.Color(*args)
def color(self):
return self.__color
diff --git a/pyqtgraph/opengl/items/GLGridItem.py b/papi/pyqtgraph/opengl/items/GLGridItem.py
similarity index 58%
rename from pyqtgraph/opengl/items/GLGridItem.py
rename to papi/pyqtgraph/opengl/items/GLGridItem.py
index 01a2b178..4d6bc9d6 100644
--- a/pyqtgraph/opengl/items/GLGridItem.py
+++ b/papi/pyqtgraph/opengl/items/GLGridItem.py
@@ -1,6 +1,8 @@
+import numpy as np
+
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
-from pyqtgraph import QtGui
+from ... import QtGui
__all__ = ['GLGridItem']
@@ -16,8 +18,9 @@ def __init__(self, size=None, color=None, antialias=True, glOptions='translucent
self.setGLOptions(glOptions)
self.antialias = antialias
if size is None:
- size = QtGui.QVector3D(1,1,1)
+ size = QtGui.QVector3D(20,20,1)
self.setSize(size=size)
+ self.setSpacing(1, 1, 1)
def setSize(self, x=None, y=None, z=None, size=None):
"""
@@ -33,8 +36,22 @@ def setSize(self, x=None, y=None, z=None, size=None):
def size(self):
return self.__size[:]
-
-
+
+ def setSpacing(self, x=None, y=None, z=None, spacing=None):
+ """
+ Set the spacing between grid lines.
+ Arguments can be x,y,z or spacing=QVector3D().
+ """
+ if spacing is not None:
+ x = spacing.x()
+ y = spacing.y()
+ z = spacing.z()
+ self.__spacing = [x,y,z]
+ self.update()
+
+ def spacing(self):
+ return self.__spacing[:]
+
def paint(self):
self.setupGLState()
@@ -42,17 +59,20 @@ def paint(self):
glEnable(GL_LINE_SMOOTH)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
+ glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
glBegin( GL_LINES )
x,y,z = self.size()
+ xs,ys,zs = self.spacing()
+ xvals = np.arange(-x/2., x/2. + xs*0.001, xs)
+ yvals = np.arange(-y/2., y/2. + ys*0.001, ys)
glColor4f(1, 1, 1, .3)
- for x in range(-10, 11):
- glVertex3f(x, -10, 0)
- glVertex3f(x, 10, 0)
- for y in range(-10, 11):
- glVertex3f(-10, y, 0)
- glVertex3f( 10, y, 0)
+ for x in xvals:
+ glVertex3f(x, yvals[0], 0)
+ glVertex3f(x, yvals[-1], 0)
+ for y in yvals:
+ glVertex3f(xvals[0], y, 0)
+ glVertex3f(xvals[-1], y, 0)
glEnd()
diff --git a/pyqtgraph/opengl/items/GLImageItem.py b/papi/pyqtgraph/opengl/items/GLImageItem.py
similarity index 92%
rename from pyqtgraph/opengl/items/GLImageItem.py
rename to papi/pyqtgraph/opengl/items/GLImageItem.py
index aca68f3d..59ddaf6f 100644
--- a/pyqtgraph/opengl/items/GLImageItem.py
+++ b/papi/pyqtgraph/opengl/items/GLImageItem.py
@@ -1,6 +1,6 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
-from pyqtgraph.Qt import QtGui
+from ...Qt import QtGui
import numpy as np
__all__ = ['GLImageItem']
@@ -25,13 +25,21 @@ def __init__(self, data, smooth=False, glOptions='translucent'):
"""
self.smooth = smooth
- self.data = data
+ self._needUpdate = False
GLGraphicsItem.__init__(self)
+ self.setData(data)
self.setGLOptions(glOptions)
def initializeGL(self):
glEnable(GL_TEXTURE_2D)
self.texture = glGenTextures(1)
+
+ def setData(self, data):
+ self.data = data
+ self._needUpdate = True
+ self.update()
+
+ def _updateTexture(self):
glBindTexture(GL_TEXTURE_2D, self.texture)
if self.smooth:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
@@ -63,7 +71,8 @@ def initializeGL(self):
def paint(self):
-
+ if self._needUpdate:
+ self._updateTexture()
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, self.texture)
diff --git a/pyqtgraph/opengl/items/GLLinePlotItem.py b/papi/pyqtgraph/opengl/items/GLLinePlotItem.py
similarity index 80%
rename from pyqtgraph/opengl/items/GLLinePlotItem.py
rename to papi/pyqtgraph/opengl/items/GLLinePlotItem.py
index 23d227c9..f5cb7545 100644
--- a/pyqtgraph/opengl/items/GLLinePlotItem.py
+++ b/papi/pyqtgraph/opengl/items/GLLinePlotItem.py
@@ -2,7 +2,7 @@
from OpenGL.arrays import vbo
from .. GLGraphicsItem import GLGraphicsItem
from .. import shaders
-from pyqtgraph import QtGui
+from ... import QtGui
import numpy as np
__all__ = ['GLLinePlotItem']
@@ -16,6 +16,7 @@ def __init__(self, **kwds):
glopts = kwds.pop('glOptions', 'additive')
self.setGLOptions(glopts)
self.pos = None
+ self.mode = 'line_strip'
self.width = 1.
self.color = (1.0,1.0,1.0,1.0)
self.setData(**kwds)
@@ -27,7 +28,7 @@ def setData(self, **kwds):
colors unchanged, etc.
==================== ==================================================
- Arguments:
+ **Arguments:**
------------------------------------------------------------------------
pos (N,3) array of floats specifying point locations.
color (N,4) array of floats (0.0-1.0) or
@@ -35,9 +36,13 @@ def setData(self, **kwds):
a single color for the entire item.
width float specifying line width
antialias enables smooth line drawing
+ mode 'lines': Each pair of vertexes draws a single line
+ segment.
+ 'line_strip': All vertexes are drawn as a
+ continuous set of line segments.
==================== ==================================================
"""
- args = ['pos', 'color', 'width', 'connected', 'antialias']
+ args = ['pos', 'color', 'width', 'mode', 'antialias']
for k in kwds.keys():
if k not in args:
raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args)))
@@ -91,9 +96,15 @@ def paint(self):
glEnable(GL_LINE_SMOOTH)
glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
- glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
+ glHint(GL_LINE_SMOOTH_HINT, GL_NICEST)
+
+ if self.mode == 'line_strip':
+ glDrawArrays(GL_LINE_STRIP, 0, int(self.pos.size / self.pos.shape[-1]))
+ elif self.mode == 'lines':
+ glDrawArrays(GL_LINES, 0, int(self.pos.size / self.pos.shape[-1]))
+ else:
+ raise Exception("Unknown line mode '%s'. (must be 'lines' or 'line_strip')" % self.mode)
- glDrawArrays(GL_LINE_STRIP, 0, int(self.pos.size / self.pos.shape[-1]))
finally:
glDisableClientState(GL_COLOR_ARRAY)
glDisableClientState(GL_VERTEX_ARRAY)
diff --git a/pyqtgraph/opengl/items/GLMeshItem.py b/papi/pyqtgraph/opengl/items/GLMeshItem.py
similarity index 94%
rename from pyqtgraph/opengl/items/GLMeshItem.py
rename to papi/pyqtgraph/opengl/items/GLMeshItem.py
index 5b245e64..55e75942 100644
--- a/pyqtgraph/opengl/items/GLMeshItem.py
+++ b/papi/pyqtgraph/opengl/items/GLMeshItem.py
@@ -1,9 +1,9 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
from .. MeshData import MeshData
-from pyqtgraph.Qt import QtGui
-import pyqtgraph as pg
+from ...Qt import QtGui
from .. import shaders
+from ... import functions as fn
import numpy as np
@@ -19,7 +19,7 @@ class GLMeshItem(GLGraphicsItem):
def __init__(self, **kwds):
"""
============== =====================================================
- Arguments
+ **Arguments:**
meshdata MeshData object from which to determine geometry for
this item.
color Default face color used if no vertex or face colors
@@ -153,8 +153,12 @@ def parseMeshData(self):
self.colors = md.faceColors(indexed='faces')
if self.opts['drawEdges']:
- self.edges = md.edges()
- self.edgeVerts = md.vertexes()
+ if not md.hasFaceIndexedData():
+ self.edges = md.edges()
+ self.edgeVerts = md.vertexes()
+ else:
+ self.edges = md.edges()
+ self.edgeVerts = md.vertexes(indexed='faces')
return
def paint(self):
@@ -177,7 +181,7 @@ def paint(self):
if self.colors is None:
color = self.opts['color']
if isinstance(color, QtGui.QColor):
- glColor4f(*pg.glColor(color))
+ glColor4f(*fn.glColor(color))
else:
glColor4f(*color)
else:
@@ -209,7 +213,7 @@ def paint(self):
if self.edgeColors is None:
color = self.opts['edgeColor']
if isinstance(color, QtGui.QColor):
- glColor4f(*pg.glColor(color))
+ glColor4f(*fn.glColor(color))
else:
glColor4f(*color)
else:
diff --git a/pyqtgraph/opengl/items/GLScatterPlotItem.py b/papi/pyqtgraph/opengl/items/GLScatterPlotItem.py
similarity index 96%
rename from pyqtgraph/opengl/items/GLScatterPlotItem.py
rename to papi/pyqtgraph/opengl/items/GLScatterPlotItem.py
index b02a9dda..dc4b298a 100644
--- a/pyqtgraph/opengl/items/GLScatterPlotItem.py
+++ b/papi/pyqtgraph/opengl/items/GLScatterPlotItem.py
@@ -2,7 +2,7 @@
from OpenGL.arrays import vbo
from .. GLGraphicsItem import GLGraphicsItem
from .. import shaders
-from pyqtgraph import QtGui
+from ... import QtGui
import numpy as np
__all__ = ['GLScatterPlotItem']
@@ -28,8 +28,7 @@ def setData(self, **kwds):
colors unchanged, etc.
==================== ==================================================
- Arguments:
- ------------------------------------------------------------------------
+ **Arguments:**
pos (N,3) array of floats specifying point locations.
color (N,4) array of floats (0.0-1.0) specifying
spot colors OR a tuple of floats specifying
@@ -60,14 +59,15 @@ def initializeGL(self):
w = 64
def fn(x,y):
r = ((x-w/2.)**2 + (y-w/2.)**2) ** 0.5
- return 200 * (w/2. - np.clip(r, w/2.-1.0, w/2.))
+ return 255 * (w/2. - np.clip(r, w/2.-1.0, w/2.))
pData = np.empty((w, w, 4))
pData[:] = 255
pData[:,:,3] = np.fromfunction(fn, pData.shape[:2])
#print pData.shape, pData.min(), pData.max()
pData = pData.astype(np.ubyte)
- self.pointTexture = glGenTextures(1)
+ if getattr(self, "pointTexture", None) is None:
+ self.pointTexture = glGenTextures(1)
glActiveTexture(GL_TEXTURE0)
glEnable(GL_TEXTURE_2D)
glBindTexture(GL_TEXTURE_2D, self.pointTexture)
diff --git a/pyqtgraph/opengl/items/GLSurfacePlotItem.py b/papi/pyqtgraph/opengl/items/GLSurfacePlotItem.py
similarity index 88%
rename from pyqtgraph/opengl/items/GLSurfacePlotItem.py
rename to papi/pyqtgraph/opengl/items/GLSurfacePlotItem.py
index 88d50fac..e39ef3bb 100644
--- a/pyqtgraph/opengl/items/GLSurfacePlotItem.py
+++ b/papi/pyqtgraph/opengl/items/GLSurfacePlotItem.py
@@ -1,8 +1,7 @@
from OpenGL.GL import *
from .GLMeshItem import GLMeshItem
from .. MeshData import MeshData
-from pyqtgraph.Qt import QtGui
-import pyqtgraph as pg
+from ...Qt import QtGui
import numpy as np
@@ -37,14 +36,14 @@ def setData(self, x=None, y=None, z=None, colors=None):
"""
Update the data in this surface plot.
- ========== =====================================================================
- Arguments
- x,y 1D arrays of values specifying the x,y positions of vertexes in the
- grid. If these are omitted, then the values will be assumed to be
- integers.
- z 2D array of height values for each grid vertex.
- colors (width, height, 4) array of vertex colors.
- ========== =====================================================================
+ ============== =====================================================================
+ **Arguments:**
+ x,y 1D arrays of values specifying the x,y positions of vertexes in the
+ grid. If these are omitted, then the values will be assumed to be
+ integers.
+ z 2D array of height values for each grid vertex.
+ colors (width, height, 4) array of vertex colors.
+ ============== =====================================================================
All arguments are optional.
diff --git a/pyqtgraph/opengl/items/GLVolumeItem.py b/papi/pyqtgraph/opengl/items/GLVolumeItem.py
similarity index 93%
rename from pyqtgraph/opengl/items/GLVolumeItem.py
rename to papi/pyqtgraph/opengl/items/GLVolumeItem.py
index 4980239d..cbe22db9 100644
--- a/pyqtgraph/opengl/items/GLVolumeItem.py
+++ b/papi/pyqtgraph/opengl/items/GLVolumeItem.py
@@ -1,7 +1,8 @@
from OpenGL.GL import *
from .. GLGraphicsItem import GLGraphicsItem
-from pyqtgraph.Qt import QtGui
+from ...Qt import QtGui
import numpy as np
+from ... import debug
__all__ = ['GLVolumeItem']
@@ -25,13 +26,22 @@ def __init__(self, data, sliceDensity=1, smooth=True, glOptions='translucent'):
self.sliceDensity = sliceDensity
self.smooth = smooth
- self.data = data
+ self.data = None
+ self._needUpload = False
+ self.texture = None
GLGraphicsItem.__init__(self)
self.setGLOptions(glOptions)
+ self.setData(data)
+
+ def setData(self, data):
+ self.data = data
+ self._needUpload = True
+ self.update()
- def initializeGL(self):
+ def _uploadData(self):
glEnable(GL_TEXTURE_3D)
- self.texture = glGenTextures(1)
+ if self.texture is None:
+ self.texture = glGenTextures(1)
glBindTexture(GL_TEXTURE_3D, self.texture)
if self.smooth:
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
@@ -60,9 +70,16 @@ def initializeGL(self):
glNewList(l, GL_COMPILE)
self.drawVolume(ax, d)
glEndList()
-
-
+
+ self._needUpload = False
+
def paint(self):
+ if self.data is None:
+ return
+
+ if self._needUpload:
+ self._uploadData()
+
self.setupGLState()
glEnable(GL_TEXTURE_3D)
diff --git a/pyqtgraph/opengl/items/__init__.py b/papi/pyqtgraph/opengl/items/__init__.py
similarity index 100%
rename from pyqtgraph/opengl/items/__init__.py
rename to papi/pyqtgraph/opengl/items/__init__.py
diff --git a/pyqtgraph/opengl/shaders.py b/papi/pyqtgraph/opengl/shaders.py
similarity index 100%
rename from pyqtgraph/opengl/shaders.py
rename to papi/pyqtgraph/opengl/shaders.py
diff --git a/pyqtgraph/ordereddict.py b/papi/pyqtgraph/ordereddict.py
similarity index 100%
rename from pyqtgraph/ordereddict.py
rename to papi/pyqtgraph/ordereddict.py
diff --git a/pyqtgraph/parametertree/Parameter.py b/papi/pyqtgraph/parametertree/Parameter.py
similarity index 80%
rename from pyqtgraph/parametertree/Parameter.py
rename to papi/pyqtgraph/parametertree/Parameter.py
index 9a7ece25..5f37ccdc 100644
--- a/pyqtgraph/parametertree/Parameter.py
+++ b/papi/pyqtgraph/parametertree/Parameter.py
@@ -1,6 +1,7 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
import os, weakref, re
-from pyqtgraph.pgcollections import OrderedDict
+from ..pgcollections import OrderedDict
+from ..python2_3 import asUnicode
from .ParameterItem import ParameterItem
PARAM_TYPES = {}
@@ -13,7 +14,9 @@ def registerParameterType(name, cls, override=False):
PARAM_TYPES[name] = cls
PARAM_NAMES[cls] = name
-
+def __reload__(old):
+ PARAM_TYPES.update(old.get('PARAM_TYPES', {}))
+ PARAM_NAMES.update(old.get('PARAM_NAMES', {}))
class Parameter(QtCore.QObject):
"""
@@ -46,6 +49,7 @@ class Parameter(QtCore.QObject):
including during editing.
sigChildAdded(self, child, index) Emitted when a child is added
sigChildRemoved(self, child) Emitted when a child is removed
+ sigRemoved(self) Emitted when this parameter is removed
sigParentChanged(self, parent) Emitted when this parameter's parent has changed
sigLimitsChanged(self, limits) Emitted when this parameter's limits have changed
sigDefaultChanged(self, default) Emitted when this parameter's default value has changed
@@ -61,6 +65,7 @@ class Parameter(QtCore.QObject):
sigChildAdded = QtCore.Signal(object, object, object) ## self, child, index
sigChildRemoved = QtCore.Signal(object, object) ## self, child
+ sigRemoved = QtCore.Signal(object) ## self
sigParentChanged = QtCore.Signal(object, object) ## self, parent
sigLimitsChanged = QtCore.Signal(object, object) ## self, limits
sigDefaultChanged = QtCore.Signal(object, object) ## self, default
@@ -107,33 +112,39 @@ def __init__(self, **opts):
Parameter instance, the options available to this method are also allowed
by most Parameter subclasses.
- ================= =========================================================
- Keyword Arguments
- name The name to give this Parameter. This is the name that
- will appear in the left-most column of a ParameterTree
- for this Parameter.
- value The value to initially assign to this Parameter.
- default The default value for this Parameter (most Parameters
- provide an option to 'reset to default').
- children A list of children for this Parameter. Children
- may be given either as a Parameter instance or as a
- dictionary to pass to Parameter.create(). In this way,
- it is possible to specify complex hierarchies of
- Parameters from a single nested data structure.
- readonly If True, the user will not be allowed to edit this
- Parameter. (default=False)
- enabled If False, any widget(s) for this parameter will appear
- disabled. (default=True)
- visible If False, the Parameter will not appear when displayed
- in a ParameterTree. (default=True)
- renamable If True, the user may rename this Parameter.
- (default=False)
- removable If True, the user may remove this Parameter.
- (default=False)
- expanded If True, the Parameter will appear expanded when
- displayed in a ParameterTree (its children will be
- visible). (default=True)
- ================= =========================================================
+ ======================= =========================================================
+ **Keyword Arguments:**
+ name The name to give this Parameter. This is the name that
+ will appear in the left-most column of a ParameterTree
+ for this Parameter.
+ value The value to initially assign to this Parameter.
+ default The default value for this Parameter (most Parameters
+ provide an option to 'reset to default').
+ children A list of children for this Parameter. Children
+ may be given either as a Parameter instance or as a
+ dictionary to pass to Parameter.create(). In this way,
+ it is possible to specify complex hierarchies of
+ Parameters from a single nested data structure.
+ readonly If True, the user will not be allowed to edit this
+ Parameter. (default=False)
+ enabled If False, any widget(s) for this parameter will appear
+ disabled. (default=True)
+ visible If False, the Parameter will not appear when displayed
+ in a ParameterTree. (default=True)
+ renamable If True, the user may rename this Parameter.
+ (default=False)
+ removable If True, the user may remove this Parameter.
+ (default=False)
+ expanded If True, the Parameter will appear expanded when
+ displayed in a ParameterTree (its children will be
+ visible). (default=True)
+ title (str or None) If specified, then the parameter will be
+ displayed to the user using this string as its name.
+ However, the parameter will still be referred to
+ internally using the *name* specified above. Note that
+ this option is not compatible with renamable=True.
+ (default=None; added in version 0.9.9)
+ ======================= =========================================================
"""
@@ -148,6 +159,7 @@ def __init__(self, **opts):
'removable': False,
'strictNaming': False, # forces name to be usable as a python variable
'expanded': True,
+ 'title': None,
#'limits': None, ## This is a bad plan--each parameter type may have a different data type for limits.
}
self.opts.update(opts)
@@ -266,16 +278,27 @@ def getValues(self):
vals[ch.name()] = (ch.value(), ch.getValues())
return vals
- def saveState(self):
+ def saveState(self, filter=None):
"""
Return a structure representing the entire state of the parameter tree.
- The tree state may be restored from this structure using restoreState()
- """
- state = self.opts.copy()
- state['children'] = OrderedDict([(ch.name(), ch.saveState()) for ch in self])
- if state['type'] is None:
- global PARAM_NAMES
- state['type'] = PARAM_NAMES.get(type(self), None)
+ The tree state may be restored from this structure using restoreState().
+
+ If *filter* is set to 'user', then only user-settable data will be included in the
+ returned state.
+ """
+ if filter is None:
+ state = self.opts.copy()
+ if state['type'] is None:
+ global PARAM_NAMES
+ state['type'] = PARAM_NAMES.get(type(self), None)
+ elif filter == 'user':
+ state = {'value': self.value()}
+ else:
+ raise ValueError("Unrecognized filter argument: '%s'" % filter)
+
+ ch = OrderedDict([(ch.name(), ch.saveState(filter=filter)) for ch in self])
+ if len(ch) > 0:
+ state['children'] = ch
return state
def restoreState(self, state, recursive=True, addChildren=True, removeChildren=True, blockSignals=True):
@@ -293,8 +316,11 @@ def restoreState(self, state, recursive=True, addChildren=True, removeChildren=T
## list of children may be stored either as list or dict.
if isinstance(childState, dict):
- childState = childState.values()
-
+ cs = []
+ for k,v in childState.items():
+ cs.append(v.copy())
+ cs[-1].setdefault('name', k)
+ childState = cs
if blockSignals:
self.blockTreeChangeSignal()
@@ -311,14 +337,14 @@ def restoreState(self, state, recursive=True, addChildren=True, removeChildren=T
for ch in childState:
name = ch['name']
- typ = ch['type']
+ #typ = ch.get('type', None)
#print('child: %s, %s' % (self.name()+'.'+name, typ))
- ## First, see if there is already a child with this name and type
+ ## First, see if there is already a child with this name
gotChild = False
for i, ch2 in enumerate(self.childs[ptr:]):
#print " ", ch2.name(), ch2.type()
- if ch2.name() != name or not ch2.isType(typ):
+ if ch2.name() != name: # or not ch2.isType(typ):
continue
gotChild = True
#print " found it"
@@ -393,15 +419,22 @@ def writable(self):
Note that the value of the parameter can *always* be changed by
calling setValue().
"""
- return not self.opts.get('readonly', False)
+ return not self.readonly()
def setWritable(self, writable=True):
"""Set whether this Parameter should be editable by the user. (This is
exactly the opposite of setReadonly)."""
self.setOpts(readonly=not writable)
+ def readonly(self):
+ """
+ Return True if this parameter is read-only. (this is the opposite of writable())
+ """
+ return self.opts.get('readonly', False)
+
def setReadonly(self, readonly=True):
- """Set whether this Parameter's value may be edited by the user."""
+ """Set whether this Parameter's value may be edited by the user
+ (this is the opposite of setWritable())."""
self.setOpts(readonly=readonly)
def setOpts(self, **opts):
@@ -453,11 +486,20 @@ def makeTreeItem(self, depth):
return ParameterItem(self, depth=depth)
- def addChild(self, child):
- """Add another parameter to the end of this parameter's child list."""
- return self.insertChild(len(self.childs), child)
+ def addChild(self, child, autoIncrementName=None):
+ """
+ Add another parameter to the end of this parameter's child list.
+
+ See insertChild() for a description of the *autoIncrementName*
+ argument.
+ """
+ return self.insertChild(len(self.childs), child, autoIncrementName=autoIncrementName)
def addChildren(self, children):
+ """
+ Add a list or dict of children to this parameter. This method calls
+ addChild once for each value in *children*.
+ """
## If children was specified as dict, then assume keys are the names.
if isinstance(children, dict):
ch2 = []
@@ -473,19 +515,24 @@ def addChildren(self, children):
self.addChild(chOpts)
- def insertChild(self, pos, child):
+ def insertChild(self, pos, child, autoIncrementName=None):
"""
Insert a new child at pos.
If pos is a Parameter, then insert at the position of that Parameter.
If child is a dict, then a parameter is constructed using
:func:`Parameter.create `.
+
+ By default, the child's 'autoIncrementName' option determines whether
+ the name will be adjusted to avoid prior name collisions. This
+ behavior may be overridden by specifying the *autoIncrementName*
+ argument. This argument was added in version 0.9.9.
"""
if isinstance(child, dict):
child = Parameter.create(**child)
name = child.name()
if name in self.names and child is not self.names[name]:
- if child.opts.get('autoIncrementName', False):
+ if autoIncrementName is True or (autoIncrementName is None and child.opts.get('autoIncrementName', False)):
name = self.incrementName(name)
child.setName(name)
else:
@@ -516,7 +563,7 @@ def removeChild(self, child):
self.sigChildRemoved.emit(self, child)
try:
child.sigTreeStateChanged.disconnect(self.treeStateChanged)
- except TypeError: ## already disconnected
+ except (TypeError, RuntimeError): ## already disconnected
pass
def clearChildren(self):
@@ -550,6 +597,7 @@ def remove(self):
if parent is None:
raise Exception("Cannot remove; no parent.")
parent.removeChild(self)
+ self.sigRemoved.emit(self)
def incrementName(self, name):
## return an unused name by adding a number to the name given
@@ -590,9 +638,12 @@ def __setitem__(self, names, value):
names = (names,)
return self.param(*names).setValue(value)
- def param(self, *names):
+ def child(self, *names):
"""Return a child parameter.
- Accepts the name of the child or a tuple (path, to, child)"""
+ Accepts the name of the child or a tuple (path, to, child)
+
+ Added in version 0.9.9. Ealier versions used the 'param' method, which is still
+ implemented for backward compatibility."""
try:
param = self.names[names[0]]
except KeyError:
@@ -603,8 +654,12 @@ def param(self, *names):
else:
return param
+ def param(self, *names):
+ # for backward compatibility.
+ return self.child(*names)
+
def __repr__(self):
- return "<%s '%s' at 0x%x>" % (self.__class__.__name__, self.name(), id(self))
+ return asUnicode("<%s '%s' at 0x%x>") % (self.__class__.__name__, self.name(), id(self))
def __getattr__(self, attr):
## Leaving this undocumented because I might like to remove it in the future..
@@ -675,13 +730,13 @@ def treeStateChanged(self, param, changes):
"""
Called when the state of any sub-parameter has changed.
- ========== ================================================================
- Arguments:
- param The immediate child whose tree state has changed.
- note that the change may have originated from a grandchild.
- changes List of tuples describing all changes that have been made
- in this event: (param, changeDescr, data)
- ========== ================================================================
+ ============== ================================================================
+ **Arguments:**
+ param The immediate child whose tree state has changed.
+ note that the change may have originated from a grandchild.
+ changes List of tuples describing all changes that have been made
+ in this event: (param, changeDescr, data)
+ ============== ================================================================
This function can be extended to react to tree state changes.
"""
@@ -692,7 +747,8 @@ def emitTreeChanges(self):
if self.blockTreeChangeEmit == 0:
changes = self.treeStateChanges
self.treeStateChanges = []
- self.sigTreeStateChanged.emit(self, changes)
+ if len(changes) > 0:
+ self.sigTreeStateChanged.emit(self, changes)
class SignalBlocker(object):
diff --git a/pyqtgraph/parametertree/ParameterItem.py b/papi/pyqtgraph/parametertree/ParameterItem.py
similarity index 88%
rename from pyqtgraph/parametertree/ParameterItem.py
rename to papi/pyqtgraph/parametertree/ParameterItem.py
index 46499fd3..c149c411 100644
--- a/pyqtgraph/parametertree/ParameterItem.py
+++ b/papi/pyqtgraph/parametertree/ParameterItem.py
@@ -1,4 +1,5 @@
-from pyqtgraph.Qt import QtGui, QtCore
+from ..Qt import QtGui, QtCore
+from ..python2_3 import asUnicode
import os, weakref, re
class ParameterItem(QtGui.QTreeWidgetItem):
@@ -15,8 +16,11 @@ class ParameterItem(QtGui.QTreeWidgetItem):
"""
def __init__(self, param, depth=0):
- QtGui.QTreeWidgetItem.__init__(self, [param.name(), ''])
-
+ title = param.opts.get('title', None)
+ if title is None:
+ title = param.name()
+ QtGui.QTreeWidgetItem.__init__(self, [title, ''])
+
self.param = param
self.param.registerItem(self) ## let parameter know this item is connected to it (for debugging)
self.depth = depth
@@ -30,7 +34,6 @@ def __init__(self, param, depth=0):
param.sigOptionsChanged.connect(self.optsChanged)
param.sigParentChanged.connect(self.parentChanged)
-
opts = param.opts
## Generate context menu for renaming/removing parameter
@@ -38,6 +41,8 @@ def __init__(self, param, depth=0):
self.contextMenu.addSeparator()
flags = QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled
if opts.get('renamable', False):
+ if param.opts.get('title', None) is not None:
+ raise Exception("Cannot make parameter with both title != None and renamable == True.")
flags |= QtCore.Qt.ItemIsEditable
self.contextMenu.addAction('Rename').triggered.connect(self.editName)
if opts.get('removable', False):
@@ -107,15 +112,15 @@ def contextMenuEvent(self, ev):
self.contextMenu.popup(ev.globalPos())
def columnChangedEvent(self, col):
- """Called when the text in a column has been edited.
+ """Called when the text in a column has been edited (or otherwise changed).
By default, we only use changes to column 0 to rename the parameter.
"""
- if col == 0:
+ if col == 0 and (self.param.opts.get('title', None) is None):
if self.ignoreNameColumnChange:
return
try:
- newName = self.param.setName(str(self.text(col)))
- except:
+ newName = self.param.setName(asUnicode(self.text(col)))
+ except Exception:
self.setText(0, self.param.name())
raise
@@ -127,8 +132,9 @@ def columnChangedEvent(self, col):
def nameChanged(self, param, name):
## called when the parameter's name has changed.
- self.setText(0, name)
-
+ if self.param.opts.get('title', None) is None:
+ self.setText(0, name)
+
def limitsChanged(self, param, limits):
"""Called when the parameter's limits have changed"""
pass
diff --git a/papi/pyqtgraph/parametertree/ParameterSystem.py b/papi/pyqtgraph/parametertree/ParameterSystem.py
new file mode 100644
index 00000000..33bb2de8
--- /dev/null
+++ b/papi/pyqtgraph/parametertree/ParameterSystem.py
@@ -0,0 +1,127 @@
+from .parameterTypes import GroupParameter
+from .. import functions as fn
+from .SystemSolver import SystemSolver
+
+
+class ParameterSystem(GroupParameter):
+ """
+ ParameterSystem is a subclass of GroupParameter that manages a tree of
+ sub-parameters with a set of interdependencies--changing any one parameter
+ may affect other parameters in the system.
+
+ See parametertree/SystemSolver for more information.
+
+ NOTE: This API is experimental and may change substantially across minor
+ version numbers.
+ """
+ def __init__(self, *args, **kwds):
+ GroupParameter.__init__(self, *args, **kwds)
+ self._system = None
+ self._fixParams = [] # all auto-generated 'fixed' params
+ sys = kwds.pop('system', None)
+ if sys is not None:
+ self.setSystem(sys)
+ self._ignoreChange = [] # params whose changes should be ignored temporarily
+ self.sigTreeStateChanged.connect(self.updateSystem)
+
+ def setSystem(self, sys):
+ self._system = sys
+
+ # auto-generate defaults to match child parameters
+ defaults = {}
+ vals = {}
+ for param in self:
+ name = param.name()
+ constraints = ''
+ if hasattr(sys, '_' + name):
+ constraints += 'n'
+
+ if not param.readonly():
+ constraints += 'f'
+ if 'n' in constraints:
+ ch = param.addChild(dict(name='fixed', type='bool', value=False))
+ self._fixParams.append(ch)
+ param.setReadonly(True)
+ param.setOpts(expanded=False)
+ else:
+ vals[name] = param.value()
+ ch = param.addChild(dict(name='fixed', type='bool', value=True, readonly=True))
+ #self._fixParams.append(ch)
+
+ defaults[name] = [None, param.type(), None, constraints]
+
+ sys.defaultState.update(defaults)
+ sys.reset()
+ for name, value in vals.items():
+ setattr(sys, name, value)
+
+ self.updateAllParams()
+
+ def updateSystem(self, param, changes):
+ changes = [ch for ch in changes if ch[0] not in self._ignoreChange]
+
+ #resets = [ch[0] for ch in changes if ch[1] == 'setToDefault']
+ sets = [ch[0] for ch in changes if ch[1] == 'value']
+ #for param in resets:
+ #setattr(self._system, param.name(), None)
+
+ for param in sets:
+ #if param in resets:
+ #continue
+
+ #if param in self._fixParams:
+ #param.parent().setWritable(param.value())
+ #else:
+ if param in self._fixParams:
+ parent = param.parent()
+ if param.value():
+ setattr(self._system, parent.name(), parent.value())
+ else:
+ setattr(self._system, parent.name(), None)
+ else:
+ setattr(self._system, param.name(), param.value())
+
+ self.updateAllParams()
+
+ def updateAllParams(self):
+ try:
+ self.sigTreeStateChanged.disconnect(self.updateSystem)
+ for name, state in self._system._vars.items():
+ param = self.child(name)
+ try:
+ v = getattr(self._system, name)
+ if self._system._vars[name][2] is None:
+ self.updateParamState(self.child(name), 'autoSet')
+ param.setValue(v)
+ else:
+ self.updateParamState(self.child(name), 'fixed')
+ except RuntimeError:
+ self.updateParamState(param, 'autoUnset')
+ finally:
+ self.sigTreeStateChanged.connect(self.updateSystem)
+
+ def updateParamState(self, param, state):
+ if state == 'autoSet':
+ bg = fn.mkBrush((200, 255, 200, 255))
+ bold = False
+ readonly = True
+ elif state == 'autoUnset':
+ bg = fn.mkBrush(None)
+ bold = False
+ readonly = False
+ elif state == 'fixed':
+ bg = fn.mkBrush('y')
+ bold = True
+ readonly = False
+
+ param.setReadonly(readonly)
+
+ #for item in param.items:
+ #item.setBackground(0, bg)
+ #f = item.font(0)
+ #f.setWeight(f.Bold if bold else f.Normal)
+ #item.setFont(0, f)
+
+
+
+
diff --git a/pyqtgraph/parametertree/ParameterTree.py b/papi/pyqtgraph/parametertree/ParameterTree.py
similarity index 67%
rename from pyqtgraph/parametertree/ParameterTree.py
rename to papi/pyqtgraph/parametertree/ParameterTree.py
index 866875e5..ef7c1030 100644
--- a/pyqtgraph/parametertree/ParameterTree.py
+++ b/papi/pyqtgraph/parametertree/ParameterTree.py
@@ -1,5 +1,5 @@
-from pyqtgraph.Qt import QtCore, QtGui
-from pyqtgraph.widgets.TreeWidget import TreeWidget
+from ..Qt import QtCore, QtGui
+from ..widgets.TreeWidget import TreeWidget
import os, weakref, re
from .ParameterItem import ParameterItem
#import functions as fn
@@ -7,9 +7,16 @@
class ParameterTree(TreeWidget):
- """Widget used to display or control data from a ParameterSet"""
+ """Widget used to display or control data from a hierarchy of Parameters"""
def __init__(self, parent=None, showHeader=True):
+ """
+ ============== ========================================================
+ **Arguments:**
+ parent (QWidget) An optional parent widget
+ showHeader (bool) If True, then the QTreeView header is displayed.
+ ============== ========================================================
+ """
TreeWidget.__init__(self, parent)
self.setVerticalScrollMode(self.ScrollPerPixel)
self.setHorizontalScrollMode(self.ScrollPerPixel)
@@ -25,10 +32,35 @@ def __init__(self, parent=None, showHeader=True):
self.setRootIsDecorated(False)
def setParameters(self, param, showTop=True):
+ """
+ Set the top-level :class:`Parameter `
+ to be displayed in this ParameterTree.
+
+ If *showTop* is False, then the top-level parameter is hidden and only
+ its children will be visible. This is a convenience method equivalent
+ to::
+
+ tree.clear()
+ tree.addParameters(param, showTop)
+ """
self.clear()
self.addParameters(param, showTop=showTop)
def addParameters(self, param, root=None, depth=0, showTop=True):
+ """
+ Adds one top-level :class:`Parameter `
+ to the view.
+
+ ============== ==========================================================
+ **Arguments:**
+ param The :class:`Parameter `
+ to add.
+ root The item within the tree to which *param* should be added.
+ By default, *param* is added as a top-level item.
+ showTop If False, then *param* will be hidden, and only its
+ children will be visible in the tree.
+ ============== ==========================================================
+ """
item = param.makeTreeItem(depth=depth)
if root is None:
root = self.invisibleRootItem()
@@ -45,11 +77,14 @@ def addParameters(self, param, root=None, depth=0, showTop=True):
self.addParameters(ch, root=item, depth=depth+1)
def clear(self):
- self.invisibleRootItem().takeChildren()
-
+ """
+ Remove all parameters from the tree.
+ """
+ self.invisibleRootItem().takeChildren()
def focusNext(self, item, forward=True):
- ## Give input focus to the next (or previous) item after 'item'
+ """Give input focus to the next (or previous) item after *item*
+ """
while True:
parent = item.parent()
if parent is None:
diff --git a/papi/pyqtgraph/parametertree/SystemSolver.py b/papi/pyqtgraph/parametertree/SystemSolver.py
new file mode 100644
index 00000000..0a889dfa
--- /dev/null
+++ b/papi/pyqtgraph/parametertree/SystemSolver.py
@@ -0,0 +1,381 @@
+from collections import OrderedDict
+import numpy as np
+
+class SystemSolver(object):
+ """
+ This abstract class is used to formalize and manage user interaction with a
+ complex system of equations (related to "constraint satisfaction problems").
+ It is often the case that devices must be controlled
+ through a large number of free variables, and interactions between these
+ variables make the system difficult to manage and conceptualize as a user
+ interface. This class does _not_ attempt to numerically solve the system
+ of equations. Rather, it provides a framework for subdividing the system
+ into manageable pieces and specifying closed-form solutions to these small
+ pieces.
+
+ For an example, see the simple Camera class below.
+
+ Theory of operation: Conceptualize the system as 1) a set of variables
+ whose values may be either user-specified or automatically generated, and
+ 2) a set of functions that define *how* each variable should be generated.
+ When a variable is accessed (as an instance attribute), the solver first
+ checks to see if it already has a value (either user-supplied, or cached
+ from a previous calculation). If it does not, then the solver calls a
+ method on itself (the method must be named `_variableName`) that will
+ either return the calculated value (which usually involves acccessing
+ other variables in the system), or raise RuntimeError if it is unable to
+ calculate the value (usually because the user has not provided sufficient
+ input to fully constrain the system).
+
+ Each method that calculates a variable value may include multiple
+ try/except blocks, so that if one method generates a RuntimeError, it may
+ fall back on others.
+ In this way, the system may be solved by recursively searching the tree of
+ possible relationships between variables. This allows the user flexibility
+ in deciding which variables are the most important to specify, while
+ avoiding the apparent combinatorial explosion of calculation pathways
+ that must be considered by the developer.
+
+ Solved values are cached for efficiency, and automatically cleared when
+ a state change invalidates the cache. The rules for this are simple: any
+ time a value is set, it invalidates the cache *unless* the previous value
+ was None (which indicates that no other variable has yet requested that
+ value). More complex cache management may be defined in subclasses.
+
+
+ Subclasses must define:
+
+ 1) The *defaultState* class attribute: This is a dict containing a
+ description of the variables in the system--their default values,
+ data types, and the ways they can be constrained. The format is::
+
+ { name: [value, type, constraint, allowed_constraints], ...}
+
+ * *value* is the default value. May be None if it has not been specified
+ yet.
+ * *type* may be float, int, bool, np.ndarray, ...
+ * *constraint* may be None, single value, or (min, max)
+ * None indicates that the value is not constrained--it may be
+ automatically generated if the value is requested.
+ * *allowed_constraints* is a string composed of (n)one, (f)ixed, and (r)ange.
+
+ Note: do not put mutable objects inside defaultState!
+
+ 2) For each variable that may be automatically determined, a method must
+ be defined with the name `_variableName`. This method may either return
+ the
+ """
+
+ defaultState = OrderedDict()
+
+ def __init__(self):
+ self.__dict__['_vars'] = OrderedDict()
+ self.__dict__['_currentGets'] = set()
+ self.reset()
+
+ def reset(self):
+ """
+ Reset all variables in the solver to their default state.
+ """
+ self._currentGets.clear()
+ for k in self.defaultState:
+ self._vars[k] = self.defaultState[k][:]
+
+ def __getattr__(self, name):
+ if name in self._vars:
+ return self.get(name)
+ raise AttributeError(name)
+
+ def __setattr__(self, name, value):
+ """
+ Set the value of a state variable.
+ If None is given for the value, then the constraint will also be set to None.
+ If a tuple is given for a scalar variable, then the tuple is used as a range constraint instead of a value.
+ Otherwise, the constraint is set to 'fixed'.
+
+ """
+ # First check this is a valid attribute
+ if name in self._vars:
+ if value is None:
+ self.set(name, value, None)
+ elif isinstance(value, tuple) and self._vars[name][1] is not np.ndarray:
+ self.set(name, None, value)
+ else:
+ self.set(name, value, 'fixed')
+ else:
+ # also allow setting any other pre-existing attribute
+ if hasattr(self, name):
+ object.__setattr__(self, name, value)
+ else:
+ raise AttributeError(name)
+
+ def get(self, name):
+ """
+ Return the value for parameter *name*.
+
+ If the value has not been specified, then attempt to compute it from
+ other interacting parameters.
+
+ If no value can be determined, then raise RuntimeError.
+ """
+ if name in self._currentGets:
+ raise RuntimeError("Cyclic dependency while calculating '%s'." % name)
+ self._currentGets.add(name)
+ try:
+ v = self._vars[name][0]
+ if v is None:
+ cfunc = getattr(self, '_' + name, None)
+ if cfunc is None:
+ v = None
+ else:
+ v = cfunc()
+ if v is None:
+ raise RuntimeError("Parameter '%s' is not specified." % name)
+ v = self.set(name, v)
+ finally:
+ self._currentGets.remove(name)
+
+ return v
+
+ def set(self, name, value=None, constraint=True):
+ """
+ Set a variable *name* to *value*. The actual set value is returned (in
+ some cases, the value may be cast into another type).
+
+ If *value* is None, then the value is left to be determined in the
+ future. At any time, the value may be re-assigned arbitrarily unless
+ a constraint is given.
+
+ If *constraint* is True (the default), then supplying a value that
+ violates a previously specified constraint will raise an exception.
+
+ If *constraint* is 'fixed', then the value is set (if provided) and
+ the variable will not be updated automatically in the future.
+
+ If *constraint* is a tuple, then the value is constrained to be within the
+ given (min, max). Either constraint may be None to disable
+ it. In some cases, a constraint cannot be satisfied automatically,
+ and the user will be forced to resolve the constraint manually.
+
+ If *constraint* is None, then any constraints are removed for the variable.
+ """
+ var = self._vars[name]
+ if constraint is None:
+ if 'n' not in var[3]:
+ raise TypeError("Empty constraints not allowed for '%s'" % name)
+ var[2] = constraint
+ elif constraint == 'fixed':
+ if 'f' not in var[3]:
+ raise TypeError("Fixed constraints not allowed for '%s'" % name)
+ var[2] = constraint
+ elif isinstance(constraint, tuple):
+ if 'r' not in var[3]:
+ raise TypeError("Range constraints not allowed for '%s'" % name)
+ assert len(constraint) == 2
+ var[2] = constraint
+ elif constraint is not True:
+ raise TypeError("constraint must be None, True, 'fixed', or tuple. (got %s)" % constraint)
+
+ # type checking / massaging
+ if var[1] is np.ndarray:
+ value = np.array(value, dtype=float)
+ elif var[1] in (int, float, tuple) and value is not None:
+ value = var[1](value)
+
+ # constraint checks
+ if constraint is True and not self.check_constraint(name, value):
+ raise ValueError("Setting %s = %s violates constraint %s" % (name, value, var[2]))
+
+ # invalidate other dependent values
+ if var[0] is not None:
+ # todo: we can make this more clever..(and might need to)
+ # we just know that a value of None cannot have dependencies
+ # (because if anyone else had asked for this value, it wouldn't be
+ # None anymore)
+ self.resetUnfixed()
+
+ var[0] = value
+ return value
+
+ def check_constraint(self, name, value):
+ c = self._vars[name][2]
+ if c is None or value is None:
+ return True
+ if isinstance(c, tuple):
+ return ((c[0] is None or c[0] <= value) and
+ (c[1] is None or c[1] >= value))
+ else:
+ return value == c
+
+ def saveState(self):
+ """
+ Return a serializable description of the solver's current state.
+ """
+ state = OrderedDict()
+ for name, var in self._vars.items():
+ state[name] = (var[0], var[2])
+ return state
+
+ def restoreState(self, state):
+ """
+ Restore the state of all values and constraints in the solver.
+ """
+ self.reset()
+ for name, var in state.items():
+ self.set(name, var[0], var[1])
+
+ def resetUnfixed(self):
+ """
+ For any variable that does not have a fixed value, reset
+ its value to None.
+ """
+ for var in self._vars.values():
+ if var[2] != 'fixed':
+ var[0] = None
+
+ def solve(self):
+ for k in self._vars:
+ getattr(self, k)
+
+ def __repr__(self):
+ state = OrderedDict()
+ for name, var in self._vars.items():
+ if var[2] == 'fixed':
+ state[name] = var[0]
+ state = ', '.join(["%s=%s" % (n, v) for n,v in state.items()])
+ return "<%s %s>" % (self.__class__.__name__, state)
+
+
+
+
+
+if __name__ == '__main__':
+
+ class Camera(SystemSolver):
+ """
+ Consider a simple SLR camera. The variables we will consider that
+ affect the camera's behavior while acquiring a photo are aperture, shutter speed,
+ ISO, and flash (of course there are many more, but let's keep the example simple).
+
+ In rare cases, the user wants to manually specify each of these variables and
+ no more work needs to be done to take the photo. More often, the user wants to
+ specify more interesting constraints like depth of field, overall exposure,
+ or maximum allowed ISO value.
+
+ If we add a simple light meter measurement into this system and an 'exposure'
+ variable that indicates the desired exposure (0 is "perfect", -1 is one stop
+ darker, etc), then the system of equations governing the camera behavior would
+ have the following variables:
+
+ aperture, shutter, iso, flash, exposure, light meter
+
+ The first four variables are the "outputs" of the system (they directly drive
+ the camera), the last is a constant (the camera itself cannot affect the
+ reading on the light meter), and 'exposure' specifies a desired relationship
+ between other variables in the system.
+
+ So the question is: how can I formalize a system like this as a user interface?
+ Typical cameras have a fairly limited approach: provide the user with a list
+ of modes, each of which defines a particular set of constraints. For example:
+
+ manual: user provides aperture, shutter, iso, and flash
+ aperture priority: user provides aperture and exposure, camera selects
+ iso, shutter, and flash automatically
+ shutter priority: user provides shutter and exposure, camera selects
+ iso, aperture, and flash
+ program: user specifies exposure, camera selects all other variables
+ automatically
+ action: camera selects all variables while attempting to maximize
+ shutter speed
+ portrait: camera selects all variables while attempting to minimize
+ aperture
+
+ A more general approach might allow the user to provide more explicit
+ constraints on each variable (for example: I want a shutter speed of 1/30 or
+ slower, an ISO no greater than 400, an exposure between -1 and 1, and the
+ smallest aperture possible given all other constraints) and have the camera
+ solve the system of equations, with a warning if no solution is found. This
+ is exactly what we will implement in this example class.
+ """
+
+ defaultState = OrderedDict([
+ # Field stop aperture
+ ('aperture', [None, float, None, 'nf']),
+ # Duration that shutter is held open.
+ ('shutter', [None, float, None, 'nf']),
+ # ISO (sensitivity) value. 100, 200, 400, 800, 1600..
+ ('iso', [None, int, None, 'nf']),
+
+ # Flash is a value indicating the brightness of the flash. A table
+ # is used to decide on "balanced" settings for each flash level:
+ # 0: no flash
+ # 1: s=1/60, a=2.0, iso=100
+ # 2: s=1/60, a=4.0, iso=100 ..and so on..
+ ('flash', [None, float, None, 'nf']),
+
+ # exposure is a value indicating how many stops brighter (+1) or
+ # darker (-1) the photographer would like the photo to appear from
+ # the 'balanced' settings indicated by the light meter (see below).
+ ('exposure', [None, float, None, 'f']),
+
+ # Let's define this as an external light meter (not affected by
+ # aperture) with logarithmic output. We arbitrarily choose the
+ # following settings as "well balanced" for each light meter value:
+ # -1: s=1/60, a=2.0, iso=100
+ # 0: s=1/60, a=4.0, iso=100
+ # 1: s=1/120, a=4.0, iso=100 ..and so on..
+ # Note that the only allowed constraint mode is (f)ixed, since the
+ # camera never _computes_ the light meter value, it only reads it.
+ ('lightMeter', [None, float, None, 'f']),
+
+ # Indicates the camera's final decision on how it thinks the photo will
+ # look, given the chosen settings. This value is _only_ determined
+ # automatically.
+ ('balance', [None, float, None, 'n']),
+ ])
+
+ def _aperture(self):
+ """
+ Determine aperture automatically under a variety of conditions.
+ """
+ iso = self.iso
+ exp = self.exposure
+ light = self.lightMeter
+
+ try:
+ # shutter-priority mode
+ sh = self.shutter # this raises RuntimeError if shutter has not
+ # been specified
+ ap = 4.0 * (sh / (1./60.)) * (iso / 100.) * (2 ** exp) * (2 ** light)
+ ap = np.clip(ap, 2.0, 16.0)
+ except RuntimeError:
+ # program mode; we can select a suitable shutter
+ # value at the same time.
+ sh = (1./60.)
+ raise
+
+
+
+ return ap
+
+ def _balance(self):
+ iso = self.iso
+ light = self.lightMeter
+ sh = self.shutter
+ ap = self.aperture
+ fl = self.flash
+
+ bal = (4.0 / ap) * (sh / (1./60.)) * (iso / 100.) * (2 ** light)
+ return np.log2(bal)
+
+ camera = Camera()
+
+ camera.iso = 100
+ camera.exposure = 0
+ camera.lightMeter = 2
+ camera.shutter = 1./60.
+ camera.flash = 0
+
+ camera.solve()
+ print(camera.saveState())
+
\ No newline at end of file
diff --git a/pyqtgraph/parametertree/__init__.py b/papi/pyqtgraph/parametertree/__init__.py
similarity index 74%
rename from pyqtgraph/parametertree/__init__.py
rename to papi/pyqtgraph/parametertree/__init__.py
index acdb7a37..722410d5 100644
--- a/pyqtgraph/parametertree/__init__.py
+++ b/papi/pyqtgraph/parametertree/__init__.py
@@ -1,5 +1,5 @@
from .Parameter import Parameter, registerParameterType
from .ParameterTree import ParameterTree
from .ParameterItem import ParameterItem
-
+from .ParameterSystem import ParameterSystem, SystemSolver
from . import parameterTypes as types
\ No newline at end of file
diff --git a/pyqtgraph/parametertree/parameterTypes.py b/papi/pyqtgraph/parametertree/parameterTypes.py
similarity index 90%
rename from pyqtgraph/parametertree/parameterTypes.py
rename to papi/pyqtgraph/parametertree/parameterTypes.py
index 3300171f..7b1c5ee6 100644
--- a/pyqtgraph/parametertree/parameterTypes.py
+++ b/papi/pyqtgraph/parametertree/parameterTypes.py
@@ -1,14 +1,14 @@
-from pyqtgraph.Qt import QtCore, QtGui
-from pyqtgraph.python2_3 import asUnicode
+from ..Qt import QtCore, QtGui
+from ..python2_3 import asUnicode
from .Parameter import Parameter, registerParameterType
from .ParameterItem import ParameterItem
-from pyqtgraph.widgets.SpinBox import SpinBox
-from pyqtgraph.widgets.ColorButton import ColorButton
-#from pyqtgraph.widgets.GradientWidget import GradientWidget ## creates import loop
-import pyqtgraph as pg
-import pyqtgraph.pixmaps as pixmaps
+from ..widgets.SpinBox import SpinBox
+from ..widgets.ColorButton import ColorButton
+#from ..widgets.GradientWidget import GradientWidget ## creates import loop
+from .. import pixmaps as pixmaps
+from .. import functions as fn
import os
-from pyqtgraph.pgcollections import OrderedDict
+from ..pgcollections import OrderedDict
class WidgetParameterItem(ParameterItem):
"""
@@ -18,16 +18,16 @@ class WidgetParameterItem(ParameterItem):
* simple widget for editing value (displayed instead of label when item is selected)
* button that resets value to default
- ================= =============================================================
- Registered Types:
- int Displays a :class:`SpinBox ` in integer
- mode.
- float Displays a :class:`SpinBox `.
- bool Displays a QCheckBox
- str Displays a QLineEdit
- color Displays a :class:`ColorButton `
- colormap Displays a :class:`GradientWidget