Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/AnyCPU.Debug/Playing-in-the-Street-WPF.vshost.exe.manifest
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/AnyCPU.Debug/Playing-in-the-Street-WPF.vshost.exe.manifest	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/AnyCPU.Debug/Playing-in-the-Street-WPF.vshost.exe.manifest	(revision 28897)
@@ -0,0 +1,11 @@
+﻿<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+</assembly>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/AnyCPU.Debug/WebserverBasics-WPF.vshost.exe.manifest
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/AnyCPU.Debug/WebserverBasics-WPF.vshost.exe.manifest	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/AnyCPU.Debug/WebserverBasics-WPF.vshost.exe.manifest	(revision 28897)
@@ -0,0 +1,11 @@
+﻿<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+  <assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
+  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
+    <security>
+      <requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
+        <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+      </requestedPrivileges>
+    </security>
+  </trustInfo>
+</assembly>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/App.xaml
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/App.xaml	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/App.xaml	(revision 28897)
@@ -0,0 +1,8 @@
+﻿<Application x:Class="Microsoft.Samples.Kinect.WebserverBasics.App"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             StartupUri="MainWindow.xaml">
+    <Application.Resources>
+         
+    </Application.Resources>
+</Application>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/App.xaml.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/App.xaml.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/App.xaml.cs	(revision 28897)
@@ -0,0 +1,17 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="App.xaml.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.WebserverBasics
+{
+    using System.Windows;
+
+    /// <summary>
+    /// Interaction logic for App.xaml
+    /// </summary>
+    public partial class App : Application
+    {
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/Kinect-1.8.0.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/Kinect-1.8.0.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/Kinect-1.8.0.css	(revision 28897)
@@ -0,0 +1,134 @@
+﻿/***************************************************************************
+ * <copyright file="Kinect-1.8.0.css" company="Microsoft">                 *
+ *     Copyright (c) Microsoft Corporation.  All rights reserved.          *
+ * </copyright>                                                            *
+ ***************************************************************************/
+
+.kinect-button {
+    margin: 10px;
+    display: inline-block;
+    padding: 25px;
+    height: 200px;
+    width: 200px;
+    position: relative;
+    font-family: 'Segoe UI';
+    font-size: 16pt;
+    font-weight: 600;
+}
+
+.kinect-button.small {
+    height: 120px;
+    width: 120px;
+    font-size: 12pt;
+    font-weight: 500;
+}
+
+.kinect-button.medium {
+    height: 200px;
+    width: 200px;
+    font-size: 16pt;
+    font-weight: 600;
+}
+
+.kinect-button.large {
+    height: 300px;
+    width: 300px;
+    font-size: 20pt;
+    font-weight: 600;
+}
+
+.kinect-button-surface {
+    position: relative;
+    left: 3%;
+    top: 3%;
+    width: 94%;
+    height: 94%;
+    transition: left 0.16s, top 0.16s, width 0.16s, height 0.16s;
+}
+
+.kinect-button-text {
+    position: absolute;
+    left: 0px;
+    top: 50%;
+    height: 20pt;
+    width: 100%;
+    margin-top: -10pt;
+    text-align: center;
+    user-select: none;
+    -webkit-user-select: none;
+}
+            
+.button-hover .kinect-button-surface {
+    left: 0%;
+    top: 0%;
+    width: 100%;
+    height: 100%;
+    transition: left 0.16s, top 0.16s, width 0.16s, height 0.16s;
+}
+
+.button-pressed .kinect-button-surface {
+    left: 7.5%;
+    top: 7.5%;
+    width: 85%;
+    height: 85%;
+    transition: left 0.10s, top 0.10s, width 0.10s, height 0.10s;
+}
+
+.kinect-button.tile .kinect-button-surface {
+    border: none;
+    background: linear-gradient(to bottom, #511c74, #311040);
+    color: white;
+}
+
+#kinect-cursor {
+    position: absolute;
+    display: none;
+    width: 120px;
+    height: 120px;
+}
+
+/* normal cursor state */
+#kinect-cursor-glow {
+    stroke-opacity: 0.0;
+}
+
+#kinect-cursor-normal {
+    stroke-opacity: 1.0;
+    fill-opacity: 1.0;
+}
+
+#cursor-progress {
+    stroke-opacity: 0.0;
+    fill-opacity: 0.0;
+}
+
+#kinect-cursor-extended {
+    stroke-opacity: 0.0;
+    fill-opacity: 0.0;
+}
+
+/* hover cursor state */
+.cursor-hover #kinect-cursor-glow {
+    stroke-opacity: 1.0;
+}
+
+.cursor-hover #kinect-cursor-progress {
+    stroke-opacity: 1.0;
+    fill-opacity: 1.0;
+}
+
+/* pressed cursor state */
+.cursor-pressed #kinect-cursor-normal {
+    stroke-opacity: 0.0;
+    fill-opacity: 0.0;
+}
+
+.cursor-pressed #kinect-cursor-progress {
+    stroke-opacity: 0.0;
+    fill-opacity: 0.0;
+}
+
+.cursor-pressed #kinect-cursor-extended {
+    stroke-opacity: 1.0;
+    fill-opacity: 1.0;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/Kinect-1.8.0.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/Kinect-1.8.0.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/Kinect-1.8.0.js	(revision 28897)
@@ -0,0 +1,2226 @@
+// -----------------------------------------------------------------------
+// <copyright file="Kinect-1.8.0.js" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+
+//////////////////////////////////////////////////////////////
+// Create the global Kinect object
+var Kinect = (function () {
+    "use strict";
+    
+    //////////////////////////////////////////////////////////////
+    // SocketManager object constructor
+    //     SocketManager(uri, onmessage(socket,message) [, onconnection(isConnected)] )
+    //
+    // uri: URI of websocket endpoint to connect to
+    // onmessage: callback function to call whenever a message is received
+    // onconnection: function to call back whenever socket connection status changes
+    //               from disconnected to connected or vice versa
+    function SocketManager(uri, onmessage, onconnection) {
+
+        //////////////////////////////////////////////////////////////
+        // Private SocketExec properties
+        var onStatusChanged = null;
+        var statusMessage = "";
+        var socket = null;
+        var socketManager = this;
+        var wsUri = uri.replace(/^http(s)?:/i, "ws$1:");
+
+        //////////////////////////////////////////////////////////////
+        // Private SocketExec methods
+        function updateStatusChanged() {
+            if (onStatusChanged != null) {
+                onStatusChanged(statusMessage);
+            }
+        }
+
+        function updateStatus(message) {
+            statusMessage = message;
+
+            updateStatusChanged();
+        }
+
+        function tryConnection() {
+            if (!socketManager.isStarted) {
+                return;
+            }
+
+            if (socket != null) {
+                updateStatus("Already connected." + (new Date()).toTimeString());
+                return;
+            }
+            updateStatus("Connecting to server...");
+
+            // Initialize a new web socket.
+            socket = new WebSocket(wsUri);
+            
+            // Receive binary data as ArrayBuffer rather than Blob
+            socket.binaryType = "arraybuffer";
+
+            // Connection established.
+            socket.onopen = function () {
+                if (typeof onconnection == "function") {
+                    onconnection(true);
+                }
+                
+                updateStatus("Connected to server.");
+            };
+
+            // Connection closed.
+            socket.onclose = function () {
+                if (typeof onconnection == "function") {
+                    onconnection(false);
+                }
+                
+                updateStatus("Connection closed.");
+                if (socketManager.isStarted) {
+                    // Keep trying to reconnect as long as we're started
+                    setTimeout(tryConnection, socketManager.retryTimeout, socketManager);
+                }
+                socket = null;
+            };
+
+            // Receive data FROM the server!
+            socket.onmessage = function(message) {
+                onmessage(socket, message);
+            };
+        }
+
+        //////////////////////////////////////////////////////////////
+        // Public SocketManager properties
+        
+        // connection retry timeout, in milliseconds
+        this.retryTimeout = 5000;
+
+        // true if socket has been started
+        this.isStarted = false;
+
+        //////////////////////////////////////////////////////////////
+        // Public SocketManager functions
+        this.setOnStatusChanged = function (statusChangedCallback) {
+            onStatusChanged = statusChangedCallback;
+            updateStatusChanged();
+        };
+
+        this.start = function() {
+            this.isStarted = true;
+
+            tryConnection(this);
+        };
+
+        this.stop = function() {
+            this.isStarted = false;
+
+            if (socket != null) {
+                socket.close();
+            }
+        };
+
+        //////////////////////////////////////////////////////////////
+        // SocketManager initialization code
+        if (window.WebSocket == null) {
+            updateStatus("Your browser does not support web sockets!");
+            return;
+        }
+    }
+
+    //////////////////////////////////////////////////////////////
+    // KinectConnector object constructor
+    function KinectConnector() {
+
+        //////////////////////////////////////////////////////////////
+        // Private KinectConnector properties
+
+        // Configure default connection values
+        var DEFAULT_HOST_URI = "http://localhost";
+        var DEFAULT_HOST_PORT = 8181;
+        var DEFAULT_BASE_PATH = "Kinect";
+        var DEFAULT_SENSOR_NAME = "default";
+
+        // If connectionUri is null, it indicates that we're not connected yet
+        var connectionUri = null;
+
+        // If explicitDisconnect is true, it indicates that client has called
+        // "KinectConnector.disconnect" explicitly.
+        var explicitDisconnect = false;
+
+        // Mapping between sensor names and KinectSensor objects
+        var sensorMap = {};
+        
+        // true if server requests to REST endpoint should be asynchronous
+        var asyncRequests = true;
+
+        var connector = this;
+
+        //////////////////////////////////////////////////////////////
+        // KinectSensor object constructor
+        //     KinectSensor(baseEndpointUri, onconnection(sensor, isConnected))
+        // 
+        // baseEndpointUri: base URI for web endpoints corresponding to sensor
+        // onconnection: Function to call back whenever status of connection to the
+        //               server that owns this sensor changes from disconnected to
+        //               connected or vice versa.
+        function KinectSensor(baseEndpointUri, onconnection) {
+
+            //////////////////////////////////////////////////////////////
+            // Private KinectSensor properties
+
+            // URI used to connect to endpoint used to configure sensor state
+            var stateEndpoint = baseEndpointUri + "/state";
+
+            // true if sensor connection to server is currently enabled, false otherwise
+            var isConnectionEnabled = false;
+            
+            // Array of registered stream frame ready handlers
+            var streamFrameHandlers = [];
+            
+            // Header portion of a two-part stream message that is still missing a binary payload
+            var pendingStreamFrame = null;
+
+            // Reference to this sensor object for use in internal functions
+            var sensor = this;
+
+            var streamSocketManager = new SocketManager(
+                baseEndpointUri + "/stream",
+                function(socket, message) {
+
+                    function broadcastFrame(frame) {
+                        for (var iHandler = 0; iHandler < streamFrameHandlers.length; ++iHandler) {
+                            streamFrameHandlers[iHandler](frame);
+                        }
+                    }
+
+                    if (message.data instanceof ArrayBuffer) {
+                        if ((pendingStreamFrame == null) || (pendingStreamFrame.bufferLength != message.data.byteLength)) {
+                            // Ignore any binary messages that were not preceded by a header message
+                            console.log("Binary socket message received without corresponding header");
+                            pendingStreamFrame = null;
+                            return;
+                        }
+
+                        pendingStreamFrame.buffer = message.data;
+                        broadcastFrame(pendingStreamFrame);
+                        pendingStreamFrame = null;
+                    } else if (typeof(message.data) == "string") {
+                        pendingStreamFrame = null;
+
+                        try {
+                            // Get the data in JSON format.
+                            var frameData = JSON.parse(message.data);
+
+                            // If message has a 'bufferLength' property, it means that this is a message header
+                            // and we should wait for the binary payload before broadcasting this message.
+                            if ("bufferLength" in frameData) {
+                                pendingStreamFrame = frameData;
+                            } else {
+                                broadcastFrame(frameData);
+                            }
+                        } catch(error) {
+                            console.log('Tried processing stream message, but failed with error: ' + error);
+                        }
+                    } else {
+                        // Ignore any messages of unexpected types.
+                        console.log("Unexpected type for received socket message");
+                        pendingStreamFrame = null;
+                        return;
+                    }
+                },
+                function(isConnected) {
+                    // Use the existence of the stream channel connection to mean that a
+                    // server exists and is ready to accept requests
+                    sensor.isConnected = isConnected;
+                    onconnection(sensor, isConnected);
+                });
+            
+            // Array of registered event handlers
+            var eventHandlers = [];
+
+            var eventSocketManager = new SocketManager(baseEndpointUri + "/events", function(socket, message) {
+                // Get the data in JSON format.
+                var eventData = JSON.parse(message.data);
+
+                for (var iHandler = 0; iHandler < eventHandlers.length; ++iHandler) {
+                    eventHandlers[iHandler](eventData);
+                }
+            });
+
+            // Registered hit test handler
+            var hitTestHandler = null;
+            
+            var hitTestSocketManager = new SocketManager(baseEndpointUri + "/interaction/client", function (socket, message) {
+                // Get the data in JSON format.
+                var eventData = JSON.parse(message.data);
+                var id = eventData.id;
+                var name = eventData.name;
+                var args = eventData.args;
+
+                switch (name) {
+                    case "getInteractionInfoAtLocation":
+                        var handlerResult = null;
+                        var defaultResult = { "isPressTarget": false, "isGripTarget": false };
+
+                        if (hitTestHandler != null) {
+                            try {
+                                handlerResult = hitTestHandler.apply(sensor, args);
+                            } catch(e) {
+                                handlerResult = null;
+                            }
+                        }
+
+                        socket.send(JSON.stringify({ "id": id, "result": ((handlerResult != null) ? handlerResult : defaultResult) }));
+                        break;
+                    
+                    case "ping":
+                        socket.send(JSON.stringify({ "id": id, "result": true }));
+                        break;
+                }
+            });
+            
+            //////////////////////////////////////////////////////////////
+            // Private KinectSensor functions
+
+            // Perform ajax request
+            //    ajax( method, uri, success(responseData) [, error(statusText)] )
+            //
+            // method: http method
+            // uri: target uri of ajax call
+            // requestData: data to send to uri as part of request (may be null)
+            // success: Callback function executed if request succeeds.
+            // error: Callback function executed if request fails (may be null)
+            function ajax(method, uri, requestData, success, error) {
+                if (!isConnectionEnabled) {
+                    if (error != null) {
+                        error("disconnected from server");
+                    }
+                    return;
+                }
+                
+                var xhr = new XMLHttpRequest();
+                xhr.open(method, uri, asyncRequests);
+                xhr.onload = function (e) {
+                    if (xhr.readyState === XMLHttpRequest.DONE) {
+                        if (xhr.status == 200) {
+                            success(JSON.parse(xhr.responseText));
+                        } else if (error != null) {
+                            error(xhr.statusText);
+                        }
+                    }
+                };
+                if (error != null) {
+                    xhr.onerror = function (e) {
+                        error(xhr.statusText);
+                    };
+                }
+                xhr.send(requestData);
+            }
+            
+            // Respond to changes in the connection enabled status
+            //    onConnectionEnabledChanged( )
+            function onConnectionEnabledChanged() {
+                if (isConnectionEnabled && !streamSocketManager.isStarted) {
+                    streamSocketManager.start();
+                } else if (!isConnectionEnabled && streamSocketManager.isStarted) {
+                    streamSocketManager.stop();
+                }
+            }
+            
+            // Respond to changes in the number of registered event handlers
+            //    onEventHandlersChanged( )
+            function onEventHandlersChanged() {
+                if ((eventHandlers.length > 0) && !eventSocketManager.isStarted) {
+                    eventSocketManager.start();
+                } else if ((eventHandlers.length == 0) && eventSocketManager.isStarted) {
+                    eventSocketManager.stop();
+                }
+            }
+            
+            // Respond to changes in the registered hit test handler
+            //    onHitTestHandlerChanged( )
+            function onHitTestHandlerChanged() {
+                if ((hitTestHandler != null) && !hitTestSocketManager.isStarted) {
+                    hitTestSocketManager.start();
+                } else if ((hitTestHandler == null) && hitTestSocketManager.isStarted) {
+                    hitTestSocketManager.stop();
+                }
+            }
+            
+            //////////////////////////////////////////////////////////////
+            // Public KinectSensor properties
+            this.isConnected = false;
+
+            //////////////////////////////////////////////////////////////
+            // Public KinectSensor functions
+
+            // Request current sensor configuration
+            //    .getConfig( success(configData) [, error(statusText)] )
+            //
+            // success: Callback function executed if request succeeds.
+            // error: Callback function executed if request fails.
+            this.getConfig = function (success, error) {
+                if ((arguments.length < 1) || (typeof success != 'function')) {
+                    throw new Error("first parameter must be specified and must be a function");
+                }
+
+                if ((arguments.length >= 2) && (typeof error != 'function')) {
+                    throw new Error("if second parameter is specified, it must be a function");
+                }
+
+                ajax("GET", stateEndpoint, null,
+                    function(response) { success(response); },
+                    (error != null) ? function (statusText) { error(statusText); } : null
+                );
+            };
+
+            // Update current sensor configuration
+            //     .postConfig( configData [, error(statusText [, data] )] )
+            //
+            // configData: New configuration property/value pairs to update
+            //             for each sensor stream.
+            // error: Callback function executed if request fails.
+            //        data parameter of error callback will be null if request
+            //        could not be completed at all, but will be a JSON object
+            //        giving failure details in case of semantic failure to
+            //        satisfy request.
+            this.postConfig = function (configData, error) {
+                if ((arguments.length < 1) || (typeof configData != 'object')) {
+                    throw new Error("first parameter must be specified and must be an object");
+                }
+
+                if ((arguments.length >= 2) && (typeof error != 'function')) {
+                    throw new Error("if second parameter is specified, it must be a function");
+                }
+
+                ajax("POST", stateEndpoint, JSON.stringify(configData),
+                    function(response) {
+                        if (!response.success && (error != null)) {
+                            error("semantic failure", response);
+                        }
+                    },
+                    (error != null) ? function (statusText) { error(statusText); } : null
+                );
+            };
+
+            // Enable connections with server
+            //    .connect()
+            this.connect = function () {
+                isConnectionEnabled = true;
+
+                onConnectionEnabledChanged();
+                onEventHandlersChanged();
+                onHitTestHandlerChanged();
+            };
+
+            // Disable connections with server
+            //    .disconnect()
+            this.disconnect = function () {
+                isConnectionEnabled = false;
+                
+                onConnectionEnabledChanged();
+                onEventHandlersChanged();
+                onHitTestHandlerChanged();
+            };
+            
+            // Add a new stream frame handler.
+            //     .addStreamFrameHandler( handler(streamFrame) )] )
+            //
+            // handler: Callback function to be executed when a stream frame is ready to
+            //          be processed.
+            this.addStreamFrameHandler = function (handler) {
+                if (typeof(handler) != "function") {
+                    throw new Error("first parameter must be specified and must be a function");
+                }
+                
+                streamFrameHandlers.push(handler);
+            };
+            
+            // Removes one (or all) stream frame handler(s).
+            //     .removeStreamFrameHandler( [handler(streamFrame)] )] )
+            //
+            // handler: Stream frame handler callback function to be removed.
+            //          If omitted, all stream frame handlers are removed.
+            this.removeStreamFrameHandler = function (handler) {
+                switch (typeof(handler)) {
+                    case "undefined":
+                        streamFrameHandlers = [];
+                        break;
+                    case "function":
+                        var index = streamFrameHandlers.indexOf(handler);
+                        if (index >= 0) {
+                            streamFrameHandlers.slice(index, index + 1);
+                        }
+                        break;
+                        
+                    default:
+                        throw new Error("first parameter must either be a function or left unspecified");
+                }
+            };
+            
+            // Add a new event handler.
+            //     .addEventHandler( handler(event) )] )
+            //
+            // handler: Callback function to be executed when an event is ready to be processed.
+            this.addEventHandler = function (handler) {
+                if (typeof (handler) != "function") {
+                    throw new Error("first parameter must be specified and must be a function");
+                }
+
+                eventHandlers.push(handler);
+
+                onEventHandlersChanged();
+            };
+
+            // Removes one (or all) event handler(s).
+            //     .removeEventHandler( [handler(streamFrame)] )] )
+            //
+            // handler: Event handler callback function to be removed.
+            //          If omitted, all event handlers are removed.
+            this.removeEventHandler = function (handler) {
+                switch (typeof (handler)) {
+                    case "undefined":
+                        eventHandlers = [];
+                        break;
+                    case "function":
+                        var index = eventHandlers.indexOf(handler);
+                        if (index >= 0) {
+                            eventHandlers.slice(index, index + 1);
+                        }
+                        break;
+
+                    default:
+                        throw new Error("first parameter must either be a function or left unspecified");
+                }
+                
+                onEventHandlersChanged();
+            };
+            
+            // Set hit test handler.
+            //     .setHitTestHandler( [handler(skeletonTrackingId, handType, x, y)] )] )
+            //
+            // handler: Callback function to be executed when interaction stream needs 
+            //          interaction information in order to adjust a hand pointer position
+            //          relative to UI.
+            //          If omitted, hit test handler is removed.
+            //          - skeletonTrackingId: The skeleton tracking ID for which interaction
+            //                                information is being retrieved.
+            //          - handType: "left" or "right" to represent hand type for which
+            //                       interaction information is being retrieved.
+            //          - x: X-coordinate of UI location for which interaction information
+            //               is being retrieved. 0.0 corresponds to left edge of interaction
+            //               region and 1.0 corresponds to right edge of interaction region.
+            //          - y: Y-coordinate of UI location for which interaction information
+            //               is being retrieved. 0.0 corresponds to top edge of interaction
+            //               region and 1.0 corresponds to bottom edge of interaction region.
+            this.setHitTestHandler = function (handler) {
+                switch (typeof (handler)) {
+                    case "undefined":
+                    case "function":
+                        hitTestHandler = handler;
+                        break;
+
+                    default:
+                        throw new Error("first parameter must be either a function or left unspecified");
+                }
+
+                onHitTestHandlerChanged();
+            };
+        }
+
+        //////////////////////////////////////////////////////////////
+        // Public KinectConnector properties
+        
+        // Server connection defaults
+        this.DEFAULT_HOST_URI = DEFAULT_HOST_URI;
+        this.DEFAULT_HOST_PORT = DEFAULT_HOST_PORT;
+        this.DEFAULT_BASE_PATH = DEFAULT_BASE_PATH;
+        this.DEFAULT_SENSOR_NAME = DEFAULT_SENSOR_NAME;
+        
+        // Supported stream names
+        this.INTERACTION_STREAM_NAME = "interaction";
+        this.USERVIEWER_STREAM_NAME = "userviewer";
+        this.BACKGROUNDREMOVAL_STREAM_NAME = "backgroundRemoval";
+        this.SKELETON_STREAM_NAME = "skeleton";
+        this.SENSORSTATUS_STREAM_NAME = "sensorStatus";
+        
+        // Supported event categories and associated event types
+        this.USERSTATE_EVENT_CATEGORY = "userState";
+        this.PRIMARYUSERCHANGED_EVENT_TYPE = "primaryUserChanged";
+        this.USERSTATESCHANGED_EVENT_TYPE = "userStatesChanged";
+        this.SENSORSTATUS_EVENT_CATEGORY = "sensorStatus";
+        this.SENSORSTATUSCHANGED_EVENT_TYPE = "statusChanged";
+        
+        //////////////////////////////////////////////////////////////
+        // Public KinectConnector methods
+
+        // Enable connections with server
+        //    .connect( [ hostUri [, hostPort [, basePath ] ] ] )
+        //
+        // hostUri: URI for host that is serving Kinect data.
+        //          Defaults to "http://localhost".
+        // hostPort: HTTP port from which Kinect data is being served.
+        //           Defaults to 8181.
+        // basePath: base URI path for all endpoints that serve Kinect data.
+        //           Defaults to "kinect".
+        this.connect = function (hostUri, hostPort, basePath) {
+            hostUri = (hostUri != null) ? hostUri : DEFAULT_HOST_URI;
+            hostPort = (hostPort != null) ? hostPort : DEFAULT_HOST_PORT;
+            basePath = (basePath != null) ? basePath : DEFAULT_BASE_PATH;
+
+            // Indicate we're now connected
+            connectionUri = hostUri + ":" + hostPort + "/" + basePath;
+            explicitDisconnect = false;
+        };
+
+        // Disable connections with server
+        //     .disconnect()
+        this.disconnect = function () {
+            // stop all sensors currently tracked
+            
+            for (var sensorKey in sensorMap) {
+                var sensor = sensorMap[sensorKey];
+                sensor.disconnect();
+            }
+
+            sensorMap = {};
+            
+            // Indicate we're now disconnected
+            connectionUri = null;
+            explicitDisconnect = true;
+        };
+
+        // Gets an object representing a connection with a specific Kinect sensor.
+        //     .sensor( [sensorName [, onconnection(sensor, isConnected)]] )
+        //
+        // sensorName: name used to refer to a specific Kinect sensor exposed by the server.
+        //             Defaults to "default".
+        // onconnection: Function to call back whenever status of connection to the
+        //               server that owns the sensor with specified name changes from
+        //               disconnected to connected or vice versa.
+        //
+        // Remarks
+        // - If client has never called KinectConnector.connect or .disconnect, .connect() is
+        //   called implicitly with default server URI parameters.
+        // - If client has explicitly called KinectConnector.disconnect, calling this method
+        //   returns null.
+        // - If a non-null sensor is returned, it will already be in connected state (i.e.:
+        //   there is no need to call KinectSensor.connect immediately after calling this
+        //   method).
+        this.sensor = function (sensorName, onconnection) {
+            switch (typeof(sensorName)) {
+                case "undefined":
+                case "string":
+                    break;
+                        
+                default:
+                    throw new Error("first parameter must be a string or left unspecified");
+            }
+            switch (typeof (onconnection)) {
+                case "undefined":
+                    // provide a guarantee to KinectSensor constructor that onconnection
+                    // will exist and be a real function
+                    onconnection = function() { };
+                    break;
+                case "function":
+                    break;
+
+                default:
+                    throw new Error("second parameter must be a function or left unspecified");
+            }
+
+            if (explicitDisconnect) {
+                return null;
+            }
+            
+            if (connectionUri == null) {
+                // If we're not connected yet, connect with default parameters
+                // when someone asks for a sensor.
+                this.connect();
+            }
+
+            sensorName = (sensorName != null) ? sensorName : DEFAULT_SENSOR_NAME;
+
+            // If sensor for specified name doesn't exist yet, create it
+            if (!sensorMap.hasOwnProperty(sensorName)) {
+                sensorMap[sensorName] = new KinectSensor(connectionUri + "/" + sensorName, onconnection);
+
+                // Start the sensors connected by default when they are requested
+                sensorMap[sensorName].connect();
+            }
+
+            return sensorMap[sensorName];
+        };
+
+        // Get whether server requests to REST endpoints should be asynchronous (true by default)
+        //     .async()
+        //
+        // -------------------------------------------------------------------------------------
+        // Specify whether server requests to REST endpoints should be asynchronous
+        //     .async( asyncRequests )
+        // 
+        // isAsync: true if server requests to REST endpoints should be asynchronous
+        //          false if server requests to REST endpoints should be synchronous
+        this.async = function(isAsync) {
+            if (typeof isAsync == 'undefined') {
+                return asyncRequests;
+            }
+
+            var newAsync = isAsync == true;
+            asyncRequests = newAsync;
+        };
+    }
+
+    // The global Kinect object is a singleton instance of our internal KinectConnector type
+    return new KinectConnector();
+})();
+
+//////////////////////////////////////////////////////////////
+// Create the global Kinect UI factory object
+var KinectUI = (function () {
+    "use strict";
+    
+    //////////////////////////////////////////////////////////////
+    // KinectUIAdapter object constructor
+    function KinectUIAdapter() {
+        
+        //////////////////////////////////////////////////////////////
+        // HandPointer object constructor
+        function HandPointer(trackingId, playerIndex, handType) {
+            //////////////////////////////////////////////////////////////
+            // Public HandPointer properties
+            this.trackingId = trackingId;
+            this.playerIndex = playerIndex;
+            this.handType = handType;
+            this.timestampOfLastUpdate = 0;
+            this.handEventType = "None";
+            this.isTracked = false;
+            this.isActive = false;
+            this.isInteractive = false;
+            this.isPressed = false;
+            this.isPrimaryHandOfUser = false;
+            this.isPrimaryUser = false;
+            this.isInGripInteraction = false;
+            this.captured = null;
+            this.x = 0.0;
+            this.y = 0.0;
+            this.pressExtent = 0.0;
+            this.rawX = 0.0;
+            this.rawY = 0.0;
+            this.rawZ = 0.0;
+            this.originalHandPointer = null;
+            this.updated = false;
+
+            //////////////////////////////////////////////////////////////
+            // Public HandPointer methods
+            
+            // Get all DOM elements that this hand pointer has entered.
+            //     .getEnteredElements()
+            this.getEnteredElements = function () {
+                var key = getHandPointerKey(this.trackingId, this.handType);
+                var data = internalHandPointerData[key];
+
+                if (data == null) {
+                    return [];
+                }
+
+                return data.enteredElements;
+            };
+
+            // Determine whether this hand pointer is over the specified DOM element.
+            //     .getIsOver( element )
+            //
+            // element: DOM element against which to check hand pointer position.
+            this.getIsOver = function (element) {
+                // Iterate over cached elements
+                var enteredElements = this.getEnteredElements();
+
+                for (var iElement = 0; iElement < enteredElements.length; ++iElement) {
+                    if (enteredElements[iElement] === element) {
+                        return true;
+                    }
+                }
+
+                return false;
+            };
+            
+            // Create a capture association between this hand pointer and the specified DOM
+            // element. This means that event handlers associated with element will receive
+            // events triggered by this hand pointer even if hand pointer is not directly
+            // over element.
+            //     .capture( element )
+            //
+            // element: DOM element capturing hand pointer.
+            //          If null, this hand pointer stops being captured and resumes normal
+            //          event routing behavior.
+            this.capture = function (element) {
+                return captureHandPointer(this, element);
+            };
+            
+            // Determine whether this hand pointer corresponds to the primary hand of the
+            // primary user.
+            //     .getIsPrimaryHandOfPrimaryUser()
+            this.getIsPrimaryHandOfPrimaryUser = function () {
+                return this.isPrimaryUser && this.isPrimaryHandOfUser;
+            };
+            
+            // Update this hand pointer's properties from the specified hand pointer received
+            // from the interaction stream.
+            //     .update( streamHandPointer )
+            this.update = function (streamHandPointer) {
+                // save the stream hand pointer in case it has additional properties
+                // that clients might be interested in
+                this.originalHandPointer = streamHandPointer;
+                
+                this.updated = true;
+                
+                this.timestampOfLastUpdate = streamHandPointer.timestampOfLastUpdate;
+                this.handEventType = streamHandPointer.handEventType;
+
+                var pressedChanged = this.isPressed != streamHandPointer.isPressed;
+                this.isPressed = streamHandPointer.isPressed;
+
+                this.isTracked = streamHandPointer.isTracked;
+                this.isActive = streamHandPointer.isActive;
+                this.isInteractive = streamHandPointer.isInteractive;
+
+                var primaryHandOfPrimaryUserChanged = this.getIsPrimaryHandOfPrimaryUser() != (streamHandPointer.isPrimaryHandOfUser && streamHandPointer.isPrimaryUser);
+                this.isPrimaryHandOfUser = streamHandPointer.isPrimaryHandOfUser;
+                this.isPrimaryUser = streamHandPointer.isPrimaryUser;
+
+                var newPosition = interactionZoneToWindow({ "x": streamHandPointer.x, "y": streamHandPointer.y });
+                var positionChanged = !areUserInterfaceValuesClose(newPosition.x, this.x) ||
+                        !areUserInterfaceValuesClose(newPosition.y, this.y) ||
+                        !areUserInterfaceValuesClose(streamHandPointer.pressExtent, this.pressExtent);
+                this.x = newPosition.x;
+                this.y = newPosition.y;
+                this.pressExtent = streamHandPointer.pressExtent;
+
+                this.rawX = streamHandPointer.rawX;
+                this.rawY = streamHandPointer.rawY;
+                this.rawZ = streamHandPointer.rawZ;
+
+                return {
+                    "pressedChanged": pressedChanged,
+                    "primaryHandOfPrimaryUserChanged": primaryHandOfPrimaryUserChanged,
+                    "positionChanged": positionChanged
+                };
+            };
+        }
+        
+        //////////////////////////////////////////////////////////////
+        // ImageWorkerManager object constructor
+        //     new ImageWorkerManager()
+        //
+        // Remarks
+        // Manages background thread work related to processing image
+        // stream frames to get them ready to be displayed in a canvas
+        // element.
+        function ImageWorkerManager() {
+            
+            //////////////////////////////////////////////////////////////
+            // ImageMetadata object constructor
+            function ImageMetadata(imageCanvas) {
+                this.isProcessing = false;
+                this.canvas = imageCanvas;
+                this.width = 0;
+                this.height = 0;
+            }
+
+            //////////////////////////////////////////////////////////////
+            // Private ImageWorkerManager properties
+            var workerThread = null;
+            var imageMetadataMap = {};
+            
+            //////////////////////////////////////////////////////////////
+            // Private ImageWorkerManager methods
+            function ensureInitialized() {
+                // Lazy-initialize web worker thread
+                if (workerThread == null) {
+                    workerThread = new Worker(scriptRootURIPath + "KinectWorker-1.8.0.js");
+                    workerThread.addEventListener("message", function (event) {
+                        var imageName = event.data.imageName;
+                        if (!imageMetadataMap.hasOwnProperty(imageName)) {
+                            return;
+                        }
+                        var metadata = imageMetadataMap[imageName];
+                        
+                        switch (event.data.message) {
+                            case "imageReady":
+                                // Put ready image data in associated canvas
+                                var canvasContext = metadata.canvas.getContext("2d");
+                                canvasContext.putImageData(event.data.imageData, 0, 0);
+                                metadata.isProcessing = false;
+                                break;
+                                
+                            case "notProcessed":
+                                metadata.isProcessing = false;
+                                break;
+                        }
+                    });
+                }
+            }
+
+            //////////////////////////////////////////////////////////////
+            // Public ImageWorkerManager methods
+            
+            // Send named image data to be processed by worker thread
+            //     .processImageData(imageName, imageBuffer, width, height)
+            //
+            // imageName: Name of image to process
+            // imageBuffer: ArrayBuffer containing image data
+            // width: width of image corresponding to imageBuffer data
+            // height: height of image corresponding to imageBuffer data
+            this.processImageData = function (imageName, imageBuffer, width, height) {
+                ensureInitialized();
+                
+                if (!imageMetadataMap.hasOwnProperty(imageName)) {
+                    // We're not tracking this image, so no work to do
+                    return;
+                }
+                var metadata = imageMetadataMap[imageName];
+                
+                if (metadata.isProcessing || (width <= 0) || (height <= 0)) {
+                    // Don't send more data to worker thread when we are in the middle
+                    // of processing data already.
+                    // Also, Only do work if image data to process is of the expected size
+                    return;
+                }
+                
+                metadata.isProcessing = true;
+
+                if ((width != metadata.width) || (height != metadata.height)) {
+                    // Whenever the image width or height changes, update image tracking metadata
+                    // and canvas ImageData associated with worker thread
+                    
+                    var canvasContext = metadata.canvas.getContext("2d");
+                    var imageData = canvasContext.createImageData(width, height);
+                    metadata.width = width;
+                    metadata.height = height;
+                    metadata.canvas.width = width;
+                    metadata.canvas.height = height;
+
+                    workerThread.postMessage({ "message": "setImageData", "imageName": imageName, "imageData": imageData });
+                }
+                
+                workerThread.postMessage({ "message": "processImageData", "imageName": imageName, "imageBuffer": imageBuffer });
+            };
+
+            // Associate specified image name with canvas ImageData object for future usage by
+            // worker thread
+            //     .setImageData(imageName, canvas)
+            //
+            // imageName: Name of image stream to associate with ImageData object
+            // canvas: Canvas to bind to user viewer stream
+            this.setImageData = function (imageName, canvas) {
+                ensureInitialized();
+
+                if (canvas != null) {
+                    var metadata = new ImageMetadata(canvas);
+                    imageMetadataMap[imageName] = metadata;
+                } else if (imageMetadataMap.hasOwnProperty(imageName)) {
+                    // If specified canvas is null but we're already tracking image data,
+                    // remove metadata associated with this image.
+                    delete imageMetadataMap[imageName];
+                }
+            };
+        }
+        
+        //////////////////////////////////////////////////////////////
+        // Private KinectUIAdapter constants
+        var LAZYRELEASE_RADIUS = 0.3;
+        var LAZYRELEASE_YCUTOFF = LAZYRELEASE_RADIUS / 3;
+        
+        //////////////////////////////////////////////////////////////
+        // Private KinectUIAdapter properties
+        var uiAdapter = this;
+        var isInInteractionFrame = false;
+        var isClearRequestPending = false;
+        var handPointerEventsEnabled = true;
+        // Property names of internalHandPointerData are keys that encode user tracking id and hand type.
+        // Property values are JSON objects with "handPointer" and "enteredElements" properties.
+        var internalHandPointerData = {};
+        var excludedElements = [];
+        var eventHandlers = {};
+        var bindableStreamNames = {};
+        bindableStreamNames[Kinect.USERVIEWER_STREAM_NAME] = true;
+        bindableStreamNames[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = true;
+        var imageWorkerManager = new ImageWorkerManager();
+
+        //////////////////////////////////////////////////////////////
+        // Private KinectUIAdapter methods
+        function interactionZoneToElement(point, element) {
+            return { "x": point.x * element.offsetWidth, "y": point.y * element.offsetHeight };
+        }
+
+        function elementToInteractionZone(point, element) {
+            return { "x": point.x / element.offsetWidth, "y": point.y / element.offsetHeight };
+        }
+
+        function interactionZoneToWindow(point) {
+            return { "x": point.x * window.innerWidth, "y": point.y * window.innerHeight };
+        }
+
+        function windowToInteractionZone(point) {
+            return { "x": point.x / window.innerWidth, "y": point.y / window.innerHeight };
+        }
+
+        function areUserInterfaceValuesClose(a, b) {
+            return Math.abs(a - b) < 0.5;
+        }
+
+        function getPressTargetPoint(element) {
+            // Hardcoded to always return the center point
+            return { "x": 0.5, "y": 0.5 };
+        }
+
+        function getHandPointerKey(trackingId, handType) {
+            return handType + trackingId;
+        }
+
+        function createInteractionEvent(eventName, detail) {
+            var event = document.createEvent("CustomEvent");
+            var canBubble = true;
+            var cancelable = false;
+
+            switch (eventName) {
+                case uiController.HANDPOINTER_ENTER:
+                case uiController.HANDPOINTER_LEAVE:
+                    canBubble = false;
+                    break;
+            }
+
+            event.initCustomEvent(eventName, canBubble, cancelable, detail);
+
+            return event;
+        }
+
+        function raiseHandPointerEvent(element, eventName, handPointer) {
+            if (!handPointerEventsEnabled) {
+                return;
+            }
+            
+            var event = createInteractionEvent(eventName, { "handPointer": handPointer });
+            element.dispatchEvent(event);
+        }
+        
+        function raiseGlobalUIEvent(eventName, eventData) {
+            if (!handPointerEventsEnabled || !eventHandlers.hasOwnProperty(eventName)) {
+                // Events are disabled, or there are no event handlers registered for event.
+                return;
+            }
+
+            var handlerArray = eventHandlers[eventName];
+            for (var i = 0; i < handlerArray.length; ++i) {
+                handlerArray[i](eventData);
+            }
+        }
+        
+        function switchCapture(handPointer, oldElement, newElement) {
+            handPointer.captured = newElement;
+
+            if (oldElement != null) {
+                raiseHandPointerEvent(oldElement, uiController.HANDPOINTER_LOSTCAPTURE, handPointer);
+            }
+
+            if (newElement != null) {
+                raiseHandPointerEvent(newElement, uiController.HANDPOINTER_GOTCAPTURE, handPointer);
+            }
+        }
+
+        function captureHandPointer(handPointer, element) {
+            var id = getHandPointerKey(handPointer.trackingId, handPointer.handType);
+
+            if (!internalHandPointerData.hasOwnProperty(id)) {
+                // We're not already tracking a hand pointer with the same id.
+                return false;
+            }
+
+            var handPointerData = internalHandPointerData[id];
+            var checkHandPointer = handPointerData.handPointer;
+            if (checkHandPointer !== handPointer) {
+                // The hand pointer we're tracking is not the same one that was handed in.
+                // Caller may have a stale hand pointer instance.
+                return false;
+            }
+
+            if (element != null && handPointer.captured != null) {
+                // Request wasn't to clear capture and some other element already has this
+                // HandPointer captured.
+                return false;
+            }
+
+            switchCapture(handPointer, handPointer.captured, element);
+            return true;
+        }
+
+        // Get the DOM element branch (parent chain), rooted in body element,
+        // that includes the specified leaf element.
+        // If leafElement is null, the returned branch will be empty
+        function getDOMBranch(leafElement) {
+            var scanStop = document.body.parentNode;
+            var branch = [];
+
+            for (var scan = leafElement; (scan != null) && (scan != scanStop) && (scan != document) ; scan = scan.parentNode) {
+                branch.push(scan);
+            }
+
+            return branch;
+        }
+
+        function doEnterLeaveNotifications(handPointer, primaryHandOfPrimaryUserChanged, oldEnteredElements, newEnteredElements) {
+            var isPrimaryHandOfPrimaryUser = handPointer.getIsPrimaryHandOfPrimaryUser();
+            var wasPrimaryHandOfPrimaryUser = primaryHandOfPrimaryUserChanged ? !isPrimaryHandOfPrimaryUser : isPrimaryHandOfPrimaryUser;
+
+            // find common elements between old and new set
+            // We take advantage of the fact that later elements in the array contain earlier
+            // elements, so they will remain in the same state for a longer time.
+            var firstOldMatch = oldEnteredElements.length;
+            var firstNewMatch = newEnteredElements.length;
+            while ((firstOldMatch > 0) &&
+                   (firstNewMatch > 0) &&
+                   (oldEnteredElements[firstOldMatch - 1] === newEnteredElements[firstNewMatch - 1])) {
+                --firstOldMatch;
+                --firstNewMatch;
+            }
+
+            // Tell old entered elements that are not entered anymore that we have left them
+            for (var iLeft = 0; iLeft < oldEnteredElements.length; ++iLeft) {
+                var leftElement = oldEnteredElements[iLeft];
+
+                if (iLeft < firstOldMatch) {
+                    // This element is not one of the common elements, so we have left it
+
+                    // If we were or still are the HandPointer for the primary user's
+                    // primary hand then clear out the "IsPrimaryHandPointerOver" property.
+                    if (wasPrimaryHandOfPrimaryUser) {
+                        uiController.setIsPrimaryHandPointerOver(leftElement, false);
+                    }
+
+                    // Tell this element that this hand pointer has left
+                    raiseHandPointerEvent(leftElement, uiController.HANDPOINTER_LEAVE, handPointer);
+                } else {
+                    // This is one of the common elements
+                    if (wasPrimaryHandOfPrimaryUser && !isPrimaryHandOfPrimaryUser) {
+                        // Hand pointer didn't leave the element but it is no longer the primary
+                        uiController.setIsPrimaryHandPointerOver(leftElement, false);
+                    }
+                }
+            }
+
+            // Tell new entered elements that were not previously entered we are now over them
+            for (var iEntered = 0; iEntered < newEnteredElements.length; ++iEntered) {
+                var enteredElement = newEnteredElements[iEntered];
+
+                if (iEntered < firstNewMatch) {
+                    // This element is not one of the common elements, so we have entered it
+
+                    // If we are the HandPointer for the primary user's primary hand then
+                    // set the "IsPrimaryHandPointerOver" property.
+                    if (isPrimaryHandOfPrimaryUser) {
+                        uiController.setIsPrimaryHandPointerOver(enteredElement, true);
+                    }
+
+                    // Tell this element that this hand pointer has left
+                    raiseHandPointerEvent(enteredElement, uiController.HANDPOINTER_ENTER, handPointer);
+                } else {
+                    // This is one of the common elements
+                    if (!wasPrimaryHandOfPrimaryUser && isPrimaryHandOfPrimaryUser) {
+                        // Hand pointer was already in this element but it became the primary
+                        uiController.setIsPrimaryHandPointerOver(enteredElement, true);
+                    }
+                }
+            }
+        }
+        
+        function beginInteractionFrame() {
+            if (isInInteractionFrame) {
+                console.log("Call to beginInteractionFrame was made without corresponding call to endInteractionFrame");
+                return;
+            }
+
+            isInInteractionFrame = true;
+            isClearRequestPending = false;
+
+            for (var handPointerKey in internalHandPointerData) {
+                var handPointer = internalHandPointerData[handPointerKey].handPointer;
+                handPointer.updated = false;
+            }
+        }
+
+        function handleHandPointerChanges(handPointerData, pressedChanged, positionChanged, primaryHandOfPrimaryUserChanged, removed) {
+            var doPress = false;
+            var doRelease = false;
+            var doMove = false;
+            var doLostCapture = false;
+            var doGrip = false;
+            var doGripRelease = false;
+            var handPointer = handPointerData.handPointer;
+
+            if (removed) {
+                // Deny the existence of this hand pointer
+                doRelease = handPointer.isPressed;
+                doLostCapture = handPointer.captured != null;
+            } else {
+                if (pressedChanged) {
+                    doPress = handPointer.isPressed;
+                    doRelease = !handPointer.isPressed;
+                }
+
+                if (positionChanged) {
+                    doMove = true;
+                }
+
+                doGrip = handPointer.handEventType.toLowerCase() == "grip";
+                doGripRelease = handPointer.handEventType.toLowerCase() == "griprelease";
+            }
+
+            if (doLostCapture) {
+                switchCapture(handPointer, handPointer.captured, null);
+            }
+
+            var targetElement = handPointer.captured;
+            if (targetElement == null) {
+                targetElement = hitTest(handPointer.x, handPointer.y);
+            }
+
+            // Update internal enter/leave state
+            var oldEnteredElements = handPointerData.enteredElements;
+            var newEnteredElements = getDOMBranch(removed ? null : targetElement);
+            handPointerData.enteredElements = newEnteredElements;
+
+            // See if this hand pointer is participating in a grip-initiated
+            // interaction.
+            var newIsInGripInteraction = false;
+            if (targetElement != null) {
+                var result = raiseHandPointerEvent(targetElement, uiController.QUERY_INTERACTION_STATUS, handPointer);
+
+                if ((result != null) && result.isInGripInteraction) {
+                    newIsInGripInteraction = true;
+                }
+            }
+
+            handPointer.isInGripInteraction = newIsInGripInteraction;
+
+            //// After this point there should be no more changes to the internal
+            //// state of the handPointers.  We don't want event handlers calling us
+            //// when our internal state is inconsistent.
+
+            doEnterLeaveNotifications(handPointer, primaryHandOfPrimaryUserChanged, oldEnteredElements, newEnteredElements);
+
+            if (targetElement == null) {
+                return;
+            }
+
+            if (doGrip) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_GRIP, handPointer);
+            }
+
+            if (doGripRelease) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_GRIPRELEASE, handPointer);
+            }
+
+            if (doPress) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_PRESS, handPointer);
+            }
+
+            if (doMove) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_MOVE, handPointer);
+            }
+
+            if (doRelease) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_PRESSRELEASE, handPointer);
+            }
+        }
+
+        function handleHandPointerData(interactionStreamHandPointers) {
+            if (!isInInteractionFrame) {
+                console.log("Call to handleHandPointerData was made without call to beginInteractionFrame");
+                return;
+            }
+
+            if (isClearRequestPending) {
+                // We don't care about new hand pointer data if client requested to clear
+                // all hand pointers while in the middle of interaction frame processing.
+                return;
+            }
+
+            for (var iData = 0; iData < interactionStreamHandPointers.length; ++iData) {
+                var streamHandPointer = interactionStreamHandPointers[iData];
+                var id = getHandPointerKey(streamHandPointer.trackingId, streamHandPointer.handType);
+
+                var handPointerData;
+                var handPointer;
+                if (internalHandPointerData.hasOwnProperty(id)) {
+                    handPointerData = internalHandPointerData[id];
+                    handPointer = handPointerData.handPointer;
+                } else {
+                    handPointer = new HandPointer(streamHandPointer.trackingId, streamHandPointer.playerIndex, streamHandPointer.handType);
+
+                    handPointerData = { "handPointer": handPointer, "enteredElements": [] };
+                    internalHandPointerData[id] = handPointerData;
+                }
+
+                var result = handPointer.update(streamHandPointer);
+                handleHandPointerChanges(handPointerData, result.pressedChanged, result.positionChanged, result.primaryHandOfPrimaryUserChanged, false);
+            }
+        }
+
+        function endInteractionFrame() {
+            if (!isInInteractionFrame) {
+                console.log("Call to endInteractionFrame was made without call to beginInteractionFrame");
+                return;
+            }
+
+            removeStaleHandPointers();
+
+            isClearRequestPending = false;
+            isInInteractionFrame = false;
+        }
+
+        function removeStaleHandPointers() {
+            var nonUpdatedHandpointers = [];
+
+            for (var handPointerKey in internalHandPointerData) {
+                // If we need to stop tracking this hand pointer
+                var handPointer = internalHandPointerData[handPointerKey].handPointer;
+                if (isClearRequestPending || !handPointer.updated) {
+                    nonUpdatedHandpointers.push(handPointerKey);
+                }
+            }
+
+            for (var i = 0; i < nonUpdatedHandpointers.length; ++i) {
+                var handPointerKey = nonUpdatedHandpointers[i];
+                var handPointerData = internalHandPointerData[handPointerKey];
+                var handPointer = handPointerData.handPointer;
+                var pressedChanged = handPointer.isPressed;
+                var positionChanged = false;
+                var primaryHandOfPrimaryUserChanged = handPointer.getIsPrimaryHandOfPrimaryUser();
+                var removed = true;
+
+                handPointer.isTracked = false;
+                handPointer.isActive = false;
+                handPointer.isInteractive = false;
+                handPointer.isPressed = false;
+                handPointer.isPrimaryUser = false;
+                handPointer.isPrimaryHandOfUser = false;
+
+                handleHandPointerChanges(handPointerData, pressedChanged, positionChanged, primaryHandOfPrimaryUserChanged, removed);
+
+                delete internalHandPointerData[handPointerKey];
+            }
+        }
+        
+        function updatePublicHandPointers() {
+            var iHandPointer = 0;
+
+            for (var handPointerKey in internalHandPointerData) {
+                var handPointer = internalHandPointerData[handPointerKey].handPointer;
+
+                if (handPointer.isTracked) {
+                    if (uiAdapter.handPointers.length > iHandPointer) {
+                        uiAdapter.handPointers[iHandPointer] = handPointer;
+                    } else {
+                        uiAdapter.handPointers.push(handPointer);
+                    }
+
+                    ++iHandPointer;
+                }
+            }
+
+            // Truncate the length of the array to the number of valid elements
+            uiAdapter.handPointers.length = iHandPointer;
+
+            raiseGlobalUIEvent(uiController.HANDPOINTERS_UPDATED, uiAdapter.handPointers);
+        }
+        
+        function hitTest(x, y) {
+            // Ensure excluded elements are hidden before performing hit test
+            var displayProp = "display";
+            var displayValues = [];
+            for (var i = 0; i < excludedElements.length; ++i) {
+                var element = excludedElements[i];
+                displayValues.push(element.style[displayProp]);
+                element.style[displayProp] = "none";
+            }
+
+            var hitElement = document.elementFromPoint(x, y);
+
+            // Restore visibility to excluded elements
+            for (var i = 0; i < excludedElements.length; ++i) {
+                excludedElements[i].style[displayProp] = displayValues[i];
+            }
+
+            return hitElement;
+        }
+        
+        function getElementId(element) {
+            // First check if we've already set the kinect element ID.
+            var id = jQuery.data(element, kinectIdPrefix);
+            if (id != null) {
+                return id;
+            }
+
+            // If not, fall back to regular element id, if present
+            id = element.id;
+            
+            if (id.trim() == "") {
+                // If there is no id on element, assign one ourselves
+                id = kinectIdPrefix + (++lastAssignedControlId);
+            }
+            
+            // Remember assigned id
+            jQuery.data(element, kinectIdPrefix, id);
+            return id;
+        }
+        
+        function setCapture(element) {
+            // Not all browsers support mouse capture functionality
+            if (typeof element.setCapture == "function") {
+                element.setCapture(true);
+                return true;
+            }
+
+            return false;
+        }
+
+        function releaseCapture() {
+            // Not all browsers support mouse capture functionality
+            if (typeof document.releaseCapture == "function") {
+                document.releaseCapture();
+                return true;
+            }
+
+            return false;
+        }
+
+        function promoteToButton(element) {
+            var content = element.innerHTML;
+
+            if (element.children.length <= 0) {
+                // If element has no children, wrap content in an element that will perform
+                // default text alignment
+                content = "<span class='kinect-button-text'>" + content + "</span>";
+            }
+
+            // Wrap all content in a "kinect-button-surface" div element to allow button
+            // shrinking and expanding as user hovers over or presses button
+            element.innerHTML = "<div class='kinect-button-surface'>" + content + "</div>";
+
+            uiController.setIsPressTarget(element, true);
+            var capturedHandPointer = null;
+            var pressedElement = null;
+            var isMouseOver = false;
+            var isMouseCaptured = false;
+            var removeCaptureEvents = false;
+
+            var releaseButtonCapture = function(event) {
+                releaseCapture();
+                isMouseCaptured = false;
+
+                if (removeCaptureEvents) {
+                    $(event.target).off("mousemove");
+                }
+            };
+
+            $(element).on({
+                // respond to hand pointer events
+                handPointerPress: function (event) {
+                    var handPointer = event.originalEvent.detail.handPointer;
+                    if (capturedHandPointer == null && handPointer.getIsPrimaryHandOfPrimaryUser()) {
+                        handPointer.capture(event.currentTarget);
+                        event.stopPropagation();
+                    }
+                },
+                handPointerGotCapture: function (event) {
+                    var handPointer = event.originalEvent.detail.handPointer;
+                    if (capturedHandPointer == null) {
+                        capturedHandPointer = handPointer;
+                        pressedElement = handPointer.captured;
+                        $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
+                        event.stopPropagation();
+                    }
+                },
+                handPointerPressRelease: function (event) {
+                    var handPointer = event.originalEvent.detail.handPointer;
+                    if (capturedHandPointer == handPointer) {
+                        var captured = handPointer.captured;
+                        handPointer.capture(null);
+
+                        if (captured == event.currentTarget) {
+                            // Trigger click on press release if release point is close enough
+                            // to around the top half of button, or if it's anywhere below
+                            // button. I.e.: accept a "lazy-press" as a real press.
+                            
+                            var box = captured.getBoundingClientRect();
+                            var insideButton = false;
+                            if ((box.left <= handPointer.x) && (handPointer.x <= box.right) &&
+                                (box.top <= handPointer.y) && (handPointer.y <= box.bottom)) {
+                                insideButton = true;
+                            }
+                            
+                            // Map hand pointer to a coordinate space around center of button
+                            // and convert to be relative to window size.
+                            var relativePoint = windowToInteractionZone({
+                                "x": handPointer.x - ((box.left + box.right) / 2),
+                                "y": handPointer.y - ((box.top + box.bottom) / 2)
+                            });
+                            var isWithinLazyReleaseArea = true;
+                            if (relativePoint.y < LAZYRELEASE_YCUTOFF) {
+                                isWithinLazyReleaseArea =
+                                    Math.sqrt((relativePoint.x * relativePoint.x) + (relativePoint.y * relativePoint.y)) < LAZYRELEASE_RADIUS;
+                            }
+                            
+                            if (insideButton || isWithinLazyReleaseArea) {
+                                $(element).click();
+                            }
+                            
+                            event.stopPropagation();
+                        }
+                    }
+                },
+                handPointerLostCapture: function (event) {
+                    var handPointer = event.originalEvent.detail.handPointer;
+                    if (capturedHandPointer == handPointer) {
+                        capturedHandPointer = null;
+                        pressedElement = null;
+                        $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
+                        event.stopPropagation();
+                    }
+                },
+                handPointerEnter: function (event) {
+                    var target = event.currentTarget;
+                    if (KinectUI.getIsPrimaryHandPointerOver(target)) {
+                        $(element).addClass(uiController.BUTTON_HOVER_CLASS);
+                    }
+                },
+                handPointerLeave: function (event) {
+                    var target = event.currentTarget;
+                    if (!KinectUI.getIsPrimaryHandPointerOver(target)) {
+                        $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
+                    }
+                },
+                // respond to mouse events in a similar way to hand pointer events
+                // Note: We use button-hover class rather than :hover pseudo-class because
+                //       :hover does not play well with our button-pressed class.
+                mouseenter: function(event) {
+                    isMouseOver = true;
+                    $(element).addClass(uiController.BUTTON_HOVER_CLASS);
+                    if (pressedElement != null) {
+                        $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
+                    }
+                },
+                mouseleave: function (event) {
+                    isMouseOver = false;
+                    if (!isMouseCaptured || (pressedElement != null)) {
+                        $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
+                        pressedElement = null;
+                    }
+                    $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
+                },
+                mousedown: function (event) {
+                    if (event.button == 0) {
+                        pressedElement = event.target;
+                        if (setCapture(event.target)) {
+                            isMouseCaptured = true;
+                            
+                            // Different browsers have slightly different behaviors related to which element
+                            // will be considered the event target, and how mouseenter and mouseleave events
+                            // are sent in case of capture, so cover all bases.
+                            if (event.target != event.currentTarget) {
+                                removeCaptureEvents = true;
+                                $(event.target).mousemove(function (moveEvent) {
+                                    var box = element.getBoundingClientRect();
+                                    var insideBox = (box.left <= moveEvent.clientX) && (moveEvent.clientX <= box.right) &&
+                                        (box.top <= moveEvent.clientY) && (moveEvent.clientY <= box.bottom);
+                                    if (!insideBox && isMouseCaptured) {
+                                        releaseButtonCapture(moveEvent);
+                                    }
+                                });
+                            }
+                        }
+                        $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
+
+                        event.stopPropagation();
+                    }
+                },
+                mouseup: function (event) {
+                    if (event.button == 0) {
+                        var fakeClick = false;
+                        if ((pressedElement != null) && !isMouseCaptured && (pressedElement != event.target)) {
+                            // If this browser doesn't support capture and the event target
+                            // during mouse up does not match exactly the event target during
+                            // mouse down then browser will not automatically trigger a click
+                            // event, so we will trigger one ourselves because we know that the
+                            // same logical Kinect button received a mouse down and mouse up.
+                            //
+                            // This condition can happen because of shrink and expand
+                            // animations for Kinect button hover and press.
+                            fakeClick = true;
+                        }
+                        
+                        pressedElement = null;
+                        if (isMouseCaptured) {
+                            releaseButtonCapture(event);
+                        }
+                        $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
+                        if (!isMouseOver) {
+                            $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
+                        }
+                        
+                        if (fakeClick) {
+                            $(element).click();
+                        }
+
+                        event.stopPropagation();
+                    }
+                }
+            });
+        }
+        
+        //////////////////////////////////////////////////////////////
+        // Public KinectUIAdapter properties
+        this.handPointers = [];
+        
+        //////////////////////////////////////////////////////////////
+        // Public KinectUIAdapter methods
+
+        // Function called back when a sensor stream frame is ready to be processed.
+        //     .streamFrameHandler(streamFrame)
+        //
+        // streamFrame: stream frame ready to be processed
+        //
+        // Remarks
+        // Processes interaction frames and ignores all other kinds of stream frames.
+        this.streamFrameHandler = function (streamFrame) {
+            if (typeof streamFrame != "object") {
+                throw new Error("Frame must be an object");
+            }
+            
+            if (streamFrame == null) {
+                // Ignore null frames
+                return;
+            }
+
+            var streamName = streamFrame.stream;
+            switch (streamName) {
+                case "interaction":
+                    beginInteractionFrame();
+
+                    if (streamFrame.handPointers != null) {
+                        handleHandPointerData(streamFrame.handPointers);
+                    }
+
+                    endInteractionFrame();
+
+                    updatePublicHandPointers();
+                    break;
+                    
+                default:
+                    if (bindableStreamNames[streamName]) {
+                        // If this is one of the bindable stream names
+                        imageWorkerManager.processImageData(streamName, streamFrame.buffer, streamFrame.width, streamFrame.height);
+                    }
+                    break;
+            }
+        };
+        
+        // Resets the hand pointer array to its initial state (zero hand pointers tracked).
+        //     .clearHandPointers()
+        this.clearHandPointers = function () {
+            if (!isInInteractionFrame) {
+                // If we're not already processing an interaction frame, we fake
+                // an empty frame so that all hand pointers get cleared out.
+                beginInteractionFrame();
+                endInteractionFrame();
+                updatePublicHandPointers();
+            } else {
+                // If we're in the middle of processing an interaction frame, we
+                // can't modify all of our data structures immediately, but need to
+                // remember to do so.
+                isClearRequestPending = true;
+            }
+        };
+        
+        // Function called back when a sensor's interaction stream needs interaction
+        // information in order to adjust a hand pointer position relative to UI.
+        //     .hitTestHandler(trackingId, handType, xPos, yPos)
+        //
+        // trackingId: The skeleton tracking ID for which interaction information is
+        //             being retrieved.
+        // handType: "left" or "right" to represent hand type for which interaction
+        //           information is being retrieved.
+        // xPos: X-coordinate of UI location for which interaction information is being
+        //       retrieved. 0.0 corresponds to left edge of interaction region and 1.0
+        //       corresponds to right edge of interaction region.
+        // yPos: Y-coordinate of UI location for which interaction information is being
+        //       retrieved. 0.0 corresponds to top edge of interaction region and 1.0
+        //       corresponds to bottom edge of interaction region.
+        this.hitTestHandler = function (trackingId, handType, xPos, yPos) {
+            if (typeof trackingId != "number") {
+                throw new Error("First parameter must be a number");
+            }
+            if (typeof handType != "string") {
+                throw new Error("Second parameter must be a string");
+            }
+
+            var interactionInfo = { "isPressTarget": false, "isGripTarget": false };
+
+            // First scale coordinates to be relative to full browser window
+            var windowPos = interactionZoneToWindow({ "x": xPos, "y": yPos });
+            var handPointerId = getHandPointerKey(trackingId, handType);
+            var capturedElement = internalHandPointerData.hasOwnProperty(handPointerId) ? internalHandPointerData[handPointerId].handPointer.captured : null;
+            var targetElement = (capturedElement != null) ? capturedElement : hitTest(windowPos.x, windowPos.y);
+
+            if (targetElement != null) {
+                // Walk up the tree and try to find a grip target and/or a press target
+                for (var searchElement = targetElement;
+                                searchElement != null && searchElement != document &&
+                                (!interactionInfo.isGripTarget || !interactionInfo.isPressTarget) ;
+                                searchElement = searchElement.parentNode) {
+
+                    if (!interactionInfo.isPressTarget) {
+                        if (uiController.getIsPressTarget(searchElement)) {
+                            interactionInfo.isPressTarget = true;
+                            interactionInfo.pressTargetControlId = getElementId(searchElement);
+
+                            // Get the press target point in client coordinates relative to search element
+                            var pressTargetPoint = interactionZoneToElement(getPressTargetPoint(searchElement), searchElement);
+                            var boundingRect = searchElement.getBoundingClientRect();
+
+                            // Now adjust press target point coordinate to be relative to viewport
+                            pressTargetPoint.x += boundingRect.left;
+                            pressTargetPoint.y += boundingRect.top;
+
+                            // Now scale press attraction point back to interaction zone coordinates
+                            var adjustedPoint = windowToInteractionZone(pressTargetPoint);
+                            interactionInfo.pressAttractionPointX = adjustedPoint.x;
+                            interactionInfo.pressAttractionPointY = adjustedPoint.y;
+                        }
+                    }
+
+                    if (!interactionInfo.isGripTarget) {
+                        if (uiController.getIsGripTarget(searchElement)) {
+                            interactionInfo.isGripTarget = true;
+                        }
+                    }
+                }
+            }
+            
+            return interactionInfo;
+        };
+        
+        // Adds an element that should be excluded from hit testing (both for press adjustment
+        // and for UI message routing).
+        //     .addHitTestExclusion( element )
+        //
+        // element: Element to exlude from hit testing.
+        this.addHitTestExclusion = function (element) {
+            if (element == null) {
+                console.log('Tried to add a hit test exclusion for a null element.');
+                return;
+            }
+            
+            excludedElements.push(element);
+        };
+
+        // Stops excluding an element from hit testing (both for press adjustment and for UI
+        // message routing).
+        //     .removeHitTestExclusion( element )
+        //
+        // element: Element to stop exluding from hit testing.
+        this.removeHitTestExclusion = function (element) {
+            if (element == null) {
+                console.log('Tried to remove a hit test exclusion for a null element.');
+                return;
+            }
+            
+            var index = excludedElements.indexOf(element);
+            if (index >= 0) {
+                excludedElements.splice(index, 1);
+            }
+        };
+
+        // Bind the specified canvas element with the specified image stream
+        //     .bindStreamToCanvas( streamName, canvas )
+        //
+        // streamName: name of stream to bind to canvas element. Must be one of the supported
+        //             image stream names (e.g.: KinectUI.USERVIEWER_STREAM_NAME and
+        //             KinectUI.BACKGROUNDREMOVAL_STREAM_NAME)
+        // canvas: Canvas to bind to user viewer stream
+        //
+        // Remarks
+        // After binding a stream to a canvas, image data for that stream will
+        // be rendered into the canvas whenever a new stream frame arrives.
+        this.bindStreamToCanvas = function (streamName, canvas, width, height) {
+            if (!bindableStreamNames[streamName]) {
+                throw new Error("first parameter must be specified and must be one of the supported stream names");
+            }
+
+            if (!(canvas instanceof HTMLCanvasElement)) {
+                throw new Error("second parameter must be specified and must be a canvas element");
+            }
+
+            this.unbindStreamFromCanvas(streamName);
+
+            imageWorkerManager.setImageData(streamName, canvas);
+        };
+
+        // Unbind the specified image stream from previously bound canvas element, if any.
+        //     .unbindStreamFromCanvas(streamName)
+        //
+        // streamName: name of stream to unbind from its corresponding canvas element
+        this.unbindStreamFromCanvas = function(streamName) {
+            imageWorkerManager.setImageData(streamName, null);
+        };
+        
+        // Attach a new handler for a specified global UI event.
+        //     .on( eventName, handler(eventData) )] )
+        //
+        // eventName: Name of global event for which handler is being attached.
+        // handler: Callback function to be executed when a global UI event of the specified
+        //          name occurs. E.g.: when set of hand pointers has been updated (eventName =
+        //          "handPointersUpdated").
+        this.on = function (eventName, handler) {
+            if (typeof (eventName) != "string") {
+                throw new Error("first parameter must be specified and must be a string");
+            }
+            if (typeof (handler) != "function") {
+                throw new Error("second parameter must be specified and must be a function");
+            }
+
+            var handlerArray;
+            if (eventHandlers.hasOwnProperty(eventName)) {
+                handlerArray = eventHandlers[eventName];
+            } else {
+                handlerArray = [];
+                eventHandlers[eventName] = handlerArray;
+            }
+            
+            handlerArray.push(handler);
+        };
+
+        // Removes one (or all) global UI event handler(s) for specified global UI event.
+        //     .removeEventHandler( eventName, [handler(eventData)] )] )
+        //
+        // eventName: Name of global event for which handler is being removed.
+        // handler: Global UI event handler callback function to be removed.
+        //          If omitted, all event handlers for specified event are removed.
+        this.off = function (eventName, handler) {
+            if (typeof (handler) != "string") {
+                throw new Error("first parameter must be specified and must be a string");
+            }
+
+            var removeAll = false;
+            switch (typeof (handler)) {
+                case "undefined":
+                    removeAll = true;
+                    break;
+                case "function":
+                    break;
+
+                default:
+                    throw new Error("second parameter must either be a function or left unspecified");
+            }
+            
+            if (!eventHandlers.hasOwnProperty(eventName)) {
+                // If there are no event handlers associated with event, just return
+                return;
+            }
+
+            if (removeAll) {
+                eventHandlers[eventName] = [];
+            } else {
+                var index = eventHandlers[eventName].indexOf(handler);
+                if (index >= 0) {
+                    eventHandlers[eventName].slice(index, index + 1);
+                }
+            }
+        };
+        
+        // Configures the specified DOM element array to act as a Kinect UI button
+        //     .promoteButtons([elements])
+        //
+        // elements: DOM element array or HTMLCollection where each element will be configured
+        //           to act as a Kinect UI button.
+        //           If left unspecified, all document elements with class equal to
+        //           KinectUI.BUTTON_CLASS will be promoted to buttons.
+        this.promoteButtons = function (elements) {
+            var type = typeof elements;
+            var error = new Error("first parameter must be left unspecified or be an array equivalent");
+            switch (type) {
+                case "undefined":
+                    elements = document.getElementsByClassName(uiController.BUTTON_CLASS);
+                    break;
+                case "object":
+                    if (elements == null) {
+                        // If null, treat as if an empty array was specified
+                        return;
+                    }
+                    
+                    if (!("length" in elements)) {
+                        throw error;
+                    }
+                    break;
+                    
+                default:
+                    throw error;
+            }
+
+            for (var i = 0; i < elements.length; ++i) {
+                var element = elements[i];
+                if ((typeof element != "object") || (element.nodeType != Node.ELEMENT_NODE)) {
+                    throw new Error("each array element must be a DOM element");
+                }
+
+                promoteToButton(elements[i]);
+            }
+        };
+
+        // Creates the default cursor UI elements with associated behavior
+        //     .createDefaultCursor()
+        //
+        // Returns an object with the following properties/functions:
+        // - element: property referencing the DOM element that represents the cursor
+        // - hide(): function used to hide the cursor
+        // - show(): function used to show the cursor
+        this.createDefaultCursor = function() {
+            // First check if cursor element has already been created
+            var cursorElement = document.getElementById(uiController.CURSOR_ID);
+            if (cursorElement != null) {
+                return jQuery.data(cursorElement, uiController.CURSOR_ID);
+            }
+            
+            var showCursor = true;
+            cursorElement = document.createElement("div");
+            cursorElement.id = uiController.CURSOR_ID;
+            cursorElement.innerHTML = '\
+                <svg viewBox="0 0 250 250">\
+                    <defs>\
+                        <radialGradient id="kinect-pressing-gradient" r="150%">\
+                            <stop stop-color="#663085" offset="0.0%"></stop>\
+                            <stop stop-color="white" offset="100.0%" ></stop>\
+                        </radialGradient>\
+                        <radialGradient id="kinect-extended-gradient">\
+                            <stop stop-color="#01b3ff" offset="0.0%"></stop>\
+                            <stop stop-color="#04e5ff" offset="98.1%" ></stop>\
+                            <stop stop-color="#04e5ff" offset="99.2%" ></stop>\
+                            <stop stop-color="#04e5ff" offset="100.0%" ></stop>\
+                        </radialGradient>\
+                        <path id="kinect-open-hand-base" stroke-width="2.66667px" stroke-miterlimit="2.75" d="M 184.325,52.329 C 177.166,51.961 174.195,56.492 173.858,59.838 C 173.858,59.838 170.148,117.100 170.148,118.229 C 170.148,121.405 166.840,124.394 163.662,124.394 C 160.488,124.394 157.912,121.819 157.912,118.644 C 157.912,117.843 160.558,26.069 160.558,26.069 C 160.746,21.648 156.897,14.964 148.905,14.845 C 140.912,14.727 137.941,20.814 137.759,25.106 C 137.759,25.106 136.400,107.066 136.400,108.288 C 136.400,111.463 133.825,114.038 130.650,114.038 C 127.474,114.038 124.900,111.463 124.900,108.288 C 124.900,107.743 120.237,15.217 119.553,13.920 C 118.498,10.296 113.940,7.103 108.746,7.227 C 102.840,7.368 98.306,11.584 98.410,15.973 C 98.412,16.051 98.427,16.125 98.431,16.202 C 98.162,17.142 102.662,111.920 102.662,112.943 C 102.662,116.118 100.087,118.693 96.912,118.693 C 93.735,118.693 89.829,116.118 89.829,112.943 C 89.829,112.611 78.418,41.710 77.470,40.035 C 77.142,38.973 75.392,30.547 65.819,31.637 C 56.246,32.727 56.261,43.062 56.588,44.996 L 71.393,145.191 C 72.726,150.746 73.426,157.547 67.168,159.684 C 66.531,159.901 46.958,133.145 46.958,133.145 C 25.587,108.413 11.363,121.842 11.363,121.842 C 5.045,128.159 9.566,133.019 12.931,137.088 C 12.931,137.088 55.426,192.383 68.087,207.555 C 80.746,222.727 102.327,240.773 125.829,240.773 C 158.920,240.773 182.465,216.875 185.017,180.696 C 187.571,144.516 193.480,62.303 193.480,62.303 C 193.712,60.008 191.484,52.696 184.325,52.329 Z"></path>\
+                        <clipPath id="kinect-cursor-press-clip">\
+                            <circle id="kinect-cursor-press-clip-circle" cx="125.0" cy="250.0" r="0.0"></circle>\
+                        </clipPath>\
+                    </defs>\
+                    <path id="kinect-cursor-glow" fill="none" stroke="#7c6dcf" stroke-width="16" stroke-miterlimit="2.75" d="M 183.605,52.966 C 176.446,52.598 173.475,57.129 173.138,60.476 C 173.138,60.476 169.428,117.739 169.428,118.866 C 169.428,122.043 166.120,125.032 162.943,125.032 C 159.768,125.032 157.193,122.457 157.193,119.282 C 157.193,118.481 159.838,26.707 159.838,26.707 C 160.026,22.285 156.177,15.602 148.185,15.483 C 140.193,15.365 137.221,21.452 137.039,25.744 C 137.039,25.744 135.680,107.703 135.680,108.926 C 135.680,112.101 133.105,114.676 129.930,114.676 C 126.754,114.676 124.179,112.101 124.179,108.926 C 124.179,108.382 119.517,15.854 118.833,14.558 C 117.779,10.934 113.220,7.741 108.026,7.865 C 102.120,8.005 97.586,12.222 97.690,16.611 C 97.693,16.689 97.707,16.763 97.711,16.840 C 97.443,17.780 101.943,112.558 101.943,113.580 C 101.943,116.757 99.367,119.330 96.193,119.330 C 93.016,119.330 89.109,116.757 89.109,113.580 C 89.109,113.249 77.698,42.348 76.750,40.673 C 76.422,39.611 74.672,31.185 65.099,32.275 C 55.526,33.365 55.542,43.700 55.868,45.634 L 70.673,145.828 C 72.006,151.384 72.706,158.185 66.448,160.322 C 65.811,160.539 46.238,133.783 46.238,133.783 C 24.867,109.051 10.643,122.479 10.643,122.479 C 4.325,128.796 8.846,133.658 12.211,137.726 C 12.211,137.726 54.706,193.021 67.367,208.193 C 80.026,223.365 101.607,241.410 125.109,241.410 C 158.200,241.410 181.745,217.513 184.297,181.334 C 186.851,145.154 192.760,62.940 192.760,62.940 C 192.992,60.645 190.764,53.335 183.605,52.966 Z "></path>\
+                    <use id="kinect-cursor-normal" xlink:href="#kinect-open-hand-base" fill="white" stroke="black"></use>\
+                    <g id="kinect-cursor-progress" clip-path="url(#kinect-cursor-press-clip)">\
+                        <use xlink:href="#kinect-open-hand-base" fill="url(#kinect-pressing-gradient)" stroke="none"></use>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,213.384 C 112.266,213.384 102.021,221.573 98.898,232.737 C 100.149,233.406 101.418,234.039 102.704,234.633 C 103.681,230.576 105.768,226.953 108.627,224.091 C 112.678,220.043 118.251,217.547 124.427,217.545 C 130.605,217.547 136.180,220.043 140.229,224.091 C 143.494,227.359 145.749,231.617 146.500,236.382 C 147.841,235.885 149.154,235.336 150.435,234.742 C 148.039,222.567 137.308,213.384 124.427,213.384"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,198.974 C 107.230,198.976 92.510,209.587 86.458,224.618 C 87.591,225.494 88.748,226.351 89.928,227.182 C 91.784,222.146 94.714,217.629 98.437,213.902 C 105.097,207.247 114.272,203.137 124.427,203.136 C 134.584,203.137 143.759,207.247 150.417,213.902 C 154.657,218.144 157.862,223.407 159.632,229.293 C 160.827,228.411 161.981,227.481 163.102,226.501 C 157.556,210.480 142.338,198.976 124.427,198.974"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,184.565 C 102.982,184.566 84.386,196.770 75.204,214.613 C 76.212,215.635 77.255,216.656 78.331,217.667 C 80.843,212.467 84.209,207.756 88.252,203.713 C 97.516,194.451 110.293,188.728 124.427,188.726 C 138.563,188.728 151.341,194.451 160.607,203.713 C 164.788,207.896 168.248,212.796 170.785,218.212 C 171.735,216.955 172.636,215.651 173.492,214.300 C 164.254,196.626 145.751,184.566 124.427,184.565"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,170.155 C 99.400,170.155 77.453,183.343 65.150,203.145 C 66.181,204.417 67.117,205.563 67.944,206.563 C 70.765,201.798 74.169,197.419 78.062,193.523 C 89.934,181.653 106.315,174.317 124.427,174.317 C 142.542,174.317 158.922,181.653 170.795,193.523 C 173.825,196.555 176.559,199.879 178.951,203.450 C 179.564,201.877 180.126,200.265 180.635,198.609 C 167.941,181.352 147.493,170.155 124.427,170.155"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,155.746 C 96.053,155.747 70.960,169.793 55.719,191.310 C 56.641,192.479 57.540,193.618 58.415,194.721 C 61.215,190.636 64.383,186.827 67.874,183.335 C 82.353,168.858 102.336,159.909 124.427,159.908 C 146.520,159.909 166.502,168.858 180.983,183.335 C 181.800,184.152 182.599,184.986 183.380,185.838 C 183.594,184.171 183.765,182.476 183.887,180.752 C 183.895,180.627 183.904,180.506 183.914,180.381 C 168.687,165.159 147.658,155.747 124.427,155.746"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,141.337 C 105.045,141.338 86.970,146.934 71.728,156.599 C 70.962,158.106 69.668,159.318 67.603,160.021 C 67.549,160.021 67.405,159.895 67.185,159.658 C 59.361,165.252 52.385,171.964 46.499,179.558 C 47.386,180.694 48.270,181.825 49.147,182.943 C 51.768,179.485 54.621,176.210 57.686,173.146 C 74.774,156.061 98.359,145.500 124.427,145.499 C 147.411,145.500 168.463,153.709 184.835,167.359 C 184.953,165.715 185.072,164.022 185.195,162.298 C 168.453,149.168 147.353,141.338 124.427,141.337"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 58.345,148.266 C 50.558,153.893 43.518,160.486 37.399,167.873 C 38.266,168.990 39.140,170.114 40.016,171.242 C 42.368,168.354 44.865,165.589 47.497,162.957 C 51.632,158.824 56.096,155.021 60.846,151.594 C 60.036,150.521 59.194,149.401 58.345,148.266 M 124.427,126.928 C 105.097,126.928 86.896,131.786 70.987,140.345 L 71.635,144.735 C 87.268,136.041 105.265,131.091 124.427,131.090 C 147.312,131.091 168.535,138.151 186.056,150.215 C 186.168,148.633 186.282,147.040 186.397,145.429 C 168.603,133.731 147.312,126.928 124.427,126.928"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 49.764,136.688 C 41.913,142.379 34.739,148.942 28.380,156.237 C 29.224,157.328 30.094,158.452 30.979,159.594 C 33.003,157.241 35.116,154.962 37.309,152.768 C 41.936,148.143 46.925,143.885 52.234,140.040 C 51.303,138.780 50.463,137.639 49.764,136.688 M 168.998,120.536 C 168.581,121.903 167.600,123.146 166.369,124.003 C 173.664,126.645 180.643,129.956 187.224,133.863 C 187.333,132.322 187.443,130.781 187.555,129.239 C 181.648,125.862 175.448,122.946 168.998,120.536 M 124.427,112.518 C 116.992,112.518 109.701,113.156 102.614,114.379 C 102.373,117.292 99.933,119.577 96.962,119.577 C 94.928,119.577 92.586,118.507 91.196,116.898 C 83.399,119.001 75.894,121.826 68.763,125.296 L 69.403,129.622 C 85.962,121.340 104.648,116.681 124.427,116.680 C 135.839,116.680 146.888,118.231 157.374,121.134 C 157.224,120.626 157.144,120.087 157.144,119.530 C 157.144,119.451 157.171,118.473 157.216,116.779 C 149.919,114.840 142.373,113.535 134.623,112.921 C 133.582,114.180 132.008,114.982 130.247,114.982 C 128.305,114.982 126.591,114.009 125.569,112.524 C 125.188,112.520 124.810,112.518 124.427,112.518"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 39.819,126.114 C 32.433,131.616 25.597,137.825 19.419,144.632 C 20.211,145.659 21.073,146.779 22.001,147.981 C 23.659,146.135 25.366,144.333 27.121,142.579 C 32.061,137.639 37.375,133.073 43.014,128.926 C 41.928,127.893 40.860,126.956 39.819,126.114 M 170.043,105.607 C 169.946,107.133 169.856,108.576 169.773,109.915 C 176.197,112.156 182.408,114.863 188.360,117.991 C 188.469,116.474 188.576,114.969 188.682,113.473 C 182.702,110.426 176.478,107.793 170.043,105.607 M 88.200,102.780 C 80.728,104.749 73.501,107.312 66.564,110.417 L 67.197,114.699 C 74.130,111.524 81.370,108.909 88.865,106.909 C 88.665,105.662 88.441,104.276 88.200,102.780 M 136.090,98.581 C 136.065,100.105 136.043,101.504 136.022,102.752 C 143.367,103.365 150.542,104.553 157.514,106.273 C 157.552,104.928 157.593,103.506 157.636,102.019 C 150.641,100.340 143.443,99.180 136.090,98.581 M 124.014,98.109 C 116.527,98.131 109.170,98.733 101.998,99.873 C 102.066,101.364 102.132,102.760 102.192,104.057 C 109.366,102.891 116.723,102.281 124.225,102.270 C 124.162,101.018 124.092,99.624 124.014,98.109"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 25.247,119.229 C 20.067,123.493 15.168,128.082 10.577,132.969 C 11.247,134.191 12.138,135.342 13.033,136.436 C 14.308,135.063 15.607,133.716 16.932,132.390 C 21.157,128.166 25.626,124.191 30.320,120.486 C 28.510,119.813 26.815,119.418 25.247,119.229 M 170.989,90.758 C 170.899,92.191 170.806,93.620 170.716,95.037 C 177.170,97.096 183.429,99.574 189.475,102.441 C 189.581,100.935 189.688,99.449 189.793,97.995 C 183.724,95.194 177.450,92.774 170.989,90.758 M 85.878,88.493 C 78.490,90.370 71.310,92.774 64.383,95.660 L 65.013,99.910 C 71.944,96.964 79.138,94.518 86.550,92.617 C 86.332,91.274 86.106,89.893 85.878,88.493 M 136.324,84.145 C 136.302,85.568 136.279,86.961 136.257,88.314 C 143.644,88.883 150.880,89.979 157.934,91.565 C 157.973,90.176 158.014,88.763 158.056,87.329 C 150.976,85.776 143.727,84.703 136.324,84.145 M 123.275,83.703 C 115.823,83.758 108.499,84.332 101.330,85.396 C 101.396,86.819 101.459,88.213 101.523,89.575 C 108.691,88.492 116.029,87.909 123.491,87.864 C 123.421,86.512 123.349,85.128 123.275,83.703"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 171.940,75.994 C 171.852,77.362 171.759,78.786 171.667,80.251 C 178.143,82.164 184.452,84.460 190.573,87.115 C 190.682,85.596 190.790,84.128 190.889,82.722 C 184.746,80.121 178.425,77.871 171.940,75.994 M 83.526,74.225 C 76.227,76.022 69.113,78.287 62.217,80.990 L 62.842,85.218 C 69.746,82.467 76.883,80.161 84.209,78.344 C 83.983,76.969 83.754,75.594 83.526,74.225 M 136.561,69.714 C 136.538,71.106 136.516,72.501 136.493,73.883 C 143.926,74.414 151.223,75.436 158.354,76.912 C 158.395,75.505 158.434,74.097 158.475,72.688 C 151.318,71.238 144.014,70.238 136.561,69.714 M 122.520,69.300 C 115.118,69.381 107.814,69.936 100.665,70.932 C 100.729,72.330 100.793,73.726 100.857,75.108 C 108.012,74.093 115.322,73.534 122.740,73.460 C 122.668,72.083 122.594,70.697 122.520,69.300"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 172.901,61.296 C 172.890,61.366 172.882,61.436 172.876,61.506 C 172.876,61.506 172.779,63.011 172.615,65.532 C 179.120,67.326 185.475,69.474 191.663,71.954 C 191.785,70.258 191.889,68.793 191.976,67.601 C 185.773,65.167 179.407,63.058 172.901,61.296 M 81.131,59.975 C 73.933,61.701 66.901,63.847 60.058,66.388 L 60.680,70.598 C 67.541,68.013 74.599,65.833 81.829,64.088 C 81.592,62.680 81.358,61.305 81.131,59.975 M 136.799,55.287 C 136.777,56.658 136.755,58.045 136.732,59.453 C 144.212,59.956 151.571,60.913 158.774,62.299 C 158.814,60.876 158.856,59.470 158.895,58.086 C 151.670,56.724 144.294,55.782 136.799,55.287 M 121.748,54.899 C 114.383,55.003 107.139,55.537 100.007,56.478 C 100.069,57.858 100.133,59.252 100.196,60.651 C 107.331,59.695 114.599,59.156 121.973,59.058 C 121.899,57.666 121.823,56.276 121.748,54.899"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 78.613,45.759 C 71.559,47.418 64.653,49.450 57.909,51.836 L 58.528,56.033 C 65.309,53.602 72.264,51.536 79.368,49.857 C 79.086,48.290 78.833,46.911 78.613,45.759 M 137.038,40.863 C 137.016,42.184 136.993,43.580 136.970,45.028 C 144.504,45.506 151.917,46.410 159.193,47.718 C 159.237,46.259 159.276,44.854 159.314,43.512 C 152.015,42.224 144.588,41.334 137.038,40.863 M 120.948,40.500 C 113.651,40.624 106.448,41.142 99.363,42.031 C 99.421,43.387 99.482,44.782 99.546,46.203 C 106.646,45.300 113.867,44.778 121.183,44.659 C 121.104,43.243 121.024,41.856 120.948,40.500"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 67.994,33.588 C 65.759,34.198 63.541,34.842 61.336,35.523 C 59.365,36.980 58.275,39.064 57.693,41.068 C 63.251,39.203 68.915,37.564 74.669,36.163 C 73.215,34.735 71.092,33.607 67.994,33.588 M 137.328,26.443 C 137.298,26.715 137.275,26.981 137.264,27.241 C 137.264,27.241 137.244,28.463 137.209,30.606 C 144.794,31.062 152.266,31.921 159.613,33.163 C 159.668,31.221 159.712,29.780 159.734,28.963 C 152.384,27.741 144.913,26.895 137.328,26.443 M 120.090,26.104 C 112.882,26.247 105.763,26.747 98.746,27.587 C 98.799,28.872 98.857,30.267 98.919,31.758 C 105.961,30.905 113.110,30.400 120.347,30.262 C 120.259,28.784 120.173,27.393 120.090,26.104"></path>\
+                        <use xlink:href="#kinect-open-hand-base" fill="none" stroke="black"></use>\
+                    </g>\
+                    <use id="kinect-cursor-extended" xlink:href="#kinect-open-hand-base" fill="url(#kinect-extended-gradient)" stroke="black"></use>\
+                </svg>';
+            document.body.appendChild(cursorElement);
+
+            var cursorData = {
+                "element": cursorElement,
+                "show": function () {
+                    // Cursor will be shown next time we get a valid hand pointer
+                    showCursor = true;
+                },
+                "hide": function () {
+                    // Hide cursor immediately
+                    showCursor = false;
+                    $(cursorElement).hide();
+                }
+            };
+            jQuery.data(cursorElement, uiController.CURSOR_ID, cursorData);
+            
+            var wasHovering = false;
+            var wasPressing = false;
+            uiAdapter.addHitTestExclusion(cursorElement);
+
+            uiAdapter.on(uiController.HANDPOINTERS_UPDATED, function(handPointers) {
+                var MAXIMUM_CURSOR_SCALE = 1.0;
+                var MINIMUM_CURSOR_SCALE = 0.8;
+                var MINIMUM_PROGRESS_CLIP_RADIUS = 8.0;
+                var RANGE_PROGRESS_CLIP_RADIUS = 240.0;
+
+                // If cursor is not supposed to be showing, bail out immediately
+                if (!showCursor) {
+                    return;
+                }
+
+                var handPointer = null;
+
+                for (var iPointer = 0; iPointer < handPointers.length; ++iPointer) {
+                    var curPointer = handPointers[iPointer];
+
+                    if (curPointer.getIsPrimaryHandOfPrimaryUser()) {
+                        handPointer = curPointer;
+                        break;
+                    }
+                }
+
+                if (cursorElement == null) {
+                    return;
+                }
+
+                function clamp(value, min, max) {
+                    return Math.max(min, Math.min(max, value));
+                }
+
+                if (handPointer != null) {
+                    var isOpen = !handPointer.isInGripInteraction;
+
+                    // Get information about what this hand pointer is over
+                    var isHovering = false;
+                    var isOverPressTarget = false;
+                    var enteredElements = handPointer.getEnteredElements();
+                    for (var iEnteredElement = 0; iEnteredElement < enteredElements.length; ++iEnteredElement) {
+                        var enteredElement = enteredElements[iEnteredElement];
+                        if (KinectUI.getIsPressTarget(enteredElement)) {
+                            isHovering = true;
+                            isOverPressTarget = true;
+                            break;
+                        }
+
+                        if (KinectUI.getIsGripTarget(enteredElement)) {
+                            isHovering = true;
+                        }
+                    }
+
+                    var isPressing = isOverPressTarget && handPointer.isPressed && !handPointer.isInGripInteraction;
+
+                    if (isHovering != wasHovering) {
+                        if (isHovering) {
+                            $(cursorElement).addClass("cursor-hover");
+                        } else {
+                            $(cursorElement).removeClass("cursor-hover");
+                        }
+                        wasHovering = isHovering;
+                    }
+
+                    if (isPressing != wasPressing) {
+                        if (isPressing) {
+                            $(cursorElement).addClass("cursor-pressed");
+                        } else {
+                            $(cursorElement).removeClass("cursor-pressed");
+                        }
+                        wasPressing = isPressing;
+                    }
+
+                    var artworkWidth = $(cursorElement).width();
+                    var artworkHeight = $(cursorElement).height();
+                    var adjustedPressExtent = isOverPressTarget ? handPointer.pressExtent : 0.0;
+
+                    document.getElementById("kinect-cursor-press-clip-circle").r.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, MINIMUM_PROGRESS_CLIP_RADIUS + (RANGE_PROGRESS_CLIP_RADIUS * adjustedPressExtent));
+
+                    var scale = (1.0 - (adjustedPressExtent * ((MAXIMUM_CURSOR_SCALE - MINIMUM_CURSOR_SCALE) / 2.0)));
+                    var xScale = (handPointer.handType == "Left") ? -scale : scale;
+                    var yScale = scale;
+                    $(cursorElement).css("transform", "scale(" + xScale + "," + yScale + ")");
+
+                    var xPos = clamp(handPointer.x, 0, window.innerWidth);
+                    var yPos = clamp(handPointer.y, 0, window.innerHeight);
+                    var isClamped = (xPos != handPointer.x) || (yPos != handPointer.y);
+                    var xDelta = (artworkWidth / 2) * scale;
+                    var yDelta = (artworkHeight / 2) * scale;
+                    var opacity = !isClamped ? 1.0 : 0.3;
+
+                    $(cursorElement).show();
+                    $(cursorElement).offset({ left: xPos - xDelta, top: yPos - yDelta });
+                    $(cursorElement).css("opacity", opacity);
+                } else {
+                    $(cursorElement).hide();
+                }
+            });
+
+            return cursorData;
+        };
+
+        // Enable triggering of UI events as interaction frames are received.
+        //     .enableEvents()
+        //
+        // Remarks
+        // This and .disableEvents() function serve as a global on/off switch
+        // so that clients don't have to be registering and unregistering all
+        // event handlers as they enter/exit a state where they don't want or
+        // need to process Kinect UI events.
+        // Events are enabled by default.
+        this.enableEvents = function() {
+            handPointerEventsEnabled = true;
+        };
+
+        // Disable triggering of UI events as interaction frames are received.
+        //     .disableEvents()
+        //
+        // Remarks
+        // This and .enableEvents() function serve as a global on/off switch
+        // so that clients don't have to be registering and unregistering all
+        // event handlers as they enter/exit a state where they don't want or
+        // need to process Kinect UI events.
+        // Events are enabled by default.
+        this.disableEvents = function () {
+            handPointerEventsEnabled = false;
+        };
+    }
+
+    //////////////////////////////////////////////////////////////
+    // KinectUIController object constructor
+    function KinectUIController() {
+
+        //////////////////////////////////////////////////////////////
+        // Public KinectUIController methods
+
+        // Creates a new Kinect UI adapter object optionally associated with a Kinect sensor.
+        //     .createAdapter( [sensor] )
+        //
+        // sensor: sensor object associated with the new UI adapter
+        this.createAdapter = function (sensor) {
+            if (typeof sensor != 'object') {
+                throw new Error("first parameter must be either a sensor object or left unspecified");
+            }
+
+            var uiAdapter = new KinectUIAdapter();
+
+            if (sensor != null) {
+                sensor.addStreamFrameHandler(uiAdapter.streamFrameHandler);
+                sensor.setHitTestHandler(uiAdapter.hitTestHandler);
+            }
+
+            return uiAdapter;
+        };
+
+        // Determines whether the specified element has been designated as a press target
+        //     .getIsPressTarget( element )
+        //
+        // element: DOM element for which we're querying press target status.
+        this.getIsPressTarget = function (element) {
+            return jQuery.data(element, "isPressTarget") ? true : false;
+        };
+
+        // Specifies whether the specified element should be designated as a press target
+        //     .setIsPressTarget( element, isPressTarget )
+        //
+        // element: DOM element for which we're specifying press target status.
+        // isPressTarget: true if element should be designated as a press target.
+        //                false otherwise.
+        this.setIsPressTarget = function (element, isPressTarget) {
+            jQuery.data(element, "isPressTarget", isPressTarget);
+        };
+
+        // Determines whether the specified element has been designated as a grip target
+        //     .getIsPressTarget( element )
+        //
+        // element: DOM element for which we're querying grip target status.
+        this.getIsGripTarget = function (element) {
+            return jQuery.data(element, "isGripTarget") ? true : false;
+        };
+
+        // Specifies whether the specified element should be designated as a grip target
+        //     .setIsGripTarget( element, isGripTarget )
+        //
+        // element: DOM element for which we're specifying grip target status.
+        // isGripTarget: true if element should be designated as a grip target.
+        //               false otherwise.
+        this.setIsGripTarget = function (element, isGripTarget) {
+            jQuery.data(element, "isGripTarget", isGripTarget);
+        };
+
+        // Determines whether the primary hand pointer is over the specified element
+        //     .getIsPrimaryHandPointerOver( element )
+        //
+        // element: DOM element for which we're querying if primary hand pointer is over it.
+        this.getIsPrimaryHandPointerOver = function (element) {
+            return jQuery.data(element, "isPrimaryHandPointerOver") ? true : false;
+        };
+
+        // Specifies whether the primary hand pointer is over the specified element
+        //     .setIsPrimaryHandPointerOver( element )
+        //
+        // element: DOM element for which we're specifying if primary hand pointer is over it.
+        // over: true if primary hand pointer is over the specified element. false otherwise.
+        this.setIsPrimaryHandPointerOver = function (element, over) {
+            jQuery.data(element, "isPrimaryHandPointerOver", over);
+        };
+
+        //////////////////////////////////////////////////////////////
+        // Public KinectUIController properties
+
+        // Element event names
+        this.HANDPOINTER_MOVE = "handPointerMove";
+        this.HANDPOINTER_ENTER = "handPointerEnter";
+        this.HANDPOINTER_LEAVE = "handPointerLeave";
+        this.HANDPOINTER_PRESS = "handPointerPress";
+        this.HANDPOINTER_PRESSRELEASE = "handPointerPressRelease";
+        this.HANDPOINTER_GRIP = "handPointerGrip";
+        this.HANDPOINTER_GRIPRELEASE = "handPointerGripRelease";
+        this.HANDPOINTER_GOTCAPTURE = "handPointerGotCapture";
+        this.HANDPOINTER_LOSTCAPTURE = "handPointerLostCapture";
+        this.QUERY_INTERACTION_STATUS = "queryInteractionStatus";
+
+        // Global UI event names
+        this.HANDPOINTERS_UPDATED = "handPointersUpdated";
+
+        // Global UI id/class names
+        this.BUTTON_CLASS = "kinect-button";
+        this.BUTTON_TEXT_CLASS = "kinect-button-text";
+        this.BUTTON_HOVER_CLASS = "button-hover";
+        this.BUTTON_PRESSED_CLASS = "button-pressed";
+
+        this.CURSOR_ID = "kinect-cursor";
+    };
+    
+    //////////////////////////////////////////////////////////////
+    // Private global properties common to all KinectUI objects
+    
+    // Get the path common to all script files
+    var scriptRootURIPath = (function () {
+        var rootPath = "";
+
+        try {
+            var scriptFileName = document.scripts[document.scripts.length - 1].src;
+            var pathSeparatorIndex = scriptFileName.lastIndexOf("/");
+
+            if (pathSeparatorIndex >= 0) {
+                // If pathSeparator is found, return path up to and including separator
+                rootPath = scriptFileName.substring(0, pathSeparatorIndex + 1);
+            }
+        } catch(error) {
+        }
+
+        return rootPath;
+    })();
+
+    // The global Kinect UI controller object is a singleton instance of our internal KinectUIController type
+    var uiController = new KinectUIController();
+    
+    // Prefix for element IDs assigned by Kinect UI layer for purposes of hit test detection
+    var kinectIdPrefix = "kinectId";
+
+    // Id in sequence to assign to controls that don't already have an assigned DOM element id
+    var lastAssignedControlId = 0;
+    
+    // return singleton
+    return uiController;
+})();
+
+// Guarantee that Kinect and KinectUI objects are available from the global scope even if
+// this file was loaded on-demand, in a restricted functional scope, a long time after web
+// document was initially loaded.
+window.Kinect = Kinect;
+window.KinectUI = KinectUI;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/KinectWorker-1.8.0.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/KinectWorker-1.8.0.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/KinectWorker-1.8.0.js	(revision 28897)
@@ -0,0 +1,61 @@
+// -----------------------------------------------------------------------
+// <copyright file="KinectWorker-1.8.0.js" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+"use strict";
+
+var imageDataMap = {};
+
+// Associate a specified ImageData (from a canvas object) with the specified image name
+//     .setImageData( imageName [, imageData] )
+//
+// imageName: Name used to refer to canvas ImageData object specified.
+// imageData: Canvas ImageData to associate with image name. If null, any previously
+//            existing association is removed.
+function setImageData(imageName, imageData) {
+    if ((imageData == null) && imageDataMap.hasOwnProperty(imageName)) {
+        delete imageDataMap[imageName];
+    } else {
+        imageDataMap[imageName] = imageData;
+    }
+}
+
+
+// Copy the information in the specified image ArrayBuffer to the ImageData associated
+// with the specified name and post the ImageData back to UI thread.
+//     .processImageData( imageName, imageData )
+//
+// imageName: Name used to refer to canvas ImageData object to receive data from
+//            ArrayBuffer.
+// imageBuffer: ArrayBuffer containing image data to copy to canvas ImageData structure.
+function processImageData(imageName, imageBuffer) {
+    if (!imageDataMap.hasOwnProperty(imageName)) {
+        self.postMessage({ "message": "notProcessed", "imageName": imageName});
+        return;
+    }
+
+    var imageData = imageDataMap[imageName];
+    var pixelArray = imageData.data;
+    var newPixelData = new Uint8Array(imageBuffer);
+
+    for (var i = 0; i < pixelArray.length; ++i) {
+        pixelArray[i] = newPixelData[i];
+    }
+
+    self.postMessage({ "message": "imageReady", "imageName": imageName, "imageData": imageData });
+}
+
+// thread message handler
+addEventListener('message', function (event) {
+    switch (event.data.message) {
+        case "setImageData":
+            setImageData(event.data.imageName, event.data.imageData);
+            break;
+
+        case "processImageData":
+            processImageData(event.data.imageName, event.data.imageBuffer);
+            break;
+    }
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/SamplePage.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/SamplePage.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web.orig/SamplePage.html	(revision 28897)
@@ -0,0 +1,527 @@
+﻿<!DOCTYPE HTML>
+<html>
+<head>
+    <title>Kinect Web Basics</title>
+    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.1.min.js"></script>
+    <script type="text/javascript" src="Kinect-1.8.0.js"></script>
+    <script type="text/javascript">
+        $(document).ready(function () {
+            var streamImageWidth = 640;
+            var streamImageHeight = 480;
+            var streamImageResolution = streamImageWidth.toString() + "x" + streamImageHeight.toString();
+
+            var isSensorConnected = false;
+            var engagedUser = null;
+            var cursor = null;
+            var userViewerCanvasElement = null;
+            var backgroundRemovalCanvasElement = null;
+
+            // Log errors encountered during sensor configuration
+            function configError(statusText, errorData) {
+                console.log((errorData != null) ? JSON.stringify(errorData) : statusText);
+            }
+
+            // Determine if the specified object has any properties or not
+            function isEmptyObject(obj) {
+                if (obj == null) {
+                    return true;
+                }
+
+                var numProperties = 0;
+
+                for (var prop in obj) {
+                    if (obj.hasOwnProperty(prop)) {
+                        ++numProperties;
+                    }
+                }
+
+                return numProperties <= 0;
+            }
+
+            // Show or hide the cursor
+            function setCursorVisibility(isVisible) {
+                if (cursor == null) {
+                    return;
+                }
+
+                if (isVisible) {
+                    cursor.show();
+                } else {
+                    cursor.hide();
+                }
+            }
+
+            // Show or hide a canvas element
+            function setCanvasVisibility(canvasElement, isVisible) {
+                if (canvasElement == null) {
+                    return;
+                }
+
+                var canvasQuery = $(canvasElement);
+
+                if (isVisible) {
+                    if (!canvasQuery.hasClass("showing")) {
+                        // Clear canvas before showing it
+                        var canvasContext = canvasElement.getContext("2d");
+                        canvasContext.clearRect(0, 0, streamImageWidth, streamImageHeight);
+                    }
+
+                    canvasQuery.addClass("showing");
+                } else {
+                    canvasQuery.removeClass("showing");
+                }
+            }
+
+            var showPanelLabel = "Choose<br/>Background";
+            var hidePanelLabel = "Hide<br/>Panel";
+            function setChoosePanelVisibility(isVisible) {
+                var togglePanelElement = document.getElementById("togglePanelButton");
+                var spanQuery = $("span", togglePanelElement);
+
+                if (isVisible) {
+                    // If choose background panel is being shown, hide toggle button
+                    // border so top of button is aligned with other buttons
+                    $("#choosePanel").addClass("showing");
+                    spanQuery.html(hidePanelLabel);
+                } else {
+                    // If choose background panel is being hidden, show toggle button
+                    // border to provide contrast against picture
+                    $("#choosePanel").removeClass("showing");
+                    spanQuery.html(showPanelLabel);
+                }
+            }
+
+            function isChoosePanelVisible() {
+                return $("#choosePanel").hasClass("showing");
+            }
+
+            // property and function used to keep track of when the choose background control panel
+            // should be hidden after an inactivity period
+            var hidePanelTimeoutId = null;
+            function resetHidePanelTimeout() {
+                // First clear any previous timeout
+                if (hidePanelTimeoutId != null) {
+                    clearTimeout(hidePanelTimeoutId);
+                    hidePanelTimeoutId = null;
+                }
+
+                if (!isSensorConnected || (engagedUser == null)) {
+                    // if there is no engaged user or no sensor connected, we hide the choose background
+                    // control panel after 10 seconds
+                    hidePanelTimeoutId = setTimeout(function () {
+                        setChoosePanelVisibility(false);
+                        hidePanelTimeoutId = null;
+                    }, 10000);
+                }
+            }
+
+            // Update sensor state and perform UI transitions (showing/hiding appropriate UI elements)
+            // related to sensor status or engagement state changes
+            var delayedConfigTimeoutId = null;
+            function updateUserState(newIsSensorConnected, newEngagedUser, sensorToConfig) {
+                var hasEngagedUser = engagedUser != null;
+                var newHasEngagedUser = newEngagedUser != null;
+
+                // If there's a pending configuration change when state changes again, cancel previous timeout
+                if (delayedConfigTimeoutId != null) {
+                    clearTimeout(delayedConfigTimeoutId);
+                    delayedConfigTimeoutId = null;
+                }
+
+                if ((isSensorConnected != newIsSensorConnected) || (engagedUser != newEngagedUser)) {
+                    if (newIsSensorConnected) {
+
+                        var immediateConfig = {};
+                        var delayedConfig = {};
+                        immediateConfig[Kinect.INTERACTION_STREAM_NAME] = { "enabled": true };
+                        immediateConfig[Kinect.USERVIEWER_STREAM_NAME] = { "resolution": streamImageResolution };
+                        immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = { "resolution": streamImageResolution };
+
+                        setCursorVisibility(newHasEngagedUser);
+                        setCanvasVisibility(userViewerCanvasElement, !newHasEngagedUser);
+                        setCanvasVisibility(backgroundRemovalCanvasElement, newHasEngagedUser);
+
+                        if (newHasEngagedUser) {
+                            immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME].enabled = true;
+                            immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME].trackingId = newEngagedUser;
+
+                            delayedConfig[Kinect.USERVIEWER_STREAM_NAME] = { "enabled": false };
+                        } else {
+                            immediateConfig[Kinect.USERVIEWER_STREAM_NAME].enabled = true;
+
+                            if (hasEngagedUser) {
+                                delayedConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = { "enabled": false };
+                            }
+                        }
+
+                        // Perform immediate configuration
+                        sensorToConfig.postConfig(immediateConfig, configError);
+
+                        // schedule delayed configuration for 2 seconds later
+                        if (!isEmptyObject(delayedConfig)) {
+                            delayedConfigTimeoutId = setTimeout(function () {
+                                sensorToConfig.postConfig(delayedConfig, configError);
+                                delayedConfigTimeoutId = null;
+                            }, 2000);
+                        }
+                    } else {
+                        setCursorVisibility(false);
+                        setCanvasVisibility(userViewerCanvasElement, false);
+                        setCanvasVisibility(backgroundRemovalCanvasElement, false);
+                    }
+                }
+
+                isSensorConnected = newIsSensorConnected;
+                engagedUser = newEngagedUser;
+
+                resetHidePanelTimeout();
+            }
+
+            // Get the id of the engaged user, if present, or null if there is no engaged user
+            function findEngagedUser(userStates) {
+                var engagedUserId = null;
+
+                for (var i = 0; i < userStates.length; ++i) {
+                    var entry = userStates[i];
+                    if (entry.userState == "engaged") {
+                        engagedUserId = entry.id;
+                        break;
+                    }
+                }
+
+                return engagedUserId;
+            }
+
+            // Respond to user state change event
+            function onUserStatesChanged(newUserStates) {
+                var newEngagedUser = findEngagedUser(newUserStates);
+
+                updateUserState(isSensorConnected, newEngagedUser, sensor);
+            }
+
+            // Create sensor and UI adapter layers
+            var sensor = Kinect.sensor(Kinect.DEFAULT_SENSOR_NAME, function (sensorToConfig, isConnected) {
+                if (isConnected) {
+                    // Determine what is the engagement state upon connection
+                    sensorToConfig.getConfig(function (data) {
+                        var engagedUserId = findEngagedUser(data[Kinect.INTERACTION_STREAM_NAME].userStates);
+
+                        updateUserState(true, engagedUserId, sensorToConfig);
+                    });
+                } else {
+                    updateUserState(false, engagedUser, sensorToConfig);
+                }
+            });
+            var uiAdapter = KinectUI.createAdapter(sensor);
+
+            uiAdapter.promoteButtons();
+            cursor = uiAdapter.createDefaultCursor();
+            userViewerCanvasElement = document.getElementById("userViewerCanvas");
+            backgroundRemovalCanvasElement = document.getElementById("backgroundRemovalCanvas");
+            uiAdapter.bindStreamToCanvas(Kinect.USERVIEWER_STREAM_NAME, userViewerCanvasElement);
+            uiAdapter.bindStreamToCanvas(Kinect.BACKGROUNDREMOVAL_STREAM_NAME, backgroundRemovalCanvasElement);
+
+            sensor.addEventHandler(function (event) {
+                switch (event.category) {
+                    case Kinect.USERSTATE_EVENT_CATEGORY:
+                        switch (event.eventType) {
+                            case Kinect.USERSTATESCHANGED_EVENT_TYPE:
+                                onUserStatesChanged(event.userStates);
+                                break;
+                        }
+                        break;
+                }
+            });
+
+            setChoosePanelVisibility(false);
+            $("#togglePanelButton").click(function (event) {
+                if (isChoosePanelVisible()) {
+                    setChoosePanelVisibility(false);
+                } else {
+                    setChoosePanelVisibility(true);
+                    resetHidePanelTimeout();
+                }
+            });
+            $(".imgButtonContainer .kinect-button").click(function (event) {
+                var imgElement = $("img", event.currentTarget)[0];
+                document.getElementById("backgroundImg").src = imgElement.src;
+                resetHidePanelTimeout();
+            });
+        });
+    </script>
+    <link rel="stylesheet" type="text/css" href="Kinect-1.8.0.css" /> 
+    <style type="text/css">
+        html {
+            width: 100%;
+            height: 100%;
+            overflow: hidden;
+        }
+
+        body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+            overflow: hidden;
+            font-family: 'Segoe UI';
+        }
+
+        #uiContainer {
+            position: relative;
+            margin: 0 auto;
+            width: 100vw;
+            height: 100%;
+        }
+
+        #logoImg {
+            position: absolute;
+            top: 18px;
+            left: 26px;
+            width: 100px;
+            height: 40px;
+        }
+
+        #sampleName {
+            position: relative;
+            display: block;
+            text-align: right;
+            font-size: 16pt;
+            color: gray;
+            top: 24px;
+            margin-right: 26px;
+        }
+
+        #backgroundContainer {
+            position: absolute;
+            top: 80px;
+            width: 100vw;
+            height: 56.25vw;
+            margin-bottom: 26px;
+        }
+
+        @media screen and (max-width: 960px) {
+            #uiContainer {
+                width: 960px;
+            }
+
+            #backgroundContainer {
+                width: 960px;
+                height: 540px;
+            }
+        }
+
+        @media screen and (min-width: 1600px) {
+            #uiContainer {
+                width: 1600px;
+            }
+
+            #backgroundContainer {
+                width: 1600px;
+                height: 900px;
+            }
+        }
+
+        #backgroundImg {
+            width: 100%;
+            height: 100%;
+        }
+
+        .streamImageCanvas {
+            position: absolute;
+            width: 75%;
+            height: 100%;
+            top: 0;
+            left: 12.5%;
+            opacity: 0.0;
+            transition: opacity 1.0s;
+        }
+
+        .streamImageCanvas.showing {
+            opacity: 1.0;
+            transition: opacity 1.0s;
+        }
+
+        #buttonContainer {
+            position: absolute;
+            display: block;
+            width: 100%;
+            height: 160px;
+            bottom: 0;
+        }
+
+        #choosePanel {
+            position: absolute;
+            background-color: white;
+            width: 100%;
+            height: 100%;
+            top: 100%;
+            transition: top 0.4s;
+        }
+
+        #choosePanel.showing {
+            top: 0;
+            transition: top 0.4s;
+        }
+
+        .commandButtonContainer {
+            position: absolute;
+            width: 18%;
+            height: 100%;
+            top: 0;
+            left: 3%;
+        }
+
+        .imgButtonContainer {
+            position: absolute;
+            width: 18%;
+            height: 100%;
+            top: 0;
+        }
+
+        #togglePanelButton {
+            /* Add an explicit background color to make sure mouse/touch events get routed
+               to button even when hovering/pressing over transparent area around button edge. */
+            background-color: rgba(0,0,0,0.0);
+        }
+
+        #imgButtonContainer1 {
+            left: 25%;
+        }
+
+        #imgButtonContainer2 {
+            left: 43%;
+        }
+
+        #imgButtonContainer3 {
+            left: 61%;
+        }
+
+        #imgButtonContainer4 {
+            left: 79%;
+        }
+
+        .kinect-button.tile {
+            position: relative;
+            display: block;
+            width: 170px;
+            height: 100px;
+            margin: 30px auto;
+            padding: 0;
+        }
+
+        .kinect-button.tile .commandButtonContent {
+            position: absolute;
+            width: 100%;
+            height: 100%;
+            border-style: solid;
+            border-color: white;
+            border-width: 2px;
+            transition: border-width 0.15s;
+            left: -2px;
+            top: -2px;
+        }
+
+        .kinect-button.image {
+            position: relative;
+            display: block;
+            width: 170px;
+            height: 100px;
+            margin: 30px auto;
+            padding: 0;
+        }
+
+        .kinect-button img {
+            width: 100%;
+            height: 100%;
+        }
+
+        .kinect-button-text {
+            font-size: 14pt;
+            width: 100%;
+            height: 3em;
+            margin-top: -1.5em;
+        }
+
+        @media screen and (min-width: 1280px) and (max-width: 1600px) {
+            #buttonContainer {
+                height: 186px;
+            }
+
+            .kinect-button.tile {
+                width: 224px;
+                height: 126px;
+            }
+
+            .kinect-button.image {
+                width: 224px;
+                height: 126px;
+            }
+
+            .kinect-button-text {
+                font-size: 16pt;
+            }
+        }
+
+        @media screen and (min-width: 1600px) {
+            #buttonContainer {
+                height: 222px;
+            }
+
+            .kinect-button.tile {
+                width: 288px;
+                height: 162px;
+            }
+
+            .kinect-button.image {
+                width: 288px;
+                height: 162px;
+            }
+
+            .kinect-button-text {
+                font-size: 18pt;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div id="uiContainer">
+        <img id="logoImg" src="Images\logo.png"/>
+        <span id="sampleName">Kinect Web Basics</span>
+        <div id="backgroundContainer">
+            <img id="backgroundImg" src="Images\beach.jpg"/>
+            <canvas id="userViewerCanvas" class="streamImageCanvas"></canvas>
+            <canvas id="backgroundRemovalCanvas" class="streamImageCanvas"></canvas>
+        </div>
+        <div id="buttonContainer">
+            <div id="choosePanel">
+                <div id="imgButtonContainer1" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/beach.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer2" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/fireworks.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer3" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/hamlet.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer4" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/operahouse.jpg"/>
+                    </div>
+                </div>
+            </div>
+            <div class="commandButtonContainer">
+                <div id="togglePanelButton" class="kinect-button tile">
+                    <div class="commandButtonContent">
+                        <span class="kinect-button-text"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/3d.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/3d.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/3d.js	(revision 28897)
@@ -0,0 +1,172 @@
+var scene = new THREE.Scene();
+var camera = new THREE.PerspectiveCamera(HorToVertFOV(90, (window.innerWidth / window.innerHeight)), window.innerWidth / window.innerHeight, 0.1, 10000);
+//var camera = new THREE.PerspectiveCamera( 90, 1, 0.1, 1000 );
+
+var renderer = new THREE.WebGLRenderer({
+	alpha: true
+});
+
+var panoGeometry = new THREE.SphereGeometry(.25, 16, 16);
+panoGeometry.applyMatrix(new THREE.Matrix4().makeScale(-1, 1, 1));
+
+/*var testMaterial = new THREE.MeshBasicMaterial( { color: 0x22cc22 } );
+var testMaterial2 = new THREE.MeshBasicMaterial( { color: 0x111111 } );*/
+
+var pathGeometry = new THREE.CubeGeometry(1, 1, 1);
+
+var materials = {};
+var curSpheres = [];
+
+var animatedSpheres = [];
+
+$(document).ready(function() {
+	renderer.setSize(window.innerWidth, window.innerHeight);
+	///renderer.setSize( 500, 500 );
+	document.body.appendChild(renderer.domElement);
+
+	renderer.domElement.id = "WebGL";
+
+	//createTestSphere( 0, 1000, 0 );
+	render();
+});
+
+function createTestSphere(heading, distance) {
+	var testMesh = new THREE.Mesh(panoGeometry, testMaterial);
+
+	var hRad = heading * Math.PI / 180;
+
+	testMesh.position.y = -2;
+	testMesh.position.z = Math.cos(hRad) * -distance;
+	testMesh.position.x = Math.sin(hRad) * distance;
+
+
+	scene.add(testMesh);
+	//console.log("Added Sphere at h:"+heading+" d:"+distance+ "  "+testMesh.position.x + ", " + testMesh.position.z);
+}
+
+function createPanoSphere(heading, distance, panoLink, rot) {
+	var m = materials[panoLink.pano];
+	//console.log("val of m: " + m);
+	if (m === undefined) {
+		m = null;
+
+		getPanoImage(panoLink.pano, function(dataUrl) {
+			var tex = THREE.ImageUtils.loadTexture(dataUrl);
+			materials[panoLink.pano] = new THREE.MeshBasicMaterial({
+				map: tex
+			});
+			materials[panoLink.pano].jumpUnused = 0;
+			panoLink.sphere = createSphere(heading, distance, materials[panoLink.pano], rot);
+		});
+	} else {
+		if (m)
+			m.jumpUnused = 0;
+		panoLink.sphere = createSphere(heading, distance, m, rot);
+		return;
+	}
+	panoLink.sphere = null;
+}
+
+function createSphere(heading, distance, material, rot) {
+	var testMesh = new THREE.Mesh(panoGeometry, material);
+
+	var hRad = heading * Math.PI / 180;
+
+
+	testMesh.position.z = Math.cos(hRad) * -distance;
+	testMesh.position.x = Math.sin(hRad) * distance;
+	
+	testMesh.distance = distance;
+
+	testMesh.position.y = -2;
+	testMesh.rotation.y = (-rot - 90) * Math.PI/180;
+	testMesh.heading = hRad;
+
+	testMesh.startPos = testMesh.position.clone();
+
+	scene.add(testMesh);
+	//console.log("Added Sphere at h:"+heading+" d:"+distance+ "  "+testMesh.position.x + ", " + testMesh.position.z);
+
+	curSpheres.push(testMesh);
+	return testMesh;
+}
+
+function getHipCameraPoint(){
+	return new THREE.Vector3(Math.sin(panorama.getPov().heading * Math.PI / 180), 0, -Math.cos(panorama.getPov().heading * Math.PI / 180 )).multiplyScalar(2);
+}
+
+function SyncThreeCamera(heading, pitch) {
+	var rot = new THREE.Matrix4().makeRotationY(-heading * Math.PI / 180).multiply(new THREE.Matrix4().makeRotationX(pitch * Math.PI / 180));
+
+	camera.matrixWorld = rot;
+	camera.matrixAutoUpdate = false;
+}
+
+function render() {
+	requestAnimationFrame(update);
+	renderer.render(scene, camera);
+}
+
+function update(time){
+	//console.log(spheros);
+	for (var i = 0; i < curSpheres.length; ++i){
+		if(curSpheres[i].anim){
+
+			console.log(curSpheres[i].anim);
+			curSpheres[i].position = curSpheres[i].anim.animate();
+		}
+	}
+	render();
+}
+
+function HorToVertFOV(horFOV, aspectRatio) {
+
+	var horFOV = horFOV * Math.PI / 180;
+	var vfov = 2 * Math.atan(Math.tan(horFOV / 2) / aspectRatio);
+	return vfov * 180 / Math.PI;
+}
+
+function nukeSpheres() {
+	for (var i = 0; i < curSpheres.length; ++i) {
+		scene.remove(curSpheres[i]);
+	};
+	curSpheres = [];
+	incrementAndClean(4);
+}
+
+function incrementAndClean(limit) {
+	for (var mat in materials) {
+
+		var actualmat = materials[mat];
+
+		if (actualmat) {
+			actualmat.jumpUnused += 1;
+			if (actualmat.jumpUnused > limit) {
+				actualmat.dispose();
+				delete materials[mat];
+				if (materials[mat])
+					alert("deletion failed!");
+			}
+		}
+	}
+}
+
+function Ease(startPosition, endPosition, animLength){
+	this.timeStart = performance.now();
+	this.startPosition = startPosition.clone();
+	this.endPosition = endPosition.clone();
+	this.path = endPosition.clone().sub(startPosition);
+	this.animLength = animLength;
+	this.time = 0;
+
+	this.animate = function(){
+		this.time = performance.now() - this.timeStart;
+		return this.startPosition.clone().add(this.path.clone().multiplyScalar(THREE.Math.smootherstep(this.time / this.animLength, 0 ,1)));
+	}
+}
+
+function cameraFromGoogleZoom(zoom){
+	var fov = 180 / Math.pow(2, zoom);
+	return new THREE.PerspectiveCamera(HorToVertFOV(fov, (window.innerWidth / window.innerHeight)), window.innerWidth / window.innerHeight, 0.1, 10000);
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/LICENSE
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/LICENSE	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/LICENSE	(revision 28897)
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2012 Jaume Sanchez Elias
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/README.md
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/README.md	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/README.md	(revision 28897)
@@ -0,0 +1,45 @@
+# GSVPano.js - Google Street View Panorama lib
+
+Library to help requesting and stitching Google Street View panoramas. 
+
+You can see it in use in a [WebGL Google Street View Panorama Viewer](http://www.clicktorelease.com/code/street).
+
+Forks, pull requests and code critiques are welcome!
+
+#### Using the code ####
+
+Include GSVPano.[min.]js and Google Maps API lib. 
+
+The lib uses google.maps.LatLng to specify the location and google.maps.StreetViewService.
+
+```html
+<script src="GSVPano.min.js"></script>
+<script src="http://maps.google.com/maps/api/js?sensor=false"></script>
+````
+
+Add this basic code:
+
+```js
+// Create a PanoLoader object
+var loader = new GSVPANO.PanoLoader();
+
+// Implement the onPanoramaLoad handler
+loader.onPanoramaLoad = function() {
+
+	/*
+		Do your thing with the panorama:
+		this.canvas: an HTML5 canvas with the texture
+		this.copyright: the copyright of the images
+	*/
+
+};
+
+// Invoke the load method with a LatLng point
+loader.load( new google.maps.LatLng( 42.216188,-75.72657859999998 ) );
+```
+
+#### License ####
+
+MIT licensed
+
+Copyright (C) 2012 Jaume Sanchez Elias, http://www.clicktorelease.com
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/build/GSVPano.min.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/build/GSVPano.min.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/build/GSVPano.min.js	(revision 28897)
@@ -0,0 +1,5 @@
+var GSVPANO=GSVPANO||{};
+GSVPANO.PanoLoader=function(g){var g=g||{},d,k,m=new google.maps.StreetViewService,f=0,h=0,a=document.createElement("canvas"),i=a.getContext("2d");this.setProgress=function(b){if(this.onProgress)this.onProgress(b)};this.throwError=function(b){if(this.onError)this.onError(b);else console.error(b)};this.adaptTextureToZoom=function(){var b=416*Math.pow(2,d),c=416*Math.pow(2,d-1);a.width=b;a.height=c;i.translate(a.width,0);i.scale(-1,1)};this.composeFromTile=function(b,c,j){i.drawImage(j,512*b,512*c);
+f++;this.setProgress(Math.round(100*f/h));if(f===h&&(this.canvas=a,this.onPanoramaLoad))this.onPanoramaLoad()};this.composePanorama=function(){this.setProgress(0);console.log("Loading panorama for zoom "+d+"...");var b=Math.pow(2,d),c=Math.pow(2,d-1),j=this,l,a,e;f=0;h=b*c;for(e=0;e<c;e++)for(a=0;a<b;a++)l="http://maps.google.com/cbk?output=tile&panoid="+k+"&zoom="+d+"&x="+a+"&y="+e+"&"+Date.now(),function(b,c){var a=new Image;a.addEventListener("load",function(){j.composeFromTile(b,c,this)});a.crossOrigin=
+"";a.src=l}(a,e)};this.load=function(b){console.log("Load for",b);var c=this;m.getPanoramaByLocation(b,50,function(a,d){if(d===google.maps.StreetViewStatus.OK){if(c.onPanoramaData)c.onPanoramaData(a);google.maps.geometry.spherical.computeHeading(b,a.location.latLng);c.copyright=a.copyright;k=a.location.pano;c.location=b;c.composePanorama()}else{if(c.onNoPanoramaData)c.onNoPanoramaData(d);c.throwError("Could not retrieve panorama for the following reason: "+d)}})};this.setZoom=function(a){d=a;this.adaptTextureToZoom()};
+this.setZoom(g.zoom||1)};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/build/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/build/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/build/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/src/GSVPano.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/src/GSVPano.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/src/GSVPano.js	(revision 28897)
@@ -0,0 +1,161 @@
+var GSVPANO = GSVPANO || {};
+GSVPANO.PanoLoader = function (parameters) {
+
+	'use strict';
+
+	var _parameters = parameters || {},
+		_location,
+		_zoom,
+		_panoId,
+		_panoClient = new google.maps.StreetViewService(),
+		_count = 0,
+		_total = 0,
+		_canvas = document.createElement('canvas'),
+		_ctx = _canvas.getContext('2d'),
+		rotation = 0,
+		pitch = 0,
+		copyright = '',
+		onSizeChange = null,
+		onPanoramaLoad = null;
+		
+	this.setProgress = function (p) {
+	
+		if (this.onProgress) {
+			this.onProgress(p);
+		}
+		
+	};
+
+	this.throwError = function (message) {
+	
+		if (this.onError) {
+			this.onError(message);
+		} else {
+			console.error(message);
+		}
+		
+	};
+
+	this.adaptTextureToZoom = function () {
+	
+		var w = 416 * Math.pow(2, _zoom),
+			h = (416 * Math.pow(2, _zoom - 1));
+		_canvas.width = w;
+		_canvas.height = h;
+
+		//donut flip
+		/*_ctx.translate( _canvas.width, 0);
+		_ctx.scale(-1, 1);*/
+	};
+
+	this.composeFromTile = function (x, y, texture) {
+	
+		_ctx.drawImage(texture, x * 512, y * 512);
+		_count++;
+		
+		var p = Math.round(_count * 100 / _total);
+		this.setProgress(p);
+		
+		if (_count === _total) {
+			this.canvas = _canvas;
+			if (this.onPanoramaLoad) {
+				this.onPanoramaLoad();
+			}
+		}
+		
+	};
+
+	this.composePanorama = function (panoId) {
+	
+		this.setProgress(0);
+		//console.log('Loading panorama for zoom ' + _zoom + '...');
+		
+		var w = (_zoom==3) ? 7 : Math.pow(2, _zoom),
+			h = Math.pow(2, _zoom - 1),
+			self = this,
+			url,
+			x,
+			y;
+				
+		_count = 0;
+		_total = w * h;
+	
+		for( y = 0; y < h; y++) {
+			for( x = 0; x < w; x++) {
+				url = 'http://maps.google.com/cbk?output=tile&panoid=' + panoId + '&zoom=' + _zoom + '&x=' + x + '&y=' + y + '&' + Date.now();
+				(function (x, y) { 
+					var img = new Image();
+					img.addEventListener('load', function () {
+						self.composeFromTile(x, y, this);
+					});
+					img.crossOrigin = '';
+					img.crossOrigin	= 'anonymous';
+					img.src = url;
+				})(x, y);
+			}
+		}
+		
+	};
+	
+	this.load = function (location, callback) {
+	
+		//console.log('Load for', location);
+		var self = this;
+		_panoClient.getPanoramaByLocation(location, 50, function (result, status) {
+			if (status === google.maps.StreetViewStatus.OK) {
+				if( self.onPanoramaData ) self.onPanoramaData( result );
+				rotation = result.tiles.centerHeading * Math.PI / 180.0;
+				pitch = result.tiles.originPitch;
+				copyright = result.copyright;
+				self.copyright = result.copyright;
+				_panoId = result.location.pano;
+				self.location = location;
+				self.rotation = rotation;
+				self.pitch = pitch;
+				self.image_date = result.imageDate;
+				self.id = _panoId;
+				callback();
+			} else {
+				if( self.onNoPanoramaData ) self.onNoPanoramaData( status );
+				self.throwError('Could not retrieve panorama for the following reason: ' + status);
+				callback();
+			}
+		});
+		
+	};
+
+	/*this.loadById = function (panoId, callback) {
+	
+		console.log('Load for', location);
+		var self = this;
+		_panoClient.getPanoramaById(panoId, function (result, status) {
+			if (status === google.maps.StreetViewStatus.OK) {
+				if( self.onPanoramaData ) self.onPanoramaData( result );
+				rotation = result.tiles.centerHeading * Math.PI / 180.0;
+				pitch = result.tiles.originPitch;
+				copyright = result.copyright;
+				self.copyright = result.copyright;
+				_panoId = result.location.pano;
+				self.location = location;
+				self.rotation = rotation;
+				self.pitch = pitch;
+				self.image_date = result.imageDate;
+				self.id = _panoId;
+				callback();
+			} else {
+				if( self.onNoPanoramaData ) self.onNoPanoramaData( status );
+				self.throwError('Could not retrieve panorama for the following reason: ' + status);
+				callback();
+			}
+		});
+		
+	};*/
+	
+	this.setZoom = function( z ) {
+		_zoom = z;
+		this.adaptTextureToZoom();
+	};
+
+	this.setZoom( _parameters.zoom || 1 );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/src/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/src/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/src/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/utils/build.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/utils/build.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/utils/build.sh	(revision 28897)
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+java -jar compiler.jar --js=../src/GSVPano.js --js_output_file=../build/GSVPano.min.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/utils/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/utils/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/GSVPano.js-master/utils/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Images/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Images/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Images/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Kinect-1.8.0.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Kinect-1.8.0.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Kinect-1.8.0.css	(revision 28897)
@@ -0,0 +1,134 @@
+﻿/***************************************************************************
+ * <copyright file="Kinect-1.8.0.css" company="Microsoft">                 *
+ *     Copyright (c) Microsoft Corporation.  All rights reserved.          *
+ * </copyright>                                                            *
+ ***************************************************************************/
+
+.kinect-button {
+    margin: 10px;
+    display: inline-block;
+    padding: 25px;
+    height: 200px;
+    width: 200px;
+    position: relative;
+    font-family: 'Segoe UI';
+    font-size: 16pt;
+    font-weight: 600;
+}
+
+.kinect-button.small {
+    height: 120px;
+    width: 120px;
+    font-size: 12pt;
+    font-weight: 500;
+}
+
+.kinect-button.medium {
+    height: 200px;
+    width: 200px;
+    font-size: 16pt;
+    font-weight: 600;
+}
+
+.kinect-button.large {
+    height: 300px;
+    width: 300px;
+    font-size: 20pt;
+    font-weight: 600;
+}
+
+.kinect-button-surface {
+    position: relative;
+    left: 3%;
+    top: 3%;
+    width: 94%;
+    height: 94%;
+    transition: left 0.16s, top 0.16s, width 0.16s, height 0.16s;
+}
+
+.kinect-button-text {
+    position: absolute;
+    left: 0px;
+    top: 50%;
+    height: 20pt;
+    width: 100%;
+    margin-top: -10pt;
+    text-align: center;
+    user-select: none;
+    -webkit-user-select: none;
+}
+            
+.button-hover .kinect-button-surface {
+    left: 0%;
+    top: 0%;
+    width: 100%;
+    height: 100%;
+    transition: left 0.16s, top 0.16s, width 0.16s, height 0.16s;
+}
+
+.button-pressed .kinect-button-surface {
+    left: 7.5%;
+    top: 7.5%;
+    width: 85%;
+    height: 85%;
+    transition: left 0.10s, top 0.10s, width 0.10s, height 0.10s;
+}
+
+.kinect-button.tile .kinect-button-surface {
+    border: none;
+    background: linear-gradient(to bottom, #511c74, #311040);
+    color: white;
+}
+
+#kinect-cursor {
+    position: absolute;
+    display: none;
+    width: 120px;
+    height: 120px;
+}
+
+/* normal cursor state */
+#kinect-cursor-glow {
+    stroke-opacity: 0.0;
+}
+
+#kinect-cursor-normal {
+    stroke-opacity: 1.0;
+    fill-opacity: 1.0;
+}
+
+#cursor-progress {
+    stroke-opacity: 0.0;
+    fill-opacity: 0.0;
+}
+
+#kinect-cursor-extended {
+    stroke-opacity: 0.0;
+    fill-opacity: 0.0;
+}
+
+/* hover cursor state */
+.cursor-hover #kinect-cursor-glow {
+    stroke-opacity: 1.0;
+}
+
+.cursor-hover #kinect-cursor-progress {
+    stroke-opacity: 1.0;
+    fill-opacity: 1.0;
+}
+
+/* pressed cursor state */
+.cursor-pressed #kinect-cursor-normal {
+    stroke-opacity: 0.0;
+    fill-opacity: 0.0;
+}
+
+.cursor-pressed #kinect-cursor-progress {
+    stroke-opacity: 0.0;
+    fill-opacity: 0.0;
+}
+
+.cursor-pressed #kinect-cursor-extended {
+    stroke-opacity: 1.0;
+    fill-opacity: 1.0;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Kinect-1.8.0.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Kinect-1.8.0.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/Kinect-1.8.0.js	(revision 28897)
@@ -0,0 +1,2228 @@
+// -----------------------------------------------------------------------
+// <copyright file="Kinect-1.8.0.js" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+
+//////////////////////////////////////////////////////////////
+// Create the global Kinect object
+var Kinect = (function () {
+    "use strict";
+    
+    //////////////////////////////////////////////////////////////
+    // SocketManager object constructor
+    //     SocketManager(uri, onmessage(socket,message) [, onconnection(isConnected)] )
+    //
+    // uri: URI of websocket endpoint to connect to
+    // onmessage: callback function to call whenever a message is received
+    // onconnection: function to call back whenever socket connection status changes
+    //               from disconnected to connected or vice versa
+    function SocketManager(uri, onmessage, onconnection) {
+
+        //////////////////////////////////////////////////////////////
+        // Private SocketExec properties
+        var onStatusChanged = null;
+        var statusMessage = "";
+        var socket = null;
+        var socketManager = this;
+        var wsUri = uri.replace(/^http(s)?:/i, "ws$1:");
+
+        //////////////////////////////////////////////////////////////
+        // Private SocketExec methods
+        function updateStatusChanged() {
+            if (onStatusChanged != null) {
+                onStatusChanged(statusMessage);
+            }
+        }
+
+        function updateStatus(message) {
+            statusMessage = message;
+
+            updateStatusChanged();
+        }
+
+        function tryConnection() {
+            if (!socketManager.isStarted) {
+                return;
+            }
+
+            if (socket != null) {
+                updateStatus("Already connected." + (new Date()).toTimeString());
+                return;
+            }
+            updateStatus("Connecting to server...");
+
+            // Initialize a new web socket.
+            socket = new WebSocket(wsUri);
+            
+            // Receive binary data as ArrayBuffer rather than Blob
+            socket.binaryType = "arraybuffer";
+
+            // Connection established.
+            socket.onopen = function () {
+                if (typeof onconnection == "function") {
+                    onconnection(true);
+                }
+                
+                updateStatus("Connected to server.");
+            };
+
+            // Connection closed.
+            socket.onclose = function () {
+                if (typeof onconnection == "function") {
+                    onconnection(false);
+                }
+                
+                updateStatus("Connection closed.");
+                if (socketManager.isStarted) {
+                    // Keep trying to reconnect as long as we're started
+                    setTimeout(tryConnection, socketManager.retryTimeout, socketManager);
+                }
+                socket = null;
+            };
+
+            // Receive data FROM the server!
+            socket.onmessage = function(message) {
+                onmessage(socket, message);
+            };
+        }
+
+        //////////////////////////////////////////////////////////////
+        // Public SocketManager properties
+        
+        // connection retry timeout, in milliseconds
+        this.retryTimeout = 5000;
+
+        // true if socket has been started
+        this.isStarted = false;
+
+        //////////////////////////////////////////////////////////////
+        // Public SocketManager functions
+        this.setOnStatusChanged = function (statusChangedCallback) {
+            onStatusChanged = statusChangedCallback;
+            updateStatusChanged();
+        };
+
+        this.start = function() {
+            this.isStarted = true;
+
+            tryConnection(this);
+        };
+
+        this.stop = function() {
+            this.isStarted = false;
+
+            if (socket != null) {
+                socket.close();
+            }
+        };
+
+        //////////////////////////////////////////////////////////////
+        // SocketManager initialization code
+        if (window.WebSocket == null) {
+            updateStatus("Your browser does not support web sockets!");
+            return;
+        }
+    }
+
+    //////////////////////////////////////////////////////////////
+    // KinectConnector object constructor
+    function KinectConnector() {
+
+        //////////////////////////////////////////////////////////////
+        // Private KinectConnector properties
+
+        // Configure default connection values
+        var DEFAULT_HOST_URI = "http://localhost";
+        var DEFAULT_HOST_PORT = 8181;
+        var DEFAULT_BASE_PATH = "Kinect";
+        var DEFAULT_SENSOR_NAME = "default";
+
+        // If connectionUri is null, it indicates that we're not connected yet
+        var connectionUri = null;
+
+        // If explicitDisconnect is true, it indicates that client has called
+        // "KinectConnector.disconnect" explicitly.
+        var explicitDisconnect = false;
+
+        // Mapping between sensor names and KinectSensor objects
+        var sensorMap = {};
+        
+        // true if server requests to REST endpoint should be asynchronous
+        var asyncRequests = true;
+
+        var connector = this;
+
+        //////////////////////////////////////////////////////////////
+        // KinectSensor object constructor
+        //     KinectSensor(baseEndpointUri, onconnection(sensor, isConnected))
+        // 
+        // baseEndpointUri: base URI for web endpoints corresponding to sensor
+        // onconnection: Function to call back whenever status of connection to the
+        //               server that owns this sensor changes from disconnected to
+        //               connected or vice versa.
+        function KinectSensor(baseEndpointUri, onconnection) {
+
+            //////////////////////////////////////////////////////////////
+            // Private KinectSensor properties
+
+            // URI used to connect to endpoint used to configure sensor state
+            var stateEndpoint = baseEndpointUri + "/state";
+
+            // true if sensor connection to server is currently enabled, false otherwise
+            var isConnectionEnabled = false;
+            
+            // Array of registered stream frame ready handlers
+            var streamFrameHandlers = [];
+            
+            // Header portion of a two-part stream message that is still missing a binary payload
+            var pendingStreamFrame = null;
+
+            // Reference to this sensor object for use in internal functions
+            var sensor = this;
+
+            var streamSocketManager = new SocketManager(
+                baseEndpointUri + "/stream",
+                function(socket, message) {
+
+                    function broadcastFrame(frame) {
+                        for (var iHandler = 0; iHandler < streamFrameHandlers.length; ++iHandler) {
+                            streamFrameHandlers[iHandler](frame);
+                        }
+                    }
+
+                    if (message.data instanceof ArrayBuffer) {
+                        if ((pendingStreamFrame == null) || (pendingStreamFrame.bufferLength != message.data.byteLength)) {
+                            // Ignore any binary messages that were not preceded by a header message
+                            console.log("Binary socket message received without corresponding header");
+                            pendingStreamFrame = null;
+                            return;
+                        }
+
+                        pendingStreamFrame.buffer = message.data;
+                        broadcastFrame(pendingStreamFrame);
+                        pendingStreamFrame = null;
+                    } else if (typeof(message.data) == "string") {
+                        pendingStreamFrame = null;
+
+                        try {
+                            // Get the data in JSON format.
+                            var frameData = JSON.parse(message.data);
+
+                            // If message has a 'bufferLength' property, it means that this is a message header
+                            // and we should wait for the binary payload before broadcasting this message.
+                            if ("bufferLength" in frameData) {
+                                pendingStreamFrame = frameData;
+                            } else {
+                                broadcastFrame(frameData);
+                            }
+                        }
+                         catch(error) {
+                            console.log('Tried processing stream message, but failed with error: ' + error);
+                        }
+                    } else {
+                        // Ignore any messages of unexpected types.
+                        console.log("Unexpected type for received socket message");
+                        pendingStreamFrame = null;
+                        return;
+                    }
+                },
+                function(isConnected) {
+                    // Use the existence of the stream channel connection to mean that a
+                    // server exists and is ready to accept requests
+                    sensor.isConnected = isConnected;
+                    onconnection(sensor, isConnected);
+                });
+            
+            // Array of registered event handlers
+            var eventHandlers = [];
+
+            var eventSocketManager = new SocketManager(baseEndpointUri + "/events", function(socket, message) {
+                // Get the data in JSON format.
+                var eventData = JSON.parse(message.data);
+
+                for (var iHandler = 0; iHandler < eventHandlers.length; ++iHandler) {
+                    eventHandlers[iHandler](eventData);
+                }
+            });
+
+            // Registered hit test handler
+            var hitTestHandler = null;
+            
+            var hitTestSocketManager = new SocketManager(baseEndpointUri + "/interaction/client", function (socket, message) {
+                // Get the data in JSON format.
+                var eventData = JSON.parse(message.data);
+                var id = eventData.id;
+                var name = eventData.name;
+                var args = eventData.args;
+
+                switch (name) {
+                    case "getInteractionInfoAtLocation":
+                        var handlerResult = null;
+                        var defaultResult = { "isPressTarget": false, "isGripTarget": false };
+
+                        if (hitTestHandler != null) {
+                            try {
+                                handlerResult = hitTestHandler.apply(sensor, args);
+                            } catch(e) {
+                                handlerResult = null;
+                            }
+                        }
+
+                        socket.send(JSON.stringify({ "id": id, "result": ((handlerResult != null) ? handlerResult : defaultResult) }));
+                        break;
+                    
+                    case "ping":
+                        socket.send(JSON.stringify({ "id": id, "result": true }));
+                        break;
+                }
+            });
+            
+            //////////////////////////////////////////////////////////////
+            // Private KinectSensor functions
+
+            // Perform ajax request
+            //    ajax( method, uri, success(responseData) [, error(statusText)] )
+            //
+            // method: http method
+            // uri: target uri of ajax call
+            // requestData: data to send to uri as part of request (may be null)
+            // success: Callback function executed if request succeeds.
+            // error: Callback function executed if request fails (may be null)
+            function ajax(method, uri, requestData, success, error) {
+                if (!isConnectionEnabled) {
+                    if (error != null) {
+                        error("disconnected from server");
+                    }
+                    return;
+                }
+                
+                var xhr = new XMLHttpRequest();
+                xhr.open(method, uri, asyncRequests);
+                xhr.onload = function (e) {
+                    if (xhr.readyState === XMLHttpRequest.DONE) {
+                        if (xhr.status == 200) {
+                            success(JSON.parse(xhr.responseText));
+                        } else if (error != null) {
+                            error(xhr.statusText);
+                        }
+                    }
+                };
+                if (error != null) {
+                    xhr.onerror = function (e) {
+                        error(xhr.statusText);
+                    };
+                }
+                xhr.send(requestData);
+            }
+            
+            // Respond to changes in the connection enabled status
+            //    onConnectionEnabledChanged( )
+            function onConnectionEnabledChanged() {
+                if (isConnectionEnabled && !streamSocketManager.isStarted) {
+                    streamSocketManager.start();
+                } else if (!isConnectionEnabled && streamSocketManager.isStarted) {
+                    streamSocketManager.stop();
+                }
+            }
+            
+            // Respond to changes in the number of registered event handlers
+            //    onEventHandlersChanged( )
+            function onEventHandlersChanged() {
+                if ((eventHandlers.length > 0) && !eventSocketManager.isStarted) {
+                    eventSocketManager.start();
+                } else if ((eventHandlers.length == 0) && eventSocketManager.isStarted) {
+                    eventSocketManager.stop();
+                }
+            }
+            
+            // Respond to changes in the registered hit test handler
+            //    onHitTestHandlerChanged( )
+            function onHitTestHandlerChanged() {
+                if ((hitTestHandler != null) && !hitTestSocketManager.isStarted) {
+                    hitTestSocketManager.start();
+                } else if ((hitTestHandler == null) && hitTestSocketManager.isStarted) {
+                    hitTestSocketManager.stop();
+                }
+            }
+            
+            //////////////////////////////////////////////////////////////
+            // Public KinectSensor properties
+            this.isConnected = false;
+
+            //////////////////////////////////////////////////////////////
+            // Public KinectSensor functions
+
+            // Request current sensor configuration
+            //    .getConfig( success(configData) [, error(statusText)] )
+            //
+            // success: Callback function executed if request succeeds.
+            // error: Callback function executed if request fails.
+            this.getConfig = function (success, error) {
+                if ((arguments.length < 1) || (typeof success != 'function')) {
+                    throw new Error("first parameter must be specified and must be a function");
+                }
+
+                if ((arguments.length >= 2) && (typeof error != 'function')) {
+                    throw new Error("if second parameter is specified, it must be a function");
+                }
+
+                ajax("GET", stateEndpoint, null,
+                    function(response) { success(response); },
+                    (error != null) ? function (statusText) { error(statusText); } : null
+                );
+            };
+
+            // Update current sensor configuration
+            //     .postConfig( configData [, error(statusText [, data] )] )
+            //
+            // configData: New configuration property/value pairs to update
+            //             for each sensor stream.
+            // error: Callback function executed if request fails.
+            //        data parameter of error callback will be null if request
+            //        could not be completed at all, but will be a JSON object
+            //        giving failure details in case of semantic failure to
+            //        satisfy request.
+            this.postConfig = function (configData, error) {
+                if ((arguments.length < 1) || (typeof configData != 'object')) {
+                    throw new Error("first parameter must be specified and must be an object");
+                }
+
+                if ((arguments.length >= 2) && (typeof error != 'function')) {
+                    throw new Error("if second parameter is specified, it must be a function");
+                }
+
+                ajax("POST", stateEndpoint, JSON.stringify(configData),
+                    function(response) {
+                        if (!response.success && (error != null)) {
+                            error("semantic failure", response);
+                        }
+                    },
+                    (error != null) ? function (statusText) { error(statusText); } : null
+                );
+            };
+
+            // Enable connections with server
+            //    .connect()
+            this.connect = function () {
+                isConnectionEnabled = true;
+
+                onConnectionEnabledChanged();
+                onEventHandlersChanged();
+                onHitTestHandlerChanged();
+            };
+
+            // Disable connections with server
+            //    .disconnect()
+            this.disconnect = function () {
+                isConnectionEnabled = false;
+                
+                onConnectionEnabledChanged();
+                onEventHandlersChanged();
+                onHitTestHandlerChanged();
+            };
+            
+            // Add a new stream frame handler.
+            //     .addStreamFrameHandler( handler(streamFrame) )] )
+            //
+            // handler: Callback function to be executed when a stream frame is ready to
+            //          be processed.
+            this.addStreamFrameHandler = function (handler) {
+                if (typeof(handler) != "function") {
+                    throw new Error("first parameter must be specified and must be a function");
+                }
+                
+                streamFrameHandlers.push(handler);
+            };
+            
+            // Removes one (or all) stream frame handler(s).
+            //     .removeStreamFrameHandler( [handler(streamFrame)] )] )
+            //
+            // handler: Stream frame handler callback function to be removed.
+            //          If omitted, all stream frame handlers are removed.
+            this.removeStreamFrameHandler = function (handler) {
+                switch (typeof(handler)) {
+                    case "undefined":
+                        streamFrameHandlers = [];
+                        break;
+                    case "function":
+                        var index = streamFrameHandlers.indexOf(handler);
+                        if (index >= 0) {
+                            streamFrameHandlers.slice(index, index + 1);
+                        }
+                        break;
+                        
+                    default:
+                        throw new Error("first parameter must either be a function or left unspecified");
+                }
+            };
+            
+            // Add a new event handler.
+            //     .addEventHandler( handler(event) )] )
+            //
+            // handler: Callback function to be executed when an event is ready to be processed.
+            this.addEventHandler = function (handler) {
+                if (typeof (handler) != "function") {
+                    throw new Error("first parameter must be specified and must be a function");
+                }
+
+                eventHandlers.push(handler);
+
+                onEventHandlersChanged();
+            };
+
+            // Removes one (or all) event handler(s).
+            //     .removeEventHandler( [handler(streamFrame)] )] )
+            //
+            // handler: Event handler callback function to be removed.
+            //          If omitted, all event handlers are removed.
+            this.removeEventHandler = function (handler) {
+                switch (typeof (handler)) {
+                    case "undefined":
+                        eventHandlers = [];
+                        break;
+                    case "function":
+                        var index = eventHandlers.indexOf(handler);
+                        if (index >= 0) {
+                            eventHandlers.slice(index, index + 1);
+                        }
+                        break;
+
+                    default:
+                        throw new Error("first parameter must either be a function or left unspecified");
+                }
+                
+                onEventHandlersChanged();
+            };
+            
+            // Set hit test handler.
+            //     .setHitTestHandler( [handler(skeletonTrackingId, handType, x, y)] )] )
+            //
+            // handler: Callback function to be executed when interaction stream needs 
+            //          interaction information in order to adjust a hand pointer position
+            //          relative to UI.
+            //          If omitted, hit test handler is removed.
+            //          - skeletonTrackingId: The skeleton tracking ID for which interaction
+            //                                information is being retrieved.
+            //          - handType: "left" or "right" to represent hand type for which
+            //                       interaction information is being retrieved.
+            //          - x: X-coordinate of UI location for which interaction information
+            //               is being retrieved. 0.0 corresponds to left edge of interaction
+            //               region and 1.0 corresponds to right edge of interaction region.
+            //          - y: Y-coordinate of UI location for which interaction information
+            //               is being retrieved. 0.0 corresponds to top edge of interaction
+            //               region and 1.0 corresponds to bottom edge of interaction region.
+            this.setHitTestHandler = function (handler) {
+                switch (typeof (handler)) {
+                    case "undefined":
+                    case "function":
+                        hitTestHandler = handler;
+                        break;
+
+                    default:
+                        throw new Error("first parameter must be either a function or left unspecified");
+                }
+
+                onHitTestHandlerChanged();
+            };
+        }
+
+        //////////////////////////////////////////////////////////////
+        // Public KinectConnector properties
+        
+        // Server connection defaults
+        this.DEFAULT_HOST_URI = DEFAULT_HOST_URI;
+        this.DEFAULT_HOST_PORT = DEFAULT_HOST_PORT;
+        this.DEFAULT_BASE_PATH = DEFAULT_BASE_PATH;
+        this.DEFAULT_SENSOR_NAME = DEFAULT_SENSOR_NAME;
+        
+        // Supported stream names
+        this.INTERACTION_STREAM_NAME = "interaction";
+        this.USERVIEWER_STREAM_NAME = "userviewer";
+        this.BACKGROUNDREMOVAL_STREAM_NAME = "backgroundRemoval";
+        this.SKELETON_STREAM_NAME = "skeleton";
+        this.SENSORSTATUS_STREAM_NAME = "sensorStatus";
+        
+        // Supported event categories and associated event types
+        this.USERSTATE_EVENT_CATEGORY = "userState";
+        this.PRIMARYUSERCHANGED_EVENT_TYPE = "primaryUserChanged";
+        this.USERSTATESCHANGED_EVENT_TYPE = "userStatesChanged";
+        this.SENSORSTATUS_EVENT_CATEGORY = "sensorStatus";
+        this.SENSORSTATUSCHANGED_EVENT_TYPE = "statusChanged";
+        
+        //////////////////////////////////////////////////////////////
+        // Public KinectConnector methods
+
+        // Enable connections with server
+        //    .connect( [ hostUri [, hostPort [, basePath ] ] ] )
+        //
+        // hostUri: URI for host that is serving Kinect data.
+        //          Defaults to "http://localhost".
+        // hostPort: HTTP port from which Kinect data is being served.
+        //           Defaults to 8181.
+        // basePath: base URI path for all endpoints that serve Kinect data.
+        //           Defaults to "kinect".
+        this.connect = function (hostUri, hostPort, basePath) {
+            hostUri = (hostUri != null) ? hostUri : DEFAULT_HOST_URI;
+            hostPort = (hostPort != null) ? hostPort : DEFAULT_HOST_PORT;
+            basePath = (basePath != null) ? basePath : DEFAULT_BASE_PATH;
+
+            // Indicate we're now connected
+            connectionUri = hostUri + ":" + hostPort + "/" + basePath;
+            explicitDisconnect = false;
+        };
+
+        // Disable connections with server
+        //     .disconnect()
+        this.disconnect = function () {
+            // stop all sensors currently tracked
+            
+            for (var sensorKey in sensorMap) {
+                var sensor = sensorMap[sensorKey];
+                sensor.disconnect();
+            }
+
+            sensorMap = {};
+            
+            // Indicate we're now disconnected
+            connectionUri = null;
+            explicitDisconnect = true;
+        };
+
+        // Gets an object representing a connection with a specific Kinect sensor.
+        //     .sensor( [sensorName [, onconnection(sensor, isConnected)]] )
+        //
+        // sensorName: name used to refer to a specific Kinect sensor exposed by the server.
+        //             Defaults to "default".
+        // onconnection: Function to call back whenever status of connection to the
+        //               server that owns the sensor with specified name changes from
+        //               disconnected to connected or vice versa.
+        //
+        // Remarks
+        // - If client has never called KinectConnector.connect or .disconnect, .connect() is
+        //   called implicitly with default server URI parameters.
+        // - If client has explicitly called KinectConnector.disconnect, calling this method
+        //   returns null.
+        // - If a non-null sensor is returned, it will already be in connected state (i.e.:
+        //   there is no need to call KinectSensor.connect immediately after calling this
+        //   method).
+        this.sensor = function (sensorName, onconnection) {
+            switch (typeof(sensorName)) {
+                case "undefined":
+                case "string":
+                    break;
+                        
+                default:
+                    throw new Error("first parameter must be a string or left unspecified");
+            }
+            switch (typeof (onconnection)) {
+                case "undefined":
+                    // provide a guarantee to KinectSensor constructor that onconnection
+                    // will exist and be a real function
+                    onconnection = function() { };
+                    break;
+                case "function":
+                    break;
+
+                default:
+                    throw new Error("second parameter must be a function or left unspecified");
+            }
+
+            if (explicitDisconnect) {
+                return null;
+            }
+            
+            if (connectionUri == null) {
+                // If we're not connected yet, connect with default parameters
+                // when someone asks for a sensor.
+                this.connect();
+            }
+
+            sensorName = (sensorName != null) ? sensorName : DEFAULT_SENSOR_NAME;
+
+            // If sensor for specified name doesn't exist yet, create it
+            if (!sensorMap.hasOwnProperty(sensorName)) {
+                sensorMap[sensorName] = new KinectSensor(connectionUri + "/" + sensorName, onconnection);
+
+                // Start the sensors connected by default when they are requested
+                sensorMap[sensorName].connect();
+            }
+
+            return sensorMap[sensorName];
+        };
+
+        // Get whether server requests to REST endpoints should be asynchronous (true by default)
+        //     .async()
+        //
+        // -------------------------------------------------------------------------------------
+        // Specify whether server requests to REST endpoints should be asynchronous
+        //     .async( asyncRequests )
+        // 
+        // isAsync: true if server requests to REST endpoints should be asynchronous
+        //          false if server requests to REST endpoints should be synchronous
+        this.async = function(isAsync) {
+            if (typeof isAsync == 'undefined') {
+                return asyncRequests;
+            }
+
+            var newAsync = isAsync == true;
+            asyncRequests = newAsync;
+        };
+    }
+
+    // The global Kinect object is a singleton instance of our internal KinectConnector type
+    return new KinectConnector();
+})();
+
+//////////////////////////////////////////////////////////////
+// Create the global Kinect UI factory object
+var KinectUI = (function () {
+    "use strict";
+    
+    //////////////////////////////////////////////////////////////
+    // KinectUIAdapter object constructor
+    function KinectUIAdapter() {
+        
+        //////////////////////////////////////////////////////////////
+        // HandPointer object constructor
+        function HandPointer(trackingId, playerIndex, handType) {
+            //////////////////////////////////////////////////////////////
+            // Public HandPointer properties
+            this.trackingId = trackingId;
+            this.playerIndex = playerIndex;
+            this.handType = handType;
+            this.timestampOfLastUpdate = 0;
+            this.handEventType = "None";
+            this.isTracked = false;
+            this.isActive = false;
+            this.isInteractive = false;
+            this.isPressed = false;
+            this.isPrimaryHandOfUser = false;
+            this.isPrimaryUser = false;
+            this.isInGripInteraction = false;
+            this.captured = null;
+            this.x = 0.0;
+            this.y = 0.0;
+            this.pressExtent = 0.0;
+            this.rawX = 0.0;
+            this.rawY = 0.0;
+            this.rawZ = 0.0;
+            this.originalHandPointer = null;
+            this.updated = false;
+
+            //////////////////////////////////////////////////////////////
+            // Public HandPointer methods
+            
+            // Get all DOM elements that this hand pointer has entered.
+            //     .getEnteredElements()
+            this.getEnteredElements = function () {
+                var key = getHandPointerKey(this.trackingId, this.handType);
+                var data = internalHandPointerData[key];
+
+                if (data == null) {
+                    return [];
+                }
+
+                return data.enteredElements;
+            };
+
+            // Determine whether this hand pointer is over the specified DOM element.
+            //     .getIsOver( element )
+            //
+            // element: DOM element against which to check hand pointer position.
+            this.getIsOver = function (element) {
+                // Iterate over cached elements
+                var enteredElements = this.getEnteredElements();
+
+                for (var iElement = 0; iElement < enteredElements.length; ++iElement) {
+                    if (enteredElements[iElement] === element) {
+                        return true;
+                    }
+                }
+
+                return false;
+            };
+            
+            // Create a capture association between this hand pointer and the specified DOM
+            // element. This means that event handlers associated with element will receive
+            // events triggered by this hand pointer even if hand pointer is not directly
+            // over element.
+            //     .capture( element )
+            //
+            // element: DOM element capturing hand pointer.
+            //          If null, this hand pointer stops being captured and resumes normal
+            //          event routing behavior.
+            this.capture = function (element) {
+                return captureHandPointer(this, element);
+            };
+            
+            // Determine whether this hand pointer corresponds to the primary hand of the
+            // primary user.
+            //     .getIsPrimaryHandOfPrimaryUser()
+            this.getIsPrimaryHandOfPrimaryUser = function () {
+                return this.isPrimaryUser && this.isPrimaryHandOfUser;
+            };
+            
+            // Update this hand pointer's properties from the specified hand pointer received
+            // from the interaction stream.
+            //     .update( streamHandPointer )
+            this.update = function (streamHandPointer) {
+                // save the stream hand pointer in case it has additional properties
+                // that clients might be interested in
+                this.originalHandPointer = streamHandPointer;
+                
+                this.updated = true;
+                
+                this.timestampOfLastUpdate = streamHandPointer.timestampOfLastUpdate;
+                this.handEventType = streamHandPointer.handEventType;
+
+                var pressedChanged = this.isPressed != streamHandPointer.isPressed;
+                this.isPressed = streamHandPointer.isPressed;
+
+                this.isTracked = streamHandPointer.isTracked;
+                this.isActive = streamHandPointer.isActive;
+                this.isInteractive = streamHandPointer.isInteractive;
+
+                var primaryHandOfPrimaryUserChanged = this.getIsPrimaryHandOfPrimaryUser() != (streamHandPointer.isPrimaryHandOfUser && streamHandPointer.isPrimaryUser);
+                this.isPrimaryHandOfUser = streamHandPointer.isPrimaryHandOfUser;
+                this.isPrimaryUser = streamHandPointer.isPrimaryUser;
+
+                var newPosition = interactionZoneToWindow({ "x": streamHandPointer.x, "y": streamHandPointer.y });
+                var positionChanged = !areUserInterfaceValuesClose(newPosition.x, this.x) ||
+                        !areUserInterfaceValuesClose(newPosition.y, this.y) ||
+                        !areUserInterfaceValuesClose(streamHandPointer.pressExtent, this.pressExtent);
+                this.x = newPosition.x;
+                this.y = newPosition.y;
+                this.pressExtent = streamHandPointer.pressExtent;
+
+                this.rawX = streamHandPointer.rawX;
+                this.rawY = streamHandPointer.rawY;
+                this.rawZ = streamHandPointer.rawZ;
+
+                return {
+                    "pressedChanged": pressedChanged,
+                    "primaryHandOfPrimaryUserChanged": primaryHandOfPrimaryUserChanged,
+                    "positionChanged": positionChanged
+                };
+            };
+        }
+        
+        //////////////////////////////////////////////////////////////
+        // ImageWorkerManager object constructor
+        //     new ImageWorkerManager()
+        //
+        // Remarks
+        // Manages background thread work related to processing image
+        // stream frames to get them ready to be displayed in a canvas
+        // element.
+        function ImageWorkerManager() {
+            
+            //////////////////////////////////////////////////////////////
+            // ImageMetadata object constructor
+            function ImageMetadata(imageCanvas) {
+                this.isProcessing = false;
+                this.canvas = imageCanvas;
+                this.width = 0;
+                this.height = 0;
+            }
+
+            //////////////////////////////////////////////////////////////
+            // Private ImageWorkerManager properties
+            var workerThread = null;
+            var imageMetadataMap = {};
+            
+            //////////////////////////////////////////////////////////////
+            // Private ImageWorkerManager methods
+            function ensureInitialized() {
+                // Lazy-initialize web worker thread
+                if (workerThread == null) {
+                    workerThread = new Worker(scriptRootURIPath + "KinectWorker-1.8.0.js");
+                    workerThread.addEventListener("message", function (event) {
+                        var imageName = event.data.imageName;
+                        if (!imageMetadataMap.hasOwnProperty(imageName)) {
+                            return;
+                        }
+                        var metadata = imageMetadataMap[imageName];
+                        
+                        switch (event.data.message) {
+                            case "imageReady":
+                                // Put ready image data in associated canvas
+                                var canvasContext = metadata.canvas.getContext("2d");
+                                canvasContext.putImageData(event.data.imageData, 0, 0);
+                                metadata.isProcessing = false;
+                                break;
+                                
+                            case "notProcessed":
+                                metadata.isProcessing = false;
+                                break;
+                        }
+                    });
+                }
+            }
+
+            //////////////////////////////////////////////////////////////
+            // Public ImageWorkerManager methods
+            
+            // Send named image data to be processed by worker thread
+            //     .processImageData(imageName, imageBuffer, width, height)
+            //
+            // imageName: Name of image to process
+            // imageBuffer: ArrayBuffer containing image data
+            // width: width of image corresponding to imageBuffer data
+            // height: height of image corresponding to imageBuffer data
+            this.processImageData = function (imageName, imageBuffer, width, height) {
+                ensureInitialized();
+                
+                if (!imageMetadataMap.hasOwnProperty(imageName)) {
+                    // We're not tracking this image, so no work to do
+                    return;
+                }
+                var metadata = imageMetadataMap[imageName];
+                
+                if (metadata.isProcessing || (width <= 0) || (height <= 0)) {
+                    // Don't send more data to worker thread when we are in the middle
+                    // of processing data already.
+                    // Also, Only do work if image data to process is of the expected size
+                    return;
+                }
+                
+                metadata.isProcessing = true;
+
+                if ((width != metadata.width) || (height != metadata.height)) {
+                    // Whenever the image width or height changes, update image tracking metadata
+                    // and canvas ImageData associated with worker thread
+                    
+                    var canvasContext = metadata.canvas.getContext("2d");
+                    var imageData = canvasContext.createImageData(width, height);
+                    metadata.width = width;
+                    metadata.height = height;
+                    metadata.canvas.width = width;
+                    metadata.canvas.height = height;
+
+                    workerThread.postMessage({ "message": "setImageData", "imageName": imageName, "imageData": imageData });
+                }
+                
+                workerThread.postMessage({ "message": "processImageData", "imageName": imageName, "imageBuffer": imageBuffer });
+            };
+
+            // Associate specified image name with canvas ImageData object for future usage by
+            // worker thread
+            //     .setImageData(imageName, canvas)
+            //
+            // imageName: Name of image stream to associate with ImageData object
+            // canvas: Canvas to bind to user viewer stream
+            this.setImageData = function (imageName, canvas) {
+                ensureInitialized();
+
+                if (canvas != null) {
+                    var metadata = new ImageMetadata(canvas);
+                    imageMetadataMap[imageName] = metadata;
+                } else if (imageMetadataMap.hasOwnProperty(imageName)) {
+                    // If specified canvas is null but we're already tracking image data,
+                    // remove metadata associated with this image.
+                    delete imageMetadataMap[imageName];
+                }
+            };
+        }
+        
+        //////////////////////////////////////////////////////////////
+        // Private KinectUIAdapter constants
+        var LAZYRELEASE_RADIUS = 0.3;
+        var LAZYRELEASE_YCUTOFF = LAZYRELEASE_RADIUS / 3;
+        
+        //////////////////////////////////////////////////////////////
+        // Private KinectUIAdapter properties
+        var uiAdapter = this;
+        var isInInteractionFrame = false;
+        var isClearRequestPending = false;
+        var handPointerEventsEnabled = true;
+        // Property names of internalHandPointerData are keys that encode user tracking id and hand type.
+        // Property values are JSON objects with "handPointer" and "enteredElements" properties.
+        var internalHandPointerData = {};
+        var excludedElements = [];
+        var eventHandlers = {};
+        var bindableStreamNames = {};
+        bindableStreamNames[Kinect.USERVIEWER_STREAM_NAME] = true;
+        bindableStreamNames[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = true;
+        var imageWorkerManager = new ImageWorkerManager();
+
+        //////////////////////////////////////////////////////////////
+        // Private KinectUIAdapter methods
+        function interactionZoneToElement(point, element) {
+            return { "x": point.x * element.offsetWidth, "y": point.y * element.offsetHeight };
+        }
+
+        function elementToInteractionZone(point, element) {
+            return { "x": point.x / element.offsetWidth, "y": point.y / element.offsetHeight };
+        }
+
+        function interactionZoneToWindow(point) {
+            return { "x": point.x * window.innerWidth, "y": point.y * window.innerHeight };
+        }
+
+        function windowToInteractionZone(point) {
+            return { "x": point.x / window.innerWidth, "y": point.y / window.innerHeight };
+        }
+
+        function areUserInterfaceValuesClose(a, b) {
+            return Math.abs(a - b) < 0.5;
+        }
+
+        function getPressTargetPoint(element) {
+            // Hardcoded to always return the center point
+            return { "x": 0.5, "y": 0.5 };
+        }
+
+        function getHandPointerKey(trackingId, handType) {
+            return handType + trackingId;
+        }
+
+        function createInteractionEvent(eventName, detail) {
+            var event = document.createEvent("CustomEvent");
+            var canBubble = true;
+            var cancelable = false;
+
+            switch (eventName) {
+                case uiController.HANDPOINTER_ENTER:
+                case uiController.HANDPOINTER_LEAVE:
+                    canBubble = false;
+                    break;
+            }
+
+            event.initCustomEvent(eventName, canBubble, cancelable, detail);
+
+            return event;
+        }
+
+        function raiseHandPointerEvent(element, eventName, handPointer) {
+            if (!handPointerEventsEnabled) {
+                return;
+            }
+            
+            var event = createInteractionEvent(eventName, { "handPointer": handPointer });
+            element.dispatchEvent(event);
+        }
+        
+        function raiseGlobalUIEvent(eventName, eventData) {
+            if (!handPointerEventsEnabled || !eventHandlers.hasOwnProperty(eventName)) {
+                // Events are disabled, or there are no event handlers registered for event.
+                return;
+            }
+
+            var handlerArray = eventHandlers[eventName];
+            for (var i = 0; i < handlerArray.length; ++i) {
+                handlerArray[i](eventData);
+            }
+        }
+        
+        function switchCapture(handPointer, oldElement, newElement) {
+            handPointer.captured = newElement;
+
+            if (oldElement != null) {
+                raiseHandPointerEvent(oldElement, uiController.HANDPOINTER_LOSTCAPTURE, handPointer);
+            }
+
+            if (newElement != null) {
+                raiseHandPointerEvent(newElement, uiController.HANDPOINTER_GOTCAPTURE, handPointer);
+            }
+        }
+
+        function captureHandPointer(handPointer, element) {
+            var id = getHandPointerKey(handPointer.trackingId, handPointer.handType);
+
+            if (!internalHandPointerData.hasOwnProperty(id)) {
+                // We're not already tracking a hand pointer with the same id.
+                return false;
+            }
+
+            var handPointerData = internalHandPointerData[id];
+            var checkHandPointer = handPointerData.handPointer;
+            if (checkHandPointer !== handPointer) {
+                // The hand pointer we're tracking is not the same one that was handed in.
+                // Caller may have a stale hand pointer instance.
+                return false;
+            }
+
+            if (element != null && handPointer.captured != null) {
+                // Request wasn't to clear capture and some other element already has this
+                // HandPointer captured.
+                return false;
+            }
+
+            switchCapture(handPointer, handPointer.captured, element);
+            return true;
+        }
+
+        // Get the DOM element branch (parent chain), rooted in body element,
+        // that includes the specified leaf element.
+        // If leafElement is null, the returned branch will be empty
+        function getDOMBranch(leafElement) {
+            var scanStop = document.body.parentNode;
+            var branch = [];
+
+            for (var scan = leafElement; (scan != null) && (scan != scanStop) && (scan != document) ; scan = scan.parentNode) {
+                branch.push(scan);
+            }
+
+            return branch;
+        }
+
+        function doEnterLeaveNotifications(handPointer, primaryHandOfPrimaryUserChanged, oldEnteredElements, newEnteredElements) {
+            var isPrimaryHandOfPrimaryUser = handPointer.getIsPrimaryHandOfPrimaryUser();
+            var wasPrimaryHandOfPrimaryUser = primaryHandOfPrimaryUserChanged ? !isPrimaryHandOfPrimaryUser : isPrimaryHandOfPrimaryUser;
+
+            // find common elements between old and new set
+            // We take advantage of the fact that later elements in the array contain earlier
+            // elements, so they will remain in the same state for a longer time.
+            var firstOldMatch = oldEnteredElements.length;
+            var firstNewMatch = newEnteredElements.length;
+            while ((firstOldMatch > 0) &&
+                   (firstNewMatch > 0) &&
+                   (oldEnteredElements[firstOldMatch - 1] === newEnteredElements[firstNewMatch - 1])) {
+                --firstOldMatch;
+                --firstNewMatch;
+            }
+
+            // Tell old entered elements that are not entered anymore that we have left them
+            for (var iLeft = 0; iLeft < oldEnteredElements.length; ++iLeft) {
+                var leftElement = oldEnteredElements[iLeft];
+
+                if (iLeft < firstOldMatch) {
+                    // This element is not one of the common elements, so we have left it
+
+                    // If we were or still are the HandPointer for the primary user's
+                    // primary hand then clear out the "IsPrimaryHandPointerOver" property.
+                    if (wasPrimaryHandOfPrimaryUser) {
+                        uiController.setIsPrimaryHandPointerOver(leftElement, false);
+                    }
+
+                    // Tell this element that this hand pointer has left
+                    raiseHandPointerEvent(leftElement, uiController.HANDPOINTER_LEAVE, handPointer);
+                } else {
+                    // This is one of the common elements
+                    if (wasPrimaryHandOfPrimaryUser && !isPrimaryHandOfPrimaryUser) {
+                        // Hand pointer didn't leave the element but it is no longer the primary
+                        uiController.setIsPrimaryHandPointerOver(leftElement, false);
+                    }
+                }
+            }
+
+            // Tell new entered elements that were not previously entered we are now over them
+            for (var iEntered = 0; iEntered < newEnteredElements.length; ++iEntered) {
+                var enteredElement = newEnteredElements[iEntered];
+
+                if (iEntered < firstNewMatch) {
+                    // This element is not one of the common elements, so we have entered it
+
+                    // If we are the HandPointer for the primary user's primary hand then
+                    // set the "IsPrimaryHandPointerOver" property.
+                    if (isPrimaryHandOfPrimaryUser) {
+                        uiController.setIsPrimaryHandPointerOver(enteredElement, true);
+                    }
+
+                    // Tell this element that this hand pointer has left
+                    raiseHandPointerEvent(enteredElement, uiController.HANDPOINTER_ENTER, handPointer);
+                } else {
+                    // This is one of the common elements
+                    if (!wasPrimaryHandOfPrimaryUser && isPrimaryHandOfPrimaryUser) {
+                        // Hand pointer was already in this element but it became the primary
+                        uiController.setIsPrimaryHandPointerOver(enteredElement, true);
+                    }
+                }
+            }
+        }
+        
+        function beginInteractionFrame() {
+            if (isInInteractionFrame) {
+                console.log("Call to beginInteractionFrame was made without corresponding call to endInteractionFrame");
+                return;
+            }
+
+            isInInteractionFrame = true;
+            isClearRequestPending = false;
+
+            for (var handPointerKey in internalHandPointerData) {
+                var handPointer = internalHandPointerData[handPointerKey].handPointer;
+                handPointer.updated = false;
+            }
+        }
+
+        function handleHandPointerChanges(handPointerData, pressedChanged, positionChanged, primaryHandOfPrimaryUserChanged, removed) {
+            var doPress = false;
+            var doRelease = false;
+            var doMove = false;
+            var doLostCapture = false;
+            var doGrip = false;
+            var doGripRelease = false;
+            var handPointer = handPointerData.handPointer;
+
+            if (removed) {
+                // Deny the existence of this hand pointer
+                doRelease = handPointer.isPressed;
+                doLostCapture = handPointer.captured != null;
+            } else {
+                if (pressedChanged) {
+                    doPress = handPointer.isPressed;
+                    doRelease = !handPointer.isPressed;
+                }
+
+                if (positionChanged) {
+                    doMove = true;
+                }
+
+                doGrip = handPointer.handEventType.toLowerCase() == "grip";
+                doGripRelease = handPointer.handEventType.toLowerCase() == "griprelease";
+            }
+
+            if (doLostCapture) {
+                switchCapture(handPointer, handPointer.captured, null);
+            }
+
+            var targetElement = handPointer.captured;
+            if (targetElement == null) {
+                targetElement = hitTest(handPointer.x, handPointer.y);
+            }
+
+            // Update internal enter/leave state
+            var oldEnteredElements = handPointerData.enteredElements;
+            var newEnteredElements = getDOMBranch(removed ? null : targetElement);
+            handPointerData.enteredElements = newEnteredElements;
+
+            // See if this hand pointer is participating in a grip-initiated
+            // interaction.
+            var newIsInGripInteraction = false;
+            if (targetElement != null) {
+                var result = raiseHandPointerEvent(targetElement, uiController.QUERY_INTERACTION_STATUS, handPointer);
+
+                if ((result != null) && result.isInGripInteraction) {
+                    newIsInGripInteraction = true;
+                }
+            }
+
+            handPointer.isInGripInteraction = newIsInGripInteraction;
+
+            //// After this point there should be no more changes to the internal
+            //// state of the handPointers.  We don't want event handlers calling us
+            //// when our internal state is inconsistent.
+
+            doEnterLeaveNotifications(handPointer, primaryHandOfPrimaryUserChanged, oldEnteredElements, newEnteredElements);
+
+            if (targetElement == null) {
+                return;
+            }
+
+            if (doGrip) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_GRIP, handPointer);
+            }
+
+            if (doGripRelease) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_GRIPRELEASE, handPointer);
+            }
+
+            if (doPress) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_PRESS, handPointer);
+            }
+
+            if (doMove) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_MOVE, handPointer);
+            }
+
+            if (doRelease) {
+                raiseHandPointerEvent(targetElement, uiController.HANDPOINTER_PRESSRELEASE, handPointer);
+            }
+        }
+
+        function handleHandPointerData(interactionStreamHandPointers) {
+            if (!isInInteractionFrame) {
+                console.log("Call to handleHandPointerData was made without call to beginInteractionFrame");
+                return;
+            }
+
+            if (isClearRequestPending) {
+                // We don't care about new hand pointer data if client requested to clear
+                // all hand pointers while in the middle of interaction frame processing.
+                return;
+            }
+
+            for (var iData = 0; iData < interactionStreamHandPointers.length; ++iData) {
+                var streamHandPointer = interactionStreamHandPointers[iData];
+                var id = getHandPointerKey(streamHandPointer.trackingId, streamHandPointer.handType);
+
+                var handPointerData;
+                var handPointer;
+                if (internalHandPointerData.hasOwnProperty(id)) {
+                    handPointerData = internalHandPointerData[id];
+                    handPointer = handPointerData.handPointer;
+                } else {
+                    handPointer = new HandPointer(streamHandPointer.trackingId, streamHandPointer.playerIndex, streamHandPointer.handType);
+
+                    handPointerData = { "handPointer": handPointer, "enteredElements": [] };
+                    internalHandPointerData[id] = handPointerData;
+                }
+
+                var result = handPointer.update(streamHandPointer);
+                handleHandPointerChanges(handPointerData, result.pressedChanged, result.positionChanged, result.primaryHandOfPrimaryUserChanged, false);
+            }
+        }
+
+        function endInteractionFrame() {
+            if (!isInInteractionFrame) {
+                console.log("Call to endInteractionFrame was made without call to beginInteractionFrame");
+                return;
+            }
+
+            removeStaleHandPointers();
+
+            isClearRequestPending = false;
+            isInInteractionFrame = false;
+        }
+
+        function removeStaleHandPointers() {
+            var nonUpdatedHandpointers = [];
+
+            for (var handPointerKey in internalHandPointerData) {
+                // If we need to stop tracking this hand pointer
+                var handPointer = internalHandPointerData[handPointerKey].handPointer;
+                if (isClearRequestPending || !handPointer.updated) {
+                    nonUpdatedHandpointers.push(handPointerKey);
+                }
+            }
+
+            for (var i = 0; i < nonUpdatedHandpointers.length; ++i) {
+                var handPointerKey = nonUpdatedHandpointers[i];
+                var handPointerData = internalHandPointerData[handPointerKey];
+                var handPointer = handPointerData.handPointer;
+                var pressedChanged = handPointer.isPressed;
+                var positionChanged = false;
+                var primaryHandOfPrimaryUserChanged = handPointer.getIsPrimaryHandOfPrimaryUser();
+                var removed = true;
+
+                handPointer.isTracked = false;
+                handPointer.isActive = false;
+                handPointer.isInteractive = false;
+                handPointer.isPressed = false;
+                handPointer.isPrimaryUser = false;
+                handPointer.isPrimaryHandOfUser = false;
+
+                handleHandPointerChanges(handPointerData, pressedChanged, positionChanged, primaryHandOfPrimaryUserChanged, removed);
+
+                delete internalHandPointerData[handPointerKey];
+            }
+        }
+        
+        function updatePublicHandPointers() {
+            var iHandPointer = 0;
+
+            for (var handPointerKey in internalHandPointerData) {
+                var handPointer = internalHandPointerData[handPointerKey].handPointer;
+
+                if (handPointer.isTracked) {
+                    if (uiAdapter.handPointers.length > iHandPointer) {
+                        uiAdapter.handPointers[iHandPointer] = handPointer;
+                    } else {
+                        uiAdapter.handPointers.push(handPointer);
+                    }
+
+                    ++iHandPointer;
+                }
+            }
+
+            // Truncate the length of the array to the number of valid elements
+            uiAdapter.handPointers.length = iHandPointer;
+
+            raiseGlobalUIEvent(uiController.HANDPOINTERS_UPDATED, uiAdapter.handPointers);
+        }
+        
+        function hitTest(x, y) {
+            // Ensure excluded elements are hidden before performing hit test
+            var displayProp = "display";
+            var displayValues = [];
+            for (var i = 0; i < excludedElements.length; ++i) {
+                var element = excludedElements[i];
+                displayValues.push(element.style[displayProp]);
+                element.style[displayProp] = "none";
+            }
+
+            var hitElement = document.elementFromPoint(x, y);
+
+            // Restore visibility to excluded elements
+            for (var i = 0; i < excludedElements.length; ++i) {
+                excludedElements[i].style[displayProp] = displayValues[i];
+            }
+
+            return hitElement;
+        }
+        
+        function getElementId(element) {
+            // First check if we've already set the kinect element ID.
+            var id = jQuery.data(element, kinectIdPrefix);
+            if (id != null) {
+                return id;
+            }
+
+            // If not, fall back to regular element id, if present
+            id = element.id;
+            
+            if (id.trim() == "") {
+                // If there is no id on element, assign one ourselves
+                id = kinectIdPrefix + (++lastAssignedControlId);
+            }
+            
+            // Remember assigned id
+            jQuery.data(element, kinectIdPrefix, id);
+            return id;
+        }
+        
+        function setCapture(element) {
+            // Not all browsers support mouse capture functionality
+            if (typeof element.setCapture == "function") {
+                element.setCapture(true);
+                return true;
+            }
+
+            return false;
+        }
+
+        function releaseCapture() {
+            // Not all browsers support mouse capture functionality
+            if (typeof document.releaseCapture == "function") {
+                document.releaseCapture();
+                return true;
+            }
+
+            return false;
+        }
+
+        function promoteToButton(element) {
+            var content = element.innerHTML;
+
+            if (element.children.length <= 0) {
+                // If element has no children, wrap content in an element that will perform
+                // default text alignment
+                content = "<span class='kinect-button-text'>" + content + "</span>";
+            }
+
+            // Wrap all content in a "kinect-button-surface" div element to allow button
+            // shrinking and expanding as user hovers over or presses button
+            element.innerHTML = "<div class='kinect-button-surface'>" + content + "</div>";
+
+            uiController.setIsPressTarget(element, true);
+            var capturedHandPointer = null;
+            var pressedElement = null;
+            var isMouseOver = false;
+            var isMouseCaptured = false;
+            var removeCaptureEvents = false;
+
+            var releaseButtonCapture = function(event) {
+                releaseCapture();
+                isMouseCaptured = false;
+
+                if (removeCaptureEvents) {
+                    $(event.target).off("mousemove");
+                }
+            };
+
+            $(element).on({
+                // respond to hand pointer events
+                handPointerPress: function (event) {
+                    var handPointer = event.originalEvent.detail.handPointer;
+                    if (capturedHandPointer == null && handPointer.getIsPrimaryHandOfPrimaryUser()) {
+                        handPointer.capture(event.currentTarget);
+                        event.stopPropagation();
+                    }
+                },
+                handPointerGotCapture: function (event) {
+                    var handPointer = event.originalEvent.detail.handPointer;
+                    if (capturedHandPointer == null) {
+                        capturedHandPointer = handPointer;
+                        pressedElement = handPointer.captured;
+                        $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
+                        event.stopPropagation();
+                    }
+                },
+                handPointerPressRelease: function (event) {
+                    var handPointer = event.originalEvent.detail.handPointer;
+                    if (capturedHandPointer == handPointer) {
+                        var captured = handPointer.captured;
+                        handPointer.capture(null);
+
+                        if (captured == event.currentTarget) {
+                            // Trigger click on press release if release point is close enough
+                            // to around the top half of button, or if it's anywhere below
+                            // button. I.e.: accept a "lazy-press" as a real press.
+                            
+                            var box = captured.getBoundingClientRect();
+                            var insideButton = false;
+                            if ((box.left <= handPointer.x) && (handPointer.x <= box.right) &&
+                                (box.top <= handPointer.y) && (handPointer.y <= box.bottom)) {
+                                insideButton = true;
+                            }
+                            
+                            // Map hand pointer to a coordinate space around center of button
+                            // and convert to be relative to window size.
+                            var relativePoint = windowToInteractionZone({
+                                "x": handPointer.x - ((box.left + box.right) / 2),
+                                "y": handPointer.y - ((box.top + box.bottom) / 2)
+                            });
+                            var isWithinLazyReleaseArea = true;
+                            if (relativePoint.y < LAZYRELEASE_YCUTOFF) {
+                                isWithinLazyReleaseArea =
+                                    Math.sqrt((relativePoint.x * relativePoint.x) + (relativePoint.y * relativePoint.y)) < LAZYRELEASE_RADIUS;
+                            }
+                            
+                            if (insideButton || isWithinLazyReleaseArea) {
+                                $(element).click();
+                            }
+                            
+                            event.stopPropagation();
+                        }
+                    }
+                },
+                handPointerLostCapture: function (event) {
+                    var handPointer = event.originalEvent.detail.handPointer;
+                    if (capturedHandPointer == handPointer) {
+                        capturedHandPointer = null;
+                        pressedElement = null;
+                        $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
+                        event.stopPropagation();
+                    }
+                },
+                handPointerEnter: function (event) {
+                    var target = event.currentTarget;
+                    if (KinectUI.getIsPrimaryHandPointerOver(target)) {
+                        $(element).addClass(uiController.BUTTON_HOVER_CLASS);
+                    }
+                },
+                handPointerLeave: function (event) {
+                    var target = event.currentTarget;
+                    if (!KinectUI.getIsPrimaryHandPointerOver(target)) {
+                        $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
+                    }
+                },
+                // respond to mouse events in a similar way to hand pointer events
+                // Note: We use button-hover class rather than :hover pseudo-class because
+                //       :hover does not play well with our button-pressed class.
+                mouseenter: function(event) {
+                    isMouseOver = true;
+                    $(element).addClass(uiController.BUTTON_HOVER_CLASS);
+                    if (pressedElement != null) {
+                        $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
+                    }
+                },
+                mouseleave: function (event) {
+                    isMouseOver = false;
+                    if (!isMouseCaptured || (pressedElement != null)) {
+                        $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
+                        pressedElement = null;
+                    }
+                    $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
+                },
+                mousedown: function (event) {
+                    if (event.button == 0) {
+                        pressedElement = event.target;
+                        if (setCapture(event.target)) {
+                            isMouseCaptured = true;
+                            
+                            // Different browsers have slightly different behaviors related to which element
+                            // will be considered the event target, and how mouseenter and mouseleave events
+                            // are sent in case of capture, so cover all bases.
+                            if (event.target != event.currentTarget) {
+                                removeCaptureEvents = true;
+                                $(event.target).mousemove(function (moveEvent) {
+                                    var box = element.getBoundingClientRect();
+                                    var insideBox = (box.left <= moveEvent.clientX) && (moveEvent.clientX <= box.right) &&
+                                        (box.top <= moveEvent.clientY) && (moveEvent.clientY <= box.bottom);
+                                    if (!insideBox && isMouseCaptured) {
+                                        releaseButtonCapture(moveEvent);
+                                    }
+                                });
+                            }
+                        }
+                        $(element).addClass(uiController.BUTTON_PRESSED_CLASS);
+
+                        event.stopPropagation();
+                    }
+                },
+                mouseup: function (event) {
+                    if (event.button == 0) {
+                        var fakeClick = false;
+                        if ((pressedElement != null) && !isMouseCaptured && (pressedElement != event.target)) {
+                            // If this browser doesn't support capture and the event target
+                            // during mouse up does not match exactly the event target during
+                            // mouse down then browser will not automatically trigger a click
+                            // event, so we will trigger one ourselves because we know that the
+                            // same logical Kinect button received a mouse down and mouse up.
+                            //
+                            // This condition can happen because of shrink and expand
+                            // animations for Kinect button hover and press.
+                            fakeClick = true;
+                        }
+                        
+                        pressedElement = null;
+                        if (isMouseCaptured) {
+                            releaseButtonCapture(event);
+                        }
+                        $(element).removeClass(uiController.BUTTON_PRESSED_CLASS);
+                        if (!isMouseOver) {
+                            $(element).removeClass(uiController.BUTTON_HOVER_CLASS);
+                        }
+                        
+                        if (fakeClick) {
+                            $(element).click();
+                        }
+
+                        event.stopPropagation();
+                    }
+                }
+            });
+        }
+        
+        //////////////////////////////////////////////////////////////
+        // Public KinectUIAdapter properties
+        this.handPointers = [];
+        
+        //////////////////////////////////////////////////////////////
+        // Public KinectUIAdapter methods
+
+        // Function called back when a sensor stream frame is ready to be processed.
+        //     .streamFrameHandler(streamFrame)
+        //
+        // streamFrame: stream frame ready to be processed
+        //
+        // Remarks
+        // Processes interaction frames and ignores all other kinds of stream frames.
+        this.streamFrameHandler = function (streamFrame) {
+            if (typeof streamFrame != "object") {
+                throw new Error("Frame must be an object");
+            }
+            
+            if (streamFrame == null) {
+                // Ignore null frames
+                return;
+            }
+
+            var streamName = streamFrame.stream;
+            switch (streamName) {
+                case "interaction":
+                    beginInteractionFrame();
+
+                    if (streamFrame.handPointers != null) {
+                        handleHandPointerData(streamFrame.handPointers);
+                    }
+
+                    endInteractionFrame();
+
+                    updatePublicHandPointers();
+                    break;
+                    
+                default:
+                    if (bindableStreamNames[streamName]) {
+                        // If this is one of the bindable stream names
+                        imageWorkerManager.processImageData(streamName, streamFrame.buffer, streamFrame.width, streamFrame.height);
+                    }
+                    break;
+            }
+        };
+        
+        // Resets the hand pointer array to its initial state (zero hand pointers tracked).
+        //     .clearHandPointers()
+        this.clearHandPointers = function () {
+            if (!isInInteractionFrame) {
+                // If we're not already processing an interaction frame, we fake
+                // an empty frame so that all hand pointers get cleared out.
+                beginInteractionFrame();
+                endInteractionFrame();
+                updatePublicHandPointers();
+            } else {
+                // If we're in the middle of processing an interaction frame, we
+                // can't modify all of our data structures immediately, but need to
+                // remember to do so.
+                isClearRequestPending = true;
+            }
+        };
+        
+        // Function called back when a sensor's interaction stream needs interaction
+        // information in order to adjust a hand pointer position relative to UI.
+        //     .hitTestHandler(trackingId, handType, xPos, yPos)
+        //
+        // trackingId: The skeleton tracking ID for which interaction information is
+        //             being retrieved.
+        // handType: "left" or "right" to represent hand type for which interaction
+        //           information is being retrieved.
+        // xPos: X-coordinate of UI location for which interaction information is being
+        //       retrieved. 0.0 corresponds to left edge of interaction region and 1.0
+        //       corresponds to right edge of interaction region.
+        // yPos: Y-coordinate of UI location for which interaction information is being
+        //       retrieved. 0.0 corresponds to top edge of interaction region and 1.0
+        //       corresponds to bottom edge of interaction region.
+        this.hitTestHandler = function (trackingId, handType, xPos, yPos) {
+            if (typeof trackingId != "number") {
+                throw new Error("First parameter must be a number");
+            }
+            if (typeof handType != "string") {
+                throw new Error("Second parameter must be a string");
+            }
+
+            var interactionInfo = { "isPressTarget": false, "isGripTarget": false };
+
+            // First scale coordinates to be relative to full browser window
+            var windowPos = interactionZoneToWindow({ "x": xPos, "y": yPos });
+            var handPointerId = getHandPointerKey(trackingId, handType);
+            var capturedElement = internalHandPointerData.hasOwnProperty(handPointerId) ? internalHandPointerData[handPointerId].handPointer.captured : null;
+            var targetElement = (capturedElement != null) ? capturedElement : hitTest(windowPos.x, windowPos.y);
+
+            if (targetElement != null) {
+                // Walk up the tree and try to find a grip target and/or a press target
+                for (var searchElement = targetElement;
+                                searchElement != null && searchElement != document &&
+                                (!interactionInfo.isGripTarget || !interactionInfo.isPressTarget) ;
+                                searchElement = searchElement.parentNode) {
+
+                    if (!interactionInfo.isPressTarget) {
+                        if (uiController.getIsPressTarget(searchElement)) {
+                            interactionInfo.isPressTarget = true;
+                            interactionInfo.pressTargetControlId = getElementId(searchElement);
+
+                            // Get the press target point in client coordinates relative to search element
+                            var pressTargetPoint = interactionZoneToElement(getPressTargetPoint(searchElement), searchElement);
+                            var boundingRect = searchElement.getBoundingClientRect();
+
+                            // Now adjust press target point coordinate to be relative to viewport
+                            pressTargetPoint.x += boundingRect.left;
+                            pressTargetPoint.y += boundingRect.top;
+
+                            // Now scale press attraction point back to interaction zone coordinates
+                            var adjustedPoint = windowToInteractionZone(pressTargetPoint);
+                            interactionInfo.pressAttractionPointX = adjustedPoint.x;
+                            interactionInfo.pressAttractionPointY = adjustedPoint.y;
+                        }
+                    }
+
+                    if (!interactionInfo.isGripTarget) {
+                        if (uiController.getIsGripTarget(searchElement)) {
+                            interactionInfo.isGripTarget = true;
+                        }
+                    }
+                }
+            }
+            
+            return interactionInfo;
+        };
+        
+        // Adds an element that should be excluded from hit testing (both for press adjustment
+        // and for UI message routing).
+        //     .addHitTestExclusion( element )
+        //
+        // element: Element to exlude from hit testing.
+        this.addHitTestExclusion = function (element) {
+            if (element == null) {
+                console.log('Tried to add a hit test exclusion for a null element.');
+                return;
+            }
+            
+            excludedElements.push(element);
+        };
+
+        // Stops excluding an element from hit testing (both for press adjustment and for UI
+        // message routing).
+        //     .removeHitTestExclusion( element )
+        //
+        // element: Element to stop exluding from hit testing.
+        this.removeHitTestExclusion = function (element) {
+            if (element == null) {
+                console.log('Tried to remove a hit test exclusion for a null element.');
+                return;
+            }
+            
+            var index = excludedElements.indexOf(element);
+            if (index >= 0) {
+                excludedElements.splice(index, 1);
+            }
+        };
+
+        // Bind the specified canvas element with the specified image stream
+        //     .bindStreamToCanvas( streamName, canvas )
+        //
+        // streamName: name of stream to bind to canvas element. Must be one of the supported
+        //             image stream names (e.g.: KinectUI.USERVIEWER_STREAM_NAME and
+        //             KinectUI.BACKGROUNDREMOVAL_STREAM_NAME)
+        // canvas: Canvas to bind to user viewer stream
+        //
+        // Remarks
+        // After binding a stream to a canvas, image data for that stream will
+        // be rendered into the canvas whenever a new stream frame arrives.
+        this.bindStreamToCanvas = function (streamName, canvas, width, height) {
+            if (!bindableStreamNames[streamName]) {
+                throw new Error("first parameter must be specified and must be one of the supported stream names");
+            }
+
+            if (!(canvas instanceof HTMLCanvasElement)) {
+                throw new Error("second parameter must be specified and must be a canvas element");
+            }
+
+            this.unbindStreamFromCanvas(streamName);
+
+            imageWorkerManager.setImageData(streamName, canvas);
+        };
+
+        // Unbind the specified image stream from previously bound canvas element, if any.
+        //     .unbindStreamFromCanvas(streamName)
+        //
+        // streamName: name of stream to unbind from its corresponding canvas element
+        this.unbindStreamFromCanvas = function(streamName) {
+            imageWorkerManager.setImageData(streamName, null);
+        };
+        
+        // Attach a new handler for a specified global UI event.
+        //     .on( eventName, handler(eventData) )] )
+        //
+        // eventName: Name of global event for which handler is being attached.
+        // handler: Callback function to be executed when a global UI event of the specified
+        //          name occurs. E.g.: when set of hand pointers has been updated (eventName =
+        //          "handPointersUpdated").
+        this.on = function (eventName, handler) {
+            if (typeof (eventName) != "string") {
+                throw new Error("first parameter must be specified and must be a string");
+            }
+            if (typeof (handler) != "function") {
+                throw new Error("second parameter must be specified and must be a function");
+            }
+
+            var handlerArray;
+            if (eventHandlers.hasOwnProperty(eventName)) {
+                handlerArray = eventHandlers[eventName];
+            } else {
+                handlerArray = [];
+                eventHandlers[eventName] = handlerArray;
+            }
+            
+            handlerArray.push(handler);
+        };
+
+        // Removes one (or all) global UI event handler(s) for specified global UI event.
+        //     .removeEventHandler( eventName, [handler(eventData)] )] )
+        //
+        // eventName: Name of global event for which handler is being removed.
+        // handler: Global UI event handler callback function to be removed.
+        //          If omitted, all event handlers for specified event are removed.
+        this.off = function (eventName, handler) {
+            if (typeof (handler) != "string") {
+                throw new Error("first parameter must be specified and must be a string");
+            }
+
+            var removeAll = false;
+            switch (typeof (handler)) {
+                case "undefined":
+                    removeAll = true;
+                    break;
+                case "function":
+                    break;
+
+                default:
+                    throw new Error("second parameter must either be a function or left unspecified");
+            }
+            
+            if (!eventHandlers.hasOwnProperty(eventName)) {
+                // If there are no event handlers associated with event, just return
+                return;
+            }
+
+            if (removeAll) {
+                eventHandlers[eventName] = [];
+            } else {
+                var index = eventHandlers[eventName].indexOf(handler);
+                if (index >= 0) {
+                    eventHandlers[eventName].slice(index, index + 1);
+                }
+            }
+        };
+        
+        // Configures the specified DOM element array to act as a Kinect UI button
+        //     .promoteButtons([elements])
+        //
+        // elements: DOM element array or HTMLCollection where each element will be configured
+        //           to act as a Kinect UI button.
+        //           If left unspecified, all document elements with class equal to
+        //           KinectUI.BUTTON_CLASS will be promoted to buttons.
+        this.promoteButtons = function (elements) {
+            var type = typeof elements;
+            var error = new Error("first parameter must be left unspecified or be an array equivalent");
+            switch (type) {
+                case "undefined":
+                    elements = document.getElementsByClassName(uiController.BUTTON_CLASS);
+                    break;
+                case "object":
+                    if (elements == null) {
+                        // If null, treat as if an empty array was specified
+                        return;
+                    }
+                    
+                    if (!("length" in elements)) {
+                        throw error;
+                    }
+                    break;
+                    
+                default:
+                    throw error;
+            }
+
+            for (var i = 0; i < elements.length; ++i) {
+                var element = elements[i];
+                if ((typeof element != "object") || (element.nodeType != Node.ELEMENT_NODE)) {
+                    throw new Error("each array element must be a DOM element");
+                }
+
+                promoteToButton(elements[i]);
+            }
+        };
+
+        // Creates the default cursor UI elements with associated behavior
+        //     .createDefaultCursor()
+        //
+        // Returns an object with the following properties/functions:
+        // - element: property referencing the DOM element that represents the cursor
+        // - hide(): function used to hide the cursor
+        // - show(): function used to show the cursor
+        this.createDefaultCursor = function() {
+            // First check if cursor element has already been created
+            var cursorElement = document.getElementById(uiController.CURSOR_ID);
+            if (cursorElement != null) {
+                return jQuery.data(cursorElement, uiController.CURSOR_ID);
+            }
+            //<path id="not-kinect-grip-hand-base" stroke-width="2.66667px" stroke-miterlimit="2.75" d="m 181.93858,119.37788 c -5.53253,-12.70786 -17.98221,-8.32935 -19.87611,-6.05884 -1.93242,2.33185 -2.55388,10.20311 -3.11979,7.43102 -0.52841,-2.58838 -1.58534,-6.48216 -3.05075,-8.33167 -3.71325,-4.68654 -18.26158,-5.14609 -20.28816,1.42556 -0.60793,-2.5391 -2.08123,9.29661 -2.71043,6.39601 -0.80661,-3.71845 -1.46647,-5.09336 -3.10202,-8.04206 -3.52367,-6.35277 -25.21241,-4.55749 -26.94562,0.8136 -2.23168,1.60831 -0.45609,3.96744 -2.20942,8.65039 -0.11175,0.29847 0.28587,-4.91744 -2.132876,-9.19244 -2.409535,-4.25872 -19.828184,-1.33427 -20.975736,0.12408 -6.966521,6.47431 -9.645445,17.93226 -6.313239,32.77615 -1.470297,3.68614 -7.438345,11.46835 -12.632726,14.31443 18.050822,-8.87437 10.347115,-23.76108 10.347115,-23.76108 1.654793,-11.75616 -15.575036,0.50533 -15.942531,0.54616 -6.318,6.317 -13.368249,17.51059 -10.003249,21.57959 0,0 12.442962,34.33433 25.103962,49.50633 16.952148,16.18215 37.23324,18.66434 60.51992,14.78272 32.68992,-5.44904 49.05986,-5.46272 56.41008,-41.64172 5.70646,-64.06554 -1.17616,-23.47973 -3.07842,-61.31823 z"></path>\
+            var showCursor = true;
+            cursorElement = document.createElement("div");
+            cursorElement.id = uiController.CURSOR_ID;
+            cursorElement.innerHTML = '\
+                <svg viewBox="0 0 250 250">\
+                    <defs>\
+                        <radialGradient id="kinect-pressing-gradient" r="150%">\
+                            <stop stop-color="#663085" offset="0.0%"></stop>\
+                            <stop stop-color="white" offset="100.0%" ></stop>\
+                        </radialGradient>\
+                        <radialGradient id="kinect-extended-gradient">\
+                            <stop stop-color="#01b3ff" offset="0.0%"></stop>\
+                            <stop stop-color="#04e5ff" offset="98.1%" ></stop>\
+                            <stop stop-color="#04e5ff" offset="99.2%" ></stop>\
+                            <stop stop-color="#04e5ff" offset="100.0%" ></stop>\
+                        </radialGradient>\
+                        <path id="kinect-open-hand-base" stroke-width="2.66667px" stroke-miterlimit="2.75" d="M 184.325,52.329 C 177.166,51.961 174.195,56.492 173.858,59.838 C 173.858,59.838 170.148,117.100 170.148,118.229 C 170.148,121.405 166.840,124.394 163.662,124.394 C 160.488,124.394 157.912,121.819 157.912,118.644 C 157.912,117.843 160.558,26.069 160.558,26.069 C 160.746,21.648 156.897,14.964 148.905,14.845 C 140.912,14.727 137.941,20.814 137.759,25.106 C 137.759,25.106 136.400,107.066 136.400,108.288 C 136.400,111.463 133.825,114.038 130.650,114.038 C 127.474,114.038 124.900,111.463 124.900,108.288 C 124.900,107.743 120.237,15.217 119.553,13.920 C 118.498,10.296 113.940,7.103 108.746,7.227 C 102.840,7.368 98.306,11.584 98.410,15.973 C 98.412,16.051 98.427,16.125 98.431,16.202 C 98.162,17.142 102.662,111.920 102.662,112.943 C 102.662,116.118 100.087,118.693 96.912,118.693 C 93.735,118.693 89.829,116.118 89.829,112.943 C 89.829,112.611 78.418,41.710 77.470,40.035 C 77.142,38.973 75.392,30.547 65.819,31.637 C 56.246,32.727 56.261,43.062 56.588,44.996 L 71.393,145.191 C 72.726,150.746 73.426,157.547 67.168,159.684 C 66.531,159.901 46.958,133.145 46.958,133.145 C 25.587,108.413 11.363,121.842 11.363,121.842 C 5.045,128.159 9.566,133.019 12.931,137.088 C 12.931,137.088 55.426,192.383 68.087,207.555 C 80.746,222.727 102.327,240.773 125.829,240.773 C 158.920,240.773 182.465,216.875 185.017,180.696 C 187.571,144.516 193.480,62.303 193.480,62.303 C 193.712,60.008 191.484,52.696 184.325,52.329 Z"></path>\
+                        <clipPath id="kinect-cursor-press-clip">\
+                            <circle id="kinect-cursor-press-clip-circle" cx="125.0" cy="250.0" r="0.0"></circle>\
+                        </clipPath>\
+                        <path id="kinect-grip-hand-base" stroke-width="2.66667px" stroke-miterlimit="2.75" d="m 181.93858,119.37788 c -5.53253,-12.70786 -17.98221,-8.32935 -19.87611,-6.05884 -1.93242,2.33185 -2.55388,10.20311 -3.11979,7.43102 -0.52841,-2.58838 -1.58534,-6.48216 -3.05075,-8.33167 -3.71325,-4.68654 -18.26158,-5.14609 -20.28816,1.42556 -0.60793,-2.5391 -2.08123,9.29661 -2.71043,6.39601 -0.80661,-3.71845 -1.46647,-5.09336 -3.10202,-8.04206 -3.52367,-6.35277 -25.21241,-4.55749 -26.94562,0.8136 -2.23168,1.60831 -0.45609,3.96744 -2.20942,8.65039 -0.11175,0.29847 0.28587,-4.91744 -2.132876,-9.19244 -2.409535,-4.25872 -19.828184,-1.33427 -20.975736,0.12408 -6.966521,6.47431 -9.645445,17.93226 -6.313239,32.77615 -1.470297,3.68614 -7.438345,11.46835 -12.632726,14.31443 18.050822,-8.87437 10.347115,-23.76108 10.347115,-23.76108 1.654793,-11.75616 -15.575036,0.50533 -15.942531,0.54616 -6.318,6.317 -13.368249,17.51059 -10.003249,21.57959 0,0 12.442962,34.33433 25.103962,49.50633 16.952148,16.18215 37.23324,18.66434 60.51992,14.78272 32.68992,-5.44904 49.05986,-5.46272 56.41008,-41.64172 5.70646,-64.06554 -1.17616,-23.47973 -3.07842,-61.31823 z"></path>\
+                    </defs>\
+                    <path id="kinect-cursor-glow" fill="none" stroke="#7c6dcf" stroke-width="16" stroke-miterlimit="2.75" d="M 183.605,52.966 C 176.446,52.598 173.475,57.129 173.138,60.476 C 173.138,60.476 169.428,117.739 169.428,118.866 C 169.428,122.043 166.120,125.032 162.943,125.032 C 159.768,125.032 157.193,122.457 157.193,119.282 C 157.193,118.481 159.838,26.707 159.838,26.707 C 160.026,22.285 156.177,15.602 148.185,15.483 C 140.193,15.365 137.221,21.452 137.039,25.744 C 137.039,25.744 135.680,107.703 135.680,108.926 C 135.680,112.101 133.105,114.676 129.930,114.676 C 126.754,114.676 124.179,112.101 124.179,108.926 C 124.179,108.382 119.517,15.854 118.833,14.558 C 117.779,10.934 113.220,7.741 108.026,7.865 C 102.120,8.005 97.586,12.222 97.690,16.611 C 97.693,16.689 97.707,16.763 97.711,16.840 C 97.443,17.780 101.943,112.558 101.943,113.580 C 101.943,116.757 99.367,119.330 96.193,119.330 C 93.016,119.330 89.109,116.757 89.109,113.580 C 89.109,113.249 77.698,42.348 76.750,40.673 C 76.422,39.611 74.672,31.185 65.099,32.275 C 55.526,33.365 55.542,43.700 55.868,45.634 L 70.673,145.828 C 72.006,151.384 72.706,158.185 66.448,160.322 C 65.811,160.539 46.238,133.783 46.238,133.783 C 24.867,109.051 10.643,122.479 10.643,122.479 C 4.325,128.796 8.846,133.658 12.211,137.726 C 12.211,137.726 54.706,193.021 67.367,208.193 C 80.026,223.365 101.607,241.410 125.109,241.410 C 158.200,241.410 181.745,217.513 184.297,181.334 C 186.851,145.154 192.760,62.940 192.760,62.940 C 192.992,60.645 190.764,53.335 183.605,52.966 Z "></path>\
+                    <use id="kinect-cursor-normal" xlink:href="#kinect-open-hand-base" fill="white" stroke="black"></use>\
+                    <g id="kinect-cursor-progress" clip-path="url(#kinect-cursor-press-clip)">\
+                        <use xlink:href="#kinect-open-hand-base" fill="url(#kinect-pressing-gradient)" stroke="none"></use>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,213.384 C 112.266,213.384 102.021,221.573 98.898,232.737 C 100.149,233.406 101.418,234.039 102.704,234.633 C 103.681,230.576 105.768,226.953 108.627,224.091 C 112.678,220.043 118.251,217.547 124.427,217.545 C 130.605,217.547 136.180,220.043 140.229,224.091 C 143.494,227.359 145.749,231.617 146.500,236.382 C 147.841,235.885 149.154,235.336 150.435,234.742 C 148.039,222.567 137.308,213.384 124.427,213.384"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,198.974 C 107.230,198.976 92.510,209.587 86.458,224.618 C 87.591,225.494 88.748,226.351 89.928,227.182 C 91.784,222.146 94.714,217.629 98.437,213.902 C 105.097,207.247 114.272,203.137 124.427,203.136 C 134.584,203.137 143.759,207.247 150.417,213.902 C 154.657,218.144 157.862,223.407 159.632,229.293 C 160.827,228.411 161.981,227.481 163.102,226.501 C 157.556,210.480 142.338,198.976 124.427,198.974"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,184.565 C 102.982,184.566 84.386,196.770 75.204,214.613 C 76.212,215.635 77.255,216.656 78.331,217.667 C 80.843,212.467 84.209,207.756 88.252,203.713 C 97.516,194.451 110.293,188.728 124.427,188.726 C 138.563,188.728 151.341,194.451 160.607,203.713 C 164.788,207.896 168.248,212.796 170.785,218.212 C 171.735,216.955 172.636,215.651 173.492,214.300 C 164.254,196.626 145.751,184.566 124.427,184.565"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,170.155 C 99.400,170.155 77.453,183.343 65.150,203.145 C 66.181,204.417 67.117,205.563 67.944,206.563 C 70.765,201.798 74.169,197.419 78.062,193.523 C 89.934,181.653 106.315,174.317 124.427,174.317 C 142.542,174.317 158.922,181.653 170.795,193.523 C 173.825,196.555 176.559,199.879 178.951,203.450 C 179.564,201.877 180.126,200.265 180.635,198.609 C 167.941,181.352 147.493,170.155 124.427,170.155"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,155.746 C 96.053,155.747 70.960,169.793 55.719,191.310 C 56.641,192.479 57.540,193.618 58.415,194.721 C 61.215,190.636 64.383,186.827 67.874,183.335 C 82.353,168.858 102.336,159.909 124.427,159.908 C 146.520,159.909 166.502,168.858 180.983,183.335 C 181.800,184.152 182.599,184.986 183.380,185.838 C 183.594,184.171 183.765,182.476 183.887,180.752 C 183.895,180.627 183.904,180.506 183.914,180.381 C 168.687,165.159 147.658,155.747 124.427,155.746"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 124.427,141.337 C 105.045,141.338 86.970,146.934 71.728,156.599 C 70.962,158.106 69.668,159.318 67.603,160.021 C 67.549,160.021 67.405,159.895 67.185,159.658 C 59.361,165.252 52.385,171.964 46.499,179.558 C 47.386,180.694 48.270,181.825 49.147,182.943 C 51.768,179.485 54.621,176.210 57.686,173.146 C 74.774,156.061 98.359,145.500 124.427,145.499 C 147.411,145.500 168.463,153.709 184.835,167.359 C 184.953,165.715 185.072,164.022 185.195,162.298 C 168.453,149.168 147.353,141.338 124.427,141.337"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 58.345,148.266 C 50.558,153.893 43.518,160.486 37.399,167.873 C 38.266,168.990 39.140,170.114 40.016,171.242 C 42.368,168.354 44.865,165.589 47.497,162.957 C 51.632,158.824 56.096,155.021 60.846,151.594 C 60.036,150.521 59.194,149.401 58.345,148.266 M 124.427,126.928 C 105.097,126.928 86.896,131.786 70.987,140.345 L 71.635,144.735 C 87.268,136.041 105.265,131.091 124.427,131.090 C 147.312,131.091 168.535,138.151 186.056,150.215 C 186.168,148.633 186.282,147.040 186.397,145.429 C 168.603,133.731 147.312,126.928 124.427,126.928"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 49.764,136.688 C 41.913,142.379 34.739,148.942 28.380,156.237 C 29.224,157.328 30.094,158.452 30.979,159.594 C 33.003,157.241 35.116,154.962 37.309,152.768 C 41.936,148.143 46.925,143.885 52.234,140.040 C 51.303,138.780 50.463,137.639 49.764,136.688 M 168.998,120.536 C 168.581,121.903 167.600,123.146 166.369,124.003 C 173.664,126.645 180.643,129.956 187.224,133.863 C 187.333,132.322 187.443,130.781 187.555,129.239 C 181.648,125.862 175.448,122.946 168.998,120.536 M 124.427,112.518 C 116.992,112.518 109.701,113.156 102.614,114.379 C 102.373,117.292 99.933,119.577 96.962,119.577 C 94.928,119.577 92.586,118.507 91.196,116.898 C 83.399,119.001 75.894,121.826 68.763,125.296 L 69.403,129.622 C 85.962,121.340 104.648,116.681 124.427,116.680 C 135.839,116.680 146.888,118.231 157.374,121.134 C 157.224,120.626 157.144,120.087 157.144,119.530 C 157.144,119.451 157.171,118.473 157.216,116.779 C 149.919,114.840 142.373,113.535 134.623,112.921 C 133.582,114.180 132.008,114.982 130.247,114.982 C 128.305,114.982 126.591,114.009 125.569,112.524 C 125.188,112.520 124.810,112.518 124.427,112.518"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 39.819,126.114 C 32.433,131.616 25.597,137.825 19.419,144.632 C 20.211,145.659 21.073,146.779 22.001,147.981 C 23.659,146.135 25.366,144.333 27.121,142.579 C 32.061,137.639 37.375,133.073 43.014,128.926 C 41.928,127.893 40.860,126.956 39.819,126.114 M 170.043,105.607 C 169.946,107.133 169.856,108.576 169.773,109.915 C 176.197,112.156 182.408,114.863 188.360,117.991 C 188.469,116.474 188.576,114.969 188.682,113.473 C 182.702,110.426 176.478,107.793 170.043,105.607 M 88.200,102.780 C 80.728,104.749 73.501,107.312 66.564,110.417 L 67.197,114.699 C 74.130,111.524 81.370,108.909 88.865,106.909 C 88.665,105.662 88.441,104.276 88.200,102.780 M 136.090,98.581 C 136.065,100.105 136.043,101.504 136.022,102.752 C 143.367,103.365 150.542,104.553 157.514,106.273 C 157.552,104.928 157.593,103.506 157.636,102.019 C 150.641,100.340 143.443,99.180 136.090,98.581 M 124.014,98.109 C 116.527,98.131 109.170,98.733 101.998,99.873 C 102.066,101.364 102.132,102.760 102.192,104.057 C 109.366,102.891 116.723,102.281 124.225,102.270 C 124.162,101.018 124.092,99.624 124.014,98.109"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 25.247,119.229 C 20.067,123.493 15.168,128.082 10.577,132.969 C 11.247,134.191 12.138,135.342 13.033,136.436 C 14.308,135.063 15.607,133.716 16.932,132.390 C 21.157,128.166 25.626,124.191 30.320,120.486 C 28.510,119.813 26.815,119.418 25.247,119.229 M 170.989,90.758 C 170.899,92.191 170.806,93.620 170.716,95.037 C 177.170,97.096 183.429,99.574 189.475,102.441 C 189.581,100.935 189.688,99.449 189.793,97.995 C 183.724,95.194 177.450,92.774 170.989,90.758 M 85.878,88.493 C 78.490,90.370 71.310,92.774 64.383,95.660 L 65.013,99.910 C 71.944,96.964 79.138,94.518 86.550,92.617 C 86.332,91.274 86.106,89.893 85.878,88.493 M 136.324,84.145 C 136.302,85.568 136.279,86.961 136.257,88.314 C 143.644,88.883 150.880,89.979 157.934,91.565 C 157.973,90.176 158.014,88.763 158.056,87.329 C 150.976,85.776 143.727,84.703 136.324,84.145 M 123.275,83.703 C 115.823,83.758 108.499,84.332 101.330,85.396 C 101.396,86.819 101.459,88.213 101.523,89.575 C 108.691,88.492 116.029,87.909 123.491,87.864 C 123.421,86.512 123.349,85.128 123.275,83.703"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 171.940,75.994 C 171.852,77.362 171.759,78.786 171.667,80.251 C 178.143,82.164 184.452,84.460 190.573,87.115 C 190.682,85.596 190.790,84.128 190.889,82.722 C 184.746,80.121 178.425,77.871 171.940,75.994 M 83.526,74.225 C 76.227,76.022 69.113,78.287 62.217,80.990 L 62.842,85.218 C 69.746,82.467 76.883,80.161 84.209,78.344 C 83.983,76.969 83.754,75.594 83.526,74.225 M 136.561,69.714 C 136.538,71.106 136.516,72.501 136.493,73.883 C 143.926,74.414 151.223,75.436 158.354,76.912 C 158.395,75.505 158.434,74.097 158.475,72.688 C 151.318,71.238 144.014,70.238 136.561,69.714 M 122.520,69.300 C 115.118,69.381 107.814,69.936 100.665,70.932 C 100.729,72.330 100.793,73.726 100.857,75.108 C 108.012,74.093 115.322,73.534 122.740,73.460 C 122.668,72.083 122.594,70.697 122.520,69.300"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 172.901,61.296 C 172.890,61.366 172.882,61.436 172.876,61.506 C 172.876,61.506 172.779,63.011 172.615,65.532 C 179.120,67.326 185.475,69.474 191.663,71.954 C 191.785,70.258 191.889,68.793 191.976,67.601 C 185.773,65.167 179.407,63.058 172.901,61.296 M 81.131,59.975 C 73.933,61.701 66.901,63.847 60.058,66.388 L 60.680,70.598 C 67.541,68.013 74.599,65.833 81.829,64.088 C 81.592,62.680 81.358,61.305 81.131,59.975 M 136.799,55.287 C 136.777,56.658 136.755,58.045 136.732,59.453 C 144.212,59.956 151.571,60.913 158.774,62.299 C 158.814,60.876 158.856,59.470 158.895,58.086 C 151.670,56.724 144.294,55.782 136.799,55.287 M 121.748,54.899 C 114.383,55.003 107.139,55.537 100.007,56.478 C 100.069,57.858 100.133,59.252 100.196,60.651 C 107.331,59.695 114.599,59.156 121.973,59.058 C 121.899,57.666 121.823,56.276 121.748,54.899"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 78.613,45.759 C 71.559,47.418 64.653,49.450 57.909,51.836 L 58.528,56.033 C 65.309,53.602 72.264,51.536 79.368,49.857 C 79.086,48.290 78.833,46.911 78.613,45.759 M 137.038,40.863 C 137.016,42.184 136.993,43.580 136.970,45.028 C 144.504,45.506 151.917,46.410 159.193,47.718 C 159.237,46.259 159.276,44.854 159.314,43.512 C 152.015,42.224 144.588,41.334 137.038,40.863 M 120.948,40.500 C 113.651,40.624 106.448,41.142 99.363,42.031 C 99.421,43.387 99.482,44.782 99.546,46.203 C 106.646,45.300 113.867,44.778 121.183,44.659 C 121.104,43.243 121.024,41.856 120.948,40.500"></path>\
+                        <path fill="#ffffff" opacity="0.26667" d="M 67.994,33.588 C 65.759,34.198 63.541,34.842 61.336,35.523 C 59.365,36.980 58.275,39.064 57.693,41.068 C 63.251,39.203 68.915,37.564 74.669,36.163 C 73.215,34.735 71.092,33.607 67.994,33.588 M 137.328,26.443 C 137.298,26.715 137.275,26.981 137.264,27.241 C 137.264,27.241 137.244,28.463 137.209,30.606 C 144.794,31.062 152.266,31.921 159.613,33.163 C 159.668,31.221 159.712,29.780 159.734,28.963 C 152.384,27.741 144.913,26.895 137.328,26.443 M 120.090,26.104 C 112.882,26.247 105.763,26.747 98.746,27.587 C 98.799,28.872 98.857,30.267 98.919,31.758 C 105.961,30.905 113.110,30.400 120.347,30.262 C 120.259,28.784 120.173,27.393 120.090,26.104"></path>\
+                        <use xlink:href="#kinect-open-hand-base" fill="none" stroke="black"></use>\
+                    </g>\
+                    <use id="kinect-cursor-extended" xlink:href="#kinect-open-hand-base" fill="url(#kinect-extended-gradient)" stroke="black"></use>\
+                </svg>';
+            document.body.appendChild(cursorElement);
+
+            var cursorData = {
+                "element": cursorElement,
+                "show": function () {
+                    // Cursor will be shown next time we get a valid hand pointer
+                    showCursor = true;
+                },
+                "hide": function () {
+                    // Hide cursor immediately
+                    showCursor = false;
+                    $(cursorElement).hide();
+                }
+            };
+            jQuery.data(cursorElement, uiController.CURSOR_ID, cursorData);
+            
+            var wasHovering = false;
+            var wasPressing = false;
+            uiAdapter.addHitTestExclusion(cursorElement);
+
+            uiAdapter.on(uiController.HANDPOINTERS_UPDATED, function(handPointers) {
+                var MAXIMUM_CURSOR_SCALE = 1.0;
+                var MINIMUM_CURSOR_SCALE = 0.8;
+                var MINIMUM_PROGRESS_CLIP_RADIUS = 8.0;
+                var RANGE_PROGRESS_CLIP_RADIUS = 240.0;
+
+                // If cursor is not supposed to be showing, bail out immediately
+                if (!showCursor) {
+                    return;
+                }
+
+                var handPointer = null;
+
+                for (var iPointer = 0; iPointer < handPointers.length; ++iPointer) {
+                    var curPointer = handPointers[iPointer];
+
+                    if (curPointer.getIsPrimaryHandOfPrimaryUser()) {
+                        handPointer = curPointer;
+                        break;
+                    }
+                }
+
+                if (cursorElement == null) {
+                    return;
+                }
+
+                function clamp(value, min, max) {
+                    return Math.max(min, Math.min(max, value));
+                }
+
+                if (handPointer != null) {
+                    var isOpen = !handPointer.isInGripInteraction;
+
+                    // Get information about what this hand pointer is over
+                    var isHovering = false;
+                    var isOverPressTarget = false;
+                    var enteredElements = handPointer.getEnteredElements();
+                    for (var iEnteredElement = 0; iEnteredElement < enteredElements.length; ++iEnteredElement) {
+                        var enteredElement = enteredElements[iEnteredElement];
+                        if (KinectUI.getIsPressTarget(enteredElement)) {
+                            isHovering = true;
+                            isOverPressTarget = true;
+                            break;
+                        }
+
+                        if (KinectUI.getIsGripTarget(enteredElement)) {
+                            isHovering = true;
+                        }
+                    }
+
+                    var isPressing = isOverPressTarget && handPointer.isPressed && !handPointer.isInGripInteraction;
+
+                    if (isHovering != wasHovering) {
+                        if (isHovering) {
+                            $(cursorElement).addClass("cursor-hover");
+                        } else {
+                            $(cursorElement).removeClass("cursor-hover");
+                        }
+                        wasHovering = isHovering;
+                    }
+
+                    if (isPressing != wasPressing) {
+                        if (isPressing) {
+                            $(cursorElement).addClass("cursor-pressed");
+                        } else {
+                            $(cursorElement).removeClass("cursor-pressed");
+                        }
+                        wasPressing = isPressing;
+                    }
+
+                    var artworkWidth = $(cursorElement).width();
+                    var artworkHeight = $(cursorElement).height();
+                    var adjustedPressExtent = isOverPressTarget ? handPointer.pressExtent : 0.0;
+
+                    document.getElementById("kinect-cursor-press-clip-circle").r.baseVal.newValueSpecifiedUnits(SVGLength.SVG_LENGTHTYPE_PX, MINIMUM_PROGRESS_CLIP_RADIUS + (RANGE_PROGRESS_CLIP_RADIUS * adjustedPressExtent));
+
+                    var scale = (1.0 - (adjustedPressExtent * ((MAXIMUM_CURSOR_SCALE - MINIMUM_CURSOR_SCALE) / 2.0)));
+                    var xScale = (handPointer.handType == "Left") ? -scale : scale;
+                    var yScale = scale;
+                    $(cursorElement).css("transform", "scale(" + xScale + "," + yScale + ")");
+
+                    var xPos = clamp(handPointer.x, 0, window.innerWidth);
+                    var yPos = clamp(handPointer.y, 0, window.innerHeight);
+                    var isClamped = (xPos != handPointer.x) || (yPos != handPointer.y);
+                    var xDelta = (artworkWidth / 2) * scale;
+                    var yDelta = (artworkHeight / 2) * scale;
+                    var opacity = !isClamped ? 1.0 : 0.3;
+
+                    $(cursorElement).show();
+                    $(cursorElement).offset({ left: xPos - xDelta, top: yPos - yDelta });
+                    $(cursorElement).css("opacity", opacity);
+                } else {
+                    $(cursorElement).hide();
+                }
+            });
+
+            return cursorData;
+        };
+
+        // Enable triggering of UI events as interaction frames are received.
+        //     .enableEvents()
+        //
+        // Remarks
+        // This and .disableEvents() function serve as a global on/off switch
+        // so that clients don't have to be registering and unregistering all
+        // event handlers as they enter/exit a state where they don't want or
+        // need to process Kinect UI events.
+        // Events are enabled by default.
+        this.enableEvents = function() {
+            handPointerEventsEnabled = true;
+        };
+
+        // Disable triggering of UI events as interaction frames are received.
+        //     .disableEvents()
+        //
+        // Remarks
+        // This and .enableEvents() function serve as a global on/off switch
+        // so that clients don't have to be registering and unregistering all
+        // event handlers as they enter/exit a state where they don't want or
+        // need to process Kinect UI events.
+        // Events are enabled by default.
+        this.disableEvents = function () {
+            handPointerEventsEnabled = false;
+        };
+    }
+
+    //////////////////////////////////////////////////////////////
+    // KinectUIController object constructor
+    function KinectUIController() {
+
+        //////////////////////////////////////////////////////////////
+        // Public KinectUIController methods
+
+        // Creates a new Kinect UI adapter object optionally associated with a Kinect sensor.
+        //     .createAdapter( [sensor] )
+        //
+        // sensor: sensor object associated with the new UI adapter
+        this.createAdapter = function (sensor) {
+            if (typeof sensor != 'object') {
+                throw new Error("first parameter must be either a sensor object or left unspecified");
+            }
+
+            var uiAdapter = new KinectUIAdapter();
+
+            if (sensor != null) {
+                sensor.addStreamFrameHandler(uiAdapter.streamFrameHandler);
+                sensor.setHitTestHandler(uiAdapter.hitTestHandler);
+            }
+
+            return uiAdapter;
+        };
+
+        // Determines whether the specified element has been designated as a press target
+        //     .getIsPressTarget( element )
+        //
+        // element: DOM element for which we're querying press target status.
+        this.getIsPressTarget = function (element) {
+            return jQuery.data(element, "isPressTarget") ? true : false;
+        };
+
+        // Specifies whether the specified element should be designated as a press target
+        //     .setIsPressTarget( element, isPressTarget )
+        //
+        // element: DOM element for which we're specifying press target status.
+        // isPressTarget: true if element should be designated as a press target.
+        //                false otherwise.
+        this.setIsPressTarget = function (element, isPressTarget) {
+            jQuery.data(element, "isPressTarget", isPressTarget);
+        };
+
+        // Determines whether the specified element has been designated as a grip target
+        //     .getIsPressTarget( element )
+        //
+        // element: DOM element for which we're querying grip target status.
+        this.getIsGripTarget = function (element) {
+            return jQuery.data(element, "isGripTarget") ? true : false;
+        };
+
+        // Specifies whether the specified element should be designated as a grip target
+        //     .setIsGripTarget( element, isGripTarget )
+        //
+        // element: DOM element for which we're specifying grip target status.
+        // isGripTarget: true if element should be designated as a grip target.
+        //               false otherwise.
+        this.setIsGripTarget = function (element, isGripTarget) {
+            jQuery.data(element, "isGripTarget", isGripTarget);
+        };
+
+        // Determines whether the primary hand pointer is over the specified element
+        //     .getIsPrimaryHandPointerOver( element )
+        //
+        // element: DOM element for which we're querying if primary hand pointer is over it.
+        this.getIsPrimaryHandPointerOver = function (element) {
+            return jQuery.data(element, "isPrimaryHandPointerOver") ? true : false;
+        };
+
+        // Specifies whether the primary hand pointer is over the specified element
+        //     .setIsPrimaryHandPointerOver( element )
+        //
+        // element: DOM element for which we're specifying if primary hand pointer is over it.
+        // over: true if primary hand pointer is over the specified element. false otherwise.
+        this.setIsPrimaryHandPointerOver = function (element, over) {
+            jQuery.data(element, "isPrimaryHandPointerOver", over);
+        };
+
+        //////////////////////////////////////////////////////////////
+        // Public KinectUIController properties
+
+        // Element event names
+        this.HANDPOINTER_MOVE = "handPointerMove";
+        this.HANDPOINTER_ENTER = "handPointerEnter";
+        this.HANDPOINTER_LEAVE = "handPointerLeave";
+        this.HANDPOINTER_PRESS = "handPointerPress";
+        this.HANDPOINTER_PRESSRELEASE = "handPointerPressRelease";
+        this.HANDPOINTER_GRIP = "handPointerGrip";
+        this.HANDPOINTER_GRIPRELEASE = "handPointerGripRelease";
+        this.HANDPOINTER_GOTCAPTURE = "handPointerGotCapture";
+        this.HANDPOINTER_LOSTCAPTURE = "handPointerLostCapture";
+        this.QUERY_INTERACTION_STATUS = "queryInteractionStatus";
+
+        // Global UI event names
+        this.HANDPOINTERS_UPDATED = "handPointersUpdated";
+
+        // Global UI id/class names
+        this.BUTTON_CLASS = "kinect-button";
+        this.BUTTON_TEXT_CLASS = "kinect-button-text";
+        this.BUTTON_HOVER_CLASS = "button-hover";
+        this.BUTTON_PRESSED_CLASS = "button-pressed";
+
+        this.CURSOR_ID = "kinect-cursor";
+    };
+    
+    //////////////////////////////////////////////////////////////
+    // Private global properties common to all KinectUI objects
+    
+    // Get the path common to all script files
+    var scriptRootURIPath = (function () {
+        var rootPath = "";
+
+        try {
+            var scriptFileName = document.scripts[document.scripts.length - 1].src;
+            var pathSeparatorIndex = scriptFileName.lastIndexOf("/");
+
+            if (pathSeparatorIndex >= 0) {
+                // If pathSeparator is found, return path up to and including separator
+                rootPath = scriptFileName.substring(0, pathSeparatorIndex + 1);
+            }
+        } catch(error) {
+        }
+
+        return rootPath;
+    })();
+
+    // The global Kinect UI controller object is a singleton instance of our internal KinectUIController type
+    var uiController = new KinectUIController();
+    
+    // Prefix for element IDs assigned by Kinect UI layer for purposes of hit test detection
+    var kinectIdPrefix = "kinectId";
+
+    // Id in sequence to assign to controls that don't already have an assigned DOM element id
+    var lastAssignedControlId = 0;
+    
+    // return singleton
+    return uiController;
+})();
+
+// Guarantee that Kinect and KinectUI objects are available from the global scope even if
+// this file was loaded on-demand, in a restricted functional scope, a long time after web
+// document was initially loaded.
+window.Kinect = Kinect;
+window.KinectUI = KinectUI;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/KinectWorker-1.8.0.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/KinectWorker-1.8.0.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/KinectWorker-1.8.0.js	(revision 28897)
@@ -0,0 +1,61 @@
+// -----------------------------------------------------------------------
+// <copyright file="KinectWorker-1.8.0.js" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+"use strict";
+
+var imageDataMap = {};
+
+// Associate a specified ImageData (from a canvas object) with the specified image name
+//     .setImageData( imageName [, imageData] )
+//
+// imageName: Name used to refer to canvas ImageData object specified.
+// imageData: Canvas ImageData to associate with image name. If null, any previously
+//            existing association is removed.
+function setImageData(imageName, imageData) {
+    if ((imageData == null) && imageDataMap.hasOwnProperty(imageName)) {
+        delete imageDataMap[imageName];
+    } else {
+        imageDataMap[imageName] = imageData;
+    }
+}
+
+
+// Copy the information in the specified image ArrayBuffer to the ImageData associated
+// with the specified name and post the ImageData back to UI thread.
+//     .processImageData( imageName, imageData )
+//
+// imageName: Name used to refer to canvas ImageData object to receive data from
+//            ArrayBuffer.
+// imageBuffer: ArrayBuffer containing image data to copy to canvas ImageData structure.
+function processImageData(imageName, imageBuffer) {
+    if (!imageDataMap.hasOwnProperty(imageName)) {
+        self.postMessage({ "message": "notProcessed", "imageName": imageName});
+        return;
+    }
+
+    var imageData = imageDataMap[imageName];
+    var pixelArray = imageData.data;
+    var newPixelData = new Uint8Array(imageBuffer);
+
+    for (var i = 0; i < pixelArray.length; ++i) {
+        pixelArray[i] = newPixelData[i];
+    }
+
+    self.postMessage({ "message": "imageReady", "imageName": imageName, "imageData": imageData });
+}
+
+// thread message handler
+addEventListener('message', function (event) {
+    switch (event.data.message) {
+        case "setImageData":
+            setImageData(event.data.imageName, event.data.imageData);
+            break;
+
+        case "processImageData":
+            processImageData(event.data.imageName, event.data.imageBuffer);
+            break;
+    }
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/SamplePage.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/SamplePage.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/SamplePage.html	(revision 28897)
@@ -0,0 +1,36 @@
+﻿<!DOCTYPE HTML>
+<html>
+<head>
+    <title>Kinect Web Basics</title>
+    <script type="text/javascript" src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
+    <!--script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.1.min.js"></script-->
+    <script type="text/javascript" src="Kinect-1.8.0.js"></script>
+
+    <script type="text/javascript" src="doKinect.js"></script>
+    <script src="mrdoob-three.js-4862f5f/build/three.js"></script>
+    <script src="GSVPano.js-master/src/GSVPano.js"></script>
+    <script src="3d.js"></script>
+    <script src="doGSVP.js"></script>
+    <link rel="stylesheet" type="text/css" href="Kinect-1.8.0.css" /> 
+    <link rel="stylesheet" type="text/css" href="css/coolstyle.css" /> 
+</head>
+<body>
+    <div id="uiContainer">
+        <div id="backgroundContainer">
+            <!--mg id="backgroundImg" src="Images\beach.jpg" /-->
+
+            <div id="map-canvas"></div>
+            <div id="pano" style="position:absolute; left:0px; top: 0px;" ></div>
+            <canvas id="userViewerCanvas" class="streamImageCanvas" ></canvas>
+
+            <canvas id="backgroundRemovalCanvas" class="streamImageCanvas" style="opacity: 0.45;"></canvas>
+
+            <div id="kinectgripthing" style="position:absolute; left:20px; top:20px; width:800px; height:200px; background-color:#CCC; z-index: 10;" hidden> sick presses </div>
+            <!--div id="tutorialBox" style="position:absolute; left: 35%; top: 40%; width: 30%; height 20%;  background-color:#CCC;"> tutorial text </div-->
+        </div>
+    </div>
+        <script src="https://maps.googleapis.com/maps/api/js?sensor=false"> </script>
+        <script src="doStreetView.js"></script>
+        <!--script src="tutorial.js"></script-->
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/SamplePageCopy.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/SamplePageCopy.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/SamplePageCopy.html	(revision 28897)
@@ -0,0 +1,527 @@
+﻿<!DOCTYPE HTML>
+<html>
+<head>
+    <title>Kinect Web Basics</title>
+    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.1.min.js"></script>
+    <script type="text/javascript" src="Kinect-1.8.0.js"></script>
+    <script type="text/javascript">
+        $(document).ready(function () {
+            var streamImageWidth = 640;
+            var streamImageHeight = 480;
+            var streamImageResolution = streamImageWidth.toString() + "x" + streamImageHeight.toString();
+
+            var isSensorConnected = false;
+            var engagedUser = null;
+            var cursor = null;
+            var userViewerCanvasElement = null;
+            var backgroundRemovalCanvasElement = null;
+
+            // Log errors encountered during sensor configuration
+            function configError(statusText, errorData) {
+                console.log((errorData != null) ? JSON.stringify(errorData) : statusText);
+            }
+
+            // Determine if the specified object has any properties or not
+            function isEmptyObject(obj) {
+                if (obj == null) {
+                    return true;
+                }
+
+                var numProperties = 0;
+
+                for (var prop in obj) {
+                    if (obj.hasOwnProperty(prop)) {
+                        ++numProperties;
+                    }
+                }
+
+                return numProperties <= 0;
+            }
+
+            // Show or hide the cursor
+            function setCursorVisibility(isVisible) {
+                if (cursor == null) {
+                    return;
+                }
+
+                if (isVisible) {
+                    cursor.show();
+                } else {
+                    cursor.hide();
+                }
+            }
+
+            // Show or hide a canvas element
+            function setCanvasVisibility(canvasElement, isVisible) {
+                if (canvasElement == null) {
+                    return;
+                }
+
+                var canvasQuery = $(canvasElement);
+
+                if (isVisible) {
+                    if (!canvasQuery.hasClass("showing")) {
+                        // Clear canvas before showing it
+                        var canvasContext = canvasElement.getContext("2d");
+                        canvasContext.clearRect(0, 0, streamImageWidth, streamImageHeight);
+                    }
+
+                    canvasQuery.addClass("showing");
+                } else {
+                    canvasQuery.removeClass("showing");
+                }
+            }
+
+            var showPanelLabel = "Choose<br/>Background";
+            var hidePanelLabel = "Hide<br/>Panel";
+            function setChoosePanelVisibility(isVisible) {
+                var togglePanelElement = document.getElementById("togglePanelButton");
+                var spanQuery = $("span", togglePanelElement);
+
+                if (isVisible) {
+                    // If choose background panel is being shown, hide toggle button
+                    // border so top of button is aligned with other buttons
+                    $("#choosePanel").addClass("showing");
+                    spanQuery.html(hidePanelLabel);
+                } else {
+                    // If choose background panel is being hidden, show toggle button
+                    // border to provide contrast against picture
+                    $("#choosePanel").removeClass("showing");
+                    spanQuery.html(showPanelLabel);
+                }
+            }
+
+            function isChoosePanelVisible() {
+                return $("#choosePanel").hasClass("showing");
+            }
+
+            // property and function used to keep track of when the choose background control panel
+            // should be hidden after an inactivity period
+            var hidePanelTimeoutId = null;
+            function resetHidePanelTimeout() {
+                // First clear any previous timeout
+                if (hidePanelTimeoutId != null) {
+                    clearTimeout(hidePanelTimeoutId);
+                    hidePanelTimeoutId = null;
+                }
+
+                if (!isSensorConnected || (engagedUser == null)) {
+                    // if there is no engaged user or no sensor connected, we hide the choose background
+                    // control panel after 10 seconds
+                    hidePanelTimeoutId = setTimeout(function () {
+                        setChoosePanelVisibility(false);
+                        hidePanelTimeoutId = null;
+                    }, 10000);
+                }
+            }
+
+            // Update sensor state and perform UI transitions (showing/hiding appropriate UI elements)
+            // related to sensor status or engagement state changes
+            var delayedConfigTimeoutId = null;
+            function updateUserState(newIsSensorConnected, newEngagedUser, sensorToConfig) {
+                var hasEngagedUser = engagedUser != null;
+                var newHasEngagedUser = newEngagedUser != null;
+
+                // If there's a pending configuration change when state changes again, cancel previous timeout
+                if (delayedConfigTimeoutId != null) {
+                    clearTimeout(delayedConfigTimeoutId);
+                    delayedConfigTimeoutId = null;
+                }
+
+                if ((isSensorConnected != newIsSensorConnected) || (engagedUser != newEngagedUser)) {
+                    if (newIsSensorConnected) {
+
+                        var immediateConfig = {};
+                        var delayedConfig = {};
+                        immediateConfig[Kinect.INTERACTION_STREAM_NAME] = { "enabled": true };
+                        immediateConfig[Kinect.USERVIEWER_STREAM_NAME] = { "resolution": streamImageResolution };
+                        immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = { "resolution": streamImageResolution };
+
+                        setCursorVisibility(newHasEngagedUser);
+                        setCanvasVisibility(userViewerCanvasElement, !newHasEngagedUser);
+                        setCanvasVisibility(backgroundRemovalCanvasElement, newHasEngagedUser);
+
+                        if (newHasEngagedUser) {
+                            immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME].enabled = true;
+                            immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME].trackingId = newEngagedUser;
+
+                            delayedConfig[Kinect.USERVIEWER_STREAM_NAME] = { "enabled": false };
+                        } else {
+                            immediateConfig[Kinect.USERVIEWER_STREAM_NAME].enabled = true;
+
+                            if (hasEngagedUser) {
+                                delayedConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = { "enabled": false };
+                            }
+                        }
+
+                        // Perform immediate configuration
+                        sensorToConfig.postConfig(immediateConfig, configError);
+
+                        // schedule delayed configuration for 2 seconds later
+                        if (!isEmptyObject(delayedConfig)) {
+                            delayedConfigTimeoutId = setTimeout(function () {
+                                sensorToConfig.postConfig(delayedConfig, configError);
+                                delayedConfigTimeoutId = null;
+                            }, 2000);
+                        }
+                    } else {
+                        setCursorVisibility(false);
+                        setCanvasVisibility(userViewerCanvasElement, false);
+                        setCanvasVisibility(backgroundRemovalCanvasElement, false);
+                    }
+                }
+
+                isSensorConnected = newIsSensorConnected;
+                engagedUser = newEngagedUser;
+
+                resetHidePanelTimeout();
+            }
+
+            // Get the id of the engaged user, if present, or null if there is no engaged user
+            function findEngagedUser(userStates) {
+                var engagedUserId = null;
+
+                for (var i = 0; i < userStates.length; ++i) {
+                    var entry = userStates[i];
+                    if (entry.userState == "engaged") {
+                        engagedUserId = entry.id;
+                        break;
+                    }
+                }
+
+                return engagedUserId;
+            }
+
+            // Respond to user state change event
+            function onUserStatesChanged(newUserStates) {
+                var newEngagedUser = findEngagedUser(newUserStates);
+
+                updateUserState(isSensorConnected, newEngagedUser, sensor);
+            }
+
+            // Create sensor and UI adapter layers
+            var sensor = Kinect.sensor(Kinect.DEFAULT_SENSOR_NAME, function (sensorToConfig, isConnected) {
+                if (isConnected) {
+                    // Determine what is the engagement state upon connection
+                    sensorToConfig.getConfig(function (data) {
+                        var engagedUserId = findEngagedUser(data[Kinect.INTERACTION_STREAM_NAME].userStates);
+
+                        updateUserState(true, engagedUserId, sensorToConfig);
+                    });
+                } else {
+                    updateUserState(false, engagedUser, sensorToConfig);
+                }
+            });
+            var uiAdapter = KinectUI.createAdapter(sensor);
+
+            uiAdapter.promoteButtons();
+            cursor = uiAdapter.createDefaultCursor();
+            userViewerCanvasElement = document.getElementById("userViewerCanvas");
+            backgroundRemovalCanvasElement = document.getElementById("backgroundRemovalCanvas");
+            uiAdapter.bindStreamToCanvas(Kinect.USERVIEWER_STREAM_NAME, userViewerCanvasElement);
+            uiAdapter.bindStreamToCanvas(Kinect.BACKGROUNDREMOVAL_STREAM_NAME, backgroundRemovalCanvasElement);
+
+            sensor.addEventHandler(function (event) {
+                switch (event.category) {
+                    case Kinect.USERSTATE_EVENT_CATEGORY:
+                        switch (event.eventType) {
+                            case Kinect.USERSTATESCHANGED_EVENT_TYPE:
+                                onUserStatesChanged(event.userStates);
+                                break;
+                        }
+                        break;
+                }
+            });
+
+            setChoosePanelVisibility(false);
+            $("#togglePanelButton").click(function (event) {
+                if (isChoosePanelVisible()) {
+                    setChoosePanelVisibility(false);
+                } else {
+                    setChoosePanelVisibility(true);
+                    resetHidePanelTimeout();
+                }
+            });
+            $(".imgButtonContainer .kinect-button").click(function (event) {
+                var imgElement = $("img", event.currentTarget)[0];
+                document.getElementById("backgroundImg").src = imgElement.src;
+                resetHidePanelTimeout();
+            });
+        });
+    </script>
+    <link rel="stylesheet" type="text/css" href="Kinect-1.8.0.css" /> 
+    <style type="text/css">
+        html {
+            width: 100%;
+            height: 100%;
+            overflow: hidden;
+        }
+
+        body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+            overflow: hidden;
+            font-family: 'Segoe UI';
+        }
+
+        #uiContainer {
+            position: relative;
+            margin: 0 auto;
+            width: 100vw;
+            height: 100%;
+        }
+
+        #logoImg {
+            position: absolute;
+            top: 18px;
+            left: 26px;
+            width: 100px;
+            height: 40px;
+        }
+
+        #sampleName {
+            position: relative;
+            display: block;
+            text-align: right;
+            font-size: 16pt;
+            color: gray;
+            top: 24px;
+            margin-right: 26px;
+        }
+
+        #backgroundContainer {
+            position: absolute;
+            top: 80px;
+            width: 100vw;
+            height: 56.25vw;
+            margin-bottom: 26px;
+        }
+
+        @media screen and (max-width: 960px) {
+            #uiContainer {
+                width: 960px;
+            }
+
+            #backgroundContainer {
+                width: 960px;
+                height: 540px;
+            }
+        }
+
+        @media screen and (min-width: 1600px) {
+            #uiContainer {
+                width: 1600px;
+            }
+
+            #backgroundContainer {
+                width: 1600px;
+                height: 900px;
+            }
+        }
+
+        #backgroundImg {
+            width: 100%;
+            height: 100%;
+        }
+
+        .streamImageCanvas {
+            position: absolute;
+            width: 75%;
+            height: 100%;
+            top: 0;
+            left: 12.5%;
+            opacity: 0.0;
+            transition: opacity 1.0s;
+        }
+
+        .streamImageCanvas.showing {
+            opacity: 1.0;
+            transition: opacity 1.0s;
+        }
+
+        #buttonContainer {
+            position: absolute;
+            display: block;
+            width: 100%;
+            height: 160px;
+            bottom: 0;
+        }
+
+        #choosePanel {
+            position: absolute;
+            background-color: white;
+            width: 100%;
+            height: 100%;
+            top: 100%;
+            transition: top 0.4s;
+        }
+
+        #choosePanel.showing {
+            top: 0;
+            transition: top 0.4s;
+        }
+
+        .commandButtonContainer {
+            position: absolute;
+            width: 18%;
+            height: 100%;
+            top: 0;
+            left: 3%;
+        }
+
+        .imgButtonContainer {
+            position: absolute;
+            width: 18%;
+            height: 100%;
+            top: 0;
+        }
+
+        #togglePanelButton {
+            /* Add an explicit background color to make sure mouse/touch events get routed
+               to button even when hovering/pressing over transparent area around button edge. */
+            background-color: rgba(0,0,0,0.0);
+        }
+
+        #imgButtonContainer1 {
+            left: 25%;
+        }
+
+        #imgButtonContainer2 {
+            left: 43%;
+        }
+
+        #imgButtonContainer3 {
+            left: 61%;
+        }
+
+        #imgButtonContainer4 {
+            left: 79%;
+        }
+
+        .kinect-button.tile {
+            position: relative;
+            display: block;
+            width: 170px;
+            height: 100px;
+            margin: 30px auto;
+            padding: 0;
+        }
+
+        .kinect-button.tile .commandButtonContent {
+            position: absolute;
+            width: 100%;
+            height: 100%;
+            border-style: solid;
+            border-color: white;
+            border-width: 2px;
+            transition: border-width 0.15s;
+            left: -2px;
+            top: -2px;
+        }
+
+        .kinect-button.image {
+            position: relative;
+            display: block;
+            width: 170px;
+            height: 100px;
+            margin: 30px auto;
+            padding: 0;
+        }
+
+        .kinect-button img {
+            width: 100%;
+            height: 100%;
+        }
+
+        .kinect-button-text {
+            font-size: 14pt;
+            width: 100%;
+            height: 3em;
+            margin-top: -1.5em;
+        }
+
+        @media screen and (min-width: 1280px) and (max-width: 1600px) {
+            #buttonContainer {
+                height: 186px;
+            }
+
+            .kinect-button.tile {
+                width: 224px;
+                height: 126px;
+            }
+
+            .kinect-button.image {
+                width: 224px;
+                height: 126px;
+            }
+
+            .kinect-button-text {
+                font-size: 16pt;
+            }
+        }
+
+        @media screen and (min-width: 1600px) {
+            #buttonContainer {
+                height: 222px;
+            }
+
+            .kinect-button.tile {
+                width: 288px;
+                height: 162px;
+            }
+
+            .kinect-button.image {
+                width: 288px;
+                height: 162px;
+            }
+
+            .kinect-button-text {
+                font-size: 18pt;
+            }
+        }
+    </style>
+</head>
+<body>
+    <div id="uiContainer">
+        <img id="logoImg" src="Images\logo.png"/>
+        <span id="sampleName">Kinect Web Basics</span>
+        <div id="backgroundContainer">
+            <img id="backgroundImg" src="Images\beach.jpg"/>
+            <canvas id="userViewerCanvas" class="streamImageCanvas"></canvas>
+            <canvas id="backgroundRemovalCanvas" class="streamImageCanvas"></canvas>
+        </div>
+        <div id="buttonContainer">
+            <div id="choosePanel">
+                <div id="imgButtonContainer1" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/beach.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer2" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/fireworks.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer3" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/hamlet.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer4" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/operahouse.jpg"/>
+                    </div>
+                </div>
+            </div>
+            <div class="commandButtonContainer">
+                <div id="togglePanelButton" class="kinect-button tile">
+                    <div class="commandButtonContent">
+                        <span class="kinect-button-text"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/StreetSamplePage.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/StreetSamplePage.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/StreetSamplePage.html	(revision 28897)
@@ -0,0 +1,532 @@
+﻿
+<!--<!DOCTYPE HTML>-->
+<html>
+<head>
+    <title>Kinect Web Basics</title>
+    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.1.min.js"></script>
+    <script type="text/javascript" src="Kinect-1.8.0.js"></script>
+    <script type="text/javascript">
+        $(document).ready(function () {
+            var streamImageWidth = 640;
+            var streamImageHeight = 480;
+            var streamImageResolution = streamImageWidth.toString() + "x" + streamImageHeight.toString();
+
+            var isSensorConnected = false;
+            var engagedUser = null;
+            var cursor = null;
+            var userViewerCanvasElement = null;
+            var backgroundRemovalCanvasElement = null;
+
+            // Log errors encountered during sensor configuration
+            function configError(statusText, errorData) {
+                console.log((errorData != null) ? JSON.stringify(errorData) : statusText);
+            }
+
+            // Determine if the specified object has any properties or not
+            function isEmptyObject(obj) {
+                if (obj == null) {
+                    return true;
+                }
+
+                var numProperties = 0;
+
+                for (var prop in obj) {
+                    if (obj.hasOwnProperty(prop)) {
+                        ++numProperties;
+                    }
+                }
+
+                return numProperties <= 0;
+            }
+
+            // Show or hide the cursor
+            function setCursorVisibility(isVisible) {
+                if (cursor == null) {
+                    return;
+                }
+
+                if (isVisible) {
+                    cursor.show();
+                } else {
+                    cursor.hide();
+                }
+            }
+
+            // Show or hide a canvas element
+            function setCanvasVisibility(canvasElement, isVisible) {
+                if (canvasElement == null) {
+                    return;
+                }
+
+                var canvasQuery = $(canvasElement);
+
+                if (isVisible) {
+                    if (!canvasQuery.hasClass("showing")) {
+                        // Clear canvas before showing it
+                        var canvasContext = canvasElement.getContext("2d");
+                        canvasContext.clearRect(0, 0, streamImageWidth, streamImageHeight);
+                    }
+
+                    canvasQuery.addClass("showing");
+                } else {
+                    canvasQuery.removeClass("showing");
+                }
+            }
+
+            var showPanelLabel = "Choose<br/>Background";
+            var hidePanelLabel = "Hide<br/>Panel";
+            function setChoosePanelVisibility(isVisible) {
+                var togglePanelElement = document.getElementById("togglePanelButton");
+                var spanQuery = $("span", togglePanelElement);
+
+                if (isVisible) {
+                    // If choose background panel is being shown, hide toggle button
+                    // border so top of button is aligned with other buttons
+                    $("#choosePanel").addClass("showing");
+                    spanQuery.html(hidePanelLabel);
+                } else {
+                    // If choose background panel is being hidden, show toggle button
+                    // border to provide contrast against picture
+                    $("#choosePanel").removeClass("showing");
+                    spanQuery.html(showPanelLabel);
+                }
+            }
+
+            function isChoosePanelVisible() {
+                return $("#choosePanel").hasClass("showing");
+            }
+
+            // property and function used to keep track of when the choose background control panel
+            // should be hidden after an inactivity period
+            var hidePanelTimeoutId = null;
+            function resetHidePanelTimeout() {
+                // First clear any previous timeout
+                if (hidePanelTimeoutId != null) {
+                    clearTimeout(hidePanelTimeoutId);
+                    hidePanelTimeoutId = null;
+                }
+
+                if (!isSensorConnected || (engagedUser == null)) {
+                    // if there is no engaged user or no sensor connected, we hide the choose background
+                    // control panel after 10 seconds
+                    hidePanelTimeoutId = setTimeout(function () {
+                        setChoosePanelVisibility(false);
+                        hidePanelTimeoutId = null;
+                    }, 10000);
+                }
+            }
+
+            // Update sensor state and perform UI transitions (showing/hiding appropriate UI elements)
+            // related to sensor status or engagement state changes
+            var delayedConfigTimeoutId = null;
+            function updateUserState(newIsSensorConnected, newEngagedUser, sensorToConfig) {
+                var hasEngagedUser = engagedUser != null;
+                var newHasEngagedUser = newEngagedUser != null;
+
+                // If there's a pending configuration change when state changes again, cancel previous timeout
+                if (delayedConfigTimeoutId != null) {
+                    clearTimeout(delayedConfigTimeoutId);
+                    delayedConfigTimeoutId = null;
+                }
+
+                if ((isSensorConnected != newIsSensorConnected) || (engagedUser != newEngagedUser)) {
+                    if (newIsSensorConnected) {
+
+                        var immediateConfig = {};
+                        var delayedConfig = {};
+                        immediateConfig[Kinect.INTERACTION_STREAM_NAME] = { "enabled": true };
+                        immediateConfig[Kinect.USERVIEWER_STREAM_NAME] = { "resolution": streamImageResolution };
+                        immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = { "resolution": streamImageResolution };
+
+                        setCursorVisibility(newHasEngagedUser);
+                        setCanvasVisibility(userViewerCanvasElement, !newHasEngagedUser);
+                        setCanvasVisibility(backgroundRemovalCanvasElement, newHasEngagedUser);
+
+                        if (newHasEngagedUser) {
+                            immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME].enabled = true;
+                            immediateConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME].trackingId = newEngagedUser;
+
+                            delayedConfig[Kinect.USERVIEWER_STREAM_NAME] = { "enabled": false };
+                        } else {
+                            immediateConfig[Kinect.USERVIEWER_STREAM_NAME].enabled = true;
+
+                            if (hasEngagedUser) {
+                                delayedConfig[Kinect.BACKGROUNDREMOVAL_STREAM_NAME] = { "enabled": false };
+                            }
+                        }
+
+                        // Perform immediate configuration
+                        sensorToConfig.postConfig(immediateConfig, configError);
+
+                        // schedule delayed configuration for 2 seconds later
+                        if (!isEmptyObject(delayedConfig)) {
+                            delayedConfigTimeoutId = setTimeout(function () {
+                                sensorToConfig.postConfig(delayedConfig, configError);
+                                delayedConfigTimeoutId = null;
+                            }, 2000);
+                        }
+                    } else {
+                        setCursorVisibility(false);
+                        setCanvasVisibility(userViewerCanvasElement, false);
+                        setCanvasVisibility(backgroundRemovalCanvasElement, false);
+                    }
+                }
+
+                isSensorConnected = newIsSensorConnected;
+                engagedUser = newEngagedUser;
+
+                resetHidePanelTimeout();
+            }
+
+            // Get the id of the engaged user, if present, or null if there is no engaged user
+            function findEngagedUser(userStates) {
+                var engagedUserId = null;
+
+                for (var i = 0; i < userStates.length; ++i) {
+                    var entry = userStates[i];
+                    if (entry.userState == "engaged") {
+                        engagedUserId = entry.id;
+                        break;
+                    }
+                }
+
+                return engagedUserId;
+            }
+
+            // Respond to user state change event
+            function onUserStatesChanged(newUserStates) {
+                var newEngagedUser = findEngagedUser(newUserStates);
+
+                updateUserState(isSensorConnected, newEngagedUser, sensor);
+            }
+
+            // Create sensor and UI adapter layers
+            var sensor = Kinect.sensor(Kinect.DEFAULT_SENSOR_NAME, function (sensorToConfig, isConnected) {
+                if (isConnected) {
+                    // Determine what is the engagement state upon connection
+                    sensorToConfig.getConfig(function (data) {
+                        var engagedUserId = findEngagedUser(data[Kinect.INTERACTION_STREAM_NAME].userStates);
+
+                        updateUserState(true, engagedUserId, sensorToConfig);
+                    });
+                } else {
+                    updateUserState(false, engagedUser, sensorToConfig);
+                }
+            });
+            var uiAdapter = KinectUI.createAdapter(sensor);
+
+            uiAdapter.promoteButtons();
+            cursor = uiAdapter.createDefaultCursor();
+            userViewerCanvasElement = document.getElementById("userViewerCanvas");
+            backgroundRemovalCanvasElement = document.getElementById("backgroundRemovalCanvas");
+            uiAdapter.bindStreamToCanvas(Kinect.USERVIEWER_STREAM_NAME, userViewerCanvasElement);
+            uiAdapter.bindStreamToCanvas(Kinect.BACKGROUNDREMOVAL_STREAM_NAME, backgroundRemovalCanvasElement);
+
+            sensor.addEventHandler(function (event) {
+                switch (event.category) {
+                    case Kinect.USERSTATE_EVENT_CATEGORY:
+                        switch (event.eventType) {
+                            case Kinect.USERSTATESCHANGED_EVENT_TYPE:
+                                onUserStatesChanged(event.userStates);
+                                break;
+                        }
+                        break;
+                }
+            });
+
+            setChoosePanelVisibility(false);
+            $("#togglePanelButton").click(function (event) {
+                if (isChoosePanelVisible()) {
+                    setChoosePanelVisibility(false);
+                } else {
+                    setChoosePanelVisibility(true);
+                    resetHidePanelTimeout();
+                }
+            });
+            $(".imgButtonContainer .kinect-button").click(function (event) {
+                var imgElement = $("img", event.currentTarget)[0];
+                document.getElementById("backgroundImg").src = imgElement.src;
+                resetHidePanelTimeout();
+            });
+        });
+    </script>
+    <link rel="stylesheet" type="text/css" href="Kinect-1.8.0.css" /> 
+    <style type="text/css">
+        html {
+            width: 100%;
+            height: 100%;
+            overflow: hidden;
+        }
+
+        body {
+            width: 100%;
+            height: 100%;
+            margin: 0;
+            overflow: hidden;
+            font-family: 'Segoe UI';
+        }
+
+        #uiContainer {
+            position: relative;
+            margin: 0 auto;
+            width: 100vw;
+            height: 100%;
+        }
+
+        #logoImg {
+            position: absolute;
+            top: 18px;
+            left: 26px;
+            width: 100px;
+            height: 40px;
+        }
+
+        #sampleName {
+            position: relative;
+            display: block;
+            text-align: right;
+            font-size: 16pt;
+            color: gray;
+            top: 24px;
+            margin-right: 26px;
+        }
+
+        #backgroundContainer {
+            position: absolute;
+            top: 80px;
+            width: 100vw;
+            height: 56.25vw;
+            margin-bottom: 26px;
+        }
+
+        @media screen and (max-width: 960px) {
+            #uiContainer {
+                width: 960px;
+            }
+
+            #backgroundContainer {
+                width: 960px;
+                height: 540px;
+            }
+        }
+
+        @media screen and (min-width: 1600px) {
+            #uiContainer {
+                width: 1600px;
+            }
+
+            #backgroundContainer {
+                width: 1600px;
+                height: 900px;
+            }
+        }
+
+        #backgroundImg {
+            width: 100%;
+            height: 100%;
+        }
+
+        .streamImageCanvas {
+            position: absolute;
+            width: 75%;
+            height: 100%;
+            top: 0;
+            left: 12.5%;
+            opacity: 0.0;
+            transition: opacity 1.0s;
+        }
+
+        .streamImageCanvas.showing {
+            opacity: 1.0;
+            transition: opacity 1.0s;
+        }
+
+        #buttonContainer {
+            position: absolute;
+            display: block;
+            width: 100%;
+            height: 160px;
+            bottom: 0;
+        }
+
+        #choosePanel {
+            position: absolute;
+            background-color: white;
+            width: 100%;
+            height: 100%;
+            top: 100%;
+            transition: top 0.4s;
+        }
+
+        #choosePanel.showing {
+            top: 0;
+            transition: top 0.4s;
+        }
+
+        .commandButtonContainer {
+            position: absolute;
+            width: 18%;
+            height: 100%;
+            top: 0;
+            left: 3%;
+        }
+
+        .imgButtonContainer {
+            position: absolute;
+            width: 18%;
+            height: 100%;
+            top: 0;
+        }
+
+        #togglePanelButton {
+            /* Add an explicit background color to make sure mouse/touch events get routed
+               to button even when hovering/pressing over transparent area around button edge. */
+            background-color: rgba(0,0,0,0.0);
+        }
+
+        #imgButtonContainer1 {
+            left: 25%;
+        }
+
+        #imgButtonContainer2 {
+            left: 43%;
+        }
+
+        #imgButtonContainer3 {
+            left: 61%;
+        }
+
+        #imgButtonContainer4 {
+            left: 79%;
+        }
+
+        .kinect-button.tile {
+            position: relative;
+            display: block;
+            width: 170px;
+            height: 100px;
+            margin: 30px auto;
+            padding: 0;
+        }
+
+        .kinect-button.tile .commandButtonContent {
+            position: absolute;
+            width: 100%;
+            height: 100%;
+            border-style: solid;
+            border-color: white;
+            border-width: 2px;
+            transition: border-width 0.15s;
+            left: -2px;
+            top: -2px;
+        }
+
+        .kinect-button.image {
+            position: relative;
+            display: block;
+            width: 170px;
+            height: 100px;
+            margin: 30px auto;
+            padding: 0;
+        }
+
+        .kinect-button img {
+            width: 100%;
+            height: 100%;
+        }
+
+        .kinect-button-text {
+            font-size: 14pt;
+            width: 100%;
+            height: 3em;
+            margin-top: -1.5em;
+        }
+
+        @media screen and (min-width: 1280px) and (max-width: 1600px) {
+            #buttonContainer {
+                height: 186px;
+            }
+
+            .kinect-button.tile {
+                width: 224px;
+                height: 126px;
+            }
+
+            .kinect-button.image {
+                width: 224px;
+                height: 126px;
+            }
+
+            .kinect-button-text {
+                font-size: 16pt;
+            }
+        }
+
+        @media screen and (min-width: 1600px) {
+            #buttonContainer {
+                height: 222px;
+            }
+
+            .kinect-button.tile {
+                width: 288px;
+                height: 162px;
+            }
+
+            .kinect-button.image {
+                width: 288px;
+                height: 162px;
+            }
+
+            .kinect-button-text {
+                font-size: 18pt;
+            }
+        }
+    </style>
+</head>
+<body>
+                    <div class="kinect-button" style="width:400px; height:300px;">button</div>
+    <div id="uiContainer">
+        <img id="logoImg" src="Images\logo.png"/>
+        <span id="sampleName">Kinect Web Basics</span>
+
+        <div id="backgroundContainer">
+            <!--<img id="backgroundImg" src="Images\fireworks.jpg"/>-->
+            <div id="backgroundImg" style="width:800px;height:600px;">foo klnsdsdfgsfgdfdb
+            </div>
+            <canvas id="userViewerCanvas" class="streamImageCanvas"></canvas>
+            <canvas id="backgroundRemovalCanvas" class="streamImageCanvas"></canvas>
+        </div>
+        <div id="buttonContainer">
+            <div id="choosePanel">
+                <div id="imgButtonContainer1" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/beach.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer2" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/fireworks.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer3" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/hamlet.jpg"/>
+                    </div>
+                </div>
+                <div id="imgButtonContainer4" class="imgButtonContainer">
+                    <div class="kinect-button image">
+                        <img src="Images/operahouse.jpg"/>
+                    </div>
+                </div>
+            </div>
+            <div class="commandButtonContainer">
+                <div id="togglePanelButton" class="kinect-button tile">
+                    <div class="commandButtonContent">
+                        <span class="kinect-button-text"></span>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/css/coolstyle.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/css/coolstyle.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/css/coolstyle.css	(revision 28897)
@@ -0,0 +1,231 @@
+html {
+    width: 100%;
+    height: 100%;
+    overflow: hidden;
+}
+
+body {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    /*overflow: hidden;*/
+    font-family: 'Segoe UI';
+}
+
+#pano{
+    width:100%;
+    height: 100%;
+    z-index: 0;
+}
+
+#WebGL{
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    left: 0px;
+    top: 0px;
+    pointer-events:none;
+    z-index: 10;
+}
+
+/*#uiContainer {
+    position: relative;
+    margin: 0 auto;
+    width: 100vw;
+    height: 100%;
+}*/
+
+#logoImg {
+    position: absolute;
+    top: 18px;
+    left: 26px;
+    width: 100px;
+    height: 40px;
+}
+
+#sampleName {
+    position: relative;
+    display: block;
+    text-align: right;
+    font-size: 16pt;
+    color: gray;
+    top: 24px;
+    margin-right: 26px;
+}
+
+/*#backgroundContainer {
+    position: absolute;
+    top: 80px;
+    width: 100vw;
+    height: 56.25vw;
+    margin-bottom: 26px;
+}*/
+
+@media screen and (max-width: 960px) {
+    #uiContainer {
+        width: 960px;
+    }
+
+    #backgroundContainer {
+        width: 960px;
+        height: 540px;
+    }
+}
+
+@media screen and (min-width: 1600px) {
+    #uiContainer {
+        width: 1600px;
+    }
+
+    #backgroundContainer {
+        width: 1600px;
+        height: 900px;
+    }
+}
+
+#backgroundImg {
+    width: 100%;
+    height: 100%;
+}
+
+.streamImageCanvas {
+    position: absolute;
+    width: 75%;
+    height: 100%;
+    top: 0;
+    left: 12.5%;
+    opacity: 0.0;
+    pointer-events:none;
+    transition: opacity 1.0s;
+    z-index: 5;
+}
+
+.streamImageCanvas.showing {
+    opacity: 1.0;
+    transition: opacity 1.0s;
+}
+
+#buttonContainer {
+    position: absolute;
+    display: block;
+    width: 100%;
+    height: 160px;
+    bottom: 0;
+}
+
+#choosePanel {
+    position: absolute;
+    background-color: white;
+    width: 100%;
+    height: 100%;
+    top: 100%;
+    transition: top 0.4s;
+}
+
+#choosePanel.showing {
+    top: 0;
+    transition: top 0.4s;
+}
+
+.commandButtonContainer {
+    position: absolute;
+    width: 18%;
+    height: 100%;
+    top: 0;
+    left: 3%;
+}
+
+.imgButtonContainer {
+    position: absolute;
+    width: 18%;
+    height: 100%;
+    top: 0;
+}
+
+/*#togglePanelButton {
+/* Add an explicit background color to make sure mouse/touch events get routed
+to button even when hovering/pressing over transparent area around button edge.
+background-color: rgba(0,0,0,0.0);
+}*/
+
+.kinect-button.tile {
+    position: relative;
+    display: block;
+    width: 170px;
+    height: 100px;
+    margin: 30px auto;
+    padding: 0;
+}
+
+.kinect-button.tile .commandButtonContent {
+    position: absolute;
+    width: 100%;
+    height: 100%;
+    border-style: solid;
+    border-color: white;
+    border-width: 2px;
+    transition: border-width 0.15s;
+    left: -2px;
+    top: -2px;
+}
+
+.kinect-button.image {
+    position: relative;
+    display: block;
+    width: 170px;
+    height: 100px;
+    margin: 30px auto;
+    padding: 0;
+}
+
+.kinect-button img {
+    width: 100%;
+    height: 100%;
+}
+
+.kinect-button-text {
+    font-size: 14pt;
+    width: 100%;
+    height: 3em;
+    margin-top: -1.5em;
+}
+
+@media screen and (min-width: 1280px) and (max-width: 1600px) {
+    #buttonContainer {
+        height: 186px;
+    }
+
+    .kinect-button.tile {
+        width: 224px;
+        height: 126px;
+    }
+
+    .kinect-button.image {
+        width: 224px;
+        height: 126px;
+    }
+
+    .kinect-button-text {
+        font-size: 16pt;
+    }
+}
+
+@media screen and (min-width: 1600px) {
+    #buttonContainer {
+        height: 222px;
+    }
+
+    .kinect-button.tile {
+        width: 288px;
+        height: 162px;
+    }
+
+    .kinect-button.image {
+        width: 288px;
+        height: 162px;
+    }
+
+    .kinect-button-text {
+        font-size: 18pt;
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/css/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/css/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/css/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doGSVP.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doGSVP.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doGSVP.js	(revision 28897)
@@ -0,0 +1,21 @@
+var seedlocation = { lat: -37.793491, lng: 175.316316 };
+
+window.addEventListener( 'load', function() {
+
+});
+
+function getPanoImage( panoId, callback ){
+	var loader = new GSVPANO.PanoLoader({
+		zoom: 1
+	});
+
+	loader.onPanoramaLoad = function(){
+		callback(this.canvas.toDataURL());
+	}
+
+	loader.onError = function( message ) {
+		alert( 'Error: ' + message );
+	};
+
+	loader.composePanorama(panoId);
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doKinect.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doKinect.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doKinect.js	(revision 28897)
@@ -0,0 +1,515 @@
+/*
+  var fenway = new google.maps.LatLng(42.345573,-71.098326);
+  var mapOptions = {
+    center: fenway,
+    zoom: 14
+  };
+  var map = new google.maps.Map(
+      document.getElementById('map-canvas'), mapOptions);
+  var panoramaOptions = {
+    position: fenway,
+    pov: {
+      heading: 34,
+      pitch: 10
+    }
+  };
+  var panorama = new  google.maps.StreetViewPanorama(document.getElementById('pano'),panoramaOptions);
+  map.setStreetView(panorama);
+  */
+
+"use strict";
+
+var SkeletonTrackingState = {
+    NotTracked: 0,
+    Position: 1,
+    Tracked: 2
+};
+var jointTrackingState = {
+    Inferred: 1,
+    NotTracked: 0,
+    Tracked: 2
+}; //need to work out which means its tracked, probably wrong right now
+var JointType = {
+    HipCenter: 0,
+    Spine: 1,
+    ShoulderCenter: 2,
+    Head: 3,
+    ShoulderLeft: 4,
+    ElbowLeft: 5,
+    WristLeft: 6,
+    HandLeft: 7,
+    ShoulderRight: 8,
+    ElbowRight: 9,
+    WristRight: 10,
+    HandRight: 11,
+    HipLeft: 12,
+    KneeLeft: 13,
+    Ankle: 14,
+    FootLeft: 15,
+    HipRight: 16,
+    KneeRight: 17,
+    AnkleRight: 18,
+    FootRight: 19
+}
+
+var offset = 0;
+var handPointer = null;
+var pressExtentY;
+var pressOriginY;
+var jumps;
+
+$( document ).ready( function() {
+
+    function testBox( col ) {
+        var b = document.createElement( 'div' );
+        b.style.width = '200px';
+        b.style.height = '50px';
+        b.style.position = 'relative';
+        b.style.top = '150px';
+        b.style.backgroundColor = col;
+        b.style.fontSize = 'x-large';
+        document.body.appendChild( b );
+        return b;
+    }
+    //var s4 = testBox('red');
+    //var s3 = testBox('green');
+
+
+    var streamImageWidth = 640;
+    var streamImageHeight = 480;
+    var streamImageResolution = streamImageWidth.toString() + "x" + streamImageHeight.toString();
+
+    var isSensorConnected = false;
+    var engagedUser = null;
+    var cursor = null;
+    var userViewerCanvasElement = null;
+    var backgroundRemovalCanvasElement = null;
+
+
+    var inPress = false;
+    var isGripping = false;
+
+    // Log errors encountered during sensor configuration
+    function configError( statusText, errorData ) {
+        console.log( ( errorData != null ) ? JSON.stringify( errorData ) : statusText );
+    }
+
+    // Determine if the specified object has any properties or not
+    function isEmptyObject( obj ) {
+        if ( obj == null ) {
+            return true;
+        }
+
+        var numProperties = 0;
+
+        for ( var prop in obj ) {
+            if ( obj.hasOwnProperty( prop ) ) {
+                ++numProperties;
+            }
+        }
+
+        return numProperties <= 0;
+    }
+
+    // Show or hide the cursor
+    function setCursorVisibility( isVisible ) {
+        if ( cursor == null ) {
+            return;
+        }
+
+        if ( isVisible ) {
+            cursor.show();
+        } else {
+            cursor.hide();
+        }
+    }
+
+    // Show or hide a canvas element
+    function setCanvasVisibility( canvasElement, isVisible ) {
+        if ( canvasElement == null ) {
+            return;
+        }
+
+        var canvasQuery = $( canvasElement );
+
+        if ( isVisible ) {
+            if ( !canvasQuery.hasClass( "showing" ) ) {
+                // Clear canvas before showing it
+                var canvasContext = canvasElement.getContext( "2d" );
+                canvasContext.clearRect( 0, 0, streamImageWidth, streamImageHeight );
+            }
+
+            canvasQuery.addClass( "showing" );
+        } else {
+            canvasQuery.removeClass( "showing" );
+        }
+    }
+
+
+    window.addEventListener( "handPointerGrip", function() {
+        //alert("I'm cool")
+        //linkHop();
+        isGripping = true;
+
+$('#kinect-cursor-normal').attr("xlink:href","#kinect-grip-hand-base");
+
+        //alert(handPointer.isInGripInteraction);
+        /*
+    var uiController = new KinectUIController();
+    var othercursor = document.getElementById(uiController.CURSOR_ID);
+    console.log(JSON.stringify(cursor));
+    console.log(JSON.stringify(othercursor));
+    $(othercursor).addClass("kinect-grip-hand-base");
+    */
+    } );
+
+    window.addEventListener( "handPointerGripRelease", function() {
+        isGripping = false;
+        $('#kinect-cursor-normal').attr("xlink:href","#kinect-open-hand-base");
+
+        if(selectedLink) {console.log("jumping to" + selectedLink); panorama.setPano(selectedLink.pano); selectedLink = null;}
+    } );
+
+    var pressOrigin;
+    var pressExtent = 0;
+    window.addEventListener( "handPointerPress", function() {
+        //alert("I'm cool")  
+        if ( !inPress ) {
+            inPress = true;
+        }
+    } );
+    /*
+    window.addEventListener( "handPointerPressRelease",function(){
+        //alert("I'm not cool")
+        //console.log("Press extended " + (pressExtent - pressOrigin) + " distance units");
+        var extent = (pressExtent - pressOrigin);
+        document.getElementById("kinectgripthing").innerHTML = "press extent: " + extent.toFixed(2) + "<br>" + "pressOrigin = " + pressOrigin.toFixed(2) + '<br>' + "pressDest = " + pressExtent.toFixed(2);
+        if(extent > .07){
+            if(!isGripping) linkHop();
+            else {  
+                steps = skipSteps;
+                jumpLoop(panorama.getPano());
+            }
+        }
+        if(inPress)
+            inPress = false;
+    });*/
+
+
+    /*document.getElementById( "pano" ).addEventListener( "handPointerLeave", function() {
+        isGripping = false;
+        $('#kinect-cursor-normal').attr("xlink:href","#kinect-open-hand-base");
+    } );*/
+
+    /*
+    document.getElementById( "pano" ).addEventListener( "handPointerEnter", function() {
+        console.log("Hand Entered");
+        if(handPointer){
+            if(handPointer.IsInGripInteraction){
+                $('#kinect-cursor-normal').attr("xlink:href","#kinect-grip-hand-base");
+                isGripping = true;
+                console.log("found hand grip");
+            }
+            else{
+                $('#kinect-cursor-normal').attr("xlink:href","#kinect-open-hand-base");
+                isGripping = false;
+                console.log("found hand normal");
+            }
+        }
+        else
+           { $('#kinect-cursor-normal').attr("xlink:href","#kinect-open-hand-base"); console.log("I goofed");}
+    } );
+    */
+
+    // Update sensor state and perform UI transitions (showing/hiding appropriate UI elements)
+    // related to sensor status or engagement state changes
+    var delayedConfigTimeoutId = null;
+
+    function updateUserState( newIsSensorConnected, newEngagedUser, sensorToConfig ) {
+        var hasEngagedUser = engagedUser != null;
+        var newHasEngagedUser = newEngagedUser != null;
+
+        // If there's a pending configuration change when state changes again, cancel previous timeout
+        if ( delayedConfigTimeoutId != null ) {
+            clearTimeout( delayedConfigTimeoutId );
+            delayedConfigTimeoutId = null;
+        }
+
+        if ( ( isSensorConnected != newIsSensorConnected ) || ( engagedUser != newEngagedUser ) ) {
+            if ( newIsSensorConnected ) {
+
+                var immediateConfig = {};
+                var delayedConfig = {};
+                immediateConfig[ Kinect.INTERACTION_STREAM_NAME ] = {
+                    "enabled": true
+                };
+                immediateConfig[ Kinect.USERVIEWER_STREAM_NAME ] = {
+                    "resolution": streamImageResolution
+                };
+                immediateConfig[ Kinect.BACKGROUNDREMOVAL_STREAM_NAME ] = {
+                    "resolution": streamImageResolution
+                };
+                immediateConfig[ Kinect.SKELETON_STREAM_NAME ] = {
+                    "enabled": true
+                };
+
+
+                setCursorVisibility( newHasEngagedUser );
+                setCanvasVisibility(userViewerCanvasElement, !newHasEngagedUser);
+                setCanvasVisibility( backgroundRemovalCanvasElement, newHasEngagedUser );
+
+                if ( newHasEngagedUser ) {
+                    immediateConfig[ Kinect.BACKGROUNDREMOVAL_STREAM_NAME ].enabled = true;
+                    immediateConfig[ Kinect.BACKGROUNDREMOVAL_STREAM_NAME ].trackingId = newEngagedUser;
+
+                    delayedConfig[ Kinect.USERVIEWER_STREAM_NAME ] = {
+                        "enabled": false
+                    };
+                } else {
+                    immediateConfig[ Kinect.USERVIEWER_STREAM_NAME ].enabled = true;
+
+                    if ( hasEngagedUser ) {
+                        delayedConfig[ Kinect.BACKGROUNDREMOVAL_STREAM_NAME ] = {
+                            "enabled": false
+                        };
+                    }
+                }
+
+                // Perform immediate configuration
+                sensorToConfig.postConfig( immediateConfig, configError );
+
+                // schedule delayed configuration for 2 seconds later
+                if ( !isEmptyObject( delayedConfig ) ) {
+                    delayedConfigTimeoutId = setTimeout( function() {
+                        sensorToConfig.postConfig( delayedConfig, configError );
+                        delayedConfigTimeoutId = null;
+                    }, 2000 );
+                }
+            } else {
+                setCursorVisibility( false );
+                setCanvasVisibility(userViewerCanvasElement, false);
+                setCanvasVisibility( backgroundRemovalCanvasElement, false );
+            }
+        }
+
+        isSensorConnected = newIsSensorConnected;
+        engagedUser = newEngagedUser;
+    }
+
+    // Get the id of the engaged user, if present, or null if there is no engaged user
+    function findEngagedUser( userStates ) {
+        var engagedUserId = null;
+
+        for ( var i = 0; i < userStates.length; ++i ) {
+            var entry = userStates[ i ];
+            if ( entry.userState == "engaged" ) {
+                engagedUserId = entry.id;
+                break;
+            }
+        }
+
+        return engagedUserId;
+    }
+
+    // Respond to user state change event
+    function onUserStatesChanged( newUserStates ) {
+        var newEngagedUser = findEngagedUser( newUserStates );
+
+        updateUserState( isSensorConnected, newEngagedUser, sensor );
+    }
+
+    // Create sensor and UI adapter layers
+    var sensor = Kinect.sensor( Kinect.DEFAULT_SENSOR_NAME, function( sensorToConfig, isConnected ) {
+        if ( isConnected ) {
+            // Determine what is the engagement state upon connection
+            sensorToConfig.getConfig( function( data ) {
+                var engagedUserId = findEngagedUser( data[ Kinect.INTERACTION_STREAM_NAME ].userStates );
+
+                //alert( JSON.stringify( data ) );
+
+                updateUserState( true, engagedUserId, sensorToConfig );
+            } );
+
+        } else {
+            updateUserState( false, engagedUser, sensorToConfig );
+        }
+    } );
+
+    var uiAdapter = KinectUI.createAdapter( sensor );
+    //KinectUI.setIsGripTarget(document.getElementById("kinectgripthing"), true);
+    //KinectUI.setIsGripTarget(document.getElementById("pano"), true);
+    uiAdapter.promoteButtons();
+    cursor = uiAdapter.createDefaultCursor();
+
+    userViewerCanvasElement = document.getElementById("userViewerCanvas");
+    backgroundRemovalCanvasElement = document.getElementById( "backgroundRemovalCanvas" );
+    //uiAdapter.bindStreamToCanvas(Kinect.USERVIEWER_STREAM_NAME, userViewerCanvasElement);
+    uiAdapter.bindStreamToCanvas( Kinect.BACKGROUNDREMOVAL_STREAM_NAME, backgroundRemovalCanvasElement );
+
+
+    var oldHeading = 0;
+    sensor.addStreamFrameHandler( function( frame ) {
+        //document.getElementById("kinectgripthing").innerHTML="pie" +offset+ " pie2 " + isGripping;
+
+        switch ( frame.stream ) {
+            case Kinect.SKELETON_STREAM_NAME:
+                for ( var iSkeleton = 0; iSkeleton < frame.skeletons.length; ++iSkeleton ) {
+                    var skeleton = frame.skeletons[ iSkeleton ];
+
+                    if ( skeleton.trackingState == 2 && skeleton.trackingId == engagedUser ) {
+
+                   //console.log("number of uiAdapter handPointers is " + uiAdapter.handPointers.length);
+                    for(var j = 0; j < uiAdapter.handPointers.length; j++){
+                        if(uiAdapter.handPointers[j].getIsPrimaryHandOfPrimaryUser() && uiAdapter.handPointers[j].isTracked) {
+                            handPointer = uiAdapter.handPointers[j]; 
+                            
+
+                                pressExtentY = handPointer.rawY;
+
+                                if ( !isGripping ) {
+                                    pressOriginY = handPointer.rawY;
+                                } else {
+                                    var diffY = ( -pressExtentY + pressOriginY );
+                                    var newJumps = Math.round( diffY * 15 );
+                                    //if ( jumps != newJumps ) {
+                                        steps = jumps = newJumps;
+                                        //alert(jumps);
+                                        //document.getElementById( "kinectgripthing" ).innerHTML = "press extentY: " + diffY.toFixed( 2 ) + "<br>" + "pressOriginY = " + pressOriginY.toFixed( 2 ) + '<br>' + "newJumps = " + newJumps;
+                                        //jumpLoop( panorama.getPano() );
+                                        selecto(steps);
+                                    //}
+                                }
+
+                               
+                                if ( !inPress ) {
+                                    pressOrigin = handPointer.rawZ;
+                                }
+                                else if ( inPress ) {
+                                     pressExtent = handPointer.rawZ;
+                                    //console.log(cursor);
+                                    var extent = ( pressExtent - pressOrigin );
+                                       //document.getElementById( "kinectgripthing" ).innerHTML = "extent check has failed <br> pressOrigin = " + pressOrigin.toFixed( 2 ) + '<br>' + "pressDest = " + pressExtent.toFixed( 2 )+ "<br>" + "press extent: " + extent.toFixed( 2 );
+                                    if ( extent > .05 ) {
+                                        
+                                        //document.getElementById( "kinectgripthing" ).innerHTML = "extent check has passed <br> pressOrigin = " + pressOrigin.toFixed( 2 ) + '<br>' + "pressDest = " + pressExtent.toFixed( 2 ) + "<br>" + "press extent: " + extent.toFixed( 2 );
+                                        
+                                        /*console.log( "link hopping" );
+                                        linkHop();*/
+
+                                        inPress = false;
+                                    }
+                                }
+
+
+                                    break;} 
+                            }
+                       
+
+                        var x = skeleton.boneOrientations[ JointType.ShoulderCenter ].absoluteRotation.matrix.m13;
+                        var y = skeleton.boneOrientations[ JointType.ShoulderCenter ].absoluteRotation.matrix.m23;
+                        var z = skeleton.boneOrientations[ JointType.ShoulderCenter ].absoluteRotation.matrix.m33;
+
+                        //var tilt = skeleton.joints[JointType.Head].position.z - skeleton.joints[JointType.ShoulderCenter].position.z; 
+
+                        var len = x * x + z * z;
+                        x = x / len;
+                        z = z / len;
+
+                        var orient = Math.atan2( x, z );
+
+                        var heading = orient * ( 180 / Math.PI ) * 2 + 70;
+                        var pitch = y * 180 - 20;
+
+                        //heading = 0.9 * oldHeading + 0.1 * heading;
+                        //if(heading > 360){alert("heading hsa gone full circle and more")}
+                        oldHeading = heading;
+                        var pov = panorama.getPov();
+                        //alert(cursor);
+                        //console.log(cursor.IsInGripInteraction);
+
+                        //if the user is not gripping, move their view
+                        if ( !isGripping ) {
+                            pov.heading = heading + offset;
+                            pov.pitch = pitch;
+
+                            panorama.setPov( pov );
+                            SyncThreeCamera( pov.heading, pov.pitch );
+                        } else {
+                            offset = ( pov.heading + 360 ) - ( heading + 360 );
+                            offset = offset % 360;
+                            //alert(offset);
+                        }
+                    }
+                    //SyncThreeCamera(pov.heading, pov.pitch)
+
+                    //var kin = document.getElementById("kinectgripthing");
+                    //kin.innerHTML = x + "<br>" + y + "<br>" + z +"<br>" + Math.atan2(x, z);
+                    //kin.innerHTML = "Heading is " + "<br>" + heading;
+                    //if(g){console.log(JSON.stringify(skeleton)); g = !g}
+                    //s1.left = skeleton.joint[]
+
+                    /*skeleton.trackingId;
+                    skeleton.trackingState;
+                    skeleton.position;*/
+                }
+
+                break;
+        }
+    } );
+    //braeden done
+
+    sensor.addEventHandler( function( event ) {
+        switch ( event.category ) {
+            case Kinect.USERSTATE_EVENT_CATEGORY:
+                switch ( event.eventType ) {
+                    case Kinect.USERSTATESCHANGED_EVENT_TYPE:
+                        onUserStatesChanged( event.userStates );
+                        break;
+                }
+                break;
+        }
+    } );
+} );
+
+
+function modePov( list ) {
+    var count = 0;
+    var mode;
+    for ( var i = 0; i < list.length - 1; i++ ) {
+        var tempCount = 0;
+        for ( var j = 1; j < list.length; j++ ) {
+            console.log( list[ i ].heading + " against " + list[ j ].heading )
+            if ( list[ i ].heading == list[ i ].heading ) {
+                tempCount++;
+                //alert("BANG BANG");
+            }
+        }
+        if ( tempCount > count ) {
+            mode = list[ i ];
+            count = tempCount;
+        } //console.log("setting mode from heading " + mode.heading);
+    }
+    console.log( "mode had frequency of " + count );
+    //console.log(list);
+    return mode;
+}
+
+document.addEventListener( 'keydown', function( e ) {
+    var keyCode = e.keyCode;
+    /*sensor.getConfig((function(data){
+        alert("ALERT ALERT Keydown detected \r\n" + JSON.stringify(data))
+    }));*/
+} );
+
+function linkHop() {
+    var panoLink = pickHop( panorama.getLinks());
+    console.log(panoLink);
+    panorama.setPano(panoLink.pano);
+}
+
+function keyDownTextField(e){
+    var keyCode = e.keyCode;
+    if(keyCode == 69){
+        selectedLink = selecto(4);
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doStreetView.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doStreetView.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/doStreetView.js	(revision 28897)
@@ -0,0 +1,377 @@
+//https://maps.google.co.nz/maps/myplaces?ll=-38.75087,174.607258&spn=0.052479,0.154324&ctz=-780&t=h&z=13&layer=c&cbll=-38.750878,174.607314&panoid=N3qt_joqFI-93LKlaJBqBQ&cbp=11,198.35,,0,2.89
+//https://maps.google.co.nz/?ll=-37.793237,175.303775&spn=0.001833,0.002411&t=h&z=19&layer=c&cbll=-37.793237,175.303775&panoid=qCIp44Wk4np0MUIEw_wjEA&cbp=12,98.51,,0,0
+var fenway = new google.maps.LatLng(-37.793237, 175.303775);
+var botswana = new google.maps.LatLng(-20.472393999999998, 24.515585999999995);
+var NowyJork = new google.maps.LatLng(42.435272, -73.789272);
+var sandy = new google.maps.LatLng(40.554113, -74.110851);
+var sanFran = new google.maps.LatLng(37.775679, -122.425017);
+var HMSOcelot = new google.maps.LatLng(51.395492,0.526726);
+
+var geocoo = new google.maps.Geocoder();
+
+
+$.urlParam = function(name){
+Â  Â  var results = new RegExp('[\\?&]' + name + '=([^&#]*)').exec(window.location.href);
+Â  Â  if (results==null){
+Â  Â  Â  Â return null;
+Â  Â  }
+Â  Â  else{
+Â  Â  Â  Â return results[1] || 0;
+Â  Â  }
+}
+
+var startLocation;
+var steps;
+
+places = { hamilton: fenway,
+          botswana: botswana,
+          newyork: NowyJork,
+          sandy: sandy,
+          sanFran: sanFran,
+          submarine: HMSOcelot
+ }
+
+var place = $.urlParam('place');
+
+
+
+if(places[place]){
+  startLocation = places[place];
+  coolParty();
+}
+else if (!!place)
+{
+  geocoo.geocode({address:decodeURIComponent(place)}, function(data, status){
+    console.log(data);
+    if(data[0])
+      startLocation = data[0].geometry.location;
+    else
+      startLocation = fenway;
+    coolParty();
+  });
+}
+else{ startLocation = fenway 
+  coolParty();
+  }
+
+
+var selectedLink;
+var timeoutHandle;
+
+var panorama;
+var dataService;
+
+var SVPaths = [];
+
+function coolParty(){
+
+var map = new google.maps.Map(
+  document.getElementById('map-canvas'));
+var panoramaOptions = {
+  position: startLocation,
+  pov: {
+    heading: 90,
+    pitch: 10,
+    zoom: 1
+
+  },
+  linksControl:false,
+  panControl:false,
+  disableDefaultUI: true
+};
+
+ dataService = new google.maps.StreetViewService();
+ panorama = new google.maps.StreetViewPanorama(document.getElementById('pano'), panoramaOptions);
+map.setStreetView(panorama);
+
+
+
+google.maps.event.addListener(panorama, "pov_changed", function() {
+  SyncThreeCamera(panorama.getPov().heading, panorama.getPov().pitch); //moved back to kinect
+});
+
+google.maps.event.addListener(panorama, "links_changed", function() {
+  console.log("nuking spheres and creating paths");
+  nukeSpheres();
+  createPaths();
+
+});
+google.maps.event.addListener(panorama, "zoom_changed", function(){
+  camera = cameraFromGoogleZoom(panorama.getZoom());
+  SyncThreeCamera(panorama.getPov().heading, panorama.getPov().pitch)
+});
+}
+/*
+function LoadLinks( depth ) {
+  var links = panorama.getLinks();
+  for ( var i = 0; i < links.length; ++i ) {
+
+    ( function( panoLink ) {
+      dataService.getPanoramaById( panoLink.pano, function( svData, svStatus ) {
+        if ( svStatus == google.maps.StreetViewStatus.OK ) {
+          var distance = google.maps.geometry.spherical.computeDistanceBetween( panorama.getPosition(), svData.location.latLng );
+          var heading = panoLink.heading;
+        } else console.log( svData );
+
+      } );
+    } )( links[ i ] )
+
+  }
+}*/
+
+var skipSteps = 2;
+
+
+
+function selecto(newIndex) {
+  var index = newIndex - 1;
+  var pathIndex = pickLink(panorama.getLinks());
+
+  //document.getElementById("kinectgripthing").innerHTML = "path: " + newIndex;
+
+  if(index >= SVPaths[pathIndex].length)
+    index = SVPaths[pathIndex].length - 1;
+  if (index >= 0 && SVPaths[pathIndex].length > index) {
+    selectSphere(SVPaths[pathIndex][index]);
+    return SVPaths[pathIndex][index];
+  } else
+    deselectSphere();
+}
+
+function selectSphere(link) {
+  if (link !== selectedLink) {
+    deselectSphere();
+    selectedLink = link;
+    if (selectedLink.sphere) {
+      selectedLink.sphere.scale = new THREE.Vector3(2, 2, 2);
+      selectoMore(selectedLink.sphere);
+    }
+  }
+}
+
+function deselectSphere() {
+  if (selectedLink && selectedLink.sphere){
+    selectedLink.sphere.scale = new THREE.Vector3(1, 1, 1);
+    if (selectedLink.sphere.anim)
+      userUnselectSphere(selectedLink.sphere);
+  }
+
+  selectedLink = null;
+  if (timeoutHandle !== null){
+    window.clearTimeout(timeoutHandle);
+    console.log("CLEARED TIMER")
+  }
+}
+
+var selectTime = 700;
+function selectoMore(sphere) {
+
+  timeoutHandle = window.setTimeout(function() {
+    userSelectSphere(sphere);
+
+/*
+    //separate stage 1
+    sphere.startTime = performance.now();
+    var speed = sphere.distance / selectTime; 
+    //alert("heading equals " + sphere.heading + " and x equals " + sphere.position.x + " and distance equals " + sphere.distance );
+    //alert("all up is " + Math.sin(sphere.heading) * (sphere.position.x / sphere.distance));
+    var vec = new THREE.Vector3(Math.sin(sphere.heading) * (sphere.position.x / sphere.distance) , 0 , Math.cos(sphere.heading) * (sphere.position.z / sphere.distance) );
+    //alert("Raw vec is");
+    //alert(JSON.stringify(vec));
+    vec.normalize();
+    vec.multiplyScalar(speed);
+    //alert("scaled vec is");
+    //alert(JSON.stringify(vec));
+    sphere.vec = vec;
+*/
+    /*
+  
+    document.getElementById("kinectgripthing").innerHTML= "sphere " + sphere.id + ":<br> " + JSON.stringify(vec) + "<br>";
+    spheros.push(sphere);//begins animation
+*/
+
+  }, selectTime);
+    console.log("SETTIMER");
+}
+
+
+function userSelectSphere(sphere){
+  //alert(JSON.stringify(getHipCameraPoint()));
+  sphere.anim = new Ease(sphere.startPos,getHipCameraPoint(), 500);
+}
+function userUnselectSphere(sphere){
+  var timeo = Math.min(500, sphere.anim.time)
+  sphere.anim = new Ease(sphere.position, sphere.startPos, timeo);
+}
+
+
+function jumpLoop(startPano) {
+
+  //console.log("entered jumpLoop with " + steps + " steps")
+  steps--;
+  getLinks(startPano, jumpLoopFinish);
+}
+
+function getLinks(startPano, finalMethod) {
+  dataService.getPanoramaById(startPano, function(StreetViewPanoramaData, StreetViewStatus) {
+
+    if (StreetViewStatus == "OK") pickHop(StreetViewPanoramaData.links, finalMethod);
+    else {
+      console.log("StreetViewStatus was " + StreetViewStatus + " so I gave up")
+    };
+  });
+}
+
+function jumpLoopFinish(responsePano) {
+  if (steps <= 0) { //break from loop 
+    //panorama.setPano(responsePano);
+    dataService.getPanoramaById(responsePano.pano, function(svData, svStatus) {
+      if (svStatus == google.maps.StreetViewStatus.OK) {
+        var distance = google.maps.geometry.spherical.computeDistanceBetween(panorama.getPosition(), svData.location.latLng);
+        console.log("jumping to " + responsePano.pano);
+        var heading = google.maps.geometry.spherical.computeHeading(panorama.getPosition(), svData.location.latLng);
+        //createPanoSphere( heading, distance, responsePano );
+        panorama.setPano(responsePano.pano);
+      }
+    });
+    skipSteps++;
+    return;
+  }
+  console.log("steps = " + steps + " and going another round now from " + responsePano);
+  jumpLoop(responsePano.pano);
+}
+
+/**
+ *links, the collection of google streetview links
+ *finalMethod, method to pass result on to
+ **/
+
+function pickLink(links) { //based off the current pano's heading return next forward jump
+  //console.log("Starting jump from " + startPano);
+  var setPano = null;
+  var povHeading = panorama.getPov().heading;
+  var minDiff = 360;
+  var theIndex;
+
+  //console.log("Starting picklink panorama pano is " + startPano);//+ " and startPano is " + startPano
+  for (var k = 0; k < links.length; k++) {
+
+    var linkBearing = (links[k].heading + 360) % 360;
+    var povHead = (povHeading + 360) % 360;
+
+    var diff = Math.abs(linkBearing - povHead);
+    //console.log("diff is " + diff + " at " + dummyLinks[k].pano);
+    if (diff > 180) {
+      diff = 360 - diff;
+    }
+    if (minDiff > diff) {
+      minDiff = diff;
+      setPano = links[k];
+      theIndex = k;
+    }
+    // console.log(links[k].description + " " + k + ": " + linkBearing + " minus " + povHead + " = " + (linkBearing - povHead) + ", mindiff is " + minDiff);
+
+  }
+  //console.log("Picked " + setPano + '\n');
+  //finalMethod( theIndex );
+  //console.log("k is " +k);
+  //console.log(theIndex);
+  return theIndex;
+}
+
+function pickHop(links) { //based off the current pano's heading return next forward jump
+  var setPano = null;
+  var povHeading = panorama.getPov().heading;
+  var minDiff = 360;
+
+  for (var k = 0; k < links.length; k++) {
+
+    var linkBearing = (links[k].heading + 360) % 360;
+    var povHead = (povHeading + 360) % 360;
+
+    var diff = Math.abs(linkBearing - povHead);
+    //console.log("diff is " + diff + " at " + dummyLinks[k].pano);
+    if (diff > 180) {
+      diff = 360 - diff;
+    }
+    if (minDiff > diff) {
+      minDiff = diff;
+      setPano = links[k];
+    }
+
+  }
+
+  return setPano;
+}
+
+function createPaths() {
+  console.log("Recomputing Paths");
+  var newPaths = panorama.getLinks();
+  SVPaths = [];
+
+  for (var i = 0; i < newPaths.length; ++i) {
+    SVPaths[i] = [newPaths[i]];
+    extendPath(SVPaths[i], 10);
+  }
+  console.log(SVPaths);
+}
+
+function extendPath(path, steps) {
+  var curLink = path[path.length - 1];
+
+  dataService.getPanoramaById(curLink.pano, function(svData, svStatus) {
+
+    if (svStatus == google.maps.StreetViewStatus.OK) {
+
+      var distance = google.maps.geometry.spherical.computeDistanceBetween(panorama.getPosition(), svData.location.latLng);
+      var heading = google.maps.geometry.spherical.computeHeading(panorama.getPosition(), svData.location.latLng);
+
+      createPanoSphere(heading, distance, curLink, svData.tiles.centerHeading);
+      curLink.distance = distance;
+      curLink.heading = heading;
+    } else
+      alert("Ohhh myyy goooooooooooood");
+
+
+    if (svStatus == "OK") pickExtendLink(curLink.heading, svData.links, function(nextLink) {
+
+      path.push(nextLink);
+
+      if (steps > 1)
+        extendPath(path, steps - 1);
+    });
+    else
+      console.log("StreetViewStatus was " + svStatus + " so I gave up");
+
+  });
+}
+
+function pickExtendLink(heading, links, finalMethod) { //based off the current pano's heading return next forward jump
+  //console.log("Starting jump from " + startPano);
+  var setPano = null;
+  var povHeading = heading;
+  var minDiff = 360;
+
+  //console.log("Starting picklink panorama pano is " + startPano);//+ " and startPano is " + startPano
+  for (var k = 0; k < links.length; k++) {
+
+    var linkBearing = (links[k].heading + 360) % 360;
+    var povHead = (povHeading + 360) % 360;
+
+    var diff = Math.abs(linkBearing - povHead);
+    //console.log("diff is " + diff + " at " + dummyLinks[k].pano);
+    if (diff > 180) {
+      diff = 360 - diff;
+    }
+    if (minDiff > diff) {
+      minDiff = diff;
+      setPano = links[k];
+    }
+    // console.log(links[k].description + " " + k + ": " + linkBearing + " minus " + povHead + " = " + (linkBearing - povHead) + ", mindiff is " + minDiff);
+
+  }
+  //console.log("Picked " + setPano + '\n');
+  finalMethod(setPano);
+  return setPano;
+}
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/.gitignore
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/.gitignore	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/.gitignore	(revision 28897)
@@ -0,0 +1,4 @@
+.DS_Store
+*.swp
+.project
+utils/npm/node_modules/*
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/CONTRIBUTING.md
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/CONTRIBUTING.md	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/CONTRIBUTING.md	(revision 28897)
@@ -0,0 +1,27 @@
+# The issues section is for bug reports and feature requests only. If you need help, please use [stackoverflow](http://stackoverflow.com/questions/tagged/three.js).
+
+
+Before reporting a bug
+---
+1. Search issue tracker for similar issues.
+2. Try the latest dev branch version of three.js.
+3. Refer to the [Migration Guide](https://github.com/mrdoob/three.js/wiki/Migration) when upgrading to the dev version.
+
+
+How to report a bug
+---
+1. Specify the revision number of the three.js library where the bug occurred.
+2. Specify your browser version, operating system, and graphics card. (for example, Chrome 23.0.1271.95, Windows 7, Nvidia Quadro 2000M)
+3. Describe the problem in detail. Explain what happened, and what you expected would happen.
+4. Provide a small test-case (http://jsfiddle.net). If a test-case is not possible, provide a link to a live version of your application.
+5. If helpful, include a screenshot. Annotate the screenshot for clarity.
+
+
+How to contribute to three.js
+---
+1. Make sure you have a GitHub account.
+2. Fork the repository on GitHub.
+3. Check the [Contribution Guidelines](https://github.com/mrdoob/three.js/wiki/How-to-contribute-to-three.js).
+4. Make changes to your clone of the repository.
+5. Submit a pull request.
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/LICENSE
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/LICENSE	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/LICENSE	(revision 28897)
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2010-2013 three.js authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/README.md
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/README.md	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/README.md	(revision 28897)
@@ -0,0 +1,69 @@
+three.js
+========
+
+#### JavaScript 3D library ####
+
+The aim of the project is to create a lightweight 3D library with a very low level of complexity â in other words, for dummies. The library provides &lt;canvas&gt;, &lt;svg&gt;, CSS3D and WebGL renderers.
+
+[Examples](http://threejs.org/) â [Documentation](http://threejs.org/docs/) â [Migrating](https://github.com/mrdoob/three.js/wiki/Migration) â [Help](http://stackoverflow.com/questions/tagged/three.js)
+
+
+### Usage ###
+
+Download the [minified library](http://threejs.org/build/three.min.js) and include it in your html.
+Alternatively see [how to build the library yourself](https://github.com/mrdoob/three.js/wiki/build.py,-or-how-to-generate-a-compressed-Three.js-file).
+
+```html
+<script src="js/three.min.js"></script>
+```
+
+This code creates a scene, then creates a camera, adds the camera and cube to the scene, creates a &lt;canvas&gt; renderer and adds its viewport in the document.body element.
+
+```html
+<script>
+
+	var camera, scene, renderer;
+	var geometry, material, mesh;
+
+	init();
+	animate();
+
+	function init() {
+
+		camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 1, 10000 );
+		camera.position.z = 1000;
+
+		scene = new THREE.Scene();
+
+		geometry = new THREE.CubeGeometry( 200, 200, 200 );
+		material = new THREE.MeshBasicMaterial( { color: 0xff0000, wireframe: true } );
+
+		mesh = new THREE.Mesh( geometry, material );
+		scene.add( mesh );
+
+		renderer = new THREE.CanvasRenderer();
+		renderer.setSize( window.innerWidth, window.innerHeight );
+
+		document.body.appendChild( renderer.domElement );
+
+	}
+
+	function animate() {
+
+		// note: three.js includes requestAnimationFrame shim
+		requestAnimationFrame( animate );
+
+		mesh.rotation.x += 0.01;
+		mesh.rotation.y += 0.02;
+
+		renderer.render( scene, camera );
+
+	}
+
+</script>
+```
+If everything went well you should see [this](http://jsfiddle.net/ksRyQ/).
+
+### Change log ###
+
+[releases](https://github.com/mrdoob/three.js/releases)
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/bower.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/bower.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/bower.json	(revision 28897)
@@ -0,0 +1,27 @@
+{
+	"name": "three.js",
+	"version": "0.0.65",
+	"homepage": "http://threejs.org/",
+	"description": "JavaScript 3D library",
+	"main": "build/three.js",
+	"keywords": [
+		"three",
+		"threejs",
+		"three.js",
+		"3D",
+		"webgl"
+	],
+	"license": "MIT",
+	"ignore": [
+		"**/.*",
+		"*.md",
+		"bower.json",
+		"docs",
+		"editor",
+		"examples",
+		"src",
+		"test",
+		"utils",
+		"LICENSE"
+	]
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/three.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/three.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/three.js	(revision 28897)
@@ -0,0 +1,37299 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author Larry Battle / http://bateru.com/news
+ * @author bhouston / http://exocortex.com
+ */
+
+var THREE = { REVISION: '65' };
+
+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 ( optionalTarget ) {
+
+		// h,s,l ranges are in 0.0 - 1.0
+
+		var hsl = optionalTarget || { h: 0, s: 0, l: 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 ) {
+
+		console.warn( "DEPRECATED: Vector3\'s .getPositionFromMatrix() has been renamed to .setFromMatrixPosition(). Please update your code." );
+
+		return this.setFromMatrixPosition( m );
+
+	},
+
+	getScaleFromMatrix: function ( m ) {
+
+		console.warn( "DEPRECATED: Vector3\'s .getScaleFromMatrix() has been renamed to .setFromMatrixScale(). Please update your code." );
+
+		return this.setFromMatrixScale( m );
+	},
+
+	getColumnFromMatrix: function ( index, matrix ) {
+
+		console.warn( "DEPRECATED: Vector3\'s .getColumnFromMatrix() has been renamed to .setFromMatrixColumn(). Please update your code." );
+
+		return this.setFromMatrixColumn( index, matrix );
+
+	},
+
+	setFromMatrixPosition: function ( m ) {
+
+		this.x = m.elements[ 12 ];
+		this.y = m.elements[ 13 ];
+		this.z = m.elements[ 14 ];
+
+		return this;
+
+	},
+
+	setFromMatrixScale: 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;
+	},
+
+	setFromMatrixColumn: 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, optionalTarget ) {
+
+		// This can potentially have a divide by zero if the box
+		// has a size dimension of 0.
+
+		var result = optionalTarget || new THREE.Vector2();
+
+		return result.set(
+			( 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, optionalTarget ) {
+
+		// This can potentially have a divide by zero if the box
+		// has a size dimension of 0.
+
+		var result = optionalTarget || new THREE.Vector3();
+
+		return result.set(
+			( 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;
+
+	},
+
+	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 ]
+		];
+
+	},
+
+	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.setFromMatrixPosition( 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();
+
+			// if determine is negative, we need to invert one scale
+			var det = this.determinant();
+			if( det < 0 ) {
+				sx = -sx;
+			}
+
+			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();
+		var m1 = new THREE.Matrix3();
+
+		return function ( matrix, optionalNormalMatrix ) {
+
+			// compute new normal based on theory here:
+			// http://www.songho.ca/opengl/gl_normaltransform.html
+			var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix );
+			var newNormal = v1.copy( this.normal ).applyMatrix3( normalMatrix );
+			
+			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 <a, b>
+
+	clamp: function ( x, a, b ) {
+
+		return ( x < a ) ? a : ( ( x > b ) ? b : x );
+
+	},
+
+	// Clamp value to range <a, inf)
+
+	clampBottom: function ( x, a ) {
+
+		return x < a ? a : x;
+
+	},
+
+	// Linear mapping from range <a1, a2> to range <b1, b2>
+
+	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 <low, high> interval
+
+	randInt: function ( low, high ) {
+
+		return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+	},
+
+	// Random float from <low, high> 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 listenerArray = listeners[ type ];
+
+		if ( listenerArray !== undefined ) {
+
+			var index = listenerArray.indexOf( listener );
+
+			if ( index !== - 1 ) {
+
+				listenerArray.splice( index, 1 );
+
+			}
+
+		}
+
+	},
+
+	dispatchEvent: function () {
+
+		var array = [];
+
+		return function ( event ) {
+
+			if ( this._listeners === undefined ) return;
+
+			var listeners = this._listeners;
+			var listenerArray = listeners[ event.type ];
+
+			if ( listenerArray !== undefined ) {
+
+				event.target = this;
+
+				var length = listenerArray.length;
+
+				for ( var i = 0; i < length; i ++ ) {
+
+					array[ i ] = listenerArray[ i ];
+
+				}
+
+				for ( var i = 0; i < length; i ++ ) {
+
+					array[ 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.Sprite ) {
+
+			matrixPosition.setFromMatrixPosition( 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.setFromMatrixPosition( 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 ]
+							);
+
+							
+							if ( material.side === THREE.BackSide ) {
+							
+								var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true ); 
+
+							} else {
+
+								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 ]
+						);
+
+						
+						if ( material.side === THREE.BackSide ) {
+							
+							var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true ); 
+
+						} else {
+
+							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 ];
+
+					if ( material.morphTargets === true ) {
+
+						var morphTargets = geometry.morphTargets;
+						var morphInfluences = object.morphTargetInfluences;
+
+						vA.set( 0, 0, 0 );
+						vB.set( 0, 0, 0 );
+						vC.set( 0, 0, 0 );
+
+						for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) {
+
+							var influence = morphInfluences[ t ];
+
+							if ( influence === 0 ) continue;
+
+							var targets = morphTargets[ t ].vertices;
+
+							vA.x += ( targets[ face.a ].x - a.x ) * influence;
+							vA.y += ( targets[ face.a ].y - a.y ) * influence;
+							vA.z += ( targets[ face.a ].z - a.z ) * influence;
+
+							vB.x += ( targets[ face.b ].x - b.x ) * influence;
+							vB.y += ( targets[ face.b ].y - b.y ) * influence;
+							vB.z += ( targets[ face.b ].z - b.z ) * influence;
+
+							vC.x += ( targets[ face.c ].x - c.x ) * influence;
+							vC.y += ( targets[ face.c ].y - c.y ) * influence;
+							vC.z += ( targets[ face.c ].z - c.z ) * influence;
+
+						}
+
+						vA.add( a );
+						vB.add( b );
+						vC.add( c );
+
+						a = vA;
+						b = vB;
+						c = vC;
+
+					}
+
+					if ( material.side === THREE.BackSide ) {
+							
+						var intersectionPoint = localRay.intersectTriangle( c, b, a, true );
+
+					} else {
+								
+						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 rotation () { 
+		return this._rotation; 
+	},
+
+	set rotation ( value ) {
+		
+		this._rotation = value;
+		this._rotation._quaternion = this._quaternion;
+		this._quaternion._euler = this._rotation;
+		this._rotation._updateQuaternion();
+		
+	},
+
+	get quaternion () { 
+		return this._quaternion; 
+	},
+	
+	set quaternion ( value ) {
+		
+		this._quaternion = value;
+		this._quaternion._euler = this._rotation;
+		this._rotation._quaternion = this._quaternion;
+		this._quaternion._updateEuler();
+		
+	},
+
+	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 ( matrix ) {
+
+		this.matrix.multiplyMatrices( matrix, this.matrix );
+
+		this.matrix.decompose( this.position, this.quaternion, this.scale );
+
+	},
+
+	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,
+	_sprite, _spriteCount, _spritePool = [], _spritePoolLength = 0,
+
+	_renderData = { objects: [], sprites: [], lights: [], elements: [] },
+
+	_vA = new THREE.Vector3(),
+	_vB = new THREE.Vector3(),
+	_vC = new THREE.Vector3(),
+
+	_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 () {
+
+		var projectionMatrixInverse = new THREE.Matrix4();
+
+		return function ( vector, camera ) {
+
+			projectionMatrixInverse.getInverse( camera.projectionMatrix );
+			_viewProjectionMatrix.multiplyMatrices( camera.matrixWorld, 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.setFromMatrixPosition( object.matrixWorld );
+			_vector3.applyProjection( _viewProjectionMatrix );
+			_object.z = _vector3.z;
+
+		}
+
+		return _object;
+
+	};
+
+	var projectVertex = function ( vertex ) {
+
+		var position = vertex.position;
+		var positionWorld = vertex.positionWorld;
+		var positionScreen = vertex.positionScreen;
+
+		positionWorld.copy( position ).applyMatrix4( _modelMatrix );
+		positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix );
+
+		var invW = 1 / positionScreen.w;
+
+		positionScreen.x *= invW;
+		positionScreen.y *= invW;
+		positionScreen.z *= invW;
+
+		vertex.visible = positionScreen.x >= -1 && positionScreen.x <= 1 &&
+				 positionScreen.y >= -1 && positionScreen.y <= 1 &&
+				 positionScreen.z >= -1 && positionScreen.z <= 1;
+
+	};
+
+	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 ) {
+
+			_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,
+		object, geometry, vertices, faces, face, faceVertexNormals, faceVertexUvs, uvs,
+		v1, v2, v3, v4, isFaceMaterial, objectMaterials;
+
+		_face3Count = 0;
+		_lineCount = 0;
+		_spriteCount = 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 ( var 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 ( var v = 0, vl = vertices.length; v < vl; v ++ ) {
+
+					_vertex = getNextVertexInPool();
+					_vertex.position.copy( vertices[ v ] );
+
+					projectVertex( _vertex );
+
+				}
+
+				for ( var 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 ];
+
+					if ( material.morphTargets === true ) {
+
+						var morphTargets = geometry.morphTargets;
+						var morphInfluences = object.morphTargetInfluences;
+
+						var v1p = v1.position;
+						var v2p = v2.position;
+						var v3p = v3.position;
+
+						_vA.set( 0, 0, 0 );
+						_vB.set( 0, 0, 0 );
+						_vC.set( 0, 0, 0 );
+
+						for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) {
+
+							var influence = morphInfluences[ t ];
+
+							if ( influence === 0 ) continue;
+
+							var targets = morphTargets[ t ].vertices;
+
+							_vA.x += ( targets[ face.a ].x - v1p.x ) * influence;
+							_vA.y += ( targets[ face.a ].y - v1p.y ) * influence;
+							_vA.z += ( targets[ face.a ].z - v1p.z ) * influence;
+
+							_vB.x += ( targets[ face.b ].x - v2p.x ) * influence;
+							_vB.y += ( targets[ face.b ].y - v2p.y ) * influence;
+							_vB.z += ( targets[ face.b ].z - v2p.z ) * influence;
+
+							_vC.x += ( targets[ face.c ].x - v3p.x ) * influence;
+							_vC.y += ( targets[ face.c ].y - v3p.y ) * influence;
+							_vC.z += ( targets[ face.c ].z - v3p.z ) * influence;
+
+						}
+
+						v1.position.add( _vA );
+						v2.position.add( _vB );
+						v3.position.add( _vC );
+
+						projectVertex( v1 );
+						projectVertex( v2 );
+						projectVertex( v3 );
+
+					}
+
+					_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 ( var 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 ( var c = 0, cl = Math.min( faceVertexUvs.length, 3 ); c < cl; c ++ ) {
+
+						uvs = faceVertexUvs[ c ][ f ];
+
+						if ( uvs === undefined ) continue;
+
+						for ( var 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;
+
+			_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 >= -1 && _vector4.z <= 1 ) {
+
+				_sprite = getNextSpriteInPool();
+				_sprite.id = object.id;
+				_sprite.x = _vector4.x * invW;
+				_sprite.y = _vector4.y * invW;
+				_sprite.z = _vector4.z;
+				_sprite.object = object;
+
+				_sprite.rotation = object.rotation;
+
+				_sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[0] ) / ( _vector4.w + camera.projectionMatrix.elements[12] ) );
+				_sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[5] ) / ( _vector4.w + camera.projectionMatrix.elements[13] ) );
+
+				_sprite.material = object.material;
+
+				_renderData.elements.push( _sprite );
+
+			}
+
+		}
+
+		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 getNextSpriteInPool() {
+
+		if ( _spriteCount === _spritePoolLength ) {
+
+			var sprite = new THREE.RenderableSprite();
+			_spritePool.push( sprite );
+			_spritePoolLength ++;
+			_spriteCount ++
+			return sprite;
+
+		}
+
+		return _spritePool[ _spriteCount ++ ];
+
+	}
+
+	//
+
+	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;
+
+		vertices = new Array( this.vertices.length );
+
+		for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+			vertices[ v ] = new THREE.Vector3();
+
+		}
+
+		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 ] = vertices[ face.a ].clone();
+			face.vertexNormals[ 1 ] = vertices[ face.b ].clone();
+			face.vertexNormals[ 2 ] = vertices[ face.c ].clone();
+
+		}
+
+	},
+
+	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;
+
+		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,
+
+	addAttribute: function( name, type, numItems, itemSize ) {
+
+		this.attributes[ name ] = {
+
+			itemSize: itemSize,
+			array: new type( numItems * itemSize )
+
+		};
+
+	},
+
+	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 ) {
+
+				box.makeEmpty();
+
+				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();
+
+};
+
+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 );
+
+	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: undefined,
+
+	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;
+
+			};
+
+			if ( _this.crossOrigin !== undefined ) 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[ "diffuse" ].value.setHex( mpars.color );
+			uniforms[ "specular" ].value.setHex( mpars.specular );
+			uniforms[ "ambient" ].value.setHex( mpars.ambient );
+
+			uniforms[ "shininess" ].value = mpars.shininess;
+
+			if ( mpars.opacity !== undefined ) {
+
+				uniforms[ "opacity" ].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;
+		// 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 ) {
+
+		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.side !== undefined ) material.side = json.side;
+		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.radialSegments,
+							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;
+
+				case 'Sprite':
+
+					var material = materials[ data.material ];
+
+					if ( material === undefined ) {
+
+						console.error( 'THREE.ObjectLoader: Undefined material ' + data.material );
+
+					}
+
+					object = new THREE.Sprite( 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 === "AmbientLight" || objJSON.type === "PointLight" ||
+						objJSON.type === "DirectionalLight" || objJSON.type === "SpotLight" ||
+						objJSON.type === "HemisphereLight" || objJSON.type === "AreaLight" ) {
+
+						var color = objJSON.color;
+						var intensity = objJSON.intensity;
+						var distance = objJSON.distance;
+						var position = objJSON.position;
+						var rotation = objJSON.rotation;
+
+						switch ( objJSON.type ) {
+
+							case 'AmbientLight':
+								light = new THREE.AmbientLight( color );
+								break;
+
+							case 'PointLight':
+								light = new THREE.PointLight( color, intensity, distance );
+								light.position.fromArray( position );
+								break;
+
+							case 'DirectionalLight':
+								light = new THREE.DirectionalLight( color, intensity );
+								light.position.fromArray( objJSON.direction );
+								break;
+
+							case 'SpotLight':
+								light = new THREE.SpotLight( color, intensity, distance, 1 );
+								light.angle = objJSON.angle;
+								light.position.fromArray( position );
+								light.target.set( position[ 0 ], position[ 1 ] - distance, position[ 2 ] );
+								light.target.applyEuler( new THREE.Euler( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ], 'XYZ' ) );
+								break;
+
+							case 'HemisphereLight':
+								light = new THREE.DirectionalLight( color, intensity, distance );
+								light.target.set( position[ 0 ], position[ 1 ] - distance, position[ 2 ] );
+								light.target.applyEuler( new THREE.Euler( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ], 'XYZ' ) );
+								break;
+
+							case 'AreaLight':
+								light = new THREE.AreaLight(color, intensity);
+								light.position.fromArray( position );
+								light.width = objJSON.size;
+								light.height = objJSON.size_y;
+								break;
+
+						}
+
+						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[ "reflectivity" ].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[ "diffuse" ].value.setHex( diffuse );
+				uniforms[ "specular" ].value.setHex( specular );
+				uniforms[ "ambient" ].value.setHex( ambient );
+
+				uniforms[ "shininess" ].value = shininess;
+
+				if ( matJSON.parameters.opacity ) {
+
+					uniforms[ "opacity" ].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: <hex>,
+ *  opacity: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  linewidth: <float>,
+ *  linecap: "round",
+ *  linejoin: "round",
+ *
+ *  vertexColors: <bool>
+ *
+ *  fog: <bool>
+ * }
+ */
+
+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: <hex>,
+ *  opacity: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  linewidth: <float>,
+ *
+ *  scale: <float>,
+ *  dashSize: <float>,
+ *  gapSize: <float>,
+ *
+ *  vertexColors: <bool>
+ *
+ *  fog: <bool>
+ * }
+ */
+
+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: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *
+ *  fog: <bool>
+ * }
+ */
+
+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: <hex>,
+ *  ambient: <hex>,
+ *  emissive: <hex>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+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: <hex>,
+ *  ambient: <hex>,
+ *  emissive: <hex>,
+ *  specular: <hex>,
+ *  shininess: <float>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
+ *
+ *  normalMap: new THREE.Texture( <Image> ),
+ *  normalScale: <Vector2>,
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+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: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>
+ * }
+ */
+
+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: <float>,
+ *
+ *  shading: THREE.FlatShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>
+ * }
+ */
+
+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: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  size: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  vertexColors: <bool>,
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.ParticleSystemMaterial = 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.ParticleSystemMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ParticleSystemMaterial.prototype.clone = function () {
+
+	var material = new THREE.ParticleSystemMaterial();
+
+	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;
+
+};
+
+// backwards compatibility
+
+THREE.ParticleBasicMaterial = THREE.ParticleSystemMaterial;
+
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  fragmentShader: <string>,
+ *  vertexShader: <string>,
+ *
+ *  uniforms: { "parameter1": { type: "f", value: 1.0 }, "parameter2": { type: "i" value2: 2 } },
+ *
+ *  defines: { "label" : "value" },
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  lights: <bool>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+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: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *	uvOffset: new THREE.Vector2(),
+ *	uvScale: new THREE.Vector2(),
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.SpriteMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	// defaults
+
+	this.color = new THREE.Color( 0xffffff );
+	this.map = null;
+
+	this.rotation = 0;
+
+	this.fog = false;
+
+	// set parameters
+
+	this.setValues( parameters );
+
+};
+
+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.rotation = this.rotation;
+
+	material.fog = this.fog;
+
+	return material;
+
+};
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  program: <function>,
+ *  opacity: <float>,
+ *  blending: THREE.NormalBlending
+ * }
+ */
+
+THREE.SpriteCanvasMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff );
+	this.program = function ( context, color ) {};
+
+	this.setValues( parameters );
+
+};
+
+THREE.SpriteCanvasMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.SpriteCanvasMaterial.prototype.clone = function () {
+
+	var material = new THREE.SpriteCanvasMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+	material.program = this.program;
+
+	return material;
+
+};
+
+// backwards compatibility
+
+THREE.ParticleCanvasMaterial = THREE.SpriteCanvasMaterial;
+/**
+ * @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,
+
+	get needsUpdate () {
+
+		return this._needsUpdate;
+
+	},
+
+	set needsUpdate ( value ) {
+
+		if ( value === true ) this.update();
+
+		this._needsUpdate = value;
+
+	},
+
+	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;
+
+	},
+
+	update: function () {
+
+		this.dispatchEvent( { type: 'update' } );
+
+	},
+
+	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 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.ParticleSystemMaterial( { 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.setFromMatrixPosition( camera.matrixWorld );
+			v2.setFromMatrixPosition( 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();
+
+};
+
+THREE.Sprite.prototype = Object.create( THREE.Object3D.prototype );
+
+/*
+ * Custom update matrix
+ */
+
+THREE.Sprite.prototype.updateMatrix = function () {
+
+	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;
+
+};
+
+// Backwards compatibility
+
+THREE.Particle = THREE.Sprite;
+/**
+ * @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 );
+
+		}
+
+	}
+
+	this.dispatchEvent( { type: 'objectAdded', object: object } );
+	object.dispatchEvent( { type: 'addedToScene', scene: this } );
+
+	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 );
+
+		}
+
+	}
+
+	this.dispatchEvent( { type: 'objectRemoved', object: object } );
+	object.dispatchEvent( { type: 'removedFromScene', scene: this } );
+
+	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 = _canvas.width,
+	_canvasHeight = _canvas.height,
+	_canvasWidthHalf = Math.floor( _canvasWidth / 2 ),
+	_canvasHeightHalf = Math.floor( _canvasHeight / 2 ),
+	
+	_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 = {},
+
+	_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();
+
+		}
+
+	};
+
+	// compatibility
+
+	this.clearColor = function () {};
+	this.clearDepth = function () {};
+	this.clearStencil = function () {};
+
+	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.RenderableSprite ) {
+
+				_v1 = element;
+				_v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf;
+
+				renderSprite( _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 sprites
+
+				_directionalLights.add( lightColor );
+
+			} else if ( light instanceof THREE.PointLight ) {
+
+				// for sprites
+
+				_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.setFromMatrixPosition( 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.setFromMatrixPosition( 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 renderSprite( v1, element, material ) {
+
+		setOpacity( material.opacity );
+		setBlending( material.blending );
+
+		var scaleX = element.scale.x * _canvasWidthHalf;
+		var scaleY = element.scale.y * _canvasHeightHalf;
+
+		var dist = 0.5 * Math.sqrt( scaleX * scaleX + scaleY * scaleY ); // allow for rotated sprite
+		_elemBox.min.set( v1.x - dist, v1.y - dist );
+		_elemBox.max.set( v1.x + dist, v1.y + dist );
+
+		if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+			_elemBox.makeEmpty();
+			return;
+
+		}
+
+		if ( material instanceof THREE.SpriteMaterial ||
+			 material instanceof THREE.ParticleSystemMaterial ) { // Backwards compatibility
+
+			var texture = material.map;
+
+			if ( texture !== null ) {
+
+				if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) {
+
+					if ( texture.image !== undefined && texture.image.width > 0 ) {
+
+						textureToPattern( texture );
+
+					}
+
+					texture.addEventListener( 'update', onTextureUpdate );
+
+				}
+
+				var pattern = _patterns[ texture.id ];
+
+				if ( pattern !== undefined ) {
+
+					setFillStyle( pattern );
+
+				} else {
+
+					setFillStyle( 'rgba( 0, 0, 0, 1 )' );
+
+				}
+
+				//
+
+				var bitmap = texture.image;
+
+				var ox = bitmap.width * texture.offset.x;
+				var oy = bitmap.height * texture.offset.y;
+
+				var sx = bitmap.width * texture.repeat.x;
+				var sy = bitmap.height * texture.repeat.y;
+
+				var cx = scaleX / sx;
+				var cy = scaleY / sy;
+
+				_context.save();
+				_context.translate( v1.x, v1.y );
+				if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+				_context.translate( - scaleX / 2, - scaleY / 2 );
+				_context.scale( cx, cy );
+				_context.translate( - ox, - oy );
+				_context.fillRect( ox, oy, sx, sy );
+				_context.restore();
+
+			} else { // no texture
+
+				setFillStyle( material.color.getStyle() );
+
+				_context.save();
+				_context.translate( v1.x, v1.y );
+				if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+				_context.scale( scaleX, - scaleY );
+				_context.fillRect( - 0.5, - 0.5, 1, 1 );
+				_context.restore();
+
+			}
+
+		} else if ( material instanceof THREE.SpriteCanvasMaterial ) {
+
+			setStrokeStyle( material.color.getStyle() );
+			setFillStyle( material.color.getStyle() );
+
+			_context.save();
+			_context.translate( v1.x, v1.y );
+			if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+			_context.scale( scaleX, scaleY );
+
+			material.program( _context );
+
+			_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();
+		*/
+
+	}
+
+	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 onTextureUpdate ( event ) {
+
+		textureToPattern( event.target );
+
+	}
+
+	function textureToPattern( texture ) {
+
+		var repeatX = texture.wrapS === THREE.RepeatWrapping;
+		var repeatY = texture.wrapT === THREE.RepeatWrapping;
+
+		var image = texture.image;
+
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = image.width;
+		canvas.height = image.height;
+
+		var context = canvas.getContext( '2d' );
+		context.setTransform( 1, 0, 0, - 1, 0, image.height );
+		context.drawImage( image, 0, 0 );
+
+		_patterns[ texture.id ] = _context.createPattern(
+			canvas, repeatX === true && repeatY === true
+				? 'repeat'
+				: repeatX === true && repeatY === false
+					? 'repeat-x'
+					: repeatX === false && repeatY === true
+						? 'repeat-y'
+						: 'no-repeat'
+		);
+
+	}
+
+	function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) {
+
+		if ( texture instanceof THREE.DataTexture ) return;
+
+		if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) {
+
+			if ( texture.image !== undefined && texture.image.width > 0 ) {
+
+				textureToPattern( texture );
+
+			}
+
+			texture.addEventListener( 'update', onTextureUpdate );
+
+		}
+
+		var pattern = _patterns[ texture.id ];
+
+		if ( pattern !== undefined ) {
+
+			setFillStyle( pattern );
+
+		} else {
+
+			setFillStyle( 'rgba(0,0,0,1)' );
+			_context.fill();
+
+			return;
+
+		}	
+
+		// 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 = ( v0 + offsetY ) * height;
+
+		u1 = ( u1 + offsetX ) * width;
+		v1 = ( v1 + offsetY ) * height;
+
+		u2 = ( u2 + offsetX ) * width;
+		v2 = ( 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 ) 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;
+
+		}
+
+	}
+
+};
+
+/**
+ * Shader chunks for WebLG Shader library
+ * 
+ * @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 );",
+
+				// 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;",
+
+			"}",
+
+		"#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 );",
+
+					// 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;",
+
+				"}",
+
+			"}",
+
+		"#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 );",
+
+					/*
+				// 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;",
+
+
+			"}",
+
+		"#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 );",
+
+				"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 ) );",
+
+			"}",
+
+		"#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, 1.0 );",
+
+		"#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")
+
+
+};
+/**
+ * Uniform Utilities
+ */
+
+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;
+
+	}
+
+};
+/**
+ * Uniforms library for shared webgl shaders
+ */
+
+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: [] }
+
+	}
+
+};
+/**
+ * Webgl Shader Library for three.js
+ *
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+
+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 },
+
+			"diffuse": { type: "c", value: new THREE.Color( 0xffffff ) },
+			"specular": { type: "c", value: new THREE.Color( 0x111111 ) },
+			"ambient": { type: "c", value: new THREE.Color( 0xffffff ) },
+			"shininess": { type: "f", value: 30 },
+			"opacity": { type: "f", value: 1 },
+
+			"useRefract": { type: "i", value: 0 },
+			"refractionRatio": { type: "f", value: 0.98 },
+			"reflectivity": { 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 ambient;",
+			"uniform vec3 diffuse;",
+			"uniform vec3 specular;",
+			"uniform float shininess;",
+			"uniform float opacity;",
+
+			"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 refractionRatio;",
+			"uniform float reflectivity;",
+
+			"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 ), opacity );",
+
+				"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 ] * diffuse * pointDiffuseWeight;",
+
+						// specular
+
+						"vec3 pointHalfVector = normalize( pointVector + viewPosition );",
+						"float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+						"float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );",
+
+						// 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( pointVector, pointHalfVector ), 5.0 );",
+						"pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;",
+
+					"}",
+
+				"#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 ] * diffuse * spotDiffuseWeight * spotEffect;",
+
+							// specular
+
+							"vec3 spotHalfVector = normalize( spotVector + viewPosition );",
+							"float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );",
+							"float spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, shininess ), 0.0 );",
+
+							// 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( spotVector, spotHalfVector ), 5.0 );",
+							"spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;",
+
+						"}",
+
+					"}",
+
+				"#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 ] * diffuse * dirDiffuseWeight;",
+
+						// specular
+
+						"vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+						"float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+						"float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );",
+
+						// 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( dirVector, dirHalfVector ), 5.0 );",
+						"dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;",
+
+					"}",
+
+				"#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 += diffuse * 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, 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 = specularTex.r * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );",
+
+						"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 ) );",
+
+					"}",
+
+				"#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 * ambient + totalSpecular );",
+
+				"#else",
+
+					"gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient ) + totalSpecular;",
+
+				"#endif",
+
+				"if ( enableReflection ) {",
+
+					"vec3 vReflect;",
+					"vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+					"if ( useRefract ) {",
+
+						"vReflect = refract( cameraToVertex, normal, refractionRatio );",
+
+					"} 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 * reflectivity );",
+
+				"}",
+
+				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 : false,
+	_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;
+
+	// 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 = _canvas.width,
+	_viewportHeight = _canvas.height,
+	_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.clearColor = function () {
+
+		_gl.clear( _gl.COLOR_BUFFER_BIT );
+
+	};
+
+	this.clearDepth = function () {
+
+		_gl.clear( _gl.DEPTH_BUFFER_BIT );
+
+	};
+
+	this.clearStencil = function () {
+
+		_gl.clear( _gl.STENCIL_BUFFER_BIT );
+
+	};
+
+	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.setFromMatrixPosition( 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.ParticleSystemMaterial ) {
+
+			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.ParticleSystemMaterial ) {
+
+				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.setFromMatrixPosition( 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.setFromMatrixPosition( light.matrixWorld );
+				_vector3.setFromMatrixPosition( 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.setFromMatrixPosition( 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.setFromMatrixPosition( light.matrixWorld );
+
+				spotPositions[ spotOffset ]     = _vector3.x;
+				spotPositions[ spotOffset + 1 ] = _vector3.y;
+				spotPositions[ spotOffset + 2 ] = _vector3.z;
+
+				spotDistances[ spotLength ] = distance;
+
+				_direction.copy( _vector3 );
+				_vector3.setFromMatrixPosition( 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.setFromMatrixPosition( 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" : "",
+
+			"#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" : "",
+
+			( 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.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight );
+		
+		_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.position = new THREE.Vector3();
+	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.RenderableSprite = function () {
+
+	this.id = 0;
+
+	this.object = null;
+
+	this.x = 0;
+	this.y = 0;
+	this.z = 0;
+
+	this.rotation = 0;
+	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: undefined,
+
+	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,w> */
+
+			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 <d
+
+};
+
+/*
+THREE.CurvePath.prototype.getTangent = function( t ) {
+};*/
+
+
+// We cannot use the default THREE.Curve getPoint() with getLength() because in
+// THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
+// getPoint() depends on getLength
+
+THREE.CurvePath.prototype.getLength = function() {
+
+	var lens = this.getCurveLengths();
+	return lens[ lens.length - 1 ];
+
+};
+
+// Compute lengths and cache them
+// We cannot overwrite getLengths() because UtoT mapping uses it.
+
+THREE.CurvePath.prototype.getCurveLengths = function() {
+
+	// We use cache values if curves and cache array are same length
+
+	if ( this.cacheLengths && this.cacheLengths.length == this.curves.length ) {
+
+		return this.cacheLengths;
+
+	};
+
+	// Get length of subsurve
+	// Push sums into cached array
+
+	var lengths = [], sums = 0;
+	var i, il = this.curves.length;
+
+	for ( i = 0; i < il; i ++ ) {
+
+		sums += this.curves[ i ].getLength();
+		lengths.push( sums );
+
+	}
+
+	this.cacheLengths = lengths;
+
+	return lengths;
+
+};
+
+
+
+// Returns min and max coordinates, as well as centroid
+
+THREE.CurvePath.prototype.getBoundingBox = function () {
+
+	var points = this.getPoints();
+
+	var maxX, maxY, maxZ;
+	var minX, minY, minZ;
+
+	maxX = maxY = Number.NEGATIVE_INFINITY;
+	minX = minY = Number.POSITIVE_INFINITY;
+
+	var p, i, il, sum;
+
+	var v3 = points[0] instanceof THREE.Vector3;
+
+	sum = v3 ? new THREE.Vector3() : new THREE.Vector2();
+
+	for ( i = 0, il = points.length; i < il; i ++ ) {
+
+		p = points[ i ];
+
+		if ( p.x > 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.getTangent( xNorm );
+		normal.set( -normal.y, normal.x ).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<pl; p++) {
+				if (allpts[p].equals(find)) return p;
+			}
+			return -1;
+		};
+
+		// triangulate
+		js.poly2tri.sweep.Triangulate(swctx);
+
+		var triangles =  swctx.GetTriangles();
+		var tr ;
+		var facesPts = [];
+		for (var t in triangles) {
+			tr =  triangles[t];
+			facesPts.push([
+				findIndexForPt(tr.GetPoint(0)),
+				findIndexForPt(tr.GetPoint(1)),
+				findIndexForPt(tr.GetPoint(2))
+					]);
+		}
+
+
+	//	console.log(facesPts);
+	//	console.log("triangles", triangles.length, triangles);
+
+		// Returns array of faces with 3 element each
+	return facesPts;
+	},
+*/
+
+	isClockWise: function ( pts ) {
+
+		return THREE.FontUtils.Triangulate.area( pts ) < 0;
+
+	},
+
+	// Bezier Curves formulas obtained from
+	// http://en.wikipedia.org/wiki/B%C3%A9zier_curve
+
+	// Quad Bezier Functions
+
+	b2p0: function ( t, p ) {
+
+		var k = 1 - t;
+		return k * k * p;
+
+	},
+
+	b2p1: function ( t, p ) {
+
+		return 2 * ( 1 - t ) * t * p;
+
+	},
+
+	b2p2: function ( t, p ) {
+
+		return t * t * p;
+
+	},
+
+	b2: function ( t, p0, p1, p2 ) {
+
+		return this.b2p0( t, p0 ) + this.b2p1( t, p1 ) + this.b2p2( t, p2 );
+
+	},
+
+	// Cubic Bezier Functions
+
+	b3p0: function ( t, p ) {
+
+		var k = 1 - t;
+		return k * k * k * p;
+
+	},
+
+	b3p1: function ( t, p ) {
+
+		var k = 1 - t;
+		return 3 * k * k * t * p;
+
+	},
+
+	b3p2: function ( t, p ) {
+
+		var k = 1 - t;
+		return 3 * k * t * t * p;
+
+	},
+
+	b3p3: function ( t, p ) {
+
+		return t * t * t * p;
+
+	},
+
+	b3: function ( t, p0, p1, p2, p3 ) {
+
+		return this.b3p0( t, p0 ) + this.b3p1( t, p1 ) + this.b3p2( t, p2 ) +  this.b3p3( t, p3 );
+
+	}
+
+};
+
+
+/**************************************************************
+ *	Line
+ **************************************************************/
+
+THREE.LineCurve = function ( v1, v2 ) {
+
+	this.v1 = v1;
+	this.v2 = v2;
+
+};
+
+THREE.LineCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.LineCurve.prototype.getPoint = function ( t ) {
+
+	var point = this.v2.clone().sub(this.v1);
+	point.multiplyScalar( t ).add( this.v1 );
+
+	return point;
+
+};
+
+// Line curve is linear, so we can overwrite default getPointAt
+
+THREE.LineCurve.prototype.getPointAt = function ( u ) {
+
+	return this.getPoint( u );
+
+};
+
+THREE.LineCurve.prototype.getTangent = function( t ) {
+
+	var tangent = this.v2.clone().sub(this.v1);
+
+	return tangent.normalize();
+
+};
+/**************************************************************
+ *	Quadratic Bezier curve
+ **************************************************************/
+
+
+THREE.QuadraticBezierCurve = function ( v0, v1, v2 ) {
+
+	this.v0 = v0;
+	this.v1 = v1;
+	this.v2 = v2;
+
+};
+
+THREE.QuadraticBezierCurve.prototype = Object.create( THREE.Curve.prototype );
+
+
+THREE.QuadraticBezierCurve.prototype.getPoint = function ( t ) {
+
+	var tx, ty;
+
+	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 );
+
+	return new THREE.Vector2( tx, ty );
+
+};
+
+
+THREE.QuadraticBezierCurve.prototype.getTangent = function( t ) {
+
+	var tx, ty;
+
+	tx = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.x, this.v1.x, this.v2.x );
+	ty = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.y, this.v1.y, this.v2.y );
+
+	// returns unit vector
+
+	var tangent = new THREE.Vector2( tx, ty );
+	tangent.normalize();
+
+	return tangent;
+
+};
+/**************************************************************
+ *	Cubic Bezier curve
+ **************************************************************/
+
+THREE.CubicBezierCurve = function ( v0, v1, v2, v3 ) {
+
+	this.v0 = v0;
+	this.v1 = v1;
+	this.v2 = v2;
+	this.v3 = v3;
+
+};
+
+THREE.CubicBezierCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.CubicBezierCurve.prototype.getPoint = function ( t ) {
+
+	var tx, ty;
+
+	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 );
+
+	return new THREE.Vector2( tx, ty );
+
+};
+
+THREE.CubicBezierCurve.prototype.getTangent = function( t ) {
+
+	var tx, ty;
+
+	tx = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x );
+	ty = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y );
+
+	var tangent = new THREE.Vector2( tx, ty );
+	tangent.normalize();
+
+	return tangent;
+
+};
+/**************************************************************
+ *	Spline curve
+ **************************************************************/
+
+THREE.SplineCurve = function ( points /* array of Vector2 */ ) {
+
+	this.points = (points == undefined) ? [] : points;
+
+};
+
+THREE.SplineCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.SplineCurve.prototype.getPoint = function ( t ) {
+
+	var v = new THREE.Vector2();
+	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;
+
+	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;
+
+	// Mod operation fails on floats
+	// was this supposed to be in frames?
+	while ( this.currentTime > this.data.length ) {
+
+		this.currentTime -= this.data.length;
+
+	}
+
+	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 );
+
+						// if ( nextKey.index < prevKey.index ) then we have wrapped over the end, and nextKey.time < currentTime will loop forever
+						while ( nextKey !== null && nextKey.time < currentTime && nextKey.index > prevKey.index) {
+
+							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 !== null && nextKey.time < currentTime && nextKey.index > prevKey.index )
+					// if ( nextKey.index < prevKey.index ) then we have wrapped over the end, and nextKey.time < currentTime will loop forever
+
+				}
+
+				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.clone(), n.clone(), n.clone() ] ) );
+		this.faceVertexUvs[ 0 ].push( [ uvs[ i ].clone(), uvs[ i + 1 ].clone(), centerUV.clone() ] );
+
+	}
+
+	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.clone(), n3, n4.clone() ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uv2.clone(), uv3, uv4.clone() ] );
+
+		}
+
+	}
+
+	// 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.x, 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.x, 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 = {
+ *
+ *  curveSegments: <int>, // number of points on the curves
+ *  steps: <int>, // number of points for z-side extrusions / used for subdividing segements of extrude spline too
+ *  amount: <int>, // Depth to extrude the shape
+ *
+ *  bevelEnabled: <bool>, // turn on bevel
+ *  bevelThickness: <float>, // how deep into the original shape bevel goes
+ *  bevelSize: <float>, // how far from shape outline is bevel
+ *  bevelSegments: <int>, // number of bevel layers
+ *
+ *  extrudePath: <THREE.CurvePath> // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined)
+ *  frames: <THREE.TubeGeometry.FrenetFrames> // containing arrays of tangents, normals, binormals
+ *
+ *  material: <int> // material index for front and back faces
+ *  extrudeMaterial: <int> // material index for extrusion and beveled faces
+ *  uvGenerator: <Object> // 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: <int>, // number of points on the curves. NOT USED AT THE MOMENT.
+ *
+ *	material: <int> // material index for front and back faces
+ *	uvGenerator: <Object> // 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 / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2 ) );
+		}
+
+		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.clone(), n.clone(), n.clone() ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]);
+
+			v1 = segment + i;
+			v2 = segment + thetaSegments + 1 + i;
+			v3 = segment + 1 + i;
+
+			this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]);
+
+		}
+	}
+
+	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 ) {
+
+				uv1.x = ( uv1.x + uv2.x ) / 2;
+				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 ) {
+
+				uv3.x = ( uv3.x + uv4.x ) / 2;
+				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.clone(), n3, n4.clone() ] ) );
+				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: 			<float>, 	// size of the text
+ *  height: 		<float>, 	// thickness to extrude text
+ *  curveSegments: 	<int>,		// number of points on the curves
+ *
+ *  font: 			<string>,		// font name
+ *  weight: 		<string>,		// font weight (normal, bold)
+ *  style: 			<string>,		// font style  (normal, italics)
+ *
+ *  bevelEnabled:	<bool>,			// turn on bevel
+ *  bevelThickness: <float>, 		// how deep into text bevel goes
+ *  bevelSize:		<float>, 		// 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 ].clone(), normals[ b ].clone(), normals[ d ].clone() ] );
+			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 ].clone(), normals[ c ].clone(), normals[ d ].clone() ] );
+			this.faces.push( face );
+			this.faceVertexUvs[ 0 ].push( [ uvs[ b ].clone(), uvs[ c ].clone(), uvs[ d ].clone() ] );
+
+		}
+
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+};
+
+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
+ *  headLength - Number
+ *  headWidth - Number
+ */
+
+THREE.ArrowHelper = function ( dir, origin, length, hex, headLength, headWidth ) {
+
+	// dir is assumed to be normalized
+
+	THREE.Object3D.call( this );
+
+	if ( hex === undefined ) hex = 0xffff00;
+	if ( length === undefined ) length = 1;
+	if ( headLength === undefined ) headLength = 0.2 * length;
+	if ( headWidth === undefined ) headWidth = 0.2 * headLength;
+
+	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.5, 1, 5, 1 );
+	coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, - 0.5, 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, headLength, headWidth );
+
+};
+
+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, headLength, headWidth ) {
+
+	if ( headLength === undefined ) headLength = 0.2 * length;
+	if ( headWidth === undefined ) headWidth = 0.2 * headLength;
+
+	this.line.scale.set( 1, length, 1 );
+	this.line.updateMatrix();
+
+	this.cone.scale.set( headWidth, headLength, headWidth );
+	this.cone.position.y = length;
+	this.cone.updateMatrix();
+
+};
+
+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 !== undefined ) ? 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/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.DirectionalLightHelper = function ( light, size ) {
+
+	THREE.Object3D.call( this );
+
+	this.light = light;
+	this.light.updateMatrixWorld();
+
+	this.matrixWorld = light.matrixWorld;
+	this.matrixAutoUpdate = false;
+
+	size = size || 1;
+	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() );
+
+	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 v1 = new THREE.Vector3();
+	var v2 = new THREE.Vector3();
+	var v3 = new THREE.Vector3();
+
+	return function () {
+
+		v1.setFromMatrixPosition( this.light.matrixWorld );
+		v2.setFromMatrixPosition( this.light.target.matrixWorld );
+		v3.subVectors( v2, v1 );
+
+		this.lightPlane.lookAt( v3 );
+		this.lightPlane.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+		this.targetLine.geometry.vertices[ 1 ].copy( v3 );
+		this.targetLine.geometry.verticesNeedUpdate = true;
+		this.targetLine.material.color.copy( this.lightPlane.material.color );
+
+	}
+
+}();
+
+
+/**
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.EdgesHelper = function ( object, hex ) {
+
+	var color = ( hex !== undefined ) ? hex : 0xffffff;
+
+	var edge = [ 0, 0 ], hash = {};
+	var sortFunction = function ( a, b ) { return a - b };
+
+	var keys = [ 'a', 'b', 'c' ];
+	var geometry = new THREE.BufferGeometry();
+
+	var geometry2 = object.geometry.clone();
+
+	geometry2.mergeVertices();
+	geometry2.computeFaceNormals();
+
+	var vertices = geometry2.vertices;
+	var faces = geometry2.faces;
+	var numEdges = 0;
+
+	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 ) {
+
+				hash[ key ] = { vert1: edge[ 0 ], vert2: edge[ 1 ], face1: i, face2: undefined };
+				numEdges ++;
+
+			} else {
+
+				hash[ key ].face2 = i;
+
+			}
+
+		}
+
+	}
+
+	geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 );
+
+	var coords = geometry.attributes.position.array;
+
+	var index = 0;
+
+	for ( var key in hash ) {
+
+		var h = hash[ key ];
+
+		if ( h.face2 === undefined || faces[ h.face1 ].normal.dot( faces[ h.face2 ].normal ) < 0.9999 ) { // hardwired const OK
+
+			var vertex = vertices[ h.vert1 ];
+			coords[ index ++ ] = vertex.x;
+			coords[ index ++ ] = vertex.y;
+			coords[ index ++ ] = vertex.z;
+
+			vertex = vertices[ h.vert2 ];
+			coords[ index ++ ] = vertex.x;
+			coords[ index ++ ] = vertex.y;
+			coords[ index ++ ] = vertex.z;
+
+		}
+
+	}
+
+	THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color } ), THREE.LinePieces );
+
+	this.matrixAutoUpdate = false;
+	this.matrixWorld = object.matrixWorld;
+
+};
+
+THREE.EdgesHelper.prototype = Object.create( THREE.Line.prototype );
+
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.FaceNormalsHelper = function ( object, size, hex, linewidth ) {
+
+	this.object = object;
+
+	this.size = ( size !== undefined ) ? size : 1;
+
+	var color = ( hex !== undefined ) ? hex : 0xffff00;
+
+	var width = ( linewidth !== undefined ) ? 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.setFromMatrixPosition( 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.setFromMatrixPosition( this.light.matrixWorld );
+		vector2.setFromMatrixPosition( 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 !== undefined ) ? size : 1;
+
+	var color = ( hex !== undefined ) ? hex : 0xff0000;
+
+	var width = ( linewidth !== undefined ) ? 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 !== undefined ) ? size : 1;
+
+	var color = ( hex !== undefined ) ? hex : 0x0000ff;
+
+	var width = ( linewidth !== undefined ) ? 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, hex ) {
+
+	var color = ( hex !== undefined ) ? hex : 0xffffff;
+
+	var edge = [ 0, 0 ], hash = {};
+	var sortFunction = function ( a, b ) { return a - b };
+
+	var keys = [ 'a', 'b', 'c' ];
+	var geometry = new THREE.BufferGeometry();
+
+	if ( object.geometry instanceof THREE.Geometry ) {
+
+		var vertices = object.geometry.vertices;
+		var faces = object.geometry.faces;
+		var numEdges = 0;
+
+		// allocate maximal size
+		var edges = new Uint32Array( 6 * faces.length );
+
+		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 ) {
+
+					edges[ 2 * numEdges ] = edge[ 0 ];
+					edges[ 2 * numEdges + 1 ] = edge[ 1 ];
+					hash[ key ] = true;
+					numEdges ++;
+
+				}
+
+			}
+
+		}
+
+		geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 );
+
+		var coords = geometry.attributes.position.array;
+
+		for ( var i = 0, l = numEdges; i < l; i ++ ) {
+
+			for ( var j = 0; j < 2; j ++ ) {
+
+				var vertex = vertices[ edges [ 2 * i + j] ];
+
+				var index = 6 * i + 3 * j;
+				coords[ index + 0 ] = vertex.x;
+				coords[ index + 1 ] = vertex.y;
+				coords[ index + 2 ] = vertex.z;
+
+			}
+
+		}
+
+	} else if ( object.geometry instanceof THREE.BufferGeometry && object.geometry.attributes.index !== undefined ) { // indexed BufferGeometry
+
+		var vertices = object.geometry.attributes.position.array;
+		var indices = object.geometry.attributes.index.array;
+		var offsets = object.geometry.offsets;
+		var numEdges = 0;
+
+		// allocate maximal size
+		var edges = new Uint32Array( 2 * indices.length );
+
+		for ( var o = 0, ol = offsets.length; o < ol; ++ o ) {
+
+			var start = offsets[ o ].start;
+			var count = offsets[ o ].count;
+			var index = offsets[ o ].index;
+
+			for ( var i = start, il = start + count; i < il; i += 3 ) {
+
+				for ( var j = 0; j < 3; j ++ ) {
+
+					edge[ 0 ] = index + indices[ i + j ];
+					edge[ 1 ] = index + indices[ i + ( j + 1 ) % 3 ];
+					edge.sort( sortFunction );
+
+					var key = edge.toString();
+
+					if ( hash[ key ] === undefined ) {
+
+						edges[ 2 * numEdges ] = edge[ 0 ];
+						edges[ 2 * numEdges + 1 ] = edge[ 1 ];
+						hash[ key ] = true;
+						numEdges ++;
+
+					}
+
+				}
+
+			}
+
+		}
+
+		geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 );
+
+		var coords = geometry.attributes.position.array;
+
+		for ( var i = 0, l = numEdges; i < l; i ++ ) {
+
+			for ( var j = 0; j < 2; j ++ ) {
+
+				var index = 6 * i + 3 * j;
+				var index2 = 3 * edges[ 2 * i + j];
+				coords[ index + 0 ] = vertices[ index2 ];
+				coords[ index + 1 ] = vertices[ index2 + 1 ];
+				coords[ index + 2 ] = vertices[ index2 + 2 ];
+
+			}
+
+		}
+
+	} else if ( object.geometry instanceof THREE.BufferGeometry	) { // non-indexed BufferGeometry
+
+		var vertices = object.geometry.attributes.position.array;
+		var numEdges = vertices.length / 3;
+		var numTris = numEdges / 3;
+
+		geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 );
+
+		var coords = geometry.attributes.position.array;
+
+		for ( var i = 0, l = numTris; i < l; i ++ ) {
+
+			for ( var j = 0; j < 3; j ++ ) {
+
+				var index = 18 * i + 6 * j;
+
+				var index1 = 9 * i + 3 * j;
+				coords[ index + 0 ] = vertices[ index1 ];
+				coords[ index + 1 ] = vertices[ index1 + 1 ];
+				coords[ index + 2 ] = vertices[ index1 + 2 ];
+
+				var index2 = 9 * i + 3 * ( ( j + 1 ) % 3 );
+				coords[ index + 3 ] = vertices[ index2 ];
+				coords[ index + 4 ] = vertices[ index2 + 1 ];
+				coords[ index + 5 ] = vertices[ index2 + 2 ];
+
+			}
+
+		}
+
+	}
+
+	THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color } ), 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.setFromMatrixPosition( light.matrixWorld );
+			_matrixPosition.setFromMatrixPosition( 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, _texture;
+
+	var vertices, faces, vertexBuffer, elementBuffer;
+	var program, attributes, uniforms;
+
+	this.init = function ( renderer ) {
+
+		_gl = renderer.context;
+		_renderer = renderer;
+
+		vertices = new Float32Array( [
+			- 0.5, - 0.5, 0, 0, 
+			  0.5, - 0.5, 1, 0,
+			  0.5,   0.5, 1, 1,
+			- 0.5,   0.5, 0, 1
+		] );
+
+		faces = new Uint16Array( [
+			0, 1, 2,
+			0, 2, 3
+		] );
+
+		vertexBuffer  = _gl.createBuffer();
+		elementBuffer = _gl.createBuffer();
+
+		_gl.bindBuffer( _gl.ARRAY_BUFFER, vertexBuffer );
+		_gl.bufferData( _gl.ARRAY_BUFFER, vertices, _gl.STATIC_DRAW );
+
+		_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, elementBuffer );
+		_gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faces, _gl.STATIC_DRAW );
+
+		program = createProgram();
+
+		attributes = {
+			position:			_gl.getAttribLocation ( program, 'position' ),
+			uv:					_gl.getAttribLocation ( program, 'uv' )
+		};
+
+		uniforms = {
+			uvOffset:			_gl.getUniformLocation( program, 'uvOffset' ),
+			uvScale:			_gl.getUniformLocation( program, 'uvScale' ),
+
+			rotation:			_gl.getUniformLocation( program, 'rotation' ),
+			scale:				_gl.getUniformLocation( program, 'scale' ),
+
+			color:				_gl.getUniformLocation( program, 'color' ),
+			map:				_gl.getUniformLocation( program, 'map' ),
+			opacity:			_gl.getUniformLocation( program, 'opacity' ),
+
+			modelViewMatrix: 	_gl.getUniformLocation( program, 'modelViewMatrix' ),
+			projectionMatrix:	_gl.getUniformLocation( program, 'projectionMatrix' ),
+
+			fogType:			_gl.getUniformLocation( program, 'fogType' ),
+			fogDensity:			_gl.getUniformLocation( program, 'fogDensity' ),
+			fogNear:			_gl.getUniformLocation( program, 'fogNear' ),
+			fogFar:				_gl.getUniformLocation( program, 'fogFar' ),
+			fogColor:			_gl.getUniformLocation( program, 'fogColor' ),
+
+			alphaTest:			_gl.getUniformLocation( program, 'alphaTest' )
+		};
+
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = 8;
+		canvas.height = 8;
+
+		var context = canvas.getContext( '2d' );
+		context.fillStyle = '#ffffff';
+		context.fillRect( 0, 0, canvas.width, canvas.height );
+
+		_texture = new THREE.Texture( canvas );
+		_texture.needsUpdate = true;
+
+	};
+
+	this.render = function ( scene, camera, viewportWidth, viewportHeight ) {
+
+		var sprites = scene.__webglSprites,
+			nSprites = sprites.length;
+
+		if ( ! nSprites ) return;
+
+		// setup gl
+
+		_gl.useProgram( program );
+
+		_gl.enableVertexAttribArray( attributes.position );
+		_gl.enableVertexAttribArray( attributes.uv );
+
+		_gl.disable( _gl.CULL_FACE );
+		_gl.enable( _gl.BLEND );
+
+		_gl.bindBuffer( _gl.ARRAY_BUFFER, 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, 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, fogType, scale = [];
+
+		for( i = 0; i < nSprites; i ++ ) {
+
+			sprite = sprites[ i ];
+			material = sprite.material;
+
+			if ( sprite.visible === false ) continue;
+
+			sprite._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld );
+			sprite.z = - sprite._modelViewMatrix.elements[ 14 ];
+
+		}
+
+		sprites.sort( painterSortStable );
+
+		// render all sprites
+
+		for( i = 0; i < nSprites; i ++ ) {
+
+			sprite = sprites[ i ];
+
+			if ( sprite.visible === false ) continue;
+
+			material = sprite.material;
+
+			_gl.uniform1f( uniforms.alphaTest, material.alphaTest );
+			_gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite._modelViewMatrix.elements );
+
+			scale[ 0 ] = sprite.scale.x;
+			scale[ 1 ] = sprite.scale.y;
+
+			if ( scene.fog && material.fog ) {
+
+				fogType = sceneFogType;
+
+			} else {
+
+				fogType = 0;
+
+			}
+
+			if ( oldFogType !== fogType ) {
+
+				_gl.uniform1i( uniforms.fogType, fogType );
+				oldFogType = fogType;
+
+			}
+
+			if ( material.map !== null ) {
+
+				_gl.uniform2f( uniforms.uvOffset, material.map.offset.x, material.map.offset.y );
+				_gl.uniform2f( uniforms.uvScale, material.map.repeat.x, material.map.repeat.y );
+
+			} else {
+
+				_gl.uniform2f( uniforms.uvOffset, 0, 0 );
+				_gl.uniform2f( uniforms.uvScale, 1, 1 );
+
+			}
+
+			_gl.uniform1f( uniforms.opacity, material.opacity );
+			_gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b );
+
+			_gl.uniform1f( uniforms.rotation, material.rotation );
+			_gl.uniform2fv( uniforms.scale, scale );
+
+			_renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+			_renderer.setDepthTest( material.depthTest );
+			_renderer.setDepthWrite( material.depthWrite );
+
+			if ( material.map && material.map.image && material.map.image.width ) {
+
+				_renderer.setTexture( material.map, 0 );
+
+			} else {
+
+				_renderer.setTexture( _texture, 0 );
+
+			}
+
+			_gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+		}
+
+		// restore gl
+
+		_gl.enable( _gl.CULL_FACE );
+
+	};
+
+	function createProgram () {
+
+		var program = _gl.createProgram();
+
+		var vertexShader = _gl.createShader( _gl.VERTEX_SHADER );
+		var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER );
+
+		_gl.shaderSource( vertexShader, [
+
+			'precision ' + _renderer.getPrecision() + ' float;',
+
+			'uniform mat4 modelViewMatrix;',
+			'uniform mat4 projectionMatrix;',
+			'uniform float rotation;',
+			'uniform vec2 scale;',
+			'uniform vec2 uvOffset;',
+			'uniform vec2 uvScale;',
+
+			'attribute vec2 position;',
+			'attribute vec2 uv;',
+
+			'varying vec2 vUV;',
+
+			'void main() {',
+
+				'vUV = uvOffset + uv * uvScale;',
+
+				'vec2 alignedPosition = position * scale;',
+
+				'vec2 rotatedPosition;',
+				'rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;',
+				'rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;',
+
+				'vec4 finalPosition;',
+
+				'finalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );',
+				'finalPosition.xy += rotatedPosition;',
+				'finalPosition = projectionMatrix * finalPosition;',
+
+				'gl_Position = finalPosition;',
+
+			'}'
+
+		].join( '\n' ) );
+
+		_gl.shaderSource( fragmentShader, [
+
+			'precision ' + _renderer.getPrecision() + ' float;',
+
+			'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' ) );
+
+		_gl.compileShader( vertexShader );
+		_gl.compileShader( fragmentShader );
+
+		_gl.attachShader( program, vertexShader );
+		_gl.attachShader( program, fragmentShader );
+
+		_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" )
+
+	}
+
+};
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/three.min.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/three.min.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/build/three.min.js	(revision 28897)
@@ -0,0 +1,707 @@
+// three.js - http://github.com/mrdoob/three.js
+'use strict';var THREE={REVISION:"65"};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,"")};THREE.extend=function(a,b){if(Object.keys)for(var c=Object.keys(b),d=0,e=c.length;d<e;d++){var f=c[d];Object.defineProperty(a,f,Object.getOwnPropertyDescriptor(b,f))}else for(f in c={}.hasOwnProperty,b)c.call(b,f)&&(a[f]=b[f]);return a};
+(function(){for(var a=0,b=["ms","moz","webkit","o"],c=0;c<b.length&&!self.requestAnimationFrame;++c)self.requestAnimationFrame=self[b[c]+"RequestAnimationFrame"],self.cancelAnimationFrame=self[b[c]+"CancelAnimationFrame"]||self[b[c]+"CancelRequestAnimationFrame"];void 0===self.requestAnimationFrame&&void 0!==self.setTimeout&&(self.requestAnimationFrame=function(b){var c=Date.now(),f=Math.max(0,16-(c-a)),g=self.setTimeout(function(){b(c+f)},f);a=c+f;return g});void 0===self.cancelAnimationFrame&&void 0!==
+self.clearTimeout&&(self.cancelAnimationFrame=function(a){self.clearTimeout(a)})})();THREE.CullFaceNone=0;THREE.CullFaceBack=1;THREE.CullFaceFront=2;THREE.CullFaceFrontBack=3;THREE.FrontFaceDirectionCW=0;THREE.FrontFaceDirectionCCW=1;THREE.BasicShadowMap=0;THREE.PCFShadowMap=1;THREE.PCFSoftShadowMap=2;THREE.FrontSide=0;THREE.BackSide=1;THREE.DoubleSide=2;THREE.NoShading=0;THREE.FlatShading=1;THREE.SmoothShading=2;THREE.NoColors=0;THREE.FaceColors=1;THREE.VertexColors=2;THREE.NoBlending=0;
+THREE.NormalBlending=1;THREE.AdditiveBlending=2;THREE.SubtractiveBlending=3;THREE.MultiplyBlending=4;THREE.CustomBlending=5;THREE.AddEquation=100;THREE.SubtractEquation=101;THREE.ReverseSubtractEquation=102;THREE.ZeroFactor=200;THREE.OneFactor=201;THREE.SrcColorFactor=202;THREE.OneMinusSrcColorFactor=203;THREE.SrcAlphaFactor=204;THREE.OneMinusSrcAlphaFactor=205;THREE.DstAlphaFactor=206;THREE.OneMinusDstAlphaFactor=207;THREE.DstColorFactor=208;THREE.OneMinusDstColorFactor=209;
+THREE.SrcAlphaSaturateFactor=210;THREE.MultiplyOperation=0;THREE.MixOperation=1;THREE.AddOperation=2;THREE.UVMapping=function(){};THREE.CubeReflectionMapping=function(){};THREE.CubeRefractionMapping=function(){};THREE.SphericalReflectionMapping=function(){};THREE.SphericalRefractionMapping=function(){};THREE.RepeatWrapping=1E3;THREE.ClampToEdgeWrapping=1001;THREE.MirroredRepeatWrapping=1002;THREE.NearestFilter=1003;THREE.NearestMipMapNearestFilter=1004;THREE.NearestMipMapLinearFilter=1005;
+THREE.LinearFilter=1006;THREE.LinearMipMapNearestFilter=1007;THREE.LinearMipMapLinearFilter=1008;THREE.UnsignedByteType=1009;THREE.ByteType=1010;THREE.ShortType=1011;THREE.UnsignedShortType=1012;THREE.IntType=1013;THREE.UnsignedIntType=1014;THREE.FloatType=1015;THREE.UnsignedShort4444Type=1016;THREE.UnsignedShort5551Type=1017;THREE.UnsignedShort565Type=1018;THREE.AlphaFormat=1019;THREE.RGBFormat=1020;THREE.RGBAFormat=1021;THREE.LuminanceFormat=1022;THREE.LuminanceAlphaFormat=1023;
+THREE.RGB_S3TC_DXT1_Format=2001;THREE.RGBA_S3TC_DXT1_Format=2002;THREE.RGBA_S3TC_DXT3_Format=2003;THREE.RGBA_S3TC_DXT5_Format=2004;THREE.Color=function(a){void 0!==a&&this.set(a);return this};
+THREE.Color.prototype={constructor:THREE.Color,r:1,g:1,b:1,set:function(a){a instanceof THREE.Color?this.copy(a):"number"===typeof a?this.setHex(a):"string"===typeof a&&this.setStyle(a);return this},setHex:function(a){a=Math.floor(a);this.r=(a>>16&255)/255;this.g=(a>>8&255)/255;this.b=(a&255)/255;return this},setRGB:function(a,b,c){this.r=a;this.g=b;this.b=c;return this},setHSL:function(a,b,c){if(0===b)this.r=this.g=this.b=c;else{var d=function(a,b,c){0>c&&(c+=1);1<c&&(c-=1);return c<1/6?a+6*(b-a)*
+c:0.5>c?b:c<2/3?a+6*(b-a)*(2/3-c):a},b=0.5>=c?c*(1+b):c+b-c*b,c=2*c-b;this.r=d(c,b,a+1/3);this.g=d(c,b,a);this.b=d(c,b,a-1/3)}return this},setStyle:function(a){if(/^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.test(a))return a=/^rgb\((\d+), ?(\d+), ?(\d+)\)$/i.exec(a),this.r=Math.min(255,parseInt(a[1],10))/255,this.g=Math.min(255,parseInt(a[2],10))/255,this.b=Math.min(255,parseInt(a[3],10))/255,this;if(/^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.test(a))return a=/^rgb\((\d+)\%, ?(\d+)\%, ?(\d+)\%\)$/i.exec(a),this.r=
+Math.min(100,parseInt(a[1],10))/100,this.g=Math.min(100,parseInt(a[2],10))/100,this.b=Math.min(100,parseInt(a[3],10))/100,this;if(/^\#([0-9a-f]{6})$/i.test(a))return a=/^\#([0-9a-f]{6})$/i.exec(a),this.setHex(parseInt(a[1],16)),this;if(/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.test(a))return a=/^\#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(a),this.setHex(parseInt(a[1]+a[1]+a[2]+a[2]+a[3]+a[3],16)),this;if(/^(\w+)$/i.test(a))return this.setHex(THREE.ColorKeywords[a]),this},copy:function(a){this.r=a.r;this.g=
+a.g;this.b=a.b;return this},copyGammaToLinear:function(a){this.r=a.r*a.r;this.g=a.g*a.g;this.b=a.b*a.b;return this},copyLinearToGamma:function(a){this.r=Math.sqrt(a.r);this.g=Math.sqrt(a.g);this.b=Math.sqrt(a.b);return this},convertGammaToLinear:function(){var a=this.r,b=this.g,c=this.b;this.r=a*a;this.g=b*b;this.b=c*c;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 255*this.r<<16^255*this.g<<
+8^255*this.b<<0},getHexString:function(){return("000000"+this.getHex().toString(16)).slice(-6)},getHSL:function(a){var a=a||{h:0,s:0,l:0},b=this.r,c=this.g,d=this.b,e=Math.max(b,c,d),f=Math.min(b,c,d),g,h=(f+e)/2;if(f===e)f=g=0;else{var i=e-f,f=0.5>=h?i/(e+f):i/(2-e-f);switch(e){case b:g=(c-d)/i+(c<d?6:0);break;case c:g=(d-b)/i+2;break;case d:g=(b-c)/i+4}g/=6}a.h=g;a.s=f;a.l=h;return a},getStyle:function(){return"rgb("+(255*this.r|0)+","+(255*this.g|0)+","+(255*this.b|0)+")"},offsetHSL:function(a,
+b,c){var d=this.getHSL();d.h+=a;d.s+=b;d.l+=c;this.setHSL(d.h,d.s,d.l);return this},add:function(a){this.r+=a.r;this.g+=a.g;this.b+=a.b;return this},addColors:function(a,b){this.r=a.r+b.r;this.g=a.g+b.g;this.b=a.b+b.b;return this},addScalar:function(a){this.r+=a;this.g+=a;this.b+=a;return this},multiply:function(a){this.r*=a.r;this.g*=a.g;this.b*=a.b;return this},multiplyScalar:function(a){this.r*=a;this.g*=a;this.b*=a;return this},lerp:function(a,b){this.r+=(a.r-this.r)*b;this.g+=(a.g-this.g)*b;
+this.b+=(a.b-this.b)*b;return this},equals:function(a){return a.r===this.r&&a.g===this.g&&a.b===this.b},fromArray:function(a){this.r=a[0];this.g=a[1];this.b=a[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:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,
+darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,
+grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,
+lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,
+palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,
+tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074};THREE.Quaternion=function(a,b,c,d){this._x=a||0;this._y=b||0;this._z=c||0;this._w=void 0!==d?d:1};
+THREE.Quaternion.prototype={constructor:THREE.Quaternion,_x:0,_y:0,_z:0,_w:0,_euler:void 0,_updateEuler:function(){void 0!==this._euler&&this._euler.setFromQuaternion(this,void 0,!1)},get x(){return this._x},set x(a){this._x=a;this._updateEuler()},get y(){return this._y},set y(a){this._y=a;this._updateEuler()},get z(){return this._z},set z(a){this._z=a;this._updateEuler()},get w(){return this._w},set w(a){this._w=a;this._updateEuler()},set:function(a,b,c,d){this._x=a;this._y=b;this._z=c;this._w=d;
+this._updateEuler();return this},copy:function(a){this._x=a._x;this._y=a._y;this._z=a._z;this._w=a._w;this._updateEuler();return this},setFromEuler:function(a,b){if(!1===a instanceof THREE.Euler)throw Error("ERROR: Quaternion's .setFromEuler() now expects a Euler rotation rather than a Vector3 and order.  Please update your code.");var c=Math.cos(a._x/2),d=Math.cos(a._y/2),e=Math.cos(a._z/2),f=Math.sin(a._x/2),g=Math.sin(a._y/2),h=Math.sin(a._z/2);"XYZ"===a.order?(this._x=f*d*e+c*g*h,this._y=c*g*
+e-f*d*h,this._z=c*d*h+f*g*e,this._w=c*d*e-f*g*h):"YXZ"===a.order?(this._x=f*d*e+c*g*h,this._y=c*g*e-f*d*h,this._z=c*d*h-f*g*e,this._w=c*d*e+f*g*h):"ZXY"===a.order?(this._x=f*d*e-c*g*h,this._y=c*g*e+f*d*h,this._z=c*d*h+f*g*e,this._w=c*d*e-f*g*h):"ZYX"===a.order?(this._x=f*d*e-c*g*h,this._y=c*g*e+f*d*h,this._z=c*d*h-f*g*e,this._w=c*d*e+f*g*h):"YZX"===a.order?(this._x=f*d*e+c*g*h,this._y=c*g*e+f*d*h,this._z=c*d*h-f*g*e,this._w=c*d*e-f*g*h):"XZY"===a.order&&(this._x=f*d*e-c*g*h,this._y=c*g*e-f*d*h,this._z=
+c*d*h+f*g*e,this._w=c*d*e+f*g*h);!1!==b&&this._updateEuler();return this},setFromAxisAngle:function(a,b){var c=b/2,d=Math.sin(c);this._x=a.x*d;this._y=a.y*d;this._z=a.z*d;this._w=Math.cos(c);this._updateEuler();return this},setFromRotationMatrix:function(a){var b=a.elements,c=b[0],a=b[4],d=b[8],e=b[1],f=b[5],g=b[9],h=b[2],i=b[6],b=b[10],k=c+f+b;0<k?(c=0.5/Math.sqrt(k+1),this._w=0.25/c,this._x=(i-g)*c,this._y=(d-h)*c,this._z=(e-a)*c):c>f&&c>b?(c=2*Math.sqrt(1+c-f-b),this._w=(i-g)/c,this._x=0.25*c,
+this._y=(a+e)/c,this._z=(d+h)/c):f>b?(c=2*Math.sqrt(1+f-c-b),this._w=(d-h)/c,this._x=(a+e)/c,this._y=0.25*c,this._z=(g+i)/c):(c=2*Math.sqrt(1+b-c-f),this._w=(e-a)/c,this._x=(d+h)/c,this._y=(g+i)/c,this._z=0.25*c);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 a=this.length();0===a?(this._z=this._y=this._x=0,this._w=1):(a=1/a,this._x*=a,this._y*=a,this._z*=a,this._w*=a);return this},multiply:function(a,b){return void 0!==b?(console.warn("DEPRECATED: Quaternion's .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead."),this.multiplyQuaternions(a,b)):this.multiplyQuaternions(this,a)},multiplyQuaternions:function(a,b){var c=a._x,d=a._y,e=a._z,f=
+a._w,g=b._x,h=b._y,i=b._z,k=b._w;this._x=c*k+f*g+d*i-e*h;this._y=d*k+f*h+e*g-c*i;this._z=e*k+f*i+c*h-d*g;this._w=f*k-c*g-d*h-e*i;this._updateEuler();return this},multiplyVector3:function(a){console.warn("DEPRECATED: Quaternion's .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.");return a.applyQuaternion(this)},slerp:function(a,b){var c=this._x,d=this._y,e=this._z,f=this._w,g=f*a._w+c*a._x+d*a._y+e*a._z;0>g?(this._w=-a._w,this._x=-a._x,this._y=-a._y,this._z=
+-a._z,g=-g):this.copy(a);if(1<=g)return this._w=f,this._x=c,this._y=d,this._z=e,this;var h=Math.acos(g),i=Math.sqrt(1-g*g);if(0.001>Math.abs(i))return this._w=0.5*(f+this._w),this._x=0.5*(c+this._x),this._y=0.5*(d+this._y),this._z=0.5*(e+this._z),this;g=Math.sin((1-b)*h)/i;h=Math.sin(b*h)/i;this._w=f*g+this._w*h;this._x=c*g+this._x*h;this._y=d*g+this._y*h;this._z=e*g+this._z*h;this._updateEuler();return this},equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&&a._w===this._w},
+fromArray:function(a){this._x=a[0];this._y=a[1];this._z=a[2];this._w=a[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(a,b,c,d){return c.copy(a).slerp(b,d)};THREE.Vector2=function(a,b){this.x=a||0;this.y=b||0};
+THREE.Vector2.prototype={constructor:THREE.Vector2,set:function(a,b){this.x=a;this.y=b;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;default:throw Error("index is out of range: "+a);}},copy:function(a){this.x=a.x;this.y=a.y;return this},add:function(a,
+b){if(void 0!==b)return console.warn("DEPRECATED: Vector2's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;return this},addVectors:function(a,b){this.x=a.x+b.x;this.y=a.y+b.y;return this},addScalar:function(a){this.x+=a;this.y+=a;return this},sub:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector2's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=
+a.y;return this},subVectors:function(a,b){this.x=a.x-b.x;this.y=a.y-b.y;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a):this.y=this.x=0;return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);return this},max:function(a){this.x<a.x&&(this.x=a.x);this.y<a.y&&(this.y=a.y);return this},clamp:function(a,b){this.x<a.x?this.x=a.x:this.x>b.x&&(this.x=b.x);this.y<a.y?this.y=a.y:this.y>b.y&&(this.y=b.y);
+return this},negate:function(){return this.multiplyScalar(-1)},dot:function(a){return this.x*a.x+this.y*a.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(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,a=this.y-a.y;return b*b+a*a},setLength:function(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/
+b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y},fromArray:function(a){this.x=a[0];this.y=a[1];return this},toArray:function(){return[this.x,this.y]},clone:function(){return new THREE.Vector2(this.x,this.y)}};THREE.Vector3=function(a,b,c){this.x=a||0;this.y=b||0;this.z=c||0};
+THREE.Vector3.prototype={constructor:THREE.Vector3,set:function(a,b,c){this.x=a;this.y=b;this.z=c;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;case 1:return this.y;case 2:return this.z;default:throw Error("index is out of range: "+
+a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;return this},add:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;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(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),
+this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.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(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead."),this.multiplyVectors(a,b);this.x*=a.x;this.y*=a.y;this.z*=a.z;return this},multiplyScalar:function(a){this.x*=a;this.y*=a;this.z*=a;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(a){var b=this.x,c=this.y,d=this.z,a=a.elements;this.x=a[0]*b+a[3]*c+a[6]*d;this.y=a[1]*b+a[4]*c+a[7]*d;this.z=a[2]*b+a[5]*c+a[8]*d;return this},applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z,a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12];this.y=a[1]*b+a[5]*c+a[9]*d+a[13];this.z=a[2]*b+a[6]*c+a[10]*d+a[14];return this},applyProjection:function(a){var b=this.x,c=this.y,d=this.z,a=a.elements,e=1/(a[3]*b+a[7]*c+a[11]*d+a[15]);
+this.x=(a[0]*b+a[4]*c+a[8]*d+a[12])*e;this.y=(a[1]*b+a[5]*c+a[9]*d+a[13])*e;this.z=(a[2]*b+a[6]*c+a[10]*d+a[14])*e;return this},applyQuaternion:function(a){var b=this.x,c=this.y,d=this.z,e=a.x,f=a.y,g=a.z,a=a.w,h=a*b+f*d-g*c,i=a*c+g*b-e*d,k=a*d+e*c-f*b,b=-e*b-f*c-g*d;this.x=h*a+b*-e+i*-g-k*-f;this.y=i*a+b*-f+k*-e-h*-g;this.z=k*a+b*-g+h*-f-i*-e;return this},transformDirection:function(a){var b=this.x,c=this.y,d=this.z,a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d;this.y=a[1]*b+a[5]*c+a[9]*d;this.z=a[2]*
+b+a[6]*c+a[10]*d;this.normalize();return this},divide:function(a){this.x/=a.x;this.y/=a.y;this.z/=a.z;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a,this.z*=a):this.z=this.y=this.x=0;return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);return this},max:function(a){this.x<a.x&&(this.x=a.x);this.y<a.y&&(this.y=a.y);this.z<a.z&&(this.z=a.z);return this},clamp:function(a,b){this.x<a.x?this.x=a.x:this.x>b.x&&(this.x=b.x);this.y<
+a.y?this.y=a.y:this.y>b.y&&(this.y=b.y);this.z<a.z?this.z=a.z:this.z>b.z&&(this.z=b.z);return this},negate:function(){return this.multiplyScalar(-1)},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.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(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;return this},cross:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector3's .cross() now only accepts one argument. Use .crossVectors( a, b ) instead."),this.crossVectors(a,b);var c=this.x,d=this.y,e=this.z;this.x=d*a.z-e*a.y;this.y=e*a.x-c*a.z;this.z=c*a.y-d*a.x;return this},crossVectors:function(a,b){var c=
+a.x,d=a.y,e=a.z,f=b.x,g=b.y,h=b.z;this.x=d*h-e*g;this.y=e*f-c*h;this.z=c*g-d*f;return this},angleTo:function(a){a=this.dot(a)/(this.length()*a.length());return Math.acos(THREE.Math.clamp(a,-1,1))},distanceTo:function(a){return Math.sqrt(this.distanceToSquared(a))},distanceToSquared:function(a){var b=this.x-a.x,c=this.y-a.y,a=this.z-a.z;return b*b+c*c+a*a},setEulerFromRotationMatrix:function(){console.error("REMOVED: Vector3's setEulerFromRotationMatrix has been removed in favor of Euler.setFromRotationMatrix(), please update your code.")},
+setEulerFromQuaternion:function(){console.error("REMOVED: Vector3's setEulerFromQuaternion: has been removed in favor of Euler.setFromQuaternion(), please update your code.")},getPositionFromMatrix:function(a){console.warn("DEPRECATED: Vector3's .getPositionFromMatrix() has been renamed to .setFromMatrixPosition(). Please update your code.");return this.setFromMatrixPosition(a)},getScaleFromMatrix:function(a){console.warn("DEPRECATED: Vector3's .getScaleFromMatrix() has been renamed to .setFromMatrixScale(). Please update your code.");
+return this.setFromMatrixScale(a)},getColumnFromMatrix:function(a,b){console.warn("DEPRECATED: Vector3's .getColumnFromMatrix() has been renamed to .setFromMatrixColumn(). Please update your code.");return this.setFromMatrixColumn(a,b)},setFromMatrixPosition:function(a){this.x=a.elements[12];this.y=a.elements[13];this.z=a.elements[14];return this},setFromMatrixScale:function(a){var b=this.set(a.elements[0],a.elements[1],a.elements[2]).length(),c=this.set(a.elements[4],a.elements[5],a.elements[6]).length(),
+a=this.set(a.elements[8],a.elements[9],a.elements[10]).length();this.x=b;this.y=c;this.z=a;return this},setFromMatrixColumn:function(a,b){var c=4*a,d=b.elements;this.x=d[c];this.y=d[c+1];this.z=d[c+2];return this},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z},fromArray:function(a){this.x=a[0];this.y=a[1];this.z=a[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 a=new THREE.Quaternion;return function(b){!1===b instanceof THREE.Euler&&console.error("ERROR: Vector3's .applyEuler() now expects a Euler rotation rather than a Vector3 and order.  Please update your code.");this.applyQuaternion(a.setFromEuler(b));return this}}(),applyAxisAngle:function(){var a=new THREE.Quaternion;return function(b,c){this.applyQuaternion(a.setFromAxisAngle(b,c));return this}}(),projectOnVector:function(){var a=new THREE.Vector3;
+return function(b){a.copy(b).normalize();b=this.dot(a);return this.copy(a).multiplyScalar(b)}}(),projectOnPlane:function(){var a=new THREE.Vector3;return function(b){a.copy(this).projectOnVector(b);return this.sub(a)}}(),reflect:function(){var a=new THREE.Vector3;return function(b){a.copy(this).projectOnVector(b).multiplyScalar(2);return this.subVectors(a,this)}}()});THREE.Vector4=function(a,b,c,d){this.x=a||0;this.y=b||0;this.z=c||0;this.w=void 0!==d?d:1};
+THREE.Vector4.prototype={constructor:THREE.Vector4,set:function(a,b,c,d){this.x=a;this.y=b;this.z=c;this.w=d;return this},setX:function(a){this.x=a;return this},setY:function(a){this.y=a;return this},setZ:function(a){this.z=a;return this},setW:function(a){this.w=a;return this},setComponent:function(a,b){switch(a){case 0:this.x=b;break;case 1:this.y=b;break;case 2:this.z=b;break;case 3:this.w=b;break;default:throw Error("index is out of range: "+a);}},getComponent:function(a){switch(a){case 0:return this.x;
+case 1:return this.y;case 2:return this.z;case 3:return this.w;default:throw Error("index is out of range: "+a);}},copy:function(a){this.x=a.x;this.y=a.y;this.z=a.z;this.w=void 0!==a.w?a.w:1;return this},add:function(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector4's .add() now only accepts one argument. Use .addVectors( a, b ) instead."),this.addVectors(a,b);this.x+=a.x;this.y+=a.y;this.z+=a.z;this.w+=a.w;return this},addScalar:function(a){this.x+=a;this.y+=a;this.z+=a;this.w+=a;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(a,b){if(void 0!==b)return console.warn("DEPRECATED: Vector4's .sub() now only accepts one argument. Use .subVectors( a, b ) instead."),this.subVectors(a,b);this.x-=a.x;this.y-=a.y;this.z-=a.z;this.w-=a.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(a){this.x*=a;this.y*=a;this.z*=a;this.w*=a;return this},
+applyMatrix4:function(a){var b=this.x,c=this.y,d=this.z,e=this.w,a=a.elements;this.x=a[0]*b+a[4]*c+a[8]*d+a[12]*e;this.y=a[1]*b+a[5]*c+a[9]*d+a[13]*e;this.z=a[2]*b+a[6]*c+a[10]*d+a[14]*e;this.w=a[3]*b+a[7]*c+a[11]*d+a[15]*e;return this},divideScalar:function(a){0!==a?(a=1/a,this.x*=a,this.y*=a,this.z*=a,this.w*=a):(this.z=this.y=this.x=0,this.w=1);return this},setAxisAngleFromQuaternion:function(a){this.w=2*Math.acos(a.w);var b=Math.sqrt(1-a.w*a.w);1E-4>b?(this.x=1,this.z=this.y=0):(this.x=a.x/b,
+this.y=a.y/b,this.z=a.z/b);return this},setAxisAngleFromRotationMatrix:function(a){var b,c,d,a=a.elements,e=a[0];d=a[4];var f=a[8],g=a[1],h=a[5],i=a[9];c=a[2];b=a[6];var k=a[10];if(0.01>Math.abs(d-g)&&0.01>Math.abs(f-c)&&0.01>Math.abs(i-b)){if(0.1>Math.abs(d+g)&&0.1>Math.abs(f+c)&&0.1>Math.abs(i+b)&&0.1>Math.abs(e+h+k-3))return this.set(1,0,0,0),this;a=Math.PI;e=(e+1)/2;h=(h+1)/2;k=(k+1)/2;d=(d+g)/4;f=(f+c)/4;i=(i+b)/4;e>h&&e>k?0.01>e?(b=0,d=c=0.707106781):(b=Math.sqrt(e),c=d/b,d=f/b):h>k?0.01>h?
+(b=0.707106781,c=0,d=0.707106781):(c=Math.sqrt(h),b=d/c,d=i/c):0.01>k?(c=b=0.707106781,d=0):(d=Math.sqrt(k),b=f/d,c=i/d);this.set(b,c,d,a);return this}a=Math.sqrt((b-i)*(b-i)+(f-c)*(f-c)+(g-d)*(g-d));0.001>Math.abs(a)&&(a=1);this.x=(b-i)/a;this.y=(f-c)/a;this.z=(g-d)/a;this.w=Math.acos((e+h+k-1)/2);return this},min:function(a){this.x>a.x&&(this.x=a.x);this.y>a.y&&(this.y=a.y);this.z>a.z&&(this.z=a.z);this.w>a.w&&(this.w=a.w);return this},max:function(a){this.x<a.x&&(this.x=a.x);this.y<a.y&&(this.y=
+a.y);this.z<a.z&&(this.z=a.z);this.w<a.w&&(this.w=a.w);return this},clamp:function(a,b){this.x<a.x?this.x=a.x:this.x>b.x&&(this.x=b.x);this.y<a.y?this.y=a.y:this.y>b.y&&(this.y=b.y);this.z<a.z?this.z=a.z:this.z>b.z&&(this.z=b.z);this.w<a.w?this.w=a.w:this.w>b.w&&(this.w=b.w);return this},negate:function(){return this.multiplyScalar(-1)},dot:function(a){return this.x*a.x+this.y*a.y+this.z*a.z+this.w*a.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(a){var b=this.length();0!==b&&a!==b&&this.multiplyScalar(a/b);return this},lerp:function(a,b){this.x+=(a.x-this.x)*b;this.y+=(a.y-this.y)*b;this.z+=(a.z-this.z)*b;this.w+=(a.w-this.w)*b;return this},equals:function(a){return a.x===this.x&&a.y===this.y&&a.z===this.z&&
+a.w===this.w},fromArray:function(a){this.x=a[0];this.y=a[1];this.z=a[2];this.w=a[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)}};THREE.Euler=function(a,b,c,d){this._x=a||0;this._y=b||0;this._z=c||0;this._order=d||THREE.Euler.DefaultOrder};THREE.Euler.RotationOrders="XYZ YZX ZXY XZY YXZ ZYX".split(" ");THREE.Euler.DefaultOrder="XYZ";
+THREE.Euler.prototype={constructor:THREE.Euler,_x:0,_y:0,_z:0,_order:THREE.Euler.DefaultOrder,_quaternion:void 0,_updateQuaternion:function(){void 0!==this._quaternion&&this._quaternion.setFromEuler(this,!1)},get x(){return this._x},set x(a){this._x=a;this._updateQuaternion()},get y(){return this._y},set y(a){this._y=a;this._updateQuaternion()},get z(){return this._z},set z(a){this._z=a;this._updateQuaternion()},get order(){return this._order},set order(a){this._order=a;this._updateQuaternion()},
+set:function(a,b,c,d){this._x=a;this._y=b;this._z=c;this._order=d||this._order;this._updateQuaternion();return this},copy:function(a){this._x=a._x;this._y=a._y;this._z=a._z;this._order=a._order;this._updateQuaternion();return this},setFromRotationMatrix:function(a,b){function c(a){return Math.min(Math.max(a,-1),1)}var d=a.elements,e=d[0],f=d[4],g=d[8],h=d[1],i=d[5],k=d[9],l=d[2],n=d[6],d=d[10],b=b||this._order;"XYZ"===b?(this._y=Math.asin(c(g)),0.99999>Math.abs(g)?(this._x=Math.atan2(-k,d),this._z=
+Math.atan2(-f,e)):(this._x=Math.atan2(n,i),this._z=0)):"YXZ"===b?(this._x=Math.asin(-c(k)),0.99999>Math.abs(k)?(this._y=Math.atan2(g,d),this._z=Math.atan2(h,i)):(this._y=Math.atan2(-l,e),this._z=0)):"ZXY"===b?(this._x=Math.asin(c(n)),0.99999>Math.abs(n)?(this._y=Math.atan2(-l,d),this._z=Math.atan2(-f,i)):(this._y=0,this._z=Math.atan2(h,e))):"ZYX"===b?(this._y=Math.asin(-c(l)),0.99999>Math.abs(l)?(this._x=Math.atan2(n,d),this._z=Math.atan2(h,e)):(this._x=0,this._z=Math.atan2(-f,i))):"YZX"===b?(this._z=
+Math.asin(c(h)),0.99999>Math.abs(h)?(this._x=Math.atan2(-k,i),this._y=Math.atan2(-l,e)):(this._x=0,this._y=Math.atan2(g,d))):"XZY"===b?(this._z=Math.asin(-c(f)),0.99999>Math.abs(f)?(this._x=Math.atan2(n,i),this._y=Math.atan2(g,e)):(this._x=Math.atan2(-k,d),this._y=0)):console.warn("WARNING: Euler.setFromRotationMatrix() given unsupported order: "+b);this._order=b;this._updateQuaternion();return this},setFromQuaternion:function(a,b,c){function d(a){return Math.min(Math.max(a,-1),1)}var e=a.x*a.x,f=
+a.y*a.y,g=a.z*a.z,h=a.w*a.w,b=b||this._order;"XYZ"===b?(this._x=Math.atan2(2*(a.x*a.w-a.y*a.z),h-e-f+g),this._y=Math.asin(d(2*(a.x*a.z+a.y*a.w))),this._z=Math.atan2(2*(a.z*a.w-a.x*a.y),h+e-f-g)):"YXZ"===b?(this._x=Math.asin(d(2*(a.x*a.w-a.y*a.z))),this._y=Math.atan2(2*(a.x*a.z+a.y*a.w),h-e-f+g),this._z=Math.atan2(2*(a.x*a.y+a.z*a.w),h-e+f-g)):"ZXY"===b?(this._x=Math.asin(d(2*(a.x*a.w+a.y*a.z))),this._y=Math.atan2(2*(a.y*a.w-a.z*a.x),h-e-f+g),this._z=Math.atan2(2*(a.z*a.w-a.x*a.y),h-e+f-g)):"ZYX"===
+b?(this._x=Math.atan2(2*(a.x*a.w+a.z*a.y),h-e-f+g),this._y=Math.asin(d(2*(a.y*a.w-a.x*a.z))),this._z=Math.atan2(2*(a.x*a.y+a.z*a.w),h+e-f-g)):"YZX"===b?(this._x=Math.atan2(2*(a.x*a.w-a.z*a.y),h-e+f-g),this._y=Math.atan2(2*(a.y*a.w-a.x*a.z),h+e-f-g),this._z=Math.asin(d(2*(a.x*a.y+a.z*a.w)))):"XZY"===b?(this._x=Math.atan2(2*(a.x*a.w+a.y*a.z),h-e+f-g),this._y=Math.atan2(2*(a.x*a.z+a.y*a.w),h+e-f-g),this._z=Math.asin(d(2*(a.z*a.w-a.x*a.y)))):console.warn("WARNING: Euler.setFromQuaternion() given unsupported order: "+
+b);this._order=b;!1!==c&&this._updateQuaternion();return this},reorder:function(){var a=new THREE.Quaternion;return function(b){a.setFromEuler(this);this.setFromQuaternion(a,b)}}(),fromArray:function(a){this._x=a[0];this._y=a[1];this._z=a[2];void 0!==a[3]&&(this._order=a[3]);this._updateQuaternion();return this},toArray:function(){return[this._x,this._y,this._z,this._order]},equals:function(a){return a._x===this._x&&a._y===this._y&&a._z===this._z&&a._order===this._order},clone:function(){return new THREE.Euler(this._x,
+this._y,this._z,this._order)}};THREE.Line3=function(a,b){this.start=void 0!==a?a:new THREE.Vector3;this.end=void 0!==b?b:new THREE.Vector3};
+THREE.Line3.prototype={constructor:THREE.Line3,set:function(a,b){this.start.copy(a);this.end.copy(b);return this},copy:function(a){this.start.copy(a.start);this.end.copy(a.end);return this},center:function(a){return(a||new THREE.Vector3).addVectors(this.start,this.end).multiplyScalar(0.5)},delta:function(a){return(a||new THREE.Vector3).subVectors(this.end,this.start)},distanceSq:function(){return this.start.distanceToSquared(this.end)},distance:function(){return this.start.distanceTo(this.end)},at:function(a,
+b){var c=b||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},closestPointToPointParameter:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d){a.subVectors(c,this.start);b.subVectors(this.end,this.start);var e=b.dot(b),e=b.dot(a)/e;d&&(e=THREE.Math.clamp(e,0,1));return e}}(),closestPointToPoint:function(a,b,c){a=this.closestPointToPointParameter(a,b);c=c||new THREE.Vector3;return this.delta(c).multiplyScalar(a).add(this.start)},applyMatrix4:function(a){this.start.applyMatrix4(a);
+this.end.applyMatrix4(a);return this},equals:function(a){return a.start.equals(this.start)&&a.end.equals(this.end)},clone:function(){return(new THREE.Line3).copy(this)}};THREE.Box2=function(a,b){this.min=void 0!==a?a:new THREE.Vector2(Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector2(-Infinity,-Infinity)};
+THREE.Box2.prototype={constructor:THREE.Box2,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},setFromPoints:function(a){if(0<a.length){var b=a[0];this.min.copy(b);this.max.copy(b);for(var c=1,d=a.length;c<d;c++)b=a[c],b.x<this.min.x?this.min.x=b.x:b.x>this.max.x&&(this.max.x=b.x),b.y<this.min.y?this.min.y=b.y:b.y>this.max.y&&(this.max.y=b.y)}else this.makeEmpty();return this},setFromCenterAndSize:function(){var a=new THREE.Vector2;return function(b,c){var d=a.copy(c).multiplyScalar(0.5);
+this.min.copy(b).sub(d);this.max.copy(b).add(d);return this}}(),copy:function(a){this.min.copy(a.min);this.max.copy(a.max);return this},makeEmpty:function(){this.min.x=this.min.y=Infinity;this.max.x=this.max.y=-Infinity;return this},empty:function(){return this.max.x<this.min.x||this.max.y<this.min.y},center:function(a){return(a||new THREE.Vector2).addVectors(this.min,this.max).multiplyScalar(0.5)},size:function(a){return(a||new THREE.Vector2).subVectors(this.max,this.min)},expandByPoint:function(a){this.min.min(a);
+this.max.max(a);return this},expandByVector:function(a){this.min.sub(a);this.max.add(a);return this},expandByScalar:function(a){this.min.addScalar(-a);this.max.addScalar(a);return this},containsPoint:function(a){return a.x<this.min.x||a.x>this.max.x||a.y<this.min.y||a.y>this.max.y?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y?!0:!1},getParameter:function(a,b){return(b||new THREE.Vector2).set((a.x-this.min.x)/(this.max.x-this.min.x),
+(a.y-this.min.y)/(this.max.y-this.min.y))},isIntersectionBox:function(a){return a.max.x<this.min.x||a.min.x>this.max.x||a.max.y<this.min.y||a.min.y>this.max.y?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector2).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector2;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);
+return this},translate:function(a){this.min.add(a);this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)},clone:function(){return(new THREE.Box2).copy(this)}};THREE.Box3=function(a,b){this.min=void 0!==a?a:new THREE.Vector3(Infinity,Infinity,Infinity);this.max=void 0!==b?b:new THREE.Vector3(-Infinity,-Infinity,-Infinity)};
+THREE.Box3.prototype={constructor:THREE.Box3,set:function(a,b){this.min.copy(a);this.max.copy(b);return this},addPoint:function(a){a.x<this.min.x?this.min.x=a.x:a.x>this.max.x&&(this.max.x=a.x);a.y<this.min.y?this.min.y=a.y:a.y>this.max.y&&(this.max.y=a.y);a.z<this.min.z?this.min.z=a.z:a.z>this.max.z&&(this.max.z=a.z)},setFromPoints:function(a){if(0<a.length){var b=a[0];this.min.copy(b);this.max.copy(b);for(var b=1,c=a.length;b<c;b++)this.addPoint(a[b])}else this.makeEmpty();return this},setFromCenterAndSize:function(){var a=
+new THREE.Vector3;return function(b,c){var d=a.copy(c).multiplyScalar(0.5);this.min.copy(b).sub(d);this.max.copy(b).add(d);return this}}(),setFromObject:function(){var a=new THREE.Vector3;return function(b){var c=this;b.updateMatrixWorld(!0);this.makeEmpty();b.traverse(function(b){if(void 0!==b.geometry&&void 0!==b.geometry.vertices)for(var e=b.geometry.vertices,f=0,g=e.length;f<g;f++)a.copy(e[f]),a.applyMatrix4(b.matrixWorld),c.expandByPoint(a)});return this}}(),copy:function(a){this.min.copy(a.min);
+this.max.copy(a.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(){return this.max.x<this.min.x||this.max.y<this.min.y||this.max.z<this.min.z},center:function(a){return(a||new THREE.Vector3).addVectors(this.min,this.max).multiplyScalar(0.5)},size:function(a){return(a||new THREE.Vector3).subVectors(this.max,this.min)},expandByPoint:function(a){this.min.min(a);this.max.max(a);return this},expandByVector:function(a){this.min.sub(a);
+this.max.add(a);return this},expandByScalar:function(a){this.min.addScalar(-a);this.max.addScalar(a);return this},containsPoint:function(a){return a.x<this.min.x||a.x>this.max.x||a.y<this.min.y||a.y>this.max.y||a.z<this.min.z||a.z>this.max.z?!1:!0},containsBox:function(a){return this.min.x<=a.min.x&&a.max.x<=this.max.x&&this.min.y<=a.min.y&&a.max.y<=this.max.y&&this.min.z<=a.min.z&&a.max.z<=this.max.z?!0:!1},getParameter:function(a,b){return(b||new THREE.Vector3).set((a.x-this.min.x)/(this.max.x-
+this.min.x),(a.y-this.min.y)/(this.max.y-this.min.y),(a.z-this.min.z)/(this.max.z-this.min.z))},isIntersectionBox:function(a){return a.max.x<this.min.x||a.min.x>this.max.x||a.max.y<this.min.y||a.min.y>this.max.y||a.max.z<this.min.z||a.min.z>this.max.z?!1:!0},clampPoint:function(a,b){return(b||new THREE.Vector3).copy(a).clamp(this.min,this.max)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){return a.copy(b).clamp(this.min,this.max).sub(b).length()}}(),getBoundingSphere:function(){var a=
+new THREE.Vector3;return function(b){b=b||new THREE.Sphere;b.center=this.center();b.radius=0.5*this.size(a).length();return b}}(),intersect:function(a){this.min.max(a.min);this.max.min(a.max);return this},union:function(a){this.min.min(a.min);this.max.max(a.max);return this},applyMatrix4:function(){var a=[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(b){a[0].set(this.min.x,this.min.y,
+this.min.z).applyMatrix4(b);a[1].set(this.min.x,this.min.y,this.max.z).applyMatrix4(b);a[2].set(this.min.x,this.max.y,this.min.z).applyMatrix4(b);a[3].set(this.min.x,this.max.y,this.max.z).applyMatrix4(b);a[4].set(this.max.x,this.min.y,this.min.z).applyMatrix4(b);a[5].set(this.max.x,this.min.y,this.max.z).applyMatrix4(b);a[6].set(this.max.x,this.max.y,this.min.z).applyMatrix4(b);a[7].set(this.max.x,this.max.y,this.max.z).applyMatrix4(b);this.makeEmpty();this.setFromPoints(a);return this}}(),translate:function(a){this.min.add(a);
+this.max.add(a);return this},equals:function(a){return a.min.equals(this.min)&&a.max.equals(this.max)},clone:function(){return(new THREE.Box3).copy(this)}};THREE.Matrix3=function(a,b,c,d,e,f,g,h,i){this.elements=new Float32Array(9);this.set(void 0!==a?a:1,b||0,c||0,d||0,void 0!==e?e:1,f||0,g||0,h||0,void 0!==i?i:1)};
+THREE.Matrix3.prototype={constructor:THREE.Matrix3,set:function(a,b,c,d,e,f,g,h,i){var k=this.elements;k[0]=a;k[3]=b;k[6]=c;k[1]=d;k[4]=e;k[7]=f;k[2]=g;k[5]=h;k[8]=i;return this},identity:function(){this.set(1,0,0,0,1,0,0,0,1);return this},copy:function(a){a=a.elements;this.set(a[0],a[3],a[6],a[1],a[4],a[7],a[2],a[5],a[8]);return this},multiplyVector3:function(a){console.warn("DEPRECATED: Matrix3's .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.");return a.applyMatrix3(this)},
+multiplyVector3Array:function(){var a=new THREE.Vector3;return function(b){for(var c=0,d=b.length;c<d;c+=3)a.x=b[c],a.y=b[c+1],a.z=b[c+2],a.applyMatrix3(this),b[c]=a.x,b[c+1]=a.y,b[c+2]=a.z;return b}}(),multiplyScalar:function(a){var b=this.elements;b[0]*=a;b[3]*=a;b[6]*=a;b[1]*=a;b[4]*=a;b[7]*=a;b[2]*=a;b[5]*=a;b[8]*=a;return this},determinant:function(){var a=this.elements,b=a[0],c=a[1],d=a[2],e=a[3],f=a[4],g=a[5],h=a[6],i=a[7],a=a[8];return b*f*a-b*g*i-c*e*a+c*g*h+d*e*i-d*f*h},getInverse:function(a,
+b){var c=a.elements,d=this.elements;d[0]=c[10]*c[5]-c[6]*c[9];d[1]=-c[10]*c[1]+c[2]*c[9];d[2]=c[6]*c[1]-c[2]*c[5];d[3]=-c[10]*c[4]+c[6]*c[8];d[4]=c[10]*c[0]-c[2]*c[8];d[5]=-c[6]*c[0]+c[2]*c[4];d[6]=c[9]*c[4]-c[5]*c[8];d[7]=-c[9]*c[0]+c[1]*c[8];d[8]=c[5]*c[0]-c[1]*c[4];c=c[0]*d[0]+c[1]*d[3]+c[2]*d[6];if(0===c){if(b)throw Error("Matrix3.getInverse(): can't invert matrix, determinant is 0");console.warn("Matrix3.getInverse(): can't invert matrix, determinant is 0");this.identity();return this}this.multiplyScalar(1/
+c);return this},transpose:function(){var a,b=this.elements;a=b[1];b[1]=b[3];b[3]=a;a=b[2];b[2]=b[6];b[6]=a;a=b[5];b[5]=b[7];b[7]=a;return this},getNormalMatrix:function(a){this.getInverse(a).transpose();return this},transposeIntoArray:function(a){var b=this.elements;a[0]=b[0];a[1]=b[3];a[2]=b[6];a[3]=b[1];a[4]=b[4];a[5]=b[7];a[6]=b[2];a[7]=b[5];a[8]=b[8];return this},fromArray:function(a){this.elements.set(a);return this},toArray:function(){var a=this.elements;return[a[0],a[1],a[2],a[3],a[4],a[5],
+a[6],a[7],a[8]]},clone:function(){var a=this.elements;return new THREE.Matrix3(a[0],a[3],a[6],a[1],a[4],a[7],a[2],a[5],a[8])}};THREE.Matrix4=function(a,b,c,d,e,f,g,h,i,k,l,n,p,t,r,m){var q=this.elements=new Float32Array(16);q[0]=void 0!==a?a:1;q[4]=b||0;q[8]=c||0;q[12]=d||0;q[1]=e||0;q[5]=void 0!==f?f:1;q[9]=g||0;q[13]=h||0;q[2]=i||0;q[6]=k||0;q[10]=void 0!==l?l:1;q[14]=n||0;q[3]=p||0;q[7]=t||0;q[11]=r||0;q[15]=void 0!==m?m:1};
+THREE.Matrix4.prototype={constructor:THREE.Matrix4,set:function(a,b,c,d,e,f,g,h,i,k,l,n,p,t,r,m){var q=this.elements;q[0]=a;q[4]=b;q[8]=c;q[12]=d;q[1]=e;q[5]=f;q[9]=g;q[13]=h;q[2]=i;q[6]=k;q[10]=l;q[14]=n;q[3]=p;q[7]=t;q[11]=r;q[15]=m;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(a){this.elements.set(a.elements);return this},extractPosition:function(a){console.warn("DEPRECATED: Matrix4's .extractPosition() has been renamed to .copyPosition().");
+return this.copyPosition(a)},copyPosition:function(a){var b=this.elements,a=a.elements;b[12]=a[12];b[13]=a[13];b[14]=a[14];return this},extractRotation:function(){var a=new THREE.Vector3;return function(b){var c=this.elements,b=b.elements,d=1/a.set(b[0],b[1],b[2]).length(),e=1/a.set(b[4],b[5],b[6]).length(),f=1/a.set(b[8],b[9],b[10]).length();c[0]=b[0]*d;c[1]=b[1]*d;c[2]=b[2]*d;c[4]=b[4]*e;c[5]=b[5]*e;c[6]=b[6]*e;c[8]=b[8]*f;c[9]=b[9]*f;c[10]=b[10]*f;return this}}(),makeRotationFromEuler:function(a){!1===
+a instanceof THREE.Euler&&console.error("ERROR: Matrix's .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.  Please update your code.");var b=this.elements,c=a.x,d=a.y,e=a.z,f=Math.cos(c),c=Math.sin(c),g=Math.cos(d),d=Math.sin(d),h=Math.cos(e),e=Math.sin(e);if("XYZ"===a.order){var a=f*h,i=f*e,k=c*h,l=c*e;b[0]=g*h;b[4]=-g*e;b[8]=d;b[1]=i+k*d;b[5]=a-l*d;b[9]=-c*g;b[2]=l-a*d;b[6]=k+i*d;b[10]=f*g}else"YXZ"===a.order?(a=g*h,i=g*e,k=d*h,l=d*e,b[0]=a+l*c,b[4]=k*c-i,b[8]=
+f*d,b[1]=f*e,b[5]=f*h,b[9]=-c,b[2]=i*c-k,b[6]=l+a*c,b[10]=f*g):"ZXY"===a.order?(a=g*h,i=g*e,k=d*h,l=d*e,b[0]=a-l*c,b[4]=-f*e,b[8]=k+i*c,b[1]=i+k*c,b[5]=f*h,b[9]=l-a*c,b[2]=-f*d,b[6]=c,b[10]=f*g):"ZYX"===a.order?(a=f*h,i=f*e,k=c*h,l=c*e,b[0]=g*h,b[4]=k*d-i,b[8]=a*d+l,b[1]=g*e,b[5]=l*d+a,b[9]=i*d-k,b[2]=-d,b[6]=c*g,b[10]=f*g):"YZX"===a.order?(a=f*g,i=f*d,k=c*g,l=c*d,b[0]=g*h,b[4]=l-a*e,b[8]=k*e+i,b[1]=e,b[5]=f*h,b[9]=-c*h,b[2]=-d*h,b[6]=i*e+k,b[10]=a-l*e):"XZY"===a.order&&(a=f*g,i=f*d,k=c*g,l=c*d,b[0]=
+g*h,b[4]=-e,b[8]=d*h,b[1]=a*e+l,b[5]=f*h,b[9]=i*e-k,b[2]=k*e-i,b[6]=c*h,b[10]=l*e+a);b[3]=0;b[7]=0;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return this},setRotationFromQuaternion:function(a){console.warn("DEPRECATED: Matrix4's .setRotationFromQuaternion() has been deprecated in favor of makeRotationFromQuaternion.  Please update your code.");return this.makeRotationFromQuaternion(a)},makeRotationFromQuaternion:function(a){var b=this.elements,c=a.x,d=a.y,e=a.z,f=a.w,g=c+c,h=d+d,i=e+e,a=c*g,k=c*h,c=
+c*i,l=d*h,d=d*i,e=e*i,g=f*g,h=f*h,f=f*i;b[0]=1-(l+e);b[4]=k-f;b[8]=c+h;b[1]=k+f;b[5]=1-(a+e);b[9]=d-g;b[2]=c-h;b[6]=d+g;b[10]=1-(a+l);b[3]=0;b[7]=0;b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return this},lookAt:function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3;return function(d,e,f){var g=this.elements;c.subVectors(d,e).normalize();0===c.length()&&(c.z=1);a.crossVectors(f,c).normalize();0===a.length()&&(c.x+=1E-4,a.crossVectors(f,c).normalize());b.crossVectors(c,a);g[0]=a.x;
+g[4]=b.x;g[8]=c.x;g[1]=a.y;g[5]=b.y;g[9]=c.y;g[2]=a.z;g[6]=b.z;g[10]=c.z;return this}}(),multiply:function(a,b){return void 0!==b?(console.warn("DEPRECATED: Matrix4's .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead."),this.multiplyMatrices(a,b)):this.multiplyMatrices(this,a)},multiplyMatrices:function(a,b){var c=a.elements,d=b.elements,e=this.elements,f=c[0],g=c[4],h=c[8],i=c[12],k=c[1],l=c[5],n=c[9],p=c[13],t=c[2],r=c[6],m=c[10],q=c[14],u=c[3],s=c[7],v=c[11],c=c[15],
+z=d[0],G=d[4],N=d[8],C=d[12],B=d[1],x=d[5],F=d[9],L=d[13],w=d[2],E=d[6],y=d[10],D=d[14],H=d[3],K=d[7],A=d[11],d=d[15];e[0]=f*z+g*B+h*w+i*H;e[4]=f*G+g*x+h*E+i*K;e[8]=f*N+g*F+h*y+i*A;e[12]=f*C+g*L+h*D+i*d;e[1]=k*z+l*B+n*w+p*H;e[5]=k*G+l*x+n*E+p*K;e[9]=k*N+l*F+n*y+p*A;e[13]=k*C+l*L+n*D+p*d;e[2]=t*z+r*B+m*w+q*H;e[6]=t*G+r*x+m*E+q*K;e[10]=t*N+r*F+m*y+q*A;e[14]=t*C+r*L+m*D+q*d;e[3]=u*z+s*B+v*w+c*H;e[7]=u*G+s*x+v*E+c*K;e[11]=u*N+s*F+v*y+c*A;e[15]=u*C+s*L+v*D+c*d;return this},multiplyToArray:function(a,b,
+c){var d=this.elements;this.multiplyMatrices(a,b);c[0]=d[0];c[1]=d[1];c[2]=d[2];c[3]=d[3];c[4]=d[4];c[5]=d[5];c[6]=d[6];c[7]=d[7];c[8]=d[8];c[9]=d[9];c[10]=d[10];c[11]=d[11];c[12]=d[12];c[13]=d[13];c[14]=d[14];c[15]=d[15];return this},multiplyScalar:function(a){var b=this.elements;b[0]*=a;b[4]*=a;b[8]*=a;b[12]*=a;b[1]*=a;b[5]*=a;b[9]*=a;b[13]*=a;b[2]*=a;b[6]*=a;b[10]*=a;b[14]*=a;b[3]*=a;b[7]*=a;b[11]*=a;b[15]*=a;return this},multiplyVector3:function(a){console.warn("DEPRECATED: Matrix4's .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) or vector.applyProjection( matrix ) instead.");
+return a.applyProjection(this)},multiplyVector4:function(a){console.warn("DEPRECATED: Matrix4's .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.");return a.applyMatrix4(this)},multiplyVector3Array:function(){var a=new THREE.Vector3;return function(b){for(var c=0,d=b.length;c<d;c+=3)a.x=b[c],a.y=b[c+1],a.z=b[c+2],a.applyProjection(this),b[c]=a.x,b[c+1]=a.y,b[c+2]=a.z;return b}}(),rotateAxis:function(a){console.warn("DEPRECATED: Matrix4's .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.");
+a.transformDirection(this)},crossVector:function(a){console.warn("DEPRECATED: Matrix4's .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.");return a.applyMatrix4(this)},determinant:function(){var a=this.elements,b=a[0],c=a[4],d=a[8],e=a[12],f=a[1],g=a[5],h=a[9],i=a[13],k=a[2],l=a[6],n=a[10],p=a[14];return a[3]*(+e*h*l-d*i*l-e*g*n+c*i*n+d*g*p-c*h*p)+a[7]*(+b*h*p-b*i*n+e*f*n-d*f*p+d*i*k-e*h*k)+a[11]*(+b*i*l-b*g*p-e*f*l+c*f*p+e*g*k-c*i*k)+a[15]*(-d*g*k-b*h*l+b*g*n+d*f*l-c*f*
+n+c*h*k)},transpose:function(){var a=this.elements,b;b=a[1];a[1]=a[4];a[4]=b;b=a[2];a[2]=a[8];a[8]=b;b=a[6];a[6]=a[9];a[9]=b;b=a[3];a[3]=a[12];a[12]=b;b=a[7];a[7]=a[13];a[13]=b;b=a[11];a[11]=a[14];a[14]=b;return this},flattenToArray:function(a){var b=this.elements;a[0]=b[0];a[1]=b[1];a[2]=b[2];a[3]=b[3];a[4]=b[4];a[5]=b[5];a[6]=b[6];a[7]=b[7];a[8]=b[8];a[9]=b[9];a[10]=b[10];a[11]=b[11];a[12]=b[12];a[13]=b[13];a[14]=b[14];a[15]=b[15];return a},flattenToArrayOffset:function(a,b){var c=this.elements;
+a[b]=c[0];a[b+1]=c[1];a[b+2]=c[2];a[b+3]=c[3];a[b+4]=c[4];a[b+5]=c[5];a[b+6]=c[6];a[b+7]=c[7];a[b+8]=c[8];a[b+9]=c[9];a[b+10]=c[10];a[b+11]=c[11];a[b+12]=c[12];a[b+13]=c[13];a[b+14]=c[14];a[b+15]=c[15];return a},getPosition:function(){var a=new THREE.Vector3;return function(){console.warn("DEPRECATED: Matrix4's .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.");var b=this.elements;return a.set(b[12],b[13],b[14])}}(),setPosition:function(a){var b=this.elements;
+b[12]=a.x;b[13]=a.y;b[14]=a.z;return this},getInverse:function(a,b){var c=this.elements,d=a.elements,e=d[0],f=d[4],g=d[8],h=d[12],i=d[1],k=d[5],l=d[9],n=d[13],p=d[2],t=d[6],r=d[10],m=d[14],q=d[3],u=d[7],s=d[11],d=d[15];c[0]=l*m*u-n*r*u+n*t*s-k*m*s-l*t*d+k*r*d;c[4]=h*r*u-g*m*u-h*t*s+f*m*s+g*t*d-f*r*d;c[8]=g*n*u-h*l*u+h*k*s-f*n*s-g*k*d+f*l*d;c[12]=h*l*t-g*n*t-h*k*r+f*n*r+g*k*m-f*l*m;c[1]=n*r*q-l*m*q-n*p*s+i*m*s+l*p*d-i*r*d;c[5]=g*m*q-h*r*q+h*p*s-e*m*s-g*p*d+e*r*d;c[9]=h*l*q-g*n*q-h*i*s+e*n*s+g*i*d-
+e*l*d;c[13]=g*n*p-h*l*p+h*i*r-e*n*r-g*i*m+e*l*m;c[2]=k*m*q-n*t*q+n*p*u-i*m*u-k*p*d+i*t*d;c[6]=h*t*q-f*m*q-h*p*u+e*m*u+f*p*d-e*t*d;c[10]=f*n*q-h*k*q+h*i*u-e*n*u-f*i*d+e*k*d;c[14]=h*k*p-f*n*p-h*i*t+e*n*t+f*i*m-e*k*m;c[3]=l*t*q-k*r*q-l*p*u+i*r*u+k*p*s-i*t*s;c[7]=f*r*q-g*t*q+g*p*u-e*r*u-f*p*s+e*t*s;c[11]=g*k*q-f*l*q-g*i*u+e*l*u+f*i*s-e*k*s;c[15]=f*l*p-g*k*p+g*i*t-e*l*t-f*i*r+e*k*r;c=e*c[0]+i*c[4]+p*c[8]+q*c[12];if(0==c){if(b)throw Error("Matrix4.getInverse(): can't invert matrix, determinant is 0");console.warn("Matrix4.getInverse(): can't invert matrix, determinant is 0");
+this.identity();return this}this.multiplyScalar(1/c);return this},translate:function(){console.warn("DEPRECATED: Matrix4's .translate() has been removed.")},rotateX:function(){console.warn("DEPRECATED: Matrix4's .rotateX() has been removed.")},rotateY:function(){console.warn("DEPRECATED: Matrix4's .rotateY() has been removed.")},rotateZ:function(){console.warn("DEPRECATED: Matrix4's .rotateZ() has been removed.")},rotateByAxis:function(){console.warn("DEPRECATED: Matrix4's .rotateByAxis() has been removed.")},
+scale:function(a){var b=this.elements,c=a.x,d=a.y,a=a.z;b[0]*=c;b[4]*=d;b[8]*=a;b[1]*=c;b[5]*=d;b[9]*=a;b[2]*=c;b[6]*=d;b[10]*=a;b[3]*=c;b[7]*=d;b[11]*=a;return this},getMaxScaleOnAxis:function(){var a=this.elements;return Math.sqrt(Math.max(a[0]*a[0]+a[1]*a[1]+a[2]*a[2],Math.max(a[4]*a[4]+a[5]*a[5]+a[6]*a[6],a[8]*a[8]+a[9]*a[9]+a[10]*a[10])))},makeTranslation:function(a,b,c){this.set(1,0,0,a,0,1,0,b,0,0,1,c,0,0,0,1);return this},makeRotationX:function(a){var b=Math.cos(a),a=Math.sin(a);this.set(1,
+0,0,0,0,b,-a,0,0,a,b,0,0,0,0,1);return this},makeRotationY:function(a){var b=Math.cos(a),a=Math.sin(a);this.set(b,0,a,0,0,1,0,0,-a,0,b,0,0,0,0,1);return this},makeRotationZ:function(a){var b=Math.cos(a),a=Math.sin(a);this.set(b,-a,0,0,a,b,0,0,0,0,1,0,0,0,0,1);return this},makeRotationAxis:function(a,b){var c=Math.cos(b),d=Math.sin(b),e=1-c,f=a.x,g=a.y,h=a.z,i=e*f,k=e*g;this.set(i*f+c,i*g-d*h,i*h+d*g,0,i*g+d*h,k*g+c,k*h-d*f,0,i*h-d*g,k*h+d*f,e*h*h+c,0,0,0,0,1);return this},makeScale:function(a,b,c){this.set(a,
+0,0,0,0,b,0,0,0,0,c,0,0,0,0,1);return this},compose:function(a,b,c){this.makeRotationFromQuaternion(b);this.scale(c);this.setPosition(a);return this},decompose:function(){var a=new THREE.Vector3,b=new THREE.Matrix4;return function(c,d,e){var f=this.elements,g=a.set(f[0],f[1],f[2]).length(),h=a.set(f[4],f[5],f[6]).length(),i=a.set(f[8],f[9],f[10]).length();0>this.determinant()&&(g=-g);c.x=f[12];c.y=f[13];c.z=f[14];b.elements.set(this.elements);var c=1/g,f=1/h,k=1/i;b.elements[0]*=c;b.elements[1]*=
+c;b.elements[2]*=c;b.elements[4]*=f;b.elements[5]*=f;b.elements[6]*=f;b.elements[8]*=k;b.elements[9]*=k;b.elements[10]*=k;d.setFromRotationMatrix(b);e.x=g;e.y=h;e.z=i;return this}}(),makeFrustum:function(a,b,c,d,e,f){var g=this.elements;g[0]=2*e/(b-a);g[4]=0;g[8]=(b+a)/(b-a);g[12]=0;g[1]=0;g[5]=2*e/(d-c);g[9]=(d+c)/(d-c);g[13]=0;g[2]=0;g[6]=0;g[10]=-(f+e)/(f-e);g[14]=-2*f*e/(f-e);g[3]=0;g[7]=0;g[11]=-1;g[15]=0;return this},makePerspective:function(a,b,c,d){var a=c*Math.tan(THREE.Math.degToRad(0.5*
+a)),e=-a;return this.makeFrustum(e*b,a*b,e,a,c,d)},makeOrthographic:function(a,b,c,d,e,f){var g=this.elements,h=b-a,i=c-d,k=f-e;g[0]=2/h;g[4]=0;g[8]=0;g[12]=-((b+a)/h);g[1]=0;g[5]=2/i;g[9]=0;g[13]=-((c+d)/i);g[2]=0;g[6]=0;g[10]=-2/k;g[14]=-((f+e)/k);g[3]=0;g[7]=0;g[11]=0;g[15]=1;return this},fromArray:function(a){this.elements.set(a);return this},toArray:function(){var a=this.elements;return[a[0],a[1],a[2],a[3],a[4],a[5],a[6],a[7],a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]]},clone:function(){var a=
+this.elements;return new THREE.Matrix4(a[0],a[4],a[8],a[12],a[1],a[5],a[9],a[13],a[2],a[6],a[10],a[14],a[3],a[7],a[11],a[15])}};THREE.Ray=function(a,b){this.origin=void 0!==a?a:new THREE.Vector3;this.direction=void 0!==b?b:new THREE.Vector3};
+THREE.Ray.prototype={constructor:THREE.Ray,set:function(a,b){this.origin.copy(a);this.direction.copy(b);return this},copy:function(a){this.origin.copy(a.origin);this.direction.copy(a.direction);return this},at:function(a,b){return(b||new THREE.Vector3).copy(this.direction).multiplyScalar(a).add(this.origin)},recast:function(){var a=new THREE.Vector3;return function(b){this.origin.copy(this.at(b,a));return this}}(),closestPointToPoint:function(a,b){var c=b||new THREE.Vector3;c.subVectors(a,this.origin);
+var d=c.dot(this.direction);return 0>d?c.copy(this.origin):c.copy(this.direction).multiplyScalar(d).add(this.origin)},distanceToPoint:function(){var a=new THREE.Vector3;return function(b){var c=a.subVectors(b,this.origin).dot(this.direction);if(0>c)return this.origin.distanceTo(b);a.copy(this.direction).multiplyScalar(c).add(this.origin);return a.distanceTo(b)}}(),distanceSqToSegment:function(a,b,c,d){var e=a.clone().add(b).multiplyScalar(0.5),f=b.clone().sub(a).normalize(),g=0.5*a.distanceTo(b),
+h=this.origin.clone().sub(e),a=-this.direction.dot(f),b=h.dot(this.direction),i=-h.dot(f),k=h.lengthSq(),l=Math.abs(1-a*a),n,p;0<=l?(h=a*i-b,n=a*b-i,p=g*l,0<=h?n>=-p?n<=p?(g=1/l,h*=g,n*=g,a=h*(h+a*n+2*b)+n*(a*h+n+2*i)+k):(n=g,h=Math.max(0,-(a*n+b)),a=-h*h+n*(n+2*i)+k):(n=-g,h=Math.max(0,-(a*n+b)),a=-h*h+n*(n+2*i)+k):n<=-p?(h=Math.max(0,-(-a*g+b)),n=0<h?-g:Math.min(Math.max(-g,-i),g),a=-h*h+n*(n+2*i)+k):n<=p?(h=0,n=Math.min(Math.max(-g,-i),g),a=n*(n+2*i)+k):(h=Math.max(0,-(a*g+b)),n=0<h?g:Math.min(Math.max(-g,
+-i),g),a=-h*h+n*(n+2*i)+k)):(n=0<a?-g:g,h=Math.max(0,-(a*n+b)),a=-h*h+n*(n+2*i)+k);c&&c.copy(this.direction.clone().multiplyScalar(h).add(this.origin));d&&d.copy(f.clone().multiplyScalar(n).add(e));return a},isIntersectionSphere:function(a){return this.distanceToPoint(a.center)<=a.radius},isIntersectionPlane:function(a){var b=a.distanceToPoint(this.origin);return 0===b||0>a.normal.dot(this.direction)*b?!0:!1},distanceToPlane:function(a){var b=a.normal.dot(this.direction);if(0==b)return 0==a.distanceToPoint(this.origin)?
+0:null;a=-(this.origin.dot(a.normal)+a.constant)/b;return 0<=a?a:null},intersectPlane:function(a,b){var c=this.distanceToPlane(a);return null===c?null:this.at(c,b)},isIntersectionBox:function(){var a=new THREE.Vector3;return function(b){return null!==this.intersectBox(b,a)}}(),intersectBox:function(a,b){var c,d,e,f,g;d=1/this.direction.x;f=1/this.direction.y;g=1/this.direction.z;var h=this.origin;0<=d?(c=(a.min.x-h.x)*d,d*=a.max.x-h.x):(c=(a.max.x-h.x)*d,d*=a.min.x-h.x);0<=f?(e=(a.min.y-h.y)*f,f*=
+a.max.y-h.y):(e=(a.max.y-h.y)*f,f*=a.min.y-h.y);if(c>f||e>d)return null;if(e>c||c!==c)c=e;if(f<d||d!==d)d=f;0<=g?(e=(a.min.z-h.z)*g,g*=a.max.z-h.z):(e=(a.max.z-h.z)*g,g*=a.min.z-h.z);if(c>g||e>d)return null;if(e>c||c!==c)c=e;if(g<d||d!==d)d=g;return 0>d?null:this.at(0<=c?c:d,b)},intersectTriangle:function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3,d=new THREE.Vector3;return function(e,f,g,h,i){b.subVectors(f,e);c.subVectors(g,e);d.crossVectors(b,c);f=this.direction.dot(d);if(0<
+f){if(h)return null;h=1}else if(0>f)h=-1,f=-f;else return null;a.subVectors(this.origin,e);e=h*this.direction.dot(c.crossVectors(a,c));if(0>e)return null;g=h*this.direction.dot(b.cross(a));if(0>g||e+g>f)return null;e=-h*a.dot(d);return 0>e?null:this.at(e/f,i)}}(),applyMatrix4:function(a){this.direction.add(this.origin).applyMatrix4(a);this.origin.applyMatrix4(a);this.direction.sub(this.origin);this.direction.normalize();return this},equals:function(a){return a.origin.equals(this.origin)&&a.direction.equals(this.direction)},
+clone:function(){return(new THREE.Ray).copy(this)}};THREE.Sphere=function(a,b){this.center=void 0!==a?a:new THREE.Vector3;this.radius=void 0!==b?b:0};
+THREE.Sphere.prototype={constructor:THREE.Sphere,set:function(a,b){this.center.copy(a);this.radius=b;return this},setFromPoints:function(){var a=new THREE.Box3;return function(b,c){var d=this.center;void 0!==c?d.copy(c):a.setFromPoints(b).center(d);for(var e=0,f=0,g=b.length;f<g;f++)e=Math.max(e,d.distanceToSquared(b[f]));this.radius=Math.sqrt(e);return this}}(),copy:function(a){this.center.copy(a.center);this.radius=a.radius;return this},empty:function(){return 0>=this.radius},containsPoint:function(a){return a.distanceToSquared(this.center)<=
+this.radius*this.radius},distanceToPoint:function(a){return a.distanceTo(this.center)-this.radius},intersectsSphere:function(a){var b=this.radius+a.radius;return a.center.distanceToSquared(this.center)<=b*b},clampPoint:function(a,b){var c=this.center.distanceToSquared(a),d=b||new THREE.Vector3;d.copy(a);c>this.radius*this.radius&&(d.sub(this.center).normalize(),d.multiplyScalar(this.radius).add(this.center));return d},getBoundingBox:function(a){a=a||new THREE.Box3;a.set(this.center,this.center);a.expandByScalar(this.radius);
+return a},applyMatrix4:function(a){this.center.applyMatrix4(a);this.radius*=a.getMaxScaleOnAxis();return this},translate:function(a){this.center.add(a);return this},equals:function(a){return a.center.equals(this.center)&&a.radius===this.radius},clone:function(){return(new THREE.Sphere).copy(this)}};THREE.Frustum=function(a,b,c,d,e,f){this.planes=[void 0!==a?a:new THREE.Plane,void 0!==b?b:new THREE.Plane,void 0!==c?c:new THREE.Plane,void 0!==d?d:new THREE.Plane,void 0!==e?e:new THREE.Plane,void 0!==f?f:new THREE.Plane]};
+THREE.Frustum.prototype={constructor:THREE.Frustum,set:function(a,b,c,d,e,f){var g=this.planes;g[0].copy(a);g[1].copy(b);g[2].copy(c);g[3].copy(d);g[4].copy(e);g[5].copy(f);return this},copy:function(a){for(var b=this.planes,c=0;6>c;c++)b[c].copy(a.planes[c]);return this},setFromMatrix:function(a){var b=this.planes,c=a.elements,a=c[0],d=c[1],e=c[2],f=c[3],g=c[4],h=c[5],i=c[6],k=c[7],l=c[8],n=c[9],p=c[10],t=c[11],r=c[12],m=c[13],q=c[14],c=c[15];b[0].setComponents(f-a,k-g,t-l,c-r).normalize();b[1].setComponents(f+
+a,k+g,t+l,c+r).normalize();b[2].setComponents(f+d,k+h,t+n,c+m).normalize();b[3].setComponents(f-d,k-h,t-n,c-m).normalize();b[4].setComponents(f-e,k-i,t-p,c-q).normalize();b[5].setComponents(f+e,k+i,t+p,c+q).normalize();return this},intersectsObject:function(){var a=new THREE.Sphere;return function(b){var c=b.geometry;null===c.boundingSphere&&c.computeBoundingSphere();a.copy(c.boundingSphere);a.applyMatrix4(b.matrixWorld);return this.intersectsSphere(a)}}(),intersectsSphere:function(a){for(var b=this.planes,
+c=a.center,a=-a.radius,d=0;6>d;d++)if(b[d].distanceToPoint(c)<a)return!1;return!0},intersectsBox:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c){for(var d=this.planes,e=0;6>e;e++){var f=d[e];a.x=0<f.normal.x?c.min.x:c.max.x;b.x=0<f.normal.x?c.max.x:c.min.x;a.y=0<f.normal.y?c.min.y:c.max.y;b.y=0<f.normal.y?c.max.y:c.min.y;a.z=0<f.normal.z?c.min.z:c.max.z;b.z=0<f.normal.z?c.max.z:c.min.z;var g=f.distanceToPoint(a),f=f.distanceToPoint(b);if(0>g&&0>f)return!1}return!0}}(),containsPoint:function(a){for(var b=
+this.planes,c=0;6>c;c++)if(0>b[c].distanceToPoint(a))return!1;return!0},clone:function(){return(new THREE.Frustum).copy(this)}};THREE.Plane=function(a,b){this.normal=void 0!==a?a:new THREE.Vector3(1,0,0);this.constant=void 0!==b?b:0};
+THREE.Plane.prototype={constructor:THREE.Plane,set:function(a,b){this.normal.copy(a);this.constant=b;return this},setComponents:function(a,b,c,d){this.normal.set(a,b,c);this.constant=d;return this},setFromNormalAndCoplanarPoint:function(a,b){this.normal.copy(a);this.constant=-b.dot(this.normal);return this},setFromCoplanarPoints:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d,e){d=a.subVectors(e,d).cross(b.subVectors(c,d)).normalize();this.setFromNormalAndCoplanarPoint(d,
+c);return this}}(),copy:function(a){this.normal.copy(a.normal);this.constant=a.constant;return this},normalize:function(){var a=1/this.normal.length();this.normal.multiplyScalar(a);this.constant*=a;return this},negate:function(){this.constant*=-1;this.normal.negate();return this},distanceToPoint:function(a){return this.normal.dot(a)+this.constant},distanceToSphere:function(a){return this.distanceToPoint(a.center)-a.radius},projectPoint:function(a,b){return this.orthoPoint(a,b).sub(a).negate()},orthoPoint:function(a,
+b){var c=this.distanceToPoint(a);return(b||new THREE.Vector3).copy(this.normal).multiplyScalar(c)},isIntersectionLine:function(a){var b=this.distanceToPoint(a.start),a=this.distanceToPoint(a.end);return 0>b&&0<a||0>a&&0<b},intersectLine:function(){var a=new THREE.Vector3;return function(b,c){var d=c||new THREE.Vector3,e=b.delta(a),f=this.normal.dot(e);if(0==f){if(0==this.distanceToPoint(b.start))return d.copy(b.start)}else return f=-(b.start.dot(this.normal)+this.constant)/f,0>f||1<f?void 0:d.copy(e).multiplyScalar(f).add(b.start)}}(),
+coplanarPoint:function(a){return(a||new THREE.Vector3).copy(this.normal).multiplyScalar(-this.constant)},applyMatrix4:function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Matrix3;return function(d,e){var f=e||c.getNormalMatrix(d),f=a.copy(this.normal).applyMatrix3(f),g=this.coplanarPoint(b);g.applyMatrix4(d);this.setFromNormalAndCoplanarPoint(f,g);return this}}(),translate:function(a){this.constant-=a.dot(this.normal);return this},equals:function(a){return a.normal.equals(this.normal)&&
+a.constant==this.constant},clone:function(){return(new THREE.Plane).copy(this)}};THREE.Math={PI2:2*Math.PI,generateUUID:function(){var a="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".split(""),b=Array(36),c=0,d;return function(){for(var e=0;36>e;e++)8==e||13==e||18==e||23==e?b[e]="-":14==e?b[e]="4":(2>=c&&(c=33554432+16777216*Math.random()|0),d=c&15,c>>=4,b[e]=a[19==e?d&3|8:d]);return b.join("")}}(),clamp:function(a,b,c){return a<b?b:a>c?c:a},clampBottom:function(a,b){return a<b?b:a},mapLinear:function(a,b,c,d,e){return d+(a-b)*(e-d)/(c-b)},smoothstep:function(a,
+b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*(3-2*a)},smootherstep:function(a,b,c){if(a<=b)return 0;if(a>=c)return 1;a=(a-b)/(c-b);return a*a*a*(a*(6*a-15)+10)},random16:function(){return(65280*Math.random()+255*Math.random())/65535},randInt:function(a,b){return a+Math.floor(Math.random()*(b-a+1))},randFloat:function(a,b){return a+Math.random()*(b-a)},randFloatSpread:function(a){return a*(0.5-Math.random())},sign:function(a){return 0>a?-1:0<a?1:0},degToRad:function(){var a=Math.PI/
+180;return function(b){return b*a}}(),radToDeg:function(){var a=180/Math.PI;return function(b){return b*a}}()};THREE.Spline=function(a){function b(a,b,c,d,e,f,g){a=0.5*(c-a);d=0.5*(d-b);return(2*(b-c)+a+d)*g+(-3*(b-c)-2*a-d)*f+a*e+b}this.points=a;var c=[],d={x:0,y:0,z:0},e,f,g,h,i,k,l,n,p;this.initFromArray=function(a){this.points=[];for(var b=0;b<a.length;b++)this.points[b]={x:a[b][0],y:a[b][1],z:a[b][2]}};this.getPoint=function(a){e=(this.points.length-1)*a;f=Math.floor(e);g=e-f;c[0]=0===f?f:f-1;c[1]=f;c[2]=f>this.points.length-2?this.points.length-1:f+1;c[3]=f>this.points.length-3?this.points.length-1:
+f+2;k=this.points[c[0]];l=this.points[c[1]];n=this.points[c[2]];p=this.points[c[3]];h=g*g;i=g*h;d.x=b(k.x,l.x,n.x,p.x,g,h,i);d.y=b(k.y,l.y,n.y,p.y,g,h,i);d.z=b(k.z,l.z,n.z,p.z,g,h,i);return d};this.getControlPointsArray=function(){var a,b,c=this.points.length,d=[];for(a=0;a<c;a++)b=this.points[a],d[a]=[b.x,b.y,b.z];return d};this.getLength=function(a){var b,c,d,e=b=b=0,f=new THREE.Vector3,g=new THREE.Vector3,h=[],i=0;h[0]=0;a||(a=100);c=this.points.length*a;f.copy(this.points[0]);for(a=1;a<c;a++)b=
+a/c,d=this.getPoint(b),g.copy(d),i+=g.distanceTo(f),f.copy(d),b*=this.points.length-1,b=Math.floor(b),b!=e&&(h[b]=i,e=b);h[h.length]=i;return{chunks:h,total:i}};this.reparametrizeByArcLength=function(a){var b,c,d,e,f,g,h=[],i=new THREE.Vector3,k=this.getLength();h.push(i.copy(this.points[0]).clone());for(b=1;b<this.points.length;b++){c=k.chunks[b]-k.chunks[b-1];g=Math.ceil(a*c/k.total);e=(b-1)/(this.points.length-1);f=b/(this.points.length-1);for(c=1;c<g-1;c++)d=e+c*(1/g)*(f-e),d=this.getPoint(d),
+h.push(i.copy(d).clone());h.push(i.copy(this.points[b]).clone())}this.points=h}};THREE.Triangle=function(a,b,c){this.a=void 0!==a?a:new THREE.Vector3;this.b=void 0!==b?b:new THREE.Vector3;this.c=void 0!==c?c:new THREE.Vector3};THREE.Triangle.normal=function(){var a=new THREE.Vector3;return function(b,c,d,e){e=e||new THREE.Vector3;e.subVectors(d,c);a.subVectors(b,c);e.cross(a);b=e.lengthSq();return 0<b?e.multiplyScalar(1/Math.sqrt(b)):e.set(0,0,0)}}();
+THREE.Triangle.barycoordFromPoint=function(){var a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3;return function(d,e,f,g,h){a.subVectors(g,e);b.subVectors(f,e);c.subVectors(d,e);var d=a.dot(a),e=a.dot(b),f=a.dot(c),i=b.dot(b),g=b.dot(c),k=d*i-e*e,h=h||new THREE.Vector3;if(0==k)return h.set(-2,-1,-1);k=1/k;i=(i*f-e*g)*k;d=(d*g-e*f)*k;return h.set(1-i-d,d,i)}}();
+THREE.Triangle.containsPoint=function(){var a=new THREE.Vector3;return function(b,c,d,e){b=THREE.Triangle.barycoordFromPoint(b,c,d,e,a);return 0<=b.x&&0<=b.y&&1>=b.x+b.y}}();
+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(a,b,c,d){this.a.copy(a[b]);this.b.copy(a[c]);this.c.copy(a[d]);return this},copy:function(a){this.a.copy(a.a);this.b.copy(a.b);this.c.copy(a.c);return this},area:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(){a.subVectors(this.c,this.b);b.subVectors(this.a,this.b);return 0.5*a.cross(b).length()}}(),midpoint:function(a){return(a||
+new THREE.Vector3).addVectors(this.a,this.b).add(this.c).multiplyScalar(1/3)},normal:function(a){return THREE.Triangle.normal(this.a,this.b,this.c,a)},plane:function(a){return(a||new THREE.Plane).setFromCoplanarPoints(this.a,this.b,this.c)},barycoordFromPoint:function(a,b){return THREE.Triangle.barycoordFromPoint(a,this.a,this.b,this.c,b)},containsPoint:function(a){return THREE.Triangle.containsPoint(a,this.a,this.b,this.c)},equals:function(a){return a.a.equals(this.a)&&a.b.equals(this.b)&&a.c.equals(this.c)},
+clone:function(){return(new THREE.Triangle).copy(this)}};THREE.Vertex=function(a){console.warn("THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.");return a};THREE.UV=function(a,b){console.warn("THREE.UV has been DEPRECATED. Use THREE.Vector2 instead.");return new THREE.Vector2(a,b)};THREE.Clock=function(a){this.autoStart=void 0!==a?a:!0;this.elapsedTime=this.oldTime=this.startTime=0;this.running=!1};
+THREE.Clock.prototype={constructor:THREE.Clock,start:function(){this.oldTime=this.startTime=void 0!==self.performance&&void 0!==self.performance.now?self.performance.now():Date.now();this.running=!0},stop:function(){this.getElapsedTime();this.running=!1},getElapsedTime:function(){this.getDelta();return this.elapsedTime},getDelta:function(){var a=0;this.autoStart&&!this.running&&this.start();if(this.running){var b=void 0!==self.performance&&void 0!==self.performance.now?self.performance.now():Date.now(),
+a=0.001*(b-this.oldTime);this.oldTime=b;this.elapsedTime+=a}return a}};THREE.EventDispatcher=function(){};
+THREE.EventDispatcher.prototype={constructor:THREE.EventDispatcher,apply:function(a){a.addEventListener=THREE.EventDispatcher.prototype.addEventListener;a.hasEventListener=THREE.EventDispatcher.prototype.hasEventListener;a.removeEventListener=THREE.EventDispatcher.prototype.removeEventListener;a.dispatchEvent=THREE.EventDispatcher.prototype.dispatchEvent},addEventListener:function(a,b){void 0===this._listeners&&(this._listeners={});var c=this._listeners;void 0===c[a]&&(c[a]=[]);-1===c[a].indexOf(b)&&
+c[a].push(b)},hasEventListener:function(a,b){if(void 0===this._listeners)return!1;var c=this._listeners;return void 0!==c[a]&&-1!==c[a].indexOf(b)?!0:!1},removeEventListener:function(a,b){if(void 0!==this._listeners){var c=this._listeners[a];if(void 0!==c){var d=c.indexOf(b);-1!==d&&c.splice(d,1)}}},dispatchEvent:function(){var a=[];return function(b){if(void 0!==this._listeners){var c=this._listeners[b.type];if(void 0!==c){b.target=this;for(var d=c.length,e=0;e<d;e++)a[e]=c[e];for(e=0;e<d;e++)a[e].call(this,
+b)}}}}()};(function(a){a.Raycaster=function(b,c,d,e){this.ray=new a.Ray(b,c);this.near=d||0;this.far=e||Infinity};var b=new a.Sphere,c=new a.Ray;new a.Plane;new a.Vector3;var d=new a.Vector3,e=new a.Matrix4,f=function(a,b){return a.distance-b.distance},g=new a.Vector3,h=new a.Vector3,i=new a.Vector3,k=function(f,l,t){if(f instanceof a.Sprite){d.setFromMatrixPosition(f.matrixWorld);var r=l.ray.distanceToPoint(d);if(r>f.scale.x)return t;t.push({distance:r,point:f.position,face:null,object:f})}else if(f instanceof
+a.LOD)d.setFromMatrixPosition(f.matrixWorld),r=l.ray.origin.distanceTo(d),k(f.getObjectForDistance(r),l,t);else if(f instanceof a.Mesh){var m=f.geometry;null===m.boundingSphere&&m.computeBoundingSphere();b.copy(m.boundingSphere);b.applyMatrix4(f.matrixWorld);if(!1===l.ray.isIntersectionSphere(b))return t;e.getInverse(f.matrixWorld);c.copy(l.ray).applyMatrix4(e);if(null!==m.boundingBox&&!1===c.isIntersectionBox(m.boundingBox))return t;if(m instanceof a.BufferGeometry){var q=f.material;if(void 0===
+q||!1===m.dynamic)return t;var u,s,v=l.precision;if(void 0!==m.attributes.index)for(var z=m.offsets,G=m.attributes.index.array,N=m.attributes.position.array,C=m.offsets.length,B=m.attributes.index.array.length/3,B=0;B<C;++B)for(var r=z[B].start,x=z[B].index,m=r,F=r+z[B].count;m<F;m+=3)r=x+G[m],u=x+G[m+1],s=x+G[m+2],g.set(N[3*r],N[3*r+1],N[3*r+2]),h.set(N[3*u],N[3*u+1],N[3*u+2]),i.set(N[3*s],N[3*s+1],N[3*s+2]),u=q.side===a.BackSide?c.intersectTriangle(i,h,g,!0):c.intersectTriangle(g,h,i,q.side!==a.DoubleSide),
+null!==u&&(u.applyMatrix4(f.matrixWorld),r=l.ray.origin.distanceTo(u),r<v||(r<l.near||r>l.far)||t.push({distance:r,point:u,face:null,faceIndex:null,object:f}));else{N=m.attributes.position.array;B=m.attributes.position.array.length;for(m=0;m<B;m+=3)r=m,u=m+1,s=m+2,g.set(N[3*r],N[3*r+1],N[3*r+2]),h.set(N[3*u],N[3*u+1],N[3*u+2]),i.set(N[3*s],N[3*s+1],N[3*s+2]),u=q.side===a.BackSide?c.intersectTriangle(i,h,g,!0):c.intersectTriangle(g,h,i,q.side!==a.DoubleSide),null!==u&&(u.applyMatrix4(f.matrixWorld),
+r=l.ray.origin.distanceTo(u),r<v||(r<l.near||r>l.far)||t.push({distance:r,point:u,face:null,faceIndex:null,object:f}))}}else if(m instanceof a.Geometry){G=f.material instanceof a.MeshFaceMaterial;N=!0===G?f.material.materials:null;v=l.precision;z=m.vertices;C=0;for(B=m.faces.length;C<B;C++)if(x=m.faces[C],q=!0===G?N[x.materialIndex]:f.material,void 0!==q){r=z[x.a];u=z[x.b];s=z[x.c];if(!0===q.morphTargets){var F=m.morphTargets,L=f.morphTargetInfluences;g.set(0,0,0);h.set(0,0,0);i.set(0,0,0);for(var w=
+0,E=F.length;w<E;w++){var y=L[w];if(0!==y){var D=F[w].vertices;g.x+=(D[x.a].x-r.x)*y;g.y+=(D[x.a].y-r.y)*y;g.z+=(D[x.a].z-r.z)*y;h.x+=(D[x.b].x-u.x)*y;h.y+=(D[x.b].y-u.y)*y;h.z+=(D[x.b].z-u.z)*y;i.x+=(D[x.c].x-s.x)*y;i.y+=(D[x.c].y-s.y)*y;i.z+=(D[x.c].z-s.z)*y}}g.add(r);h.add(u);i.add(s);r=g;u=h;s=i}u=q.side===a.BackSide?c.intersectTriangle(s,u,r,!0):c.intersectTriangle(r,u,s,q.side!==a.DoubleSide);null!==u&&(u.applyMatrix4(f.matrixWorld),r=l.ray.origin.distanceTo(u),r<v||(r<l.near||r>l.far)||t.push({distance:r,
+point:u,face:x,faceIndex:C,object:f}))}}}else if(f instanceof a.Line){v=l.linePrecision;q=v*v;m=f.geometry;null===m.boundingSphere&&m.computeBoundingSphere();b.copy(m.boundingSphere);b.applyMatrix4(f.matrixWorld);if(!1===l.ray.isIntersectionSphere(b))return t;e.getInverse(f.matrixWorld);c.copy(l.ray).applyMatrix4(e);if(m instanceof a.Geometry){z=m.vertices;v=z.length;u=new a.Vector3;s=new a.Vector3;B=f.type===a.LineStrip?1:2;for(m=0;m<v-1;m+=B)c.distanceSqToSegment(z[m],z[m+1],s,u)>q||(r=c.origin.distanceTo(s),
+r<l.near||r>l.far||t.push({distance:r,point:u.clone().applyMatrix4(f.matrixWorld),face:null,faceIndex:null,object:f}))}}},l=function(a,b,c){for(var a=a.getDescendants(),d=0,e=a.length;d<e;d++)k(a[d],b,c)};a.Raycaster.prototype.precision=1E-4;a.Raycaster.prototype.linePrecision=1;a.Raycaster.prototype.set=function(a,b){this.ray.set(a,b)};a.Raycaster.prototype.intersectObject=function(a,b){var c=[];!0===b&&l(a,this,c);k(a,this,c);c.sort(f);return c};a.Raycaster.prototype.intersectObjects=function(a,
+b){for(var c=[],d=0,e=a.length;d<e;d++)k(a[d],this,c),!0===b&&l(a[d],this,c);c.sort(f);return c}})(THREE);THREE.Object3D=function(){this.id=THREE.Object3DIdCount++;this.uuid=THREE.Math.generateUUID();this.name="";this.parent=void 0;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);this._rotation._quaternion=this.quaternion;this._quaternion._euler=this.rotation;this.renderDepth=null;this.rotationAutoUpdate=!0;this.matrix=new THREE.Matrix4;this.matrixWorld=new THREE.Matrix4;
+this.visible=this.matrixWorldNeedsUpdate=this.matrixAutoUpdate=!0;this.receiveShadow=this.castShadow=!1;this.frustumCulled=!0;this.userData={}};
+THREE.Object3D.prototype={constructor:THREE.Object3D,get rotation(){return this._rotation},set rotation(a){this._rotation=a;this._rotation._quaternion=this._quaternion;this._quaternion._euler=this._rotation;this._rotation._updateQuaternion()},get quaternion(){return this._quaternion},set quaternion(a){this._quaternion=a;this._quaternion._euler=this._rotation;this._rotation._quaternion=this._quaternion;this._quaternion._updateEuler()},get eulerOrder(){console.warn("DEPRECATED: Object3D's .eulerOrder has been moved to Object3D's .rotation.order.");
+return this.rotation.order},set eulerOrder(a){console.warn("DEPRECATED: Object3D's .eulerOrder has been moved to Object3D's .rotation.order.");this.rotation.order=a},get useQuaternion(){console.warn("DEPRECATED: Object3D's .useQuaternion has been removed. The library now uses quaternions by default.")},set useQuaternion(a){console.warn("DEPRECATED: Object3D's .useQuaternion has been removed. The library now uses quaternions by default.")},applyMatrix:function(a){this.matrix.multiplyMatrices(a,this.matrix);
+this.matrix.decompose(this.position,this.quaternion,this.scale)},setRotationFromAxisAngle:function(a,b){this.quaternion.setFromAxisAngle(a,b)},setRotationFromEuler:function(a){this.quaternion.setFromEuler(a,!0)},setRotationFromMatrix:function(a){this.quaternion.setFromRotationMatrix(a)},setRotationFromQuaternion:function(a){this.quaternion.copy(a)},rotateOnAxis:function(){var a=new THREE.Quaternion;return function(b,c){a.setFromAxisAngle(b,c);this.quaternion.multiply(a);return this}}(),rotateX:function(){var a=
+new THREE.Vector3(1,0,0);return function(b){return this.rotateOnAxis(a,b)}}(),rotateY:function(){var a=new THREE.Vector3(0,1,0);return function(b){return this.rotateOnAxis(a,b)}}(),rotateZ:function(){var a=new THREE.Vector3(0,0,1);return function(b){return this.rotateOnAxis(a,b)}}(),translateOnAxis:function(){var a=new THREE.Vector3;return function(b,c){a.copy(b);a.applyQuaternion(this.quaternion);this.position.add(a.multiplyScalar(c));return this}}(),translate:function(a,b){console.warn("DEPRECATED: Object3D's .translate() has been removed. Use .translateOnAxis( axis, distance ) instead. Note args have been changed.");
+return this.translateOnAxis(b,a)},translateX:function(){var a=new THREE.Vector3(1,0,0);return function(b){return this.translateOnAxis(a,b)}}(),translateY:function(){var a=new THREE.Vector3(0,1,0);return function(b){return this.translateOnAxis(a,b)}}(),translateZ:function(){var a=new THREE.Vector3(0,0,1);return function(b){return this.translateOnAxis(a,b)}}(),localToWorld:function(a){return a.applyMatrix4(this.matrixWorld)},worldToLocal:function(){var a=new THREE.Matrix4;return function(b){return b.applyMatrix4(a.getInverse(this.matrixWorld))}}(),
+lookAt:function(){var a=new THREE.Matrix4;return function(b){a.lookAt(b,this.position,this.up);this.quaternion.setFromRotationMatrix(a)}}(),add:function(a){if(a===this)console.warn("THREE.Object3D.add: An object can't be added as a child of itself.");else if(a instanceof THREE.Object3D){void 0!==a.parent&&a.parent.remove(a);a.parent=this;a.dispatchEvent({type:"added"});this.children.push(a);for(var b=this;void 0!==b.parent;)b=b.parent;void 0!==b&&b instanceof THREE.Scene&&b.__addObject(a)}},remove:function(a){var b=
+this.children.indexOf(a);if(-1!==b){a.parent=void 0;a.dispatchEvent({type:"removed"});this.children.splice(b,1);for(b=this;void 0!==b.parent;)b=b.parent;void 0!==b&&b instanceof THREE.Scene&&b.__removeObject(a)}},traverse:function(a){a(this);for(var b=0,c=this.children.length;b<c;b++)this.children[b].traverse(a)},getObjectById:function(a,b){for(var c=0,d=this.children.length;c<d;c++){var e=this.children[c];if(e.id===a||!0===b&&(e=e.getObjectById(a,b),void 0!==e))return e}},getObjectByName:function(a,
+b){for(var c=0,d=this.children.length;c<d;c++){var e=this.children[c];if(e.name===a||!0===b&&(e=e.getObjectByName(a,b),void 0!==e))return e}},getChildByName:function(a,b){console.warn("DEPRECATED: Object3D's .getChildByName() has been renamed to .getObjectByName().");return this.getObjectByName(a,b)},getDescendants:function(a){void 0===a&&(a=[]);Array.prototype.push.apply(a,this.children);for(var b=0,c=this.children.length;b<c;b++)this.children[b].getDescendants(a);return a},updateMatrix:function(){this.matrix.compose(this.position,
+this.quaternion,this.scale);this.matrixWorldNeedsUpdate=!0},updateMatrixWorld:function(a){!0===this.matrixAutoUpdate&&this.updateMatrix();if(!0===this.matrixWorldNeedsUpdate||!0===a)void 0===this.parent?this.matrixWorld.copy(this.matrix):this.matrixWorld.multiplyMatrices(this.parent.matrixWorld,this.matrix),this.matrixWorldNeedsUpdate=!1,a=!0;for(var b=0,c=this.children.length;b<c;b++)this.children[b].updateMatrixWorld(a)},clone:function(a,b){void 0===a&&(a=new THREE.Object3D);void 0===b&&(b=!0);
+a.name=this.name;a.up.copy(this.up);a.position.copy(this.position);a.quaternion.copy(this.quaternion);a.scale.copy(this.scale);a.renderDepth=this.renderDepth;a.rotationAutoUpdate=this.rotationAutoUpdate;a.matrix.copy(this.matrix);a.matrixWorld.copy(this.matrixWorld);a.matrixAutoUpdate=this.matrixAutoUpdate;a.matrixWorldNeedsUpdate=this.matrixWorldNeedsUpdate;a.visible=this.visible;a.castShadow=this.castShadow;a.receiveShadow=this.receiveShadow;a.frustumCulled=this.frustumCulled;a.userData=JSON.parse(JSON.stringify(this.userData));
+if(!0===b)for(var c=0;c<this.children.length;c++)a.add(this.children[c].clone());return a}};THREE.EventDispatcher.prototype.apply(THREE.Object3D.prototype);THREE.Object3DIdCount=0;THREE.Projector=function(){function a(){if(i===l){var a=new THREE.RenderableVertex;k.push(a);l++;i++;return a}return k[i++]}function b(a,b){return a.z!==b.z?b.z-a.z:a.id!==b.id?a.id-b.id:0}function c(a,b){var c=0,d=1,e=a.z+a.w,f=b.z+b.w,g=-a.z+a.w,h=-b.z+b.w;if(0<=e&&0<=f&&0<=g&&0<=h)return!0;if(0>e&&0>f||0>g&&0>h)return!1;0>e?c=Math.max(c,e/(e-f)):0>f&&(d=Math.min(d,e/(e-f)));0>g?c=Math.max(c,g/(g-h)):0>h&&(d=Math.min(d,g/(g-h)));if(d<c)return!1;a.lerp(b,c);b.lerp(a,1-d);return!0}var d,e,f=[],g=
+0,h,i,k=[],l=0,n,p,t=[],r=0,m,q,u=[],s=0,v,z,G=[],N=0,C={objects:[],sprites:[],lights:[],elements:[]},B=new THREE.Vector3,x=new THREE.Vector3,F=new THREE.Vector3,L=new THREE.Vector3,w=new THREE.Vector4,E=new THREE.Box3(new THREE.Vector3(-1,-1,-1),new THREE.Vector3(1,1,1)),y=new THREE.Box3,D=Array(3),H=new THREE.Matrix4,K=new THREE.Matrix4,A,da=new THREE.Matrix4,ha=new THREE.Matrix3,ua=new THREE.Matrix3,O=new THREE.Vector3,T=new THREE.Frustum,ea=new THREE.Vector4,I=new THREE.Vector4;this.projectVector=
+function(a,b){b.matrixWorldInverse.getInverse(b.matrixWorld);K.multiplyMatrices(b.projectionMatrix,b.matrixWorldInverse);return a.applyProjection(K)};var P=new THREE.Matrix4;this.unprojectVector=function(a,b){P.getInverse(b.projectionMatrix);K.multiplyMatrices(b.matrixWorld,P);return a.applyProjection(K)};this.pickingRay=function(a,b){a.z=-1;var c=new THREE.Vector3(a.x,a.y,1);this.unprojectVector(a,b);this.unprojectVector(c,b);c.sub(a).normalize();return new THREE.Raycaster(a,c)};var Q=function(a){if(e===
+g){var b=new THREE.RenderableObject;f.push(b);g++;e++;d=b}else d=f[e++];d.id=a.id;d.object=a;null!==a.renderDepth?d.z=a.renderDepth:(L.setFromMatrixPosition(a.matrixWorld),L.applyProjection(K),d.z=L.z);return d},oa=function(a){var b=a.positionWorld,c=a.positionScreen;b.copy(a.position).applyMatrix4(A);c.copy(b).applyMatrix4(K);b=1/c.w;c.x*=b;c.y*=b;c.z*=b;a.visible=-1<=c.x&&1>=c.x&&-1<=c.y&&1>=c.y&&-1<=c.z&&1>=c.z},va=function(a){if(!1!==a.visible){a instanceof THREE.Light?C.lights.push(a):a instanceof
+THREE.Mesh||a instanceof THREE.Line?(!1===a.frustumCulled||!0===T.intersectsObject(a))&&C.objects.push(Q(a)):a instanceof THREE.Sprite&&C.sprites.push(Q(a));for(var b=0,c=a.children.length;b<c;b++)va(a.children[b])}};this.projectScene=function(d,f,g,l){var P=!1,L,Q,ba,W,Ka,U,ca,wa,Ua;z=q=p=0;C.elements.length=0;!0===d.autoUpdate&&d.updateMatrixWorld();void 0===f.parent&&f.updateMatrixWorld();H.copy(f.matrixWorldInverse.getInverse(f.matrixWorld));K.multiplyMatrices(f.projectionMatrix,H);ua.getNormalMatrix(H);
+T.setFromMatrix(K);e=0;C.objects.length=0;C.sprites.length=0;C.lights.length=0;va(d);!0===g&&C.objects.sort(b);for(var g=0,Oa=C.objects.length;g<Oa;g++)if(d=C.objects[g].object,A=d.matrixWorld,i=0,d instanceof THREE.Mesh){L=d.geometry;Q=L.vertices;ba=L.faces;Ka=L.faceVertexUvs;ha.getNormalMatrix(A);wa=d.material instanceof THREE.MeshFaceMaterial;Ua=!0===wa?d.material:null;for(var la=0,ia=Q.length;la<ia;la++)h=a(),h.position.copy(Q[la]),oa(h);la=0;for(ia=ba.length;la<ia;la++){Q=ba[la];var Fa=!0===
+wa?Ua.materials[Q.materialIndex]:d.material;if(void 0!==Fa){var Ga=Fa.side;W=k[Q.a];U=k[Q.b];ca=k[Q.c];if(!0===Fa.morphTargets){var P=L.morphTargets,Ba=d.morphTargetInfluences,xa=W.position,Va=U.position,La=ca.position;B.set(0,0,0);x.set(0,0,0);F.set(0,0,0);for(var Ma=0,ya=P.length;Ma<ya;Ma++){var V=Ba[Ma];if(0!==V){var pa=P[Ma].vertices;B.x+=(pa[Q.a].x-xa.x)*V;B.y+=(pa[Q.a].y-xa.y)*V;B.z+=(pa[Q.a].z-xa.z)*V;x.x+=(pa[Q.b].x-Va.x)*V;x.y+=(pa[Q.b].y-Va.y)*V;x.z+=(pa[Q.b].z-Va.z)*V;F.x+=(pa[Q.c].x-La.x)*
+V;F.y+=(pa[Q.c].y-La.y)*V;F.z+=(pa[Q.c].z-La.z)*V}}W.position.add(B);U.position.add(x);ca.position.add(F);oa(W);oa(U);oa(ca)}D[0]=W.positionScreen;D[1]=U.positionScreen;D[2]=ca.positionScreen;if(!0===W.visible||!0===U.visible||!0===ca.visible||E.isIntersectionBox(y.setFromPoints(D)))if(P=0>(ca.positionScreen.x-W.positionScreen.x)*(U.positionScreen.y-W.positionScreen.y)-(ca.positionScreen.y-W.positionScreen.y)*(U.positionScreen.x-W.positionScreen.x),Ga===THREE.DoubleSide||P===(Ga===THREE.FrontSide)){p===
+r?(Ba=new THREE.RenderableFace3,t.push(Ba),r++,p++,n=Ba):n=t[p++];n.id=d.id;n.v1.copy(W);n.v2.copy(U);n.v3.copy(ca);n.normalModel.copy(Q.normal);!1===P&&(Ga===THREE.BackSide||Ga===THREE.DoubleSide)&&n.normalModel.negate();n.normalModel.applyMatrix3(ha).normalize();n.normalModelView.copy(n.normalModel).applyMatrix3(ua);n.centroidModel.copy(Q.centroid).applyMatrix4(A);W=Q.vertexNormals;U=0;for(ca=Math.min(W.length,3);U<ca;U++)Ba=n.vertexNormalsModel[U],Ba.copy(W[U]),!1===P&&(Ga===THREE.BackSide||Ga===
+THREE.DoubleSide)&&Ba.negate(),Ba.applyMatrix3(ha).normalize(),n.vertexNormalsModelView[U].copy(Ba).applyMatrix3(ua);n.vertexNormalsLength=W.length;U=0;for(Ga=Math.min(Ka.length,3);U<Ga;U++)if(W=Ka[U][la],void 0!==W){ca=0;for(P=W.length;ca<P;ca++)n.uvs[U][ca]=W[ca]}n.color=Q.color;n.material=Fa;O.copy(n.centroidModel).applyProjection(K);n.z=O.z;C.elements.push(n)}}}}else if(d instanceof THREE.Line){da.multiplyMatrices(K,A);Q=d.geometry.vertices;W=a();W.positionScreen.copy(Q[0]).applyMatrix4(da);L=
+d.type===THREE.LinePieces?2:1;la=1;for(ia=Q.length;la<ia;la++)W=a(),W.positionScreen.copy(Q[la]).applyMatrix4(da),0<(la+1)%L||(U=k[i-2],ea.copy(W.positionScreen),I.copy(U.positionScreen),!0===c(ea,I)&&(ea.multiplyScalar(1/ea.w),I.multiplyScalar(1/I.w),q===s?(ba=new THREE.RenderableLine,u.push(ba),s++,q++,m=ba):m=u[q++],m.id=d.id,m.v1.positionScreen.copy(ea),m.v2.positionScreen.copy(I),m.z=Math.max(ea.z,I.z),m.material=d.material,d.material.vertexColors===THREE.VertexColors&&(m.vertexColors[0].copy(d.geometry.colors[la]),
+m.vertexColors[1].copy(d.geometry.colors[la-1])),C.elements.push(m)))}g=0;for(Oa=C.sprites.length;g<Oa;g++)d=C.sprites[g].object,A=d.matrixWorld,w.set(A.elements[12],A.elements[13],A.elements[14],1),w.applyMatrix4(K),L=1/w.w,w.z*=L,-1<=w.z&&1>=w.z&&(z===N?(ba=new THREE.RenderableSprite,G.push(ba),N++,z++,v=ba):v=G[z++],v.id=d.id,v.x=w.x*L,v.y=w.y*L,v.z=w.z,v.object=d,v.rotation=d.rotation,v.scale.x=d.scale.x*Math.abs(v.x-(w.x+f.projectionMatrix.elements[0])/(w.w+f.projectionMatrix.elements[12])),
+v.scale.y=d.scale.y*Math.abs(v.y-(w.y+f.projectionMatrix.elements[5])/(w.w+f.projectionMatrix.elements[13])),v.material=d.material,C.elements.push(v));!0===l&&C.elements.sort(b);return C}};THREE.Face3=function(a,b,c,d,e,f){this.a=a;this.b=b;this.c=c;this.normal=d instanceof THREE.Vector3?d:new THREE.Vector3;this.vertexNormals=d instanceof Array?d:[];this.color=e instanceof THREE.Color?e:new THREE.Color;this.vertexColors=e instanceof Array?e:[];this.vertexTangents=[];this.materialIndex=void 0!==f?f:0;this.centroid=new THREE.Vector3};
+THREE.Face3.prototype={constructor:THREE.Face3,clone:function(){var a=new THREE.Face3(this.a,this.b,this.c);a.normal.copy(this.normal);a.color.copy(this.color);a.centroid.copy(this.centroid);a.materialIndex=this.materialIndex;var b,c;b=0;for(c=this.vertexNormals.length;b<c;b++)a.vertexNormals[b]=this.vertexNormals[b].clone();b=0;for(c=this.vertexColors.length;b<c;b++)a.vertexColors[b]=this.vertexColors[b].clone();b=0;for(c=this.vertexTangents.length;b<c;b++)a.vertexTangents[b]=this.vertexTangents[b].clone();
+return a}};THREE.Face4=function(a,b,c,d,e,f,g){console.warn("THREE.Face4 has been removed. A THREE.Face3 will be created instead.");return new THREE.Face3(a,b,c,e,f,g)};THREE.Geometry=function(){this.id=THREE.GeometryIdCount++;this.uuid=THREE.Math.generateUUID();this.name="";this.vertices=[];this.colors=[];this.faces=[];this.faceVertexUvs=[[]];this.morphTargets=[];this.morphColors=[];this.morphNormals=[];this.skinWeights=[];this.skinIndices=[];this.lineDistances=[];this.boundingSphere=this.boundingBox=null;this.hasTangents=!1;this.dynamic=!0;this.buffersNeedUpdate=this.lineDistancesNeedUpdate=this.colorsNeedUpdate=this.tangentsNeedUpdate=this.normalsNeedUpdate=this.uvsNeedUpdate=
+this.elementsNeedUpdate=this.verticesNeedUpdate=!1};
+THREE.Geometry.prototype={constructor:THREE.Geometry,applyMatrix:function(a){for(var b=(new THREE.Matrix3).getNormalMatrix(a),c=0,d=this.vertices.length;c<d;c++)this.vertices[c].applyMatrix4(a);c=0;for(d=this.faces.length;c<d;c++){var e=this.faces[c];e.normal.applyMatrix3(b).normalize();for(var f=0,g=e.vertexNormals.length;f<g;f++)e.vertexNormals[f].applyMatrix3(b).normalize();e.centroid.applyMatrix4(a)}this.boundingBox instanceof THREE.Box3&&this.computeBoundingBox();this.boundingSphere instanceof
+THREE.Sphere&&this.computeBoundingSphere()},computeCentroids:function(){var a,b,c;a=0;for(b=this.faces.length;a<b;a++)c=this.faces[a],c.centroid.set(0,0,0),c.centroid.add(this.vertices[c.a]),c.centroid.add(this.vertices[c.b]),c.centroid.add(this.vertices[c.c]),c.centroid.divideScalar(3)},computeFaceNormals:function(){for(var a=new THREE.Vector3,b=new THREE.Vector3,c=0,d=this.faces.length;c<d;c++){var e=this.faces[c],f=this.vertices[e.a],g=this.vertices[e.b];a.subVectors(this.vertices[e.c],g);b.subVectors(f,
+g);a.cross(b);a.normalize();e.normal.copy(a)}},computeVertexNormals:function(a){var b,c,d;d=Array(this.vertices.length);b=0;for(c=this.vertices.length;b<c;b++)d[b]=new THREE.Vector3;if(a){var e,f,g,h=new THREE.Vector3,i=new THREE.Vector3;new THREE.Vector3;new THREE.Vector3;new THREE.Vector3;a=0;for(b=this.faces.length;a<b;a++)c=this.faces[a],e=this.vertices[c.a],f=this.vertices[c.b],g=this.vertices[c.c],h.subVectors(g,f),i.subVectors(e,f),h.cross(i),d[c.a].add(h),d[c.b].add(h),d[c.c].add(h)}else{a=
+0;for(b=this.faces.length;a<b;a++)c=this.faces[a],d[c.a].add(c.normal),d[c.b].add(c.normal),d[c.c].add(c.normal)}b=0;for(c=this.vertices.length;b<c;b++)d[b].normalize();a=0;for(b=this.faces.length;a<b;a++)c=this.faces[a],c.vertexNormals[0]=d[c.a].clone(),c.vertexNormals[1]=d[c.b].clone(),c.vertexNormals[2]=d[c.c].clone()},computeMorphNormals:function(){var a,b,c,d,e;c=0;for(d=this.faces.length;c<d;c++){e=this.faces[c];e.__originalFaceNormal?e.__originalFaceNormal.copy(e.normal):e.__originalFaceNormal=
+e.normal.clone();e.__originalVertexNormals||(e.__originalVertexNormals=[]);a=0;for(b=e.vertexNormals.length;a<b;a++)e.__originalVertexNormals[a]?e.__originalVertexNormals[a].copy(e.vertexNormals[a]):e.__originalVertexNormals[a]=e.vertexNormals[a].clone()}var f=new THREE.Geometry;f.faces=this.faces;a=0;for(b=this.morphTargets.length;a<b;a++){if(!this.morphNormals[a]){this.morphNormals[a]={};this.morphNormals[a].faceNormals=[];this.morphNormals[a].vertexNormals=[];e=this.morphNormals[a].faceNormals;
+var g=this.morphNormals[a].vertexNormals,h,i;c=0;for(d=this.faces.length;c<d;c++)h=new THREE.Vector3,i={a:new THREE.Vector3,b:new THREE.Vector3,c:new THREE.Vector3},e.push(h),g.push(i)}g=this.morphNormals[a];f.vertices=this.morphTargets[a].vertices;f.computeFaceNormals();f.computeVertexNormals();c=0;for(d=this.faces.length;c<d;c++)e=this.faces[c],h=g.faceNormals[c],i=g.vertexNormals[c],h.copy(e.normal),i.a.copy(e.vertexNormals[0]),i.b.copy(e.vertexNormals[1]),i.c.copy(e.vertexNormals[2])}c=0;for(d=
+this.faces.length;c<d;c++)e=this.faces[c],e.normal=e.__originalFaceNormal,e.vertexNormals=e.__originalVertexNormals},computeTangents:function(){var a,b,c,d,e,f,g,h,i,k,l,n,p,t,r,m,q,u=[],s=[];c=new THREE.Vector3;var v=new THREE.Vector3,z=new THREE.Vector3,G=new THREE.Vector3,N=new THREE.Vector3;a=0;for(b=this.vertices.length;a<b;a++)u[a]=new THREE.Vector3,s[a]=new THREE.Vector3;a=0;for(b=this.faces.length;a<b;a++)e=this.faces[a],f=this.faceVertexUvs[0][a],d=e.a,q=e.b,e=e.c,g=this.vertices[d],h=this.vertices[q],
+i=this.vertices[e],k=f[0],l=f[1],n=f[2],f=h.x-g.x,p=i.x-g.x,t=h.y-g.y,r=i.y-g.y,h=h.z-g.z,g=i.z-g.z,i=l.x-k.x,m=n.x-k.x,l=l.y-k.y,k=n.y-k.y,n=1/(i*k-m*l),c.set((k*f-l*p)*n,(k*t-l*r)*n,(k*h-l*g)*n),v.set((i*p-m*f)*n,(i*r-m*t)*n,(i*g-m*h)*n),u[d].add(c),u[q].add(c),u[e].add(c),s[d].add(v),s[q].add(v),s[e].add(v);v=["a","b","c","d"];a=0;for(b=this.faces.length;a<b;a++){e=this.faces[a];for(c=0;c<Math.min(e.vertexNormals.length,3);c++)N.copy(e.vertexNormals[c]),d=e[v[c]],q=u[d],z.copy(q),z.sub(N.multiplyScalar(N.dot(q))).normalize(),
+G.crossVectors(e.vertexNormals[c],q),d=G.dot(s[d]),d=0>d?-1:1,e.vertexTangents[c]=new THREE.Vector4(z.x,z.y,z.z,d)}this.hasTangents=!0},computeLineDistances:function(){for(var a=0,b=this.vertices,c=0,d=b.length;c<d;c++)0<c&&(a+=b[c].distanceTo(b[c-1])),this.lineDistances[c]=a},computeBoundingBox:function(){null===this.boundingBox&&(this.boundingBox=new THREE.Box3);this.boundingBox.setFromPoints(this.vertices)},computeBoundingSphere:function(){null===this.boundingSphere&&(this.boundingSphere=new THREE.Sphere);
+this.boundingSphere.setFromPoints(this.vertices)},mergeVertices:function(){var a={},b=[],c=[],d,e=Math.pow(10,4),f,g;f=0;for(g=this.vertices.length;f<g;f++)d=this.vertices[f],d=Math.round(d.x*e)+"_"+Math.round(d.y*e)+"_"+Math.round(d.z*e),void 0===a[d]?(a[d]=f,b.push(this.vertices[f]),c[f]=b.length-1):c[f]=c[a[d]];a=[];f=0;for(g=this.faces.length;f<g;f++){e=this.faces[f];e.a=c[e.a];e.b=c[e.b];e.c=c[e.c];e=[e.a,e.b,e.c];for(d=0;3>d;d++)if(e[d]==e[(d+1)%3]){a.push(f);break}}for(f=a.length-1;0<=f;f--){e=
+a[f];this.faces.splice(e,1);c=0;for(g=this.faceVertexUvs.length;c<g;c++)this.faceVertexUvs[c].splice(e,1)}f=this.vertices.length-b.length;this.vertices=b;return f},clone:function(){for(var a=new THREE.Geometry,b=this.vertices,c=0,d=b.length;c<d;c++)a.vertices.push(b[c].clone());b=this.faces;c=0;for(d=b.length;c<d;c++)a.faces.push(b[c].clone());b=this.faceVertexUvs[0];c=0;for(d=b.length;c<d;c++){for(var e=b[c],f=[],g=0,h=e.length;g<h;g++)f.push(new THREE.Vector2(e[g].x,e[g].y));a.faceVertexUvs[0].push(f)}return a},
+dispose:function(){this.dispatchEvent({type:"dispose"})}};THREE.EventDispatcher.prototype.apply(THREE.Geometry.prototype);THREE.GeometryIdCount=0;THREE.BufferGeometry=function(){this.id=THREE.GeometryIdCount++;this.uuid=THREE.Math.generateUUID();this.name="";this.attributes={};this.dynamic=!0;this.offsets=[];this.boundingSphere=this.boundingBox=null;this.hasTangents=!1;this.morphTargets=[]};
+THREE.BufferGeometry.prototype={constructor:THREE.BufferGeometry,addAttribute:function(a,b,c,d){this.attributes[a]={itemSize:d,array:new b(c*d)}},applyMatrix:function(a){var b,c;this.attributes.position&&(b=this.attributes.position.array);this.attributes.normal&&(c=this.attributes.normal.array);void 0!==b&&(a.multiplyVector3Array(b),this.verticesNeedUpdate=!0);void 0!==c&&((new THREE.Matrix3).getNormalMatrix(a).multiplyVector3Array(c),this.normalizeNormals(),this.normalsNeedUpdate=!0)},computeBoundingBox:function(){null===
+this.boundingBox&&(this.boundingBox=new THREE.Box3);var a=this.attributes.position.array;if(a){var b=this.boundingBox,c,d,e;3<=a.length&&(b.min.x=b.max.x=a[0],b.min.y=b.max.y=a[1],b.min.z=b.max.z=a[2]);for(var f=3,g=a.length;f<g;f+=3)c=a[f],d=a[f+1],e=a[f+2],c<b.min.x?b.min.x=c:c>b.max.x&&(b.max.x=c),d<b.min.y?b.min.y=d:d>b.max.y&&(b.max.y=d),e<b.min.z?b.min.z=e:e>b.max.z&&(b.max.z=e)}if(void 0===a||0===a.length)this.boundingBox.min.set(0,0,0),this.boundingBox.max.set(0,0,0)},computeBoundingSphere:function(){var a=
+new THREE.Box3,b=new THREE.Vector3;return function(){null===this.boundingSphere&&(this.boundingSphere=new THREE.Sphere);var c=this.attributes.position.array;if(c){a.makeEmpty();for(var d=this.boundingSphere.center,e=0,f=c.length;e<f;e+=3)b.set(c[e],c[e+1],c[e+2]),a.addPoint(b);a.center(d);for(var g=0,e=0,f=c.length;e<f;e+=3)b.set(c[e],c[e+1],c[e+2]),g=Math.max(g,d.distanceToSquared(b));this.boundingSphere.radius=Math.sqrt(g)}}}(),computeVertexNormals:function(){if(this.attributes.position){var a,
+b,c,d;a=this.attributes.position.array.length;if(void 0===this.attributes.normal)this.attributes.normal={itemSize:3,array:new Float32Array(a)};else{a=0;for(b=this.attributes.normal.array.length;a<b;a++)this.attributes.normal.array[a]=0}var e=this.attributes.position.array,f=this.attributes.normal.array,g,h,i,k,l,n,p=new THREE.Vector3,t=new THREE.Vector3,r=new THREE.Vector3,m=new THREE.Vector3,q=new THREE.Vector3;if(this.attributes.index){var u=this.attributes.index.array,s=this.offsets;c=0;for(d=
+s.length;c<d;++c){b=s[c].start;g=s[c].count;var v=s[c].index;a=b;for(b+=g;a<b;a+=3)g=v+u[a],h=v+u[a+1],i=v+u[a+2],k=e[3*g],l=e[3*g+1],n=e[3*g+2],p.set(k,l,n),k=e[3*h],l=e[3*h+1],n=e[3*h+2],t.set(k,l,n),k=e[3*i],l=e[3*i+1],n=e[3*i+2],r.set(k,l,n),m.subVectors(r,t),q.subVectors(p,t),m.cross(q),f[3*g]+=m.x,f[3*g+1]+=m.y,f[3*g+2]+=m.z,f[3*h]+=m.x,f[3*h+1]+=m.y,f[3*h+2]+=m.z,f[3*i]+=m.x,f[3*i+1]+=m.y,f[3*i+2]+=m.z}}else{a=0;for(b=e.length;a<b;a+=9)k=e[a],l=e[a+1],n=e[a+2],p.set(k,l,n),k=e[a+3],l=e[a+4],
+n=e[a+5],t.set(k,l,n),k=e[a+6],l=e[a+7],n=e[a+8],r.set(k,l,n),m.subVectors(r,t),q.subVectors(p,t),m.cross(q),f[a]=m.x,f[a+1]=m.y,f[a+2]=m.z,f[a+3]=m.x,f[a+4]=m.y,f[a+5]=m.z,f[a+6]=m.x,f[a+7]=m.y,f[a+8]=m.z}this.normalizeNormals();this.normalsNeedUpdate=!0}},normalizeNormals:function(){for(var a=this.attributes.normal.array,b,c,d,e=0,f=a.length;e<f;e+=3)b=a[e],c=a[e+1],d=a[e+2],b=1/Math.sqrt(b*b+c*c+d*d),a[e]*=b,a[e+1]*=b,a[e+2]*=b},computeTangents:function(){function a(a){ha.x=d[3*a];ha.y=d[3*a+1];
+ha.z=d[3*a+2];ua.copy(ha);T=h[a];A.copy(T);A.sub(ha.multiplyScalar(ha.dot(T))).normalize();da.crossVectors(ua,T);ea=da.dot(i[a]);O=0>ea?-1:1;g[4*a]=A.x;g[4*a+1]=A.y;g[4*a+2]=A.z;g[4*a+3]=O}if(void 0===this.attributes.index||void 0===this.attributes.position||void 0===this.attributes.normal||void 0===this.attributes.uv)console.warn("Missing required attributes (index, position, normal or uv) in BufferGeometry.computeTangents()");else{var b=this.attributes.index.array,c=this.attributes.position.array,
+d=this.attributes.normal.array,e=this.attributes.uv.array,f=c.length/3;void 0===this.attributes.tangent&&(this.attributes.tangent={itemSize:4,array:new Float32Array(4*f)});for(var g=this.attributes.tangent.array,h=[],i=[],k=0;k<f;k++)h[k]=new THREE.Vector3,i[k]=new THREE.Vector3;var l,n,p,t,r,m,q,u,s,v,z,G,N,C,B,f=new THREE.Vector3,k=new THREE.Vector3,x,F,L,w,E,y,D,H=this.offsets;L=0;for(w=H.length;L<w;++L){F=H[L].start;E=H[L].count;var K=H[L].index;x=F;for(F+=E;x<F;x+=3)E=K+b[x],y=K+b[x+1],D=K+b[x+
+2],l=c[3*E],n=c[3*E+1],p=c[3*E+2],t=c[3*y],r=c[3*y+1],m=c[3*y+2],q=c[3*D],u=c[3*D+1],s=c[3*D+2],v=e[2*E],z=e[2*E+1],G=e[2*y],N=e[2*y+1],C=e[2*D],B=e[2*D+1],t-=l,l=q-l,r-=n,n=u-n,m-=p,p=s-p,G-=v,v=C-v,N-=z,z=B-z,B=1/(G*z-v*N),f.set((z*t-N*l)*B,(z*r-N*n)*B,(z*m-N*p)*B),k.set((G*l-v*t)*B,(G*n-v*r)*B,(G*p-v*m)*B),h[E].add(f),h[y].add(f),h[D].add(f),i[E].add(k),i[y].add(k),i[D].add(k)}var A=new THREE.Vector3,da=new THREE.Vector3,ha=new THREE.Vector3,ua=new THREE.Vector3,O,T,ea;L=0;for(w=H.length;L<w;++L){F=
+H[L].start;E=H[L].count;K=H[L].index;x=F;for(F+=E;x<F;x+=3)E=K+b[x],y=K+b[x+1],D=K+b[x+2],a(E),a(y),a(D)}this.tangentsNeedUpdate=this.hasTangents=!0}},clone:function(){var a=new THREE.BufferGeometry,b=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array],c;for(c in this.attributes){for(var d=this.attributes[c],e=d.array,f={itemSize:d.itemSize,numItems:d.numItems,array:null},d=0,g=b.length;d<g;d++){var h=b[d];if(e instanceof h){f.array=new h(e);
+break}}a.attributes[c]=f}d=0;for(g=this.offsets.length;d<g;d++)b=this.offsets[d],a.offsets.push({start:b.start,index:b.index,count:b.count});return a},dispose:function(){this.dispatchEvent({type:"dispose"})}};THREE.EventDispatcher.prototype.apply(THREE.BufferGeometry.prototype);THREE.Camera=function(){THREE.Object3D.call(this);this.matrixWorldInverse=new THREE.Matrix4;this.projectionMatrix=new THREE.Matrix4};THREE.Camera.prototype=Object.create(THREE.Object3D.prototype);THREE.Camera.prototype.lookAt=function(){var a=new THREE.Matrix4;return function(b){a.lookAt(this.position,b,this.up);this.quaternion.setFromRotationMatrix(a)}}();
+THREE.Camera.prototype.clone=function(a){void 0===a&&(a=new THREE.Camera);THREE.Object3D.prototype.clone.call(this,a);a.matrixWorldInverse.copy(this.matrixWorldInverse);a.projectionMatrix.copy(this.projectionMatrix);return a};THREE.OrthographicCamera=function(a,b,c,d,e,f){THREE.Camera.call(this);this.left=a;this.right=b;this.top=c;this.bottom=d;this.near=void 0!==e?e:0.1;this.far=void 0!==f?f:2E3;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 a=new THREE.OrthographicCamera;THREE.Camera.prototype.clone.call(this,a);a.left=this.left;a.right=this.right;a.top=this.top;a.bottom=this.bottom;a.near=this.near;a.far=this.far;return a};THREE.PerspectiveCamera=function(a,b,c,d){THREE.Camera.call(this);this.fov=void 0!==a?a:50;this.aspect=void 0!==b?b:1;this.near=void 0!==c?c:0.1;this.far=void 0!==d?d:2E3;this.updateProjectionMatrix()};THREE.PerspectiveCamera.prototype=Object.create(THREE.Camera.prototype);THREE.PerspectiveCamera.prototype.setLens=function(a,b){void 0===b&&(b=24);this.fov=2*THREE.Math.radToDeg(Math.atan(b/(2*a)));this.updateProjectionMatrix()};
+THREE.PerspectiveCamera.prototype.setViewOffset=function(a,b,c,d,e,f){this.fullWidth=a;this.fullHeight=b;this.x=c;this.y=d;this.width=e;this.height=f;this.updateProjectionMatrix()};
+THREE.PerspectiveCamera.prototype.updateProjectionMatrix=function(){if(this.fullWidth){var a=this.fullWidth/this.fullHeight,b=Math.tan(THREE.Math.degToRad(0.5*this.fov))*this.near,c=-b,d=a*c,a=Math.abs(a*b-d),c=Math.abs(b-c);this.projectionMatrix.makeFrustum(d+this.x*a/this.fullWidth,d+(this.x+this.width)*a/this.fullWidth,b-(this.y+this.height)*c/this.fullHeight,b-this.y*c/this.fullHeight,this.near,this.far)}else this.projectionMatrix.makePerspective(this.fov,this.aspect,this.near,this.far)};
+THREE.PerspectiveCamera.prototype.clone=function(){var a=new THREE.PerspectiveCamera;THREE.Camera.prototype.clone.call(this,a);a.fov=this.fov;a.aspect=this.aspect;a.near=this.near;a.far=this.far;return a};THREE.Light=function(a){THREE.Object3D.call(this);this.color=new THREE.Color(a)};THREE.Light.prototype=Object.create(THREE.Object3D.prototype);THREE.Light.prototype.clone=function(a){void 0===a&&(a=new THREE.Light);THREE.Object3D.prototype.clone.call(this,a);a.color.copy(this.color);return a};THREE.AmbientLight=function(a){THREE.Light.call(this,a)};THREE.AmbientLight.prototype=Object.create(THREE.Light.prototype);THREE.AmbientLight.prototype.clone=function(){var a=new THREE.AmbientLight;THREE.Light.prototype.clone.call(this,a);return a};THREE.AreaLight=function(a,b){THREE.Light.call(this,a);this.normal=new THREE.Vector3(0,-1,0);this.right=new THREE.Vector3(1,0,0);this.intensity=void 0!==b?b:1;this.height=this.width=1;this.constantAttenuation=1.5;this.linearAttenuation=0.5;this.quadraticAttenuation=0.1};THREE.AreaLight.prototype=Object.create(THREE.Light.prototype);THREE.DirectionalLight=function(a,b){THREE.Light.call(this,a);this.position.set(0,1,0);this.target=new THREE.Object3D;this.intensity=void 0!==b?b:1;this.onlyShadow=this.castShadow=!1;this.shadowCameraNear=50;this.shadowCameraFar=5E3;this.shadowCameraLeft=-500;this.shadowCameraTop=this.shadowCameraRight=500;this.shadowCameraBottom=-500;this.shadowCameraVisible=!1;this.shadowBias=0;this.shadowDarkness=0.5;this.shadowMapHeight=this.shadowMapWidth=512;this.shadowCascade=!1;this.shadowCascadeOffset=new THREE.Vector3(0,
+0,-1E3);this.shadowCascadeCount=2;this.shadowCascadeBias=[0,0,0];this.shadowCascadeWidth=[512,512,512];this.shadowCascadeHeight=[512,512,512];this.shadowCascadeNearZ=[-1,0.99,0.998];this.shadowCascadeFarZ=[0.99,0.998,1];this.shadowCascadeArray=[];this.shadowMatrix=this.shadowCamera=this.shadowMapSize=this.shadowMap=null};THREE.DirectionalLight.prototype=Object.create(THREE.Light.prototype);
+THREE.DirectionalLight.prototype.clone=function(){var a=new THREE.DirectionalLight;THREE.Light.prototype.clone.call(this,a);a.target=this.target.clone();a.intensity=this.intensity;a.castShadow=this.castShadow;a.onlyShadow=this.onlyShadow;return a};THREE.HemisphereLight=function(a,b,c){THREE.Light.call(this,a);this.position.set(0,100,0);this.groundColor=new THREE.Color(b);this.intensity=void 0!==c?c:1};THREE.HemisphereLight.prototype=Object.create(THREE.Light.prototype);THREE.HemisphereLight.prototype.clone=function(){var a=new THREE.HemisphereLight;THREE.Light.prototype.clone.call(this,a);a.groundColor.copy(this.groundColor);a.intensity=this.intensity;return a};THREE.PointLight=function(a,b,c){THREE.Light.call(this,a);this.intensity=void 0!==b?b:1;this.distance=void 0!==c?c:0};THREE.PointLight.prototype=Object.create(THREE.Light.prototype);THREE.PointLight.prototype.clone=function(){var a=new THREE.PointLight;THREE.Light.prototype.clone.call(this,a);a.intensity=this.intensity;a.distance=this.distance;return a};THREE.SpotLight=function(a,b,c,d,e){THREE.Light.call(this,a);this.position.set(0,1,0);this.target=new THREE.Object3D;this.intensity=void 0!==b?b:1;this.distance=void 0!==c?c:0;this.angle=void 0!==d?d:Math.PI/3;this.exponent=void 0!==e?e:10;this.onlyShadow=this.castShadow=!1;this.shadowCameraNear=50;this.shadowCameraFar=5E3;this.shadowCameraFov=50;this.shadowCameraVisible=!1;this.shadowBias=0;this.shadowDarkness=0.5;this.shadowMapHeight=this.shadowMapWidth=512;this.shadowMatrix=this.shadowCamera=this.shadowMapSize=
+this.shadowMap=null};THREE.SpotLight.prototype=Object.create(THREE.Light.prototype);THREE.SpotLight.prototype.clone=function(){var a=new THREE.SpotLight;THREE.Light.prototype.clone.call(this,a);a.target=this.target.clone();a.intensity=this.intensity;a.distance=this.distance;a.angle=this.angle;a.exponent=this.exponent;a.castShadow=this.castShadow;a.onlyShadow=this.onlyShadow;return a};THREE.Loader=function(a){this.statusDomElement=(this.showStatus=a)?THREE.Loader.prototype.addStatusElement():null;this.onLoadStart=function(){};this.onLoadProgress=function(){};this.onLoadComplete=function(){}};
+THREE.Loader.prototype={constructor:THREE.Loader,crossOrigin:void 0,addStatusElement:function(){var a=document.createElement("div");a.style.position="absolute";a.style.right="0px";a.style.top="0px";a.style.fontSize="0.8em";a.style.textAlign="left";a.style.background="rgba(0,0,0,0.25)";a.style.color="#fff";a.style.width="120px";a.style.padding="0.5em 0.5em 0.5em 0.5em";a.style.zIndex=1E3;a.innerHTML="Loading ...";return a},updateProgress:function(a){var b="Loaded ",b=a.total?b+((100*a.loaded/a.total).toFixed(0)+
+"%"):b+((a.loaded/1E3).toFixed(2)+" KB");this.statusDomElement.innerHTML=b},extractUrlBase:function(a){a=a.split("/");a.pop();return(1>a.length?".":a.join("/"))+"/"},initMaterials:function(a,b){for(var c=[],d=0;d<a.length;++d)c[d]=THREE.Loader.prototype.createMaterial(a[d],b);return c},needsTangents:function(a){for(var b=0,c=a.length;b<c;b++)if(a[b]instanceof THREE.ShaderMaterial)return!0;return!1},createMaterial:function(a,b){function c(a){a=Math.log(a)/Math.LN2;return Math.floor(a)==a}function d(a){a=
+Math.log(a)/Math.LN2;return Math.pow(2,Math.round(a))}function e(a,e,f,h,i,k,q){var u=/\.dds$/i.test(f),s=b+"/"+f;if(u){var v=THREE.ImageUtils.loadCompressedTexture(s);a[e]=v}else v=document.createElement("canvas"),a[e]=new THREE.Texture(v);a[e].sourceFile=f;h&&(a[e].repeat.set(h[0],h[1]),1!==h[0]&&(a[e].wrapS=THREE.RepeatWrapping),1!==h[1]&&(a[e].wrapT=THREE.RepeatWrapping));i&&a[e].offset.set(i[0],i[1]);k&&(f={repeat:THREE.RepeatWrapping,mirror:THREE.MirroredRepeatWrapping},void 0!==f[k[0]]&&(a[e].wrapS=
+f[k[0]]),void 0!==f[k[1]]&&(a[e].wrapT=f[k[1]]));q&&(a[e].anisotropy=q);if(!u){var z=a[e],a=new Image;a.onload=function(){if(!c(this.width)||!c(this.height)){var a=d(this.width),b=d(this.height);z.image.width=a;z.image.height=b;z.image.getContext("2d").drawImage(this,0,0,a,b)}else z.image=this;z.needsUpdate=!0};void 0!==g.crossOrigin&&(a.crossOrigin=g.crossOrigin);a.src=s}}function f(a){return(255*a[0]<<16)+(255*a[1]<<8)+255*a[2]}var g=this,h="MeshLambertMaterial",i={color:15658734,opacity:1,map:null,
+lightMap:null,normalMap:null,bumpMap:null,wireframe:!1};if(a.shading){var k=a.shading.toLowerCase();"phong"===k?h="MeshPhongMaterial":"basic"===k&&(h="MeshBasicMaterial")}void 0!==a.blending&&void 0!==THREE[a.blending]&&(i.blending=THREE[a.blending]);if(void 0!==a.transparent||1>a.opacity)i.transparent=a.transparent;void 0!==a.depthTest&&(i.depthTest=a.depthTest);void 0!==a.depthWrite&&(i.depthWrite=a.depthWrite);void 0!==a.visible&&(i.visible=a.visible);void 0!==a.flipSided&&(i.side=THREE.BackSide);
+void 0!==a.doubleSided&&(i.side=THREE.DoubleSide);void 0!==a.wireframe&&(i.wireframe=a.wireframe);void 0!==a.vertexColors&&("face"===a.vertexColors?i.vertexColors=THREE.FaceColors:a.vertexColors&&(i.vertexColors=THREE.VertexColors));a.colorDiffuse?i.color=f(a.colorDiffuse):a.DbgColor&&(i.color=a.DbgColor);a.colorSpecular&&(i.specular=f(a.colorSpecular));a.colorAmbient&&(i.ambient=f(a.colorAmbient));a.transparency&&(i.opacity=a.transparency);a.specularCoef&&(i.shininess=a.specularCoef);a.mapDiffuse&&
+b&&e(i,"map",a.mapDiffuse,a.mapDiffuseRepeat,a.mapDiffuseOffset,a.mapDiffuseWrap,a.mapDiffuseAnisotropy);a.mapLight&&b&&e(i,"lightMap",a.mapLight,a.mapLightRepeat,a.mapLightOffset,a.mapLightWrap,a.mapLightAnisotropy);a.mapBump&&b&&e(i,"bumpMap",a.mapBump,a.mapBumpRepeat,a.mapBumpOffset,a.mapBumpWrap,a.mapBumpAnisotropy);a.mapNormal&&b&&e(i,"normalMap",a.mapNormal,a.mapNormalRepeat,a.mapNormalOffset,a.mapNormalWrap,a.mapNormalAnisotropy);a.mapSpecular&&b&&e(i,"specularMap",a.mapSpecular,a.mapSpecularRepeat,
+a.mapSpecularOffset,a.mapSpecularWrap,a.mapSpecularAnisotropy);a.mapBumpScale&&(i.bumpScale=a.mapBumpScale);a.mapNormal?(h=THREE.ShaderLib.normalmap,k=THREE.UniformsUtils.clone(h.uniforms),k.tNormal.value=i.normalMap,a.mapNormalFactor&&k.uNormalScale.value.set(a.mapNormalFactor,a.mapNormalFactor),i.map&&(k.tDiffuse.value=i.map,k.enableDiffuse.value=!0),i.specularMap&&(k.tSpecular.value=i.specularMap,k.enableSpecular.value=!0),i.lightMap&&(k.tAO.value=i.lightMap,k.enableAO.value=!0),k.diffuse.value.setHex(i.color),
+k.specular.value.setHex(i.specular),k.ambient.value.setHex(i.ambient),k.shininess.value=i.shininess,void 0!==i.opacity&&(k.opacity.value=i.opacity),h=new THREE.ShaderMaterial({fragmentShader:h.fragmentShader,vertexShader:h.vertexShader,uniforms:k,lights:!0,fog:!0}),i.transparent&&(h.transparent=!0)):h=new THREE[h](i);void 0!==a.DbgName&&(h.name=a.DbgName);return h}};THREE.XHRLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
+THREE.XHRLoader.prototype={constructor:THREE.XHRLoader,load:function(a,b,c,d){var e=this,f=new XMLHttpRequest;void 0!==b&&f.addEventListener("load",function(c){b(c.target.responseText);e.manager.itemEnd(a)},!1);void 0!==c&&f.addEventListener("progress",function(a){c(a)},!1);void 0!==d&&f.addEventListener("error",function(a){d(a)},!1);void 0!==this.crossOrigin&&(f.crossOrigin=this.crossOrigin);f.open("GET",a,!0);f.send(null);e.manager.itemStart(a)},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.ImageLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
+THREE.ImageLoader.prototype={constructor:THREE.ImageLoader,load:function(a,b,c,d){var e=this,f=document.createElement("img");void 0!==b&&f.addEventListener("load",function(){e.manager.itemEnd(a);b(this)},!1);void 0!==c&&f.addEventListener("progress",function(a){c(a)},!1);void 0!==d&&f.addEventListener("error",function(a){d(a)},!1);void 0!==this.crossOrigin&&(f.crossOrigin=this.crossOrigin);f.src=a;e.manager.itemStart(a);return f},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.JSONLoader=function(a){THREE.Loader.call(this,a);this.withCredentials=!1};THREE.JSONLoader.prototype=Object.create(THREE.Loader.prototype);THREE.JSONLoader.prototype.load=function(a,b,c){c=c&&"string"===typeof c?c:this.extractUrlBase(a);this.onLoadStart();this.loadAjaxJSON(this,a,b,c)};
+THREE.JSONLoader.prototype.loadAjaxJSON=function(a,b,c,d,e){var f=new XMLHttpRequest,g=0;f.onreadystatechange=function(){if(f.readyState===f.DONE)if(200===f.status||0===f.status){if(f.responseText){var h=JSON.parse(f.responseText),h=a.parse(h,d);c(h.geometry,h.materials)}else console.warn("THREE.JSONLoader: ["+b+"] seems to be unreachable or file there is empty");a.onLoadComplete()}else console.error("THREE.JSONLoader: Couldn't load ["+b+"] ["+f.status+"]");else f.readyState===f.LOADING?e&&(0===g&&
+(g=f.getResponseHeader("Content-Length")),e({total:g,loaded:f.responseText.length})):f.readyState===f.HEADERS_RECEIVED&&void 0!==e&&(g=f.getResponseHeader("Content-Length"))};f.open("GET",b,!0);f.withCredentials=this.withCredentials;f.send(null)};
+THREE.JSONLoader.prototype.parse=function(a,b){var c=new THREE.Geometry,d=void 0!==a.scale?1/a.scale:1,e,f,g,h,i,k,l,n,p,t,r,m,q,u,s=a.faces;p=a.vertices;var v=a.normals,z=a.colors,G=0;if(void 0!==a.uvs){for(e=0;e<a.uvs.length;e++)a.uvs[e].length&&G++;for(e=0;e<G;e++)c.faceVertexUvs[e]=[]}h=0;for(i=p.length;h<i;)k=new THREE.Vector3,k.x=p[h++]*d,k.y=p[h++]*d,k.z=p[h++]*d,c.vertices.push(k);h=0;for(i=s.length;h<i;)if(p=s[h++],t=p&1,g=p&2,e=p&8,l=p&16,r=p&32,k=p&64,p&=128,t){t=new THREE.Face3;t.a=s[h];
+t.b=s[h+1];t.c=s[h+3];m=new THREE.Face3;m.a=s[h+1];m.b=s[h+2];m.c=s[h+3];h+=4;g&&(g=s[h++],t.materialIndex=g,m.materialIndex=g);g=c.faces.length;if(e)for(e=0;e<G;e++){q=a.uvs[e];c.faceVertexUvs[e][g]=[];c.faceVertexUvs[e][g+1]=[];for(f=0;4>f;f++)n=s[h++],u=q[2*n],n=q[2*n+1],u=new THREE.Vector2(u,n),2!==f&&c.faceVertexUvs[e][g].push(u),0!==f&&c.faceVertexUvs[e][g+1].push(u)}l&&(l=3*s[h++],t.normal.set(v[l++],v[l++],v[l]),m.normal.copy(t.normal));if(r)for(e=0;4>e;e++)l=3*s[h++],r=new THREE.Vector3(v[l++],
+v[l++],v[l]),2!==e&&t.vertexNormals.push(r),0!==e&&m.vertexNormals.push(r);k&&(k=s[h++],k=z[k],t.color.setHex(k),m.color.setHex(k));if(p)for(e=0;4>e;e++)k=s[h++],k=z[k],2!==e&&t.vertexColors.push(new THREE.Color(k)),0!==e&&m.vertexColors.push(new THREE.Color(k));c.faces.push(t);c.faces.push(m)}else{t=new THREE.Face3;t.a=s[h++];t.b=s[h++];t.c=s[h++];g&&(g=s[h++],t.materialIndex=g);g=c.faces.length;if(e)for(e=0;e<G;e++){q=a.uvs[e];c.faceVertexUvs[e][g]=[];for(f=0;3>f;f++)n=s[h++],u=q[2*n],n=q[2*n+1],
+u=new THREE.Vector2(u,n),c.faceVertexUvs[e][g].push(u)}l&&(l=3*s[h++],t.normal.set(v[l++],v[l++],v[l]));if(r)for(e=0;3>e;e++)l=3*s[h++],r=new THREE.Vector3(v[l++],v[l++],v[l]),t.vertexNormals.push(r);k&&(k=s[h++],t.color.setHex(z[k]));if(p)for(e=0;3>e;e++)k=s[h++],t.vertexColors.push(new THREE.Color(z[k]));c.faces.push(t)}if(a.skinWeights){h=0;for(i=a.skinWeights.length;h<i;h+=2)s=a.skinWeights[h],v=a.skinWeights[h+1],c.skinWeights.push(new THREE.Vector4(s,v,0,0))}if(a.skinIndices){h=0;for(i=a.skinIndices.length;h<
+i;h+=2)s=a.skinIndices[h],v=a.skinIndices[h+1],c.skinIndices.push(new THREE.Vector4(s,v,0,0))}c.bones=a.bones;c.animation=a.animation;c.animations=a.animations;if(void 0!==a.morphTargets){h=0;for(i=a.morphTargets.length;h<i;h++){c.morphTargets[h]={};c.morphTargets[h].name=a.morphTargets[h].name;c.morphTargets[h].vertices=[];z=c.morphTargets[h].vertices;G=a.morphTargets[h].vertices;s=0;for(v=G.length;s<v;s+=3)p=new THREE.Vector3,p.x=G[s]*d,p.y=G[s+1]*d,p.z=G[s+2]*d,z.push(p)}}if(void 0!==a.morphColors){h=
+0;for(i=a.morphColors.length;h<i;h++){c.morphColors[h]={};c.morphColors[h].name=a.morphColors[h].name;c.morphColors[h].colors=[];v=c.morphColors[h].colors;z=a.morphColors[h].colors;d=0;for(s=z.length;d<s;d+=3)G=new THREE.Color(16755200),G.setRGB(z[d],z[d+1],z[d+2]),v.push(G)}}c.computeCentroids();c.computeFaceNormals();c.computeBoundingSphere();if(void 0===a.materials)return{geometry:c};d=this.initMaterials(a.materials,b);this.needsTangents(d)&&c.computeTangents();return{geometry:c,materials:d}};THREE.LoadingManager=function(a,b,c){var d=this,e=0,f=0;this.onLoad=a;this.onProgress=b;this.onError=c;this.itemStart=function(){f++};this.itemEnd=function(a){e++;if(void 0!==d.onProgress)d.onProgress(a,e,f);if(e===f&&void 0!==d.onLoad)d.onLoad()}};THREE.DefaultLoadingManager=new THREE.LoadingManager;THREE.BufferGeometryLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
+THREE.BufferGeometryLoader.prototype={constructor:THREE.BufferGeometryLoader,load:function(a,b){var c=this,d=new THREE.XHRLoader;d.setCrossOrigin(this.crossOrigin);d.load(a,function(a){b(c.parse(JSON.parse(a)))})},setCrossOrigin:function(a){this.crossOrigin=a},parse:function(a){var b=new THREE.BufferGeometry,c=a.attributes,d=a.offsets,a=a.boundingSphere,e;for(e in c){var f=c[e];b.attributes[e]={itemSize:f.itemSize,array:new self[f.type](f.array)}}void 0!==d&&(b.offsets=JSON.parse(JSON.stringify(d)));
+void 0!==a&&(b.boundingSphere=new THREE.Sphere((new THREE.Vector3).fromArray(void 0!==a.center?a.center:[0,0,0]),a.radius));return b}};THREE.GeometryLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};THREE.GeometryLoader.prototype={constructor:THREE.GeometryLoader,load:function(a,b){var c=this,d=new THREE.XHRLoader;d.setCrossOrigin(this.crossOrigin);d.load(a,function(a){b(c.parse(JSON.parse(a)))})},setCrossOrigin:function(a){this.crossOrigin=a},parse:function(){}};THREE.MaterialLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
+THREE.MaterialLoader.prototype={constructor:THREE.MaterialLoader,load:function(a,b){var c=this,d=new THREE.XHRLoader;d.setCrossOrigin(this.crossOrigin);d.load(a,function(a){b(c.parse(JSON.parse(a)))})},setCrossOrigin:function(a){this.crossOrigin=a},parse:function(a){var b=new THREE[a.type];void 0!==a.color&&b.color.setHex(a.color);void 0!==a.ambient&&b.ambient.setHex(a.ambient);void 0!==a.emissive&&b.emissive.setHex(a.emissive);void 0!==a.specular&&b.specular.setHex(a.specular);void 0!==a.shininess&&
+(b.shininess=a.shininess);void 0!==a.vertexColors&&(b.vertexColors=a.vertexColors);void 0!==a.blending&&(b.blending=a.blending);void 0!==a.side&&(b.side=a.side);void 0!==a.opacity&&(b.opacity=a.opacity);void 0!==a.transparent&&(b.transparent=a.transparent);void 0!==a.wireframe&&(b.wireframe=a.wireframe);if(void 0!==a.materials)for(var c=0,d=a.materials.length;c<d;c++)b.materials.push(this.parse(a.materials[c]));return b}};THREE.ObjectLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};
+THREE.ObjectLoader.prototype={constructor:THREE.ObjectLoader,load:function(a,b){var c=this,d=new THREE.XHRLoader(c.manager);d.setCrossOrigin(this.crossOrigin);d.load(a,function(a){b(c.parse(JSON.parse(a)))})},setCrossOrigin:function(a){this.crossOrigin=a},parse:function(a){var b=this.parseGeometries(a.geometries),c=this.parseMaterials(a.materials);return this.parseObject(a.object,b,c)},parseGeometries:function(a){var b={};if(void 0!==a)for(var c=new THREE.JSONLoader,d=new THREE.BufferGeometryLoader,
+e=0,f=a.length;e<f;e++){var g,h=a[e];switch(h.type){case "PlaneGeometry":g=new THREE.PlaneGeometry(h.width,h.height,h.widthSegments,h.heightSegments);break;case "CircleGeometry":g=new THREE.CircleGeometry(h.radius,h.segments);break;case "CubeGeometry":g=new THREE.CubeGeometry(h.width,h.height,h.depth,h.widthSegments,h.heightSegments,h.depthSegments);break;case "CylinderGeometry":g=new THREE.CylinderGeometry(h.radiusTop,h.radiusBottom,h.height,h.radialSegments,h.heightSegments,h.openEnded);break;case "SphereGeometry":g=
+new THREE.SphereGeometry(h.radius,h.widthSegments,h.heightSegments,h.phiStart,h.phiLength,h.thetaStart,h.thetaLength);break;case "IcosahedronGeometry":g=new THREE.IcosahedronGeometry(h.radius,h.detail);break;case "TorusGeometry":g=new THREE.TorusGeometry(h.radius,h.tube,h.radialSegments,h.tubularSegments,h.arc);break;case "TorusKnotGeometry":g=new THREE.TorusKnotGeometry(h.radius,h.tube,h.radialSegments,h.tubularSegments,h.p,h.q,h.heightScale);break;case "BufferGeometry":g=d.parse(h.data);break;case "Geometry":g=
+c.parse(h.data).geometry}g.uuid=h.uuid;void 0!==h.name&&(g.name=h.name);b[h.uuid]=g}return b},parseMaterials:function(a){var b={};if(void 0!==a)for(var c=new THREE.MaterialLoader,d=0,e=a.length;d<e;d++){var f=a[d],g=c.parse(f);g.uuid=f.uuid;void 0!==f.name&&(g.name=f.name);b[f.uuid]=g}return b},parseObject:function(){var a=new THREE.Matrix4;return function(b,c,d){var e;switch(b.type){case "Scene":e=new THREE.Scene;break;case "PerspectiveCamera":e=new THREE.PerspectiveCamera(b.fov,b.aspect,b.near,
+b.far);break;case "OrthographicCamera":e=new THREE.OrthographicCamera(b.left,b.right,b.top,b.bottom,b.near,b.far);break;case "AmbientLight":e=new THREE.AmbientLight(b.color);break;case "DirectionalLight":e=new THREE.DirectionalLight(b.color,b.intensity);break;case "PointLight":e=new THREE.PointLight(b.color,b.intensity,b.distance);break;case "SpotLight":e=new THREE.SpotLight(b.color,b.intensity,b.distance,b.angle,b.exponent);break;case "HemisphereLight":e=new THREE.HemisphereLight(b.color,b.groundColor,
+b.intensity);break;case "Mesh":e=c[b.geometry];var f=d[b.material];void 0===e&&console.error("THREE.ObjectLoader: Undefined geometry "+b.geometry);void 0===f&&console.error("THREE.ObjectLoader: Undefined material "+b.material);e=new THREE.Mesh(e,f);break;case "Sprite":f=d[b.material];void 0===f&&console.error("THREE.ObjectLoader: Undefined material "+b.material);e=new THREE.Sprite(f);break;default:e=new THREE.Object3D}e.uuid=b.uuid;void 0!==b.name&&(e.name=b.name);void 0!==b.matrix?(a.fromArray(b.matrix),
+a.decompose(e.position,e.quaternion,e.scale)):(void 0!==b.position&&e.position.fromArray(b.position),void 0!==b.rotation&&e.rotation.fromArray(b.rotation),void 0!==b.scale&&e.scale.fromArray(b.scale));void 0!==b.visible&&(e.visible=b.visible);void 0!==b.userData&&(e.userData=b.userData);if(void 0!==b.children)for(var g in b.children)e.add(this.parseObject(b.children[g],c,d));return e}}()};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(a,b){var c=this,d=new THREE.XHRLoader(c.manager);d.setCrossOrigin(this.crossOrigin);d.load(a,function(d){c.parse(JSON.parse(d),b,a)})},setCrossOrigin:function(a){this.crossOrigin=a},addGeometryHandler:function(a,b){this.geometryHandlers[a]={loaderClass:b}},addHierarchyHandler:function(a,b){this.hierarchyHandlers[a]={loaderClass:b}},parse:function(a,b,c){function d(a,b){return"relativeToHTML"==b?a:p+"/"+a}function e(){f(C.scene,
+x.objects)}function f(a,b){var c,e,g,i,k,l;for(l in b){var p=C.objects[l],q=b[l];if(void 0===p){if(q.type&&q.type in n.hierarchyHandlers){if(void 0===q.loading){c={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 u={},v;for(v in q)v in c||(u[v]=q[v]);r=C.materials[q.material];q.loading=!0;c=n.hierarchyHandlers[q.type].loaderObject;c.options?c.load(d(q.url,x.urlBaseType),h(l,a,r,q)):c.load(d(q.url,x.urlBaseType),h(l,
+a,r,q),u)}}else if(void 0!==q.geometry){if(t=C.geometries[q.geometry]){p=!1;r=C.materials[q.material];p=r instanceof THREE.ShaderMaterial;e=q.position;g=q.rotation;i=q.scale;c=q.matrix;k=q.quaternion;q.material||(r=new THREE.MeshFaceMaterial(C.face_materials[q.geometry]));r instanceof THREE.MeshFaceMaterial&&0===r.materials.length&&(r=new THREE.MeshFaceMaterial(C.face_materials[q.geometry]));if(r instanceof THREE.MeshFaceMaterial)for(u=0;u<r.materials.length;u++)p=p||r.materials[u]instanceof THREE.ShaderMaterial;
+p&&t.computeTangents();q.skin?p=new THREE.SkinnedMesh(t,r):q.morph?(p=new THREE.MorphAnimMesh(t,r),void 0!==q.duration&&(p.duration=q.duration),void 0!==q.time&&(p.time=q.time),void 0!==q.mirroredLoop&&(p.mirroredLoop=q.mirroredLoop),r.morphNormals&&t.computeMorphNormals()):p=new THREE.Mesh(t,r);p.name=l;c?(p.matrixAutoUpdate=!1,p.matrix.set(c[0],c[1],c[2],c[3],c[4],c[5],c[6],c[7],c[8],c[9],c[10],c[11],c[12],c[13],c[14],c[15])):(p.position.fromArray(e),k?p.quaternion.fromArray(k):p.rotation.fromArray(g),
+p.scale.fromArray(i));p.visible=q.visible;p.castShadow=q.castShadow;p.receiveShadow=q.receiveShadow;a.add(p);C.objects[l]=p}}else if("AmbientLight"===q.type||"PointLight"===q.type||"DirectionalLight"===q.type||"SpotLight"===q.type||"HemisphereLight"===q.type||"AreaLight"===q.type){u=q.color;c=q.intensity;e=q.distance;g=q.position;i=q.rotation;switch(q.type){case "AmbientLight":s=new THREE.AmbientLight(u);break;case "PointLight":s=new THREE.PointLight(u,c,e);s.position.fromArray(g);break;case "DirectionalLight":s=
+new THREE.DirectionalLight(u,c);s.position.fromArray(q.direction);break;case "SpotLight":s=new THREE.SpotLight(u,c,e,1);s.angle=q.angle;s.position.fromArray(g);s.target.set(g[0],g[1]-e,g[2]);s.target.applyEuler(new THREE.Euler(i[0],i[1],i[2],"XYZ"));break;case "HemisphereLight":s=new THREE.DirectionalLight(u,c,e);s.target.set(g[0],g[1]-e,g[2]);s.target.applyEuler(new THREE.Euler(i[0],i[1],i[2],"XYZ"));break;case "AreaLight":s=new THREE.AreaLight(u,c),s.position.fromArray(g),s.width=q.size,s.height=
+q.size_y}a.add(s);s.name=l;C.lights[l]=s;C.objects[l]=s}else"PerspectiveCamera"===q.type||"OrthographicCamera"===q.type?(e=q.position,g=q.rotation,k=q.quaternion,"PerspectiveCamera"===q.type?m=new THREE.PerspectiveCamera(q.fov,q.aspect,q.near,q.far):"OrthographicCamera"===q.type&&(m=new THREE.OrthographicCamera(q.left,q.right,q.top,q.bottom,q.near,q.far)),m.name=l,m.position.fromArray(e),void 0!==k?m.quaternion.fromArray(k):void 0!==g&&m.rotation.fromArray(g),a.add(m),C.cameras[l]=m,C.objects[l]=
+m):(e=q.position,g=q.rotation,i=q.scale,k=q.quaternion,p=new THREE.Object3D,p.name=l,p.position.fromArray(e),k?p.quaternion.fromArray(k):p.rotation.fromArray(g),p.scale.fromArray(i),p.visible=void 0!==q.visible?q.visible:!1,a.add(p),C.objects[l]=p,C.empties[l]=p);if(p){if(void 0!==q.userData)for(var z in q.userData)p.userData[z]=q.userData[z];if(void 0!==q.groups)for(u=0;u<q.groups.length;u++)c=q.groups[u],void 0===C.groups[c]&&(C.groups[c]=[]),C.groups[c].push(l)}}void 0!==p&&void 0!==q.children&&
+f(p,q.children)}}function g(a){return function(b,c){b.name=a;C.geometries[a]=b;C.face_materials[a]=c;e();v-=1;n.onLoadComplete();k()}}function h(a,b,c,d){return function(f){var f=f.content?f.content:f.dae?f.scene:f,g=d.rotation,h=d.quaternion,i=d.scale;f.position.fromArray(d.position);h?f.quaternion.fromArray(h):f.rotation.fromArray(g);f.scale.fromArray(i);c&&f.traverse(function(a){a.material=c});var l=void 0!==d.visible?d.visible:!0;f.traverse(function(a){a.visible=l});b.add(f);f.name=a;C.objects[a]=
+f;e();v-=1;n.onLoadComplete();k()}}function i(a){return function(b,c){b.name=a;C.geometries[a]=b;C.face_materials[a]=c}}function k(){n.callbackProgress({totalModels:G,totalTextures:N,loadedModels:G-v,loadedTextures:N-z},C);n.onLoadProgress();if(0===v&&0===z){for(var a=0;a<B.length;a++){var c=B[a],d=C.objects[c.targetName];d?c.object.target=d:(c.object.target=new THREE.Object3D,C.scene.add(c.object.target));c.object.target.userData.targetInverse=c.object}b(C)}}function l(a,b){b(a);if(void 0!==a.children)for(var c in a.children)l(a.children[c],
+b)}var n=this,p=THREE.Loader.prototype.extractUrlBase(c),t,r,m,q,u,s,v,z,G,N,C,B=[],x=a,F;for(F in this.geometryHandlers)a=this.geometryHandlers[F].loaderClass,this.geometryHandlers[F].loaderObject=new a;for(F in this.hierarchyHandlers)a=this.hierarchyHandlers[F].loaderClass,this.hierarchyHandlers[F].loaderObject=new a;z=v=0;C={scene:new THREE.Scene,geometries:{},face_materials:{},materials:{},textures:{},objects:{},cameras:{},lights:{},fogs:{},empties:{},groups:{}};if(x.transform&&(F=x.transform.position,
+a=x.transform.rotation,c=x.transform.scale,F&&C.scene.position.fromArray(F),a&&C.scene.rotation.fromArray(a),c&&C.scene.scale.fromArray(c),F||a||c))C.scene.updateMatrix(),C.scene.updateMatrixWorld();F=function(a){return function(){z-=a;k();n.onLoadComplete()}};for(var L in x.fogs)a=x.fogs[L],"linear"===a.type?q=new THREE.Fog(0,a.near,a.far):"exp2"===a.type&&(q=new THREE.FogExp2(0,a.density)),a=a.color,q.color.setRGB(a[0],a[1],a[2]),C.fogs[L]=q;for(var w in x.geometries)q=x.geometries[w],q.type in
+this.geometryHandlers&&(v+=1,n.onLoadStart());for(var E in x.objects)l(x.objects[E],function(a){a.type&&a.type in n.hierarchyHandlers&&(v+=1,n.onLoadStart())});G=v;for(w in x.geometries)if(q=x.geometries[w],"cube"===q.type)t=new THREE.CubeGeometry(q.width,q.height,q.depth,q.widthSegments,q.heightSegments,q.depthSegments),t.name=w,C.geometries[w]=t;else if("plane"===q.type)t=new THREE.PlaneGeometry(q.width,q.height,q.widthSegments,q.heightSegments),t.name=w,C.geometries[w]=t;else if("sphere"===q.type)t=
+new THREE.SphereGeometry(q.radius,q.widthSegments,q.heightSegments),t.name=w,C.geometries[w]=t;else if("cylinder"===q.type)t=new THREE.CylinderGeometry(q.topRad,q.botRad,q.height,q.radSegs,q.heightSegs),t.name=w,C.geometries[w]=t;else if("torus"===q.type)t=new THREE.TorusGeometry(q.radius,q.tube,q.segmentsR,q.segmentsT),t.name=w,C.geometries[w]=t;else if("icosahedron"===q.type)t=new THREE.IcosahedronGeometry(q.radius,q.subdivisions),t.name=w,C.geometries[w]=t;else if(q.type in this.geometryHandlers){E=
+{};for(u in q)"type"!==u&&"url"!==u&&(E[u]=q[u]);this.geometryHandlers[q.type].loaderObject.load(d(q.url,x.urlBaseType),g(w),E)}else"embedded"===q.type&&(E=x.embeds[q.id],E.metadata=x.metadata,E&&(E=this.geometryHandlers.ascii.loaderObject.parse(E,""),i(w)(E.geometry,E.materials)));for(var y in x.textures)if(w=x.textures[y],w.url instanceof Array){z+=w.url.length;for(u=0;u<w.url.length;u++)n.onLoadStart()}else z+=1,n.onLoadStart();N=z;for(y in x.textures){w=x.textures[y];void 0!==w.mapping&&void 0!==
+THREE[w.mapping]&&(w.mapping=new THREE[w.mapping]);if(w.url instanceof Array){E=w.url.length;q=[];for(u=0;u<E;u++)q[u]=d(w.url[u],x.urlBaseType);u=(u=/\.dds$/i.test(q[0]))?THREE.ImageUtils.loadCompressedTextureCube(q,w.mapping,F(E)):THREE.ImageUtils.loadTextureCube(q,w.mapping,F(E))}else u=/\.dds$/i.test(w.url),E=d(w.url,x.urlBaseType),q=F(1),u=u?THREE.ImageUtils.loadCompressedTexture(E,w.mapping,q):THREE.ImageUtils.loadTexture(E,w.mapping,q),void 0!==THREE[w.minFilter]&&(u.minFilter=THREE[w.minFilter]),
+void 0!==THREE[w.magFilter]&&(u.magFilter=THREE[w.magFilter]),w.anisotropy&&(u.anisotropy=w.anisotropy),w.repeat&&(u.repeat.set(w.repeat[0],w.repeat[1]),1!==w.repeat[0]&&(u.wrapS=THREE.RepeatWrapping),1!==w.repeat[1]&&(u.wrapT=THREE.RepeatWrapping)),w.offset&&u.offset.set(w.offset[0],w.offset[1]),w.wrap&&(E={repeat:THREE.RepeatWrapping,mirror:THREE.MirroredRepeatWrapping},void 0!==E[w.wrap[0]]&&(u.wrapS=E[w.wrap[0]]),void 0!==E[w.wrap[1]]&&(u.wrapT=E[w.wrap[1]]));C.textures[y]=u}var D,H;for(D in x.materials){y=
+x.materials[D];for(H in y.parameters)"envMap"===H||"map"===H||"lightMap"===H||"bumpMap"===H?y.parameters[H]=C.textures[y.parameters[H]]:"shading"===H?y.parameters[H]="flat"===y.parameters[H]?THREE.FlatShading:THREE.SmoothShading:"side"===H?y.parameters[H]="double"==y.parameters[H]?THREE.DoubleSide:"back"==y.parameters[H]?THREE.BackSide:THREE.FrontSide:"blending"===H?y.parameters[H]=y.parameters[H]in THREE?THREE[y.parameters[H]]:THREE.NormalBlending:"combine"===H?y.parameters[H]=y.parameters[H]in THREE?
+THREE[y.parameters[H]]:THREE.MultiplyOperation:"vertexColors"===H?"face"==y.parameters[H]?y.parameters[H]=THREE.FaceColors:y.parameters[H]&&(y.parameters[H]=THREE.VertexColors):"wrapRGB"===H&&(F=y.parameters[H],y.parameters[H]=new THREE.Vector3(F[0],F[1],F[2]));void 0!==y.parameters.opacity&&1>y.parameters.opacity&&(y.parameters.transparent=!0);y.parameters.normalMap?(F=THREE.ShaderLib.normalmap,w=THREE.UniformsUtils.clone(F.uniforms),u=y.parameters.color,E=y.parameters.specular,q=y.parameters.ambient,
+L=y.parameters.shininess,w.tNormal.value=C.textures[y.parameters.normalMap],y.parameters.normalScale&&w.uNormalScale.value.set(y.parameters.normalScale[0],y.parameters.normalScale[1]),y.parameters.map&&(w.tDiffuse.value=y.parameters.map,w.enableDiffuse.value=!0),y.parameters.envMap&&(w.tCube.value=y.parameters.envMap,w.enableReflection.value=!0,w.reflectivity.value=y.parameters.reflectivity),y.parameters.lightMap&&(w.tAO.value=y.parameters.lightMap,w.enableAO.value=!0),y.parameters.specularMap&&(w.tSpecular.value=
+C.textures[y.parameters.specularMap],w.enableSpecular.value=!0),y.parameters.displacementMap&&(w.tDisplacement.value=C.textures[y.parameters.displacementMap],w.enableDisplacement.value=!0,w.uDisplacementBias.value=y.parameters.displacementBias,w.uDisplacementScale.value=y.parameters.displacementScale),w.diffuse.value.setHex(u),w.specular.value.setHex(E),w.ambient.value.setHex(q),w.shininess.value=L,y.parameters.opacity&&(w.opacity.value=y.parameters.opacity),r=new THREE.ShaderMaterial({fragmentShader:F.fragmentShader,
+vertexShader:F.vertexShader,uniforms:w,lights:!0,fog:!0})):r=new THREE[y.type](y.parameters);r.name=D;C.materials[D]=r}for(D in x.materials)if(y=x.materials[D],y.parameters.materials){H=[];for(u=0;u<y.parameters.materials.length;u++)H.push(C.materials[y.parameters.materials[u]]);C.materials[D].materials=H}e();C.cameras&&x.defaults.camera&&(C.currentCamera=C.cameras[x.defaults.camera]);C.fogs&&x.defaults.fog&&(C.scene.fog=C.fogs[x.defaults.fog]);n.callbackSync(C);k()}};THREE.TextureLoader=function(a){this.manager=void 0!==a?a:THREE.DefaultLoadingManager};THREE.TextureLoader.prototype={constructor:THREE.TextureLoader,load:function(a,b){var c=new THREE.ImageLoader(this.manager);c.setCrossOrigin(this.crossOrigin);c.load(a,function(a){a=new THREE.Texture(a);a.needsUpdate=!0;void 0!==b&&b(a)})},setCrossOrigin:function(a){this.crossOrigin=a}};THREE.Material=function(){this.id=THREE.MaterialIdCount++;this.uuid=THREE.Math.generateUUID();this.name="";this.side=THREE.FrontSide;this.opacity=1;this.transparent=!1;this.blending=THREE.NormalBlending;this.blendSrc=THREE.SrcAlphaFactor;this.blendDst=THREE.OneMinusSrcAlphaFactor;this.blendEquation=THREE.AddEquation;this.depthWrite=this.depthTest=!0;this.polygonOffset=!1;this.overdraw=this.alphaTest=this.polygonOffsetUnits=this.polygonOffsetFactor=0;this.needsUpdate=this.visible=!0};
+THREE.Material.prototype={constructor:THREE.Material,setValues:function(a){if(void 0!==a)for(var b in a){var c=a[b];if(void 0===c)console.warn("THREE.Material: '"+b+"' parameter is undefined.");else if(b in this){var d=this[b];d instanceof THREE.Color?d.set(c):d instanceof THREE.Vector3&&c instanceof THREE.Vector3?d.copy(c):this[b]="overdraw"==b?Number(c):c}}},clone:function(a){void 0===a&&(a=new THREE.Material);a.name=this.name;a.side=this.side;a.opacity=this.opacity;a.transparent=this.transparent;
+a.blending=this.blending;a.blendSrc=this.blendSrc;a.blendDst=this.blendDst;a.blendEquation=this.blendEquation;a.depthTest=this.depthTest;a.depthWrite=this.depthWrite;a.polygonOffset=this.polygonOffset;a.polygonOffsetFactor=this.polygonOffsetFactor;a.polygonOffsetUnits=this.polygonOffsetUnits;a.alphaTest=this.alphaTest;a.overdraw=this.overdraw;a.visible=this.visible;return a},dispose:function(){this.dispatchEvent({type:"dispose"})}};THREE.EventDispatcher.prototype.apply(THREE.Material.prototype);
+THREE.MaterialIdCount=0;THREE.LineBasicMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.linewidth=1;this.linejoin=this.linecap="round";this.vertexColors=!1;this.fog=!0;this.setValues(a)};THREE.LineBasicMaterial.prototype=Object.create(THREE.Material.prototype);
+THREE.LineBasicMaterial.prototype.clone=function(){var a=new THREE.LineBasicMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.linewidth=this.linewidth;a.linecap=this.linecap;a.linejoin=this.linejoin;a.vertexColors=this.vertexColors;a.fog=this.fog;return a};THREE.LineDashedMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.scale=this.linewidth=1;this.dashSize=3;this.gapSize=1;this.vertexColors=!1;this.fog=!0;this.setValues(a)};THREE.LineDashedMaterial.prototype=Object.create(THREE.Material.prototype);
+THREE.LineDashedMaterial.prototype.clone=function(){var a=new THREE.LineDashedMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.linewidth=this.linewidth;a.scale=this.scale;a.dashSize=this.dashSize;a.gapSize=this.gapSize;a.vertexColors=this.vertexColors;a.fog=this.fog;return a};THREE.MeshBasicMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.envMap=this.specularMap=this.lightMap=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refractionRatio=0.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth=1;this.wireframeLinejoin=this.wireframeLinecap="round";this.vertexColors=THREE.NoColors;this.morphTargets=this.skinning=!1;this.setValues(a)};
+THREE.MeshBasicMaterial.prototype=Object.create(THREE.Material.prototype);
+THREE.MeshBasicMaterial.prototype.clone=function(){var a=new THREE.MeshBasicMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.map=this.map;a.lightMap=this.lightMap;a.specularMap=this.specularMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog;a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap;a.wireframeLinejoin=
+this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;return a};THREE.MeshLambertMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.ambient=new THREE.Color(16777215);this.emissive=new THREE.Color(0);this.wrapAround=!1;this.wrapRGB=new THREE.Vector3(1,1,1);this.envMap=this.specularMap=this.lightMap=this.map=null;this.combine=THREE.MultiplyOperation;this.reflectivity=1;this.refractionRatio=0.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth=1;this.wireframeLinejoin=this.wireframeLinecap=
+"round";this.vertexColors=THREE.NoColors;this.morphNormals=this.morphTargets=this.skinning=!1;this.setValues(a)};THREE.MeshLambertMaterial.prototype=Object.create(THREE.Material.prototype);
+THREE.MeshLambertMaterial.prototype.clone=function(){var a=new THREE.MeshLambertMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.ambient.copy(this.ambient);a.emissive.copy(this.emissive);a.wrapAround=this.wrapAround;a.wrapRGB.copy(this.wrapRGB);a.map=this.map;a.lightMap=this.lightMap;a.specularMap=this.specularMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog;a.shading=this.shading;
+a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap;a.wireframeLinejoin=this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;a.morphNormals=this.morphNormals;return a};THREE.MeshPhongMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.ambient=new THREE.Color(16777215);this.emissive=new THREE.Color(0);this.specular=new THREE.Color(1118481);this.shininess=30;this.metal=!1;this.perPixel=!0;this.wrapAround=!1;this.wrapRGB=new THREE.Vector3(1,1,1);this.bumpMap=this.lightMap=this.map=null;this.bumpScale=1;this.normalMap=null;this.normalScale=new THREE.Vector2(1,1);this.envMap=this.specularMap=null;this.combine=THREE.MultiplyOperation;
+this.reflectivity=1;this.refractionRatio=0.98;this.fog=!0;this.shading=THREE.SmoothShading;this.wireframe=!1;this.wireframeLinewidth=1;this.wireframeLinejoin=this.wireframeLinecap="round";this.vertexColors=THREE.NoColors;this.morphNormals=this.morphTargets=this.skinning=!1;this.setValues(a)};THREE.MeshPhongMaterial.prototype=Object.create(THREE.Material.prototype);
+THREE.MeshPhongMaterial.prototype.clone=function(){var a=new THREE.MeshPhongMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.ambient.copy(this.ambient);a.emissive.copy(this.emissive);a.specular.copy(this.specular);a.shininess=this.shininess;a.metal=this.metal;a.perPixel=this.perPixel;a.wrapAround=this.wrapAround;a.wrapRGB.copy(this.wrapRGB);a.map=this.map;a.lightMap=this.lightMap;a.bumpMap=this.bumpMap;a.bumpScale=this.bumpScale;a.normalMap=this.normalMap;a.normalScale.copy(this.normalScale);
+a.specularMap=this.specularMap;a.envMap=this.envMap;a.combine=this.combine;a.reflectivity=this.reflectivity;a.refractionRatio=this.refractionRatio;a.fog=this.fog;a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.wireframeLinecap=this.wireframeLinecap;a.wireframeLinejoin=this.wireframeLinejoin;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=this.morphTargets;a.morphNormals=this.morphNormals;return a};THREE.MeshDepthMaterial=function(a){THREE.Material.call(this);this.wireframe=!1;this.wireframeLinewidth=1;this.setValues(a)};THREE.MeshDepthMaterial.prototype=Object.create(THREE.Material.prototype);THREE.MeshDepthMaterial.prototype.clone=function(){var a=new THREE.MeshDepthMaterial;THREE.Material.prototype.clone.call(this,a);a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;return a};THREE.MeshNormalMaterial=function(a){THREE.Material.call(this,a);this.shading=THREE.FlatShading;this.wireframe=!1;this.wireframeLinewidth=1;this.morphTargets=!1;this.setValues(a)};THREE.MeshNormalMaterial.prototype=Object.create(THREE.Material.prototype);THREE.MeshNormalMaterial.prototype.clone=function(){var a=new THREE.MeshNormalMaterial;THREE.Material.prototype.clone.call(this,a);a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;return a};THREE.MeshFaceMaterial=function(a){this.materials=a instanceof Array?a:[]};THREE.MeshFaceMaterial.prototype.clone=function(){for(var a=new THREE.MeshFaceMaterial,b=0;b<this.materials.length;b++)a.materials.push(this.materials[b].clone());return a};THREE.ParticleSystemMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.map=null;this.size=1;this.sizeAttenuation=!0;this.vertexColors=!1;this.fog=!0;this.setValues(a)};THREE.ParticleSystemMaterial.prototype=Object.create(THREE.Material.prototype);
+THREE.ParticleSystemMaterial.prototype.clone=function(){var a=new THREE.ParticleSystemMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.map=this.map;a.size=this.size;a.sizeAttenuation=this.sizeAttenuation;a.vertexColors=this.vertexColors;a.fog=this.fog;return a};THREE.ParticleBasicMaterial=THREE.ParticleSystemMaterial;THREE.ShaderMaterial=function(a){THREE.Material.call(this);this.vertexShader=this.fragmentShader="void main() {}";this.uniforms={};this.defines={};this.attributes=null;this.shading=THREE.SmoothShading;this.linewidth=1;this.wireframe=!1;this.wireframeLinewidth=1;this.lights=this.fog=!1;this.vertexColors=THREE.NoColors;this.morphNormals=this.morphTargets=this.skinning=!1;this.defaultAttributeValues={color:[1,1,1],uv:[0,0],uv2:[0,0]};this.index0AttributeName="position";this.setValues(a)};
+THREE.ShaderMaterial.prototype=Object.create(THREE.Material.prototype);
+THREE.ShaderMaterial.prototype.clone=function(){var a=new THREE.ShaderMaterial;THREE.Material.prototype.clone.call(this,a);a.fragmentShader=this.fragmentShader;a.vertexShader=this.vertexShader;a.uniforms=THREE.UniformsUtils.clone(this.uniforms);a.attributes=this.attributes;a.defines=this.defines;a.shading=this.shading;a.wireframe=this.wireframe;a.wireframeLinewidth=this.wireframeLinewidth;a.fog=this.fog;a.lights=this.lights;a.vertexColors=this.vertexColors;a.skinning=this.skinning;a.morphTargets=
+this.morphTargets;a.morphNormals=this.morphNormals;return a};THREE.SpriteMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.map=null;this.rotation=0;this.fog=!1;this.setValues(a)};THREE.SpriteMaterial.prototype=Object.create(THREE.Material.prototype);THREE.SpriteMaterial.prototype.clone=function(){var a=new THREE.SpriteMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.map=this.map;a.rotation=this.rotation;a.fog=this.fog;return a};THREE.SpriteCanvasMaterial=function(a){THREE.Material.call(this);this.color=new THREE.Color(16777215);this.program=function(){};this.setValues(a)};THREE.SpriteCanvasMaterial.prototype=Object.create(THREE.Material.prototype);THREE.SpriteCanvasMaterial.prototype.clone=function(){var a=new THREE.SpriteCanvasMaterial;THREE.Material.prototype.clone.call(this,a);a.color.copy(this.color);a.program=this.program;return a};THREE.ParticleCanvasMaterial=THREE.SpriteCanvasMaterial;THREE.Texture=function(a,b,c,d,e,f,g,h,i){this.id=THREE.TextureIdCount++;this.uuid=THREE.Math.generateUUID();this.name="";this.image=a;this.mipmaps=[];this.mapping=void 0!==b?b:new THREE.UVMapping;this.wrapS=void 0!==c?c:THREE.ClampToEdgeWrapping;this.wrapT=void 0!==d?d:THREE.ClampToEdgeWrapping;this.magFilter=void 0!==e?e:THREE.LinearFilter;this.minFilter=void 0!==f?f:THREE.LinearMipMapLinearFilter;this.anisotropy=void 0!==i?i:1;this.format=void 0!==g?g:THREE.RGBAFormat;this.type=void 0!==h?h:THREE.UnsignedByteType;
+this.offset=new THREE.Vector2(0,0);this.repeat=new THREE.Vector2(1,1);this.generateMipmaps=!0;this.premultiplyAlpha=!1;this.flipY=!0;this.unpackAlignment=4;this._needsUpdate=!1;this.onUpdate=null};
+THREE.Texture.prototype={constructor:THREE.Texture,get needsUpdate(){return this._needsUpdate},set needsUpdate(a){!0===a&&this.update();this._needsUpdate=a},clone:function(a){void 0===a&&(a=new THREE.Texture);a.image=this.image;a.mipmaps=this.mipmaps.slice(0);a.mapping=this.mapping;a.wrapS=this.wrapS;a.wrapT=this.wrapT;a.magFilter=this.magFilter;a.minFilter=this.minFilter;a.anisotropy=this.anisotropy;a.format=this.format;a.type=this.type;a.offset.copy(this.offset);a.repeat.copy(this.repeat);a.generateMipmaps=
+this.generateMipmaps;a.premultiplyAlpha=this.premultiplyAlpha;a.flipY=this.flipY;a.unpackAlignment=this.unpackAlignment;return a},update:function(){this.dispatchEvent({type:"update"})},dispose:function(){this.dispatchEvent({type:"dispose"})}};THREE.EventDispatcher.prototype.apply(THREE.Texture.prototype);THREE.TextureIdCount=0;THREE.CompressedTexture=function(a,b,c,d,e,f,g,h,i,k,l){THREE.Texture.call(this,null,f,g,h,i,k,d,e,l);this.image={width:b,height:c};this.mipmaps=a;this.generateMipmaps=!1};THREE.CompressedTexture.prototype=Object.create(THREE.Texture.prototype);THREE.CompressedTexture.prototype.clone=function(){var a=new THREE.CompressedTexture;THREE.Texture.prototype.clone.call(this,a);return a};THREE.DataTexture=function(a,b,c,d,e,f,g,h,i,k,l){THREE.Texture.call(this,null,f,g,h,i,k,d,e,l);this.image={data:a,width:b,height:c}};THREE.DataTexture.prototype=Object.create(THREE.Texture.prototype);THREE.DataTexture.prototype.clone=function(){var a=new THREE.DataTexture;THREE.Texture.prototype.clone.call(this,a);return a};THREE.ParticleSystem=function(a,b){THREE.Object3D.call(this);this.geometry=void 0!==a?a:new THREE.Geometry;this.material=void 0!==b?b:new THREE.ParticleSystemMaterial({color:16777215*Math.random()});this.frustumCulled=this.sortParticles=!1};THREE.ParticleSystem.prototype=Object.create(THREE.Object3D.prototype);
+THREE.ParticleSystem.prototype.clone=function(a){void 0===a&&(a=new THREE.ParticleSystem(this.geometry,this.material));a.sortParticles=this.sortParticles;THREE.Object3D.prototype.clone.call(this,a);return a};THREE.Line=function(a,b,c){THREE.Object3D.call(this);this.geometry=void 0!==a?a:new THREE.Geometry;this.material=void 0!==b?b:new THREE.LineBasicMaterial({color:16777215*Math.random()});this.type=void 0!==c?c:THREE.LineStrip};THREE.LineStrip=0;THREE.LinePieces=1;THREE.Line.prototype=Object.create(THREE.Object3D.prototype);THREE.Line.prototype.clone=function(a){void 0===a&&(a=new THREE.Line(this.geometry,this.material,this.type));THREE.Object3D.prototype.clone.call(this,a);return a};THREE.Mesh=function(a,b){THREE.Object3D.call(this);this.geometry=void 0!==a?a:new THREE.Geometry;this.material=void 0!==b?b:new THREE.MeshBasicMaterial({color:16777215*Math.random()});this.updateMorphTargets()};THREE.Mesh.prototype=Object.create(THREE.Object3D.prototype);
+THREE.Mesh.prototype.updateMorphTargets=function(){if(0<this.geometry.morphTargets.length){this.morphTargetBase=-1;this.morphTargetForcedOrder=[];this.morphTargetInfluences=[];this.morphTargetDictionary={};for(var a=0,b=this.geometry.morphTargets.length;a<b;a++)this.morphTargetInfluences.push(0),this.morphTargetDictionary[this.geometry.morphTargets[a].name]=a}};
+THREE.Mesh.prototype.getMorphTargetIndexByName=function(a){if(void 0!==this.morphTargetDictionary[a])return this.morphTargetDictionary[a];console.log("THREE.Mesh.getMorphTargetIndexByName: morph target "+a+" does not exist. Returning 0.");return 0};THREE.Mesh.prototype.clone=function(a){void 0===a&&(a=new THREE.Mesh(this.geometry,this.material));THREE.Object3D.prototype.clone.call(this,a);return a};THREE.Bone=function(a){THREE.Object3D.call(this);this.skin=a;this.skinMatrix=new THREE.Matrix4};THREE.Bone.prototype=Object.create(THREE.Object3D.prototype);THREE.Bone.prototype.update=function(a,b){this.matrixAutoUpdate&&(b|=this.updateMatrix());if(b||this.matrixWorldNeedsUpdate)a?this.skinMatrix.multiplyMatrices(a,this.matrix):this.skinMatrix.copy(this.matrix),this.matrixWorldNeedsUpdate=!1,b=!0;var c,d=this.children.length;for(c=0;c<d;c++)this.children[c].update(this.skinMatrix,b)};THREE.SkinnedMesh=function(a,b,c){THREE.Mesh.call(this,a,b);this.useVertexTexture=void 0!==c?c:!0;this.identityMatrix=new THREE.Matrix4;this.bones=[];this.boneMatrices=[];var d,e,f;if(this.geometry&&void 0!==this.geometry.bones){for(a=0;a<this.geometry.bones.length;a++)c=this.geometry.bones[a],d=c.pos,e=c.rotq,f=c.scl,b=this.addBone(),b.name=c.name,b.position.set(d[0],d[1],d[2]),b.quaternion.set(e[0],e[1],e[2],e[3]),void 0!==f?b.scale.set(f[0],f[1],f[2]):b.scale.set(1,1,1);for(a=0;a<this.bones.length;a++)c=
+this.geometry.bones[a],b=this.bones[a],-1===c.parent?this.add(b):this.bones[c.parent].add(b);a=this.bones.length;this.useVertexTexture?(this.boneTextureHeight=this.boneTextureWidth=a=256<a?64:64<a?32:16<a?16:8,this.boneMatrices=new Float32Array(4*this.boneTextureWidth*this.boneTextureHeight),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=!1,this.boneTexture.flipY=!1):this.boneMatrices=new Float32Array(16*a);this.pose()}};THREE.SkinnedMesh.prototype=Object.create(THREE.Mesh.prototype);THREE.SkinnedMesh.prototype.addBone=function(a){void 0===a&&(a=new THREE.Bone(this));this.bones.push(a);return a};
+THREE.SkinnedMesh.prototype.updateMatrixWorld=function(){var a=new THREE.Matrix4;return function(b){this.matrixAutoUpdate&&this.updateMatrix();if(this.matrixWorldNeedsUpdate||b)this.parent?this.matrixWorld.multiplyMatrices(this.parent.matrixWorld,this.matrix):this.matrixWorld.copy(this.matrix),this.matrixWorldNeedsUpdate=!1;for(var b=0,c=this.children.length;b<c;b++){var d=this.children[b];d instanceof THREE.Bone?d.update(this.identityMatrix,!1):d.updateMatrixWorld(!0)}if(void 0==this.boneInverses){this.boneInverses=
+[];b=0;for(c=this.bones.length;b<c;b++)d=new THREE.Matrix4,d.getInverse(this.bones[b].skinMatrix),this.boneInverses.push(d)}b=0;for(c=this.bones.length;b<c;b++)a.multiplyMatrices(this.bones[b].skinMatrix,this.boneInverses[b]),a.flattenToArrayOffset(this.boneMatrices,16*b);this.useVertexTexture&&(this.boneTexture.needsUpdate=!0)}}();THREE.SkinnedMesh.prototype.pose=function(){this.updateMatrixWorld(!0);this.normalizeSkinWeights()};
+THREE.SkinnedMesh.prototype.normalizeSkinWeights=function(){if(this.geometry instanceof THREE.Geometry)for(var a=0;a<this.geometry.skinIndices.length;a++){var b=this.geometry.skinWeights[a],c=1/b.lengthManhattan();Infinity!==c?b.multiplyScalar(c):b.set(1)}};THREE.SkinnedMesh.prototype.clone=function(a){void 0===a&&(a=new THREE.SkinnedMesh(this.geometry,this.material,this.useVertexTexture));THREE.Mesh.prototype.clone.call(this,a);return a};THREE.MorphAnimMesh=function(a,b){THREE.Mesh.call(this,a,b);this.duration=1E3;this.mirroredLoop=!1;this.currentKeyframe=this.lastKeyframe=this.time=0;this.direction=1;this.directionBackwards=!1;this.setFrameRange(0,this.geometry.morphTargets.length-1)};THREE.MorphAnimMesh.prototype=Object.create(THREE.Mesh.prototype);THREE.MorphAnimMesh.prototype.setFrameRange=function(a,b){this.startKeyframe=a;this.endKeyframe=b;this.length=this.endKeyframe-this.startKeyframe+1};
+THREE.MorphAnimMesh.prototype.setDirectionForward=function(){this.direction=1;this.directionBackwards=!1};THREE.MorphAnimMesh.prototype.setDirectionBackward=function(){this.direction=-1;this.directionBackwards=!0};
+THREE.MorphAnimMesh.prototype.parseAnimations=function(){var a=this.geometry;a.animations||(a.animations={});for(var b,c=a.animations,d=/([a-z]+)(\d+)/,e=0,f=a.morphTargets.length;e<f;e++){var g=a.morphTargets[e].name.match(d);if(g&&1<g.length){g=g[1];c[g]||(c[g]={start:Infinity,end:-Infinity});var h=c[g];e<h.start&&(h.start=e);e>h.end&&(h.end=e);b||(b=g)}}a.firstAnimation=b};
+THREE.MorphAnimMesh.prototype.setAnimationLabel=function(a,b,c){this.geometry.animations||(this.geometry.animations={});this.geometry.animations[a]={start:b,end:c}};THREE.MorphAnimMesh.prototype.playAnimation=function(a,b){var c=this.geometry.animations[a];c?(this.setFrameRange(c.start,c.end),this.duration=1E3*((c.end-c.start)/b),this.time=0):console.warn("animation["+a+"] undefined")};
+THREE.MorphAnimMesh.prototype.updateAnimation=function(a){var b=this.duration/this.length;this.time+=this.direction*a;if(this.mirroredLoop){if(this.time>this.duration||0>this.time)this.direction*=-1,this.time>this.duration&&(this.time=this.duration,this.directionBackwards=!0),0>this.time&&(this.time=0,this.directionBackwards=!1)}else this.time%=this.duration,0>this.time&&(this.time+=this.duration);a=this.startKeyframe+THREE.Math.clamp(Math.floor(this.time/b),0,this.length-1);a!==this.currentKeyframe&&
+(this.morphTargetInfluences[this.lastKeyframe]=0,this.morphTargetInfluences[this.currentKeyframe]=1,this.morphTargetInfluences[a]=0,this.lastKeyframe=this.currentKeyframe,this.currentKeyframe=a);b=this.time%b/b;this.directionBackwards&&(b=1-b);this.morphTargetInfluences[this.currentKeyframe]=b;this.morphTargetInfluences[this.lastKeyframe]=1-b};
+THREE.MorphAnimMesh.prototype.clone=function(a){void 0===a&&(a=new THREE.MorphAnimMesh(this.geometry,this.material));a.duration=this.duration;a.mirroredLoop=this.mirroredLoop;a.time=this.time;a.lastKeyframe=this.lastKeyframe;a.currentKeyframe=this.currentKeyframe;a.direction=this.direction;a.directionBackwards=this.directionBackwards;THREE.Mesh.prototype.clone.call(this,a);return a};THREE.LOD=function(){THREE.Object3D.call(this);this.objects=[]};THREE.LOD.prototype=Object.create(THREE.Object3D.prototype);THREE.LOD.prototype.addLevel=function(a,b){void 0===b&&(b=0);for(var b=Math.abs(b),c=0;c<this.objects.length&&!(b<this.objects[c].distance);c++);this.objects.splice(c,0,{distance:b,object:a});this.add(a)};THREE.LOD.prototype.getObjectForDistance=function(a){for(var b=1,c=this.objects.length;b<c&&!(a<this.objects[b].distance);b++);return this.objects[b-1].object};
+THREE.LOD.prototype.update=function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c){if(1<this.objects.length){a.setFromMatrixPosition(c.matrixWorld);b.setFromMatrixPosition(this.matrixWorld);c=a.distanceTo(b);this.objects[0].object.visible=!0;for(var d=1,e=this.objects.length;d<e;d++)if(c>=this.objects[d].distance)this.objects[d-1].object.visible=!1,this.objects[d].object.visible=!0;else break;for(;d<e;d++)this.objects[d].object.visible=!1}}}();THREE.LOD.prototype.clone=function(){};THREE.Sprite=function(a){THREE.Object3D.call(this);this.material=void 0!==a?a:new THREE.SpriteMaterial};THREE.Sprite.prototype=Object.create(THREE.Object3D.prototype);THREE.Sprite.prototype.updateMatrix=function(){this.matrix.compose(this.position,this.quaternion,this.scale);this.matrixWorldNeedsUpdate=!0};THREE.Sprite.prototype.clone=function(a){void 0===a&&(a=new THREE.Sprite(this.material));THREE.Object3D.prototype.clone.call(this,a);return a};THREE.Particle=THREE.Sprite;THREE.Scene=function(){THREE.Object3D.call(this);this.overrideMaterial=this.fog=null;this.autoUpdate=!0;this.matrixAutoUpdate=!1;this.__lights=[];this.__objectsAdded=[];this.__objectsRemoved=[]};THREE.Scene.prototype=Object.create(THREE.Object3D.prototype);
+THREE.Scene.prototype.__addObject=function(a){if(a instanceof THREE.Light)-1===this.__lights.indexOf(a)&&this.__lights.push(a),a.target&&void 0===a.target.parent&&this.add(a.target);else if(!(a instanceof THREE.Camera||a instanceof THREE.Bone)){this.__objectsAdded.push(a);var b=this.__objectsRemoved.indexOf(a);-1!==b&&this.__objectsRemoved.splice(b,1)}this.dispatchEvent({type:"objectAdded",object:a});a.dispatchEvent({type:"addedToScene",scene:this});for(b=0;b<a.children.length;b++)this.__addObject(a.children[b])};
+THREE.Scene.prototype.__removeObject=function(a){if(a instanceof THREE.Light){var b=this.__lights.indexOf(a);-1!==b&&this.__lights.splice(b,1);if(a.shadowCascadeArray)for(b=0;b<a.shadowCascadeArray.length;b++)this.__removeObject(a.shadowCascadeArray[b])}else a instanceof THREE.Camera||(this.__objectsRemoved.push(a),b=this.__objectsAdded.indexOf(a),-1!==b&&this.__objectsAdded.splice(b,1));this.dispatchEvent({type:"objectRemoved",object:a});a.dispatchEvent({type:"removedFromScene",scene:this});for(b=
+0;b<a.children.length;b++)this.__removeObject(a.children[b])};THREE.Scene.prototype.clone=function(a){void 0===a&&(a=new THREE.Scene);THREE.Object3D.prototype.clone.call(this,a);null!==this.fog&&(a.fog=this.fog.clone());null!==this.overrideMaterial&&(a.overrideMaterial=this.overrideMaterial.clone());a.autoUpdate=this.autoUpdate;a.matrixAutoUpdate=this.matrixAutoUpdate;return a};THREE.Fog=function(a,b,c){this.name="";this.color=new THREE.Color(a);this.near=void 0!==b?b:1;this.far=void 0!==c?c:1E3};THREE.Fog.prototype.clone=function(){return new THREE.Fog(this.color.getHex(),this.near,this.far)};THREE.FogExp2=function(a,b){this.name="";this.color=new THREE.Color(a);this.density=void 0!==b?b:2.5E-4};THREE.FogExp2.prototype.clone=function(){return new THREE.FogExp2(this.color.getHex(),this.density)};THREE.CanvasRenderer=function(a){function b(a,b,c){for(var d=0,e=N.length;d<e;d++){var f=N[d];wa.copy(f.color);if(f instanceof THREE.DirectionalLight){var g=Na.setFromMatrixPosition(f.matrixWorld).normalize(),h=b.dot(g);0>=h||(h*=f.intensity,c.add(wa.multiplyScalar(h)))}else f instanceof THREE.PointLight&&(g=Na.setFromMatrixPosition(f.matrixWorld),h=b.dot(Na.subVectors(g,a).normalize()),0>=h||(h*=0==f.distance?1:1-Math.min(a.distanceTo(g)/f.distance,1),0!=h&&(h*=f.intensity,c.add(wa.multiplyScalar(h)))))}}
+function c(a,b,c,d){p(b);t(c);r(d);m(a.getStyle());E.stroke();pa.expandByScalar(2*b)}function d(a){q(a.getStyle());E.fill()}function e(a){f(a.target)}function f(a){var b=a.wrapS===THREE.RepeatWrapping,c=a.wrapT===THREE.RepeatWrapping,d=a.image,e=document.createElement("canvas");e.width=d.width;e.height=d.height;var f=e.getContext("2d");f.setTransform(1,0,0,-1,0,d.height);f.drawImage(d,0,0);Ua[a.id]=E.createPattern(e,!0===b&&!0===c?"repeat":!0===b&&!1===c?"repeat-x":!1===b&&!0===c?"repeat-y":"no-repeat")}
+function g(a,b,c,d,g,h,j,i,k,l,n,p,m){if(!(m instanceof THREE.DataTexture)){!1===m.hasEventListener("update",e)&&(void 0!==m.image&&0<m.image.width&&f(m),m.addEventListener("update",e));var t=Ua[m.id];if(void 0!==t){q(t);var t=m.offset.x/m.repeat.x,r=m.offset.y/m.repeat.y,s=m.image.width*m.repeat.x,m=m.image.height*m.repeat.y,j=(j+t)*s,i=(i+r)*m,c=c-a,d=d-b,g=g-a,h=h-b,k=(k+t)*s-j,l=(l+r)*m-i,n=(n+t)*s-j,p=(p+r)*m-i,m=k*p-n*l;0!==m&&(t=1/m,m=(p*c-l*g)*t,l=(p*d-l*h)*t,c=(k*g-n*c)*t,d=(k*h-n*d)*t,a=
+a-m*j-c*i,b=b-l*j-d*i,E.save(),E.transform(m,l,c,d,a,b),E.fill(),E.restore())}else q("rgba(0,0,0,1)"),E.fill()}}function h(a,b,c,d,e,f,g,h,j,i,k,l,n){var m,p;m=n.width-1;p=n.height-1;g*=m;h*=p;c-=a;d-=b;e-=a;f-=b;j=j*m-g;i=i*p-h;k=k*m-g;l=l*p-h;p=1/(j*l-k*i);m=(l*c-i*e)*p;i=(l*d-i*f)*p;c=(j*e-k*c)*p;d=(j*f-k*d)*p;a=a-m*g-c*h;b=b-i*g-d*h;E.save();E.transform(m,i,c,d,a,b);E.clip();E.drawImage(n,0,0);E.restore()}function i(a,b,c,d){qa[0]=255*a.r|0;qa[1]=255*a.g|0;qa[2]=255*a.b|0;qa[4]=255*b.r|0;qa[5]=
+255*b.g|0;qa[6]=255*b.b|0;qa[8]=255*c.r|0;qa[9]=255*c.g|0;qa[10]=255*c.b|0;qa[12]=255*d.r|0;qa[13]=255*d.g|0;qa[14]=255*d.b|0;Pa.putImageData(Wa,0,0);hb.drawImage(j,0,0);return ta}function k(a,b,c){var d=b.x-a.x,e=b.y-a.y,f=d*d+e*e;0!==f&&(c/=Math.sqrt(f),d*=c,e*=c,b.x+=d,b.y+=e,a.x-=d,a.y-=e)}function l(a){H!==a&&(H=E.globalAlpha=a)}function n(a){K!==a&&(a===THREE.NormalBlending?E.globalCompositeOperation="source-over":a===THREE.AdditiveBlending?E.globalCompositeOperation="lighter":a===THREE.SubtractiveBlending&&
+(E.globalCompositeOperation="darker"),K=a)}function p(a){ha!==a&&(ha=E.lineWidth=a)}function t(a){ua!==a&&(ua=E.lineCap=a)}function r(a){O!==a&&(O=E.lineJoin=a)}function m(a){A!==a&&(A=E.strokeStyle=a)}function q(a){da!==a&&(da=E.fillStyle=a)}function u(a,b){if(T!==a||ea!==b)E.setLineDash([a,b]),T=a,ea=b}console.log("THREE.CanvasRenderer",THREE.REVISION);var s=THREE.Math.smoothstep,a=a||{},v=this,z,G,N,C=new THREE.Projector,B=void 0!==a.canvas?a.canvas:document.createElement("canvas"),x=B.width,F=
+B.height,L=Math.floor(x/2),w=Math.floor(F/2),E=B.getContext("2d"),y=new THREE.Color(0),D=0,H=1,K=0,A=null,da=null,ha=null,ua=null,O=null,T=null,ea=0,I,P,Q,oa;new THREE.RenderableVertex;new THREE.RenderableVertex;var va,Ea,za,Ha,Ta,Ja,sa=new THREE.Color,Aa=new THREE.Color,ba=new THREE.Color,W=new THREE.Color,Ka=new THREE.Color,U=new THREE.Color,ca=new THREE.Color,wa=new THREE.Color,Ua={},Oa,la,ia,Fa,Ga,Ba,xa,Va,La,Ma,ya=new THREE.Box2,V=new THREE.Box2,pa=new THREE.Box2,ja=new THREE.Color,fa=new THREE.Color,
+Za=new THREE.Color,Na=new THREE.Vector3,j,Pa,Wa,qa,ta,hb,db=16;j=document.createElement("canvas");j.width=j.height=2;Pa=j.getContext("2d");Pa.fillStyle="rgba(0,0,0,1)";Pa.fillRect(0,0,2,2);Wa=Pa.getImageData(0,0,2,2);qa=Wa.data;ta=document.createElement("canvas");ta.width=ta.height=db;hb=ta.getContext("2d");hb.translate(-db/2,-db/2);hb.scale(db,db);db--;void 0===E.setLineDash&&(E.setLineDash=void 0!==E.mozDash?function(a){E.mozDash=null!==a[0]?a:null}:function(){});this.domElement=B;this.devicePixelRatio=
+void 0!==a.devicePixelRatio?a.devicePixelRatio:void 0!==self.devicePixelRatio?self.devicePixelRatio:1;this.sortElements=this.sortObjects=this.autoClear=!0;this.info={render:{vertices:0,faces:0}};this.supportsVertexTextures=function(){};this.setFaceCulling=function(){};this.setSize=function(a,b,c){x=a*this.devicePixelRatio;F=b*this.devicePixelRatio;L=Math.floor(x/2);w=Math.floor(F/2);B.width=x;B.height=F;1!==this.devicePixelRatio&&!1!==c&&(B.style.width=a+"px",B.style.height=b+"px");ya.set(new THREE.Vector2(-L,
+-w),new THREE.Vector2(L,w));V.set(new THREE.Vector2(-L,-w),new THREE.Vector2(L,w));H=1;K=0;O=ua=ha=da=A=null};this.setClearColor=function(a,b){y.set(a);D=void 0!==b?b:1;V.set(new THREE.Vector2(-L,-w),new THREE.Vector2(L,w))};this.setClearColorHex=function(a,b){console.warn("DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.");this.setClearColor(a,b)};this.getMaxAnisotropy=function(){return 0};this.clear=function(){E.setTransform(1,0,0,-1,L,w);!1===V.empty()&&(V.intersect(ya),
+V.expandByScalar(2),1>D&&E.clearRect(V.min.x|0,V.min.y|0,V.max.x-V.min.x|0,V.max.y-V.min.y|0),0<D&&(n(THREE.NormalBlending),l(1),q("rgba("+Math.floor(255*y.r)+","+Math.floor(255*y.g)+","+Math.floor(255*y.b)+","+D+")"),E.fillRect(V.min.x|0,V.min.y|0,V.max.x-V.min.x|0,V.max.y-V.min.y|0)),V.makeEmpty())};this.clearColor=function(){};this.clearDepth=function(){};this.clearStencil=function(){};this.render=function(a,j){if(!1===j instanceof THREE.Camera)console.error("THREE.CanvasRenderer.render: camera is not an instance of THREE.Camera.");
+else{!0===this.autoClear&&this.clear();E.setTransform(1,0,0,-1,L,w);v.info.render.vertices=0;v.info.render.faces=0;z=C.projectScene(a,j,this.sortObjects,this.sortElements);G=z.elements;N=z.lights;I=j;ja.setRGB(0,0,0);fa.setRGB(0,0,0);Za.setRGB(0,0,0);for(var x=0,H=N.length;x<H;x++){var B=N[x],F=B.color;B instanceof THREE.AmbientLight?ja.add(F):B instanceof THREE.DirectionalLight?fa.add(F):B instanceof THREE.PointLight&&Za.add(F)}x=0;for(H=G.length;x<H;x++){var y=G[x],D=y.material;if(!(void 0===D||
+!1===D.visible)){pa.makeEmpty();if(y instanceof THREE.RenderableSprite){P=y;P.x*=L;P.y*=w;B=P;F=D;l(F.opacity);n(F.blending);var A=y.scale.x*L,y=y.scale.y*w,D=0.5*Math.sqrt(A*A+y*y);pa.min.set(B.x-D,B.y-D);pa.max.set(B.x+D,B.y+D);if(!1===ya.isIntersectionBox(pa))pa.makeEmpty();else if(F instanceof THREE.SpriteMaterial||F instanceof THREE.ParticleSystemMaterial){var O=F.map;if(null!==O){!1===O.hasEventListener("update",e)&&(void 0!==O.image&&0<O.image.width&&f(O),O.addEventListener("update",e));D=
+Ua[O.id];void 0!==D?q(D):q("rgba( 0, 0, 0, 1 )");var K=O.image,D=K.width*O.offset.x,T=K.height*O.offset.y,da=K.width*O.repeat.x,O=K.height*O.repeat.y,K=A/da,ha=y/O;E.save();E.translate(B.x,B.y);0!==F.rotation&&E.rotate(F.rotation);E.translate(-A/2,-y/2);E.scale(K,ha);E.translate(-D,-T);E.fillRect(D,T,da,O)}else q(F.color.getStyle()),E.save(),E.translate(B.x,B.y),0!==F.rotation&&E.rotate(F.rotation),E.scale(A,-y),E.fillRect(-0.5,-0.5,1,1);E.restore()}else F instanceof THREE.SpriteCanvasMaterial&&(m(F.color.getStyle()),
+q(F.color.getStyle()),E.save(),E.translate(B.x,B.y),0!==F.rotation&&E.rotate(F.rotation),E.scale(A,y),F.program(E),E.restore())}else if(y instanceof THREE.RenderableLine){if(P=y.v1,Q=y.v2,P.positionScreen.x*=L,P.positionScreen.y*=w,Q.positionScreen.x*=L,Q.positionScreen.y*=w,pa.setFromPoints([P.positionScreen,Q.positionScreen]),!0===ya.isIntersectionBox(pa))if(B=P,F=Q,A=y,y=D,l(y.opacity),n(y.blending),E.beginPath(),E.moveTo(B.positionScreen.x,B.positionScreen.y),E.lineTo(F.positionScreen.x,F.positionScreen.y),
+y instanceof THREE.LineBasicMaterial){p(y.linewidth);t(y.linecap);r(y.linejoin);if(y.vertexColors!==THREE.VertexColors)m(y.color.getStyle());else if(D=A.vertexColors[0].getStyle(),A=A.vertexColors[1].getStyle(),D===A)m(D);else{try{var ea=E.createLinearGradient(B.positionScreen.x,B.positionScreen.y,F.positionScreen.x,F.positionScreen.y);ea.addColorStop(0,D);ea.addColorStop(1,A)}catch(qa){ea=D}m(ea)}E.stroke();pa.expandByScalar(2*y.linewidth)}else y instanceof THREE.LineDashedMaterial&&(p(y.linewidth),
+t(y.linecap),r(y.linejoin),m(y.color.getStyle()),u(y.dashSize,y.gapSize),E.stroke(),pa.expandByScalar(2*y.linewidth),u(null,null))}else if(y instanceof THREE.RenderableFace3){P=y.v1;Q=y.v2;oa=y.v3;if(-1>P.positionScreen.z||1<P.positionScreen.z)continue;if(-1>Q.positionScreen.z||1<Q.positionScreen.z)continue;if(-1>oa.positionScreen.z||1<oa.positionScreen.z)continue;P.positionScreen.x*=L;P.positionScreen.y*=w;Q.positionScreen.x*=L;Q.positionScreen.y*=w;oa.positionScreen.x*=L;oa.positionScreen.y*=w;
+0<D.overdraw&&(k(P.positionScreen,Q.positionScreen,D.overdraw),k(Q.positionScreen,oa.positionScreen,D.overdraw),k(oa.positionScreen,P.positionScreen,D.overdraw));pa.setFromPoints([P.positionScreen,Q.positionScreen,oa.positionScreen]);if(!0===ya.isIntersectionBox(pa)){B=P;F=Q;A=oa;v.info.render.vertices+=3;v.info.render.faces++;l(D.opacity);n(D.blending);va=B.positionScreen.x;Ea=B.positionScreen.y;za=F.positionScreen.x;Ha=F.positionScreen.y;Ta=A.positionScreen.x;Ja=A.positionScreen.y;var T=va,da=Ea,
+O=za,K=Ha,ha=Ta,ua=Ja;E.beginPath();E.moveTo(T,da);E.lineTo(O,K);E.lineTo(ha,ua);E.closePath();(D instanceof THREE.MeshLambertMaterial||D instanceof THREE.MeshPhongMaterial)&&null===D.map?(U.copy(D.color),ca.copy(D.emissive),D.vertexColors===THREE.FaceColors&&U.multiply(y.color),!1===D.wireframe&&D.shading===THREE.SmoothShading&&3===y.vertexNormalsLength?(Aa.copy(ja),ba.copy(ja),W.copy(ja),b(y.v1.positionWorld,y.vertexNormalsModel[0],Aa),b(y.v2.positionWorld,y.vertexNormalsModel[1],ba),b(y.v3.positionWorld,
+y.vertexNormalsModel[2],W),Aa.multiply(U).add(ca),ba.multiply(U).add(ca),W.multiply(U).add(ca),Ka.addColors(ba,W).multiplyScalar(0.5),ia=i(Aa,ba,W,Ka),h(va,Ea,za,Ha,Ta,Ja,0,0,1,0,0,1,ia)):(sa.copy(ja),b(y.centroidModel,y.normalModel,sa),sa.multiply(U).add(ca),!0===D.wireframe?c(sa,D.wireframeLinewidth,D.wireframeLinecap,D.wireframeLinejoin):d(sa))):D instanceof THREE.MeshBasicMaterial||D instanceof THREE.MeshLambertMaterial||D instanceof THREE.MeshPhongMaterial?null!==D.map?D.map.mapping instanceof
+THREE.UVMapping&&(Fa=y.uvs[0],g(va,Ea,za,Ha,Ta,Ja,Fa[0].x,Fa[0].y,Fa[1].x,Fa[1].y,Fa[2].x,Fa[2].y,D.map)):null!==D.envMap?D.envMap.mapping instanceof THREE.SphericalReflectionMapping&&(Na.copy(y.vertexNormalsModelView[0]),Ga=0.5*Na.x+0.5,Ba=0.5*Na.y+0.5,Na.copy(y.vertexNormalsModelView[1]),xa=0.5*Na.x+0.5,Va=0.5*Na.y+0.5,Na.copy(y.vertexNormalsModelView[2]),La=0.5*Na.x+0.5,Ma=0.5*Na.y+0.5,g(va,Ea,za,Ha,Ta,Ja,Ga,Ba,xa,Va,La,Ma,D.envMap)):(sa.copy(D.color),D.vertexColors===THREE.FaceColors&&sa.multiply(y.color),
+!0===D.wireframe?c(sa,D.wireframeLinewidth,D.wireframeLinecap,D.wireframeLinejoin):d(sa)):D instanceof THREE.MeshDepthMaterial?(Oa=I.near,la=I.far,Aa.r=Aa.g=Aa.b=1-s(B.positionScreen.z*B.positionScreen.w,Oa,la),ba.r=ba.g=ba.b=1-s(F.positionScreen.z*F.positionScreen.w,Oa,la),W.r=W.g=W.b=1-s(A.positionScreen.z*A.positionScreen.w,Oa,la),Ka.addColors(ba,W).multiplyScalar(0.5),ia=i(Aa,ba,W,Ka),h(va,Ea,za,Ha,Ta,Ja,0,0,1,0,0,1,ia)):D instanceof THREE.MeshNormalMaterial&&(B=void 0,D.shading===THREE.FlatShading?
+(B=y.normalModelView,sa.setRGB(B.x,B.y,B.z).multiplyScalar(0.5).addScalar(0.5),!0===D.wireframe?c(sa,D.wireframeLinewidth,D.wireframeLinecap,D.wireframeLinejoin):d(sa)):D.shading===THREE.SmoothShading&&(B=y.vertexNormalsModelView[0],Aa.setRGB(B.x,B.y,B.z).multiplyScalar(0.5).addScalar(0.5),B=y.vertexNormalsModelView[1],ba.setRGB(B.x,B.y,B.z).multiplyScalar(0.5).addScalar(0.5),B=y.vertexNormalsModelView[2],W.setRGB(B.x,B.y,B.z).multiplyScalar(0.5).addScalar(0.5),Ka.addColors(ba,W).multiplyScalar(0.5),
+ia=i(Aa,ba,W,Ka),h(va,Ea,za,Ha,Ta,Ja,0,0,1,0,0,1,ia)))}}V.union(pa)}}E.setTransform(1,0,0,1,0,0)}}};THREE.ShaderChunk={fog_pars_fragment:"#ifdef USE_FOG\nuniform vec3 fogColor;\n#ifdef FOG_EXP2\nuniform float fogDensity;\n#else\nuniform float fogNear;\nuniform float fogFar;\n#endif\n#endif",fog_fragment:"#ifdef USE_FOG\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\n#ifdef FOG_EXP2\nconst float LOG2 = 1.442695;\nfloat fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\nfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\n#else\nfloat fogFactor = smoothstep( fogNear, fogFar, depth );\n#endif\ngl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\n#endif",
+envmap_pars_fragment:"#ifdef USE_ENVMAP\nuniform float reflectivity;\nuniform samplerCube envMap;\nuniform float flipEnvMap;\nuniform int combine;\n#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\nuniform bool useRefract;\nuniform float refractionRatio;\n#else\nvarying vec3 vReflect;\n#endif\n#endif",envmap_fragment:"#ifdef USE_ENVMAP\nvec3 reflectVec;\n#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP )\nvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\nif ( useRefract ) {\nreflectVec = refract( cameraToVertex, normal, refractionRatio );\n} else { \nreflectVec = reflect( cameraToVertex, normal );\n}\n#else\nreflectVec = vReflect;\n#endif\n#ifdef DOUBLE_SIDED\nfloat flipNormal = ( -1.0 + 2.0 * float( gl_FrontFacing ) );\nvec4 cubeColor = textureCube( envMap, flipNormal * vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n#else\nvec4 cubeColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n#endif\n#ifdef GAMMA_INPUT\ncubeColor.xyz *= cubeColor.xyz;\n#endif\nif ( combine == 1 ) {\ngl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularStrength * reflectivity );\n} else if ( combine == 2 ) {\ngl_FragColor.xyz += cubeColor.xyz * specularStrength * reflectivity;\n} else {\ngl_FragColor.xyz = mix( gl_FragColor.xyz, gl_FragColor.xyz * cubeColor.xyz, specularStrength * reflectivity );\n}\n#endif",
+envmap_pars_vertex:"#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )\nvarying vec3 vReflect;\nuniform float refractionRatio;\nuniform bool useRefract;\n#endif",worldpos_vertex:"#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )\n#ifdef USE_SKINNING\nvec4 worldPosition = modelMatrix * skinned;\n#endif\n#if defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )\nvec4 worldPosition = modelMatrix * vec4( morphed, 1.0 );\n#endif\n#if ! defined( USE_MORPHTARGETS ) && ! defined( USE_SKINNING )\nvec4 worldPosition = modelMatrix * vec4( position, 1.0 );\n#endif\n#endif",
+envmap_vertex:"#if defined( USE_ENVMAP ) && ! defined( USE_BUMPMAP ) && ! defined( USE_NORMALMAP )\nvec3 worldNormal = mat3( modelMatrix[ 0 ].xyz, modelMatrix[ 1 ].xyz, modelMatrix[ 2 ].xyz ) * objectNormal;\nworldNormal = normalize( worldNormal );\nvec3 cameraToVertex = normalize( worldPosition.xyz - cameraPosition );\nif ( useRefract ) {\nvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n} else {\nvReflect = reflect( cameraToVertex, worldNormal );\n}\n#endif",map_particle_pars_fragment:"#ifdef USE_MAP\nuniform sampler2D map;\n#endif",
+map_particle_fragment:"#ifdef USE_MAP\ngl_FragColor = gl_FragColor * texture2D( map, vec2( gl_PointCoord.x, 1.0 - gl_PointCoord.y ) );\n#endif",map_pars_vertex:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )\nvarying vec2 vUv;\nuniform vec4 offsetRepeat;\n#endif",map_pars_fragment:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )\nvarying vec2 vUv;\n#endif\n#ifdef USE_MAP\nuniform sampler2D map;\n#endif",
+map_vertex:"#if defined( USE_MAP ) || defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( USE_SPECULARMAP )\nvUv = uv * offsetRepeat.zw + offsetRepeat.xy;\n#endif",map_fragment:"#ifdef USE_MAP\nvec4 texelColor = texture2D( map, vUv );\n#ifdef GAMMA_INPUT\ntexelColor.xyz *= texelColor.xyz;\n#endif\ngl_FragColor = gl_FragColor * texelColor;\n#endif",lightmap_pars_fragment:"#ifdef USE_LIGHTMAP\nvarying vec2 vUv2;\nuniform sampler2D lightMap;\n#endif",lightmap_pars_vertex:"#ifdef USE_LIGHTMAP\nvarying vec2 vUv2;\n#endif",
+lightmap_fragment:"#ifdef USE_LIGHTMAP\ngl_FragColor = gl_FragColor * texture2D( lightMap, vUv2 );\n#endif",lightmap_vertex:"#ifdef USE_LIGHTMAP\nvUv2 = uv2;\n#endif",bumpmap_pars_fragment:"#ifdef USE_BUMPMAP\nuniform sampler2D bumpMap;\nuniform float bumpScale;\nvec2 dHdxy_fwd() {\nvec2 dSTdx = dFdx( vUv );\nvec2 dSTdy = dFdy( vUv );\nfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\nfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\nfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\nreturn vec2( dBx, dBy );\n}\nvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\nvec3 vSigmaX = dFdx( surf_pos );\nvec3 vSigmaY = dFdy( surf_pos );\nvec3 vN = surf_norm;\nvec3 R1 = cross( vSigmaY, vN );\nvec3 R2 = cross( vN, vSigmaX );\nfloat fDet = dot( vSigmaX, R1 );\nvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\nreturn normalize( abs( fDet ) * surf_norm - vGrad );\n}\n#endif",
+normalmap_pars_fragment:"#ifdef USE_NORMALMAP\nuniform sampler2D normalMap;\nuniform vec2 normalScale;\nvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm ) {\nvec3 q0 = dFdx( eye_pos.xyz );\nvec3 q1 = dFdy( eye_pos.xyz );\nvec2 st0 = dFdx( vUv.st );\nvec2 st1 = dFdy( vUv.st );\nvec3 S = normalize(  q0 * st1.t - q1 * st0.t );\nvec3 T = normalize( -q0 * st1.s + q1 * st0.s );\nvec3 N = normalize( surf_norm );\nvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\nmapN.xy = normalScale * mapN.xy;\nmat3 tsn = mat3( S, T, N );\nreturn normalize( tsn * mapN );\n}\n#endif",
+specularmap_pars_fragment:"#ifdef USE_SPECULARMAP\nuniform sampler2D specularMap;\n#endif",specularmap_fragment:"float specularStrength;\n#ifdef USE_SPECULARMAP\nvec4 texelSpecular = texture2D( specularMap, vUv );\nspecularStrength = texelSpecular.r;\n#else\nspecularStrength = 1.0;\n#endif",lights_lambert_pars_vertex:"uniform vec3 ambient;\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif",
+lights_lambert_vertex:"vLightFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\nvLightBack = vec3( 0.0 );\n#endif\ntransformedNormal = normalize( transformedNormal );\n#if MAX_DIR_LIGHTS > 0\nfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( transformedNormal, dirVector );\nvec3 directionalLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 directionalLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 directionalLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 directionalLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\ndirectionalLightWeighting = mix( directionalLightWeighting, directionalLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\ndirectionalLightWeightingBack = mix( directionalLightWeightingBack, directionalLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += directionalLightColor[ i ] * directionalLightWeighting;\n#ifdef DOUBLE_SIDED\nvLightBack += directionalLightColor[ i ] * directionalLightWeightingBack;\n#endif\n}\n#endif\n#if MAX_POINT_LIGHTS > 0\nfor( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat lDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\nfloat dotProduct = dot( transformedNormal, lVector );\nvec3 pointLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 pointLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 pointLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 pointLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\npointLightWeighting = mix( pointLightWeighting, pointLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\npointLightWeightingBack = mix( pointLightWeightingBack, pointLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += pointLightColor[ i ] * pointLightWeighting * lDistance;\n#ifdef DOUBLE_SIDED\nvLightBack += pointLightColor[ i ] * pointLightWeightingBack * lDistance;\n#endif\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nfor( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - worldPosition.xyz ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\nfloat lDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\nfloat dotProduct = dot( transformedNormal, lVector );\nvec3 spotLightWeighting = vec3( max( dotProduct, 0.0 ) );\n#ifdef DOUBLE_SIDED\nvec3 spotLightWeightingBack = vec3( max( -dotProduct, 0.0 ) );\n#ifdef WRAP_AROUND\nvec3 spotLightWeightingHalfBack = vec3( max( -0.5 * dotProduct + 0.5, 0.0 ) );\n#endif\n#endif\n#ifdef WRAP_AROUND\nvec3 spotLightWeightingHalf = vec3( max( 0.5 * dotProduct + 0.5, 0.0 ) );\nspotLightWeighting = mix( spotLightWeighting, spotLightWeightingHalf, wrapRGB );\n#ifdef DOUBLE_SIDED\nspotLightWeightingBack = mix( spotLightWeightingBack, spotLightWeightingHalfBack, wrapRGB );\n#endif\n#endif\nvLightFront += spotLightColor[ i ] * spotLightWeighting * lDistance * spotEffect;\n#ifdef DOUBLE_SIDED\nvLightBack += spotLightColor[ i ] * spotLightWeightingBack * lDistance * spotEffect;\n#endif\n}\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( transformedNormal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nfloat hemiDiffuseWeightBack = -0.5 * dotProduct + 0.5;\nvLightFront += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\n#ifdef DOUBLE_SIDED\nvLightBack += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeightBack );\n#endif\n}\n#endif\nvLightFront = vLightFront * diffuse + ambient * ambientLightColor + emissive;\n#ifdef DOUBLE_SIDED\nvLightBack = vLightBack * diffuse + ambient * ambientLightColor + emissive;\n#endif",
+lights_phong_pars_vertex:"#ifndef PHONG_PER_PIXEL\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\nvarying vec4 vPointLight[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\nvarying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];\n#endif\n#endif\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvarying vec3 vWorldPosition;\n#endif",
+lights_phong_vertex:"#ifndef PHONG_PER_PIXEL\n#if MAX_POINT_LIGHTS > 0\nfor( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat lDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\nvPointLight[ i ] = vec4( lVector, lDistance );\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nfor( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz - mvPosition.xyz;\nfloat lDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\nvSpotLight[ i ] = vec4( lVector, lDistance );\n}\n#endif\n#endif\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvWorldPosition = worldPosition.xyz;\n#endif",
+lights_phong_pars_fragment:"uniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\n#ifdef PHONG_PER_PIXEL\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#else\nvarying vec4 vPointLight[ MAX_POINT_LIGHTS ];\n#endif\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\n#ifdef PHONG_PER_PIXEL\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n#else\nvarying vec4 vSpotLight[ MAX_SPOT_LIGHTS ];\n#endif\n#endif\n#if MAX_SPOT_LIGHTS > 0 || defined( USE_BUMPMAP )\nvarying vec3 vWorldPosition;\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif\nvarying vec3 vViewPosition;\nvarying vec3 vNormal;",
+lights_phong_fragment:"vec3 normal = normalize( vNormal );\nvec3 viewPosition = normalize( vViewPosition );\n#ifdef DOUBLE_SIDED\nnormal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );\n#endif\n#ifdef USE_NORMALMAP\nnormal = perturbNormal2Arb( -vViewPosition, normal );\n#elif defined( USE_BUMPMAP )\nnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif\n#if MAX_POINT_LIGHTS > 0\nvec3 pointDiffuse  = vec3( 0.0 );\nvec3 pointSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\n#ifdef PHONG_PER_PIXEL\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz + vViewPosition.xyz;\nfloat lDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / pointLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\n#else\nvec3 lVector = normalize( vPointLight[ i ].xyz );\nfloat lDistance = vPointLight[ i ].w;\n#endif\nfloat dotProduct = dot( normal, lVector );\n#ifdef WRAP_AROUND\nfloat pointDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat pointDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\n#else\nfloat pointDiffuseWeight = max( dotProduct, 0.0 );\n#endif\npointDiffuse  += diffuse * pointLightColor[ i ] * pointDiffuseWeight * lDistance;\nvec3 pointHalfVector = normalize( lVector + viewPosition );\nfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\nfloat pointSpecularWeight = specularStrength * max( pow( pointDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, pointHalfVector ), 5.0 );\npointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * lDistance * specularNormalization;\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nvec3 spotDiffuse  = vec3( 0.0 );\nvec3 spotSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\n#ifdef PHONG_PER_PIXEL\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 lVector = lPosition.xyz + vViewPosition.xyz;\nfloat lDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nlDistance = 1.0 - min( ( length( lVector ) / spotLightDistance[ i ] ), 1.0 );\nlVector = normalize( lVector );\n#else\nvec3 lVector = normalize( vSpotLight[ i ].xyz );\nfloat lDistance = vSpotLight[ i ].w;\n#endif\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\nfloat dotProduct = dot( normal, lVector );\n#ifdef WRAP_AROUND\nfloat spotDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat spotDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\n#else\nfloat spotDiffuseWeight = max( dotProduct, 0.0 );\n#endif\nspotDiffuse += diffuse * spotLightColor[ i ] * spotDiffuseWeight * lDistance * spotEffect;\nvec3 spotHalfVector = normalize( lVector + viewPosition );\nfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\nfloat spotSpecularWeight = specularStrength * max( pow( spotDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, spotHalfVector ), 5.0 );\nspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * lDistance * specularNormalization * spotEffect;\n}\n}\n#endif\n#if MAX_DIR_LIGHTS > 0\nvec3 dirDiffuse  = vec3( 0.0 );\nvec3 dirSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_DIR_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, dirVector );\n#ifdef WRAP_AROUND\nfloat dirDiffuseWeightFull = max( dotProduct, 0.0 );\nfloat dirDiffuseWeightHalf = max( 0.5 * dotProduct + 0.5, 0.0 );\nvec3 dirDiffuseWeight = mix( vec3( dirDiffuseWeightFull ), vec3( dirDiffuseWeightHalf ), wrapRGB );\n#else\nfloat dirDiffuseWeight = max( dotProduct, 0.0 );\n#endif\ndirDiffuse  += diffuse * directionalLightColor[ i ] * dirDiffuseWeight;\nvec3 dirHalfVector = normalize( dirVector + viewPosition );\nfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\nfloat dirSpecularWeight = specularStrength * max( pow( dirDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );\ndirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nvec3 hemiDiffuse  = vec3( 0.0 );\nvec3 hemiSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\nhemiDiffuse += diffuse * hemiColor;\nvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\nfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\nfloat hemiSpecularWeightSky = specularStrength * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );\nvec3 lVectorGround = -lVector;\nvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\nfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\nfloat hemiSpecularWeightGround = specularStrength * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );\nfloat dotProductGround = dot( normal, lVectorGround );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );\nvec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );\nhemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\n}\n#endif\nvec3 totalDiffuse = vec3( 0.0 );\nvec3 totalSpecular = vec3( 0.0 );\n#if MAX_DIR_LIGHTS > 0\ntotalDiffuse += dirDiffuse;\ntotalSpecular += dirSpecular;\n#endif\n#if MAX_HEMI_LIGHTS > 0\ntotalDiffuse += hemiDiffuse;\ntotalSpecular += hemiSpecular;\n#endif\n#if MAX_POINT_LIGHTS > 0\ntotalDiffuse += pointDiffuse;\ntotalSpecular += pointSpecular;\n#endif\n#if MAX_SPOT_LIGHTS > 0\ntotalDiffuse += spotDiffuse;\ntotalSpecular += spotSpecular;\n#endif\n#ifdef METAL\ngl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient + totalSpecular );\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * ( emissive + totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\n#endif",
+color_pars_fragment:"#ifdef USE_COLOR\nvarying vec3 vColor;\n#endif",color_fragment:"#ifdef USE_COLOR\ngl_FragColor = gl_FragColor * vec4( vColor, 1.0 );\n#endif",color_pars_vertex:"#ifdef USE_COLOR\nvarying vec3 vColor;\n#endif",color_vertex:"#ifdef USE_COLOR\n#ifdef GAMMA_INPUT\nvColor = color * color;\n#else\nvColor = color;\n#endif\n#endif",skinning_pars_vertex:"#ifdef USE_SKINNING\n#ifdef BONE_TEXTURE\nuniform sampler2D boneTexture;\nuniform int boneTextureWidth;\nuniform int boneTextureHeight;\nmat4 getBoneMatrix( const in float i ) {\nfloat j = i * 4.0;\nfloat x = mod( j, float( boneTextureWidth ) );\nfloat y = floor( j / float( boneTextureWidth ) );\nfloat dx = 1.0 / float( boneTextureWidth );\nfloat dy = 1.0 / float( boneTextureHeight );\ny = dy * ( y + 0.5 );\nvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\nvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\nvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\nvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\nmat4 bone = mat4( v1, v2, v3, v4 );\nreturn bone;\n}\n#else\nuniform mat4 boneGlobalMatrices[ MAX_BONES ];\nmat4 getBoneMatrix( const in float i ) {\nmat4 bone = boneGlobalMatrices[ int(i) ];\nreturn bone;\n}\n#endif\n#endif",
+skinbase_vertex:"#ifdef USE_SKINNING\nmat4 boneMatX = getBoneMatrix( skinIndex.x );\nmat4 boneMatY = getBoneMatrix( skinIndex.y );\n#endif",skinning_vertex:"#ifdef USE_SKINNING\n#ifdef USE_MORPHTARGETS\nvec4 skinVertex = vec4( morphed, 1.0 );\n#else\nvec4 skinVertex = vec4( position, 1.0 );\n#endif\nvec4 skinned  = boneMatX * skinVertex * skinWeight.x;\nskinned \t  += boneMatY * skinVertex * skinWeight.y;\n#endif",morphtarget_pars_vertex:"#ifdef USE_MORPHTARGETS\n#ifndef USE_MORPHNORMALS\nuniform float morphTargetInfluences[ 8 ];\n#else\nuniform float morphTargetInfluences[ 4 ];\n#endif\n#endif",
+morphtarget_vertex:"#ifdef USE_MORPHTARGETS\nvec3 morphed = vec3( 0.0 );\nmorphed += ( morphTarget0 - position ) * morphTargetInfluences[ 0 ];\nmorphed += ( morphTarget1 - position ) * morphTargetInfluences[ 1 ];\nmorphed += ( morphTarget2 - position ) * morphTargetInfluences[ 2 ];\nmorphed += ( morphTarget3 - position ) * morphTargetInfluences[ 3 ];\n#ifndef USE_MORPHNORMALS\nmorphed += ( morphTarget4 - position ) * morphTargetInfluences[ 4 ];\nmorphed += ( morphTarget5 - position ) * morphTargetInfluences[ 5 ];\nmorphed += ( morphTarget6 - position ) * morphTargetInfluences[ 6 ];\nmorphed += ( morphTarget7 - position ) * morphTargetInfluences[ 7 ];\n#endif\nmorphed += position;\n#endif",
+default_vertex:"vec4 mvPosition;\n#ifdef USE_SKINNING\nmvPosition = modelViewMatrix * skinned;\n#endif\n#if !defined( USE_SKINNING ) && defined( USE_MORPHTARGETS )\nmvPosition = modelViewMatrix * vec4( morphed, 1.0 );\n#endif\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHTARGETS )\nmvPosition = modelViewMatrix * vec4( position, 1.0 );\n#endif\ngl_Position = projectionMatrix * mvPosition;",morphnormal_vertex:"#ifdef USE_MORPHNORMALS\nvec3 morphedNormal = vec3( 0.0 );\nmorphedNormal +=  ( morphNormal0 - normal ) * morphTargetInfluences[ 0 ];\nmorphedNormal +=  ( morphNormal1 - normal ) * morphTargetInfluences[ 1 ];\nmorphedNormal +=  ( morphNormal2 - normal ) * morphTargetInfluences[ 2 ];\nmorphedNormal +=  ( morphNormal3 - normal ) * morphTargetInfluences[ 3 ];\nmorphedNormal += normal;\n#endif",
+skinnormal_vertex:"#ifdef USE_SKINNING\nmat4 skinMatrix = skinWeight.x * boneMatX;\nskinMatrix \t+= skinWeight.y * boneMatY;\n#ifdef USE_MORPHNORMALS\nvec4 skinnedNormal = skinMatrix * vec4( morphedNormal, 0.0 );\n#else\nvec4 skinnedNormal = skinMatrix * vec4( normal, 0.0 );\n#endif\n#endif",defaultnormal_vertex:"vec3 objectNormal;\n#ifdef USE_SKINNING\nobjectNormal = skinnedNormal.xyz;\n#endif\n#if !defined( USE_SKINNING ) && defined( USE_MORPHNORMALS )\nobjectNormal = morphedNormal;\n#endif\n#if !defined( USE_SKINNING ) && ! defined( USE_MORPHNORMALS )\nobjectNormal = normal;\n#endif\n#ifdef FLIP_SIDED\nobjectNormal = -objectNormal;\n#endif\nvec3 transformedNormal = normalMatrix * objectNormal;",
+shadowmap_pars_fragment:"#ifdef USE_SHADOWMAP\nuniform sampler2D shadowMap[ MAX_SHADOWS ];\nuniform vec2 shadowMapSize[ MAX_SHADOWS ];\nuniform float shadowDarkness[ MAX_SHADOWS ];\nuniform float shadowBias[ MAX_SHADOWS ];\nvarying vec4 vShadowCoord[ MAX_SHADOWS ];\nfloat unpackDepth( const in vec4 rgba_depth ) {\nconst vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );\nfloat depth = dot( rgba_depth, bit_shift );\nreturn depth;\n}\n#endif",shadowmap_fragment:"#ifdef USE_SHADOWMAP\n#ifdef SHADOWMAP_DEBUG\nvec3 frustumColors[3];\nfrustumColors[0] = vec3( 1.0, 0.5, 0.0 );\nfrustumColors[1] = vec3( 0.0, 1.0, 0.8 );\nfrustumColors[2] = vec3( 0.0, 0.5, 1.0 );\n#endif\n#ifdef SHADOWMAP_CASCADE\nint inFrustumCount = 0;\n#endif\nfloat fDepth;\nvec3 shadowColor = vec3( 1.0 );\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvec3 shadowCoord = vShadowCoord[ i ].xyz / vShadowCoord[ i ].w;\nbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\nbool inFrustum = all( inFrustumVec );\n#ifdef SHADOWMAP_CASCADE\ninFrustumCount += int( inFrustum );\nbvec3 frustumTestVec = bvec3( inFrustum, inFrustumCount == 1, shadowCoord.z <= 1.0 );\n#else\nbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n#endif\nbool frustumTest = all( frustumTestVec );\nif ( frustumTest ) {\nshadowCoord.z += shadowBias[ i ];\n#if defined( SHADOWMAP_TYPE_PCF )\nfloat shadow = 0.0;\nconst float shadowDelta = 1.0 / 9.0;\nfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\nfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\nfloat dx0 = -1.25 * xPixelOffset;\nfloat dy0 = -1.25 * yPixelOffset;\nfloat dx1 = 1.25 * xPixelOffset;\nfloat dy1 = 1.25 * yPixelOffset;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nfDepth = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\nif ( fDepth < shadowCoord.z ) shadow += shadowDelta;\nshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\nfloat shadow = 0.0;\nfloat xPixelOffset = 1.0 / shadowMapSize[ i ].x;\nfloat yPixelOffset = 1.0 / shadowMapSize[ i ].y;\nfloat dx0 = -1.0 * xPixelOffset;\nfloat dy0 = -1.0 * yPixelOffset;\nfloat dx1 = 1.0 * xPixelOffset;\nfloat dy1 = 1.0 * yPixelOffset;\nmat3 shadowKernel;\nmat3 depthKernel;\ndepthKernel[0][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy0 ) ) );\ndepthKernel[0][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, 0.0 ) ) );\ndepthKernel[0][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx0, dy1 ) ) );\ndepthKernel[1][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy0 ) ) );\ndepthKernel[1][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy ) );\ndepthKernel[1][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( 0.0, dy1 ) ) );\ndepthKernel[2][0] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy0 ) ) );\ndepthKernel[2][1] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, 0.0 ) ) );\ndepthKernel[2][2] = unpackDepth( texture2D( shadowMap[ i ], shadowCoord.xy + vec2( dx1, dy1 ) ) );\nvec3 shadowZ = vec3( shadowCoord.z );\nshadowKernel[0] = vec3(lessThan(depthKernel[0], shadowZ ));\nshadowKernel[0] *= vec3(0.25);\nshadowKernel[1] = vec3(lessThan(depthKernel[1], shadowZ ));\nshadowKernel[1] *= vec3(0.25);\nshadowKernel[2] = vec3(lessThan(depthKernel[2], shadowZ ));\nshadowKernel[2] *= vec3(0.25);\nvec2 fractionalCoord = 1.0 - fract( shadowCoord.xy * shadowMapSize[i].xy );\nshadowKernel[0] = mix( shadowKernel[1], shadowKernel[0], fractionalCoord.x );\nshadowKernel[1] = mix( shadowKernel[2], shadowKernel[1], fractionalCoord.x );\nvec4 shadowValues;\nshadowValues.x = mix( shadowKernel[0][1], shadowKernel[0][0], fractionalCoord.y );\nshadowValues.y = mix( shadowKernel[0][2], shadowKernel[0][1], fractionalCoord.y );\nshadowValues.z = mix( shadowKernel[1][1], shadowKernel[1][0], fractionalCoord.y );\nshadowValues.w = mix( shadowKernel[1][2], shadowKernel[1][1], fractionalCoord.y );\nshadow = dot( shadowValues, vec4( 1.0 ) );\nshadowColor = shadowColor * vec3( ( 1.0 - shadowDarkness[ i ] * shadow ) );\n#else\nvec4 rgbaDepth = texture2D( shadowMap[ i ], shadowCoord.xy );\nfloat fDepth = unpackDepth( rgbaDepth );\nif ( fDepth < shadowCoord.z )\nshadowColor = shadowColor * vec3( 1.0 - shadowDarkness[ i ] );\n#endif\n}\n#ifdef SHADOWMAP_DEBUG\n#ifdef SHADOWMAP_CASCADE\nif ( inFrustum && inFrustumCount == 1 ) gl_FragColor.xyz *= frustumColors[ i ];\n#else\nif ( inFrustum ) gl_FragColor.xyz *= frustumColors[ i ];\n#endif\n#endif\n}\n#ifdef GAMMA_OUTPUT\nshadowColor *= shadowColor;\n#endif\ngl_FragColor.xyz = gl_FragColor.xyz * shadowColor;\n#endif",
+shadowmap_pars_vertex:"#ifdef USE_SHADOWMAP\nvarying vec4 vShadowCoord[ MAX_SHADOWS ];\nuniform mat4 shadowMatrix[ MAX_SHADOWS ];\n#endif",shadowmap_vertex:"#ifdef USE_SHADOWMAP\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n}\n#endif",alphatest_fragment:"#ifdef ALPHATEST\nif ( gl_FragColor.a < ALPHATEST ) discard;\n#endif",linear_to_gamma_fragment:"#ifdef GAMMA_OUTPUT\ngl_FragColor.xyz = sqrt( gl_FragColor.xyz );\n#endif"};THREE.UniformsUtils={merge:function(a){var b,c,d,e={};for(b=0;b<a.length;b++)for(c in d=this.clone(a[b]),d)e[c]=d[c];return e},clone:function(a){var b,c,d,e={};for(b in a)for(c in e[b]={},a[b])d=a[b][c],e[b][c]=d instanceof THREE.Color||d instanceof THREE.Vector2||d instanceof THREE.Vector3||d instanceof THREE.Vector4||d instanceof THREE.Matrix4||d instanceof THREE.Texture?d.clone():d instanceof Array?d.slice():d;return e}};THREE.UniformsLib={common:{diffuse:{type:"c",value:new THREE.Color(15658734)},opacity:{type:"f",value:1},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},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:2.5E-4},fogNear:{type:"f",value:1},fogFar:{type:"f",value:2E3},fogColor:{type:"c",value:new THREE.Color(16777215)}},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(15658734)},opacity:{type:"f",value:1},size:{type:"f",
+value:1},scale:{type:"f",value:1},map:{type:"t",value:null},fogDensity:{type:"f",value:2.5E-4},fogNear:{type:"f",value:1},fogFar:{type:"f",value:2E3},fogColor:{type:"c",value:new THREE.Color(16777215)}},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;\nuniform 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() {\ngl_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(16777215)},emissive:{type:"c",value:new THREE.Color(0)},wrapRGB:{type:"v3",value:new THREE.Vector3(1,1,1)}}]),vertexShader:["#define LAMBERT\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\nvarying vec3 vLightBack;\n#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;\nvarying vec3 vLightFront;\n#ifdef DOUBLE_SIDED\nvarying vec3 vLightBack;\n#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() {\ngl_FragColor = vec4( vec3 ( 1.0 ), opacity );",THREE.ShaderChunk.map_fragment,THREE.ShaderChunk.alphatest_fragment,THREE.ShaderChunk.specularmap_fragment,"#ifdef DOUBLE_SIDED\nif ( gl_FrontFacing )\ngl_FragColor.xyz *= vLightFront;\nelse\ngl_FragColor.xyz *= vLightBack;\n#else\ngl_FragColor.xyz *= vLightFront;\n#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(16777215)},emissive:{type:"c",value:new THREE.Color(0)},specular:{type:"c",
+value:new THREE.Color(1118481)},shininess:{type:"f",value:30},wrapRGB:{type:"v3",value:new THREE.Vector3(1,1,1)}}]),vertexShader:["#define PHONG\nvarying vec3 vViewPosition;\nvarying 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;\nuniform float opacity;\nuniform vec3 ambient;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform 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() {\ngl_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;\nuniform 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 );\n#ifdef USE_SIZEATTENUATION\ngl_PointSize = size * ( scale / length( mvPosition.xyz ) );\n#else\ngl_PointSize = size;\n#endif\ngl_Position = projectionMatrix * mvPosition;",
+THREE.ShaderChunk.worldpos_vertex,THREE.ShaderChunk.shadowmap_vertex,"}"].join("\n"),fragmentShader:["uniform vec3 psColor;\nuniform 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() {\ngl_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;\nattribute float lineDistance;\nvarying float vLineDistance;",THREE.ShaderChunk.color_pars_vertex,"void main() {",THREE.ShaderChunk.color_vertex,"vLineDistance = scale * lineDistance;\nvec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );\ngl_Position = projectionMatrix * mvPosition;\n}"].join("\n"),
+fragmentShader:["uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;",THREE.ShaderChunk.color_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,"void main() {\nif ( mod( vLineDistance, totalSize ) > dashSize ) {\ndiscard;\n}\ngl_FragColor = vec4( diffuse, opacity );",THREE.ShaderChunk.color_fragment,THREE.ShaderChunk.fog_fragment,"}"].join("\n")},depth:{uniforms:{mNear:{type:"f",value:1},mFar:{type:"f",value:2E3},opacity:{type:"f",
+value:1}},vertexShader:"void main() {\ngl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}",fragmentShader:"uniform float mNear;\nuniform float mFar;\nuniform float opacity;\nvoid main() {\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\nfloat color = 1.0 - smoothstep( mNear, mFar, depth );\ngl_FragColor = vec4( vec3( color ), opacity );\n}"},normal:{uniforms:{opacity:{type:"f",value:1}},vertexShader:["varying vec3 vNormal;",THREE.ShaderChunk.morphtarget_pars_vertex,"void main() {\nvNormal = normalize( normalMatrix * normal );",
+THREE.ShaderChunk.morphtarget_vertex,THREE.ShaderChunk.default_vertex,"}"].join("\n"),fragmentShader:"uniform float opacity;\nvarying vec3 vNormal;\nvoid main() {\ngl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );\n}"},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},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},uDisplacementScale:{type:"f",value:1},diffuse:{type:"c",value:new THREE.Color(16777215)},specular:{type:"c",value:new THREE.Color(1118481)},ambient:{type:"c",value:new THREE.Color(16777215)},shininess:{type:"f",value:30},
+opacity:{type:"f",value:1},useRefract:{type:"i",value:0},refractionRatio:{type:"f",value:0.98},reflectivity:{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 ambient;\nuniform vec3 diffuse;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\nuniform bool enableDiffuse;\nuniform bool enableSpecular;\nuniform bool enableAO;\nuniform bool enableReflection;\nuniform sampler2D tDiffuse;\nuniform sampler2D tNormal;\nuniform sampler2D tSpecular;\nuniform sampler2D tAO;\nuniform samplerCube tCube;\nuniform vec2 uNormalScale;\nuniform bool useRefract;\nuniform float refractionRatio;\nuniform float reflectivity;\nvarying vec3 vTangent;\nvarying vec3 vBinormal;\nvarying vec3 vNormal;\nvarying vec2 vUv;\nuniform vec3 ambientLightColor;\n#if MAX_DIR_LIGHTS > 0\nuniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];\nuniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];\n#endif\n#if MAX_HEMI_LIGHTS > 0\nuniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];\nuniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];\n#endif\n#if MAX_POINT_LIGHTS > 0\nuniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];\nuniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];\nuniform float pointLightDistance[ MAX_POINT_LIGHTS ];\n#endif\n#if MAX_SPOT_LIGHTS > 0\nuniform vec3 spotLightColor[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightPosition[ MAX_SPOT_LIGHTS ];\nuniform vec3 spotLightDirection[ MAX_SPOT_LIGHTS ];\nuniform float spotLightAngleCos[ MAX_SPOT_LIGHTS ];\nuniform float spotLightExponent[ MAX_SPOT_LIGHTS ];\nuniform float spotLightDistance[ MAX_SPOT_LIGHTS ];\n#endif\n#ifdef WRAP_AROUND\nuniform vec3 wrapRGB;\n#endif\nvarying vec3 vWorldPosition;\nvarying vec3 vViewPosition;",
+THREE.ShaderChunk.shadowmap_pars_fragment,THREE.ShaderChunk.fog_pars_fragment,"void main() {\ngl_FragColor = vec4( vec3( 1.0 ), opacity );\nvec3 specularTex = vec3( 1.0 );\nvec3 normalTex = texture2D( tNormal, vUv ).xyz * 2.0 - 1.0;\nnormalTex.xy *= uNormalScale;\nnormalTex = normalize( normalTex );\nif( enableDiffuse ) {\n#ifdef GAMMA_INPUT\nvec4 texelColor = texture2D( tDiffuse, vUv );\ntexelColor.xyz *= texelColor.xyz;\ngl_FragColor = gl_FragColor * texelColor;\n#else\ngl_FragColor = gl_FragColor * texture2D( tDiffuse, vUv );\n#endif\n}\nif( enableAO ) {\n#ifdef GAMMA_INPUT\nvec4 aoColor = texture2D( tAO, vUv );\naoColor.xyz *= aoColor.xyz;\ngl_FragColor.xyz = gl_FragColor.xyz * aoColor.xyz;\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * texture2D( tAO, vUv ).xyz;\n#endif\n}\nif( enableSpecular )\nspecularTex = texture2D( tSpecular, vUv ).xyz;\nmat3 tsb = mat3( normalize( vTangent ), normalize( vBinormal ), normalize( vNormal ) );\nvec3 finalNormal = tsb * normalTex;\n#ifdef FLIP_SIDED\nfinalNormal = -finalNormal;\n#endif\nvec3 normal = normalize( finalNormal );\nvec3 viewPosition = normalize( vViewPosition );\n#if MAX_POINT_LIGHTS > 0\nvec3 pointDiffuse = vec3( 0.0 );\nvec3 pointSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( pointLightPosition[ i ], 1.0 );\nvec3 pointVector = lPosition.xyz + vViewPosition.xyz;\nfloat pointDistance = 1.0;\nif ( pointLightDistance[ i ] > 0.0 )\npointDistance = 1.0 - min( ( length( pointVector ) / pointLightDistance[ i ] ), 1.0 );\npointVector = normalize( pointVector );\n#ifdef WRAP_AROUND\nfloat pointDiffuseWeightFull = max( dot( normal, pointVector ), 0.0 );\nfloat pointDiffuseWeightHalf = max( 0.5 * dot( normal, pointVector ) + 0.5, 0.0 );\nvec3 pointDiffuseWeight = mix( vec3 ( pointDiffuseWeightFull ), vec3( pointDiffuseWeightHalf ), wrapRGB );\n#else\nfloat pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );\n#endif\npointDiffuse += pointDistance * pointLightColor[ i ] * diffuse * pointDiffuseWeight;\nvec3 pointHalfVector = normalize( pointVector + viewPosition );\nfloat pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );\nfloat pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( pointVector, pointHalfVector ), 5.0 );\npointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;\n}\n#endif\n#if MAX_SPOT_LIGHTS > 0\nvec3 spotDiffuse = vec3( 0.0 );\nvec3 spotSpecular = vec3( 0.0 );\nfor ( int i = 0; i < MAX_SPOT_LIGHTS; i ++ ) {\nvec4 lPosition = viewMatrix * vec4( spotLightPosition[ i ], 1.0 );\nvec3 spotVector = lPosition.xyz + vViewPosition.xyz;\nfloat spotDistance = 1.0;\nif ( spotLightDistance[ i ] > 0.0 )\nspotDistance = 1.0 - min( ( length( spotVector ) / spotLightDistance[ i ] ), 1.0 );\nspotVector = normalize( spotVector );\nfloat spotEffect = dot( spotLightDirection[ i ], normalize( spotLightPosition[ i ] - vWorldPosition ) );\nif ( spotEffect > spotLightAngleCos[ i ] ) {\nspotEffect = max( pow( spotEffect, spotLightExponent[ i ] ), 0.0 );\n#ifdef WRAP_AROUND\nfloat spotDiffuseWeightFull = max( dot( normal, spotVector ), 0.0 );\nfloat spotDiffuseWeightHalf = max( 0.5 * dot( normal, spotVector ) + 0.5, 0.0 );\nvec3 spotDiffuseWeight = mix( vec3 ( spotDiffuseWeightFull ), vec3( spotDiffuseWeightHalf ), wrapRGB );\n#else\nfloat spotDiffuseWeight = max( dot( normal, spotVector ), 0.0 );\n#endif\nspotDiffuse += spotDistance * spotLightColor[ i ] * diffuse * spotDiffuseWeight * spotEffect;\nvec3 spotHalfVector = normalize( spotVector + viewPosition );\nfloat spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );\nfloat spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( spotVector, spotHalfVector ), 5.0 );\nspotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;\n}\n}\n#endif\n#if MAX_DIR_LIGHTS > 0\nvec3 dirDiffuse = vec3( 0.0 );\nvec3 dirSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {\nvec4 lDirection = viewMatrix * vec4( directionalLightDirection[ i ], 0.0 );\nvec3 dirVector = normalize( lDirection.xyz );\n#ifdef WRAP_AROUND\nfloat directionalLightWeightingFull = max( dot( normal, dirVector ), 0.0 );\nfloat directionalLightWeightingHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );\nvec3 dirDiffuseWeight = mix( vec3( directionalLightWeightingFull ), vec3( directionalLightWeightingHalf ), wrapRGB );\n#else\nfloat dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );\n#endif\ndirDiffuse += directionalLightColor[ i ] * diffuse * dirDiffuseWeight;\nvec3 dirHalfVector = normalize( dirVector + viewPosition );\nfloat dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );\nfloat dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlick = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( dirVector, dirHalfVector ), 5.0 );\ndirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;\n}\n#endif\n#if MAX_HEMI_LIGHTS > 0\nvec3 hemiDiffuse  = vec3( 0.0 );\nvec3 hemiSpecular = vec3( 0.0 );\nfor( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {\nvec4 lDirection = viewMatrix * vec4( hemisphereLightDirection[ i ], 0.0 );\nvec3 lVector = normalize( lDirection.xyz );\nfloat dotProduct = dot( normal, lVector );\nfloat hemiDiffuseWeight = 0.5 * dotProduct + 0.5;\nvec3 hemiColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );\nhemiDiffuse += diffuse * hemiColor;\nvec3 hemiHalfVectorSky = normalize( lVector + viewPosition );\nfloat hemiDotNormalHalfSky = 0.5 * dot( normal, hemiHalfVectorSky ) + 0.5;\nfloat hemiSpecularWeightSky = specularTex.r * max( pow( hemiDotNormalHalfSky, shininess ), 0.0 );\nvec3 lVectorGround = -lVector;\nvec3 hemiHalfVectorGround = normalize( lVectorGround + viewPosition );\nfloat hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;\nfloat hemiSpecularWeightGround = specularTex.r * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );\nfloat dotProductGround = dot( normal, lVectorGround );\nfloat specularNormalization = ( shininess + 2.0001 ) / 8.0;\nvec3 schlickSky = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVector, hemiHalfVectorSky ), 5.0 );\nvec3 schlickGround = specular + vec3( 1.0 - specular ) * pow( 1.0 - dot( lVectorGround, hemiHalfVectorGround ), 5.0 );\nhemiSpecular += hemiColor * specularNormalization * ( schlickSky * hemiSpecularWeightSky * max( dotProduct, 0.0 ) + schlickGround * hemiSpecularWeightGround * max( dotProductGround, 0.0 ) );\n}\n#endif\nvec3 totalDiffuse = vec3( 0.0 );\nvec3 totalSpecular = vec3( 0.0 );\n#if MAX_DIR_LIGHTS > 0\ntotalDiffuse += dirDiffuse;\ntotalSpecular += dirSpecular;\n#endif\n#if MAX_HEMI_LIGHTS > 0\ntotalDiffuse += hemiDiffuse;\ntotalSpecular += hemiSpecular;\n#endif\n#if MAX_POINT_LIGHTS > 0\ntotalDiffuse += pointDiffuse;\ntotalSpecular += pointSpecular;\n#endif\n#if MAX_SPOT_LIGHTS > 0\ntotalDiffuse += spotDiffuse;\ntotalSpecular += spotSpecular;\n#endif\n#ifdef METAL\ngl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient + totalSpecular );\n#else\ngl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient ) + totalSpecular;\n#endif\nif ( enableReflection ) {\nvec3 vReflect;\nvec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );\nif ( useRefract ) {\nvReflect = refract( cameraToVertex, normal, refractionRatio );\n} else {\nvReflect = reflect( cameraToVertex, normal );\n}\nvec4 cubeColor = textureCube( tCube, vec3( -vReflect.x, vReflect.yz ) );\n#ifdef GAMMA_INPUT\ncubeColor.xyz *= cubeColor.xyz;\n#endif\ngl_FragColor.xyz = mix( gl_FragColor.xyz, cubeColor.xyz, specularTex.r * reflectivity );\n}",
+THREE.ShaderChunk.shadowmap_fragment,THREE.ShaderChunk.linear_to_gamma_fragment,THREE.ShaderChunk.fog_fragment,"}"].join("\n"),vertexShader:["attribute vec4 tangent;\nuniform vec2 uOffset;\nuniform vec2 uRepeat;\nuniform bool enableDisplacement;\n#ifdef VERTEX_TEXTURES\nuniform sampler2D tDisplacement;\nuniform float uDisplacementScale;\nuniform float uDisplacementBias;\n#endif\nvarying vec3 vTangent;\nvarying vec3 vBinormal;\nvarying vec3 vNormal;\nvarying vec2 vUv;\nvarying vec3 vWorldPosition;\nvarying vec3 vViewPosition;",
+THREE.ShaderChunk.skinning_pars_vertex,THREE.ShaderChunk.shadowmap_pars_vertex,"void main() {",THREE.ShaderChunk.skinbase_vertex,THREE.ShaderChunk.skinnormal_vertex,"#ifdef USE_SKINNING\nvNormal = normalize( normalMatrix * skinnedNormal.xyz );\nvec4 skinnedTangent = skinMatrix * vec4( tangent.xyz, 0.0 );\nvTangent = normalize( normalMatrix * skinnedTangent.xyz );\n#else\nvNormal = normalize( normalMatrix * normal );\nvTangent = normalize( normalMatrix * tangent.xyz );\n#endif\nvBinormal = normalize( cross( vNormal, vTangent ) * tangent.w );\nvUv = uv * uRepeat + uOffset;\nvec3 displacedPosition;\n#ifdef VERTEX_TEXTURES\nif ( enableDisplacement ) {\nvec3 dv = texture2D( tDisplacement, uv ).xyz;\nfloat df = uDisplacementScale * dv.x + uDisplacementBias;\ndisplacedPosition = position + normalize( normal ) * df;\n} else {\n#ifdef USE_SKINNING\nvec4 skinVertex = vec4( position, 1.0 );\nvec4 skinned  = boneMatX * skinVertex * skinWeight.x;\nskinned \t  += boneMatY * skinVertex * skinWeight.y;\ndisplacedPosition  = skinned.xyz;\n#else\ndisplacedPosition = position;\n#endif\n}\n#else\n#ifdef USE_SKINNING\nvec4 skinVertex = vec4( position, 1.0 );\nvec4 skinned  = boneMatX * skinVertex * skinWeight.x;\nskinned \t  += boneMatY * skinVertex * skinWeight.y;\ndisplacedPosition  = skinned.xyz;\n#else\ndisplacedPosition = position;\n#endif\n#endif\nvec4 mvPosition = modelViewMatrix * vec4( displacedPosition, 1.0 );\nvec4 worldPosition = modelMatrix * vec4( displacedPosition, 1.0 );\ngl_Position = projectionMatrix * mvPosition;\nvWorldPosition = worldPosition.xyz;\nvViewPosition = -mvPosition.xyz;\n#ifdef USE_SHADOWMAP\nfor( int i = 0; i < MAX_SHADOWS; i ++ ) {\nvShadowCoord[ i ] = shadowMatrix[ i ] * worldPosition;\n}\n#endif\n}"].join("\n")},
+cube:{uniforms:{tCube:{type:"t",value:null},tFlip:{type:"f",value:-1}},vertexShader:"varying vec3 vWorldPosition;\nvoid main() {\nvec4 worldPosition = modelMatrix * vec4( position, 1.0 );\nvWorldPosition = worldPosition.xyz;\ngl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}",fragmentShader:"uniform samplerCube tCube;\nuniform float tFlip;\nvarying vec3 vWorldPosition;\nvoid main() {\ngl_FragColor = textureCube( tCube, vec3( tFlip * vWorldPosition.x, vWorldPosition.yz ) );\n}"},
+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 ) {\nconst vec4 bit_shift = vec4( 256.0 * 256.0 * 256.0, 256.0 * 256.0, 256.0, 1.0 );\nconst vec4 bit_mask  = vec4( 0.0, 1.0 / 256.0, 1.0 / 256.0, 1.0 / 256.0 );\nvec4 res = fract( depth * bit_shift );\nres -= res.xxyz * bit_mask;\nreturn res;\n}\nvoid main() {\ngl_FragData[ 0 ] = pack_depth( gl_FragCoord.z );\n}"}};THREE.WebGLRenderer=function(a){function b(a,b){var c=a.vertices.length,d=b.material;if(d.attributes){void 0===a.__webglCustomAttributesList&&(a.__webglCustomAttributesList=[]);for(var e in d.attributes){var f=d.attributes[e];if(!f.__webglInitialized||f.createUniqueBuffers){f.__webglInitialized=!0;var g=1;"v2"===f.type?g=2:"v3"===f.type?g=3:"v4"===f.type?g=4:"c"===f.type&&(g=3);f.size=g;f.array=new Float32Array(c*g);f.buffer=j.createBuffer();f.buffer.belongsToAttribute=e;f.needsUpdate=!0}a.__webglCustomAttributesList.push(f)}}}
+function c(a,b){var c=b.geometry,g=a.faces3,h=3*g.length,i=1*g.length,k=3*g.length,g=d(b,a),l=f(g),m=e(g),n=g.vertexColors?g.vertexColors:!1;a.__vertexArray=new Float32Array(3*h);m&&(a.__normalArray=new Float32Array(3*h));c.hasTangents&&(a.__tangentArray=new Float32Array(4*h));n&&(a.__colorArray=new Float32Array(3*h));l&&(0<c.faceVertexUvs.length&&(a.__uvArray=new Float32Array(2*h)),1<c.faceVertexUvs.length&&(a.__uv2Array=new Float32Array(2*h)));b.geometry.skinWeights.length&&b.geometry.skinIndices.length&&
+(a.__skinIndexArray=new Float32Array(4*h),a.__skinWeightArray=new Float32Array(4*h));a.__faceArray=new Uint16Array(3*i);a.__lineArray=new Uint16Array(2*k);if(a.numMorphTargets){a.__morphTargetsArrays=[];c=0;for(l=a.numMorphTargets;c<l;c++)a.__morphTargetsArrays.push(new Float32Array(3*h))}if(a.numMorphNormals){a.__morphNormalsArrays=[];c=0;for(l=a.numMorphNormals;c<l;c++)a.__morphNormalsArrays.push(new Float32Array(3*h))}a.__webglFaceCount=3*i;a.__webglLineCount=2*k;if(g.attributes){void 0===a.__webglCustomAttributesList&&
+(a.__webglCustomAttributesList=[]);for(var p in g.attributes){var i=g.attributes[p],k={},q;for(q in i)k[q]=i[q];if(!k.__webglInitialized||k.createUniqueBuffers)k.__webglInitialized=!0,c=1,"v2"===k.type?c=2:"v3"===k.type?c=3:"v4"===k.type?c=4:"c"===k.type&&(c=3),k.size=c,k.array=new Float32Array(h*c),k.buffer=j.createBuffer(),k.buffer.belongsToAttribute=p,i.needsUpdate=!0,k.__original=i;a.__webglCustomAttributesList.push(k)}}a.__inittedArrays=!0}function d(a,b){return a.material instanceof THREE.MeshFaceMaterial?
+a.material.materials[b.materialIndex]:a.material}function e(a){return a instanceof THREE.MeshBasicMaterial&&!a.envMap||a instanceof THREE.MeshDepthMaterial?!1:a&&void 0!==a.shading&&a.shading===THREE.SmoothShading?THREE.SmoothShading:THREE.FlatShading}function f(a){return a.map||a.lightMap||a.bumpMap||a.normalMap||a.specularMap||a instanceof THREE.ShaderMaterial?!0:!1}function g(a){Ma[a]||(j.enableVertexAttribArray(a),Ma[a]=!0)}function h(){for(var a in Ma)Ma[a]&&(j.disableVertexAttribArray(a),Ma[a]=
+!1)}function i(a,b){return a.z!==b.z?b.z-a.z:a.id-b.id}function k(a,b){return b[0]-a[0]}function l(a,b,c){if(a.length)for(var d=0,e=a.length;d<e;d++)Ha=oa=null,Ea=za=Aa=sa=wa=ca=ba=-1,Za=!0,a[d].render(b,c,Va,La),Ha=oa=null,Ea=za=Aa=sa=wa=ca=ba=-1,Za=!0}function n(a,b,c,d,e,f,g,h){var j,i,k,l;b?(i=a.length-1,l=b=-1):(i=0,b=a.length,l=1);for(var m=i;m!==b;m+=l)if(j=a[m],j.render){i=j.object;k=j.buffer;if(h)j=h;else{j=j[c];if(!j)continue;g&&I.setBlending(j.blending,j.blendEquation,j.blendSrc,j.blendDst);
+I.setDepthTest(j.depthTest);I.setDepthWrite(j.depthWrite);x(j.polygonOffset,j.polygonOffsetFactor,j.polygonOffsetUnits)}I.setMaterialFaces(j);k instanceof THREE.BufferGeometry?I.renderBufferDirect(d,e,f,j,k,i):I.renderBuffer(d,e,f,j,k,i)}}function p(a,b,c,d,e,f,g){for(var h,j,i=0,k=a.length;i<k;i++)if(h=a[i],j=h.object,j.visible){if(g)h=g;else{h=h[b];if(!h)continue;f&&I.setBlending(h.blending,h.blendEquation,h.blendSrc,h.blendDst);I.setDepthTest(h.depthTest);I.setDepthWrite(h.depthWrite);x(h.polygonOffset,
+h.polygonOffsetFactor,h.polygonOffsetUnits)}I.renderImmediateObject(c,d,e,h,j)}}function t(a,d){var e,f,g,h;if(void 0===a.__webglInit&&(a.__webglInit=!0,a._modelViewMatrix=new THREE.Matrix4,a._normalMatrix=new THREE.Matrix3,void 0!==a.geometry&&void 0===a.geometry.__webglInit&&(a.geometry.__webglInit=!0,a.geometry.addEventListener("dispose",Lb)),f=a.geometry,void 0!==f))if(f instanceof THREE.BufferGeometry){var i,k;for(i in f.attributes)k="index"===i?j.ELEMENT_ARRAY_BUFFER:j.ARRAY_BUFFER,h=f.attributes[i],
+void 0===h.numItems&&(h.numItems=h.array.length),h.buffer=j.createBuffer(),j.bindBuffer(k,h.buffer),j.bufferData(k,h.array,j.STATIC_DRAW)}else if(a instanceof THREE.Mesh){g=a.material;if(void 0===f.geometryGroups){i=f;var l,m,n;k={};var p=i.morphTargets.length,q=i.morphNormals.length,t=g instanceof THREE.MeshFaceMaterial;i.geometryGroups={};g=0;for(l=i.faces.length;g<l;g++)m=i.faces[g],m=t?m.materialIndex:0,void 0===k[m]&&(k[m]={hash:m,counter:0}),n=k[m].hash+"_"+k[m].counter,void 0===i.geometryGroups[n]&&
+(i.geometryGroups[n]={faces3:[],materialIndex:m,vertices:0,numMorphTargets:p,numMorphNormals:q}),65535<i.geometryGroups[n].vertices+3&&(k[m].counter+=1,n=k[m].hash+"_"+k[m].counter,void 0===i.geometryGroups[n]&&(i.geometryGroups[n]={faces3:[],materialIndex:m,vertices:0,numMorphTargets:p,numMorphNormals:q})),i.geometryGroups[n].faces3.push(g),i.geometryGroups[n].vertices+=3;i.geometryGroupsList=[];for(h in i.geometryGroups)i.geometryGroups[h].id=Ta++,i.geometryGroupsList.push(i.geometryGroups[h])}for(e in f.geometryGroups)if(h=
+f.geometryGroups[e],!h.__webglVertexBuffer){i=h;i.__webglVertexBuffer=j.createBuffer();i.__webglNormalBuffer=j.createBuffer();i.__webglTangentBuffer=j.createBuffer();i.__webglColorBuffer=j.createBuffer();i.__webglUVBuffer=j.createBuffer();i.__webglUV2Buffer=j.createBuffer();i.__webglSkinIndicesBuffer=j.createBuffer();i.__webglSkinWeightsBuffer=j.createBuffer();i.__webglFaceBuffer=j.createBuffer();i.__webglLineBuffer=j.createBuffer();p=k=void 0;if(i.numMorphTargets){i.__webglMorphTargetsBuffers=[];
+k=0;for(p=i.numMorphTargets;k<p;k++)i.__webglMorphTargetsBuffers.push(j.createBuffer())}if(i.numMorphNormals){i.__webglMorphNormalsBuffers=[];k=0;for(p=i.numMorphNormals;k<p;k++)i.__webglMorphNormalsBuffers.push(j.createBuffer())}I.info.memory.geometries++;c(h,a);f.verticesNeedUpdate=!0;f.morphTargetsNeedUpdate=!0;f.elementsNeedUpdate=!0;f.uvsNeedUpdate=!0;f.normalsNeedUpdate=!0;f.tangentsNeedUpdate=!0;f.colorsNeedUpdate=!0}}else a instanceof THREE.Line?f.__webglVertexBuffer||(h=f,h.__webglVertexBuffer=
+j.createBuffer(),h.__webglColorBuffer=j.createBuffer(),h.__webglLineDistanceBuffer=j.createBuffer(),I.info.memory.geometries++,h=f,i=h.vertices.length,h.__vertexArray=new Float32Array(3*i),h.__colorArray=new Float32Array(3*i),h.__lineDistanceArray=new Float32Array(1*i),h.__webglLineCount=i,b(h,a),f.verticesNeedUpdate=!0,f.colorsNeedUpdate=!0,f.lineDistancesNeedUpdate=!0):a instanceof THREE.ParticleSystem&&!f.__webglVertexBuffer&&(h=f,h.__webglVertexBuffer=j.createBuffer(),h.__webglColorBuffer=j.createBuffer(),
+I.info.memory.geometries++,h=f,i=h.vertices.length,h.__vertexArray=new Float32Array(3*i),h.__colorArray=new Float32Array(3*i),h.__sortArray=[],h.__webglParticleCount=i,b(h,a),f.verticesNeedUpdate=!0,f.colorsNeedUpdate=!0);if(void 0===a.__webglActive){if(a instanceof THREE.Mesh)if(f=a.geometry,f instanceof THREE.BufferGeometry)r(d.__webglObjects,f,a);else{if(f instanceof THREE.Geometry)for(e in f.geometryGroups)h=f.geometryGroups[e],r(d.__webglObjects,h,a)}else a instanceof THREE.Line||a instanceof
+THREE.ParticleSystem?(f=a.geometry,r(d.__webglObjects,f,a)):a instanceof THREE.ImmediateRenderObject||a.immediateRenderCallback?d.__webglObjectsImmediate.push({id:null,object:a,opaque:null,transparent:null,z:0}):a instanceof THREE.Sprite?d.__webglSprites.push(a):a instanceof THREE.LensFlare&&d.__webglFlares.push(a);a.__webglActive=!0}}function r(a,b,c){a.push({id:null,buffer:b,object:c,opaque:null,transparent:null,z:0})}function m(a){for(var b in a.attributes)if(a.attributes[b].needsUpdate)return!0;
+return!1}function q(a){for(var b in a.attributes)a.attributes[b].needsUpdate=!1}function u(a,b){a instanceof THREE.Mesh||a instanceof THREE.ParticleSystem||a instanceof THREE.Line?s(b.__webglObjects,a):a instanceof THREE.Sprite?v(b.__webglSprites,a):a instanceof THREE.LensFlare?v(b.__webglFlares,a):(a instanceof THREE.ImmediateRenderObject||a.immediateRenderCallback)&&s(b.__webglObjectsImmediate,a);delete a.__webglActive}function s(a,b){for(var c=a.length-1;0<=c;c--)a[c].object===b&&a.splice(c,1)}
+function v(a,b){for(var c=a.length-1;0<=c;c--)a[c]===b&&a.splice(c,1)}function z(a,b,c,d,e){Ja=0;d.needsUpdate&&(d.program&&Eb(d),I.initMaterial(d,b,c,e),d.needsUpdate=!1);d.morphTargets&&!e.__webglMorphTargetInfluences&&(e.__webglMorphTargetInfluences=new Float32Array(I.maxMorphTargets));var f=!1,g=d.program,h=g.uniforms,i=d.uniforms;g!==oa&&(j.useProgram(g),oa=g,f=!0);d.id!==Ea&&(Ea=d.id,f=!0);if(f||a!==Ha)j.uniformMatrix4fv(h.projectionMatrix,!1,a.projectionMatrix.elements),a!==Ha&&(Ha=a);if(d.skinning)if(tb&&
+e.useVertexTexture){if(null!==h.boneTexture){var k=G();j.uniform1i(h.boneTexture,k);I.setTexture(e.boneTexture,k)}null!==h.boneTextureWidth&&j.uniform1i(h.boneTextureWidth,e.boneTextureWidth);null!==h.boneTextureHeight&&j.uniform1i(h.boneTextureHeight,e.boneTextureHeight)}else null!==h.boneGlobalMatrices&&j.uniformMatrix4fv(h.boneGlobalMatrices,!1,e.boneMatrices);if(f){c&&d.fog&&(i.fogColor.value=c.color,c instanceof THREE.Fog?(i.fogNear.value=c.near,i.fogFar.value=c.far):c instanceof THREE.FogExp2&&
+(i.fogDensity.value=c.density));if(d instanceof THREE.MeshPhongMaterial||d instanceof THREE.MeshLambertMaterial||d.lights){if(Za){for(var l,m=k=0,n=0,p,q,t,r=Na,s=r.directional.colors,u=r.directional.positions,v=r.point.colors,z=r.point.positions,y=r.point.distances,x=r.spot.colors,B=r.spot.positions,F=r.spot.distances,E=r.spot.directions,L=r.spot.anglesCos,A=r.spot.exponents,O=r.hemi.skyColors,Q=r.hemi.groundColors,K=r.hemi.positions,P=0,W=0,T=0,va=0,sa=0,Y=0,Z=0,X=0,U=l=0,c=t=U=0,f=b.length;c<f;c++)l=
+b[c],l.onlyShadow||(p=l.color,q=l.intensity,t=l.distance,l instanceof THREE.AmbientLight?l.visible&&(I.gammaInput?(k+=p.r*p.r,m+=p.g*p.g,n+=p.b*p.b):(k+=p.r,m+=p.g,n+=p.b)):l instanceof THREE.DirectionalLight?(sa+=1,l.visible&&(fa.setFromMatrixPosition(l.matrixWorld),ja.setFromMatrixPosition(l.target.matrixWorld),fa.sub(ja),fa.normalize(),0===fa.x&&0===fa.y&&0===fa.z||(l=3*P,u[l]=fa.x,u[l+1]=fa.y,u[l+2]=fa.z,I.gammaInput?N(s,l,p,q*q):C(s,l,p,q),P+=1))):l instanceof THREE.PointLight?(Y+=1,l.visible&&
+(U=3*W,I.gammaInput?N(v,U,p,q*q):C(v,U,p,q),ja.setFromMatrixPosition(l.matrixWorld),z[U]=ja.x,z[U+1]=ja.y,z[U+2]=ja.z,y[W]=t,W+=1)):l instanceof THREE.SpotLight?(Z+=1,l.visible&&(U=3*T,I.gammaInput?N(x,U,p,q*q):C(x,U,p,q),ja.setFromMatrixPosition(l.matrixWorld),B[U]=ja.x,B[U+1]=ja.y,B[U+2]=ja.z,F[T]=t,fa.copy(ja),ja.setFromMatrixPosition(l.target.matrixWorld),fa.sub(ja),fa.normalize(),E[U]=fa.x,E[U+1]=fa.y,E[U+2]=fa.z,L[T]=Math.cos(l.angle),A[T]=l.exponent,T+=1)):l instanceof THREE.HemisphereLight&&
+(X+=1,l.visible&&(fa.setFromMatrixPosition(l.matrixWorld),fa.normalize(),0===fa.x&&0===fa.y&&0===fa.z||(t=3*va,K[t]=fa.x,K[t+1]=fa.y,K[t+2]=fa.z,p=l.color,l=l.groundColor,I.gammaInput?(q*=q,N(O,t,p,q),N(Q,t,l,q)):(C(O,t,p,q),C(Q,t,l,q)),va+=1))));c=3*P;for(f=Math.max(s.length,3*sa);c<f;c++)s[c]=0;c=3*W;for(f=Math.max(v.length,3*Y);c<f;c++)v[c]=0;c=3*T;for(f=Math.max(x.length,3*Z);c<f;c++)x[c]=0;c=3*va;for(f=Math.max(O.length,3*X);c<f;c++)O[c]=0;c=3*va;for(f=Math.max(Q.length,3*X);c<f;c++)Q[c]=0;r.directional.length=
+P;r.point.length=W;r.spot.length=T;r.hemi.length=va;r.ambient[0]=k;r.ambient[1]=m;r.ambient[2]=n;Za=!1}c=Na;i.ambientLightColor.value=c.ambient;i.directionalLightColor.value=c.directional.colors;i.directionalLightDirection.value=c.directional.positions;i.pointLightColor.value=c.point.colors;i.pointLightPosition.value=c.point.positions;i.pointLightDistance.value=c.point.distances;i.spotLightColor.value=c.spot.colors;i.spotLightPosition.value=c.spot.positions;i.spotLightDistance.value=c.spot.distances;
+i.spotLightDirection.value=c.spot.directions;i.spotLightAngleCos.value=c.spot.anglesCos;i.spotLightExponent.value=c.spot.exponents;i.hemisphereLightSkyColor.value=c.hemi.skyColors;i.hemisphereLightGroundColor.value=c.hemi.groundColors;i.hemisphereLightDirection.value=c.hemi.positions}if(d instanceof THREE.MeshBasicMaterial||d instanceof THREE.MeshLambertMaterial||d instanceof THREE.MeshPhongMaterial){i.opacity.value=d.opacity;I.gammaInput?i.diffuse.value.copyGammaToLinear(d.color):i.diffuse.value=
+d.color;i.map.value=d.map;i.lightMap.value=d.lightMap;i.specularMap.value=d.specularMap;d.bumpMap&&(i.bumpMap.value=d.bumpMap,i.bumpScale.value=d.bumpScale);d.normalMap&&(i.normalMap.value=d.normalMap,i.normalScale.value.copy(d.normalScale));var V;d.map?V=d.map:d.specularMap?V=d.specularMap:d.normalMap?V=d.normalMap:d.bumpMap&&(V=d.bumpMap);void 0!==V&&(c=V.offset,V=V.repeat,i.offsetRepeat.value.set(c.x,c.y,V.x,V.y));i.envMap.value=d.envMap;i.flipEnvMap.value=d.envMap instanceof THREE.WebGLRenderTargetCube?
+1:-1;i.reflectivity.value=d.reflectivity;i.refractionRatio.value=d.refractionRatio;i.combine.value=d.combine;i.useRefract.value=d.envMap&&d.envMap.mapping instanceof THREE.CubeRefractionMapping}d instanceof THREE.LineBasicMaterial?(i.diffuse.value=d.color,i.opacity.value=d.opacity):d instanceof THREE.LineDashedMaterial?(i.diffuse.value=d.color,i.opacity.value=d.opacity,i.dashSize.value=d.dashSize,i.totalSize.value=d.dashSize+d.gapSize,i.scale.value=d.scale):d instanceof THREE.ParticleSystemMaterial?
+(i.psColor.value=d.color,i.opacity.value=d.opacity,i.size.value=d.size,i.scale.value=H.height/2,i.map.value=d.map):d instanceof THREE.MeshPhongMaterial?(i.shininess.value=d.shininess,I.gammaInput?(i.ambient.value.copyGammaToLinear(d.ambient),i.emissive.value.copyGammaToLinear(d.emissive),i.specular.value.copyGammaToLinear(d.specular)):(i.ambient.value=d.ambient,i.emissive.value=d.emissive,i.specular.value=d.specular),d.wrapAround&&i.wrapRGB.value.copy(d.wrapRGB)):d instanceof THREE.MeshLambertMaterial?
+(I.gammaInput?(i.ambient.value.copyGammaToLinear(d.ambient),i.emissive.value.copyGammaToLinear(d.emissive)):(i.ambient.value=d.ambient,i.emissive.value=d.emissive),d.wrapAround&&i.wrapRGB.value.copy(d.wrapRGB)):d instanceof THREE.MeshDepthMaterial?(i.mNear.value=a.near,i.mFar.value=a.far,i.opacity.value=d.opacity):d instanceof THREE.MeshNormalMaterial&&(i.opacity.value=d.opacity);if(e.receiveShadow&&!d._shadowPass&&i.shadowMatrix){c=V=0;for(f=b.length;c<f;c++)if(k=b[c],k.castShadow&&(k instanceof
+THREE.SpotLight||k instanceof THREE.DirectionalLight&&!k.shadowCascade))i.shadowMap.value[V]=k.shadowMap,i.shadowMapSize.value[V]=k.shadowMapSize,i.shadowMatrix.value[V]=k.shadowMatrix,i.shadowDarkness.value[V]=k.shadowDarkness,i.shadowBias.value[V]=k.shadowBias,V++}b=d.uniformsList;i=0;for(V=b.length;i<V;i++)if(f=g.uniforms[b[i][1]])if(c=b[i][0],m=c.type,k=c.value,"i"===m)j.uniform1i(f,k);else if("f"===m)j.uniform1f(f,k);else if("v2"===m)j.uniform2f(f,k.x,k.y);else if("v3"===m)j.uniform3f(f,k.x,
+k.y,k.z);else if("v4"===m)j.uniform4f(f,k.x,k.y,k.z,k.w);else if("c"===m)j.uniform3f(f,k.r,k.g,k.b);else if("iv1"===m)j.uniform1iv(f,k);else if("iv"===m)j.uniform3iv(f,k);else if("fv1"===m)j.uniform1fv(f,k);else if("fv"===m)j.uniform3fv(f,k);else if("v2v"===m){void 0===c._array&&(c._array=new Float32Array(2*k.length));m=0;for(n=k.length;m<n;m++)r=2*m,c._array[r]=k[m].x,c._array[r+1]=k[m].y;j.uniform2fv(f,c._array)}else if("v3v"===m){void 0===c._array&&(c._array=new Float32Array(3*k.length));m=0;for(n=
+k.length;m<n;m++)r=3*m,c._array[r]=k[m].x,c._array[r+1]=k[m].y,c._array[r+2]=k[m].z;j.uniform3fv(f,c._array)}else if("v4v"===m){void 0===c._array&&(c._array=new Float32Array(4*k.length));m=0;for(n=k.length;m<n;m++)r=4*m,c._array[r]=k[m].x,c._array[r+1]=k[m].y,c._array[r+2]=k[m].z,c._array[r+3]=k[m].w;j.uniform4fv(f,c._array)}else if("m4"===m)void 0===c._array&&(c._array=new Float32Array(16)),k.flattenToArray(c._array),j.uniformMatrix4fv(f,!1,c._array);else if("m4v"===m){void 0===c._array&&(c._array=
+new Float32Array(16*k.length));m=0;for(n=k.length;m<n;m++)k[m].flattenToArrayOffset(c._array,16*m);j.uniformMatrix4fv(f,!1,c._array)}else if("t"===m){if(r=k,k=G(),j.uniform1i(f,k),r)if(r.image instanceof Array&&6===r.image.length){if(c=r,f=k,6===c.image.length)if(c.needsUpdate){c.image.__webglTextureCube||(c.addEventListener("dispose",Bb),c.image.__webglTextureCube=j.createTexture(),I.info.memory.textures++);j.activeTexture(j.TEXTURE0+f);j.bindTexture(j.TEXTURE_CUBE_MAP,c.image.__webglTextureCube);
+j.pixelStorei(j.UNPACK_FLIP_Y_WEBGL,c.flipY);f=c instanceof THREE.CompressedTexture;k=[];for(m=0;6>m;m++)I.autoScaleCubemaps&&!f?(n=k,r=m,s=c.image[m],v=Yb,s.width<=v&&s.height<=v||(z=Math.max(s.width,s.height),u=Math.floor(s.width*v/z),v=Math.floor(s.height*v/z),z=document.createElement("canvas"),z.width=u,z.height=v,z.getContext("2d").drawImage(s,0,0,s.width,s.height,0,0,u,v),s=z),n[r]=s):k[m]=c.image[m];m=k[0];n=0===(m.width&m.width-1)&&0===(m.height&m.height-1);r=D(c.format);s=D(c.type);w(j.TEXTURE_CUBE_MAP,
+c,n);for(m=0;6>m;m++)if(f){v=k[m].mipmaps;z=0;for(y=v.length;z<y;z++)u=v[z],c.format!==THREE.RGBAFormat?j.compressedTexImage2D(j.TEXTURE_CUBE_MAP_POSITIVE_X+m,z,r,u.width,u.height,0,u.data):j.texImage2D(j.TEXTURE_CUBE_MAP_POSITIVE_X+m,z,r,u.width,u.height,0,r,s,u.data)}else j.texImage2D(j.TEXTURE_CUBE_MAP_POSITIVE_X+m,0,r,r,s,k[m]);c.generateMipmaps&&n&&j.generateMipmap(j.TEXTURE_CUBE_MAP);c.needsUpdate=!1;if(c.onUpdate)c.onUpdate()}else j.activeTexture(j.TEXTURE0+f),j.bindTexture(j.TEXTURE_CUBE_MAP,
+c.image.__webglTextureCube)}else r instanceof THREE.WebGLRenderTargetCube?(c=r,j.activeTexture(j.TEXTURE0+k),j.bindTexture(j.TEXTURE_CUBE_MAP,c.__webglTexture)):I.setTexture(r,k)}else if("tv"===m){void 0===c._array&&(c._array=[]);m=0;for(n=c.value.length;m<n;m++)c._array[m]=G();j.uniform1iv(f,c._array);m=0;for(n=c.value.length;m<n;m++)r=c.value[m],k=c._array[m],r&&I.setTexture(r,k)}else console.warn("THREE.WebGLRenderer: Unknown uniform type: "+m);if((d instanceof THREE.ShaderMaterial||d instanceof
+THREE.MeshPhongMaterial||d.envMap)&&null!==h.cameraPosition)ja.setFromMatrixPosition(a.matrixWorld),j.uniform3f(h.cameraPosition,ja.x,ja.y,ja.z);(d instanceof THREE.MeshPhongMaterial||d instanceof THREE.MeshLambertMaterial||d instanceof THREE.ShaderMaterial||d.skinning)&&null!==h.viewMatrix&&j.uniformMatrix4fv(h.viewMatrix,!1,a.matrixWorldInverse.elements)}j.uniformMatrix4fv(h.modelViewMatrix,!1,e._modelViewMatrix.elements);h.normalMatrix&&j.uniformMatrix3fv(h.normalMatrix,!1,e._normalMatrix.elements);
+null!==h.modelMatrix&&j.uniformMatrix4fv(h.modelMatrix,!1,e.matrixWorld.elements);return g}function G(){var a=Ja;a>=Ab&&console.warn("WebGLRenderer: trying to use "+a+" texture units while this GPU supports only "+Ab);Ja+=1;return a}function N(a,b,c,d){a[b]=c.r*c.r*d;a[b+1]=c.g*c.g*d;a[b+2]=c.b*c.b*d}function C(a,b,c,d){a[b]=c.r*d;a[b+1]=c.g*d;a[b+2]=c.b*d}function B(a){a!==ia&&(j.lineWidth(a),ia=a)}function x(a,b,c){Ua!==a&&(a?j.enable(j.POLYGON_OFFSET_FILL):j.disable(j.POLYGON_OFFSET_FILL),Ua=a);
+if(a&&(Oa!==b||la!==c))j.polygonOffset(b,c),Oa=b,la=c}function F(a){for(var a=a.split("\n"),b=0,c=a.length;b<c;b++)a[b]=b+1+": "+a[b];return a.join("\n")}function L(a,b){var c;"fragment"===a?c=j.createShader(j.FRAGMENT_SHADER):"vertex"===a&&(c=j.createShader(j.VERTEX_SHADER));j.shaderSource(c,b);j.compileShader(c);return!j.getShaderParameter(c,j.COMPILE_STATUS)?(console.error(j.getShaderInfoLog(c)),console.error(F(b)),null):c}function w(a,b,c){c?(j.texParameteri(a,j.TEXTURE_WRAP_S,D(b.wrapS)),j.texParameteri(a,
+j.TEXTURE_WRAP_T,D(b.wrapT)),j.texParameteri(a,j.TEXTURE_MAG_FILTER,D(b.magFilter)),j.texParameteri(a,j.TEXTURE_MIN_FILTER,D(b.minFilter))):(j.texParameteri(a,j.TEXTURE_WRAP_S,j.CLAMP_TO_EDGE),j.texParameteri(a,j.TEXTURE_WRAP_T,j.CLAMP_TO_EDGE),j.texParameteri(a,j.TEXTURE_MAG_FILTER,y(b.magFilter)),j.texParameteri(a,j.TEXTURE_MIN_FILTER,y(b.minFilter)));if(qa&&b.type!==THREE.FloatType&&(1<b.anisotropy||b.__oldAnisotropy))j.texParameterf(a,qa.TEXTURE_MAX_ANISOTROPY_EXT,Math.min(b.anisotropy,Jb)),b.__oldAnisotropy=
+b.anisotropy}function E(a,b){j.bindRenderbuffer(j.RENDERBUFFER,a);b.depthBuffer&&!b.stencilBuffer?(j.renderbufferStorage(j.RENDERBUFFER,j.DEPTH_COMPONENT16,b.width,b.height),j.framebufferRenderbuffer(j.FRAMEBUFFER,j.DEPTH_ATTACHMENT,j.RENDERBUFFER,a)):b.depthBuffer&&b.stencilBuffer?(j.renderbufferStorage(j.RENDERBUFFER,j.DEPTH_STENCIL,b.width,b.height),j.framebufferRenderbuffer(j.FRAMEBUFFER,j.DEPTH_STENCIL_ATTACHMENT,j.RENDERBUFFER,a)):j.renderbufferStorage(j.RENDERBUFFER,j.RGBA4,b.width,b.height)}
+function y(a){return a===THREE.NearestFilter||a===THREE.NearestMipMapNearestFilter||a===THREE.NearestMipMapLinearFilter?j.NEAREST:j.LINEAR}function D(a){if(a===THREE.RepeatWrapping)return j.REPEAT;if(a===THREE.ClampToEdgeWrapping)return j.CLAMP_TO_EDGE;if(a===THREE.MirroredRepeatWrapping)return j.MIRRORED_REPEAT;if(a===THREE.NearestFilter)return j.NEAREST;if(a===THREE.NearestMipMapNearestFilter)return j.NEAREST_MIPMAP_NEAREST;if(a===THREE.NearestMipMapLinearFilter)return j.NEAREST_MIPMAP_LINEAR;if(a===
+THREE.LinearFilter)return j.LINEAR;if(a===THREE.LinearMipMapNearestFilter)return j.LINEAR_MIPMAP_NEAREST;if(a===THREE.LinearMipMapLinearFilter)return j.LINEAR_MIPMAP_LINEAR;if(a===THREE.UnsignedByteType)return j.UNSIGNED_BYTE;if(a===THREE.UnsignedShort4444Type)return j.UNSIGNED_SHORT_4_4_4_4;if(a===THREE.UnsignedShort5551Type)return j.UNSIGNED_SHORT_5_5_5_1;if(a===THREE.UnsignedShort565Type)return j.UNSIGNED_SHORT_5_6_5;if(a===THREE.ByteType)return j.BYTE;if(a===THREE.ShortType)return j.SHORT;if(a===
+THREE.UnsignedShortType)return j.UNSIGNED_SHORT;if(a===THREE.IntType)return j.INT;if(a===THREE.UnsignedIntType)return j.UNSIGNED_INT;if(a===THREE.FloatType)return j.FLOAT;if(a===THREE.AlphaFormat)return j.ALPHA;if(a===THREE.RGBFormat)return j.RGB;if(a===THREE.RGBAFormat)return j.RGBA;if(a===THREE.LuminanceFormat)return j.LUMINANCE;if(a===THREE.LuminanceAlphaFormat)return j.LUMINANCE_ALPHA;if(a===THREE.AddEquation)return j.FUNC_ADD;if(a===THREE.SubtractEquation)return j.FUNC_SUBTRACT;if(a===THREE.ReverseSubtractEquation)return j.FUNC_REVERSE_SUBTRACT;
+if(a===THREE.ZeroFactor)return j.ZERO;if(a===THREE.OneFactor)return j.ONE;if(a===THREE.SrcColorFactor)return j.SRC_COLOR;if(a===THREE.OneMinusSrcColorFactor)return j.ONE_MINUS_SRC_COLOR;if(a===THREE.SrcAlphaFactor)return j.SRC_ALPHA;if(a===THREE.OneMinusSrcAlphaFactor)return j.ONE_MINUS_SRC_ALPHA;if(a===THREE.DstAlphaFactor)return j.DST_ALPHA;if(a===THREE.OneMinusDstAlphaFactor)return j.ONE_MINUS_DST_ALPHA;if(a===THREE.DstColorFactor)return j.DST_COLOR;if(a===THREE.OneMinusDstColorFactor)return j.ONE_MINUS_DST_COLOR;
+if(a===THREE.SrcAlphaSaturateFactor)return j.SRC_ALPHA_SATURATE;if(void 0!==ta){if(a===THREE.RGB_S3TC_DXT1_Format)return ta.COMPRESSED_RGB_S3TC_DXT1_EXT;if(a===THREE.RGBA_S3TC_DXT1_Format)return ta.COMPRESSED_RGBA_S3TC_DXT1_EXT;if(a===THREE.RGBA_S3TC_DXT3_Format)return ta.COMPRESSED_RGBA_S3TC_DXT3_EXT;if(a===THREE.RGBA_S3TC_DXT5_Format)return ta.COMPRESSED_RGBA_S3TC_DXT5_EXT}return 0}console.log("THREE.WebGLRenderer",THREE.REVISION);var a=a||{},H=void 0!==a.canvas?a.canvas:document.createElement("canvas"),
+K=void 0!==a.precision?a.precision:"highp",A=void 0!==a.alpha?a.alpha:!1,da=void 0!==a.premultipliedAlpha?a.premultipliedAlpha:!0,ha=void 0!==a.antialias?a.antialias:!1,ua=void 0!==a.stencil?a.stencil:!0,O=void 0!==a.preserveDrawingBuffer?a.preserveDrawingBuffer:!1,T=new THREE.Color(0),ea=0;this.domElement=H;this.context=null;this.devicePixelRatio=void 0!==a.devicePixelRatio?a.devicePixelRatio:void 0!==self.devicePixelRatio?self.devicePixelRatio:1;this.autoUpdateObjects=this.sortObjects=this.autoClearStencil=
+this.autoClearDepth=this.autoClearColor=this.autoClear=!0;this.shadowMapEnabled=this.gammaOutput=this.gammaInput=!1;this.shadowMapAutoUpdate=!0;this.shadowMapType=THREE.PCFShadowMap;this.shadowMapCullFace=THREE.CullFaceFront;this.shadowMapCascade=this.shadowMapDebug=!1;this.maxMorphTargets=8;this.maxMorphNormals=4;this.autoScaleCubemaps=!0;this.renderPluginsPre=[];this.renderPluginsPost=[];this.info={memory:{programs:0,geometries:0,textures:0},render:{calls:0,vertices:0,faces:0,points:0}};var I=this,
+P=[],Q=0,oa=null,va=null,Ea=-1,za=null,Ha=null,Ta=0,Ja=0,sa=-1,Aa=-1,ba=-1,W=-1,Ka=-1,U=-1,ca=-1,wa=-1,Ua=null,Oa=null,la=null,ia=null,Fa=0,Ga=0,Ba=H.width,xa=H.height,Va=0,La=0,Ma={},ya=new THREE.Frustum,V=new THREE.Matrix4,pa=new THREE.Matrix4,ja=new THREE.Vector3,fa=new THREE.Vector3,Za=!0,Na={ambient:[0,0,0],directional:{length:0,colors:[],positions:[]},point:{length:0,colors:[],positions:[],distances:[]},spot:{length:0,colors:[],positions:[],distances:[],directions:[],anglesCos:[],exponents:[]},
+hemi:{length:0,skyColors:[],groundColors:[],positions:[]}},j,Pa,Wa,qa,ta;try{var hb={alpha:A,premultipliedAlpha:da,antialias:ha,stencil:ua,preserveDrawingBuffer:O};j=H.getContext("webgl",hb)||H.getContext("experimental-webgl",hb);if(null===j)throw"Error creating WebGL context.";}catch(db){console.error(db)}Pa=j.getExtension("OES_texture_float");j.getExtension("OES_texture_float_linear");Wa=j.getExtension("OES_standard_derivatives");qa=j.getExtension("EXT_texture_filter_anisotropic")||j.getExtension("MOZ_EXT_texture_filter_anisotropic")||
+j.getExtension("WEBKIT_EXT_texture_filter_anisotropic");ta=j.getExtension("WEBGL_compressed_texture_s3tc")||j.getExtension("MOZ_WEBGL_compressed_texture_s3tc")||j.getExtension("WEBKIT_WEBGL_compressed_texture_s3tc");Pa||console.log("THREE.WebGLRenderer: Float textures not supported.");Wa||console.log("THREE.WebGLRenderer: Standard derivatives not supported.");qa||console.log("THREE.WebGLRenderer: Anisotropic texture filtering not supported.");ta||console.log("THREE.WebGLRenderer: S3TC compressed textures not supported.");
+void 0===j.getShaderPrecisionFormat&&(j.getShaderPrecisionFormat=function(){return{rangeMin:1,rangeMax:1,precision:1}});j.clearColor(0,0,0,1);j.clearDepth(1);j.clearStencil(0);j.enable(j.DEPTH_TEST);j.depthFunc(j.LEQUAL);j.frontFace(j.CCW);j.cullFace(j.BACK);j.enable(j.CULL_FACE);j.enable(j.BLEND);j.blendEquation(j.FUNC_ADD);j.blendFunc(j.SRC_ALPHA,j.ONE_MINUS_SRC_ALPHA);j.viewport(Fa,Ga,Ba,xa);j.clearColor(T.r,T.g,T.b,ea);this.context=j;var Ab=j.getParameter(j.MAX_TEXTURE_IMAGE_UNITS),Xb=j.getParameter(j.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
+j.getParameter(j.MAX_TEXTURE_SIZE);var Yb=j.getParameter(j.MAX_CUBE_MAP_TEXTURE_SIZE),Jb=qa?j.getParameter(qa.MAX_TEXTURE_MAX_ANISOTROPY_EXT):0,xb=0<Xb,tb=xb&&Pa;ta&&j.getParameter(j.COMPRESSED_TEXTURE_FORMATS);var Zb=j.getShaderPrecisionFormat(j.VERTEX_SHADER,j.HIGH_FLOAT),$b=j.getShaderPrecisionFormat(j.VERTEX_SHADER,j.MEDIUM_FLOAT);j.getShaderPrecisionFormat(j.VERTEX_SHADER,j.LOW_FLOAT);var ac=j.getShaderPrecisionFormat(j.FRAGMENT_SHADER,j.HIGH_FLOAT),pc=j.getShaderPrecisionFormat(j.FRAGMENT_SHADER,
+j.MEDIUM_FLOAT);j.getShaderPrecisionFormat(j.FRAGMENT_SHADER,j.LOW_FLOAT);j.getShaderPrecisionFormat(j.VERTEX_SHADER,j.HIGH_INT);j.getShaderPrecisionFormat(j.VERTEX_SHADER,j.MEDIUM_INT);j.getShaderPrecisionFormat(j.VERTEX_SHADER,j.LOW_INT);j.getShaderPrecisionFormat(j.FRAGMENT_SHADER,j.HIGH_INT);j.getShaderPrecisionFormat(j.FRAGMENT_SHADER,j.MEDIUM_INT);j.getShaderPrecisionFormat(j.FRAGMENT_SHADER,j.LOW_INT);var qc=0<Zb.precision&&0<ac.precision,Kb=0<$b.precision&&0<pc.precision;"highp"===K&&!qc&&
+(Kb?(K="mediump",console.warn("WebGLRenderer: highp not supported, using mediump")):(K="lowp",console.warn("WebGLRenderer: highp and mediump not supported, using lowp")));"mediump"===K&&!Kb&&(K="lowp",console.warn("WebGLRenderer: mediump not supported, using lowp"));this.getContext=function(){return j};this.supportsVertexTextures=function(){return xb};this.supportsFloatTextures=function(){return Pa};this.supportsStandardDerivatives=function(){return Wa};this.supportsCompressedTextureS3TC=function(){return ta};
+this.getMaxAnisotropy=function(){return Jb};this.getPrecision=function(){return K};this.setSize=function(a,b,c){H.width=a*this.devicePixelRatio;H.height=b*this.devicePixelRatio;1!==this.devicePixelRatio&&!1!==c&&(H.style.width=a+"px",H.style.height=b+"px");this.setViewport(0,0,H.width,H.height)};this.setViewport=function(a,b,c,d){Fa=void 0!==a?a:0;Ga=void 0!==b?b:0;Ba=void 0!==c?c:H.width;xa=void 0!==d?d:H.height;j.viewport(Fa,Ga,Ba,xa)};this.setScissor=function(a,b,c,d){j.scissor(a,b,c,d)};this.enableScissorTest=
+function(a){a?j.enable(j.SCISSOR_TEST):j.disable(j.SCISSOR_TEST)};this.setClearColor=function(a,b){T.set(a);ea=void 0!==b?b:1;j.clearColor(T.r,T.g,T.b,ea)};this.setClearColorHex=function(a,b){console.warn("DEPRECATED: .setClearColorHex() is being removed. Use .setClearColor() instead.");this.setClearColor(a,b)};this.getClearColor=function(){return T};this.getClearAlpha=function(){return ea};this.clear=function(a,b,c){var d=0;if(void 0===a||a)d|=j.COLOR_BUFFER_BIT;if(void 0===b||b)d|=j.DEPTH_BUFFER_BIT;
+if(void 0===c||c)d|=j.STENCIL_BUFFER_BIT;j.clear(d)};this.clearColor=function(){j.clear(j.COLOR_BUFFER_BIT)};this.clearDepth=function(){j.clear(j.DEPTH_BUFFER_BIT)};this.clearStencil=function(){j.clear(j.STENCIL_BUFFER_BIT)};this.clearTarget=function(a,b,c,d){this.setRenderTarget(a);this.clear(b,c,d)};this.addPostPlugin=function(a){a.init(this);this.renderPluginsPost.push(a)};this.addPrePlugin=function(a){a.init(this);this.renderPluginsPre.push(a)};this.updateShadowMap=function(a,b){oa=null;Ea=za=
+wa=ca=ba=-1;Za=!0;Aa=sa=-1;this.shadowMapPlugin.update(a,b)};var Lb=function(a){a=a.target;a.removeEventListener("dispose",Lb);a.__webglInit=void 0;if(a instanceof THREE.BufferGeometry){var b=a.attributes,c;for(c in b)void 0!==b[c].buffer&&j.deleteBuffer(b[c].buffer);I.info.memory.geometries--}else if(void 0!==a.geometryGroups)for(b in a.geometryGroups){c=a.geometryGroups[b];if(void 0!==c.numMorphTargets)for(var d=0,e=c.numMorphTargets;d<e;d++)j.deleteBuffer(c.__webglMorphTargetsBuffers[d]);if(void 0!==
+c.numMorphNormals){d=0;for(e=c.numMorphNormals;d<e;d++)j.deleteBuffer(c.__webglMorphNormalsBuffers[d])}Db(c)}else Db(a)},Bb=function(a){a=a.target;a.removeEventListener("dispose",Bb);a.image&&a.image.__webglTextureCube?j.deleteTexture(a.image.__webglTextureCube):a.__webglInit&&(a.__webglInit=!1,j.deleteTexture(a.__webglTexture));I.info.memory.textures--},Mb=function(a){a=a.target;a.removeEventListener("dispose",Mb);if(a&&a.__webglTexture)if(j.deleteTexture(a.__webglTexture),a instanceof THREE.WebGLRenderTargetCube)for(var b=
+0;6>b;b++)j.deleteFramebuffer(a.__webglFramebuffer[b]),j.deleteRenderbuffer(a.__webglRenderbuffer[b]);else j.deleteFramebuffer(a.__webglFramebuffer),j.deleteRenderbuffer(a.__webglRenderbuffer);I.info.memory.textures--},Cb=function(a){a=a.target;a.removeEventListener("dispose",Cb);Eb(a)},Db=function(a){void 0!==a.__webglVertexBuffer&&j.deleteBuffer(a.__webglVertexBuffer);void 0!==a.__webglNormalBuffer&&j.deleteBuffer(a.__webglNormalBuffer);void 0!==a.__webglTangentBuffer&&j.deleteBuffer(a.__webglTangentBuffer);
+void 0!==a.__webglColorBuffer&&j.deleteBuffer(a.__webglColorBuffer);void 0!==a.__webglUVBuffer&&j.deleteBuffer(a.__webglUVBuffer);void 0!==a.__webglUV2Buffer&&j.deleteBuffer(a.__webglUV2Buffer);void 0!==a.__webglSkinIndicesBuffer&&j.deleteBuffer(a.__webglSkinIndicesBuffer);void 0!==a.__webglSkinWeightsBuffer&&j.deleteBuffer(a.__webglSkinWeightsBuffer);void 0!==a.__webglFaceBuffer&&j.deleteBuffer(a.__webglFaceBuffer);void 0!==a.__webglLineBuffer&&j.deleteBuffer(a.__webglLineBuffer);void 0!==a.__webglLineDistanceBuffer&&
+j.deleteBuffer(a.__webglLineDistanceBuffer);if(void 0!==a.__webglCustomAttributesList)for(var b in a.__webglCustomAttributesList)j.deleteBuffer(a.__webglCustomAttributesList[b].buffer);I.info.memory.geometries--},Eb=function(a){var b=a.program;if(void 0!==b){a.program=void 0;var c,d,e=!1,a=0;for(c=P.length;a<c;a++)if(d=P[a],d.program===b){d.usedTimes--;0===d.usedTimes&&(e=!0);break}if(!0===e){e=[];a=0;for(c=P.length;a<c;a++)d=P[a],d.program!==b&&e.push(d);P=e;j.deleteProgram(b);I.info.memory.programs--}}};
+this.renderBufferImmediate=function(a,b,c){a.hasPositions&&!a.__webglVertexBuffer&&(a.__webglVertexBuffer=j.createBuffer());a.hasNormals&&!a.__webglNormalBuffer&&(a.__webglNormalBuffer=j.createBuffer());a.hasUvs&&!a.__webglUvBuffer&&(a.__webglUvBuffer=j.createBuffer());a.hasColors&&!a.__webglColorBuffer&&(a.__webglColorBuffer=j.createBuffer());a.hasPositions&&(j.bindBuffer(j.ARRAY_BUFFER,a.__webglVertexBuffer),j.bufferData(j.ARRAY_BUFFER,a.positionArray,j.DYNAMIC_DRAW),j.enableVertexAttribArray(b.attributes.position),
+j.vertexAttribPointer(b.attributes.position,3,j.FLOAT,!1,0,0));if(a.hasNormals){j.bindBuffer(j.ARRAY_BUFFER,a.__webglNormalBuffer);if(c.shading===THREE.FlatShading){var d,e,f,g,h,i,k,l,m,n,p,q=3*a.count;for(p=0;p<q;p+=9)n=a.normalArray,d=n[p],e=n[p+1],f=n[p+2],g=n[p+3],i=n[p+4],l=n[p+5],h=n[p+6],k=n[p+7],m=n[p+8],d=(d+g+h)/3,e=(e+i+k)/3,f=(f+l+m)/3,n[p]=d,n[p+1]=e,n[p+2]=f,n[p+3]=d,n[p+4]=e,n[p+5]=f,n[p+6]=d,n[p+7]=e,n[p+8]=f}j.bufferData(j.ARRAY_BUFFER,a.normalArray,j.DYNAMIC_DRAW);j.enableVertexAttribArray(b.attributes.normal);
+j.vertexAttribPointer(b.attributes.normal,3,j.FLOAT,!1,0,0)}a.hasUvs&&c.map&&(j.bindBuffer(j.ARRAY_BUFFER,a.__webglUvBuffer),j.bufferData(j.ARRAY_BUFFER,a.uvArray,j.DYNAMIC_DRAW),j.enableVertexAttribArray(b.attributes.uv),j.vertexAttribPointer(b.attributes.uv,2,j.FLOAT,!1,0,0));a.hasColors&&c.vertexColors!==THREE.NoColors&&(j.bindBuffer(j.ARRAY_BUFFER,a.__webglColorBuffer),j.bufferData(j.ARRAY_BUFFER,a.colorArray,j.DYNAMIC_DRAW),j.enableVertexAttribArray(b.attributes.color),j.vertexAttribPointer(b.attributes.color,
+3,j.FLOAT,!1,0,0));j.drawArrays(j.TRIANGLES,0,a.count);a.count=0};this.renderBufferDirect=function(a,b,c,d,e,f){if(!1!==d.visible){var i,k,l,m;i=z(a,b,c,d,f);b=i.attributes;a=e.attributes;c=!1;i=16777215*e.id+2*i.id+(d.wireframe?1:0);i!==za&&(za=i,c=!0);c&&h();if(f instanceof THREE.Mesh)if(f=a.index){e=e.offsets;1<e.length&&(c=!0);for(var n=0,p=e.length;n<p;n++){var q=e[n].index;if(c){for(k in b)l=b[k],i=a[k],0<=l&&(i?(m=i.itemSize,j.bindBuffer(j.ARRAY_BUFFER,i.buffer),g(l),j.vertexAttribPointer(l,
+m,j.FLOAT,!1,0,4*q*m)):d.defaultAttributeValues&&(2===d.defaultAttributeValues[k].length?j.vertexAttrib2fv(l,d.defaultAttributeValues[k]):3===d.defaultAttributeValues[k].length&&j.vertexAttrib3fv(l,d.defaultAttributeValues[k])));j.bindBuffer(j.ELEMENT_ARRAY_BUFFER,f.buffer)}j.drawElements(j.TRIANGLES,e[n].count,j.UNSIGNED_SHORT,2*e[n].start);I.info.render.calls++;I.info.render.vertices+=e[n].count;I.info.render.faces+=e[n].count/3}}else{if(c)for(k in b)"index"!==k&&(l=b[k],i=a[k],0<=l&&(i?(m=i.itemSize,
+j.bindBuffer(j.ARRAY_BUFFER,i.buffer),g(l),j.vertexAttribPointer(l,m,j.FLOAT,!1,0,0)):d.defaultAttributeValues&&d.defaultAttributeValues[k]&&(2===d.defaultAttributeValues[k].length?j.vertexAttrib2fv(l,d.defaultAttributeValues[k]):3===d.defaultAttributeValues[k].length&&j.vertexAttrib3fv(l,d.defaultAttributeValues[k]))));d=e.attributes.position;j.drawArrays(j.TRIANGLES,0,d.numItems/3);I.info.render.calls++;I.info.render.vertices+=d.numItems/3;I.info.render.faces+=d.numItems/3/3}else if(f instanceof
+THREE.ParticleSystem){if(c)for(k in b)l=b[k],i=a[k],0<=l&&(i?(m=i.itemSize,j.bindBuffer(j.ARRAY_BUFFER,i.buffer),g(l),j.vertexAttribPointer(l,m,j.FLOAT,!1,0,0)):d.defaultAttributeValues&&d.defaultAttributeValues[k]&&(2===d.defaultAttributeValues[k].length?j.vertexAttrib2fv(l,d.defaultAttributeValues[k]):3===d.defaultAttributeValues[k].length&&j.vertexAttrib3fv(l,d.defaultAttributeValues[k])));d=a.position;j.drawArrays(j.POINTS,0,d.numItems/3);I.info.render.calls++;I.info.render.points+=d.numItems/
+3}else if(f instanceof THREE.Line){if(c)for(k in b)l=b[k],i=a[k],0<=l&&(i?(m=i.itemSize,j.bindBuffer(j.ARRAY_BUFFER,i.buffer),g(l),j.vertexAttribPointer(l,m,j.FLOAT,!1,0,0)):d.defaultAttributeValues&&d.defaultAttributeValues[k]&&(2===d.defaultAttributeValues[k].length?j.vertexAttrib2fv(l,d.defaultAttributeValues[k]):3===d.defaultAttributeValues[k].length&&j.vertexAttrib3fv(l,d.defaultAttributeValues[k])));k=f.type===THREE.LineStrip?j.LINE_STRIP:j.LINES;B(d.linewidth);d=a.position;j.drawArrays(k,0,
+d.numItems/3);I.info.render.calls++;I.info.render.points+=d.numItems}}};this.renderBuffer=function(a,b,c,d,e,f){if(!1!==d.visible){var i,l,c=z(a,b,c,d,f),a=c.attributes,b=!1,c=16777215*e.id+2*c.id+(d.wireframe?1:0);c!==za&&(za=c,b=!0);b&&h();if(!d.morphTargets&&0<=a.position)b&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglVertexBuffer),g(a.position),j.vertexAttribPointer(a.position,3,j.FLOAT,!1,0,0));else if(f.morphTargetBase){c=d.program.attributes;-1!==f.morphTargetBase&&0<=c.position?(j.bindBuffer(j.ARRAY_BUFFER,
+e.__webglMorphTargetsBuffers[f.morphTargetBase]),g(c.position),j.vertexAttribPointer(c.position,3,j.FLOAT,!1,0,0)):0<=c.position&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglVertexBuffer),g(c.position),j.vertexAttribPointer(c.position,3,j.FLOAT,!1,0,0));if(f.morphTargetForcedOrder.length){var m=0;l=f.morphTargetForcedOrder;for(i=f.morphTargetInfluences;m<d.numSupportedMorphTargets&&m<l.length;)0<=c["morphTarget"+m]&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglMorphTargetsBuffers[l[m]]),g(c["morphTarget"+m]),
+j.vertexAttribPointer(c["morphTarget"+m],3,j.FLOAT,!1,0,0)),0<=c["morphNormal"+m]&&d.morphNormals&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglMorphNormalsBuffers[l[m]]),g(c["morphNormal"+m]),j.vertexAttribPointer(c["morphNormal"+m],3,j.FLOAT,!1,0,0)),f.__webglMorphTargetInfluences[m]=i[l[m]],m++}else{l=[];i=f.morphTargetInfluences;var n,p=i.length;for(n=0;n<p;n++)m=i[n],0<m&&l.push([m,n]);l.length>d.numSupportedMorphTargets?(l.sort(k),l.length=d.numSupportedMorphTargets):l.length>d.numSupportedMorphNormals?
+l.sort(k):0===l.length&&l.push([0,0]);for(m=0;m<d.numSupportedMorphTargets;)l[m]?(n=l[m][1],0<=c["morphTarget"+m]&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglMorphTargetsBuffers[n]),g(c["morphTarget"+m]),j.vertexAttribPointer(c["morphTarget"+m],3,j.FLOAT,!1,0,0)),0<=c["morphNormal"+m]&&d.morphNormals&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglMorphNormalsBuffers[n]),g(c["morphNormal"+m]),j.vertexAttribPointer(c["morphNormal"+m],3,j.FLOAT,!1,0,0)),f.__webglMorphTargetInfluences[m]=i[n]):f.__webglMorphTargetInfluences[m]=
+0,m++}null!==d.program.uniforms.morphTargetInfluences&&j.uniform1fv(d.program.uniforms.morphTargetInfluences,f.__webglMorphTargetInfluences)}if(b){if(e.__webglCustomAttributesList){i=0;for(l=e.__webglCustomAttributesList.length;i<l;i++)c=e.__webglCustomAttributesList[i],0<=a[c.buffer.belongsToAttribute]&&(j.bindBuffer(j.ARRAY_BUFFER,c.buffer),g(a[c.buffer.belongsToAttribute]),j.vertexAttribPointer(a[c.buffer.belongsToAttribute],c.size,j.FLOAT,!1,0,0))}0<=a.color&&(0<f.geometry.colors.length||0<f.geometry.faces.length?
+(j.bindBuffer(j.ARRAY_BUFFER,e.__webglColorBuffer),g(a.color),j.vertexAttribPointer(a.color,3,j.FLOAT,!1,0,0)):d.defaultAttributeValues&&j.vertexAttrib3fv(a.color,d.defaultAttributeValues.color));0<=a.normal&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglNormalBuffer),g(a.normal),j.vertexAttribPointer(a.normal,3,j.FLOAT,!1,0,0));0<=a.tangent&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglTangentBuffer),g(a.tangent),j.vertexAttribPointer(a.tangent,4,j.FLOAT,!1,0,0));0<=a.uv&&(f.geometry.faceVertexUvs[0]?(j.bindBuffer(j.ARRAY_BUFFER,
+e.__webglUVBuffer),g(a.uv),j.vertexAttribPointer(a.uv,2,j.FLOAT,!1,0,0)):d.defaultAttributeValues&&j.vertexAttrib2fv(a.uv,d.defaultAttributeValues.uv));0<=a.uv2&&(f.geometry.faceVertexUvs[1]?(j.bindBuffer(j.ARRAY_BUFFER,e.__webglUV2Buffer),g(a.uv2),j.vertexAttribPointer(a.uv2,2,j.FLOAT,!1,0,0)):d.defaultAttributeValues&&j.vertexAttrib2fv(a.uv2,d.defaultAttributeValues.uv2));d.skinning&&(0<=a.skinIndex&&0<=a.skinWeight)&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglSkinIndicesBuffer),g(a.skinIndex),j.vertexAttribPointer(a.skinIndex,
+4,j.FLOAT,!1,0,0),j.bindBuffer(j.ARRAY_BUFFER,e.__webglSkinWeightsBuffer),g(a.skinWeight),j.vertexAttribPointer(a.skinWeight,4,j.FLOAT,!1,0,0));0<=a.lineDistance&&(j.bindBuffer(j.ARRAY_BUFFER,e.__webglLineDistanceBuffer),g(a.lineDistance),j.vertexAttribPointer(a.lineDistance,1,j.FLOAT,!1,0,0))}f instanceof THREE.Mesh?(d.wireframe?(B(d.wireframeLinewidth),b&&j.bindBuffer(j.ELEMENT_ARRAY_BUFFER,e.__webglLineBuffer),j.drawElements(j.LINES,e.__webglLineCount,j.UNSIGNED_SHORT,0)):(b&&j.bindBuffer(j.ELEMENT_ARRAY_BUFFER,
+e.__webglFaceBuffer),j.drawElements(j.TRIANGLES,e.__webglFaceCount,j.UNSIGNED_SHORT,0)),I.info.render.calls++,I.info.render.vertices+=e.__webglFaceCount,I.info.render.faces+=e.__webglFaceCount/3):f instanceof THREE.Line?(f=f.type===THREE.LineStrip?j.LINE_STRIP:j.LINES,B(d.linewidth),j.drawArrays(f,0,e.__webglLineCount),I.info.render.calls++):f instanceof THREE.ParticleSystem&&(j.drawArrays(j.POINTS,0,e.__webglParticleCount),I.info.render.calls++,I.info.render.points+=e.__webglParticleCount)}};this.render=
+function(a,b,c,d){if(!1===b instanceof THREE.Camera)console.error("THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.");else{var e,f,g,h,k=a.__lights,m=a.fog;Ea=-1;Za=!0;!0===a.autoUpdate&&a.updateMatrixWorld();void 0===b.parent&&b.updateMatrixWorld();b.matrixWorldInverse.getInverse(b.matrixWorld);V.multiplyMatrices(b.projectionMatrix,b.matrixWorldInverse);ya.setFromMatrix(V);this.autoUpdateObjects&&this.initWebGLObjects(a);l(this.renderPluginsPre,a,b);I.info.render.calls=0;I.info.render.vertices=
+0;I.info.render.faces=0;I.info.render.points=0;this.setRenderTarget(c);(this.autoClear||d)&&this.clear(this.autoClearColor,this.autoClearDepth,this.autoClearStencil);h=a.__webglObjects;d=0;for(e=h.length;d<e;d++)if(f=h[d],g=f.object,f.id=d,f.render=!1,g.visible&&(!(g instanceof THREE.Mesh||g instanceof THREE.ParticleSystem)||!g.frustumCulled||ya.intersectsObject(g))){var q=g;q._modelViewMatrix.multiplyMatrices(b.matrixWorldInverse,q.matrixWorld);q._normalMatrix.getNormalMatrix(q._modelViewMatrix);
+var q=f,r=q.buffer,t=void 0,s=t=void 0,s=q.object.material;if(s instanceof THREE.MeshFaceMaterial)t=r.materialIndex,t=s.materials[t],t.transparent?(q.transparent=t,q.opaque=null):(q.opaque=t,q.transparent=null);else if(t=s)t.transparent?(q.transparent=t,q.opaque=null):(q.opaque=t,q.transparent=null);f.render=!0;!0===this.sortObjects&&(null!==g.renderDepth?f.z=g.renderDepth:(ja.setFromMatrixPosition(g.matrixWorld),ja.applyProjection(V),f.z=ja.z))}this.sortObjects&&h.sort(i);h=a.__webglObjectsImmediate;
+d=0;for(e=h.length;d<e;d++)f=h[d],g=f.object,g.visible&&(g._modelViewMatrix.multiplyMatrices(b.matrixWorldInverse,g.matrixWorld),g._normalMatrix.getNormalMatrix(g._modelViewMatrix),g=f.object.material,g.transparent?(f.transparent=g,f.opaque=null):(f.opaque=g,f.transparent=null));a.overrideMaterial?(d=a.overrideMaterial,this.setBlending(d.blending,d.blendEquation,d.blendSrc,d.blendDst),this.setDepthTest(d.depthTest),this.setDepthWrite(d.depthWrite),x(d.polygonOffset,d.polygonOffsetFactor,d.polygonOffsetUnits),
+n(a.__webglObjects,!1,"",b,k,m,!0,d),p(a.__webglObjectsImmediate,"",b,k,m,!1,d)):(d=null,this.setBlending(THREE.NoBlending),n(a.__webglObjects,!0,"opaque",b,k,m,!1,d),p(a.__webglObjectsImmediate,"opaque",b,k,m,!1,d),n(a.__webglObjects,!1,"transparent",b,k,m,!0,d),p(a.__webglObjectsImmediate,"transparent",b,k,m,!0,d));l(this.renderPluginsPost,a,b);c&&(c.generateMipmaps&&c.minFilter!==THREE.NearestFilter&&c.minFilter!==THREE.LinearFilter)&&(c instanceof THREE.WebGLRenderTargetCube?(j.bindTexture(j.TEXTURE_CUBE_MAP,
+c.__webglTexture),j.generateMipmap(j.TEXTURE_CUBE_MAP),j.bindTexture(j.TEXTURE_CUBE_MAP,null)):(j.bindTexture(j.TEXTURE_2D,c.__webglTexture),j.generateMipmap(j.TEXTURE_2D),j.bindTexture(j.TEXTURE_2D,null)));this.setDepthTest(!0);this.setDepthWrite(!0)}};this.renderImmediateObject=function(a,b,c,d,e){var f=z(a,b,c,d,e);za=-1;I.setMaterialFaces(d);e.immediateRenderCallback?e.immediateRenderCallback(f,j,ya):e.render(function(a){I.renderBufferImmediate(a,f,d)})};this.initWebGLObjects=function(a){a.__webglObjects||
+(a.__webglObjects=[],a.__webglObjectsImmediate=[],a.__webglSprites=[],a.__webglFlares=[]);for(;a.__objectsAdded.length;)t(a.__objectsAdded[0],a),a.__objectsAdded.splice(0,1);for(;a.__objectsRemoved.length;)u(a.__objectsRemoved[0],a),a.__objectsRemoved.splice(0,1);for(var b=0,g=a.__webglObjects.length;b<g;b++){var h=a.__webglObjects[b].object;void 0===h.__webglInit&&(void 0!==h.__webglActive&&u(h,a),t(h,a));var i=h,l=i.geometry,n=void 0,p=void 0,r=void 0;if(l instanceof THREE.BufferGeometry){var s=
+j.DYNAMIC_DRAW,v=!l.dynamic,z=l.attributes,y=void 0,x=void 0;for(y in z)x=z[y],x.needsUpdate&&("index"===y?(j.bindBuffer(j.ELEMENT_ARRAY_BUFFER,x.buffer),j.bufferData(j.ELEMENT_ARRAY_BUFFER,x.array,s)):(j.bindBuffer(j.ARRAY_BUFFER,x.buffer),j.bufferData(j.ARRAY_BUFFER,x.array,s)),x.needsUpdate=!1),v&&!x.dynamic&&(x.array=null)}else if(i instanceof THREE.Mesh){for(var B=0,C=l.geometryGroupsList.length;B<C;B++)if(n=l.geometryGroupsList[B],r=d(i,n),l.buffersNeedUpdate&&c(n,i),p=r.attributes&&m(r),l.verticesNeedUpdate||
+l.morphTargetsNeedUpdate||l.elementsNeedUpdate||l.uvsNeedUpdate||l.normalsNeedUpdate||l.colorsNeedUpdate||l.tangentsNeedUpdate||p){var w=n,D=i,F=j.DYNAMIC_DRAW,E=!l.dynamic,G=r;if(w.__inittedArrays){var H=e(G),L=G.vertexColors?G.vertexColors:!1,N=f(G),O=H===THREE.SmoothShading,A=void 0,I=void 0,Q=void 0,K=void 0,P=void 0,U=void 0,T=void 0,W=void 0,oa=void 0,va=void 0,sa=void 0,Y=void 0,Z=void 0,X=void 0,Ea=void 0,Ta=void 0,ba=void 0,da=void 0,ca=void 0,za=void 0,Ja=void 0,ea=void 0,fa=void 0,ha=void 0,
+Ha=void 0,la=void 0,Aa=void 0,qa=void 0,ua=void 0,Ia=void 0,Fa=void 0,Ba=void 0,Ka=void 0,Ga=void 0,ia=void 0,Na=void 0,ta=void 0,wa=void 0,Ma=void 0,Oa=void 0,xa=0,ya=0,Pa=0,Ua=0,Va=0,ib=0,Ya=0,La=0,eb=0,ra=0,Ca=0,M=0,Xa=void 0,jb=w.__vertexArray,Za=w.__uvArray,db=w.__uv2Array,Wa=w.__normalArray,$a=w.__tangentArray,kb=w.__colorArray,ab=w.__skinIndexArray,bb=w.__skinWeightArray,hb=w.__morphTargetsArrays,xb=w.__morphNormalsArrays,tb=w.__webglCustomAttributesList,J=void 0,Nb=w.__faceArray,ub=w.__lineArray,
+Qa=D.geometry,Bb=Qa.elementsNeedUpdate,Ab=Qa.uvsNeedUpdate,Jb=Qa.normalsNeedUpdate,Kb=Qa.tangentsNeedUpdate,Lb=Qa.colorsNeedUpdate,Mb=Qa.morphTargetsNeedUpdate,bc=Qa.vertices,aa=w.faces3,lb=Qa.faces,Cb=Qa.faceVertexUvs[0],Db=Qa.faceVertexUvs[1],cc=Qa.skinIndices,Ob=Qa.skinWeights,Pb=Qa.morphTargets,Eb=Qa.morphNormals;if(Qa.verticesNeedUpdate){A=0;for(I=aa.length;A<I;A++)K=lb[aa[A]],Y=bc[K.a],Z=bc[K.b],X=bc[K.c],jb[ya]=Y.x,jb[ya+1]=Y.y,jb[ya+2]=Y.z,jb[ya+3]=Z.x,jb[ya+4]=Z.y,jb[ya+5]=Z.z,jb[ya+6]=X.x,
+jb[ya+7]=X.y,jb[ya+8]=X.z,ya+=9;j.bindBuffer(j.ARRAY_BUFFER,w.__webglVertexBuffer);j.bufferData(j.ARRAY_BUFFER,jb,F)}if(Mb){ia=0;for(Na=Pb.length;ia<Na;ia++){A=Ca=0;for(I=aa.length;A<I;A++)Ma=aa[A],K=lb[Ma],Y=Pb[ia].vertices[K.a],Z=Pb[ia].vertices[K.b],X=Pb[ia].vertices[K.c],ta=hb[ia],ta[Ca]=Y.x,ta[Ca+1]=Y.y,ta[Ca+2]=Y.z,ta[Ca+3]=Z.x,ta[Ca+4]=Z.y,ta[Ca+5]=Z.z,ta[Ca+6]=X.x,ta[Ca+7]=X.y,ta[Ca+8]=X.z,G.morphNormals&&(O?(Oa=Eb[ia].vertexNormals[Ma],da=Oa.a,ca=Oa.b,za=Oa.c):za=ca=da=Eb[ia].faceNormals[Ma],
+wa=xb[ia],wa[Ca]=da.x,wa[Ca+1]=da.y,wa[Ca+2]=da.z,wa[Ca+3]=ca.x,wa[Ca+4]=ca.y,wa[Ca+5]=ca.z,wa[Ca+6]=za.x,wa[Ca+7]=za.y,wa[Ca+8]=za.z),Ca+=9;j.bindBuffer(j.ARRAY_BUFFER,w.__webglMorphTargetsBuffers[ia]);j.bufferData(j.ARRAY_BUFFER,hb[ia],F);G.morphNormals&&(j.bindBuffer(j.ARRAY_BUFFER,w.__webglMorphNormalsBuffers[ia]),j.bufferData(j.ARRAY_BUFFER,xb[ia],F))}}if(Ob.length){A=0;for(I=aa.length;A<I;A++)K=lb[aa[A]],ha=Ob[K.a],Ha=Ob[K.b],la=Ob[K.c],bb[ra]=ha.x,bb[ra+1]=ha.y,bb[ra+2]=ha.z,bb[ra+3]=ha.w,
+bb[ra+4]=Ha.x,bb[ra+5]=Ha.y,bb[ra+6]=Ha.z,bb[ra+7]=Ha.w,bb[ra+8]=la.x,bb[ra+9]=la.y,bb[ra+10]=la.z,bb[ra+11]=la.w,Aa=cc[K.a],qa=cc[K.b],ua=cc[K.c],ab[ra]=Aa.x,ab[ra+1]=Aa.y,ab[ra+2]=Aa.z,ab[ra+3]=Aa.w,ab[ra+4]=qa.x,ab[ra+5]=qa.y,ab[ra+6]=qa.z,ab[ra+7]=qa.w,ab[ra+8]=ua.x,ab[ra+9]=ua.y,ab[ra+10]=ua.z,ab[ra+11]=ua.w,ra+=12;0<ra&&(j.bindBuffer(j.ARRAY_BUFFER,w.__webglSkinIndicesBuffer),j.bufferData(j.ARRAY_BUFFER,ab,F),j.bindBuffer(j.ARRAY_BUFFER,w.__webglSkinWeightsBuffer),j.bufferData(j.ARRAY_BUFFER,
+bb,F))}if(Lb&&L){A=0;for(I=aa.length;A<I;A++)K=lb[aa[A]],T=K.vertexColors,W=K.color,3===T.length&&L===THREE.VertexColors?(Ja=T[0],ea=T[1],fa=T[2]):fa=ea=Ja=W,kb[eb]=Ja.r,kb[eb+1]=Ja.g,kb[eb+2]=Ja.b,kb[eb+3]=ea.r,kb[eb+4]=ea.g,kb[eb+5]=ea.b,kb[eb+6]=fa.r,kb[eb+7]=fa.g,kb[eb+8]=fa.b,eb+=9;0<eb&&(j.bindBuffer(j.ARRAY_BUFFER,w.__webglColorBuffer),j.bufferData(j.ARRAY_BUFFER,kb,F))}if(Kb&&Qa.hasTangents){A=0;for(I=aa.length;A<I;A++)K=lb[aa[A]],oa=K.vertexTangents,Ea=oa[0],Ta=oa[1],ba=oa[2],$a[Ya]=Ea.x,
+$a[Ya+1]=Ea.y,$a[Ya+2]=Ea.z,$a[Ya+3]=Ea.w,$a[Ya+4]=Ta.x,$a[Ya+5]=Ta.y,$a[Ya+6]=Ta.z,$a[Ya+7]=Ta.w,$a[Ya+8]=ba.x,$a[Ya+9]=ba.y,$a[Ya+10]=ba.z,$a[Ya+11]=ba.w,Ya+=12;j.bindBuffer(j.ARRAY_BUFFER,w.__webglTangentBuffer);j.bufferData(j.ARRAY_BUFFER,$a,F)}if(Jb&&H){A=0;for(I=aa.length;A<I;A++)if(K=lb[aa[A]],P=K.vertexNormals,U=K.normal,3===P.length&&O)for(Ia=0;3>Ia;Ia++)Ba=P[Ia],Wa[ib]=Ba.x,Wa[ib+1]=Ba.y,Wa[ib+2]=Ba.z,ib+=3;else for(Ia=0;3>Ia;Ia++)Wa[ib]=U.x,Wa[ib+1]=U.y,Wa[ib+2]=U.z,ib+=3;j.bindBuffer(j.ARRAY_BUFFER,
+w.__webglNormalBuffer);j.bufferData(j.ARRAY_BUFFER,Wa,F)}if(Ab&&Cb&&N){A=0;for(I=aa.length;A<I;A++)if(Q=aa[A],va=Cb[Q],void 0!==va)for(Ia=0;3>Ia;Ia++)Ka=va[Ia],Za[Pa]=Ka.x,Za[Pa+1]=Ka.y,Pa+=2;0<Pa&&(j.bindBuffer(j.ARRAY_BUFFER,w.__webglUVBuffer),j.bufferData(j.ARRAY_BUFFER,Za,F))}if(Ab&&Db&&N){A=0;for(I=aa.length;A<I;A++)if(Q=aa[A],sa=Db[Q],void 0!==sa)for(Ia=0;3>Ia;Ia++)Ga=sa[Ia],db[Ua]=Ga.x,db[Ua+1]=Ga.y,Ua+=2;0<Ua&&(j.bindBuffer(j.ARRAY_BUFFER,w.__webglUV2Buffer),j.bufferData(j.ARRAY_BUFFER,db,
+F))}if(Bb){A=0;for(I=aa.length;A<I;A++)Nb[Va]=xa,Nb[Va+1]=xa+1,Nb[Va+2]=xa+2,Va+=3,ub[La]=xa,ub[La+1]=xa+1,ub[La+2]=xa,ub[La+3]=xa+2,ub[La+4]=xa+1,ub[La+5]=xa+2,La+=6,xa+=3;j.bindBuffer(j.ELEMENT_ARRAY_BUFFER,w.__webglFaceBuffer);j.bufferData(j.ELEMENT_ARRAY_BUFFER,Nb,F);j.bindBuffer(j.ELEMENT_ARRAY_BUFFER,w.__webglLineBuffer);j.bufferData(j.ELEMENT_ARRAY_BUFFER,ub,F)}if(tb){Ia=0;for(Fa=tb.length;Ia<Fa;Ia++)if(J=tb[Ia],J.__original.needsUpdate){M=0;if(1===J.size)if(void 0===J.boundTo||"vertices"===
+J.boundTo){A=0;for(I=aa.length;A<I;A++)K=lb[aa[A]],J.array[M]=J.value[K.a],J.array[M+1]=J.value[K.b],J.array[M+2]=J.value[K.c],M+=3}else{if("faces"===J.boundTo){A=0;for(I=aa.length;A<I;A++)Xa=J.value[aa[A]],J.array[M]=Xa,J.array[M+1]=Xa,J.array[M+2]=Xa,M+=3}}else if(2===J.size)if(void 0===J.boundTo||"vertices"===J.boundTo){A=0;for(I=aa.length;A<I;A++)K=lb[aa[A]],Y=J.value[K.a],Z=J.value[K.b],X=J.value[K.c],J.array[M]=Y.x,J.array[M+1]=Y.y,J.array[M+2]=Z.x,J.array[M+3]=Z.y,J.array[M+4]=X.x,J.array[M+
+5]=X.y,M+=6}else{if("faces"===J.boundTo){A=0;for(I=aa.length;A<I;A++)X=Z=Y=Xa=J.value[aa[A]],J.array[M]=Y.x,J.array[M+1]=Y.y,J.array[M+2]=Z.x,J.array[M+3]=Z.y,J.array[M+4]=X.x,J.array[M+5]=X.y,M+=6}}else if(3===J.size){var na;na="c"===J.type?["r","g","b"]:["x","y","z"];if(void 0===J.boundTo||"vertices"===J.boundTo){A=0;for(I=aa.length;A<I;A++)K=lb[aa[A]],Y=J.value[K.a],Z=J.value[K.b],X=J.value[K.c],J.array[M]=Y[na[0]],J.array[M+1]=Y[na[1]],J.array[M+2]=Y[na[2]],J.array[M+3]=Z[na[0]],J.array[M+4]=
+Z[na[1]],J.array[M+5]=Z[na[2]],J.array[M+6]=X[na[0]],J.array[M+7]=X[na[1]],J.array[M+8]=X[na[2]],M+=9}else if("faces"===J.boundTo){A=0;for(I=aa.length;A<I;A++)X=Z=Y=Xa=J.value[aa[A]],J.array[M]=Y[na[0]],J.array[M+1]=Y[na[1]],J.array[M+2]=Y[na[2]],J.array[M+3]=Z[na[0]],J.array[M+4]=Z[na[1]],J.array[M+5]=Z[na[2]],J.array[M+6]=X[na[0]],J.array[M+7]=X[na[1]],J.array[M+8]=X[na[2]],M+=9}else if("faceVertices"===J.boundTo){A=0;for(I=aa.length;A<I;A++)Xa=J.value[aa[A]],Y=Xa[0],Z=Xa[1],X=Xa[2],J.array[M]=
+Y[na[0]],J.array[M+1]=Y[na[1]],J.array[M+2]=Y[na[2]],J.array[M+3]=Z[na[0]],J.array[M+4]=Z[na[1]],J.array[M+5]=Z[na[2]],J.array[M+6]=X[na[0]],J.array[M+7]=X[na[1]],J.array[M+8]=X[na[2]],M+=9}}else if(4===J.size)if(void 0===J.boundTo||"vertices"===J.boundTo){A=0;for(I=aa.length;A<I;A++)K=lb[aa[A]],Y=J.value[K.a],Z=J.value[K.b],X=J.value[K.c],J.array[M]=Y.x,J.array[M+1]=Y.y,J.array[M+2]=Y.z,J.array[M+3]=Y.w,J.array[M+4]=Z.x,J.array[M+5]=Z.y,J.array[M+6]=Z.z,J.array[M+7]=Z.w,J.array[M+8]=X.x,J.array[M+
+9]=X.y,J.array[M+10]=X.z,J.array[M+11]=X.w,M+=12}else if("faces"===J.boundTo){A=0;for(I=aa.length;A<I;A++)X=Z=Y=Xa=J.value[aa[A]],J.array[M]=Y.x,J.array[M+1]=Y.y,J.array[M+2]=Y.z,J.array[M+3]=Y.w,J.array[M+4]=Z.x,J.array[M+5]=Z.y,J.array[M+6]=Z.z,J.array[M+7]=Z.w,J.array[M+8]=X.x,J.array[M+9]=X.y,J.array[M+10]=X.z,J.array[M+11]=X.w,M+=12}else if("faceVertices"===J.boundTo){A=0;for(I=aa.length;A<I;A++)Xa=J.value[aa[A]],Y=Xa[0],Z=Xa[1],X=Xa[2],J.array[M]=Y.x,J.array[M+1]=Y.y,J.array[M+2]=Y.z,J.array[M+
+3]=Y.w,J.array[M+4]=Z.x,J.array[M+5]=Z.y,J.array[M+6]=Z.z,J.array[M+7]=Z.w,J.array[M+8]=X.x,J.array[M+9]=X.y,J.array[M+10]=X.z,J.array[M+11]=X.w,M+=12}j.bindBuffer(j.ARRAY_BUFFER,J.buffer);j.bufferData(j.ARRAY_BUFFER,J.array,F)}}E&&(delete w.__inittedArrays,delete w.__colorArray,delete w.__normalArray,delete w.__tangentArray,delete w.__uvArray,delete w.__uv2Array,delete w.__faceArray,delete w.__vertexArray,delete w.__lineArray,delete w.__skinIndexArray,delete w.__skinWeightArray)}}l.verticesNeedUpdate=
+!1;l.morphTargetsNeedUpdate=!1;l.elementsNeedUpdate=!1;l.uvsNeedUpdate=!1;l.normalsNeedUpdate=!1;l.colorsNeedUpdate=!1;l.tangentsNeedUpdate=!1;l.buffersNeedUpdate=!1;r.attributes&&q(r)}else if(i instanceof THREE.Line){r=d(i,l);p=r.attributes&&m(r);if(l.verticesNeedUpdate||l.colorsNeedUpdate||l.lineDistancesNeedUpdate||p){var cb=l,Qb=j.DYNAMIC_DRAW,Fb=void 0,Gb=void 0,Hb=void 0,Rb=void 0,ma=void 0,Sb=void 0,hc=cb.vertices,ic=cb.colors,jc=cb.lineDistances,Xb=hc.length,Yb=ic.length,Zb=jc.length,Tb=cb.__vertexArray,
+Ub=cb.__colorArray,kc=cb.__lineDistanceArray,$b=cb.colorsNeedUpdate,ac=cb.lineDistancesNeedUpdate,dc=cb.__webglCustomAttributesList,Vb=void 0,lc=void 0,Da=void 0,yb=void 0,Ra=void 0,ka=void 0;if(cb.verticesNeedUpdate){for(Fb=0;Fb<Xb;Fb++)Rb=hc[Fb],ma=3*Fb,Tb[ma]=Rb.x,Tb[ma+1]=Rb.y,Tb[ma+2]=Rb.z;j.bindBuffer(j.ARRAY_BUFFER,cb.__webglVertexBuffer);j.bufferData(j.ARRAY_BUFFER,Tb,Qb)}if($b){for(Gb=0;Gb<Yb;Gb++)Sb=ic[Gb],ma=3*Gb,Ub[ma]=Sb.r,Ub[ma+1]=Sb.g,Ub[ma+2]=Sb.b;j.bindBuffer(j.ARRAY_BUFFER,cb.__webglColorBuffer);
+j.bufferData(j.ARRAY_BUFFER,Ub,Qb)}if(ac){for(Hb=0;Hb<Zb;Hb++)kc[Hb]=jc[Hb];j.bindBuffer(j.ARRAY_BUFFER,cb.__webglLineDistanceBuffer);j.bufferData(j.ARRAY_BUFFER,kc,Qb)}if(dc){Vb=0;for(lc=dc.length;Vb<lc;Vb++)if(ka=dc[Vb],ka.needsUpdate&&(void 0===ka.boundTo||"vertices"===ka.boundTo)){ma=0;yb=ka.value.length;if(1===ka.size)for(Da=0;Da<yb;Da++)ka.array[Da]=ka.value[Da];else if(2===ka.size)for(Da=0;Da<yb;Da++)Ra=ka.value[Da],ka.array[ma]=Ra.x,ka.array[ma+1]=Ra.y,ma+=2;else if(3===ka.size)if("c"===ka.type)for(Da=
+0;Da<yb;Da++)Ra=ka.value[Da],ka.array[ma]=Ra.r,ka.array[ma+1]=Ra.g,ka.array[ma+2]=Ra.b,ma+=3;else for(Da=0;Da<yb;Da++)Ra=ka.value[Da],ka.array[ma]=Ra.x,ka.array[ma+1]=Ra.y,ka.array[ma+2]=Ra.z,ma+=3;else if(4===ka.size)for(Da=0;Da<yb;Da++)Ra=ka.value[Da],ka.array[ma]=Ra.x,ka.array[ma+1]=Ra.y,ka.array[ma+2]=Ra.z,ka.array[ma+3]=Ra.w,ma+=4;j.bindBuffer(j.ARRAY_BUFFER,ka.buffer);j.bufferData(j.ARRAY_BUFFER,ka.array,Qb)}}}l.verticesNeedUpdate=!1;l.colorsNeedUpdate=!1;l.lineDistancesNeedUpdate=!1;r.attributes&&
+q(r)}else if(i instanceof THREE.ParticleSystem){r=d(i,l);p=r.attributes&&m(r);if(l.verticesNeedUpdate||l.colorsNeedUpdate||i.sortParticles||p){var mb=l,ec=j.DYNAMIC_DRAW,Ib=i,Sa=void 0,nb=void 0,ob=void 0,S=void 0,pb=void 0,sb=void 0,Wb=mb.vertices,fc=Wb.length,gc=mb.colors,mc=gc.length,vb=mb.__vertexArray,wb=mb.__colorArray,qb=mb.__sortArray,nc=mb.verticesNeedUpdate,oc=mb.colorsNeedUpdate,rb=mb.__webglCustomAttributesList,fb=void 0,zb=void 0,$=void 0,gb=void 0,ga=void 0,R=void 0;if(Ib.sortParticles){pa.copy(V);
+pa.multiply(Ib.matrixWorld);for(Sa=0;Sa<fc;Sa++)ob=Wb[Sa],ja.copy(ob),ja.applyProjection(pa),qb[Sa]=[ja.z,Sa];qb.sort(k);for(Sa=0;Sa<fc;Sa++)ob=Wb[qb[Sa][1]],S=3*Sa,vb[S]=ob.x,vb[S+1]=ob.y,vb[S+2]=ob.z;for(nb=0;nb<mc;nb++)S=3*nb,sb=gc[qb[nb][1]],wb[S]=sb.r,wb[S+1]=sb.g,wb[S+2]=sb.b;if(rb){fb=0;for(zb=rb.length;fb<zb;fb++)if(R=rb[fb],void 0===R.boundTo||"vertices"===R.boundTo)if(S=0,gb=R.value.length,1===R.size)for($=0;$<gb;$++)pb=qb[$][1],R.array[$]=R.value[pb];else if(2===R.size)for($=0;$<gb;$++)pb=
+qb[$][1],ga=R.value[pb],R.array[S]=ga.x,R.array[S+1]=ga.y,S+=2;else if(3===R.size)if("c"===R.type)for($=0;$<gb;$++)pb=qb[$][1],ga=R.value[pb],R.array[S]=ga.r,R.array[S+1]=ga.g,R.array[S+2]=ga.b,S+=3;else for($=0;$<gb;$++)pb=qb[$][1],ga=R.value[pb],R.array[S]=ga.x,R.array[S+1]=ga.y,R.array[S+2]=ga.z,S+=3;else if(4===R.size)for($=0;$<gb;$++)pb=qb[$][1],ga=R.value[pb],R.array[S]=ga.x,R.array[S+1]=ga.y,R.array[S+2]=ga.z,R.array[S+3]=ga.w,S+=4}}else{if(nc)for(Sa=0;Sa<fc;Sa++)ob=Wb[Sa],S=3*Sa,vb[S]=ob.x,
+vb[S+1]=ob.y,vb[S+2]=ob.z;if(oc)for(nb=0;nb<mc;nb++)sb=gc[nb],S=3*nb,wb[S]=sb.r,wb[S+1]=sb.g,wb[S+2]=sb.b;if(rb){fb=0;for(zb=rb.length;fb<zb;fb++)if(R=rb[fb],R.needsUpdate&&(void 0===R.boundTo||"vertices"===R.boundTo))if(gb=R.value.length,S=0,1===R.size)for($=0;$<gb;$++)R.array[$]=R.value[$];else if(2===R.size)for($=0;$<gb;$++)ga=R.value[$],R.array[S]=ga.x,R.array[S+1]=ga.y,S+=2;else if(3===R.size)if("c"===R.type)for($=0;$<gb;$++)ga=R.value[$],R.array[S]=ga.r,R.array[S+1]=ga.g,R.array[S+2]=ga.b,S+=
+3;else for($=0;$<gb;$++)ga=R.value[$],R.array[S]=ga.x,R.array[S+1]=ga.y,R.array[S+2]=ga.z,S+=3;else if(4===R.size)for($=0;$<gb;$++)ga=R.value[$],R.array[S]=ga.x,R.array[S+1]=ga.y,R.array[S+2]=ga.z,R.array[S+3]=ga.w,S+=4}}if(nc||Ib.sortParticles)j.bindBuffer(j.ARRAY_BUFFER,mb.__webglVertexBuffer),j.bufferData(j.ARRAY_BUFFER,vb,ec);if(oc||Ib.sortParticles)j.bindBuffer(j.ARRAY_BUFFER,mb.__webglColorBuffer),j.bufferData(j.ARRAY_BUFFER,wb,ec);if(rb){fb=0;for(zb=rb.length;fb<zb;fb++)if(R=rb[fb],R.needsUpdate||
+Ib.sortParticles)j.bindBuffer(j.ARRAY_BUFFER,R.buffer),j.bufferData(j.ARRAY_BUFFER,R.array,ec)}}l.verticesNeedUpdate=!1;l.colorsNeedUpdate=!1;r.attributes&&q(r)}}};this.initMaterial=function(a,b,c,d){var e,f,g,h;a.addEventListener("dispose",Cb);var i,k,l,m,n;a instanceof THREE.MeshDepthMaterial?n="depth":a instanceof THREE.MeshNormalMaterial?n="normal":a instanceof THREE.MeshBasicMaterial?n="basic":a instanceof THREE.MeshLambertMaterial?n="lambert":a instanceof THREE.MeshPhongMaterial?n="phong":a instanceof
+THREE.LineBasicMaterial?n="basic":a instanceof THREE.LineDashedMaterial?n="dashed":a instanceof THREE.ParticleSystemMaterial&&(n="particle_basic");if(n){var p=THREE.ShaderLib[n];a.uniforms=THREE.UniformsUtils.clone(p.uniforms);a.vertexShader=p.vertexShader;a.fragmentShader=p.fragmentShader}var q=e=0,r=0,t=p=0;for(f=b.length;t<f;t++)g=b[t],g.onlyShadow||(g instanceof THREE.DirectionalLight&&e++,g instanceof THREE.PointLight&&q++,g instanceof THREE.SpotLight&&r++,g instanceof THREE.HemisphereLight&&
+p++);f=q;g=r;h=p;r=p=0;for(q=b.length;r<q;r++)t=b[r],t.castShadow&&(t instanceof THREE.SpotLight&&p++,t instanceof THREE.DirectionalLight&&!t.shadowCascade&&p++);m=p;tb&&d&&d.useVertexTexture?l=1024:(b=j.getParameter(j.MAX_VERTEX_UNIFORM_VECTORS),b=Math.floor((b-20)/4),void 0!==d&&d instanceof THREE.SkinnedMesh&&(b=Math.min(d.bones.length,b),b<d.bones.length&&console.warn("WebGLRenderer: too many bones - "+d.bones.length+", this GPU supports just "+b+" (try OpenGL instead of ANGLE)")),l=b);a:{var r=
+a.fragmentShader,q=a.vertexShader,p=a.uniforms,b=a.attributes,t=a.defines,c={map:!!a.map,envMap:!!a.envMap,lightMap:!!a.lightMap,bumpMap:!!a.bumpMap,normalMap:!!a.normalMap,specularMap:!!a.specularMap,vertexColors:a.vertexColors,fog:c,useFog:a.fog,fogExp:c instanceof THREE.FogExp2,sizeAttenuation:a.sizeAttenuation,skinning:a.skinning,maxBones:l,useVertexTexture:tb&&d&&d.useVertexTexture,morphTargets:a.morphTargets,morphNormals:a.morphNormals,maxMorphTargets:this.maxMorphTargets,maxMorphNormals:this.maxMorphNormals,
+maxDirLights:e,maxPointLights:f,maxSpotLights:g,maxHemiLights:h,maxShadows:m,shadowMapEnabled:this.shadowMapEnabled&&d.receiveShadow,shadowMapType:this.shadowMapType,shadowMapDebug:this.shadowMapDebug,shadowMapCascade:this.shadowMapCascade,alphaTest:a.alphaTest,metal:a.metal,perPixel:a.perPixel,wrapAround:a.wrapAround,doubleSided:a.side===THREE.DoubleSide,flipSided:a.side===THREE.BackSide},d=a.index0AttributeName,s,u,v;e=[];n?e.push(n):(e.push(r),e.push(q));for(u in t)e.push(u),e.push(t[u]);for(s in c)e.push(s),
+e.push(c[s]);n=e.join();s=0;for(u=P.length;s<u;s++)if(e=P[s],e.code===n){e.usedTimes++;k=e.program;break a}s="SHADOWMAP_TYPE_BASIC";c.shadowMapType===THREE.PCFShadowMap?s="SHADOWMAP_TYPE_PCF":c.shadowMapType===THREE.PCFSoftShadowMap&&(s="SHADOWMAP_TYPE_PCF_SOFT");u=[];for(v in t)e=t[v],!1!==e&&(e="#define "+v+" "+e,u.push(e));e=u.join("\n");v=j.createProgram();u=["precision "+K+" float;","precision "+K+" int;",e,xb?"#define VERTEX_TEXTURES":"",I.gammaInput?"#define GAMMA_INPUT":"",I.gammaOutput?"#define GAMMA_OUTPUT":
+"","#define MAX_DIR_LIGHTS "+c.maxDirLights,"#define MAX_POINT_LIGHTS "+c.maxPointLights,"#define MAX_SPOT_LIGHTS "+c.maxSpotLights,"#define MAX_HEMI_LIGHTS "+c.maxHemiLights,"#define MAX_SHADOWS "+c.maxShadows,"#define MAX_BONES "+c.maxBones,c.map?"#define USE_MAP":"",c.envMap?"#define USE_ENVMAP":"",c.lightMap?"#define USE_LIGHTMAP":"",c.bumpMap?"#define USE_BUMPMAP":"",c.normalMap?"#define USE_NORMALMAP":"",c.specularMap?"#define USE_SPECULARMAP":"",c.vertexColors?"#define USE_COLOR":"",c.skinning?
+"#define USE_SKINNING":"",c.useVertexTexture?"#define BONE_TEXTURE":"",c.morphTargets?"#define USE_MORPHTARGETS":"",c.morphNormals?"#define USE_MORPHNORMALS":"",c.perPixel?"#define PHONG_PER_PIXEL":"",c.wrapAround?"#define WRAP_AROUND":"",c.doubleSided?"#define DOUBLE_SIDED":"",c.flipSided?"#define FLIP_SIDED":"",c.shadowMapEnabled?"#define USE_SHADOWMAP":"",c.shadowMapEnabled?"#define "+s:"",c.shadowMapDebug?"#define SHADOWMAP_DEBUG":"",c.shadowMapCascade?"#define SHADOWMAP_CASCADE":"",c.sizeAttenuation?
+"#define USE_SIZEATTENUATION":"","uniform mat4 modelMatrix;\nuniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform mat4 viewMatrix;\nuniform mat3 normalMatrix;\nuniform vec3 cameraPosition;\nattribute vec3 position;\nattribute vec3 normal;\nattribute vec2 uv;\nattribute vec2 uv2;\n#ifdef USE_COLOR\nattribute vec3 color;\n#endif\n#ifdef USE_MORPHTARGETS\nattribute vec3 morphTarget0;\nattribute vec3 morphTarget1;\nattribute vec3 morphTarget2;\nattribute vec3 morphTarget3;\n#ifdef USE_MORPHNORMALS\nattribute vec3 morphNormal0;\nattribute vec3 morphNormal1;\nattribute vec3 morphNormal2;\nattribute vec3 morphNormal3;\n#else\nattribute vec3 morphTarget4;\nattribute vec3 morphTarget5;\nattribute vec3 morphTarget6;\nattribute vec3 morphTarget7;\n#endif\n#endif\n#ifdef USE_SKINNING\nattribute vec4 skinIndex;\nattribute vec4 skinWeight;\n#endif\n"].join("\n");
+s=["precision "+K+" float;","precision "+K+" int;",c.bumpMap||c.normalMap?"#extension GL_OES_standard_derivatives : enable":"",e,"#define MAX_DIR_LIGHTS "+c.maxDirLights,"#define MAX_POINT_LIGHTS "+c.maxPointLights,"#define MAX_SPOT_LIGHTS "+c.maxSpotLights,"#define MAX_HEMI_LIGHTS "+c.maxHemiLights,"#define MAX_SHADOWS "+c.maxShadows,c.alphaTest?"#define ALPHATEST "+c.alphaTest:"",I.gammaInput?"#define GAMMA_INPUT":"",I.gammaOutput?"#define GAMMA_OUTPUT":"",c.useFog&&c.fog?"#define USE_FOG":"",c.useFog&&
+c.fogExp?"#define FOG_EXP2":"",c.map?"#define USE_MAP":"",c.envMap?"#define USE_ENVMAP":"",c.lightMap?"#define USE_LIGHTMAP":"",c.bumpMap?"#define USE_BUMPMAP":"",c.normalMap?"#define USE_NORMALMAP":"",c.specularMap?"#define USE_SPECULARMAP":"",c.vertexColors?"#define USE_COLOR":"",c.metal?"#define METAL":"",c.perPixel?"#define PHONG_PER_PIXEL":"",c.wrapAround?"#define WRAP_AROUND":"",c.doubleSided?"#define DOUBLE_SIDED":"",c.flipSided?"#define FLIP_SIDED":"",c.shadowMapEnabled?"#define USE_SHADOWMAP":
+"",c.shadowMapEnabled?"#define "+s:"",c.shadowMapDebug?"#define SHADOWMAP_DEBUG":"",c.shadowMapCascade?"#define SHADOWMAP_CASCADE":"","uniform mat4 viewMatrix;\nuniform vec3 cameraPosition;\n"].join("\n");u=L("vertex",u+q);s=L("fragment",s+r);j.attachShader(v,u);j.attachShader(v,s);d&&j.bindAttribLocation(v,0,d);j.linkProgram(v);j.getProgramParameter(v,j.LINK_STATUS)||(console.error("Could not initialise shader\nVALIDATE_STATUS: "+j.getProgramParameter(v,j.VALIDATE_STATUS)+", gl error ["+j.getError()+
+"]"),console.error("Program Info Log: "+j.getProgramInfoLog(v)));j.deleteShader(s);j.deleteShader(u);v.uniforms={};v.attributes={};var w;s="viewMatrix modelViewMatrix projectionMatrix normalMatrix modelMatrix cameraPosition morphTargetInfluences".split(" ");c.useVertexTexture?(s.push("boneTexture"),s.push("boneTextureWidth"),s.push("boneTextureHeight")):s.push("boneGlobalMatrices");for(w in p)s.push(w);w=s;s=0;for(u=w.length;s<u;s++)p=w[s],v.uniforms[p]=j.getUniformLocation(v,p);s="position normal uv uv2 tangent color skinIndex skinWeight lineDistance".split(" ");
+for(w=0;w<c.maxMorphTargets;w++)s.push("morphTarget"+w);for(w=0;w<c.maxMorphNormals;w++)s.push("morphNormal"+w);for(k in b)s.push(k);k=s;w=0;for(b=k.length;w<b;w++)s=k[w],v.attributes[s]=j.getAttribLocation(v,s);v.id=Q++;P.push({program:v,code:n,usedTimes:1});I.info.memory.programs=P.length;k=v}a.program=k;w=a.program.attributes;if(a.morphTargets){a.numSupportedMorphTargets=0;b="morphTarget";for(k=0;k<this.maxMorphTargets;k++)v=b+k,0<=w[v]&&a.numSupportedMorphTargets++}if(a.morphNormals){a.numSupportedMorphNormals=
+0;b="morphNormal";for(k=0;k<this.maxMorphNormals;k++)v=b+k,0<=w[v]&&a.numSupportedMorphNormals++}a.uniformsList=[];for(i in a.uniforms)a.uniformsList.push([a.uniforms[i],i])};this.setFaceCulling=function(a,b){a===THREE.CullFaceNone?j.disable(j.CULL_FACE):(b===THREE.FrontFaceDirectionCW?j.frontFace(j.CW):j.frontFace(j.CCW),a===THREE.CullFaceBack?j.cullFace(j.BACK):a===THREE.CullFaceFront?j.cullFace(j.FRONT):j.cullFace(j.FRONT_AND_BACK),j.enable(j.CULL_FACE))};this.setMaterialFaces=function(a){var b=
+a.side===THREE.DoubleSide,a=a.side===THREE.BackSide;sa!==b&&(b?j.disable(j.CULL_FACE):j.enable(j.CULL_FACE),sa=b);Aa!==a&&(a?j.frontFace(j.CW):j.frontFace(j.CCW),Aa=a)};this.setDepthTest=function(a){ca!==a&&(a?j.enable(j.DEPTH_TEST):j.disable(j.DEPTH_TEST),ca=a)};this.setDepthWrite=function(a){wa!==a&&(j.depthMask(a),wa=a)};this.setBlending=function(a,b,c,d){a!==ba&&(a===THREE.NoBlending?j.disable(j.BLEND):a===THREE.AdditiveBlending?(j.enable(j.BLEND),j.blendEquation(j.FUNC_ADD),j.blendFunc(j.SRC_ALPHA,
+j.ONE)):a===THREE.SubtractiveBlending?(j.enable(j.BLEND),j.blendEquation(j.FUNC_ADD),j.blendFunc(j.ZERO,j.ONE_MINUS_SRC_COLOR)):a===THREE.MultiplyBlending?(j.enable(j.BLEND),j.blendEquation(j.FUNC_ADD),j.blendFunc(j.ZERO,j.SRC_COLOR)):a===THREE.CustomBlending?j.enable(j.BLEND):(j.enable(j.BLEND),j.blendEquationSeparate(j.FUNC_ADD,j.FUNC_ADD),j.blendFuncSeparate(j.SRC_ALPHA,j.ONE_MINUS_SRC_ALPHA,j.ONE,j.ONE_MINUS_SRC_ALPHA)),ba=a);if(a===THREE.CustomBlending){if(b!==W&&(j.blendEquation(D(b)),W=b),
+c!==Ka||d!==U)j.blendFunc(D(c),D(d)),Ka=c,U=d}else U=Ka=W=null};this.setTexture=function(a,b){if(a.needsUpdate){a.__webglInit||(a.__webglInit=!0,a.addEventListener("dispose",Bb),a.__webglTexture=j.createTexture(),I.info.memory.textures++);j.activeTexture(j.TEXTURE0+b);j.bindTexture(j.TEXTURE_2D,a.__webglTexture);j.pixelStorei(j.UNPACK_FLIP_Y_WEBGL,a.flipY);j.pixelStorei(j.UNPACK_PREMULTIPLY_ALPHA_WEBGL,a.premultiplyAlpha);j.pixelStorei(j.UNPACK_ALIGNMENT,a.unpackAlignment);var c=a.image,d=0===(c.width&
+c.width-1)&&0===(c.height&c.height-1),e=D(a.format),f=D(a.type);w(j.TEXTURE_2D,a,d);var g=a.mipmaps;if(a instanceof THREE.DataTexture)if(0<g.length&&d){for(var h=0,i=g.length;h<i;h++)c=g[h],j.texImage2D(j.TEXTURE_2D,h,e,c.width,c.height,0,e,f,c.data);a.generateMipmaps=!1}else j.texImage2D(j.TEXTURE_2D,0,e,c.width,c.height,0,e,f,c.data);else if(a instanceof THREE.CompressedTexture){h=0;for(i=g.length;h<i;h++)c=g[h],a.format!==THREE.RGBAFormat?j.compressedTexImage2D(j.TEXTURE_2D,h,e,c.width,c.height,
+0,c.data):j.texImage2D(j.TEXTURE_2D,h,e,c.width,c.height,0,e,f,c.data)}else if(0<g.length&&d){h=0;for(i=g.length;h<i;h++)c=g[h],j.texImage2D(j.TEXTURE_2D,h,e,e,f,c);a.generateMipmaps=!1}else j.texImage2D(j.TEXTURE_2D,0,e,e,f,a.image);a.generateMipmaps&&d&&j.generateMipmap(j.TEXTURE_2D);a.needsUpdate=!1;if(a.onUpdate)a.onUpdate()}else j.activeTexture(j.TEXTURE0+b),j.bindTexture(j.TEXTURE_2D,a.__webglTexture)};this.setRenderTarget=function(a){var b=a instanceof THREE.WebGLRenderTargetCube;if(a&&!a.__webglFramebuffer){void 0===
+a.depthBuffer&&(a.depthBuffer=!0);void 0===a.stencilBuffer&&(a.stencilBuffer=!0);a.addEventListener("dispose",Mb);a.__webglTexture=j.createTexture();I.info.memory.textures++;var c=0===(a.width&a.width-1)&&0===(a.height&a.height-1),d=D(a.format),e=D(a.type);if(b){a.__webglFramebuffer=[];a.__webglRenderbuffer=[];j.bindTexture(j.TEXTURE_CUBE_MAP,a.__webglTexture);w(j.TEXTURE_CUBE_MAP,a,c);for(var f=0;6>f;f++){a.__webglFramebuffer[f]=j.createFramebuffer();a.__webglRenderbuffer[f]=j.createRenderbuffer();
+j.texImage2D(j.TEXTURE_CUBE_MAP_POSITIVE_X+f,0,d,a.width,a.height,0,d,e,null);var g=a,h=j.TEXTURE_CUBE_MAP_POSITIVE_X+f;j.bindFramebuffer(j.FRAMEBUFFER,a.__webglFramebuffer[f]);j.framebufferTexture2D(j.FRAMEBUFFER,j.COLOR_ATTACHMENT0,h,g.__webglTexture,0);E(a.__webglRenderbuffer[f],a)}c&&j.generateMipmap(j.TEXTURE_CUBE_MAP)}else a.__webglFramebuffer=j.createFramebuffer(),a.__webglRenderbuffer=a.shareDepthFrom?a.shareDepthFrom.__webglRenderbuffer:j.createRenderbuffer(),j.bindTexture(j.TEXTURE_2D,a.__webglTexture),
+w(j.TEXTURE_2D,a,c),j.texImage2D(j.TEXTURE_2D,0,d,a.width,a.height,0,d,e,null),d=j.TEXTURE_2D,j.bindFramebuffer(j.FRAMEBUFFER,a.__webglFramebuffer),j.framebufferTexture2D(j.FRAMEBUFFER,j.COLOR_ATTACHMENT0,d,a.__webglTexture,0),a.shareDepthFrom?a.depthBuffer&&!a.stencilBuffer?j.framebufferRenderbuffer(j.FRAMEBUFFER,j.DEPTH_ATTACHMENT,j.RENDERBUFFER,a.__webglRenderbuffer):a.depthBuffer&&a.stencilBuffer&&j.framebufferRenderbuffer(j.FRAMEBUFFER,j.DEPTH_STENCIL_ATTACHMENT,j.RENDERBUFFER,a.__webglRenderbuffer):
+E(a.__webglRenderbuffer,a),c&&j.generateMipmap(j.TEXTURE_2D);b?j.bindTexture(j.TEXTURE_CUBE_MAP,null):j.bindTexture(j.TEXTURE_2D,null);j.bindRenderbuffer(j.RENDERBUFFER,null);j.bindFramebuffer(j.FRAMEBUFFER,null)}a?(b=b?a.__webglFramebuffer[a.activeCubeFace]:a.__webglFramebuffer,c=a.width,a=a.height,e=d=0):(b=null,c=Ba,a=xa,d=Fa,e=Ga);b!==va&&(j.bindFramebuffer(j.FRAMEBUFFER,b),j.viewport(d,e,c,a),va=b);Va=c;La=a};this.shadowMapPlugin=new THREE.ShadowMapPlugin;this.addPrePlugin(this.shadowMapPlugin);
+this.addPostPlugin(new THREE.SpritePlugin);this.addPostPlugin(new THREE.LensFlarePlugin)};THREE.WebGLRenderTarget=function(a,b,c){this.width=a;this.height=b;c=c||{};this.wrapS=void 0!==c.wrapS?c.wrapS:THREE.ClampToEdgeWrapping;this.wrapT=void 0!==c.wrapT?c.wrapT:THREE.ClampToEdgeWrapping;this.magFilter=void 0!==c.magFilter?c.magFilter:THREE.LinearFilter;this.minFilter=void 0!==c.minFilter?c.minFilter:THREE.LinearMipMapLinearFilter;this.anisotropy=void 0!==c.anisotropy?c.anisotropy:1;this.offset=new THREE.Vector2(0,0);this.repeat=new THREE.Vector2(1,1);this.format=void 0!==c.format?c.format:
+THREE.RGBAFormat;this.type=void 0!==c.type?c.type:THREE.UnsignedByteType;this.depthBuffer=void 0!==c.depthBuffer?c.depthBuffer:!0;this.stencilBuffer=void 0!==c.stencilBuffer?c.stencilBuffer:!0;this.generateMipmaps=!0;this.shareDepthFrom=null};
+THREE.WebGLRenderTarget.prototype={constructor:THREE.WebGLRenderTarget,clone:function(){var a=new THREE.WebGLRenderTarget(this.width,this.height);a.wrapS=this.wrapS;a.wrapT=this.wrapT;a.magFilter=this.magFilter;a.minFilter=this.minFilter;a.anisotropy=this.anisotropy;a.offset.copy(this.offset);a.repeat.copy(this.repeat);a.format=this.format;a.type=this.type;a.depthBuffer=this.depthBuffer;a.stencilBuffer=this.stencilBuffer;a.generateMipmaps=this.generateMipmaps;a.shareDepthFrom=this.shareDepthFrom;
+return a},dispose:function(){this.dispatchEvent({type:"dispose"})}};THREE.EventDispatcher.prototype.apply(THREE.WebGLRenderTarget.prototype);THREE.WebGLRenderTargetCube=function(a,b,c){THREE.WebGLRenderTarget.call(this,a,b,c);this.activeCubeFace=0};THREE.WebGLRenderTargetCube.prototype=Object.create(THREE.WebGLRenderTarget.prototype);THREE.RenderableVertex=function(){this.position=new THREE.Vector3;this.positionWorld=new THREE.Vector3;this.positionScreen=new THREE.Vector4;this.visible=!0};THREE.RenderableVertex.prototype.copy=function(a){this.positionWorld.copy(a.positionWorld);this.positionScreen.copy(a.positionScreen)};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.material=this.color=null;this.uvs=[[]];this.z=
+0};THREE.RenderableObject=function(){this.id=0;this.object=null;this.z=0};THREE.RenderableSprite=function(){this.id=0;this.object=null;this.rotation=this.z=this.y=this.x=0;this.scale=new THREE.Vector2;this.material=null};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};THREE.GeometryUtils={merge:function(a,b,c){var d,e,f=a.vertices.length,g=b instanceof THREE.Mesh?b.geometry:b,h=a.vertices,i=g.vertices,k=a.faces,l=g.faces,a=a.faceVertexUvs[0],g=g.faceVertexUvs[0];void 0===c&&(c=0);b instanceof THREE.Mesh&&(b.matrixAutoUpdate&&b.updateMatrix(),d=b.matrix,e=(new THREE.Matrix3).getNormalMatrix(d));for(var b=0,n=i.length;b<n;b++){var p=i[b].clone();d&&p.applyMatrix4(d);h.push(p)}b=0;for(n=l.length;b<n;b++){var p=l[b],t,r,m=p.vertexNormals,q=p.vertexColors;t=new THREE.Face3(p.a+
+f,p.b+f,p.c+f);t.normal.copy(p.normal);e&&t.normal.applyMatrix3(e).normalize();h=0;for(i=m.length;h<i;h++)r=m[h].clone(),e&&r.applyMatrix3(e).normalize(),t.vertexNormals.push(r);t.color.copy(p.color);h=0;for(i=q.length;h<i;h++)r=q[h],t.vertexColors.push(r.clone());t.materialIndex=p.materialIndex+c;t.centroid.copy(p.centroid);d&&t.centroid.applyMatrix4(d);k.push(t)}b=0;for(n=g.length;b<n;b++){c=g[b];d=[];h=0;for(i=c.length;h<i;h++)d.push(new THREE.Vector2(c[h].x,c[h].y));a.push(d)}},randomPointInTriangle:function(){var a=
+new THREE.Vector3;return function(b,c,d){var e=new THREE.Vector3,f=THREE.Math.random16(),g=THREE.Math.random16();1<f+g&&(f=1-f,g=1-g);var h=1-f-g;e.copy(b);e.multiplyScalar(f);a.copy(c);a.multiplyScalar(g);e.add(a);a.copy(d);a.multiplyScalar(h);e.add(a);return e}}(),randomPointInFace:function(a,b){return THREE.GeometryUtils.randomPointInTriangle(b.vertices[a.a],b.vertices[a.b],b.vertices[a.c])},randomPointsInGeometry:function(a,b){function c(a){function b(c,d){if(d<c)return c;var e=c+Math.floor((d-
+c)/2);return k[e]>a?b(c,e-1):k[e]<a?b(e+1,d):e}return b(0,k.length-1)}var d,e,f=a.faces,g=a.vertices,h=f.length,i=0,k=[],l,n,p;for(e=0;e<h;e++)d=f[e],l=g[d.a],n=g[d.b],p=g[d.c],d._area=THREE.GeometryUtils.triangleArea(l,n,p),i+=d._area,k[e]=i;d=[];for(e=0;e<b;e++)g=THREE.Math.random16()*i,g=c(g),d[e]=THREE.GeometryUtils.randomPointInFace(f[g],a,!0);return d},triangleArea:function(){var a=new THREE.Vector3,b=new THREE.Vector3;return function(c,d,e){a.subVectors(d,c);b.subVectors(e,c);a.cross(b);return 0.5*
+a.length()}}(),center:function(a){a.computeBoundingBox();var b=a.boundingBox,c=new THREE.Vector3;c.addVectors(b.min,b.max);c.multiplyScalar(-0.5);a.applyMatrix((new THREE.Matrix4).makeTranslation(c.x,c.y,c.z));a.computeBoundingBox();return c},triangulateQuads:function(a){var b,c,d,e,f=[],g=[];b=0;for(c=a.faceVertexUvs.length;b<c;b++)g[b]=[];b=0;for(c=a.faces.length;b<c;b++){f.push(a.faces[b]);d=0;for(e=a.faceVertexUvs.length;d<e;d++)g[d].push(a.faceVertexUvs[d][b])}a.faces=f;a.faceVertexUvs=g;a.computeCentroids();
+a.computeFaceNormals();a.computeVertexNormals();a.hasTangents&&a.computeTangents()}};THREE.ImageUtils={crossOrigin:void 0,loadTexture:function(a,b,c){var d=new THREE.ImageLoader;d.crossOrigin=this.crossOrigin;var e=new THREE.Texture(void 0,b),b=d.load(a,function(){e.needsUpdate=!0;c&&c(e)});e.image=b;e.sourceFile=a;return e},loadCompressedTexture:function(a,b,c,d){var e=new THREE.CompressedTexture;e.mapping=b;var f=new XMLHttpRequest;f.onload=function(){var a=THREE.ImageUtils.parseDDS(f.response,!0);e.format=a.format;e.mipmaps=a.mipmaps;e.image.width=a.width;e.image.height=a.height;
+e.generateMipmaps=!1;e.needsUpdate=!0;c&&c(e)};f.onerror=d;f.open("GET",a,!0);f.responseType="arraybuffer";f.send(null);return e},loadTextureCube:function(a,b,c,d){var e=[];e.loadCount=0;var f=new THREE.Texture;f.image=e;void 0!==b&&(f.mapping=b);f.flipY=!1;for(var b=0,g=a.length;b<g;++b){var h=new Image;e[b]=h;h.onload=function(){e.loadCount+=1;6===e.loadCount&&(f.needsUpdate=!0,c&&c(f))};h.onerror=d;h.crossOrigin=this.crossOrigin;h.src=a[b]}return f},loadCompressedTextureCube:function(a,b,c,d){var e=
+[];e.loadCount=0;var f=new THREE.CompressedTexture;f.image=e;void 0!==b&&(f.mapping=b);f.flipY=!1;f.generateMipmaps=!1;b=function(a,b){return function(){var d=THREE.ImageUtils.parseDDS(a.response,!0);b.format=d.format;b.mipmaps=d.mipmaps;b.width=d.width;b.height=d.height;e.loadCount+=1;6===e.loadCount&&(f.format=d.format,f.needsUpdate=!0,c&&c(f))}};if(a instanceof Array)for(var g=0,h=a.length;g<h;++g){var i={};e[g]=i;var k=new XMLHttpRequest;k.onload=b(k,i);k.onerror=d;i=a[g];k.open("GET",i,!0);k.responseType=
+"arraybuffer";k.send(null)}else k=new XMLHttpRequest,k.onload=function(){var a=THREE.ImageUtils.parseDDS(k.response,!0);if(a.isCubemap){for(var b=a.mipmaps.length/a.mipmapCount,d=0;d<b;d++){e[d]={mipmaps:[]};for(var g=0;g<a.mipmapCount;g++)e[d].mipmaps.push(a.mipmaps[d*a.mipmapCount+g]),e[d].format=a.format,e[d].width=a.width,e[d].height=a.height}f.format=a.format;f.needsUpdate=!0;c&&c(f)}},k.onerror=d,k.open("GET",a,!0),k.responseType="arraybuffer",k.send(null);return f},loadDDSTexture:function(a,
+b,c,d){var e=[];e.loadCount=0;var f=new THREE.CompressedTexture;f.image=e;void 0!==b&&(f.mapping=b);f.flipY=!1;f.generateMipmaps=!1;var g=new XMLHttpRequest;g.onload=function(){var a=THREE.ImageUtils.parseDDS(g.response,!0);if(a.isCubemap)for(var b=a.mipmaps.length/a.mipmapCount,d=0;d<b;d++){e[d]={mipmaps:[]};for(var l=0;l<a.mipmapCount;l++)e[d].mipmaps.push(a.mipmaps[d*a.mipmapCount+l]),e[d].format=a.format,e[d].width=a.width,e[d].height=a.height}else f.image.width=a.width,f.image.height=a.height,
+f.mipmaps=a.mipmaps;f.format=a.format;f.needsUpdate=!0;c&&c(f)};g.onerror=d;g.open("GET",a,!0);g.responseType="arraybuffer";g.send(null);return f},parseDDS:function(a,b){function c(a){return a.charCodeAt(0)+(a.charCodeAt(1)<<8)+(a.charCodeAt(2)<<16)+(a.charCodeAt(3)<<24)}var d={mipmaps:[],width:0,height:0,format:null,mipmapCount:1},e=c("DXT1"),f=c("DXT3"),g=c("DXT5"),h=new Int32Array(a,0,31);if(542327876!==h[0])return console.error("ImageUtils.parseDDS(): Invalid magic number in DDS header"),d;if(!h[20]&
+4)return console.error("ImageUtils.parseDDS(): Unsupported format, must contain a FourCC code"),d;var i=h[21],k=!1;switch(i){case e:e=8;d.format=THREE.RGB_S3TC_DXT1_Format;break;case f:e=16;d.format=THREE.RGBA_S3TC_DXT3_Format;break;case g:e=16;d.format=THREE.RGBA_S3TC_DXT5_Format;break;default:if(32==h[22]&&h[23]&16711680&&h[24]&65280&&h[25]&255&&h[26]&4278190080)k=!0,e=64,d.format=THREE.RGBAFormat;else return console.error("ImageUtils.parseDDS(): Unsupported FourCC code: ",String.fromCharCode(i&
+255,i>>8&255,i>>16&255,i>>24&255)),d}d.mipmapCount=1;h[2]&131072&&!1!==b&&(d.mipmapCount=Math.max(1,h[7]));d.isCubemap=h[28]&512?!0:!1;d.width=h[4];d.height=h[3];for(var h=h[1]+4,f=d.width,g=d.height,i=d.isCubemap?6:1,l=0;l<i;l++){for(var n=0;n<d.mipmapCount;n++){if(k){var p;p=f;for(var t=g,r=4*p*t,m=new Uint8Array(a,h,r),r=new Uint8Array(r),q=0,u=0,s=0;s<t;s++)for(var v=0;v<p;v++){var z=m[u];u++;var G=m[u];u++;var N=m[u];u++;var C=m[u];u++;r[q]=N;q++;r[q]=G;q++;r[q]=z;q++;r[q]=C;q++}p=r;t=p.length}else t=
+Math.max(4,f)/4*Math.max(4,g)/4*e,p=new Uint8Array(a,h,t);d.mipmaps.push({data:p,width:f,height:g});h+=t;f=Math.max(0.5*f,1);g=Math.max(0.5*g,1)}f=d.width;g=d.height}return d},getNormalMap:function(a,b){var c=function(a){var b=Math.sqrt(a[0]*a[0]+a[1]*a[1]+a[2]*a[2]);return[a[0]/b,a[1]/b,a[2]/b]},b=b|1,d=a.width,e=a.height,f=document.createElement("canvas");f.width=d;f.height=e;var g=f.getContext("2d");g.drawImage(a,0,0);for(var h=g.getImageData(0,0,d,e).data,i=g.createImageData(d,e),k=i.data,l=0;l<
+d;l++)for(var n=0;n<e;n++){var p=0>n-1?0:n-1,t=n+1>e-1?e-1:n+1,r=0>l-1?0:l-1,m=l+1>d-1?d-1:l+1,q=[],u=[0,0,h[4*(n*d+l)]/255*b];q.push([-1,0,h[4*(n*d+r)]/255*b]);q.push([-1,-1,h[4*(p*d+r)]/255*b]);q.push([0,-1,h[4*(p*d+l)]/255*b]);q.push([1,-1,h[4*(p*d+m)]/255*b]);q.push([1,0,h[4*(n*d+m)]/255*b]);q.push([1,1,h[4*(t*d+m)]/255*b]);q.push([0,1,h[4*(t*d+l)]/255*b]);q.push([-1,1,h[4*(t*d+r)]/255*b]);p=[];r=q.length;for(t=0;t<r;t++){var m=q[t],s=q[(t+1)%r],m=[m[0]-u[0],m[1]-u[1],m[2]-u[2]],s=[s[0]-u[0],
+s[1]-u[1],s[2]-u[2]];p.push(c([m[1]*s[2]-m[2]*s[1],m[2]*s[0]-m[0]*s[2],m[0]*s[1]-m[1]*s[0]]))}q=[0,0,0];for(t=0;t<p.length;t++)q[0]+=p[t][0],q[1]+=p[t][1],q[2]+=p[t][2];q[0]/=p.length;q[1]/=p.length;q[2]/=p.length;u=4*(n*d+l);k[u]=255*((q[0]+1)/2)|0;k[u+1]=255*((q[1]+1)/2)|0;k[u+2]=255*q[2]|0;k[u+3]=255}g.putImageData(i,0,0);return f},generateDataTexture:function(a,b,c){for(var d=a*b,e=new Uint8Array(3*d),f=Math.floor(255*c.r),g=Math.floor(255*c.g),c=Math.floor(255*c.b),h=0;h<d;h++)e[3*h]=f,e[3*h+
+1]=g,e[3*h+2]=c;a=new THREE.DataTexture(e,a,b,THREE.RGBFormat);a.needsUpdate=!0;return a}};THREE.SceneUtils={createMultiMaterialObject:function(a,b){for(var c=new THREE.Object3D,d=0,e=b.length;d<e;d++)c.add(new THREE.Mesh(a,b[d]));return c},detach:function(a,b,c){a.applyMatrix(b.matrixWorld);b.remove(a);c.add(a)},attach:function(a,b,c){var d=new THREE.Matrix4;d.getInverse(c.matrixWorld);a.applyMatrix(d);b.remove(a);c.add(a)}};THREE.FontUtils={faces:{},face:"helvetiker",weight:"normal",style:"normal",size:150,divisions:10,getFace:function(){return this.faces[this.face][this.weight][this.style]},loadFace:function(a){var b=a.familyName.toLowerCase();this.faces[b]=this.faces[b]||{};this.faces[b][a.cssFontWeight]=this.faces[b][a.cssFontWeight]||{};this.faces[b][a.cssFontWeight][a.cssFontStyle]=a;return this.faces[b][a.cssFontWeight][a.cssFontStyle]=a},drawText:function(a){for(var b=this.getFace(),c=this.size/b.resolution,d=
+0,e=String(a).split(""),f=e.length,g=[],a=0;a<f;a++){var h=new THREE.Path,h=this.extractGlyphPoints(e[a],b,c,d,h),d=d+h.offset;g.push(h.path)}return{paths:g,offset:d/2}},extractGlyphPoints:function(a,b,c,d,e){var f=[],g,h,i,k,l,n,p,t,r,m,q,u=b.glyphs[a]||b.glyphs["?"];if(u){if(u.o){b=u._cachedOutline||(u._cachedOutline=u.o.split(" "));k=b.length;for(a=0;a<k;)switch(i=b[a++],i){case "m":i=b[a++]*c+d;l=b[a++]*c;e.moveTo(i,l);break;case "l":i=b[a++]*c+d;l=b[a++]*c;e.lineTo(i,l);break;case "q":i=b[a++]*
+c+d;l=b[a++]*c;t=b[a++]*c+d;r=b[a++]*c;e.quadraticCurveTo(t,r,i,l);if(g=f[f.length-1]){n=g.x;p=g.y;g=1;for(h=this.divisions;g<=h;g++){var s=g/h;THREE.Shape.Utils.b2(s,n,t,i);THREE.Shape.Utils.b2(s,p,r,l)}}break;case "b":if(i=b[a++]*c+d,l=b[a++]*c,t=b[a++]*c+d,r=b[a++]*-c,m=b[a++]*c+d,q=b[a++]*-c,e.bezierCurveTo(i,l,t,r,m,q),g=f[f.length-1]){n=g.x;p=g.y;g=1;for(h=this.divisions;g<=h;g++)s=g/h,THREE.Shape.Utils.b3(s,n,t,m,i),THREE.Shape.Utils.b3(s,p,r,q,l)}}}return{offset:u.ha*c,path:e}}}};
+THREE.FontUtils.generateShapes=function(a,b){var b=b||{},c=void 0!==b.curveSegments?b.curveSegments:4,d=void 0!==b.font?b.font:"helvetiker",e=void 0!==b.weight?b.weight:"normal",f=void 0!==b.style?b.style:"normal";THREE.FontUtils.size=void 0!==b.size?b.size:100;THREE.FontUtils.divisions=c;THREE.FontUtils.face=d;THREE.FontUtils.weight=e;THREE.FontUtils.style=f;c=THREE.FontUtils.drawText(a).paths;d=[];e=0;for(f=c.length;e<f;e++)Array.prototype.push.apply(d,c[e].toShapes());return d};
+(function(a){var b=function(a){for(var b=a.length,e=0,f=b-1,g=0;g<b;f=g++)e+=a[f].x*a[g].y-a[g].x*a[f].y;return 0.5*e};a.Triangulate=function(a,d){var e=a.length;if(3>e)return null;var f=[],g=[],h=[],i,k,l;if(0<b(a))for(k=0;k<e;k++)g[k]=k;else for(k=0;k<e;k++)g[k]=e-1-k;var n=2*e;for(k=e-1;2<e;){if(0>=n--){console.log("Warning, unable to triangulate polygon!");break}i=k;e<=i&&(i=0);k=i+1;e<=k&&(k=0);l=k+1;e<=l&&(l=0);var p;a:{var t=p=void 0,r=void 0,m=void 0,q=void 0,u=void 0,s=void 0,v=void 0,z=
+void 0,t=a[g[i]].x,r=a[g[i]].y,m=a[g[k]].x,q=a[g[k]].y,u=a[g[l]].x,s=a[g[l]].y;if(1E-10>(m-t)*(s-r)-(q-r)*(u-t))p=!1;else{var G=void 0,N=void 0,C=void 0,B=void 0,x=void 0,F=void 0,L=void 0,w=void 0,E=void 0,y=void 0,E=w=L=z=v=void 0,G=u-m,N=s-q,C=t-u,B=r-s,x=m-t,F=q-r;for(p=0;p<e;p++)if(!(p===i||p===k||p===l))if(v=a[g[p]].x,z=a[g[p]].y,L=v-t,w=z-r,E=v-m,y=z-q,v-=u,z-=s,E=G*y-N*E,L=x*w-F*L,w=C*z-B*v,-1E-10<=E&&-1E-10<=w&&-1E-10<=L){p=!1;break a}p=!0}}if(p){f.push([a[g[i]],a[g[k]],a[g[l]]]);h.push([g[i],
+g[k],g[l]]);i=k;for(l=k+1;l<e;i++,l++)g[i]=g[l];e--;n=2*e}}return d?h:f};a.Triangulate.area=b;return a})(THREE.FontUtils);self._typeface_js={faces:THREE.FontUtils.faces,loadFace:THREE.FontUtils.loadFace};THREE.typeface_js=self._typeface_js;THREE.Curve=function(){};THREE.Curve.prototype.getPoint=function(){console.log("Warning, getPoint() not implemented!");return null};THREE.Curve.prototype.getPointAt=function(a){a=this.getUtoTmapping(a);return this.getPoint(a)};THREE.Curve.prototype.getPoints=function(a){a||(a=5);var b,c=[];for(b=0;b<=a;b++)c.push(this.getPoint(b/a));return c};THREE.Curve.prototype.getSpacedPoints=function(a){a||(a=5);var b,c=[];for(b=0;b<=a;b++)c.push(this.getPointAt(b/a));return c};
+THREE.Curve.prototype.getLength=function(){var a=this.getLengths();return a[a.length-1]};THREE.Curve.prototype.getLengths=function(a){a||(a=this.__arcLengthDivisions?this.__arcLengthDivisions:200);if(this.cacheArcLengths&&this.cacheArcLengths.length==a+1&&!this.needsUpdate)return this.cacheArcLengths;this.needsUpdate=!1;var b=[],c,d=this.getPoint(0),e,f=0;b.push(0);for(e=1;e<=a;e++)c=this.getPoint(e/a),f+=c.distanceTo(d),b.push(f),d=c;return this.cacheArcLengths=b};
+THREE.Curve.prototype.updateArcLengths=function(){this.needsUpdate=!0;this.getLengths()};THREE.Curve.prototype.getUtoTmapping=function(a,b){var c=this.getLengths(),d=0,e=c.length,f;f=b?b:a*c[e-1];for(var g=0,h=e-1,i;g<=h;)if(d=Math.floor(g+(h-g)/2),i=c[d]-f,0>i)g=d+1;else if(0<i)h=d-1;else{h=d;break}d=h;if(c[d]==f)return d/(e-1);g=c[d];return c=(d+(f-g)/(c[d+1]-g))/(e-1)};THREE.Curve.prototype.getTangent=function(a){var b=a-1E-4,a=a+1E-4;0>b&&(b=0);1<a&&(a=1);b=this.getPoint(b);return this.getPoint(a).clone().sub(b).normalize()};
+THREE.Curve.prototype.getTangentAt=function(a){a=this.getUtoTmapping(a);return this.getTangent(a)};
+THREE.Curve.Utils={tangentQuadraticBezier:function(a,b,c,d){return 2*(1-a)*(c-b)+2*a*(d-c)},tangentCubicBezier:function(a,b,c,d,e){return-3*b*(1-a)*(1-a)+3*c*(1-a)*(1-a)-6*a*c*(1-a)+6*a*d*(1-a)-3*a*a*d+3*a*a*e},tangentSpline:function(a){return 6*a*a-6*a+(3*a*a-4*a+1)+(-6*a*a+6*a)+(3*a*a-2*a)},interpolate:function(a,b,c,d,e){var a=0.5*(c-a),d=0.5*(d-b),f=e*e;return(2*b-2*c+a+d)*e*f+(-3*b+3*c-2*a-d)*f+a*e+b}};
+THREE.Curve.create=function(a,b){a.prototype=Object.create(THREE.Curve.prototype);a.prototype.getPoint=b;return a};THREE.CurvePath=function(){this.curves=[];this.bends=[];this.autoClose=!1};THREE.CurvePath.prototype=Object.create(THREE.Curve.prototype);THREE.CurvePath.prototype.add=function(a){this.curves.push(a)};THREE.CurvePath.prototype.checkConnection=function(){};THREE.CurvePath.prototype.closePath=function(){var a=this.curves[0].getPoint(0),b=this.curves[this.curves.length-1].getPoint(1);a.equals(b)||this.curves.push(new THREE.LineCurve(b,a))};
+THREE.CurvePath.prototype.getPoint=function(a){for(var b=a*this.getLength(),c=this.getCurveLengths(),a=0;a<c.length;){if(c[a]>=b)return b=c[a]-b,a=this.curves[a],b=1-b/a.getLength(),a.getPointAt(b);a++}return null};THREE.CurvePath.prototype.getLength=function(){var a=this.getCurveLengths();return a[a.length-1]};
+THREE.CurvePath.prototype.getCurveLengths=function(){if(this.cacheLengths&&this.cacheLengths.length==this.curves.length)return this.cacheLengths;var a=[],b=0,c,d=this.curves.length;for(c=0;c<d;c++)b+=this.curves[c].getLength(),a.push(b);return this.cacheLengths=a};
+THREE.CurvePath.prototype.getBoundingBox=function(){var a=this.getPoints(),b,c,d,e,f,g;b=c=Number.NEGATIVE_INFINITY;e=f=Number.POSITIVE_INFINITY;var h,i,k,l,n=a[0]instanceof THREE.Vector3;l=n?new THREE.Vector3:new THREE.Vector2;i=0;for(k=a.length;i<k;i++)h=a[i],h.x>b?b=h.x:h.x<e&&(e=h.x),h.y>c?c=h.y:h.y<f&&(f=h.y),n&&(h.z>d?d=h.z:h.z<g&&(g=h.z)),l.add(h);a={minX:e,minY:f,maxX:b,maxY:c,centroid:l.divideScalar(k)};n&&(a.maxZ=d,a.minZ=g);return a};
+THREE.CurvePath.prototype.createPointsGeometry=function(a){a=this.getPoints(a,!0);return this.createGeometry(a)};THREE.CurvePath.prototype.createSpacedPointsGeometry=function(a){a=this.getSpacedPoints(a,!0);return this.createGeometry(a)};THREE.CurvePath.prototype.createGeometry=function(a){for(var b=new THREE.Geometry,c=0;c<a.length;c++)b.vertices.push(new THREE.Vector3(a[c].x,a[c].y,a[c].z||0));return b};THREE.CurvePath.prototype.addWrapPath=function(a){this.bends.push(a)};
+THREE.CurvePath.prototype.getTransformedPoints=function(a,b){var c=this.getPoints(a),d,e;b||(b=this.bends);d=0;for(e=b.length;d<e;d++)c=this.getWrapPoints(c,b[d]);return c};THREE.CurvePath.prototype.getTransformedSpacedPoints=function(a,b){var c=this.getSpacedPoints(a),d,e;b||(b=this.bends);d=0;for(e=b.length;d<e;d++)c=this.getWrapPoints(c,b[d]);return c};
+THREE.CurvePath.prototype.getWrapPoints=function(a,b){var c=this.getBoundingBox(),d,e,f,g,h,i;d=0;for(e=a.length;d<e;d++)f=a[d],g=f.x,h=f.y,i=g/c.maxX,i=b.getUtoTmapping(i,g),g=b.getPoint(i),i=b.getTangent(i),i.set(-i.y,i.x).multiplyScalar(h),f.x=g.x+i.x,f.y=g.y+i.y;return a};THREE.Gyroscope=function(){THREE.Object3D.call(this)};THREE.Gyroscope.prototype=Object.create(THREE.Object3D.prototype);
+THREE.Gyroscope.prototype.updateMatrixWorld=function(a){this.matrixAutoUpdate&&this.updateMatrix();if(this.matrixWorldNeedsUpdate||a)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)):this.matrixWorld.copy(this.matrix),
+this.matrixWorldNeedsUpdate=!1,a=!0;for(var b=0,c=this.children.length;b<c;b++)this.children[b].updateMatrixWorld(a)};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;THREE.Path=function(a){THREE.CurvePath.call(this);this.actions=[];a&&this.fromPoints(a)};THREE.Path.prototype=Object.create(THREE.CurvePath.prototype);THREE.PathActions={MOVE_TO:"moveTo",LINE_TO:"lineTo",QUADRATIC_CURVE_TO:"quadraticCurveTo",BEZIER_CURVE_TO:"bezierCurveTo",CSPLINE_THRU:"splineThru",ARC:"arc",ELLIPSE:"ellipse"};THREE.Path.prototype.fromPoints=function(a){this.moveTo(a[0].x,a[0].y);for(var b=1,c=a.length;b<c;b++)this.lineTo(a[b].x,a[b].y)};
+THREE.Path.prototype.moveTo=function(a,b){var c=Array.prototype.slice.call(arguments);this.actions.push({action:THREE.PathActions.MOVE_TO,args:c})};THREE.Path.prototype.lineTo=function(a,b){var c=Array.prototype.slice.call(arguments),d=this.actions[this.actions.length-1].args,d=new THREE.LineCurve(new THREE.Vector2(d[d.length-2],d[d.length-1]),new THREE.Vector2(a,b));this.curves.push(d);this.actions.push({action:THREE.PathActions.LINE_TO,args:c})};
+THREE.Path.prototype.quadraticCurveTo=function(a,b,c,d){var e=Array.prototype.slice.call(arguments),f=this.actions[this.actions.length-1].args,f=new THREE.QuadraticBezierCurve(new THREE.Vector2(f[f.length-2],f[f.length-1]),new THREE.Vector2(a,b),new THREE.Vector2(c,d));this.curves.push(f);this.actions.push({action:THREE.PathActions.QUADRATIC_CURVE_TO,args:e})};
+THREE.Path.prototype.bezierCurveTo=function(a,b,c,d,e,f){var g=Array.prototype.slice.call(arguments),h=this.actions[this.actions.length-1].args,h=new THREE.CubicBezierCurve(new THREE.Vector2(h[h.length-2],h[h.length-1]),new THREE.Vector2(a,b),new THREE.Vector2(c,d),new THREE.Vector2(e,f));this.curves.push(h);this.actions.push({action:THREE.PathActions.BEZIER_CURVE_TO,args:g})};
+THREE.Path.prototype.splineThru=function(a){var b=Array.prototype.slice.call(arguments),c=this.actions[this.actions.length-1].args,c=[new THREE.Vector2(c[c.length-2],c[c.length-1])];Array.prototype.push.apply(c,a);c=new THREE.SplineCurve(c);this.curves.push(c);this.actions.push({action:THREE.PathActions.CSPLINE_THRU,args:b})};THREE.Path.prototype.arc=function(a,b,c,d,e,f){var g=this.actions[this.actions.length-1].args;this.absarc(a+g[g.length-2],b+g[g.length-1],c,d,e,f)};
+THREE.Path.prototype.absarc=function(a,b,c,d,e,f){this.absellipse(a,b,c,c,d,e,f)};THREE.Path.prototype.ellipse=function(a,b,c,d,e,f,g){var h=this.actions[this.actions.length-1].args;this.absellipse(a+h[h.length-2],b+h[h.length-1],c,d,e,f,g)};THREE.Path.prototype.absellipse=function(a,b,c,d,e,f,g){var h=Array.prototype.slice.call(arguments),i=new THREE.EllipseCurve(a,b,c,d,e,f,g);this.curves.push(i);i=i.getPoint(1);h.push(i.x);h.push(i.y);this.actions.push({action:THREE.PathActions.ELLIPSE,args:h})};
+THREE.Path.prototype.getSpacedPoints=function(a){a||(a=40);for(var b=[],c=0;c<a;c++)b.push(this.getPoint(c/a));return b};
+THREE.Path.prototype.getPoints=function(a,b){if(this.useSpacedPoints)return console.log("tata"),this.getSpacedPoints(a,b);var a=a||12,c=[],d,e,f,g,h,i,k,l,n,p,t,r,m;d=0;for(e=this.actions.length;d<e;d++)switch(f=this.actions[d],g=f.action,f=f.args,g){case THREE.PathActions.MOVE_TO:c.push(new THREE.Vector2(f[0],f[1]));break;case THREE.PathActions.LINE_TO:c.push(new THREE.Vector2(f[0],f[1]));break;case THREE.PathActions.QUADRATIC_CURVE_TO:h=f[2];i=f[3];n=f[0];p=f[1];0<c.length?(g=c[c.length-1],t=g.x,
+r=g.y):(g=this.actions[d-1].args,t=g[g.length-2],r=g[g.length-1]);for(f=1;f<=a;f++)m=f/a,g=THREE.Shape.Utils.b2(m,t,n,h),m=THREE.Shape.Utils.b2(m,r,p,i),c.push(new THREE.Vector2(g,m));break;case THREE.PathActions.BEZIER_CURVE_TO:h=f[4];i=f[5];n=f[0];p=f[1];k=f[2];l=f[3];0<c.length?(g=c[c.length-1],t=g.x,r=g.y):(g=this.actions[d-1].args,t=g[g.length-2],r=g[g.length-1]);for(f=1;f<=a;f++)m=f/a,g=THREE.Shape.Utils.b3(m,t,n,k,h),m=THREE.Shape.Utils.b3(m,r,p,l,i),c.push(new THREE.Vector2(g,m));break;case THREE.PathActions.CSPLINE_THRU:g=
+this.actions[d-1].args;m=[new THREE.Vector2(g[g.length-2],g[g.length-1])];g=a*f[0].length;m=m.concat(f[0]);m=new THREE.SplineCurve(m);for(f=1;f<=g;f++)c.push(m.getPointAt(f/g));break;case THREE.PathActions.ARC:h=f[0];i=f[1];p=f[2];k=f[3];g=f[4];n=!!f[5];t=g-k;r=2*a;for(f=1;f<=r;f++)m=f/r,n||(m=1-m),m=k+m*t,g=h+p*Math.cos(m),m=i+p*Math.sin(m),c.push(new THREE.Vector2(g,m));break;case THREE.PathActions.ELLIPSE:h=f[0];i=f[1];p=f[2];l=f[3];k=f[4];g=f[5];n=!!f[6];t=g-k;r=2*a;for(f=1;f<=r;f++)m=f/r,n||
+(m=1-m),m=k+m*t,g=h+p*Math.cos(m),m=i+l*Math.sin(m),c.push(new THREE.Vector2(g,m))}d=c[c.length-1];1E-10>Math.abs(d.x-c[0].x)&&1E-10>Math.abs(d.y-c[0].y)&&c.splice(c.length-1,1);b&&c.push(c[0]);return c};
+THREE.Path.prototype.toShapes=function(a){var b,c,d,e,f=[],g=new THREE.Path;b=0;for(c=this.actions.length;b<c;b++)d=this.actions[b],e=d.args,d=d.action,d==THREE.PathActions.MOVE_TO&&0!=g.actions.length&&(f.push(g),g=new THREE.Path),g[d].apply(g,e);0!=g.actions.length&&f.push(g);if(0==f.length)return[];var h;e=[];if(1==f.length)return d=f[0],h=new THREE.Shape,h.actions=d.actions,h.curves=d.curves,e.push(h),e;b=!THREE.Shape.Utils.isClockWise(f[0].getPoints());if(a?!b:b){h=new THREE.Shape;b=0;for(c=
+f.length;b<c;b++)d=f[b],g=THREE.Shape.Utils.isClockWise(d.getPoints()),(g=a?!g:g)?(h.actions=d.actions,h.curves=d.curves,e.push(h),h=new THREE.Shape):h.holes.push(d)}else{h=void 0;b=0;for(c=f.length;b<c;b++)d=f[b],g=THREE.Shape.Utils.isClockWise(d.getPoints()),(g=a?!g:g)?(h&&e.push(h),h=new THREE.Shape,h.actions=d.actions,h.curves=d.curves):h.holes.push(d);e.push(h)}return e};THREE.Shape=function(){THREE.Path.apply(this,arguments);this.holes=[]};THREE.Shape.prototype=Object.create(THREE.Path.prototype);THREE.Shape.prototype.extrude=function(a){return new THREE.ExtrudeGeometry(this,a)};THREE.Shape.prototype.makeGeometry=function(a){return new THREE.ShapeGeometry(this,a)};THREE.Shape.prototype.getPointsHoles=function(a){var b,c=this.holes.length,d=[];for(b=0;b<c;b++)d[b]=this.holes[b].getTransformedPoints(a,this.bends);return d};
+THREE.Shape.prototype.getSpacedPointsHoles=function(a){var b,c=this.holes.length,d=[];for(b=0;b<c;b++)d[b]=this.holes[b].getTransformedSpacedPoints(a,this.bends);return d};THREE.Shape.prototype.extractAllPoints=function(a){return{shape:this.getTransformedPoints(a),holes:this.getPointsHoles(a)}};THREE.Shape.prototype.extractPoints=function(a){return this.useSpacedPoints?this.extractAllSpacedPoints(a):this.extractAllPoints(a)};
+THREE.Shape.prototype.extractAllSpacedPoints=function(a){return{shape:this.getTransformedSpacedPoints(a),holes:this.getSpacedPointsHoles(a)}};
+THREE.Shape.Utils={removeHoles:function(a,b){var c=a.concat(),d=c.concat(),e,f,g,h,i,k,l,n,p,t,r=[];for(i=0;i<b.length;i++){k=b[i];Array.prototype.push.apply(d,k);f=Number.POSITIVE_INFINITY;for(e=0;e<k.length;e++){p=k[e];t=[];for(n=0;n<c.length;n++)l=c[n],l=p.distanceToSquared(l),t.push(l),l<f&&(f=l,g=e,h=n)}e=0<=h-1?h-1:c.length-1;f=0<=g-1?g-1:k.length-1;var m=[k[g],c[h],c[e]];n=THREE.FontUtils.Triangulate.area(m);var q=[k[g],k[f],c[h]];p=THREE.FontUtils.Triangulate.area(q);t=h;l=g;h+=1;g+=-1;0>
+h&&(h+=c.length);h%=c.length;0>g&&(g+=k.length);g%=k.length;e=0<=h-1?h-1:c.length-1;f=0<=g-1?g-1:k.length-1;m=[k[g],c[h],c[e]];m=THREE.FontUtils.Triangulate.area(m);q=[k[g],k[f],c[h]];q=THREE.FontUtils.Triangulate.area(q);n+p>m+q&&(h=t,g=l,0>h&&(h+=c.length),h%=c.length,0>g&&(g+=k.length),g%=k.length,e=0<=h-1?h-1:c.length-1,f=0<=g-1?g-1:k.length-1);n=c.slice(0,h);p=c.slice(h);t=k.slice(g);l=k.slice(0,g);f=[k[g],k[f],c[h]];r.push([k[g],c[h],c[e]]);r.push(f);c=n.concat(t).concat(l).concat(p)}return{shape:c,
+isolatedPts:r,allpoints:d}},triangulateShape:function(a,b){var c=THREE.Shape.Utils.removeHoles(a,b),d=c.allpoints,e=c.isolatedPts,c=THREE.FontUtils.Triangulate(c.shape,!1),f,g,h,i,k={};f=0;for(g=d.length;f<g;f++)i=d[f].x+":"+d[f].y,void 0!==k[i]&&console.log("Duplicate point",i),k[i]=f;f=0;for(g=c.length;f<g;f++){h=c[f];for(d=0;3>d;d++)i=h[d].x+":"+h[d].y,i=k[i],void 0!==i&&(h[d]=i)}f=0;for(g=e.length;f<g;f++){h=e[f];for(d=0;3>d;d++)i=h[d].x+":"+h[d].y,i=k[i],void 0!==i&&(h[d]=i)}return c.concat(e)},
+isClockWise:function(a){return 0>THREE.FontUtils.Triangulate.area(a)},b2p0:function(a,b){var c=1-a;return c*c*b},b2p1:function(a,b){return 2*(1-a)*a*b},b2p2:function(a,b){return a*a*b},b2:function(a,b,c,d){return this.b2p0(a,b)+this.b2p1(a,c)+this.b2p2(a,d)},b3p0:function(a,b){var c=1-a;return c*c*c*b},b3p1:function(a,b){var c=1-a;return 3*c*c*a*b},b3p2:function(a,b){return 3*(1-a)*a*a*b},b3p3:function(a,b){return a*a*a*b},b3:function(a,b,c,d,e){return this.b3p0(a,b)+this.b3p1(a,c)+this.b3p2(a,d)+
+this.b3p3(a,e)}};THREE.LineCurve=function(a,b){this.v1=a;this.v2=b};THREE.LineCurve.prototype=Object.create(THREE.Curve.prototype);THREE.LineCurve.prototype.getPoint=function(a){var b=this.v2.clone().sub(this.v1);b.multiplyScalar(a).add(this.v1);return b};THREE.LineCurve.prototype.getPointAt=function(a){return this.getPoint(a)};THREE.LineCurve.prototype.getTangent=function(){return this.v2.clone().sub(this.v1).normalize()};THREE.QuadraticBezierCurve=function(a,b,c){this.v0=a;this.v1=b;this.v2=c};THREE.QuadraticBezierCurve.prototype=Object.create(THREE.Curve.prototype);THREE.QuadraticBezierCurve.prototype.getPoint=function(a){var b;b=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);a=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);return new THREE.Vector2(b,a)};
+THREE.QuadraticBezierCurve.prototype.getTangent=function(a){var b;b=THREE.Curve.Utils.tangentQuadraticBezier(a,this.v0.x,this.v1.x,this.v2.x);a=THREE.Curve.Utils.tangentQuadraticBezier(a,this.v0.y,this.v1.y,this.v2.y);b=new THREE.Vector2(b,a);b.normalize();return b};THREE.CubicBezierCurve=function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d};THREE.CubicBezierCurve.prototype=Object.create(THREE.Curve.prototype);THREE.CubicBezierCurve.prototype.getPoint=function(a){var b;b=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);a=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);return new THREE.Vector2(b,a)};
+THREE.CubicBezierCurve.prototype.getTangent=function(a){var b;b=THREE.Curve.Utils.tangentCubicBezier(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);a=THREE.Curve.Utils.tangentCubicBezier(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);b=new THREE.Vector2(b,a);b.normalize();return b};THREE.SplineCurve=function(a){this.points=void 0==a?[]:a};THREE.SplineCurve.prototype=Object.create(THREE.Curve.prototype);THREE.SplineCurve.prototype.getPoint=function(a){var b=new THREE.Vector2,c=[],d=this.points,e;e=(d.length-1)*a;a=Math.floor(e);e-=a;c[0]=0==a?a:a-1;c[1]=a;c[2]=a>d.length-2?d.length-1:a+1;c[3]=a>d.length-3?d.length-1:a+2;b.x=THREE.Curve.Utils.interpolate(d[c[0]].x,d[c[1]].x,d[c[2]].x,d[c[3]].x,e);b.y=THREE.Curve.Utils.interpolate(d[c[0]].y,d[c[1]].y,d[c[2]].y,d[c[3]].y,e);return b};THREE.EllipseCurve=function(a,b,c,d,e,f,g){this.aX=a;this.aY=b;this.xRadius=c;this.yRadius=d;this.aStartAngle=e;this.aEndAngle=f;this.aClockwise=g};THREE.EllipseCurve.prototype=Object.create(THREE.Curve.prototype);
+THREE.EllipseCurve.prototype.getPoint=function(a){var b;b=this.aEndAngle-this.aStartAngle;0>b&&(b+=2*Math.PI);b>2*Math.PI&&(b-=2*Math.PI);b=!0===this.aClockwise?this.aEndAngle+(1-a)*(2*Math.PI-b):this.aStartAngle+a*b;a=this.aX+this.xRadius*Math.cos(b);b=this.aY+this.yRadius*Math.sin(b);return new THREE.Vector2(a,b)};THREE.ArcCurve=function(a,b,c,d,e,f){THREE.EllipseCurve.call(this,a,b,c,c,d,e,f)};THREE.ArcCurve.prototype=Object.create(THREE.EllipseCurve.prototype);THREE.LineCurve3=THREE.Curve.create(function(a,b){this.v1=a;this.v2=b},function(a){var b=new THREE.Vector3;b.subVectors(this.v2,this.v1);b.multiplyScalar(a);b.add(this.v1);return b});THREE.QuadraticBezierCurve3=THREE.Curve.create(function(a,b,c){this.v0=a;this.v1=b;this.v2=c},function(a){var b,c;b=THREE.Shape.Utils.b2(a,this.v0.x,this.v1.x,this.v2.x);c=THREE.Shape.Utils.b2(a,this.v0.y,this.v1.y,this.v2.y);a=THREE.Shape.Utils.b2(a,this.v0.z,this.v1.z,this.v2.z);return new THREE.Vector3(b,c,a)});THREE.CubicBezierCurve3=THREE.Curve.create(function(a,b,c,d){this.v0=a;this.v1=b;this.v2=c;this.v3=d},function(a){var b,c;b=THREE.Shape.Utils.b3(a,this.v0.x,this.v1.x,this.v2.x,this.v3.x);c=THREE.Shape.Utils.b3(a,this.v0.y,this.v1.y,this.v2.y,this.v3.y);a=THREE.Shape.Utils.b3(a,this.v0.z,this.v1.z,this.v2.z,this.v3.z);return new THREE.Vector3(b,c,a)});THREE.SplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=new THREE.Vector3,c=[],d=this.points,e,a=(d.length-1)*a;e=Math.floor(a);a-=e;c[0]=0==e?e:e-1;c[1]=e;c[2]=e>d.length-2?d.length-1:e+1;c[3]=e>d.length-3?d.length-1:e+2;e=d[c[0]];var f=d[c[1]],g=d[c[2]],c=d[c[3]];b.x=THREE.Curve.Utils.interpolate(e.x,f.x,g.x,c.x,a);b.y=THREE.Curve.Utils.interpolate(e.y,f.y,g.y,c.y,a);b.z=THREE.Curve.Utils.interpolate(e.z,f.z,g.z,c.z,a);return b});THREE.ClosedSplineCurve3=THREE.Curve.create(function(a){this.points=void 0==a?[]:a},function(a){var b=new THREE.Vector3,c=[],d=this.points,e;e=(d.length-0)*a;a=Math.floor(e);e-=a;a+=0<a?0:(Math.floor(Math.abs(a)/d.length)+1)*d.length;c[0]=(a-1)%d.length;c[1]=a%d.length;c[2]=(a+1)%d.length;c[3]=(a+2)%d.length;b.x=THREE.Curve.Utils.interpolate(d[c[0]].x,d[c[1]].x,d[c[2]].x,d[c[3]].x,e);b.y=THREE.Curve.Utils.interpolate(d[c[0]].y,d[c[1]].y,d[c[2]].y,d[c[3]].y,e);b.z=THREE.Curve.Utils.interpolate(d[c[0]].z,
+d[c[1]].z,d[c[2]].z,d[c[3]].z,e);return b});THREE.AnimationHandler=function(){var a=[],b={},c={update:function(b){for(var c=0;c<a.length;c++)a[c].update(b)},addToUpdate:function(b){-1===a.indexOf(b)&&a.push(b)},removeFromUpdate:function(b){b=a.indexOf(b);-1!==b&&a.splice(b,1)},add:function(a){void 0!==b[a.name]&&console.log("THREE.AnimationHandler.add: Warning! "+a.name+" already exists in library. Overwriting.");b[a.name]=a;if(!0!==a.initialized){for(var c=0;c<a.hierarchy.length;c++){for(var d=0;d<a.hierarchy[c].keys.length;d++)if(0>a.hierarchy[c].keys[d].time&&
+(a.hierarchy[c].keys[d].time=0),void 0!==a.hierarchy[c].keys[d].rot&&!(a.hierarchy[c].keys[d].rot instanceof THREE.Quaternion)){var h=a.hierarchy[c].keys[d].rot;a.hierarchy[c].keys[d].rot=new THREE.Quaternion(h[0],h[1],h[2],h[3])}if(a.hierarchy[c].keys.length&&void 0!==a.hierarchy[c].keys[0].morphTargets){h={};for(d=0;d<a.hierarchy[c].keys.length;d++)for(var i=0;i<a.hierarchy[c].keys[d].morphTargets.length;i++){var k=a.hierarchy[c].keys[d].morphTargets[i];h[k]=-1}a.hierarchy[c].usedMorphTargets=h;
+for(d=0;d<a.hierarchy[c].keys.length;d++){var l={};for(k in h){for(i=0;i<a.hierarchy[c].keys[d].morphTargets.length;i++)if(a.hierarchy[c].keys[d].morphTargets[i]===k){l[k]=a.hierarchy[c].keys[d].morphTargetsInfluences[i];break}i===a.hierarchy[c].keys[d].morphTargets.length&&(l[k]=0)}a.hierarchy[c].keys[d].morphTargetsInfluences=l}}for(d=1;d<a.hierarchy[c].keys.length;d++)a.hierarchy[c].keys[d].time===a.hierarchy[c].keys[d-1].time&&(a.hierarchy[c].keys.splice(d,1),d--);for(d=0;d<a.hierarchy[c].keys.length;d++)a.hierarchy[c].keys[d].index=
+d}d=parseInt(a.length*a.fps,10);a.JIT={};a.JIT.hierarchy=[];for(c=0;c<a.hierarchy.length;c++)a.JIT.hierarchy.push(Array(d));a.initialized=!0}},get:function(a){if("string"===typeof a){if(b[a])return b[a];console.log("THREE.AnimationHandler.get: Couldn't find animation "+a);return null}},parse:function(a){var b=[];if(a instanceof THREE.SkinnedMesh)for(var c=0;c<a.bones.length;c++)b.push(a.bones[c]);else d(a,b);return b}},d=function(a,b){b.push(a);for(var c=0;c<a.children.length;c++)d(a.children[c],
+b)};c.LINEAR=0;c.CATMULLROM=1;c.CATMULLROM_FORWARD=2;return c}();THREE.Animation=function(a,b,c){this.root=a;this.data=THREE.AnimationHandler.get(b);this.hierarchy=THREE.AnimationHandler.parse(a);this.currentTime=0;this.timeScale=1;this.isPlaying=!1;this.loop=this.isPaused=!0;this.interpolationType=void 0!==c?c:THREE.AnimationHandler.LINEAR;this.points=[];this.target=new THREE.Vector3};
+THREE.Animation.prototype.play=function(a,b){if(!1===this.isPlaying){this.isPlaying=!0;this.loop=void 0!==a?a:!0;this.currentTime=void 0!==b?b:0;var c,d=this.hierarchy.length,e;for(c=0;c<d;c++){e=this.hierarchy[c];e.matrixAutoUpdate=!0;void 0===e.animationCache&&(e.animationCache={},e.animationCache.prevKey={pos:0,rot:0,scl:0},e.animationCache.nextKey={pos:0,rot:0,scl:0},e.animationCache.originalMatrix=e instanceof THREE.Bone?e.skinMatrix:e.matrix);var f=e.animationCache.prevKey;e=e.animationCache.nextKey;
+f.pos=this.data.hierarchy[c].keys[0];f.rot=this.data.hierarchy[c].keys[0];f.scl=this.data.hierarchy[c].keys[0];e.pos=this.getNextKeyWith("pos",c,1);e.rot=this.getNextKeyWith("rot",c,1);e.scl=this.getNextKeyWith("scl",c,1)}this.update(0)}this.isPaused=!1;THREE.AnimationHandler.addToUpdate(this)};THREE.Animation.prototype.pause=function(){!0===this.isPaused?THREE.AnimationHandler.addToUpdate(this):THREE.AnimationHandler.removeFromUpdate(this);this.isPaused=!this.isPaused};
+THREE.Animation.prototype.stop=function(){this.isPaused=this.isPlaying=!1;THREE.AnimationHandler.removeFromUpdate(this)};
+THREE.Animation.prototype.update=function(a){if(!1!==this.isPlaying){var b=["pos","rot","scl"],c,d,e,f,g,h,i,k,l;for(l=this.currentTime+=a*this.timeScale;this.currentTime>this.data.length;)this.currentTime-=this.data.length;k=this.currentTime%=this.data.length;parseInt(Math.min(k*this.data.fps,this.data.length*this.data.fps),10);for(var n=0,p=this.hierarchy.length;n<p;n++){a=this.hierarchy[n];i=a.animationCache;for(var t=0;3>t;t++){c=b[t];g=i.prevKey[c];h=i.nextKey[c];if(h.time<=l){if(k<=l)if(this.loop){g=
+this.data.hierarchy[n].keys[0];for(h=this.getNextKeyWith(c,n,1);null!==h&&h.time<k&&h.index>g.index;)g=h,h=this.getNextKeyWith(c,n,h.index+1)}else{this.stop();return}else{do g=h,h=this.getNextKeyWith(c,n,h.index+1);while(null!==h&&h.time<k&&h.index>g.index)}i.prevKey[c]=g;i.nextKey[c]=h}a.matrixAutoUpdate=!0;a.matrixWorldNeedsUpdate=!0;d=(k-g.time)/(h.time-g.time);e=g[c];f=h[c];if(0>d||1<d)console.log("THREE.Animation.update: Warning! Scale out of bounds:"+d+" on bone "+n),d=0>d?0:1;if("pos"===c)if(c=
+a.position,this.interpolationType===THREE.AnimationHandler.LINEAR)c.x=e[0]+(f[0]-e[0])*d,c.y=e[1]+(f[1]-e[1])*d,c.z=e[2]+(f[2]-e[2])*d;else{if(this.interpolationType===THREE.AnimationHandler.CATMULLROM||this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD)this.points[0]=this.getPrevKeyWith("pos",n,g.index-1).pos,this.points[1]=e,this.points[2]=f,this.points[3]=this.getNextKeyWith("pos",n,h.index+1).pos,d=0.33*d+0.33,e=this.interpolateCatmullRom(this.points,d),c.x=e[0],c.y=e[1],c.z=e[2],
+this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD&&(d=this.interpolateCatmullRom(this.points,1.01*d),this.target.set(d[0],d[1],d[2]),this.target.sub(c),this.target.y=0,this.target.normalize(),d=Math.atan2(this.target.x,this.target.z),a.rotation.set(0,d,0))}else"rot"===c?THREE.Quaternion.slerp(e,f,a.quaternion,d):"scl"===c&&(c=a.scale,c.x=e[0]+(f[0]-e[0])*d,c.y=e[1]+(f[1]-e[1])*d,c.z=e[2]+(f[2]-e[2])*d)}}}};
+THREE.Animation.prototype.interpolateCatmullRom=function(a,b){var c=[],d=[],e,f,g,h,i,k;e=(a.length-1)*b;f=Math.floor(e);e-=f;c[0]=0===f?f:f-1;c[1]=f;c[2]=f>a.length-2?f:f+1;c[3]=f>a.length-3?f:f+2;f=a[c[0]];h=a[c[1]];i=a[c[2]];k=a[c[3]];c=e*e;g=e*c;d[0]=this.interpolate(f[0],h[0],i[0],k[0],e,c,g);d[1]=this.interpolate(f[1],h[1],i[1],k[1],e,c,g);d[2]=this.interpolate(f[2],h[2],i[2],k[2],e,c,g);return d};
+THREE.Animation.prototype.interpolate=function(a,b,c,d,e,f,g){a=0.5*(c-a);d=0.5*(d-b);return(2*(b-c)+a+d)*g+(-3*(b-c)-2*a-d)*f+a*e+b};THREE.Animation.prototype.getNextKeyWith=function(a,b,c){for(var d=this.data.hierarchy[b].keys,c=this.interpolationType===THREE.AnimationHandler.CATMULLROM||this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD?c<d.length-1?c:d.length-1:c%d.length;c<d.length;c++)if(void 0!==d[c][a])return d[c];return this.data.hierarchy[b].keys[0]};
+THREE.Animation.prototype.getPrevKeyWith=function(a,b,c){for(var d=this.data.hierarchy[b].keys,c=this.interpolationType===THREE.AnimationHandler.CATMULLROM||this.interpolationType===THREE.AnimationHandler.CATMULLROM_FORWARD?0<c?c:0:0<=c?c:c+d.length;0<=c;c--)if(void 0!==d[c][a])return d[c];return this.data.hierarchy[b].keys[d.length-1]};THREE.KeyFrameAnimation=function(a,b,c){this.root=a;this.data=THREE.AnimationHandler.get(b);this.hierarchy=THREE.AnimationHandler.parse(a);this.currentTime=0;this.timeScale=0.001;this.isPlaying=!1;this.loop=this.isPaused=!0;this.JITCompile=void 0!==c?c:!0;a=0;for(b=this.hierarchy.length;a<b;a++){var c=this.data.hierarchy[a].sids,d=this.hierarchy[a];if(this.data.hierarchy[a].keys.length&&c){for(var e=0;e<c.length;e++){var f=c[e],g=this.getNextKeyWith(f,a,0);g&&g.apply(f)}d.matrixAutoUpdate=!1;this.data.hierarchy[a].node.updateMatrix();
+d.matrixWorldNeedsUpdate=!0}}};
+THREE.KeyFrameAnimation.prototype.play=function(a,b){if(!this.isPlaying){this.isPlaying=!0;this.loop=void 0!==a?a:!0;this.currentTime=void 0!==b?b:0;this.startTimeMs=b;this.startTime=1E7;this.endTime=-this.startTime;var c,d=this.hierarchy.length,e,f;for(c=0;c<d;c++)e=this.hierarchy[c],f=this.data.hierarchy[c],void 0===f.animationCache&&(f.animationCache={},f.animationCache.prevKey=null,f.animationCache.nextKey=null,f.animationCache.originalMatrix=e instanceof THREE.Bone?e.skinMatrix:e.matrix),e=this.data.hierarchy[c].keys,
+e.length&&(f.animationCache.prevKey=e[0],f.animationCache.nextKey=e[1],this.startTime=Math.min(e[0].time,this.startTime),this.endTime=Math.max(e[e.length-1].time,this.endTime));this.update(0)}this.isPaused=!1;THREE.AnimationHandler.addToUpdate(this)};THREE.KeyFrameAnimation.prototype.pause=function(){this.isPaused?THREE.AnimationHandler.addToUpdate(this):THREE.AnimationHandler.removeFromUpdate(this);this.isPaused=!this.isPaused};
+THREE.KeyFrameAnimation.prototype.stop=function(){this.isPaused=this.isPlaying=!1;THREE.AnimationHandler.removeFromUpdate(this);for(var a=0;a<this.data.hierarchy.length;a++){var b=this.hierarchy[a],c=this.data.hierarchy[a];if(void 0!==c.animationCache){var d=c.animationCache.originalMatrix;b instanceof THREE.Bone?(d.copy(b.skinMatrix),b.skinMatrix=d):(d.copy(b.matrix),b.matrix=d);delete c.animationCache}}};
+THREE.KeyFrameAnimation.prototype.update=function(a){if(this.isPlaying){var b,c,d,e,f=this.data.JIT.hierarchy,g,h,i;h=this.currentTime+=a*this.timeScale;g=this.currentTime%=this.data.length;g<this.startTimeMs&&(g=this.currentTime=this.startTimeMs+g);e=parseInt(Math.min(g*this.data.fps,this.data.length*this.data.fps),10);if((i=g<h)&&!this.loop){for(var a=0,k=this.hierarchy.length;a<k;a++){var l=this.data.hierarchy[a].keys,f=this.data.hierarchy[a].sids;d=l.length-1;e=this.hierarchy[a];if(l.length){for(l=
+0;l<f.length;l++)g=f[l],(h=this.getPrevKeyWith(g,a,d))&&h.apply(g);this.data.hierarchy[a].node.updateMatrix();e.matrixWorldNeedsUpdate=!0}}this.stop()}else if(!(g<this.startTime)){a=0;for(k=this.hierarchy.length;a<k;a++){d=this.hierarchy[a];b=this.data.hierarchy[a];var l=b.keys,n=b.animationCache;if(this.JITCompile&&void 0!==f[a][e])d instanceof THREE.Bone?(d.skinMatrix=f[a][e],d.matrixWorldNeedsUpdate=!1):(d.matrix=f[a][e],d.matrixWorldNeedsUpdate=!0);else if(l.length){this.JITCompile&&n&&(d instanceof
+THREE.Bone?d.skinMatrix=n.originalMatrix:d.matrix=n.originalMatrix);b=n.prevKey;c=n.nextKey;if(b&&c){if(c.time<=h){if(i&&this.loop){b=l[0];for(c=l[1];c.time<g;)b=c,c=l[b.index+1]}else if(!i)for(var p=l.length-1;c.time<g&&c.index!==p;)b=c,c=l[b.index+1];n.prevKey=b;n.nextKey=c}c.time>=g?b.interpolate(c,g):b.interpolate(c,c.time)}this.data.hierarchy[a].node.updateMatrix();d.matrixWorldNeedsUpdate=!0}}if(this.JITCompile&&void 0===f[0][e]){this.hierarchy[0].updateMatrixWorld(!0);for(a=0;a<this.hierarchy.length;a++)f[a][e]=
+this.hierarchy[a]instanceof THREE.Bone?this.hierarchy[a].skinMatrix.clone():this.hierarchy[a].matrix.clone()}}}};THREE.KeyFrameAnimation.prototype.getNextKeyWith=function(a,b,c){b=this.data.hierarchy[b].keys;for(c%=b.length;c<b.length;c++)if(b[c].hasTarget(a))return b[c];return b[0]};THREE.KeyFrameAnimation.prototype.getPrevKeyWith=function(a,b,c){b=this.data.hierarchy[b].keys;for(c=0<=c?c:c+b.length;0<=c;c--)if(b[c].hasTarget(a))return b[c];return b[b.length-1]};THREE.CubeCamera=function(a,b,c){THREE.Object3D.call(this);var d=new THREE.PerspectiveCamera(90,1,a,b);d.up.set(0,-1,0);d.lookAt(new THREE.Vector3(1,0,0));this.add(d);var e=new THREE.PerspectiveCamera(90,1,a,b);e.up.set(0,-1,0);e.lookAt(new THREE.Vector3(-1,0,0));this.add(e);var f=new THREE.PerspectiveCamera(90,1,a,b);f.up.set(0,0,1);f.lookAt(new THREE.Vector3(0,1,0));this.add(f);var g=new THREE.PerspectiveCamera(90,1,a,b);g.up.set(0,0,-1);g.lookAt(new THREE.Vector3(0,-1,0));this.add(g);var h=new THREE.PerspectiveCamera(90,
+1,a,b);h.up.set(0,-1,0);h.lookAt(new THREE.Vector3(0,0,1));this.add(h);var i=new THREE.PerspectiveCamera(90,1,a,b);i.up.set(0,-1,0);i.lookAt(new THREE.Vector3(0,0,-1));this.add(i);this.renderTarget=new THREE.WebGLRenderTargetCube(c,c,{format:THREE.RGBFormat,magFilter:THREE.LinearFilter,minFilter:THREE.LinearFilter});this.updateCubeMap=function(a,b){var c=this.renderTarget,p=c.generateMipmaps;c.generateMipmaps=!1;c.activeCubeFace=0;a.render(b,d,c);c.activeCubeFace=1;a.render(b,e,c);c.activeCubeFace=
+2;a.render(b,f,c);c.activeCubeFace=3;a.render(b,g,c);c.activeCubeFace=4;a.render(b,h,c);c.generateMipmaps=p;c.activeCubeFace=5;a.render(b,i,c)}};THREE.CubeCamera.prototype=Object.create(THREE.Object3D.prototype);THREE.CombinedCamera=function(a,b,c,d,e,f,g){THREE.Camera.call(this);this.fov=c;this.left=-a/2;this.right=a/2;this.top=b/2;this.bottom=-b/2;this.cameraO=new THREE.OrthographicCamera(a/-2,a/2,b/2,b/-2,f,g);this.cameraP=new THREE.PerspectiveCamera(c,a/b,d,e);this.zoom=1;this.toPerspective()};THREE.CombinedCamera.prototype=Object.create(THREE.Camera.prototype);
+THREE.CombinedCamera.prototype.toPerspective=function(){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=!0;this.inOrthographicMode=!1};
+THREE.CombinedCamera.prototype.toOrthographic=function(){var a=this.cameraP.aspect,b=(this.cameraP.near+this.cameraP.far)/2,b=Math.tan(this.fov/2)*b,a=2*b*a/2,b=b/this.zoom,a=a/this.zoom;this.cameraO.left=-a;this.cameraO.right=a;this.cameraO.top=b;this.cameraO.bottom=-b;this.cameraO.updateProjectionMatrix();this.near=this.cameraO.near;this.far=this.cameraO.far;this.projectionMatrix=this.cameraO.projectionMatrix;this.inPerspectiveMode=!1;this.inOrthographicMode=!0};
+THREE.CombinedCamera.prototype.setSize=function(a,b){this.cameraP.aspect=a/b;this.left=-a/2;this.right=a/2;this.top=b/2;this.bottom=-b/2};THREE.CombinedCamera.prototype.setFov=function(a){this.fov=a;this.inPerspectiveMode?this.toPerspective():this.toOrthographic()};THREE.CombinedCamera.prototype.updateProjectionMatrix=function(){this.inPerspectiveMode?this.toPerspective():(this.toPerspective(),this.toOrthographic())};
+THREE.CombinedCamera.prototype.setLens=function(a,b){void 0===b&&(b=24);var c=2*THREE.Math.radToDeg(Math.atan(b/(2*a)));this.setFov(c);return c};THREE.CombinedCamera.prototype.setZoom=function(a){this.zoom=a;this.inPerspectiveMode?this.toPerspective():this.toOrthographic()};THREE.CombinedCamera.prototype.toFrontView=function(){this.rotation.x=0;this.rotation.y=0;this.rotation.z=0;this.rotationAutoUpdate=!1};
+THREE.CombinedCamera.prototype.toBackView=function(){this.rotation.x=0;this.rotation.y=Math.PI;this.rotation.z=0;this.rotationAutoUpdate=!1};THREE.CombinedCamera.prototype.toLeftView=function(){this.rotation.x=0;this.rotation.y=-Math.PI/2;this.rotation.z=0;this.rotationAutoUpdate=!1};THREE.CombinedCamera.prototype.toRightView=function(){this.rotation.x=0;this.rotation.y=Math.PI/2;this.rotation.z=0;this.rotationAutoUpdate=!1};
+THREE.CombinedCamera.prototype.toTopView=function(){this.rotation.x=-Math.PI/2;this.rotation.y=0;this.rotation.z=0;this.rotationAutoUpdate=!1};THREE.CombinedCamera.prototype.toBottomView=function(){this.rotation.x=Math.PI/2;this.rotation.y=0;this.rotation.z=0;this.rotationAutoUpdate=!1};THREE.CircleGeometry=function(a,b,c,d){THREE.Geometry.call(this);this.radius=a=a||50;this.segments=b=void 0!==b?Math.max(3,b):8;this.thetaStart=c=void 0!==c?c:0;this.thetaLength=d=void 0!==d?d:2*Math.PI;var e,f=[];e=new THREE.Vector3;var g=new THREE.Vector2(0.5,0.5);this.vertices.push(e);f.push(g);for(e=0;e<=b;e++){var h=new THREE.Vector3,i=c+e/b*d;h.x=a*Math.cos(i);h.y=a*Math.sin(i);this.vertices.push(h);f.push(new THREE.Vector2((h.x/a+1)/2,(h.y/a+1)/2))}c=new THREE.Vector3(0,0,1);for(e=1;e<=b;e++)this.faces.push(new THREE.Face3(e,
+e+1,0,[c.clone(),c.clone(),c.clone()])),this.faceVertexUvs[0].push([f[e].clone(),f[e+1].clone(),g.clone()]);this.computeCentroids();this.computeFaceNormals();this.boundingSphere=new THREE.Sphere(new THREE.Vector3,a)};THREE.CircleGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.CubeGeometry=function(a,b,c,d,e,f){function g(a,b,c,d,e,f,g,m){var q,u=h.widthSegments,s=h.heightSegments,v=e/2,z=f/2,G=h.vertices.length;if("x"===a&&"y"===b||"y"===a&&"x"===b)q="z";else if("x"===a&&"z"===b||"z"===a&&"x"===b)q="y",s=h.depthSegments;else if("z"===a&&"y"===b||"y"===a&&"z"===b)q="x",u=h.depthSegments;var N=u+1,C=s+1,B=e/u,x=f/s,F=new THREE.Vector3;F[q]=0<g?1:-1;for(e=0;e<C;e++)for(f=0;f<N;f++){var L=new THREE.Vector3;L[a]=(f*B-v)*c;L[b]=(e*x-z)*d;L[q]=g;h.vertices.push(L)}for(e=
+0;e<s;e++)for(f=0;f<u;f++)z=f+N*e,a=f+N*(e+1),b=f+1+N*(e+1),c=f+1+N*e,d=new THREE.Vector2(f/u,1-e/s),g=new THREE.Vector2(f/u,1-(e+1)/s),q=new THREE.Vector2((f+1)/u,1-(e+1)/s),v=new THREE.Vector2((f+1)/u,1-e/s),z=new THREE.Face3(z+G,a+G,c+G),z.normal.copy(F),z.vertexNormals.push(F.clone(),F.clone(),F.clone()),z.materialIndex=m,h.faces.push(z),h.faceVertexUvs[0].push([d,g,v]),z=new THREE.Face3(a+G,b+G,c+G),z.normal.copy(F),z.vertexNormals.push(F.clone(),F.clone(),F.clone()),z.materialIndex=m,h.faces.push(z),
+h.faceVertexUvs[0].push([g.clone(),q,v.clone()])}THREE.Geometry.call(this);var h=this;this.width=a;this.height=b;this.depth=c;this.widthSegments=d||1;this.heightSegments=e||1;this.depthSegments=f||1;a=this.width/2;b=this.height/2;c=this.depth/2;g("z","y",-1,-1,this.depth,this.height,a,0);g("z","y",1,-1,this.depth,this.height,-a,1);g("x","z",1,1,this.width,this.depth,b,2);g("x","z",1,-1,this.width,this.depth,-b,3);g("x","y",1,-1,this.width,this.height,c,4);g("x","y",-1,-1,this.width,this.height,-c,
+5);this.computeCentroids();this.mergeVertices()};THREE.CubeGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.CylinderGeometry=function(a,b,c,d,e,f){THREE.Geometry.call(this);this.radiusTop=a=void 0!==a?a:20;this.radiusBottom=b=void 0!==b?b:20;this.height=c=void 0!==c?c:100;this.radialSegments=d=d||8;this.heightSegments=e=e||1;this.openEnded=f=void 0!==f?f:!1;var g=c/2,h,i,k=[],l=[];for(i=0;i<=e;i++){var n=[],p=[],t=i/e,r=t*(b-a)+a;for(h=0;h<=d;h++){var m=h/d,q=new THREE.Vector3;q.x=r*Math.sin(2*m*Math.PI);q.y=-t*c+g;q.z=r*Math.cos(2*m*Math.PI);this.vertices.push(q);n.push(this.vertices.length-1);p.push(new THREE.Vector2(m,
+1-t))}k.push(n);l.push(p)}c=(b-a)/c;for(h=0;h<d;h++){0!==a?(n=this.vertices[k[0][h]].clone(),p=this.vertices[k[0][h+1]].clone()):(n=this.vertices[k[1][h]].clone(),p=this.vertices[k[1][h+1]].clone());n.setY(Math.sqrt(n.x*n.x+n.z*n.z)*c).normalize();p.setY(Math.sqrt(p.x*p.x+p.z*p.z)*c).normalize();for(i=0;i<e;i++){var t=k[i][h],r=k[i+1][h],m=k[i+1][h+1],q=k[i][h+1],u=n.clone(),s=n.clone(),v=p.clone(),z=p.clone(),G=l[i][h].clone(),N=l[i+1][h].clone(),C=l[i+1][h+1].clone(),B=l[i][h+1].clone();this.faces.push(new THREE.Face3(t,
+r,q,[u,s,z]));this.faceVertexUvs[0].push([G,N,B]);this.faces.push(new THREE.Face3(r,m,q,[s.clone(),v,z.clone()]));this.faceVertexUvs[0].push([N.clone(),C,B.clone()])}}if(!1===f&&0<a){this.vertices.push(new THREE.Vector3(0,g,0));for(h=0;h<d;h++)t=k[0][h],r=k[0][h+1],m=this.vertices.length-1,u=new THREE.Vector3(0,1,0),s=new THREE.Vector3(0,1,0),v=new THREE.Vector3(0,1,0),G=l[0][h].clone(),N=l[0][h+1].clone(),C=new THREE.Vector2(N.x,0),this.faces.push(new THREE.Face3(t,r,m,[u,s,v])),this.faceVertexUvs[0].push([G,
+N,C])}if(!1===f&&0<b){this.vertices.push(new THREE.Vector3(0,-g,0));for(h=0;h<d;h++)t=k[i][h+1],r=k[i][h],m=this.vertices.length-1,u=new THREE.Vector3(0,-1,0),s=new THREE.Vector3(0,-1,0),v=new THREE.Vector3(0,-1,0),G=l[i][h+1].clone(),N=l[i][h].clone(),C=new THREE.Vector2(N.x,1),this.faces.push(new THREE.Face3(t,r,m,[u,s,v])),this.faceVertexUvs[0].push([G,N,C])}this.computeCentroids();this.computeFaceNormals()};THREE.CylinderGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ExtrudeGeometry=function(a,b){"undefined"!==typeof a&&(THREE.Geometry.call(this),a=a instanceof Array?a:[a],this.shapebb=a[a.length-1].getBoundingBox(),this.addShapeList(a,b),this.computeCentroids(),this.computeFaceNormals())};THREE.ExtrudeGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ExtrudeGeometry.prototype.addShapeList=function(a,b){for(var c=a.length,d=0;d<c;d++)this.addShape(a[d],b)};
+THREE.ExtrudeGeometry.prototype.addShape=function(a,b){function c(a,b,c){b||console.log("die");return b.clone().multiplyScalar(c).add(a)}function d(a,b,c){var d=THREE.ExtrudeGeometry.__v1,e=THREE.ExtrudeGeometry.__v2,f=THREE.ExtrudeGeometry.__v3,g=THREE.ExtrudeGeometry.__v4,h=THREE.ExtrudeGeometry.__v5,i=THREE.ExtrudeGeometry.__v6;d.set(a.x-b.x,a.y-b.y);e.set(a.x-c.x,a.y-c.y);d=d.normalize();e=e.normalize();f.set(-d.y,d.x);g.set(e.y,-e.x);h.copy(a).add(f);i.copy(a).add(g);if(h.equals(i))return g.clone();
+h.copy(b).add(f);i.copy(c).add(g);f=d.dot(g);g=i.sub(h).dot(g);0===f&&(console.log("Either infinite or no solutions!"),0===g?console.log("Its finite solutions."):console.log("Too bad, no solutions."));g/=f;return 0>g?(b=Math.atan2(b.y-a.y,b.x-a.x),a=Math.atan2(c.y-a.y,c.x-a.x),b>a&&(a+=2*Math.PI),c=(b+a)/2,a=-Math.cos(c),c=-Math.sin(c),new THREE.Vector2(a,c)):d.multiplyScalar(g).add(h).sub(a).clone()}function e(c,d){var e,f;for(O=c.length;0<=--O;){e=O;f=O-1;0>f&&(f=c.length-1);for(var g=0,h=t+2*l,
+g=0;g<h;g++){var i=da*g,k=da*(g+1),m=d+e+i,i=d+f+i,n=d+f+k,k=d+e+k,p=c,q=g,r=h,u=e,z=f,m=m+w,i=i+w,n=n+w,k=k+w;L.faces.push(new THREE.Face3(m,i,k,null,null,s));L.faces.push(new THREE.Face3(i,n,k,null,null,s));m=v.generateSideWallUV(L,a,p,b,m,i,n,k,q,r,u,z);L.faceVertexUvs[0].push([m[0],m[1],m[3]]);L.faceVertexUvs[0].push([m[1],m[2],m[3]])}}}function f(a,b,c){L.vertices.push(new THREE.Vector3(a,b,c))}function g(c,d,e,f){c+=w;d+=w;e+=w;L.faces.push(new THREE.Face3(c,d,e,null,null,u));c=f?v.generateBottomUV(L,
+a,b,c,d,e):v.generateTopUV(L,a,b,c,d,e);L.faceVertexUvs[0].push(c)}var h=void 0!==b.amount?b.amount:100,i=void 0!==b.bevelThickness?b.bevelThickness:6,k=void 0!==b.bevelSize?b.bevelSize:i-2,l=void 0!==b.bevelSegments?b.bevelSegments:3,n=void 0!==b.bevelEnabled?b.bevelEnabled:!0,p=void 0!==b.curveSegments?b.curveSegments:12,t=void 0!==b.steps?b.steps:1,r=b.extrudePath,m,q=!1,u=b.material,s=b.extrudeMaterial,v=void 0!==b.UVGenerator?b.UVGenerator:THREE.ExtrudeGeometry.WorldUVGenerator,z,G,N,C;r&&(m=
+r.getSpacedPoints(t),q=!0,n=!1,z=void 0!==b.frames?b.frames:new THREE.TubeGeometry.FrenetFrames(r,t,!1),G=new THREE.Vector3,N=new THREE.Vector3,C=new THREE.Vector3);n||(k=i=l=0);var B,x,F,L=this,w=this.vertices.length,p=a.extractPoints(p),E=p.shape,p=p.holes;if(r=!THREE.Shape.Utils.isClockWise(E)){E=E.reverse();x=0;for(F=p.length;x<F;x++)B=p[x],THREE.Shape.Utils.isClockWise(B)&&(p[x]=B.reverse());r=!1}var y=THREE.Shape.Utils.triangulateShape(E,p),r=E;x=0;for(F=p.length;x<F;x++)B=p[x],E=E.concat(B);
+var D,H,K,A,da=E.length,ha=y.length,ua=[],O=0,T=r.length;D=T-1;for(H=O+1;O<T;O++,D++,H++)D===T&&(D=0),H===T&&(H=0),ua[O]=d(r[O],r[D],r[H]);var ea=[],I,P=ua.concat();x=0;for(F=p.length;x<F;x++){B=p[x];I=[];O=0;T=B.length;D=T-1;for(H=O+1;O<T;O++,D++,H++)D===T&&(D=0),H===T&&(H=0),I[O]=d(B[O],B[D],B[H]);ea.push(I);P=P.concat(I)}for(D=0;D<l;D++){B=D/l;K=i*(1-B);H=k*Math.sin(B*Math.PI/2);O=0;for(T=r.length;O<T;O++)A=c(r[O],ua[O],H),f(A.x,A.y,-K);x=0;for(F=p.length;x<F;x++){B=p[x];I=ea[x];O=0;for(T=B.length;O<
+T;O++)A=c(B[O],I[O],H),f(A.x,A.y,-K)}}H=k;for(O=0;O<da;O++)A=n?c(E[O],P[O],H):E[O],q?(N.copy(z.normals[0]).multiplyScalar(A.x),G.copy(z.binormals[0]).multiplyScalar(A.y),C.copy(m[0]).add(N).add(G),f(C.x,C.y,C.z)):f(A.x,A.y,0);for(B=1;B<=t;B++)for(O=0;O<da;O++)A=n?c(E[O],P[O],H):E[O],q?(N.copy(z.normals[B]).multiplyScalar(A.x),G.copy(z.binormals[B]).multiplyScalar(A.y),C.copy(m[B]).add(N).add(G),f(C.x,C.y,C.z)):f(A.x,A.y,h/t*B);for(D=l-1;0<=D;D--){B=D/l;K=i*(1-B);H=k*Math.sin(B*Math.PI/2);O=0;for(T=
+r.length;O<T;O++)A=c(r[O],ua[O],H),f(A.x,A.y,h+K);x=0;for(F=p.length;x<F;x++){B=p[x];I=ea[x];O=0;for(T=B.length;O<T;O++)A=c(B[O],I[O],H),q?f(A.x,A.y+m[t-1].y,m[t-1].x+K):f(A.x,A.y,h+K)}}if(n){i=0*da;for(O=0;O<ha;O++)h=y[O],g(h[2]+i,h[1]+i,h[0]+i,!0);i=da*(t+2*l);for(O=0;O<ha;O++)h=y[O],g(h[0]+i,h[1]+i,h[2]+i,!1)}else{for(O=0;O<ha;O++)h=y[O],g(h[2],h[1],h[0],!0);for(O=0;O<ha;O++)h=y[O],g(h[0]+da*t,h[1]+da*t,h[2]+da*t,!1)}h=0;e(r,h);h+=r.length;x=0;for(F=p.length;x<F;x++)B=p[x],e(B,h),h+=B.length};
+THREE.ExtrudeGeometry.WorldUVGenerator={generateTopUV:function(a,b,c,d,e,f){b=a.vertices[e].x;e=a.vertices[e].y;c=a.vertices[f].x;f=a.vertices[f].y;return[new THREE.Vector2(a.vertices[d].x,a.vertices[d].y),new THREE.Vector2(b,e),new THREE.Vector2(c,f)]},generateBottomUV:function(a,b,c,d,e,f){return this.generateTopUV(a,b,c,d,e,f)},generateSideWallUV:function(a,b,c,d,e,f,g,h){var b=a.vertices[e].x,c=a.vertices[e].y,e=a.vertices[e].z,d=a.vertices[f].x,i=a.vertices[f].y,f=a.vertices[f].z,k=a.vertices[g].x,
+l=a.vertices[g].y,g=a.vertices[g].z,n=a.vertices[h].x,p=a.vertices[h].y,a=a.vertices[h].z;return 0.01>Math.abs(c-i)?[new THREE.Vector2(b,1-e),new THREE.Vector2(d,1-f),new THREE.Vector2(k,1-g),new THREE.Vector2(n,1-a)]:[new THREE.Vector2(c,1-e),new THREE.Vector2(i,1-f),new THREE.Vector2(l,1-g),new THREE.Vector2(p,1-a)]}};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;THREE.ShapeGeometry=function(a,b){THREE.Geometry.call(this);!1===a instanceof Array&&(a=[a]);this.shapebb=a[a.length-1].getBoundingBox();this.addShapeList(a,b);this.computeCentroids();this.computeFaceNormals()};THREE.ShapeGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ShapeGeometry.prototype.addShapeList=function(a,b){for(var c=0,d=a.length;c<d;c++)this.addShape(a[c],b);return this};
+THREE.ShapeGeometry.prototype.addShape=function(a,b){void 0===b&&(b={});var c=b.material,d=void 0===b.UVGenerator?THREE.ExtrudeGeometry.WorldUVGenerator:b.UVGenerator,e,f,g,h=this.vertices.length;e=a.extractPoints(void 0!==b.curveSegments?b.curveSegments:12);var i=e.shape,k=e.holes;if(!THREE.Shape.Utils.isClockWise(i)){i=i.reverse();e=0;for(f=k.length;e<f;e++)g=k[e],THREE.Shape.Utils.isClockWise(g)&&(k[e]=g.reverse())}var l=THREE.Shape.Utils.triangulateShape(i,k);e=0;for(f=k.length;e<f;e++)g=k[e],
+i=i.concat(g);k=i.length;f=l.length;for(e=0;e<k;e++)g=i[e],this.vertices.push(new THREE.Vector3(g.x,g.y,0));for(e=0;e<f;e++)k=l[e],i=k[0]+h,g=k[1]+h,k=k[2]+h,this.faces.push(new THREE.Face3(i,g,k,null,null,c)),this.faceVertexUvs[0].push(d.generateBottomUV(this,a,b,i,g,k))};THREE.LatheGeometry=function(a,b,c,d){THREE.Geometry.call(this);for(var b=b||12,c=c||0,d=d||2*Math.PI,e=1/(a.length-1),f=1/b,g=0,h=b;g<=h;g++)for(var i=c+g*f*d,k=Math.cos(i),l=Math.sin(i),i=0,n=a.length;i<n;i++){var p=a[i],t=new THREE.Vector3;t.x=k*p.x-l*p.y;t.y=l*p.x+k*p.y;t.z=p.z;this.vertices.push(t)}c=a.length;g=0;for(h=b;g<h;g++){i=0;for(n=a.length-1;i<n;i++){var b=l=i+c*g,d=l+c,k=l+1+c,l=l+1,p=g*f,t=i*e,r=p+f,m=t+e;this.faces.push(new THREE.Face3(b,d,l));this.faceVertexUvs[0].push([new THREE.Vector2(p,
+t),new THREE.Vector2(r,t),new THREE.Vector2(p,m)]);this.faces.push(new THREE.Face3(d,k,l));this.faceVertexUvs[0].push([new THREE.Vector2(r,t),new THREE.Vector2(r,m),new THREE.Vector2(p,m)])}}this.mergeVertices();this.computeCentroids();this.computeFaceNormals();this.computeVertexNormals()};THREE.LatheGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.PlaneGeometry=function(a,b,c,d){THREE.Geometry.call(this);this.width=a;this.height=b;this.widthSegments=c||1;this.heightSegments=d||1;for(var e=a/2,f=b/2,c=this.widthSegments,d=this.heightSegments,g=c+1,h=d+1,i=this.width/c,k=this.height/d,l=new THREE.Vector3(0,0,1),a=0;a<h;a++)for(b=0;b<g;b++)this.vertices.push(new THREE.Vector3(b*i-e,-(a*k-f),0));for(a=0;a<d;a++)for(b=0;b<c;b++){var n=b+g*a,e=b+g*(a+1),f=b+1+g*(a+1),h=b+1+g*a,i=new THREE.Vector2(b/c,1-a/d),k=new THREE.Vector2(b/c,1-(a+1)/
+d),p=new THREE.Vector2((b+1)/c,1-(a+1)/d),t=new THREE.Vector2((b+1)/c,1-a/d),n=new THREE.Face3(n,e,h);n.normal.copy(l);n.vertexNormals.push(l.clone(),l.clone(),l.clone());this.faces.push(n);this.faceVertexUvs[0].push([i,k,t]);n=new THREE.Face3(e,f,h);n.normal.copy(l);n.vertexNormals.push(l.clone(),l.clone(),l.clone());this.faces.push(n);this.faceVertexUvs[0].push([k.clone(),p,t.clone()])}this.computeCentroids()};THREE.PlaneGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.RingGeometry=function(a,b,c,d,e,f){THREE.Geometry.call(this);for(var a=a||0,b=b||50,e=void 0!==e?e:0,f=void 0!==f?f:2*Math.PI,c=void 0!==c?Math.max(3,c):8,d=void 0!==d?Math.max(3,d):8,g,h=[],i=a,k=(b-a)/d,a=0;a<=d;a++){for(g=0;g<=c;g++){var l=new THREE.Vector3,n=e+g/c*f;l.x=i*Math.cos(n);l.y=i*Math.sin(n);this.vertices.push(l);h.push(new THREE.Vector2((l.x/b+1)/2,(l.y/b+1)/2))}i+=k}b=new THREE.Vector3(0,0,1);for(a=0;a<d;a++){e=a*c;for(g=0;g<=c;g++)n=g+e,f=n+a,k=n+c+a,l=n+c+1+a,this.faces.push(new THREE.Face3(f,
+k,l,[b.clone(),b.clone(),b.clone()])),this.faceVertexUvs[0].push([h[f].clone(),h[k].clone(),h[l].clone()]),f=n+a,k=n+c+1+a,l=n+1+a,this.faces.push(new THREE.Face3(f,k,l,[b.clone(),b.clone(),b.clone()])),this.faceVertexUvs[0].push([h[f].clone(),h[k].clone(),h[l].clone()])}this.computeCentroids();this.computeFaceNormals();this.boundingSphere=new THREE.Sphere(new THREE.Vector3,i)};THREE.RingGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.SphereGeometry=function(a,b,c,d,e,f,g){THREE.Geometry.call(this);this.radius=a=a||50;this.widthSegments=b=Math.max(3,Math.floor(b)||8);this.heightSegments=c=Math.max(2,Math.floor(c)||6);this.phiStart=d=void 0!==d?d:0;this.phiLength=e=void 0!==e?e:2*Math.PI;this.thetaStart=f=void 0!==f?f:0;this.thetaLength=g=void 0!==g?g:Math.PI;var h,i,k=[],l=[];for(i=0;i<=c;i++){var n=[],p=[];for(h=0;h<=b;h++){var t=h/b,r=i/c,m=new THREE.Vector3;m.x=-a*Math.cos(d+t*e)*Math.sin(f+r*g);m.y=a*Math.cos(f+r*g);
+m.z=a*Math.sin(d+t*e)*Math.sin(f+r*g);this.vertices.push(m);n.push(this.vertices.length-1);p.push(new THREE.Vector2(t,1-r))}k.push(n);l.push(p)}for(i=0;i<this.heightSegments;i++)for(h=0;h<this.widthSegments;h++){var b=k[i][h+1],c=k[i][h],d=k[i+1][h],e=k[i+1][h+1],f=this.vertices[b].clone().normalize(),g=this.vertices[c].clone().normalize(),n=this.vertices[d].clone().normalize(),p=this.vertices[e].clone().normalize(),t=l[i][h+1].clone(),r=l[i][h].clone(),m=l[i+1][h].clone(),q=l[i+1][h+1].clone();Math.abs(this.vertices[b].y)===
+this.radius?(t.x=(t.x+r.x)/2,this.faces.push(new THREE.Face3(b,d,e,[f,n,p])),this.faceVertexUvs[0].push([t,m,q])):Math.abs(this.vertices[d].y)===this.radius?(m.x=(m.x+q.x)/2,this.faces.push(new THREE.Face3(b,c,d,[f,g,n])),this.faceVertexUvs[0].push([t,r,m])):(this.faces.push(new THREE.Face3(b,c,e,[f,g,p])),this.faceVertexUvs[0].push([t,r,q]),this.faces.push(new THREE.Face3(c,d,e,[g.clone(),n,p.clone()])),this.faceVertexUvs[0].push([r.clone(),m,q.clone()]))}this.computeCentroids();this.computeFaceNormals();
+this.boundingSphere=new THREE.Sphere(new THREE.Vector3,a)};THREE.SphereGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.TextGeometry=function(a,b){var b=b||{},c=THREE.FontUtils.generateShapes(a,b);b.amount=void 0!==b.height?b.height:50;void 0===b.bevelThickness&&(b.bevelThickness=10);void 0===b.bevelSize&&(b.bevelSize=8);void 0===b.bevelEnabled&&(b.bevelEnabled=!1);THREE.ExtrudeGeometry.call(this,c,b)};THREE.TextGeometry.prototype=Object.create(THREE.ExtrudeGeometry.prototype);THREE.TorusGeometry=function(a,b,c,d,e){THREE.Geometry.call(this);this.radius=a||100;this.tube=b||40;this.radialSegments=c||8;this.tubularSegments=d||6;this.arc=e||2*Math.PI;e=new THREE.Vector3;a=[];b=[];for(c=0;c<=this.radialSegments;c++)for(d=0;d<=this.tubularSegments;d++){var f=d/this.tubularSegments*this.arc,g=2*c/this.radialSegments*Math.PI;e.x=this.radius*Math.cos(f);e.y=this.radius*Math.sin(f);var h=new THREE.Vector3;h.x=(this.radius+this.tube*Math.cos(g))*Math.cos(f);h.y=(this.radius+this.tube*
+Math.cos(g))*Math.sin(f);h.z=this.tube*Math.sin(g);this.vertices.push(h);a.push(new THREE.Vector2(d/this.tubularSegments,c/this.radialSegments));b.push(h.clone().sub(e).normalize())}for(c=1;c<=this.radialSegments;c++)for(d=1;d<=this.tubularSegments;d++){var e=(this.tubularSegments+1)*c+d-1,f=(this.tubularSegments+1)*(c-1)+d-1,g=(this.tubularSegments+1)*(c-1)+d,h=(this.tubularSegments+1)*c+d,i=new THREE.Face3(e,f,h,[b[e].clone(),b[f].clone(),b[h].clone()]);this.faces.push(i);this.faceVertexUvs[0].push([a[e].clone(),
+a[f].clone(),a[h].clone()]);i=new THREE.Face3(f,g,h,[b[f].clone(),b[g].clone(),b[h].clone()]);this.faces.push(i);this.faceVertexUvs[0].push([a[f].clone(),a[g].clone(),a[h].clone()])}this.computeCentroids();this.computeFaceNormals()};THREE.TorusGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.TorusKnotGeometry=function(a,b,c,d,e,f,g){function h(a,b,c,d,e){var f=Math.cos(a),g=Math.sin(a),a=b/c*a,b=Math.cos(a),f=0.5*(d*(2+b))*f,g=0.5*d*(2+b)*g,d=0.5*e*d*Math.sin(a);return new THREE.Vector3(f,g,d)}THREE.Geometry.call(this);this.radius=a||100;this.tube=b||40;this.radialSegments=c||64;this.tubularSegments=d||8;this.p=e||2;this.q=f||3;this.heightScale=g||1;this.grid=Array(this.radialSegments);c=new THREE.Vector3;d=new THREE.Vector3;e=new THREE.Vector3;for(a=0;a<this.radialSegments;++a){this.grid[a]=
+Array(this.tubularSegments);b=2*(a/this.radialSegments)*this.p*Math.PI;f=h(b,this.q,this.p,this.radius,this.heightScale);b=h(b+0.01,this.q,this.p,this.radius,this.heightScale);c.subVectors(b,f);d.addVectors(b,f);e.crossVectors(c,d);d.crossVectors(e,c);e.normalize();d.normalize();for(b=0;b<this.tubularSegments;++b){var i=2*(b/this.tubularSegments)*Math.PI,g=-this.tube*Math.cos(i),i=this.tube*Math.sin(i),k=new THREE.Vector3;k.x=f.x+g*d.x+i*e.x;k.y=f.y+g*d.y+i*e.y;k.z=f.z+g*d.z+i*e.z;this.grid[a][b]=
+this.vertices.push(k)-1}}for(a=0;a<this.radialSegments;++a)for(b=0;b<this.tubularSegments;++b){var e=(a+1)%this.radialSegments,f=(b+1)%this.tubularSegments,c=this.grid[a][b],d=this.grid[e][b],e=this.grid[e][f],f=this.grid[a][f],g=new THREE.Vector2(a/this.radialSegments,b/this.tubularSegments),i=new THREE.Vector2((a+1)/this.radialSegments,b/this.tubularSegments),k=new THREE.Vector2((a+1)/this.radialSegments,(b+1)/this.tubularSegments),l=new THREE.Vector2(a/this.radialSegments,(b+1)/this.tubularSegments);
+this.faces.push(new THREE.Face3(c,d,f));this.faceVertexUvs[0].push([g,i,l]);this.faces.push(new THREE.Face3(d,e,f));this.faceVertexUvs[0].push([i.clone(),k,l.clone()])}this.computeCentroids();this.computeFaceNormals();this.computeVertexNormals()};THREE.TorusKnotGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.TubeGeometry=function(a,b,c,d,e){THREE.Geometry.call(this);this.path=a;this.segments=b||64;this.radius=c||1;this.radialSegments=d||8;this.closed=e||!1;this.grid=[];var f,g,d=this.segments+1,h,i,k,e=new THREE.Vector3,l,n,b=new THREE.TubeGeometry.FrenetFrames(this.path,this.segments,this.closed);l=b.normals;n=b.binormals;this.tangents=b.tangents;this.normals=l;this.binormals=n;for(b=0;b<d;b++){this.grid[b]=[];c=b/(d-1);k=a.getPointAt(c);f=l[b];g=n[b];for(c=0;c<this.radialSegments;c++)h=2*(c/this.radialSegments)*
+Math.PI,i=-this.radius*Math.cos(h),h=this.radius*Math.sin(h),e.copy(k),e.x+=i*f.x+h*g.x,e.y+=i*f.y+h*g.y,e.z+=i*f.z+h*g.z,this.grid[b][c]=this.vertices.push(new THREE.Vector3(e.x,e.y,e.z))-1}for(b=0;b<this.segments;b++)for(c=0;c<this.radialSegments;c++)e=this.closed?(b+1)%this.segments:b+1,l=(c+1)%this.radialSegments,a=this.grid[b][c],d=this.grid[e][c],e=this.grid[e][l],l=this.grid[b][l],n=new THREE.Vector2(b/this.segments,c/this.radialSegments),f=new THREE.Vector2((b+1)/this.segments,c/this.radialSegments),
+g=new THREE.Vector2((b+1)/this.segments,(c+1)/this.radialSegments),i=new THREE.Vector2(b/this.segments,(c+1)/this.radialSegments),this.faces.push(new THREE.Face3(a,d,l)),this.faceVertexUvs[0].push([n,f,i]),this.faces.push(new THREE.Face3(d,e,l)),this.faceVertexUvs[0].push([f.clone(),g,i.clone()]);this.computeCentroids();this.computeFaceNormals();this.computeVertexNormals()};THREE.TubeGeometry.prototype=Object.create(THREE.Geometry.prototype);
+THREE.TubeGeometry.FrenetFrames=function(a,b,c){new THREE.Vector3;var d=new THREE.Vector3;new THREE.Vector3;var e=[],f=[],g=[],h=new THREE.Vector3,i=new THREE.Matrix4,b=b+1,k,l,n;this.tangents=e;this.normals=f;this.binormals=g;for(k=0;k<b;k++)l=k/(b-1),e[k]=a.getTangentAt(l),e[k].normalize();f[0]=new THREE.Vector3;g[0]=new THREE.Vector3;a=Number.MAX_VALUE;k=Math.abs(e[0].x);l=Math.abs(e[0].y);n=Math.abs(e[0].z);k<=a&&(a=k,d.set(1,0,0));l<=a&&(a=l,d.set(0,1,0));n<=a&&d.set(0,0,1);h.crossVectors(e[0],
+d).normalize();f[0].crossVectors(e[0],h);g[0].crossVectors(e[0],f[0]);for(k=1;k<b;k++)f[k]=f[k-1].clone(),g[k]=g[k-1].clone(),h.crossVectors(e[k-1],e[k]),1E-4<h.length()&&(h.normalize(),d=Math.acos(THREE.Math.clamp(e[k-1].dot(e[k]),-1,1)),f[k].applyMatrix4(i.makeRotationAxis(h,d))),g[k].crossVectors(e[k],f[k]);if(c){d=Math.acos(THREE.Math.clamp(f[0].dot(f[b-1]),-1,1));d/=b-1;0<e[0].dot(h.crossVectors(f[0],f[b-1]))&&(d=-d);for(k=1;k<b;k++)f[k].applyMatrix4(i.makeRotationAxis(e[k],d*k)),g[k].crossVectors(e[k],
+f[k])}};THREE.PolyhedronGeometry=function(a,b,c,d){function e(a){var b=a.normalize().clone();b.index=h.vertices.push(b)-1;var c=Math.atan2(a.z,-a.x)/2/Math.PI+0.5,a=Math.atan2(-a.y,Math.sqrt(a.x*a.x+a.z*a.z))/Math.PI+0.5;b.uv=new THREE.Vector2(c,1-a);return b}function f(a,b,c){var d=new THREE.Face3(a.index,b.index,c.index,[a.clone(),b.clone(),c.clone()]);d.centroid.add(a).add(b).add(c).divideScalar(3);h.faces.push(d);d=Math.atan2(d.centroid.z,-d.centroid.x);h.faceVertexUvs[0].push([g(a.uv,a,d),g(b.uv,b,d),
+g(c.uv,c,d)])}function g(a,b,c){0>c&&1===a.x&&(a=new THREE.Vector2(a.x-1,a.y));0===b.x&&0===b.z&&(a=new THREE.Vector2(c/2/Math.PI+0.5,a.y));return a.clone()}THREE.Geometry.call(this);for(var c=c||1,d=d||0,h=this,i=0,k=a.length;i<k;i++)e(new THREE.Vector3(a[i][0],a[i][1],a[i][2]));for(var l=this.vertices,a=[],i=0,k=b.length;i<k;i++){var n=l[b[i][0]],p=l[b[i][1]],t=l[b[i][2]];a[i]=new THREE.Face3(n.index,p.index,t.index,[n.clone(),p.clone(),t.clone()])}i=0;for(k=a.length;i<k;i++){p=a[i];l=d;b=Math.pow(2,
+l);Math.pow(4,l);for(var l=e(h.vertices[p.a]),n=e(h.vertices[p.b]),r=e(h.vertices[p.c]),p=[],t=0;t<=b;t++){p[t]=[];for(var m=e(l.clone().lerp(r,t/b)),q=e(n.clone().lerp(r,t/b)),u=b-t,s=0;s<=u;s++)p[t][s]=0==s&&t==b?m:e(m.clone().lerp(q,s/u))}for(t=0;t<b;t++)for(s=0;s<2*(b-t)-1;s++)l=Math.floor(s/2),0==s%2?f(p[t][l+1],p[t+1][l],p[t][l]):f(p[t][l+1],p[t+1][l+1],p[t+1][l])}i=0;for(k=this.faceVertexUvs[0].length;i<k;i++)d=this.faceVertexUvs[0][i],a=d[0].x,b=d[1].x,l=d[2].x,n=Math.max(a,Math.max(b,l)),
+p=Math.min(a,Math.min(b,l)),0.9<n&&0.1>p&&(0.2>a&&(d[0].x+=1),0.2>b&&(d[1].x+=1),0.2>l&&(d[2].x+=1));i=0;for(k=this.vertices.length;i<k;i++)this.vertices[i].multiplyScalar(c);this.mergeVertices();this.computeCentroids();this.computeFaceNormals();this.boundingSphere=new THREE.Sphere(new THREE.Vector3,c)};THREE.PolyhedronGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.IcosahedronGeometry=function(a,b){this.radius=a;this.detail=b;var c=(1+Math.sqrt(5))/2;THREE.PolyhedronGeometry.call(this,[[-1,c,0],[1,c,0],[-1,-c,0],[1,-c,0],[0,-1,c],[0,1,c],[0,-1,-c],[0,1,-c],[c,0,-1],[c,0,1],[-c,0,-1],[-c,0,1]],[[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]],a,b)};THREE.IcosahedronGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.OctahedronGeometry=function(a,b){THREE.PolyhedronGeometry.call(this,[[1,0,0],[-1,0,0],[0,1,0],[0,-1,0],[0,0,1],[0,0,-1]],[[0,2,4],[0,4,3],[0,3,5],[0,5,2],[1,2,5],[1,5,3],[1,3,4],[1,4,2]],a,b)};THREE.OctahedronGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.TetrahedronGeometry=function(a,b){THREE.PolyhedronGeometry.call(this,[[1,1,1],[-1,-1,1],[-1,1,-1],[1,-1,-1]],[[2,1,0],[0,3,2],[1,3,0],[2,3,1]],a,b)};THREE.TetrahedronGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.ParametricGeometry=function(a,b,c){THREE.Geometry.call(this);var d=this.vertices,e=this.faces,f=this.faceVertexUvs[0],g,h,i,k,l=b+1;for(g=0;g<=c;g++){k=g/c;for(h=0;h<=b;h++)i=h/b,i=a(i,k),d.push(i)}var n,p,t,r;for(g=0;g<c;g++)for(h=0;h<b;h++)a=g*l+h,d=g*l+h+1,k=(g+1)*l+h+1,i=(g+1)*l+h,n=new THREE.Vector2(h/b,g/c),p=new THREE.Vector2((h+1)/b,g/c),t=new THREE.Vector2((h+1)/b,(g+1)/c),r=new THREE.Vector2(h/b,(g+1)/c),e.push(new THREE.Face3(a,d,i)),f.push([n,p,r]),e.push(new THREE.Face3(d,k,i)),
+f.push([p.clone(),t,r.clone()]);this.computeCentroids();this.computeFaceNormals();this.computeVertexNormals()};THREE.ParametricGeometry.prototype=Object.create(THREE.Geometry.prototype);THREE.AxisHelper=function(a){var a=a||1,b=new THREE.Geometry;b.vertices.push(new THREE.Vector3,new THREE.Vector3(a,0,0),new THREE.Vector3,new THREE.Vector3(0,a,0),new THREE.Vector3,new THREE.Vector3(0,0,a));b.colors.push(new THREE.Color(16711680),new THREE.Color(16755200),new THREE.Color(65280),new THREE.Color(11206400),new THREE.Color(255),new THREE.Color(43775));a=new THREE.LineBasicMaterial({vertexColors:THREE.VertexColors});THREE.Line.call(this,b,a,THREE.LinePieces)};
+THREE.AxisHelper.prototype=Object.create(THREE.Line.prototype);THREE.ArrowHelper=function(a,b,c,d,e,f){THREE.Object3D.call(this);void 0===d&&(d=16776960);void 0===c&&(c=1);void 0===e&&(e=0.2*c);void 0===f&&(f=0.2*e);this.position=b;b=new THREE.Geometry;b.vertices.push(new THREE.Vector3(0,0,0));b.vertices.push(new THREE.Vector3(0,1,0));this.line=new THREE.Line(b,new THREE.LineBasicMaterial({color:d}));this.line.matrixAutoUpdate=!1;this.add(this.line);b=new THREE.CylinderGeometry(0,0.5,1,5,1);b.applyMatrix((new THREE.Matrix4).makeTranslation(0,-0.5,0));this.cone=
+new THREE.Mesh(b,new THREE.MeshBasicMaterial({color:d}));this.cone.matrixAutoUpdate=!1;this.add(this.cone);this.setDirection(a);this.setLength(c,e,f)};THREE.ArrowHelper.prototype=Object.create(THREE.Object3D.prototype);THREE.ArrowHelper.prototype.setDirection=function(){var a=new THREE.Vector3,b;return function(c){0.99999<c.y?this.quaternion.set(0,0,0,1):-0.99999>c.y?this.quaternion.set(1,0,0,0):(a.set(c.z,0,-c.x).normalize(),b=Math.acos(c.y),this.quaternion.setFromAxisAngle(a,b))}}();
+THREE.ArrowHelper.prototype.setLength=function(a,b,c){void 0===b&&(b=0.2*a);void 0===c&&(c=0.2*b);this.line.scale.set(1,a,1);this.line.updateMatrix();this.cone.scale.set(c,b,c);this.cone.position.y=a;this.cone.updateMatrix()};THREE.ArrowHelper.prototype.setColor=function(a){this.line.material.color.setHex(a);this.cone.material.color.setHex(a)};THREE.BoxHelper=function(a){var b=[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=b;var c=new THREE.Geometry;c.vertices.push(b[0],b[1],b[1],b[2],b[2],b[3],b[3],b[0],b[4],b[5],b[5],b[6],b[6],b[7],b[7],b[4],b[0],b[4],b[1],b[5],b[2],b[6],b[3],b[7]);THREE.Line.call(this,c,new THREE.LineBasicMaterial({color:16776960}),THREE.LinePieces);
+void 0!==a&&this.update(a)};THREE.BoxHelper.prototype=Object.create(THREE.Line.prototype);
+THREE.BoxHelper.prototype.update=function(a){var b=a.geometry;null===b.boundingBox&&b.computeBoundingBox();var c=b.boundingBox.min,b=b.boundingBox.max,d=this.vertices;d[0].set(b.x,b.y,b.z);d[1].set(c.x,b.y,b.z);d[2].set(c.x,c.y,b.z);d[3].set(b.x,c.y,b.z);d[4].set(b.x,b.y,c.z);d[5].set(c.x,b.y,c.z);d[6].set(c.x,c.y,c.z);d[7].set(b.x,c.y,c.z);this.geometry.computeBoundingSphere();this.geometry.verticesNeedUpdate=!0;this.matrixAutoUpdate=!1;this.matrixWorld=a.matrixWorld};THREE.BoundingBoxHelper=function(a,b){var c=void 0!==b?b:8947848;this.object=a;this.box=new THREE.Box3;THREE.Mesh.call(this,new THREE.CubeGeometry(1,1,1),new THREE.MeshBasicMaterial({color:c,wireframe:!0}))};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)};THREE.CameraHelper=function(a){function b(a,b,d){c(a,d);c(b,d)}function c(a,b){d.vertices.push(new THREE.Vector3);d.colors.push(new THREE.Color(b));void 0===f[a]&&(f[a]=[]);f[a].push(d.vertices.length-1)}var d=new THREE.Geometry,e=new THREE.LineBasicMaterial({color:16777215,vertexColors:THREE.FaceColors}),f={};b("n1","n2",16755200);b("n2","n4",16755200);b("n4","n3",16755200);b("n3","n1",16755200);b("f1","f2",16755200);b("f2","f4",16755200);b("f4","f3",16755200);b("f3","f1",16755200);b("n1","f1",16755200);
+b("n2","f2",16755200);b("n3","f3",16755200);b("n4","f4",16755200);b("p","n1",16711680);b("p","n2",16711680);b("p","n3",16711680);b("p","n4",16711680);b("u1","u2",43775);b("u2","u3",43775);b("u3","u1",43775);b("c","t",16777215);b("p","c",3355443);b("cn1","cn2",3355443);b("cn3","cn4",3355443);b("cf1","cf2",3355443);b("cf3","cf4",3355443);THREE.Line.call(this,d,e,THREE.LinePieces);this.camera=a;this.matrixWorld=a.matrixWorld;this.matrixAutoUpdate=!1;this.pointMap=f;this.update()};
+THREE.CameraHelper.prototype=Object.create(THREE.Line.prototype);
+THREE.CameraHelper.prototype.update=function(){var a=new THREE.Vector3,b=new THREE.Camera,c=new THREE.Projector;return function(){function d(d,g,h,i){a.set(g,h,i);c.unprojectVector(a,b);d=e.pointMap[d];if(void 0!==d){g=0;for(h=d.length;g<h;g++)e.geometry.vertices[d[g]].copy(a)}}var e=this;b.projectionMatrix.copy(this.camera.projectionMatrix);d("c",0,0,-1);d("t",0,0,1);d("n1",-1,-1,-1);d("n2",1,-1,-1);d("n3",-1,1,-1);d("n4",1,1,-1);d("f1",-1,-1,1);d("f2",1,-1,1);d("f3",-1,1,1);d("f4",1,1,1);d("u1",
+0.7,1.1,-1);d("u2",-0.7,1.1,-1);d("u3",0,2,-1);d("cf1",-1,0,1);d("cf2",1,0,1);d("cf3",0,-1,1);d("cf4",0,1,1);d("cn1",-1,0,-1);d("cn2",1,0,-1);d("cn3",0,-1,-1);d("cn4",0,1,-1);this.geometry.verticesNeedUpdate=!0}}();THREE.DirectionalLightHelper=function(a,b){THREE.Object3D.call(this);this.light=a;this.light.updateMatrixWorld();this.matrixWorld=a.matrixWorld;this.matrixAutoUpdate=!1;var b=b||1,c=new THREE.PlaneGeometry(b,b),d=new THREE.MeshBasicMaterial({wireframe:!0,fog:!1});d.color.copy(this.light.color).multiplyScalar(this.light.intensity);this.lightPlane=new THREE.Mesh(c,d);this.add(this.lightPlane);c=new THREE.Geometry;c.vertices.push(new THREE.Vector3);c.vertices.push(new THREE.Vector3);d=new THREE.LineBasicMaterial({fog:!1});
+d.color.copy(this.light.color).multiplyScalar(this.light.intensity);this.targetLine=new THREE.Line(c,d);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 a=new THREE.Vector3,b=new THREE.Vector3,c=new THREE.Vector3;return function(){a.setFromMatrixPosition(this.light.matrixWorld);b.setFromMatrixPosition(this.light.target.matrixWorld);c.subVectors(b,a);this.lightPlane.lookAt(c);this.lightPlane.material.color.copy(this.light.color).multiplyScalar(this.light.intensity);this.targetLine.geometry.vertices[1].copy(c);this.targetLine.geometry.verticesNeedUpdate=!0;this.targetLine.material.color.copy(this.lightPlane.material.color)}}();THREE.EdgesHelper=function(a,b){var c=void 0!==b?b:16777215,d=[0,0],e={},f=function(a,b){return a-b},g=["a","b","c"],h=new THREE.BufferGeometry,i=a.geometry.clone();i.mergeVertices();i.computeFaceNormals();for(var k=i.vertices,i=i.faces,l=0,n=0,p=i.length;n<p;n++)for(var t=i[n],r=0;3>r;r++){d[0]=t[g[r]];d[1]=t[g[(r+1)%3]];d.sort(f);var m=d.toString();void 0===e[m]?(e[m]={vert1:d[0],vert2:d[1],face1:n,face2:void 0},l++):e[m].face2=n}h.addAttribute("position",Float32Array,2*l,3);d=h.attributes.position.array;
+f=0;for(m in e)if(g=e[m],void 0===g.face2||0.9999>i[g.face1].normal.dot(i[g.face2].normal))l=k[g.vert1],d[f++]=l.x,d[f++]=l.y,d[f++]=l.z,l=k[g.vert2],d[f++]=l.x,d[f++]=l.y,d[f++]=l.z;THREE.Line.call(this,h,new THREE.LineBasicMaterial({color:c}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.matrixWorld=a.matrixWorld};THREE.EdgesHelper.prototype=Object.create(THREE.Line.prototype);THREE.FaceNormalsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;for(var a=void 0!==c?c:16776960,d=void 0!==d?d:1,b=new THREE.Geometry,c=0,e=this.object.geometry.faces.length;c<e;c++)b.vertices.push(new THREE.Vector3),b.vertices.push(new THREE.Vector3);THREE.Line.call(this,b,new THREE.LineBasicMaterial({color:a,linewidth:d}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.normalMatrix=new THREE.Matrix3;this.update()};THREE.FaceNormalsHelper.prototype=Object.create(THREE.Line.prototype);
+THREE.FaceNormalsHelper.prototype.update=function(){var a=new THREE.Vector3;return function(){this.object.updateMatrixWorld(!0);this.normalMatrix.getNormalMatrix(this.object.matrixWorld);for(var b=this.geometry.vertices,c=this.object.geometry.faces,d=this.object.matrixWorld,e=0,f=c.length;e<f;e++){var g=c[e];a.copy(g.normal).applyMatrix3(this.normalMatrix).normalize().multiplyScalar(this.size);var h=2*e;b[h].copy(g.centroid).applyMatrix4(d);b[h+1].addVectors(b[h],a)}this.geometry.verticesNeedUpdate=
+!0;return this}}();THREE.GridHelper=function(a,b){var c=new THREE.Geometry,d=new THREE.LineBasicMaterial({vertexColors:THREE.VertexColors});this.color1=new THREE.Color(4473924);this.color2=new THREE.Color(8947848);for(var e=-a;e<=a;e+=b){c.vertices.push(new THREE.Vector3(-a,0,e),new THREE.Vector3(a,0,e),new THREE.Vector3(e,0,-a),new THREE.Vector3(e,0,a));var f=0===e?this.color1:this.color2;c.colors.push(f,f,f,f)}THREE.Line.call(this,c,d,THREE.LinePieces)};THREE.GridHelper.prototype=Object.create(THREE.Line.prototype);
+THREE.GridHelper.prototype.setColors=function(a,b){this.color1.set(a);this.color2.set(b);this.geometry.colorsNeedUpdate=!0};THREE.HemisphereLightHelper=function(a,b){THREE.Object3D.call(this);this.light=a;this.light.updateMatrixWorld();this.matrixWorld=a.matrixWorld;this.matrixAutoUpdate=!1;this.colors=[new THREE.Color,new THREE.Color];var c=new THREE.SphereGeometry(b,4,2);c.applyMatrix((new THREE.Matrix4).makeRotationX(-Math.PI/2));for(var d=0;8>d;d++)c.faces[d].color=this.colors[4>d?0:1];d=new THREE.MeshBasicMaterial({vertexColors:THREE.FaceColors,wireframe:!0});this.lightSphere=new THREE.Mesh(c,d);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 a=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(a.setFromMatrixPosition(this.light.matrixWorld).negate());this.lightSphere.geometry.colorsNeedUpdate=!0}}();THREE.PointLightHelper=function(a,b){this.light=a;this.light.updateMatrixWorld();var c=new THREE.SphereGeometry(b,4,2),d=new THREE.MeshBasicMaterial({wireframe:!0,fog:!1});d.color.copy(this.light.color).multiplyScalar(this.light.intensity);THREE.Mesh.call(this,c,d);this.matrixWorld=this.light.matrixWorld;this.matrixAutoUpdate=!1};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)};THREE.SpotLightHelper=function(a){THREE.Object3D.call(this);this.light=a;this.light.updateMatrixWorld();this.matrixWorld=a.matrixWorld;this.matrixAutoUpdate=!1;a=new THREE.CylinderGeometry(0,1,1,8,1,!0);a.applyMatrix((new THREE.Matrix4).makeTranslation(0,-0.5,0));a.applyMatrix((new THREE.Matrix4).makeRotationX(-Math.PI/2));var b=new THREE.MeshBasicMaterial({wireframe:!0,fog:!1});this.cone=new THREE.Mesh(a,b);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 a=new THREE.Vector3,b=new THREE.Vector3;return function(){var c=this.light.distance?this.light.distance:1E4,d=c*Math.tan(this.light.angle);this.cone.scale.set(d,d,c);a.setFromMatrixPosition(this.light.matrixWorld);b.setFromMatrixPosition(this.light.target.matrixWorld);this.cone.lookAt(b.sub(a));this.cone.material.color.copy(this.light.color).multiplyScalar(this.light.intensity)}}();THREE.VertexNormalsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;for(var b=void 0!==c?c:16711680,d=void 0!==d?d:1,c=new THREE.Geometry,a=a.geometry.faces,e=0,f=a.length;e<f;e++)for(var g=0,h=a[e].vertexNormals.length;g<h;g++)c.vertices.push(new THREE.Vector3),c.vertices.push(new THREE.Vector3);THREE.Line.call(this,c,new THREE.LineBasicMaterial({color:b,linewidth:d}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.normalMatrix=new THREE.Matrix3;this.update()};
+THREE.VertexNormalsHelper.prototype=Object.create(THREE.Line.prototype);
+THREE.VertexNormalsHelper.prototype.update=function(){var a=new THREE.Vector3;return function(){var b=["a","b","c","d"];this.object.updateMatrixWorld(!0);this.normalMatrix.getNormalMatrix(this.object.matrixWorld);for(var c=this.geometry.vertices,d=this.object.geometry.vertices,e=this.object.geometry.faces,f=this.object.matrixWorld,g=0,h=0,i=e.length;h<i;h++)for(var k=e[h],l=0,n=k.vertexNormals.length;l<n;l++){var p=k.vertexNormals[l];c[g].copy(d[k[b[l]]]).applyMatrix4(f);a.copy(p).applyMatrix3(this.normalMatrix).normalize().multiplyScalar(this.size);
+a.add(c[g]);g+=1;c[g].copy(a);g+=1}this.geometry.verticesNeedUpdate=!0;return this}}();THREE.VertexTangentsHelper=function(a,b,c,d){this.object=a;this.size=void 0!==b?b:1;for(var b=void 0!==c?c:255,d=void 0!==d?d:1,c=new THREE.Geometry,a=a.geometry.faces,e=0,f=a.length;e<f;e++)for(var g=0,h=a[e].vertexTangents.length;g<h;g++)c.vertices.push(new THREE.Vector3),c.vertices.push(new THREE.Vector3);THREE.Line.call(this,c,new THREE.LineBasicMaterial({color:b,linewidth:d}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.update()};THREE.VertexTangentsHelper.prototype=Object.create(THREE.Line.prototype);
+THREE.VertexTangentsHelper.prototype.update=function(){var a=new THREE.Vector3;return function(){var b=["a","b","c","d"];this.object.updateMatrixWorld(!0);for(var c=this.geometry.vertices,d=this.object.geometry.vertices,e=this.object.geometry.faces,f=this.object.matrixWorld,g=0,h=0,i=e.length;h<i;h++)for(var k=e[h],l=0,n=k.vertexTangents.length;l<n;l++){var p=k.vertexTangents[l];c[g].copy(d[k[b[l]]]).applyMatrix4(f);a.copy(p).transformDirection(f).multiplyScalar(this.size);a.add(c[g]);g+=1;c[g].copy(a);
+g+=1}this.geometry.verticesNeedUpdate=!0;return this}}();THREE.WireframeHelper=function(a,b){var c=void 0!==b?b:16777215,d=[0,0],e={},f=function(a,b){return a-b},g=["a","b","c"],h=new THREE.BufferGeometry;if(a.geometry instanceof THREE.Geometry){for(var i=a.geometry.vertices,k=a.geometry.faces,l=0,n=new Uint32Array(6*k.length),p=0,t=k.length;p<t;p++)for(var r=k[p],m=0;3>m;m++){d[0]=r[g[m]];d[1]=r[g[(m+1)%3]];d.sort(f);var q=d.toString();void 0===e[q]&&(n[2*l]=d[0],n[2*l+1]=d[1],e[q]=!0,l++)}h.addAttribute("position",Float32Array,2*l,3);d=h.attributes.position.array;
+p=0;for(t=l;p<t;p++)for(m=0;2>m;m++)l=i[n[2*p+m]],g=6*p+3*m,d[g+0]=l.x,d[g+1]=l.y,d[g+2]=l.z}else if(a.geometry instanceof THREE.BufferGeometry&&void 0!==a.geometry.attributes.index){for(var i=a.geometry.attributes.position.array,t=a.geometry.attributes.index.array,k=a.geometry.offsets,l=0,n=new Uint32Array(2*t.length),r=0,u=k.length;r<u;++r)for(var m=k[r].start,q=k[r].count,g=k[r].index,p=m,s=m+q;p<s;p+=3)for(m=0;3>m;m++)d[0]=g+t[p+m],d[1]=g+t[p+(m+1)%3],d.sort(f),q=d.toString(),void 0===e[q]&&(n[2*
+l]=d[0],n[2*l+1]=d[1],e[q]=!0,l++);h.addAttribute("position",Float32Array,2*l,3);d=h.attributes.position.array;p=0;for(t=l;p<t;p++)for(m=0;2>m;m++)g=6*p+3*m,l=3*n[2*p+m],d[g+0]=i[l],d[g+1]=i[l+1],d[g+2]=i[l+2]}else if(a.geometry instanceof THREE.BufferGeometry){i=a.geometry.attributes.position.array;l=i.length/3;n=l/3;h.addAttribute("position",Float32Array,2*l,3);d=h.attributes.position.array;p=0;for(t=n;p<t;p++)for(m=0;3>m;m++)g=18*p+6*m,n=9*p+3*m,d[g+0]=i[n],d[g+1]=i[n+1],d[g+2]=i[n+2],l=9*p+3*
+((m+1)%3),d[g+3]=i[l],d[g+4]=i[l+1],d[g+5]=i[l+2]}THREE.Line.call(this,h,new THREE.LineBasicMaterial({color:c}),THREE.LinePieces);this.matrixAutoUpdate=!1;this.matrixWorld=a.matrixWorld};THREE.WireframeHelper.prototype=Object.create(THREE.Line.prototype);THREE.ImmediateRenderObject=function(){THREE.Object3D.call(this);this.render=function(){}};THREE.ImmediateRenderObject.prototype=Object.create(THREE.Object3D.prototype);THREE.LensFlare=function(a,b,c,d,e){THREE.Object3D.call(this);this.lensFlares=[];this.positionScreen=new THREE.Vector3;this.customUpdateCallback=void 0;void 0!==a&&this.add(a,b,c,d,e)};THREE.LensFlare.prototype=Object.create(THREE.Object3D.prototype);
+THREE.LensFlare.prototype.add=function(a,b,c,d,e,f){void 0===b&&(b=-1);void 0===c&&(c=0);void 0===f&&(f=1);void 0===e&&(e=new THREE.Color(16777215));void 0===d&&(d=THREE.NormalBlending);c=Math.min(c,Math.max(0,c));this.lensFlares.push({texture:a,size:b,distance:c,x:0,y:0,z:0,scale:1,rotation:1,opacity:f,color:e,blending:d})};
+THREE.LensFlare.prototype.updateLensFlares=function(){var a,b=this.lensFlares.length,c,d=2*-this.positionScreen.x,e=2*-this.positionScreen.y;for(a=0;a<b;a++)c=this.lensFlares[a],c.x=this.positionScreen.x+d*c.distance,c.y=this.positionScreen.y+e*c.distance,c.wantedRotation=0.25*c.x*Math.PI,c.rotation+=0.25*(c.wantedRotation-c.rotation)};THREE.MorphBlendMesh=function(a,b){THREE.Mesh.call(this,a,b);this.animationsMap={};this.animationsList=[];var c=this.geometry.morphTargets.length;this.createAnimation("__default",0,c-1,c/1);this.setAnimationWeight("__default",1)};THREE.MorphBlendMesh.prototype=Object.create(THREE.Mesh.prototype);
+THREE.MorphBlendMesh.prototype.createAnimation=function(a,b,c,d){b={startFrame:b,endFrame:c,length:c-b+1,fps:d,duration:(c-b)/d,lastFrame:0,currentFrame:0,active:!1,time:0,direction:1,weight:1,directionBackwards:!1,mirroredLoop:!1};this.animationsMap[a]=b;this.animationsList.push(b)};
+THREE.MorphBlendMesh.prototype.autoCreateAnimations=function(a){for(var b=/([a-z]+)(\d+)/,c,d={},e=this.geometry,f=0,g=e.morphTargets.length;f<g;f++){var h=e.morphTargets[f].name.match(b);if(h&&1<h.length){var i=h[1];d[i]||(d[i]={start:Infinity,end:-Infinity});h=d[i];f<h.start&&(h.start=f);f>h.end&&(h.end=f);c||(c=i)}}for(i in d)h=d[i],this.createAnimation(i,h.start,h.end,a);this.firstAnimation=c};
+THREE.MorphBlendMesh.prototype.setAnimationDirectionForward=function(a){if(a=this.animationsMap[a])a.direction=1,a.directionBackwards=!1};THREE.MorphBlendMesh.prototype.setAnimationDirectionBackward=function(a){if(a=this.animationsMap[a])a.direction=-1,a.directionBackwards=!0};THREE.MorphBlendMesh.prototype.setAnimationFPS=function(a,b){var c=this.animationsMap[a];c&&(c.fps=b,c.duration=(c.end-c.start)/c.fps)};
+THREE.MorphBlendMesh.prototype.setAnimationDuration=function(a,b){var c=this.animationsMap[a];c&&(c.duration=b,c.fps=(c.end-c.start)/c.duration)};THREE.MorphBlendMesh.prototype.setAnimationWeight=function(a,b){var c=this.animationsMap[a];c&&(c.weight=b)};THREE.MorphBlendMesh.prototype.setAnimationTime=function(a,b){var c=this.animationsMap[a];c&&(c.time=b)};THREE.MorphBlendMesh.prototype.getAnimationTime=function(a){var b=0;if(a=this.animationsMap[a])b=a.time;return b};
+THREE.MorphBlendMesh.prototype.getAnimationDuration=function(a){var b=-1;if(a=this.animationsMap[a])b=a.duration;return b};THREE.MorphBlendMesh.prototype.playAnimation=function(a){var b=this.animationsMap[a];b?(b.time=0,b.active=!0):console.warn("animation["+a+"] undefined")};THREE.MorphBlendMesh.prototype.stopAnimation=function(a){if(a=this.animationsMap[a])a.active=!1};
+THREE.MorphBlendMesh.prototype.update=function(a){for(var b=0,c=this.animationsList.length;b<c;b++){var d=this.animationsList[b];if(d.active){var e=d.duration/d.length;d.time+=d.direction*a;if(d.mirroredLoop){if(d.time>d.duration||0>d.time)d.direction*=-1,d.time>d.duration&&(d.time=d.duration,d.directionBackwards=!0),0>d.time&&(d.time=0,d.directionBackwards=!1)}else d.time%=d.duration,0>d.time&&(d.time+=d.duration);var f=d.startFrame+THREE.Math.clamp(Math.floor(d.time/e),0,d.length-1),g=d.weight;
+f!==d.currentFrame&&(this.morphTargetInfluences[d.lastFrame]=0,this.morphTargetInfluences[d.currentFrame]=1*g,this.morphTargetInfluences[f]=0,d.lastFrame=d.currentFrame,d.currentFrame=f);e=d.time%e/e;d.directionBackwards&&(e=1-e);this.morphTargetInfluences[d.currentFrame]=e*g;this.morphTargetInfluences[d.lastFrame]=(1-e)*g}}};THREE.LensFlarePlugin=function(){function a(a,c){var d=b.createProgram(),e=b.createShader(b.FRAGMENT_SHADER),f=b.createShader(b.VERTEX_SHADER),g="precision "+c+" float;\n";b.shaderSource(e,g+a.fragmentShader);b.shaderSource(f,g+a.vertexShader);b.compileShader(e);b.compileShader(f);b.attachShader(d,e);b.attachShader(d,f);b.linkProgram(d);return d}var b,c,d,e,f,g,h,i,k,l,n,p,t;this.init=function(r){b=r.context;c=r;d=r.getPrecision();e=new Float32Array(16);f=new Uint16Array(6);r=0;e[r++]=-1;e[r++]=-1;
+e[r++]=0;e[r++]=0;e[r++]=1;e[r++]=-1;e[r++]=1;e[r++]=0;e[r++]=1;e[r++]=1;e[r++]=1;e[r++]=1;e[r++]=-1;e[r++]=1;e[r++]=0;e[r++]=1;r=0;f[r++]=0;f[r++]=1;f[r++]=2;f[r++]=0;f[r++]=2;f[r++]=3;g=b.createBuffer();h=b.createBuffer();b.bindBuffer(b.ARRAY_BUFFER,g);b.bufferData(b.ARRAY_BUFFER,e,b.STATIC_DRAW);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,h);b.bufferData(b.ELEMENT_ARRAY_BUFFER,f,b.STATIC_DRAW);i=b.createTexture();k=b.createTexture();b.bindTexture(b.TEXTURE_2D,i);b.texImage2D(b.TEXTURE_2D,0,b.RGB,16,16,
+0,b.RGB,b.UNSIGNED_BYTE,null);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,b.NEAREST);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,b.NEAREST);b.bindTexture(b.TEXTURE_2D,k);b.texImage2D(b.TEXTURE_2D,0,b.RGBA,16,16,0,b.RGBA,b.UNSIGNED_BYTE,null);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_S,b.CLAMP_TO_EDGE);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_WRAP_T,b.CLAMP_TO_EDGE);
+b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MAG_FILTER,b.NEAREST);b.texParameteri(b.TEXTURE_2D,b.TEXTURE_MIN_FILTER,b.NEAREST);0>=b.getParameter(b.MAX_VERTEX_TEXTURE_IMAGE_UNITS)?(l=!1,n=a(THREE.ShaderFlares.lensFlare,d)):(l=!0,n=a(THREE.ShaderFlares.lensFlareVertexTexture,d));p={};t={};p.vertex=b.getAttribLocation(n,"position");p.uv=b.getAttribLocation(n,"uv");t.renderType=b.getUniformLocation(n,"renderType");t.map=b.getUniformLocation(n,"map");t.occlusionMap=b.getUniformLocation(n,"occlusionMap");t.opacity=
+b.getUniformLocation(n,"opacity");t.color=b.getUniformLocation(n,"color");t.scale=b.getUniformLocation(n,"scale");t.rotation=b.getUniformLocation(n,"rotation");t.screenPosition=b.getUniformLocation(n,"screenPosition")};this.render=function(a,d,e,f){var a=a.__webglFlares,s=a.length;if(s){var v=new THREE.Vector3,z=f/e,G=0.5*e,N=0.5*f,C=16/f,B=new THREE.Vector2(C*z,C),x=new THREE.Vector3(1,1,0),F=new THREE.Vector2(1,1),L=t,C=p;b.useProgram(n);b.enableVertexAttribArray(p.vertex);b.enableVertexAttribArray(p.uv);
+b.uniform1i(L.occlusionMap,0);b.uniform1i(L.map,1);b.bindBuffer(b.ARRAY_BUFFER,g);b.vertexAttribPointer(C.vertex,2,b.FLOAT,!1,16,0);b.vertexAttribPointer(C.uv,2,b.FLOAT,!1,16,8);b.bindBuffer(b.ELEMENT_ARRAY_BUFFER,h);b.disable(b.CULL_FACE);b.depthMask(!1);var w,E,y,D,H;for(w=0;w<s;w++)if(C=16/f,B.set(C*z,C),D=a[w],v.set(D.matrixWorld.elements[12],D.matrixWorld.elements[13],D.matrixWorld.elements[14]),v.applyMatrix4(d.matrixWorldInverse),v.applyProjection(d.projectionMatrix),x.copy(v),F.x=x.x*G+G,
+F.y=x.y*N+N,l||0<F.x&&F.x<e&&0<F.y&&F.y<f){b.activeTexture(b.TEXTURE1);b.bindTexture(b.TEXTURE_2D,i);b.copyTexImage2D(b.TEXTURE_2D,0,b.RGB,F.x-8,F.y-8,16,16,0);b.uniform1i(L.renderType,0);b.uniform2f(L.scale,B.x,B.y);b.uniform3f(L.screenPosition,x.x,x.y,x.z);b.disable(b.BLEND);b.enable(b.DEPTH_TEST);b.drawElements(b.TRIANGLES,6,b.UNSIGNED_SHORT,0);b.activeTexture(b.TEXTURE0);b.bindTexture(b.TEXTURE_2D,k);b.copyTexImage2D(b.TEXTURE_2D,0,b.RGBA,F.x-8,F.y-8,16,16,0);b.uniform1i(L.renderType,1);b.disable(b.DEPTH_TEST);
+b.activeTexture(b.TEXTURE1);b.bindTexture(b.TEXTURE_2D,i);b.drawElements(b.TRIANGLES,6,b.UNSIGNED_SHORT,0);D.positionScreen.copy(x);D.customUpdateCallback?D.customUpdateCallback(D):D.updateLensFlares();b.uniform1i(L.renderType,2);b.enable(b.BLEND);E=0;for(y=D.lensFlares.length;E<y;E++)H=D.lensFlares[E],0.001<H.opacity&&0.001<H.scale&&(x.x=H.x,x.y=H.y,x.z=H.z,C=H.size*H.scale/f,B.x=C*z,B.y=C,b.uniform3f(L.screenPosition,x.x,x.y,x.z),b.uniform2f(L.scale,B.x,B.y),b.uniform1f(L.rotation,H.rotation),b.uniform1f(L.opacity,
+H.opacity),b.uniform3f(L.color,H.color.r,H.color.g,H.color.b),c.setBlending(H.blending,H.blendEquation,H.blendSrc,H.blendDst),c.setTexture(H.texture,1),b.drawElements(b.TRIANGLES,6,b.UNSIGNED_SHORT,0))}b.enable(b.CULL_FACE);b.enable(b.DEPTH_TEST);b.depthMask(!0)}}};THREE.ShadowMapPlugin=function(){var a,b,c,d,e,f,g=new THREE.Frustum,h=new THREE.Matrix4,i=new THREE.Vector3,k=new THREE.Vector3,l=new THREE.Vector3;this.init=function(g){a=g.context;b=g;var g=THREE.ShaderLib.depthRGBA,h=THREE.UniformsUtils.clone(g.uniforms);c=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,vertexShader:g.vertexShader,uniforms:h});d=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,vertexShader:g.vertexShader,uniforms:h,morphTargets:!0});e=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,
+vertexShader:g.vertexShader,uniforms:h,skinning:!0});f=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,vertexShader:g.vertexShader,uniforms:h,morphTargets:!0,skinning:!0});c._shadowPass=!0;d._shadowPass=!0;e._shadowPass=!0;f._shadowPass=!0};this.render=function(a,c){b.shadowMapEnabled&&b.shadowMapAutoUpdate&&this.update(a,c)};this.update=function(n,p){var t,r,m,q,u,s,v,z,G,N=[];q=0;a.clearColor(1,1,1,1);a.disable(a.BLEND);a.enable(a.CULL_FACE);a.frontFace(a.CCW);b.shadowMapCullFace===THREE.CullFaceFront?
+a.cullFace(a.FRONT):a.cullFace(a.BACK);b.setDepthTest(!0);t=0;for(r=n.__lights.length;t<r;t++)if(m=n.__lights[t],m.castShadow)if(m instanceof THREE.DirectionalLight&&m.shadowCascade)for(u=0;u<m.shadowCascadeCount;u++){var C;if(m.shadowCascadeArray[u])C=m.shadowCascadeArray[u];else{G=m;v=u;C=new THREE.DirectionalLight;C.isVirtual=!0;C.onlyShadow=!0;C.castShadow=!0;C.shadowCameraNear=G.shadowCameraNear;C.shadowCameraFar=G.shadowCameraFar;C.shadowCameraLeft=G.shadowCameraLeft;C.shadowCameraRight=G.shadowCameraRight;
+C.shadowCameraBottom=G.shadowCameraBottom;C.shadowCameraTop=G.shadowCameraTop;C.shadowCameraVisible=G.shadowCameraVisible;C.shadowDarkness=G.shadowDarkness;C.shadowBias=G.shadowCascadeBias[v];C.shadowMapWidth=G.shadowCascadeWidth[v];C.shadowMapHeight=G.shadowCascadeHeight[v];C.pointsWorld=[];C.pointsFrustum=[];z=C.pointsWorld;s=C.pointsFrustum;for(var B=0;8>B;B++)z[B]=new THREE.Vector3,s[B]=new THREE.Vector3;z=G.shadowCascadeNearZ[v];G=G.shadowCascadeFarZ[v];s[0].set(-1,-1,z);s[1].set(1,-1,z);s[2].set(-1,
+1,z);s[3].set(1,1,z);s[4].set(-1,-1,G);s[5].set(1,-1,G);s[6].set(-1,1,G);s[7].set(1,1,G);C.originalCamera=p;s=new THREE.Gyroscope;s.position=m.shadowCascadeOffset;s.add(C);s.add(C.target);p.add(s);m.shadowCascadeArray[u]=C;console.log("Created virtualLight",C)}v=m;z=u;G=v.shadowCascadeArray[z];G.position.copy(v.position);G.target.position.copy(v.target.position);G.lookAt(G.target);G.shadowCameraVisible=v.shadowCameraVisible;G.shadowDarkness=v.shadowDarkness;G.shadowBias=v.shadowCascadeBias[z];s=v.shadowCascadeNearZ[z];
+v=v.shadowCascadeFarZ[z];G=G.pointsFrustum;G[0].z=s;G[1].z=s;G[2].z=s;G[3].z=s;G[4].z=v;G[5].z=v;G[6].z=v;G[7].z=v;N[q]=C;q++}else N[q]=m,q++;t=0;for(r=N.length;t<r;t++){m=N[t];m.shadowMap||(u=THREE.LinearFilter,b.shadowMapType===THREE.PCFSoftShadowMap&&(u=THREE.NearestFilter),m.shadowMap=new THREE.WebGLRenderTarget(m.shadowMapWidth,m.shadowMapHeight,{minFilter:u,magFilter:u,format:THREE.RGBAFormat}),m.shadowMapSize=new THREE.Vector2(m.shadowMapWidth,m.shadowMapHeight),m.shadowMatrix=new THREE.Matrix4);
+if(!m.shadowCamera){if(m instanceof THREE.SpotLight)m.shadowCamera=new THREE.PerspectiveCamera(m.shadowCameraFov,m.shadowMapWidth/m.shadowMapHeight,m.shadowCameraNear,m.shadowCameraFar);else if(m instanceof THREE.DirectionalLight)m.shadowCamera=new THREE.OrthographicCamera(m.shadowCameraLeft,m.shadowCameraRight,m.shadowCameraTop,m.shadowCameraBottom,m.shadowCameraNear,m.shadowCameraFar);else{console.error("Unsupported light type for shadow");continue}n.add(m.shadowCamera);!0===n.autoUpdate&&n.updateMatrixWorld()}m.shadowCameraVisible&&
+!m.cameraHelper&&(m.cameraHelper=new THREE.CameraHelper(m.shadowCamera),m.shadowCamera.add(m.cameraHelper));if(m.isVirtual&&C.originalCamera==p){u=p;q=m.shadowCamera;s=m.pointsFrustum;G=m.pointsWorld;i.set(Infinity,Infinity,Infinity);k.set(-Infinity,-Infinity,-Infinity);for(v=0;8>v;v++)z=G[v],z.copy(s[v]),THREE.ShadowMapPlugin.__projector.unprojectVector(z,u),z.applyMatrix4(q.matrixWorldInverse),z.x<i.x&&(i.x=z.x),z.x>k.x&&(k.x=z.x),z.y<i.y&&(i.y=z.y),z.y>k.y&&(k.y=z.y),z.z<i.z&&(i.z=z.z),z.z>k.z&&
+(k.z=z.z);q.left=i.x;q.right=k.x;q.top=k.y;q.bottom=i.y;q.updateProjectionMatrix()}q=m.shadowMap;s=m.shadowMatrix;u=m.shadowCamera;u.position.setFromMatrixPosition(m.matrixWorld);l.setFromMatrixPosition(m.target.matrixWorld);u.lookAt(l);u.updateMatrixWorld();u.matrixWorldInverse.getInverse(u.matrixWorld);m.cameraHelper&&(m.cameraHelper.visible=m.shadowCameraVisible);m.shadowCameraVisible&&m.cameraHelper.update();s.set(0.5,0,0,0.5,0,0.5,0,0.5,0,0,0.5,0.5,0,0,0,1);s.multiply(u.projectionMatrix);s.multiply(u.matrixWorldInverse);
+h.multiplyMatrices(u.projectionMatrix,u.matrixWorldInverse);g.setFromMatrix(h);b.setRenderTarget(q);b.clear();G=n.__webglObjects;m=0;for(q=G.length;m<q;m++)if(v=G[m],s=v.object,v.render=!1,s.visible&&s.castShadow&&(!(s instanceof THREE.Mesh||s instanceof THREE.ParticleSystem)||!s.frustumCulled||g.intersectsObject(s)))s._modelViewMatrix.multiplyMatrices(u.matrixWorldInverse,s.matrixWorld),v.render=!0;m=0;for(q=G.length;m<q;m++)v=G[m],v.render&&(s=v.object,v=v.buffer,B=s.material instanceof THREE.MeshFaceMaterial?
+s.material.materials[0]:s.material,z=0<s.geometry.morphTargets.length&&B.morphTargets,B=s instanceof THREE.SkinnedMesh&&B.skinning,z=s.customDepthMaterial?s.customDepthMaterial:B?z?f:e:z?d:c,v instanceof THREE.BufferGeometry?b.renderBufferDirect(u,n.__lights,null,z,v,s):b.renderBuffer(u,n.__lights,null,z,v,s));G=n.__webglObjectsImmediate;m=0;for(q=G.length;m<q;m++)v=G[m],s=v.object,s.visible&&s.castShadow&&(s._modelViewMatrix.multiplyMatrices(u.matrixWorldInverse,s.matrixWorld),b.renderImmediateObject(u,
+n.__lights,null,c,s))}t=b.getClearColor();r=b.getClearAlpha();a.clearColor(t.r,t.g,t.b,r);a.enable(a.BLEND);b.shadowMapCullFace===THREE.CullFaceFront&&a.cullFace(a.BACK)}};THREE.ShadowMapPlugin.__projector=new THREE.Projector;THREE.SpritePlugin=function(){var a,b,c,d,e,f,g,h,i,k,l,n,p,t,r,m,q;function u(a,b){return a.z!==b.z?b.z-a.z:b.id-a.id}var s,v,z,G,N,C,B,x;this.init=function(u){s=u.context;v=u;G=new Float32Array([-0.5,-0.5,0,0,0.5,-0.5,1,0,0.5,0.5,1,1,-0.5,0.5,0,1]);N=new Uint16Array([0,1,2,0,2,3]);C=s.createBuffer();B=s.createBuffer();s.bindBuffer(s.ARRAY_BUFFER,C);s.bufferData(s.ARRAY_BUFFER,G,s.STATIC_DRAW);s.bindBuffer(s.ELEMENT_ARRAY_BUFFER,B);s.bufferData(s.ELEMENT_ARRAY_BUFFER,N,s.STATIC_DRAW);var u=s.createProgram(),
+L=s.createShader(s.VERTEX_SHADER),w=s.createShader(s.FRAGMENT_SHADER);s.shaderSource(L,["precision "+v.getPrecision()+" float;","uniform mat4 modelViewMatrix;\nuniform mat4 projectionMatrix;\nuniform float rotation;\nuniform vec2 scale;\nuniform vec2 uvOffset;\nuniform vec2 uvScale;\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUV;\nvoid main() {\nvUV = uvOffset + uv * uvScale;\nvec2 alignedPosition = position * scale;\nvec2 rotatedPosition;\nrotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\nrotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\nvec4 finalPosition;\nfinalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\nfinalPosition.xy += rotatedPosition;\nfinalPosition = projectionMatrix * finalPosition;\ngl_Position = finalPosition;\n}"].join("\n"));
+s.shaderSource(w,["precision "+v.getPrecision()+" float;","uniform vec3 color;\nuniform sampler2D map;\nuniform float opacity;\nuniform int fogType;\nuniform vec3 fogColor;\nuniform float fogDensity;\nuniform float fogNear;\nuniform float fogFar;\nuniform float alphaTest;\nvarying vec2 vUV;\nvoid main() {\nvec4 texture = texture2D( map, vUV );\nif ( texture.a < alphaTest ) discard;\ngl_FragColor = vec4( color * texture.xyz, texture.a * opacity );\nif ( fogType > 0 ) {\nfloat depth = gl_FragCoord.z / gl_FragCoord.w;\nfloat fogFactor = 0.0;\nif ( fogType == 1 ) {\nfogFactor = smoothstep( fogNear, fogFar, depth );\n} else {\nconst float LOG2 = 1.442695;\nfloat fogFactor = exp2( - fogDensity * fogDensity * depth * depth * LOG2 );\nfogFactor = 1.0 - clamp( fogFactor, 0.0, 1.0 );\n}\ngl_FragColor = mix( gl_FragColor, vec4( fogColor, gl_FragColor.w ), fogFactor );\n}\n}"].join("\n"));
+s.compileShader(L);s.compileShader(w);s.attachShader(u,L);s.attachShader(u,w);s.linkProgram(u);x=u;m=s.getAttribLocation(x,"position");q=s.getAttribLocation(x,"uv");a=s.getUniformLocation(x,"uvOffset");b=s.getUniformLocation(x,"uvScale");c=s.getUniformLocation(x,"rotation");d=s.getUniformLocation(x,"scale");e=s.getUniformLocation(x,"color");f=s.getUniformLocation(x,"map");g=s.getUniformLocation(x,"opacity");h=s.getUniformLocation(x,"modelViewMatrix");i=s.getUniformLocation(x,"projectionMatrix");k=
+s.getUniformLocation(x,"fogType");l=s.getUniformLocation(x,"fogDensity");n=s.getUniformLocation(x,"fogNear");p=s.getUniformLocation(x,"fogFar");t=s.getUniformLocation(x,"fogColor");r=s.getUniformLocation(x,"alphaTest");u=document.createElement("canvas");u.width=8;u.height=8;L=u.getContext("2d");L.fillStyle="#ffffff";L.fillRect(0,0,u.width,u.height);z=new THREE.Texture(u);z.needsUpdate=!0};this.render=function(F,G){var w=F.__webglSprites,E=w.length;if(E){s.useProgram(x);s.enableVertexAttribArray(m);
+s.enableVertexAttribArray(q);s.disable(s.CULL_FACE);s.enable(s.BLEND);s.bindBuffer(s.ARRAY_BUFFER,C);s.vertexAttribPointer(m,2,s.FLOAT,!1,16,0);s.vertexAttribPointer(q,2,s.FLOAT,!1,16,8);s.bindBuffer(s.ELEMENT_ARRAY_BUFFER,B);s.uniformMatrix4fv(i,!1,G.projectionMatrix.elements);s.activeTexture(s.TEXTURE0);s.uniform1i(f,0);var y=0,D=0,H=F.fog;H?(s.uniform3f(t,H.color.r,H.color.g,H.color.b),H instanceof THREE.Fog?(s.uniform1f(n,H.near),s.uniform1f(p,H.far),s.uniform1i(k,1),D=y=1):H instanceof THREE.FogExp2&&
+(s.uniform1f(l,H.density),s.uniform1i(k,2),D=y=2)):(s.uniform1i(k,0),D=y=0);for(var K,A,N=[],H=0;H<E;H++)K=w[H],!1!==K.visible&&(K._modelViewMatrix.multiplyMatrices(G.matrixWorldInverse,K.matrixWorld),K.z=-K._modelViewMatrix.elements[14]);w.sort(u);for(H=0;H<E;H++)K=w[H],!1!==K.visible&&(A=K.material,s.uniform1f(r,A.alphaTest),s.uniformMatrix4fv(h,!1,K._modelViewMatrix.elements),N[0]=K.scale.x,N[1]=K.scale.y,K=F.fog&&A.fog?D:0,y!==K&&(s.uniform1i(k,K),y=K),null!==A.map?(s.uniform2f(a,A.map.offset.x,
+A.map.offset.y),s.uniform2f(b,A.map.repeat.x,A.map.repeat.y)):(s.uniform2f(a,0,0),s.uniform2f(b,1,1)),s.uniform1f(g,A.opacity),s.uniform3f(e,A.color.r,A.color.g,A.color.b),s.uniform1f(c,A.rotation),s.uniform2fv(d,N),v.setBlending(A.blending,A.blendEquation,A.blendSrc,A.blendDst),v.setDepthTest(A.depthTest),v.setDepthWrite(A.depthWrite),A.map&&A.map.image&&A.map.image.width?v.setTexture(A.map,0):v.setTexture(z,0),s.drawElements(s.TRIANGLES,6,s.UNSIGNED_SHORT,0));s.enable(s.CULL_FACE)}}};THREE.DepthPassPlugin=function(){this.enabled=!1;this.renderTarget=null;var a,b,c,d,e,f,g=new THREE.Frustum,h=new THREE.Matrix4;this.init=function(g){a=g.context;b=g;var g=THREE.ShaderLib.depthRGBA,h=THREE.UniformsUtils.clone(g.uniforms);c=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,vertexShader:g.vertexShader,uniforms:h});d=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,vertexShader:g.vertexShader,uniforms:h,morphTargets:!0});e=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,
+vertexShader:g.vertexShader,uniforms:h,skinning:!0});f=new THREE.ShaderMaterial({fragmentShader:g.fragmentShader,vertexShader:g.vertexShader,uniforms:h,morphTargets:!0,skinning:!0});c._shadowPass=!0;d._shadowPass=!0;e._shadowPass=!0;f._shadowPass=!0};this.render=function(a,b){this.enabled&&this.update(a,b)};this.update=function(i,k){var l,n,p,t,r,m;a.clearColor(1,1,1,1);a.disable(a.BLEND);b.setDepthTest(!0);!0===i.autoUpdate&&i.updateMatrixWorld();k.matrixWorldInverse.getInverse(k.matrixWorld);h.multiplyMatrices(k.projectionMatrix,
+k.matrixWorldInverse);g.setFromMatrix(h);b.setRenderTarget(this.renderTarget);b.clear();m=i.__webglObjects;l=0;for(n=m.length;l<n;l++)if(p=m[l],r=p.object,p.render=!1,r.visible&&(!(r instanceof THREE.Mesh||r instanceof THREE.ParticleSystem)||!r.frustumCulled||g.intersectsObject(r)))r._modelViewMatrix.multiplyMatrices(k.matrixWorldInverse,r.matrixWorld),p.render=!0;var q;l=0;for(n=m.length;l<n;l++)if(p=m[l],p.render&&(r=p.object,p=p.buffer,!(r instanceof THREE.ParticleSystem)||r.customDepthMaterial))(q=
+r.material instanceof THREE.MeshFaceMaterial?r.material.materials[0]:r.material)&&b.setMaterialFaces(r.material),t=0<r.geometry.morphTargets.length&&q.morphTargets,q=r instanceof THREE.SkinnedMesh&&q.skinning,t=r.customDepthMaterial?r.customDepthMaterial:q?t?f:e:t?d:c,p instanceof THREE.BufferGeometry?b.renderBufferDirect(k,i.__lights,null,t,p,r):b.renderBuffer(k,i.__lights,null,t,p,r);m=i.__webglObjectsImmediate;l=0;for(n=m.length;l<n;l++)p=m[l],r=p.object,r.visible&&(r._modelViewMatrix.multiplyMatrices(k.matrixWorldInverse,
+r.matrixWorld),b.renderImmediateObject(k,i.__lights,null,c,r));l=b.getClearColor();n=b.getClearAlpha();a.clearColor(l.r,l.g,l.b,n);a.enable(a.BLEND)}};THREE.ShaderFlares={lensFlareVertexTexture:{vertexShader:"uniform lowp int renderType;\nuniform vec3 screenPosition;\nuniform vec2 scale;\nuniform float rotation;\nuniform sampler2D occlusionMap;\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUV;\nvarying float vVisibility;\nvoid main() {\nvUV = uv;\nvec2 pos = position;\nif( renderType == 2 ) {\nvec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );\nvisibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );\nvisibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );\nvisibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );\nvisibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );\nvisibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );\nvisibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );\nvisibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );\nvisibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );\nvVisibility =        visibility.r / 9.0;\nvVisibility *= 1.0 - visibility.g / 9.0;\nvVisibility *=       visibility.b / 9.0;\nvVisibility *= 1.0 - visibility.a / 9.0;\npos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;\npos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;\n}\ngl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );\n}",
+fragmentShader:"uniform lowp int renderType;\nuniform sampler2D map;\nuniform float opacity;\nuniform vec3 color;\nvarying vec2 vUV;\nvarying float vVisibility;\nvoid main() {\nif( renderType == 0 ) {\ngl_FragColor = vec4( 1.0, 0.0, 1.0, 0.0 );\n} else if( renderType == 1 ) {\ngl_FragColor = texture2D( map, vUV );\n} else {\nvec4 texture = texture2D( map, vUV );\ntexture.a *= opacity * vVisibility;\ngl_FragColor = texture;\ngl_FragColor.rgb *= color;\n}\n}"},lensFlare:{vertexShader:"uniform lowp int renderType;\nuniform vec3 screenPosition;\nuniform vec2 scale;\nuniform float rotation;\nattribute vec2 position;\nattribute vec2 uv;\nvarying vec2 vUV;\nvoid main() {\nvUV = uv;\nvec2 pos = position;\nif( renderType == 2 ) {\npos.x = cos( rotation ) * position.x - sin( rotation ) * position.y;\npos.y = sin( rotation ) * position.x + cos( rotation ) * position.y;\n}\ngl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );\n}",
+fragmentShader:"precision mediump float;\nuniform lowp int renderType;\nuniform sampler2D map;\nuniform sampler2D occlusionMap;\nuniform float opacity;\nuniform vec3 color;\nvarying vec2 vUV;\nvoid main() {\nif( renderType == 0 ) {\ngl_FragColor = vec4( texture2D( map, vUV ).rgb, 0.0 );\n} else if( renderType == 1 ) {\ngl_FragColor = texture2D( map, vUV );\n} else {\nfloat visibility = texture2D( occlusionMap, vec2( 0.5, 0.1 ) ).a;\nvisibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) ).a;\nvisibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) ).a;\nvisibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) ).a;\nvisibility = ( 1.0 - visibility / 4.0 );\nvec4 texture = texture2D( map, vUV );\ntexture.a *= opacity * visibility;\ngl_FragColor = texture;\ngl_FragColor.rgb *= color;\n}\n}"}};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/Camera.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/Camera.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/Camera.html	(revision 28897)
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">Abstract base class for cameras. This class should always be inherited when you build a new camera.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+			This constructor sets the following properties to the correct type: matrixWorldInverse and projectionMatrix.
+			
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Matrix4 matrixWorldInverse]</h3>
+		<div>This is the inverse of matrixWorld. MatrixWorld contains the Matrix which has the world transform of the Camera.</div>
+
+		<h3>.[page:Matrix4 projectionMatrix]</h3>
+		<div>This is the matrix which contains the projection.</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.lookAt( [page:Vector3 vector] )</h3>
+		<div>
+		vector â point to look at<br />
+		<br />
+		This makes the camera look at the vector position in the global space as long as the parent of this camera is the scene or at position (0,0,0).
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/OrthographicCamera.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/OrthographicCamera.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/OrthographicCamera.html	(revision 28897)
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr; [page:Camera] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">Camera with orthographic projection.</div>
+
+
+		<h2>Example</h2>
+
+		<code>var camera = new THREE.OrthographicCamera( width / - 2, width / 2, height / 2, height / - 2, 1, 1000 );
+scene.add( camera );</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Float left], [page:Float right], [page:Float top], [page:Float bottom], [page:Float near], [page:Float far] )</h3>
+		<div>
+		left â Camera frustum left plane.<br />
+		right â Camera frustum right plane.<br />
+		top â Camera frustum top plane.<br />
+		bottom â Camera frustum bottom plane.<br />
+		near â Camera frustum near plane.<br />
+		far â Camera frustum far plane.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float left]</h3>
+		<div>Camera frustum left plane.</div>
+
+		<h3>.[page:Float right]</h3>
+		<div>Camera frustum right plane.</div>
+
+		<h3>.[page:Float top]</h3>
+		<div>Camera frustum top plane.</div>
+
+		<h3>.[page:Float bottom]</h3>
+		<div>Camera frustum bottom plane.</div>
+
+		<h3>.[page:Float near]</h3>
+		<div>Camera frustum near plane.</div>
+
+		<h3>.[page:Float far]</h3>
+		<div>Camera frustum far plane.</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.updateProjectionMatrix()</h3>
+		<div>
+		Updates the camera projection matrix. Must be called after change of parameters.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/PerspectiveCamera.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/PerspectiveCamera.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/PerspectiveCamera.html	(revision 28897)
@@ -0,0 +1,119 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr; [page:Camera] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">Camera with perspective projection.</div>
+
+
+		<h2>Example</h2>
+
+		<code>var camera = new THREE.PerspectiveCamera( 45, width / height, 1, 1000 );
+scene.add( camera );</code>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [page:Float fov], [page:Float aspect], [page:Float near], [page:Float far] )</h3>
+		<div>
+		fov â Camera frustum vertical field of view.<br />
+		aspect â Camera frustum aspect ratio.<br />
+		near â Camera frustum near plane.<br />
+		far â Camera frustum far plane.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float fov]</h3>
+		<div>Camera frustum vertical field of view, from bottom to top of view, in degrees.</div>
+
+		<h3>.[page:Float aspect]</h3>
+		<div>Camera frustum aspect ratio, window width divided by window height.</div>
+
+		<h3>.[page:Float near]</h3>
+		<div>Camera frustum near plane.</div>
+
+		<h3>.[page:Float far]</h3>
+		<div>Camera frustum far plane.</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.setLens( [page:Float focalLength], [page:Float frameSize] )</h3>
+		<div>
+		focalLength â focal length<br />
+		frameSize â frame size
+		</div>
+
+		<div>
+		Uses focal length (in mm) to estimate and set FOV 35mm (fullframe) camera is used if frame size is not specified.<br />
+		Formula based on [link:http://www.bobatkins.com/photography/technical/field_of_view.html]
+		</div>
+
+		<h3>.setViewOffset( [page:Float fullWidth], [page:Float fullHeight], [page:Float x], [page:Float y], [page:Float width], [page:Float height] )</h3>
+		<div>
+		fullWidth â full width of multiview setup<br />
+		fullHeight â full height of multiview setup<br />
+		x â horizontal offset of subcamera<br />
+		y â vertical offset of subcamera<br />
+		width â width of subcamera<br />
+		height â height of subcamera
+		</div>
+
+		<div>
+		Sets an offset in a larger frustum. This is useful for multi-window or multi-monitor/multi-machine setups.
+		</div>
+
+		<div>
+		For example, if you have 3x2 monitors and each monitor is 1920x1080 and the monitors are in grid like this:<br />
+
+		<pre>+---+---+---+
+| A | B | C |
++---+---+---+
+| D | E | F |
++---+---+---+</pre>
+
+		then for each monitor you would call it like this:<br />
+
+		<code>var w = 1920;
+var h = 1080;
+var fullWidth = w * 3;
+var fullHeight = h * 2;
+
+// A
+camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
+// B
+camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
+// C
+camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
+// D
+camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
+// E
+camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
+// F
+camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
+</code>
+
+		Note there is no reason monitors have to be the same size or in a grid.
+		</div>
+
+		<h3>.updateProjectionMatrix()</h3>
+		<div>
+		Updates the camera projection matrix. Must be called after change of parameters.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/cameras/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/CustomBlendingEquations.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/CustomBlendingEquations.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/CustomBlendingEquations.html	(revision 28897)
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>Custom Blending Equation Constants</h1>
+
+		<h2>Equations</h2>
+		<div>
+		THREE.AddEquation<br />
+		THREE.SubtractEquation<br />
+		THREE.ReverseSubtractEquation
+		</div>
+
+		<h2>Destination Factors</h2>
+		<div>
+		THREE.ZeroFactor<br />
+		THREE.OneFactor<br />
+		THREE.SrcColorFactor<br />
+		THREE.OneMinusSrcColorFactor<br />
+		THREE.SrcAlphaFactor<br />
+		THREE.OneMinusSrcAlphaFactor<br />
+		THREE.DstAlphaFactor<br />
+		THREE.OneMinusDstAlphaFactor
+		</div>
+
+		<h2>Source Factors</h2>
+		<div>
+		THREE.DstColorFactor<br />
+		THREE.OneMinusDstColorFactor<br />
+		THREE.SrcAlphaSaturateFactor
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/Three.js src/Three.js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/GLState.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/GLState.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/GLState.html	(revision 28897)
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>GL State Conflicts</h1>
+
+		<h2>Cull Face</h2>
+		<div>
+		THREE.CullFaceNone<br />
+		THREE.CullFaceBack<br />
+		THREE.CullFaceFront<br />
+		THREE.CullFaceFrontBack
+		</div>
+
+		<h2>Front Face Direction</h2>
+		<div>
+		THREE.FrontFaceDirectionCW<br />
+		THREE.FrontFaceDirectionCCW<br />
+		</div>
+	
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/Three.js src/Three.js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/Materials.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/Materials.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/Materials.html	(revision 28897)
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>Material Constants</h1>
+
+		<h2>Side</h2>
+		<div>
+		THREE.FrontSide<br />
+		THREE.BackSide<br />
+		THREE.DoubleSide
+		</div>
+
+		<h2>Shading</h2>
+		<div>
+		THREE.NoShading<br />
+		THREE.FlatShading<br />
+		THREE.SmoothShading
+		</div>
+
+		<h2>Colors</h2>
+		<div>
+		THREE.NoColors<br />
+		THREE.FaceColors<br />
+		THREE.VertexColors
+		</div>
+
+		<h2>Blending Mode</h2>
+		<div>
+		THREE.NoBlending<br />
+		THREE.NormalBlending<br />
+		THREE.AdditiveBlending<br />
+		THREE.SubtractiveBlending<br />
+		THREE.MultiplyBlending<br />
+		THREE.CustomBlending
+		</div>
+	
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/Three.js src/Three.js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/ShadowingTypes.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/ShadowingTypes.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/ShadowingTypes.html	(revision 28897)
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>Shadowing Type Contants</h1>
+
+		<h2>Shadow Map</h2>
+		<div>
+		THREE.BasicShadowMap<br />
+		THREE.PCFShadowMap<br />
+		THREE.PCFSoftShadowMap
+		</div>
+	
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/Three.js src/Three.js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/Textures.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/Textures.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/Textures.html	(revision 28897)
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>Texture Constants</h1>
+
+		<h2>Operations</h2>
+		<div>
+		THREE.MultiplyOperation<br />
+		THREE.MixOperation<br />
+		THREE.AddOperation
+		</div>
+
+		<h2>Mapping Modes</h2>
+		<div>
+		THREE.UVMapping<br />
+		THREE.CubeReflectionMapping<br />
+		THREE.CubeRefractionMapping<br />
+		THREE.SphericalReflectionMapping<br />
+		THREE.SphericalRefractionMapping
+		</div>
+
+		<h2>Wrapping Modes</h2>
+		<div>
+		THREE.RepeatWrapping<br />
+		THREE.ClampToEdgeWrapping<br />
+		THREE.MirroredRepeatWrapping
+		</div>
+
+		<h2>Filters</h2>
+		<div>
+		THREE.NearestFilter<br />
+		THREE.NearestMipMapNearestFilter<br />
+		THREE.NearestMipMapLinearFilter<br />
+		THREE.LinearFilter<br />
+		THREE.LinearMipMapNearestFilter<br />
+		THREE.LinearMipMapLinearFilter
+		</div>
+
+		<h2>Data Types</h2>
+		<div>
+		THREE.UnsignedByteType<br />
+		THREE.ByteType<br />
+		THREE.ShortType<br />
+		THREE.UnsignedShortType<br />
+		THREE.IntType<br />
+		THREE.UnsignedIntType<br />
+		THREE.FloatType
+		</div>
+
+		<h2>Pixel Types</h2>
+		<div>
+		THREE.UnsignedShort4444Type<br />
+		THREE.UnsignedShort5551Type<br />
+		THREE.UnsignedShort565Type
+		</div>
+
+		<h2>Pixel Formats</h2>
+		<div>
+		THREE.AlphaFormat<br />
+		THREE.RGBFormat<br />
+		THREE.RGBAFormat<br />
+		THREE.LuminanceFormat<br />
+		THREE.LuminanceAlphaFormat
+		</div>
+
+		<h2>Compressed Texture Formats</h2>
+		<div>
+		THREE.RGB_S3TC_DXT1_Format<br />
+		THREE.RGBA_S3TC_DXT1_Format<br />
+		THREE.RGBA_S3TC_DXT3_Format<br />
+		THREE.RGBA_S3TC_DXT5_Format
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/Three.js src/Three.js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/constants/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/BufferGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/BufferGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/BufferGeometry.html	(revision 28897)
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">
+		This is a superefficent class for geometries because it saves all data in buffers. <br />
+		It reduces memory costs and cpu cycles. But it is not as easy to work with because of all the necessary buffer calculations.<br />
+		It is mainly interesting when working with static objects.
+		</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+			This creates a new [name]. It also sets several properties to an default value.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Integer id]</h3>
+		<div>
+		Unique number of this buffergeometry instance
+		</div>
+		
+		<h3>.[page:Hashmap attributes]</h3>
+		<div>
+		This hashmap has as id the name of the attribute to be set and as value the buffer to set it to.
+		</div>
+		
+		<h3>.[page:Boolean dynamic]</h3>
+		<div>
+		When set, it holds certain buffers in memory to have faster updates for this object. When unset, it deletes those buffers and saves memory.
+		</div>
+		
+		<h3>.[page:Array offsets]</h3>
+		<div>
+		This Array should contain every offset at which the buffers should be rendered. This is important for indexed buffers.
+		</div>
+
+		<h3>.[page:Object boundingBox]</h3>
+		<div>
+		Bounding box.
+		<code>{ min: new THREE.Vector3(), max: new THREE.Vector3() }</code>
+		</div>
+
+		<h3>.[page:Object boundingSphere]</h3>
+		<div>
+		Bounding sphere.
+		<code>{ radius: float }</code>
+		</div>
+		
+		<h3>.[page:Array morphTargets]</h3>
+		<div>
+		Array of morph targets. Each morph target is a Javascript object:
+		<code>{ name: "targetName", vertices: [ new THREE.Vertex(), ... ] }</code>
+		Morph vertices match number and order of primary vertices.
+		</div>
+
+		<h3>.[page:boolean hasTangents]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
+		
+		<h3>.applyMatrix( [page:Matrix4 matrix] )</h3>
+		<div>
+		Bakes matrix transform directly into vertex coordinates.
+		</div>
+
+		<h3>.computeVertexNormals()</h3>
+		<div>
+		Computes vertex normals by averaging face normals.<br />
+		</div>
+
+		<h3>.computeTangents()</h3>
+		<div>
+		Computes vertex tangents.<br />
+		Based on [link:http://www.terathon.com/code/tangent.html]<br />
+		Geometry must have vertex [page:UV UVs] (layer 0 will be used).
+		</div>
+
+		<h3>.computeBoundingBox()</h3>
+		<div>
+		Computes bounding box of the geometry, updating [page:Geometry Geometry.boundingBox] attribute.<br />
+		Bounding boxes aren't computed by default. They need to be explicitly computed, otherwise they are *null*.
+		</div>
+
+		<h3>.computeBoundingSphere()</h3>
+		<div>
+		Computes bounding sphere of the geometry, updating [page:Geometry Geometry.boundingSphere] attribute.<br />
+		Bounding spheres aren't computed by default. They need to be explicitly computed, otherwise they are *null*.
+		</div>
+		
+		<h3>.dispose()</h3>
+		<div>
+		Disposes the object from memory. <br />
+		You need to call this when you want the bufferGeometry removed while the application is running.
+		</div>
+
+		<h3>.normalizeNormals() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Clock.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Clock.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Clock.html	(revision 28897)
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Object for keeping track of time.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Boolean autoStart] )</h3>
+		<div>
+		autoStart â Automatically start the clock.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Boolean autoStart]</h3>
+		<div>
+		If set, starts the clock automatically when the first update is called.
+		</div>
+
+		<h3>.[page:Float startTime]</h3>
+		<div>
+		When the clock is running, It holds the start time of the clock. <br />
+		This counted from the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC.
+ 		</div>
+
+
+		<h3>.[page:Float oldTime]</h3>
+		<div>
+		When the clock is running, It holds the previous time from a update.<br />
+		This counted from the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC.
+ 		</div>
+
+		<h3>.[page:Float elapsedTime]</h3>
+		<div>
+		When the clock is running, It holds the time elapsed between the start of the clock to the previous update.<br />
+		This counted from the number of milliseconds elapsed since 1 January 1970 00:00:00 UTC.
+ 		</div>
+
+		<h3>.[page:Boolean running]</h3>
+		<div>
+		This property keeps track whether the clock is running or not.
+ 		</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.start()</h3>
+		<div>
+		Starts clock.
+		</div>
+
+		<h3>.stop()</h3>
+		<div>
+		Stops clock.
+		</div>
+
+		<h3>.getElapsedTime() [page:Float]</h3>
+		<div>
+		Get the seconds passed since the clock started.
+		</div>
+
+		<h3>.getDelta() [page:Float]</h3>
+		<div>
+		Get the seconds passed since the last call to this method.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/EventDispatcher.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/EventDispatcher.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/EventDispatcher.html	(revision 28897)
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">JavaScript events for custom objects.<br />
+		<a href="https://github.com/mrdoob/eventdispatcher.js">https://github.com/mrdoob/eventdispatcher.js</a></div>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		Creates EventDispatcher object.
+		</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.addEventListener( [page:String type], [page:Function listener] )</h3>
+		<div>
+		type - The type of event to listen to.<br />
+		listener - The function that gets called when the event is fired.
+		</div>
+		<div>
+		Adds a listener to an event type.
+		</div>
+
+		<h3>.hasEventListener( [page:String type], [page:Function listener] )</h3>
+		<div>
+		type - The type of event to listen to.<br />
+		listener - The function that gets called when the event is fired.
+		</div>
+		<div>
+		Checks if listener is added to an event type.
+		</div>
+
+		<h3>.removeEventListener( [page:String type], [page:Function listener] )</h3>
+		<div>
+		type - The type of the listener that gets removed.<br />
+		listener - The listener function that gets removed.
+		</div>
+		<div>
+		Removes a listener from an event type.
+		</div>
+
+		<h3>.dispatchEvent( [page:String type] )</h3>
+		<div>
+		type - The type of event that gets fired.
+		</div>
+		<div>
+		Fire an event type.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Face3.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Face3.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Face3.html	(revision 28897)
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">
+		Triangle face.
+		</div>
+
+
+		<h2>Example</h2>
+
+		<code>var normal = new THREE.Vector3( 0, 1, 0 );
+		var color = new THREE.Color( 0xffaa00 );
+		var face = new THREE.Face3( 0, 1, 2, normal, color, 0 );</code>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [page:Integer a], [page:Integer b], [page:Integer c], [page:Vector3 normal], [page:Color color], [page:Integer materialIndex] )</h3>
+		<div>
+		a â Vertex A index.<br />
+		b â Vertex B index.<br />
+		c â Vertex C index.<br />
+		normal â Face normal or array of vertex normals.<br />
+		color â Face color or array of vertex colors.<br />
+		materialIndex â Material index.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Integer a]</h3>
+		<div>
+		Vertex A index.
+		</div>
+
+		<h3>.[page:Integer b]</h3>
+		<div>
+		Vertex B index.
+		</div>
+
+		<h3>.[page:Integer c]</h3>
+		<div>
+		Vertex C index.
+		</div>
+
+		<h3>.[page:Vector3 normal]</h3>
+		<div>
+		Face normal.
+		</div>
+
+		<h3>.[page:Color color]</h3>
+		<div>
+		Face color.
+		</div>
+
+		<h3>.[page:Array vertexNormals]</h3>
+		<div>
+		Array of 3 vertex normals.
+		</div>
+
+		<h3>.[page:Array vertexColors]</h3>
+		<div>
+		Array of 3 vertex colors.
+		</div>
+
+		<h3>.[page:Array vertexTangents]</h3>
+		<div>
+		Array of 3 vertex tangents.
+		</div>
+
+
+		<h3>.[page:Integer materialIndex]</h3>
+		<div>
+		Material index (points to [page:MeshFaceMaterial MeshFaceMaterial.materials]).
+		</div>
+
+		<h3>.[page:Vector3 centroid]</h3>
+		<div>
+		Face centroid.
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>.clone()</h3>
+		<div>
+		Creates a new clone of the Face3 object.
+		</div>
+		
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Geometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Geometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Geometry.html	(revision 28897)
@@ -0,0 +1,258 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">
+		Base class for geometries.<br />
+		A geometry holds all data necessary to describe a 3D model.
+		</div>
+
+
+		<h2>Example</h2>
+
+		<code>var geometry = new THREE.Geometry();
+
+		geometry.vertices.push( new THREE.Vector3( -10,  10, 0 ) );
+		geometry.vertices.push( new THREE.Vector3( -10, -10, 0 ) );
+		geometry.vertices.push( new THREE.Vector3(  10, -10, 0 ) );
+
+		geometry.faces.push( new THREE.Face3( 0, 1, 2 ) );
+
+		geometry.computeBoundingSphere();
+		</code>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		The constructor takes no arguments.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Integer id]</h3>
+		<div>
+		Unique number of this geometry instance
+		</div>
+		
+		<h3>.[page:String name]</h3>
+		<div>
+		Name for this geometry. Default is an empty string.
+		</div>
+
+		<h3>.[page:Array vertices]</h3>
+		<div>
+		Array of [page:Vector3 vertices].<br />
+		The array of vertices holds every position of points in the model.<br />
+		To signal an update in this array, [page:Geometry Geometry.verticesNeedUpdate] needs to be set to true.
+		</div>
+
+		<h3>.[page:Array colors]</h3>
+		<div>
+		Array of vertex [page:Color colors], matching number and order of vertices.<br />
+		Used in [page:ParticleSystem] and [page:Line].<br />
+		[page:Mesh Meshes] use per-face-use-of-vertex colors embedded directly in faces.<br />
+		To signal an update in this array, [page:Geometry Geometry.colorsNeedUpdate] needs to be set to true.
+		</div>
+
+		<h3>.[page:Array faces]</h3>
+		<div>
+		Array of [page:Face3 triangles].<br />
+		The array of faces describe how each vertex in the model is connected with each other.<br />
+		To signal an update in this array, [page:Geometry Geometry.elementsNeedUpdate] needs to be set to true.
+		</div>
+
+		<h3>.[page:Array faceVertexUvs]</h3>
+		<div>
+		Array of face [page:UV] layers.<br />
+		Each UV layer is an array of [page:UV]s matching the order and number of vertices in faces.<br />
+		To signal an update in this array, [page:Geometry Geometry.uvsNeedUpdate] needs to be set to true.
+		</div>
+
+		<h3>.[page:Array morphTargets]</h3>
+		<div>
+		Array of morph targets. Each morph target is a Javascript object:
+		<code>{ name: "targetName", vertices: [ new THREE.Vector3(), ... ] }</code>
+		Morph vertices match number and order of primary vertices.
+		</div>
+
+		<h3>.[page:Array morphColors]</h3>
+		<div>
+		Array of morph colors. Morph colors have similar structure as morph targets, each color set is a Javascript object:
+		<code>morphColor = { name: "colorName", colors: [ new THREE.Color(), ... ] }</code>
+		Morph colors can match either the number and order of faces (face colors) or the number of vertices (vertex colors).
+		</div>
+
+		<h3>.[page:Array morphNormals]</h3>
+		<div>
+		Array of morph normals. Morph normals have similar structure as morph targets, each normal set is a Javascript object:
+		<code>morphNormal = { name: "NormalName", normals: [ new THREE.Vector3(), ... ] }</code>
+		</div>
+
+		<h3>.[page:Array skinWeights]</h3>
+		<div>
+		Array of skinning weights, matching number and order of vertices.
+		</div>
+
+		<h3>.[page:Array skinIndices]</h3>
+		<div>
+		Array of skinning indices, matching number and order of vertices.
+		</div>
+
+		<h3>.[page:Object boundingBox]</h3>
+		<div>
+		Bounding box.
+		<code>{ min: new THREE.Vector3(), max: new THREE.Vector3() }</code>
+		</div>
+
+		<h3>.[page:Object boundingSphere]</h3>
+		<div>
+		Bounding sphere.
+		<code>{ radius: float }</code>
+		</div>
+
+		<h3>.[page:Boolean hasTangents]</h3>
+		<div>
+		True if geometry has tangents. Set in [page:Geometry Geometry.computeTangents].
+		</div>
+
+		<h3>.[page:Boolean dynamic]</h3>
+		<div>
+		Set to *true* if attribute buffers will need to change in runtime (using "dirty" flags).<br/>
+		Unless set to true internal typed arrays corresponding to buffers will be deleted once sent to GPU.<br/>
+		Defaults to true.
+		</div>
+		
+		<h3>.[page:Boolean verticesNeedUpdate]</h3>
+		<div>
+		Set to *true* if the vertices array has been updated.
+		</div>
+		
+		<h3>.[page:Boolean elementsNeedUpdate]</h3>
+		<div>
+		Set to *true* if the faces array has been updated.
+		</div>
+		
+		<h3>.[page:Boolean uvsNeedUpdate]</h3>
+		<div>
+		Set to *true* if the uvs array has been updated.
+		</div>
+		
+		<h3>.[page:Boolean normalsNeedUpdate]</h3>
+		<div>
+		Set to *true* if the normals array has been updated.
+		</div>
+		
+		<h3>.[page:Boolean tangentsNeedUpdate]</h3>
+		<div>
+		Set to *true* if the tangents in the faces has been updated.
+		</div>
+		
+		<h3>.[page:Boolean colorsNeedUpdate]</h3>
+		<div>
+		Set to *true* if the colors array has been updated.
+		</div>
+		
+		<h3>.[page:Boolean lineDistancesNeedUpdate]</h3>
+		<div>
+		Set to *true* if the linedistances array has been updated.
+		</div>
+		
+		<h3>.[page:Boolean buffersNeedUpdate]</h3>
+		<div>
+		Set to *true* if an array has changed in length.
+		</div>
+
+		<h3>.[page:array lineDistances]</h3>
+		<div>
+		An array containing distances between vertices for Line geometries.
+		This is required for LinePieces/LineDashedMaterial to render correctly.
+		Line distances can also be generated with computeLineDistances.
+		</div> 
+
+		<h2>Methods</h2>
+		
+		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
+
+		<h3>.applyMatrix( [page:Matrix4 matrix] )</h3>
+		<div>
+		Bakes matrix transform directly into vertex coordinates.
+		</div>
+
+		<h3>.computeCentroids()</h3>
+		<div>
+		Computes centroids for all faces.
+		</div>
+
+		<h3>.computeFaceNormals()</h3>
+		<div>
+		Computes face normals.
+		</div>
+
+		<h3>.computeVertexNormals()</h3>
+		<div>
+		Computes vertex normals by averaging face normals.<br />
+		Face normals must be existing / computed beforehand.
+		</div>
+
+		<h3>.computeMorphNormals()</h3>
+		<div>
+		Computes morph normals.
+		</div>
+
+		<h3>.computeTangents()</h3>
+		<div>
+		Computes vertex tangents.<br />
+		Based on [link:http://www.terathon.com/code/tangent.html]<br />
+		Geometry must have vertex [page:UV UVs] (layer 0 will be used).
+		</div>
+
+		<h3>.computeBoundingBox()</h3>
+		<div>
+		Computes bounding box of the geometry, updating [page:Geometry Geometry.boundingBox] attribute.
+		</div>
+
+		<h3>.computeBoundingSphere()</h3>
+		<div>
+		Computes bounding sphere of the geometry, updating [page:Geometry Geometry.boundingSphere] attribute.
+		</div>
+		
+		<div>Neither bounding boxes or bounding spheres are computed by default. They need to be explicitly computed, otherwise they are *null*.</div>
+
+		<h3>.mergeVertices()</h3>
+		<div>
+		Checks for duplicate vertices using hashmap.<br />
+		Duplicated vertices are removed and faces' vertices are updated.
+		</div>
+		
+		<h3>.clone()</h3>
+		<div>
+		Creates a new clone of the Geometry.
+		</div>
+		
+		<h3>.dispose()</h3>
+		<div>
+		Removes The object from memory. <br />
+		Don't forget to call this method when you remove a geometry because it can cause memory leaks. 
+		</div>
+
+		<h3>.computeLineDistances()</h3>
+		<div>
+		Compute distances between vertices for Line geometries.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Object3D.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Object3D.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Object3D.html	(revision 28897)
@@ -0,0 +1,285 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Base class for scene graph objects.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		The constructor takes no arguments.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Integer id]</h3>
+		<div>
+		Unique number of this object instance.
+		</div>
+
+		<h3>.[page:String name]</h3>
+		<div>
+		Optional name of the object (doesn't need to be unique).
+		</div>
+
+		<h3>.[page:Object3D parent]</h3>
+		<div>
+		Object's parent in the scene graph.
+		</div>
+
+		<h3>.[page:Object3D children]</h3>
+		<div>
+		Array with object's children.
+		</div>
+
+		<h3>.[page:Vector3 position]</h3>
+		<div>
+		Object's local position.
+		</div>
+
+		<h3>.[page:Euler rotation]</h3>
+		<div>
+		Object's local rotation (<a href="https://en.wikipedia.org/wiki/Euler_angles" target="_blank">Euler angles</a>), in radians.
+		</div>
+
+		<h3>.[page:String eulerOrder]</h3>
+		<div>
+		Order of axis for Euler angles.
+		</div>
+
+		<h3>.[page:Vector3 scale]</h3>
+		<div>
+		Object's local scale.
+		</div>
+
+		<h3>.[page:Vector3 up]</h3>
+		<div>
+		Up direction.
+		</div>
+
+		<h3>.[page:Matrix4 matrix]</h3>
+		<div>
+		Local transform.
+		</div>
+
+		<h3>.[page:Quaternion quaternion]</h3>
+		<div>
+		Object's local rotation as [page:Quaternion Quaternion]. Only used when useQuaternion is set to true.
+		</div>
+
+		<h3>.[page:Boolean useQuaternion]</h3>
+		<div>
+		Use quaternion instead of Euler angles for specifying local rotation.
+		</div>
+
+		<h3>.[page:Float renderDepth]</h3>
+		<div>
+		Override depth-sorting order if non *null*.
+		</div>
+
+		<h3>.[page:Boolean visible]</h3>
+		<div>
+		Object gets rendered if *true*.
+		</div>
+
+		<h3>.[page:Boolean castShadow]</h3>
+		<div>
+		Gets rendered into shadow map.
+		</div>
+
+		<h3>.[page:Boolean receiveShadow]</h3>
+		<div>
+		Material gets baked in shadow receiving.
+		</div>
+
+		<h3>.[page:Boolean frustumCulled]</h3>
+		<div>
+		When this is set, it checks every frame if the object is in the frustum of the camera. Otherwise the object gets drawn every frame even if it isn't visible.
+		</div>
+
+		<h3>.[page:Boolean matrixAutoUpdate]</h3>
+		<div>
+		When this is set, it calculates the matrix of position, (rotation or quaternion) and scale every frame and also recalculates the matrixWorld property.
+		</div>
+
+		<h3>.[page:Boolean matrixWorldNeedsUpdate]</h3>
+		<div>
+		When this is set, it calculates the matrixWorld in that frame and resets this property to false.
+		</div>
+
+		<h3>.[page:Boolean rotationAutoUpdate]</h3>
+		<div>
+		When this is set, then the rotationMatrix gets calculated every frame.
+		</div>
+
+		<h3>.[page:object userData]</h3>
+		<div>
+		An object that can be used to store custom data about the Object3d. It should not hold references to functions as these will not be cloned.
+		</div> 
+
+		<h3>.[page:Matrix4 matrixWorld]</h3>
+		<div>
+		The global transform of the object. If the Object3d has no parent, then it's identical to the local transform.
+		</div> 
+
+		
+		<h2>Methods</h2>
+		
+		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
+		
+		<h3>.applyMatrix( [page:Matrix4 matrix])</h3>
+		<div>
+		matrix - matrix
+		</div>
+		<div>
+		This updates the position, rotation and scale with the matrix.
+		</div>
+		
+		<h3>.translateX( [page:Float distance] )</h3>
+		<div>
+		distance - Distance.<br />
+		</div>
+		<div>
+		Translates object along x axis by distance.
+		</div>
+
+		<h3>.translateY( [page:Float distance] )</h3>
+		<div>
+		distance - Distance.<br />
+		</div>
+		<div>
+		Translates object along y axis by distance.
+		</div>
+
+		<h3>.translateZ( [page:Float distance] )</h3>
+		<div>
+		distance - Distance.<br />
+		</div>
+		<div>
+		Translates object along z axis by distance.
+		</div>
+		
+		<h3>.localToWorld( [page:Vector3 vector] )</h3>
+		<div>
+		vector - A local vector.<br />
+		</div>
+		<div>
+		Updates the vector from local space to world space.
+		</div>
+
+		<h3>.worldToLocal( [page:Vector3 vector] )</h3>
+		<div>
+		vector - A world vector.<br />
+		</div>
+		<div>
+		Updates the vector from world space to local space.
+		</div>
+
+		<h3>.lookAt( [page:Vector3 vector] )</h3>
+		<div>
+		vector - A world vector to look at.<br />
+		</div>
+		<div>
+		Rotates object to face point in space.
+		</div>
+
+		<h3>.add( [page:Object3D object] )</h3>
+		<div>
+		object - An object.<br />
+		</div>
+		<div>
+		Adds *object* as child of this object.
+		</div>
+
+		<h3>.remove( [page:Object3D object] )</h3>
+		<div>
+		object - An object.<br />
+		</div>
+		<div>
+		Removes *object* as child of this object.
+		</div>
+
+		<h3>.traverse( [page:Function callback] )</h3>
+		<div>
+		callback - An Function with as first argument an object3D object.<br />
+		</div>
+		<div>
+		Executes the callback on this object and all descendants. 
+		</div>
+
+		<h3>.getDescendants( [page:Array array] )</h3>
+		<div>
+		array - optional argument that returns the the array with descendants.<br />
+		</div>
+		<div>
+		Searches whole subgraph recursively to add all objects in the array.
+		</div>
+
+		<h3>.updateMatrix()</h3>
+		<div>
+		Updates local transform.
+		</div>
+
+		<h3>.updateMatrixWorld( [page:Boolean force] )</h3>
+		<div>
+		Updates global transform of the object and its children.
+		</div>
+
+		<h3>.clone()</h3>
+		<div>
+		Creates a new clone of this object and all descendants.
+		</div>
+
+
+		<h3>.getObjectByName([page:String name], [page:Boolean recursive]) [page:Object3d Object3d]</h3>
+		<div>
+		name -- String to match to the children's Object3d.name property. <br />
+		recursive -- Boolean whether to search through the children's children. Default is false.
+		</div>
+		<div>
+		Searches through the object's children and returns the first with a matching name, optionally recursive.
+		</div>
+
+		<h3>.getObjectById([page:Integer id], [page:Boolean recursive]) [page:Object3D]</h3>
+		<div>
+		id -- Unique number of the object instance<br />
+		recursive -- Boolean whether to search through the children's children. Default is false.
+		</div>
+		<div>
+		Searches through the object's children and returns the first with a matching id, optionally recursive.
+		</div>
+		
+		<h3>.translateOnAxis([page:Vector3 axis], [page:Float distance]) [page:Object3D]</h3>
+		<div>
+		axis -- A normalized vector in object space.<br />
+		distance -- The distance to translate.
+		</div>
+		<div>
+		Translate an object by distance along an axis in object space. The axis is assumed to be normalized.
+		</div>
+		
+		<h3>.rotateOnAxis([page:Vector3 axis], [page:Float angle]) [page:Object3D]</h3>
+		<div>
+		axis -- A normalized vector in object space. <br />
+		angle -- The angle in radians.
+		</div>
+		<div>
+		Rotate an object along an axis in object space. The axis is assumed to be normalized.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Projector.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Projector.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Projector.html	(revision 28897)
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Projects points between spaces.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+		
+		
+		<h2>Properties</h2>
+
+		<h2>Methods</h2>
+
+		<h3>.projectVector( [page:Vector3 vector], [page:Camera camera] ) [page:Vector3]</h3>
+		<div>
+		[page:Vector3 vector] â vector to project.<br />
+		[page:Camera camera] â camera to use in the projection.<br />
+		</div>
+		<div>
+		Projects a vector with the camera. Caution, this method changes 'vector'.
+		</div>
+
+		<h3>.unprojectVector( [page:Vector3 vector], [page:Camera camera] ) [page:Vector3]</h3>
+		<div>
+		[page:Vector3 vector] â vector to unproject.<br />
+		[page:Camera camera] â camera to use in the projection.<br />
+		</div>
+		<div>
+		Unprojects a vector with the camera. Caution, this method changes 'vector'. 
+		</div>
+		
+		<h3>.pickingRay( [page:Vector3 vector], [page:Camera camera] ) [page:Raycaster]</h3>
+		<div>
+		Translates a 2D point from NDC (<em>Normalized Device Coordinates</em>) to a [page:Raycaster] that can be used for picking. NDC range from [-1..1] in x (left to right) and [1.0 .. -1.0] in y (top to bottom).
+		</div>
+
+		<h3>.projectScene( [page:Scene scene], [page:Camera camera], [page:Boolean sort] ) [page:Object]</h3>
+		<div>
+		[page:Scene scene] â scene to project.<br />
+		[page:Camera camera] â camera to use in the projection.<br />
+		[page:Boolean sort] â select whether to sort elements using the <a href="http://en.wikipedia.org/wiki/Painter%27s_algorithm">Painter's algorithm</a>.
+		</div>
+		
+		<div>
+		Transforms a 3D [page:Scene scene] object into 2D render data that can be rendered in a screen with your renderer of choice, projecting and clipping things out according to the used camera.
+		</div>
+		<div>
+		If the <em>scene</em> were a real scene, this method would be the equivalent of taking a picture with the <em>camera</em> (and developing the film would be the next step, using a Renderer).
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Raycaster.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Raycaster.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/Raycaster.html	(revision 28897)
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">
+		This class makes raycasting easier. Raycasting is used for picking and more.
+			
+		</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Vector3 origin], [page:Vector3 direction], [page:Float near], [page:Float far] ) {</h3>
+		<div>
+		[page:Vector3 origin] â The origin vector where the ray casts from.<br />
+		[page:Vector3 direction] â The direction vector that  gives direction to the ray.<br />
+		[page:Float near] â All results returned are further away then near. Near can't be negative. Default value is 0.<br />
+		[page:Float far] â All results returned are closer then far. Far can't be lower then near . Default value is Infinity.
+		</div>
+		<div>
+		This creates a new raycaster object.<br />
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Ray ray]</h3>
+		<div>
+		The Ray used for the raycasting.
+		</div>
+
+		<h3>.[page:float near]</h3>
+		<div>
+		The near factor of the raycaster. This value indicates which objects can be discarded based on the distance.<br />
+		This value shouldn't be negative and should be smaller than the far property. 
+		</div>
+
+		<h3>.[page:float far]</h3>
+		<div>
+		The far factor of the raycaster. This value indicates which objects can be discarded based on the distance.<br />
+		This value shouldn't be negative and should be larger than the near property. 
+		</div>
+
+		<h3>.[page:float precision]</h3>
+		<div>
+		The precision factor of the raycaster. 
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>.set( [page:Vector3 origin], [page:Vector3 direction] )</h3>
+		<div>
+		[page:Vector3 origin] â The origin vector where the ray casts from.<br />
+		[page:Vector3 direction] â The direction vector that  gives direction to the ray.
+		</div>
+		<div>
+		Updates the ray with a new origin and direction.
+		</div>
+
+		<h3>.intersectObject( [page:Object3D object], [page:Boolean recursive] )</h3>
+		<div>
+		[page:Object3D object] â The object to check for intersection with the ray.<br />
+		[page:Boolean recursive] â If set, it also checks all descendants. Otherwise it only checks intersecton with the object.
+		</div>
+		<div>
+		checks all intersection between the ray and the object with or without the descendants.
+		</div>
+
+		<h3>.intersectObjects( [page:Array objects], [page:Boolean recursive] )</h3>
+		<div>
+		[page:Array objects] â The objects to check for intersection with the ray.<br />
+		[page:Boolean recursive] â If set, it also checks all descendants of the objects. Otherwise it only checks intersecton with the objects.
+		</div>
+		<div>
+		checks all intersection between the ray and the objects with or without the descendants.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/core/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/FontUtils.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/FontUtils.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/FontUtils.html	(revision 28897)
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:number divisions]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:string style]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:string weight]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:string face]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object faces]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number size]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.drawText([page:todo text]) [page:todo]</h3>
+		<div>
+		text -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.Triangulate([page:todo contour], [page:todo indices]) [page:todo]</h3>
+		<div>
+		contour -- todo <br />
+		indices -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.extractGlyphPoints([page:todo c], [page:todo face], [page:todo scale], [page:todo offset], [page:todo path]) [page:todo]</h3>
+		<div>
+		c -- todo <br />
+		face -- todo <br />
+		scale -- todo <br />
+		offset -- todo <br />
+		path -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.generateShapes([page:todo text], [page:todo parameters]) [page:todo]</h3>
+		<div>
+		text -- todo <br />
+		parameters -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.loadFace([page:todo data]) [page:todo]</h3>
+		<div>
+		data -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getFace() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/GeometryUtils.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/GeometryUtils.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/GeometryUtils.html	(revision 28897)
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Contains handy functions geometry manipulations.</div>
+
+
+		<h2>Methods</h2>
+
+		<h3> .merge( [page:Geometry geometry1] , [page:Geometry geometry2], [page:Integer materialIndexOffset] )</h3>
+		<div>
+		geometry1 â Parent geomentry element <br />
+		geometry2 â Geometry that need to be added in parent <br />
+		materialIndexOffset â Offset applied to the materialIndex of all the new faces in the merged geometry. Default : 0 <br />
+		</div>
+
+		<h3> .randomPointInTriangle( [page:Vector VectorA] , [page:Vector VectorB]  , [page:Vector VectorC])</h3>
+		<div>
+		VectorA â Vector  <br />
+		VectorB â Vector  <br />
+		VectorC â Vector  <br />
+
+		 returns [page:Int Point]
+
+		</div>
+
+		<h3> .randomPointInFace( [page:face Face] , [page:geometry Geometry]  , [page:Boolean useCachedAreas])</h3>
+		<div>
+		Face â Face id<br />
+		Geometry â Geometry that contains the Face  <br />
+		useCachedAreas â Flag to use cached areas. Default : False  <br />
+
+		 returns [page:Int Point]
+
+		</div>
+
+		<h3> .randomPointsInGeometry( [page:geometry Geometry]  , [page:Integer Points])</h3>
+		<div>
+		Geometry â Geometry  <br />
+
+		 returns [page:Int Point]
+
+		</div>
+
+
+		<h3> .triangleArea ( [page:Vector VectorA] , [page:Vector VectorB]  , [page:Vector VectorC]) </h3>
+
+		<div>
+		VectorA â Vector  <br />
+		VectorB â Vector  <br />
+		VectorC â Vector  <br />
+
+		returns [page:Int Area]
+
+		</div>
+
+		<h3> .center ( [page:geometry Geometry] ) </h3>
+
+		<div>
+
+		Geometry â Geometry to Center position
+
+		</div>
+
+		<h3> .triangulateQuads ( [page:geometry Geometry] ) </h3>
+
+		<div>
+
+		Geometry â Geometry to triangulate Quads <br />
+
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/ImageUtils.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/ImageUtils.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/ImageUtils.html	(revision 28897)
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+		
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:string crossOrigin]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.generateDataTexture([page:todo width], [page:todo height], [page:todo color]) [page:todo]</h3>
+		<div>
+		width -- todo <br />
+		height -- todo <br />
+		color -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.parseDDS([page:todo buffer], [page:todo loadMipmaps]) [page:todo]</h3>
+		<div>
+		buffer -- todo <br />
+		loadMipmaps -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.loadCompressedTexture([page:todo url], [page:todo mapping], [page:todo onLoad], [page:todo onError]) [page:todo]</h3>
+		<div>
+		url -- todo <br />
+		mapping -- todo <br />
+		onLoad -- todo <br />
+		onError -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.loadTexture([page:String url], [page:UVMapping mapping], [page:Function onLoad], [page:Function onError]) [page:todo]</h3>
+		<div>
+		url -- the url of the texture<br />
+		mapping -- Can be an instance of [page:UVMapping Three.UVMapping], [page:CubeReflectionMapping THREE.CubeReflectionMapping], [page:SphericalReflectionMapping THREE.SphericalReflectionMapping] or [page:SphericalRefractionMapping THREE.SphericalRefractionMapping]. Describes how the image is applied to the object.<br />Use undefined instead of null as a default value. See mapping property of [page:Texture texture] for more details.
+		onLoad -- callback function<br />
+		onError -- callback function
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getNormalMap([page:todo image], [page:todo depth]) [page:todo]</h3>
+		<div>
+		image -- todo <br />
+		depth -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.loadCompressedTextureCube([page:todo array], [page:todo mapping], [page:todo onLoad], [page:todo onError]) [page:todo]</h3>
+		<div>
+		array -- todo <br />
+		mapping -- todo <br />
+		onLoad -- todo <br />
+		onError -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.loadTextureCube([page:todo array], [page:todo mapping], [page:todo onLoad], [page:todo onError]) [page:todo]</h3>
+		<div>
+		array -- todo <br />
+		mapping -- todo <br />
+		onLoad -- todo <br />
+		onError -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/SceneUtils.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/SceneUtils.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/SceneUtils.html	(revision 28897)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Methods</h2>
+
+
+		<h3>.createMultiMaterialObject([page:todo geometry], [page:todo materials]) [page:todo]</h3>
+		<div>
+		geometry -- todo <br />
+		materials -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.attach([page:todo child], [page:todo scene], [page:todo parent]) [page:todo]</h3>
+		<div>
+		child -- todo <br />
+		scene -- todo <br />
+		parent -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.detach([page:todo child], [page:todo parent], [page:todo scene]) [page:todo]</h3>
+		<div>
+		child -- todo <br />
+		parent -- todo <br />
+		scene -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/Animation.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/Animation.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/Animation.html	(revision 28897)
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo root], [page:todo name], [page:todo interpolationType])</h3>
+		<div>
+		root -- todo <br />
+		name -- todo <br />
+		interpolationType -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:todo root]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:todo data]</h3>
+		<div>
+		todo
+		</div> 
+		
+		<h3>.[page:todo hierarchy]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.[page:number currentTime]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:number timeScale]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.[page:boolean isPlaying]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:boolean isPaused]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:boolean loop]</h3>
+		<div>
+		todo
+		</div> 
+		
+		<h3>.[page:number interpolationType]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:array points]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:Vector3 target]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Methods</h2>
+		
+		<h3>.play() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.pause() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.stop() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.update([page:todo deltaTimeMS]) [page:todo]</h3>
+		<div>
+		deltaTimeMS -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.interpolateCatmullRom([page:todo points], [page:todo scale]) [page:todo]</h3>
+		<div>
+		points -- todo <br />
+		scale -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getNextKeyWith([page:todo type], [page:todo h], [page:todo key]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		h -- todo <br />
+		key -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getPrevKeyWith([page:todo type], [page:todo h], [page:todo key]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		h -- todo <br />
+		key -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/AnimationHandler.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/AnimationHandler.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/AnimationHandler.html	(revision 28897)
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:number CATMULLROM]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number CATMULLROM_FORWARD]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number LINEAR]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.removeFromUpdate([page:todo animation]) [page:todo]</h3>
+		<div>
+		animation -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.get([page:todo name]) [page:todo]</h3>
+		<div>
+		name -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.update([page:todo deltaTimeMS]) [page:todo]</h3>
+		<div>
+		deltaTimeMS -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.parse([page:todo root]) [page:todo]</h3>
+		<div>
+		root -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.add([page:todo data]) [page:todo]</h3>
+		<div>
+		data -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addToUpdate([page:todo animation]) [page:todo]</h3>
+		<div>
+		animation -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/AnimationMorphTarget.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/AnimationMorphTarget.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/AnimationMorphTarget.html	(revision 28897)
@@ -0,0 +1,105 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo root], [page:todo data])</h3>
+		<div>
+		root -- todo <br />
+		data -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Properties</h2>
+		
+		<h3>.[page:todo root]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:todo data]</h3>
+		<div>
+		todo
+		</div> 
+		
+		<h3>.[page:todo hierarchy]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.[page:number currentTime]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:number timeScale]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.[page:boolean isPlaying]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:boolean isPaused]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:boolean loop]</h3>
+		<div>
+		todo
+		</div> 
+		
+		<h3>.[page:number influence]</h3>
+		<div>
+		todo
+		</div> 
+
+
+		<h2>Methods</h2>
+
+		<h3>.play() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.pause() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.stop() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.update([page:todo deltaTimeMS]) [page:todo]</h3>
+		<div>
+		deltaTimeMS -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/KeyFrameAnimation.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/KeyFrameAnimation.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/KeyFrameAnimation.html	(revision 28897)
@@ -0,0 +1,133 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo root], [page:todo data], [page:todo JITCompile])</h3>
+		<div>
+		root -- todo <br />
+		data -- todo <br />
+		JITCompile -- todo
+		</div>
+		<div>
+		todo
+		</div>
+		
+
+		<h2>Properties</h2>
+
+		<h3>.[page:todo root]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:todo data]</h3>
+		<div>
+		todo
+		</div> 
+		
+		<h3>.[page:todo hierarchy]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.[page:number currentTime]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:number timeScale]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.[page:boolean isPlaying]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:boolean isPaused]</h3>
+		<div>
+		todo
+		</div>
+		
+		<h3>.[page:boolean loop]</h3>
+		<div>
+		todo
+		</div> 
+		
+		<h3>.[page:boolean JITCompile]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Methods</h2>
+		
+		<h3>.play() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.pause() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.stop() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.update([page:todo deltaTimeMS]) [page:todo]</h3>
+		<div>
+		deltaTimeMS -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.interpolateCatmullRom([page:todo points], [page:todo scale]) [page:todo]</h3>
+		<div>
+		points -- todo <br />
+		scale -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getNextKeyWith([page:todo sid], [page:todo h], [page:todo key]) [page:todo]</h3>
+		<div>
+		sid -- todo <br />
+		h -- todo <br />
+		key -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getPrevKeyWith([page:todo sid], [page:todo h], [page:todo key]) [page:todo]</h3>
+		<div>
+		sid -- todo <br />
+		h -- todo <br />
+		key -- todo
+		</div>
+		<div>
+		todo
+		</div>
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/animation/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/CombinedCamera.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/CombinedCamera.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/CombinedCamera.html	(revision 28897)
@@ -0,0 +1,184 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo width], [page:todo height], [page:todo fov], [page:todo near], [page:todo far], [page:todo orthoNear], [page:todo orthoFar])</h3>
+		<div>
+		width -- todo <br />
+		height -- todo <br />
+		fov -- todo <br />
+		near -- todo <br />
+		far -- todo <br />
+		orthoNear -- todo <br />
+		orthoFar -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:todo fov]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number right]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number bottom]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:PerspectiveCamera cameraP]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number top]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number zoom]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number far]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number near]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean inPerspectiveMode]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:OrthographicCamera cameraO]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean inOrthographicMode]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number left]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.toBottomView() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.setFov([page:todo fov]) [page:todo]</h3>
+		<div>
+		fov -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.toBackView() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.setZoom([page:todo zoom]) [page:todo]</h3>
+		<div>
+		zoom -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setLens([page:todo focalLength], [page:todo frameHeight]) [page:todo]</h3>
+		<div>
+		focalLength -- todo <br />
+		frameHeight -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.toFrontView() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.toLeftView() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.updateProjectionMatrix() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.toTopView() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.toOrthographic() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.setSize([page:todo width], [page:todo height]) [page:todo]</h3>
+		<div>
+		width -- todo <br />
+		height -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.toPerspective() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.toRightView() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/CubeCamera.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/CubeCamera.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/CubeCamera.html	(revision 28897)
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo near], [page:todo far], [page:todo cubeResolution])</h3>
+		<div>
+		near -- todo <br />
+		far -- todo <br />
+		cubeResolution -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:WebGLRenderTargetCube renderTarget]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.updateCubeMap([page:todo renderer], [page:todo scene]) [page:todo]</h3>
+		<div>
+		renderer -- todo <br />
+		scene -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/cameras/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Curve.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Curve.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Curve.html	(revision 28897)
@@ -0,0 +1,61 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">An extensible curve object which contains methods for interpolation.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		This constructor creates a new [name].
+		</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.getPoint ( t )</h3>
+		<div>Returns a vector for point t of the curve where t is between 0 and 1</div>
+
+		<h3>.getPointAt ( u )</h3>
+		<div>Returns a vector for point at relative position in curve according to arc length</div>
+
+		<h3>.getPoints ( divisions )</h3>
+		<div>Get sequence of points using getPoint( t ) </div>
+
+		<h3>.getSpacedPoints ( divisions )</h3>
+		<div>Get sequence of equi-spaced points using getPointAt( u )</div>
+
+		<h3>.getLength ()</h3>
+		<div>Get total curve arc length</div>
+
+		<h3>.getLengths ( divisions )</h3>
+		<div>Get list of cumulative segment lengths</div>
+
+		<h3>.updateArcLengths ()</h3>
+		<div>Update the cumlative segment distance cache</div>
+
+		<h3>.getUtoTmapping ( u, distance )</h3>
+		<div>Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant</div>
+
+		<h3>.getTangent ( t )</h3>
+		<div>Returns a unit vector tangent at t. If the subclassed curve do 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</div>
+
+		<h3>.getTangentAt ( u )</h3>
+		<div>Returns tangent at equidistant point u on the curve</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/CurvePath.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/CurvePath.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/CurvePath.html	(revision 28897)
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+		
+
+		<h3>.[page:array curves]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:array bends]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean autoClose]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+		<h3>.getWrapPoints([page:todo oldPts], [page:todo path]) [page:todo]</h3>
+		<div>
+		oldPts -- todo <br />
+		path -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.createPointsGeometry([page:todo divisions]) [page:todo]</h3>
+		<div>
+		divisions -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addWrapPath([page:todo bendpath]) [page:todo]</h3>
+		<div>
+		bendpath -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.createGeometry([page:todo points]) [page:todo]</h3>
+		<div>
+		points -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.add([page:todo curve]) [page:todo]</h3>
+		<div>
+		curve -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getTransformedSpacedPoints([page:todo segments], [page:todo bends]) [page:todo]</h3>
+		<div>
+		segments -- todo <br />
+		bends -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.createSpacedPointsGeometry([page:todo divisions]) [page:todo]</h3>
+		<div>
+		divisions -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.closePath() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.getBoundingBox() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.getCurveLengths() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.getTransformedPoints([page:todo segments], [page:todo bends]) [page:todo]</h3>
+		<div>
+		segments -- todo <br />
+		bends -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.checkConnection() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Gyroscope.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Gyroscope.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Gyroscope.html	(revision 28897)
@@ -0,0 +1,66 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Vector3 scaleWorld]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 translationWorld]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Quaternion rotationWorld]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 translationObject]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 scaleObject]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Quaternion rotationObject]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Path.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Path.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Path.html	(revision 28897)
@@ -0,0 +1,75 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A 2d path representation, comprising of points, lines, and cubes,  similar to the html5 2d canvas api. It extends CurvePath.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo points])</h3>
+		<div>
+		points -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:array actions]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h3>.fromPoints ( vectors ) </h3>
+		<div>todo</div>
+
+		<h3>.moveTo ( x, y ) </h3>
+		<div>todo</div>
+
+		<h3>.lineTo ( x, y ) </h3>
+		<div>todo</div>
+
+		<h3>.quadraticCurveTo ( aCPx, aCPy, aX, aY ) </h3>
+		<div>todo</div>
+
+		<h3>.bezierCurveTo ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) </h3>
+		<div>todo</div>
+
+		<h3>.splineThru ( pts /*Array of Vector*/ ) </h3>
+		<div>todo</div>
+
+		<h3>.arc ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) </h3>
+		<div>todo</div>
+
+		<h3>.absarc ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) </h3>
+		<div>todo</div>
+
+		<h3>.ellipse ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) </h3>
+		<div>todo</div>
+
+		<h3>.absellipse ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) </h3>
+		<div>todo</div>
+
+		<h3>.toShapes () </h3>
+		<div>todo</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Shape.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Shape.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/Shape.html	(revision 28897)
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Defines a 2d shape plane using paths.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:array holes]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+		<h3>.makeGeometry([page:todo options]) [page:todo]</h3>
+		<div>
+		options -- The options passed as the second argument to [page:ShapeGeometry ShapeGeometry]
+		</div>
+		<div>
+		Convenience method to return ShapeGeometry
+		</div>
+
+		<h3>.extractAllPoints([page:todo divisions]) [page:todo]</h3>
+		<div>
+		divisions -- todo
+		</div>
+		<div>
+		Get points of shape and holes (keypoints based on segments parameter)
+		</div>
+
+		<h3>.extrude([page:todo options]) [page:todo]</h3>
+		<div>
+		options -- The options passed as the second argument to [page:ExtrudeGeometry ExtrudeGeometry]
+		</div>
+		<div>
+		Convenience method to return ExtrudeGeometry
+		</div>
+
+		<h3>.extractPoints([page:todo divisions]) [page:todo]</h3>
+		<div>
+		divisions -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.extractAllSpacedPoints([page:todo divisions]) [page:todo]</h3>
+		<div>
+		divisions -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getPointsHoles([page:todo divisions]) [page:todo]</h3>
+		<div>
+		divisions -- todo
+		</div>
+		<div>
+		Get points of holes
+		</div>
+
+		<h3>.getSpacedPointsHoles([page:todo divisions]) [page:todo]</h3>
+		<div>
+		divisions -- todo
+		</div>
+		<div>
+		Get points of holes (spaced by regular distance)
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/core/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CircleGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CircleGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CircleGeometry.html	(revision 28897)
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">CircleGeometry is a simple shape of Euclidean geometry.  It is contructed from a number of triangular segments that are oriented around a central point and extend as far out as a given radius.  It is built counter-clockwise from a start angle and a given central angle.  It can also be used to create regular polygons, where the number of segments determines the number of sides.
+		</div>
+
+		<h2>Constructor</h2>
+
+		<h3>[name]([page:Float radius], [page:Integer segments], [page:Float thetaStart], [page:Float thetaLength])</h3>
+		<div>
+		radius â Radius of the circle, default = 50.<br />
+		segments â Number of segments (triangles), minimum = 3, default = 8.<br />
+		thetaStart â Start angle for first segment, default = 0 (three o'clock position).<br />
+		thetaLength â The central angle, often called theta, of the circular sector. The default is 2*Pi, which makes for a complete circle.
+		</div>
+		
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ConvexGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ConvexGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ConvexGeometry.html	(revision 28897)
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+	
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Array vertices])</h3>
+		<div>
+		vertices â todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CubeGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CubeGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CubeGeometry.html	(revision 28897)
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+	
+		<h1>[name]</h1>
+
+		<div class="desc">CubeGeometry is the quadrilateral primitive geometry class. It is typically used for creating a cube or irregular quadrilateral of the dimensions provided with the 'width', 'height', and 'depth' constructor arguments.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float width], [page:Float height], [page:Float depth], [page:Integer widthSegments], [page:Integer heightSegments], [page:Integer depthSegments])</h3>
+		<div>
+		width â Width of the sides on the X axis.<br />
+		height â Height of the sides on the Y axis.<br />
+		depth â Depth of the sides on the Z axis.<br />
+		widthSegments â Optional. Number of segmented faces along the width of the sides. Default is 1.<br />
+		heightSegments â Optional. Number of segmented faces along the height of the sides. Default is 1.<br />
+		depthSegments â Optional. Number of segmented faces along the depth of the sides. Default is 1.
+		</div>
+
+		<h2>Properties</h2>
+		
+		<div>
+		Each of the constructor parameters is accessible as a property of the same name. Any modification of these properties after instantiation does not change the geometry.
+		</div>
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CylinderGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CylinderGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/CylinderGeometry.html	(revision 28897)
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float radiusTop], [page:Float radiusBottom], [page:Float height], [page:Integer radiusSegments], [page:Integer heightSegments], [page:Boolean openEnded])</h3>
+		<div>
+		radiusTop â Radius of the cylinder at the top. Default is 20.<br />
+		radiusBottom â Radius of the cylinder at the bottom. Default is 20.<br />
+		height â Height of the cylinder. Default is 100.<br />
+		radiusSegments â Number of segmented faces around the circumference of the cylinder. Default is 8<br />
+		heightSegments â Number of rows of faces along the height of the cylinder. Default is 1.<br />
+		openEnded â A Boolean indicating whether the ends of the cylinder are open or capped. Default is false, meaning capped.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<div>
+		Each of the contructor parameters is accessible as a property of the same name. Any modification of these properties after instantiation does not change the geometry.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ExtrudeGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ExtrudeGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ExtrudeGeometry.html	(revision 28897)
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">Creates extruded geometry from a path shape</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo shapes], [page:Object options])</h3>
+		<div>
+		shapes â Shape or an array of shapes. <br />
+		options â Object that can contain the following parameters.
+
+	<ul>
+<li>curveSegments â  int. number of points on the curves</li>
+<li>steps â  int. number of points used for subdividing segements of extrude spline</li>
+<li>amount â  int. Depth to extrude the shape</li>
+<li>bevelEnabled â  bool. turn on bevel</li>
+<li>bevelThickness â  float. how deep into the original shape bevel goes</li>
+<li>bevelSize â  float. how far from shape outline is bevel</li>
+<li>bevelSegments â  int. number of bevel layers</li>
+<li>extrudePath â  THREE.CurvePath. 3d spline path to extrude shape along. (creates Frames if (frames aren't defined)</li>
+<li>frames â  THREE.TubeGeometry.FrenetFrames.  containing arrays of tangents, normals, binormals</li>
+<li>material â  int. material index for front and back faces</li>
+<li>extrudeMaterial â  int. material index for extrusion and beveled faces</li>
+<li>uvGenerator â  Object. object that provides UV generator functions</li>
+	</ul>
+
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+		<h2>Methods</h2>
+
+		<h3>.addShapeList ([page:todo shapes], [page:Object options])</h3>
+		<div>
+			shapes â todo <br />
+			options â todo
+		</div>
+		<div>todo</div>
+
+		<h3>.addShape ([page:todo shape], [page:Object options])</h3>
+		<div>
+			shape â todo <br />
+			options â todo
+		</div>
+		<div>todo</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/IcosahedronGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/IcosahedronGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/IcosahedronGeometry.html	(revision 28897)
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:PolyhedronGeometry] &rarr;
+		<h1>[name]</h1>
+
+		<div class="desc">A class for generating an icosahedron geometry.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float radius], [page:Integer detail])</h3>
+		<div>
+		radius â Default is 1. <br />
+		detail â Default is 0.  Setting this to a value greater than 0 adds more vertices making it no longer an icosahedron.  When detail is greater than 1, it's effectively a sphere.
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+		<div>
+		Each of the contructor parameters is accessible as a property of the same name. Any modification of these properties after instantiation does not change the geometry.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/LatheGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/LatheGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/LatheGeometry.html	(revision 28897)
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">Class for generating meshes with axial symmetry. Possible uses include donuts, pipes, vases etc. The lathe rotate around the Z axis.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Array points], [page:Integer segments], [page:Float phiStart], [page:Float phiLength])</h3>
+		<div>
+		points â Array of Vector3s. Since this rotates around Z axis, the y-values can be set to 0<br />
+		segments â the number of circumference segments to generate. Default is 12.<br />
+		phiStart â the starting angle in radians. Default is 0.<br />
+		phiLength â the radian (0 to 2*PI) range of the lathed section 2*PI is a closed lathe, less than 2PI is a portion. Default is 2*PI
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/OctahedronGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/OctahedronGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/OctahedronGeometry.html	(revision 28897)
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:PolyhedronGeometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float radius], [page:Integer detail])</h3>
+		<div>
+		radius â Radius of the octahedron. Default is 1.<br />
+		detail â Default is 0.  Setting this to a value greater than zero add vertices making it no longer an octahedron.
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ParametricGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ParametricGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ParametricGeometry.html	(revision 28897)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo func], [page:todo slices], [page:todo stacks])</h3>
+		<div>
+		func â todo <br />
+		slices â todo <br />
+		stacks â todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/PlaneGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/PlaneGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/PlaneGeometry.html	(revision 28897)
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float width], [page:Float height], [page:Integer widthSegments], [page:Integer heightSegments])</h3>
+		<div>
+		width â Width along the X axis.<br />
+		height â Height along the Y axis.<br />
+		widthSegments â Optional. Default is 1. <br />
+		heightSegments â Optional. Default is 1.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<div>
+		Each of the contructor parameters is accessible as a property of the same name. Any modification of these properties after instantiation does not change the geometry.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/PolyhedronGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/PolyhedronGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/PolyhedronGeometry.html	(revision 28897)
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Array vertices], [page:Array faces], [page:Float radius], [page:Integer detail])</h3>
+		<div>
+		vertices â todo <br />
+		faces â todo <br />
+		radius â todo <br />
+		detail â todo
+		</div>
+		<div>
+		todo
+		</div>
+		
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/RingGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/RingGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/RingGeometry.html	(revision 28897)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">A class for generating a two-dimensional ring geometry.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float innerRadius], [page:Float outerRadius], [page:Integer thetaSegments], [page:Integer phiSegments], [page:Float thetaStart], [page:Float thetaLength])</h3>
+		<div>
+		innerRadius â Default is 0, but it doesn't work right when innerRadius is set to 0.<br />
+		outerRadius â Default is 50. <br />
+		thetaSegments â Number of segments.  A higher number means the ring will be more round.  Minimum is 3.  Default is 8. <br />
+		phiSegments â Minimum is 3.  Default is 8.<br />
+		thetaStart â Starting angle. Default is 0. <br />
+		thetaLength â Central angle.  Default is Math.PI * 2.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ShapeGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ShapeGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/ShapeGeometry.html	(revision 28897)
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Array shapes], [page:Object options])</h3>
+		<div>
+		shapes â todo <br />
+		options â 
+		<ul>
+		<li>curveSegments - todo</li>
+		<li>material - todo</li>
+		<li>UVGenerator - todo</li>
+		</ul>
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Object shapebb]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.addShapeList([page:todo shapes], [page:Object options]) [page:this]</h3>
+		<div>
+		shapes â todo <br />
+		options â todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addShape([page:todo shape], [page:Object options])</h3>
+		<div>
+		shape â todo <br />
+		options â todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/SphereGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/SphereGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/SphereGeometry.html	(revision 28897)
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">A class for generating sphere geometries</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float radius], [page:Integer widthSegments], [page:Integer heightSegments], [page:Float phiStart], [page:Float phiLength], [page:Float thetaStart], [page:Float thetaLength])</h3>
+
+		<div>
+		radius â sphere radius. Default is 50.<br />
+		widthSegments â number of horizontal segments. Minimum value is 3, and the default is 8.<br />
+		heightSegments â number of vertical segments. Minimum value is 2, and the default is 6.<br />
+		phiStart â specify horizontal starting angle. Default is 0.<br />
+		phiLength â specify horizontal sweep angle size. Default is Math.PI * 2.<br />
+		thetaStart â specify vertical starting angle. Default is 0.<br />
+		thetaLength â specify vertical sweep angle size. Default is Math.PI.<br />
+		</div>
+		
+		<div>
+		The geometry is created by sweeping and calculating vertexes around the Y axis (horizontal sweep) and the Z axis (vertical sweep). Thus, incomplete spheres (akin to <em>'sphere slices'</em>) can be created through the use of different values of phiStart, phiLength, thetaStart and thetaLength, in order to define the points in which we start (or end) calculating those vertices.
+		</div>
+
+
+		<h2>Properties</h2>
+		
+		<div>
+		Each of the contructor parameters is accessible as a property of the same name. Any modification of these properties after instantiation does not change the geometry.
+		</div>
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TetrahedronGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TetrahedronGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TetrahedronGeometry.html	(revision 28897)
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:PolyhedronGeometry] &rarr;
+	
+		<h1>[name]</h1>
+
+		<div class="desc">A class for generating a tetrahedron geometries.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float radius], [page:Integer detail])</h3>
+		<div>
+		radius â Radius of the tetrahedron. Default is 1.<br />
+		detail â Default is 0. Setting this to a value greater than 0 adds vertices making it no longer a tetrahedron.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TextGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TextGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TextGeometry.html	(revision 28897)
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:ExtrudeGeometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:String text], [page:Object parameters])</h3>
+		<div>
+		text â todo <br />
+		parameters â Object that can contain the following parameters.
+		<ul>
+			<li>size â Float. Size of the text.</li>
+			<li>height â Float. Thickness to extrude text.  Default is 50.</li>
+			<li>curveSegments â Integer. Number of points on the curves.</li>
+			<li>font â String. Font name.</li>
+			<li>weight â String. Font weight (normal, bold).</li>
+			<li>style â  String. Font style (normal, italics).</li>
+			<li>bevelEnabled â Boolean. Turn on bevel. Default is False.</li>
+			<li>bevelThickness â Float. How deep into text bevel goes. Default is 10.</li>
+			<li>bevelSize â Float. How far from text outline is bevel. Default is 8.</li>
+		</ul>
+		</div>
+		
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TorusGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TorusGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TorusGeometry.html	(revision 28897)
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+	
+		<h1>[name]</h1>
+
+		<div class="desc">Creates a torus.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float radius], [page:Float tube], [page:Integer radialSegments], [page:Integer tubularSegments], [page:Float arc])</h3>
+		<div>
+		radius â Default is 100. <br />
+		tube â Diameter of the tube.  Default is 40. <br />
+		radialSegments â Default is 8 <br />
+		tubularSegments â Default is 6. <br />
+		arc â Central angle.  Default is Math.PI * 2.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<div>
+		Each of the contructor parameters is accessible as a property of the same name. Any modification of these properties after instantiation does not change the geometry.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TorusKnotGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TorusKnotGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TorusKnotGeometry.html	(revision 28897)
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+	
+		<h1>[name]</h1>
+
+		<div class="desc">Creates a torus knot, the particular shape of which is defined by a pair of coprime integers, p and q.  If p and q are not coprime, the result will be a torus link.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float radius], [page:Float tube], [page:Integer radialSegments], [page:Integer tubularSegments], [page:Integer p], [page:Integer q], [page:Float heightScale])</h3>
+		<div>
+		radius â Default is 100. <br />
+		tube â Default is 40. <br />
+		radialSegments â Default is 64. <br />
+		tubularSegments â Default is 8. <br />
+		p â Default is 2. <br />
+		q â Default is 3. <br />
+		heightScale â Default is 1.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		
+		<div>
+		Each of the contructor parameters is accessible as a property of the same name. Any modification of these properties after instantiation does not change the geometry.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TubeGeometry.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TubeGeometry.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/TubeGeometry.html	(revision 28897)
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		[page:Geometry] &rarr;
+		
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo path], [page:Integer segments], [page:Float radius], [page:Integer radiusSegments], [page:Boolean closed], [page:Boolean debug])</h3>
+		<div>
+		path â todo <br />
+		segments â todo <br />
+		radius â todo <br />
+		radiusSegments â todo <br />
+		closed â todo <br />
+		debug â todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:todo path]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Integer segments]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Float radius]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Integer radiusSegments]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Boolean closed]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Array tangents]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Array normals]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Array binormals]</h3>
+		<div>
+		todo
+		</div> 
+
+
+		<h2>Methods</h2>
+		
+
+		<h3>.FrenetFrames([page:todo path], [page:Integer segments], [page:Boolean closed])</h3>
+		<div>
+		path â todo <br />
+		segments â todo <br />
+		closed â todo
+		</div>
+		<div>
+		todo
+		</div>
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/geometries/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/ArrowHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/ArrowHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/ArrowHelper.html	(revision 28897)
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Example</h2>
+
+		<code>var dir = new THREE.Vector3( 1, 0, 0 );
+		var origin = new THREE.Vector3( 0, 0, 0 );
+		var length = 1;
+		var hex = 0xffff00;
+
+		var arrowHelper = new THREE.ArrowHelper( dir, origin, length, hex );
+		scene.add( arrowHelper );
+		</code>
+
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo dir], [page:todo origin], [page:todo length], [page:todo hex])</h3>
+		<div>
+		dir -- Vector3 -- direction from origin <br />
+		origin -- Vector3 <br />
+		length -- scalar <br />
+		hex -- hexadecimal value to define color ex:0xffff00
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Line line]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Mesh cone]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.setColor([page:todo hex]) [page:todo]</h3>
+		<div>
+		hex -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setLength([page:todo length]) [page:todo]</h3>
+		<div>
+		length -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setDirection([page:todo dir]) [page:todo]</h3>
+		<div>
+		dir -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/AxisHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/AxisHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/AxisHelper.html	(revision 28897)
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Example</h2>
+
+                <code>var axisHelper = new THREE.AxisHelper( 5 );
+		scene.add( axisHelper );
+                </code>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo size])</h3>
+		<div>
+		size -- Define the size of the helper
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/BoxHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/BoxHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/BoxHelper.html	(revision 28897)
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo size])</h3>
+		<div>
+		size -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:array vertices]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+		<h3>.update([page:todo object]) [page:todo]</h3>
+		<div>
+		object -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/CameraHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/CameraHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/CameraHelper.html	(revision 28897)
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo camera])</h3>
+		<div>
+		camera -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:object pointMap]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:PerspectiveCamera camera]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+		<h3>.update() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/DirectionalLightHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/DirectionalLightHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/DirectionalLightHelper.html	(revision 28897)
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo light], [page:todo sphereSize])</h3>
+		<div>
+		light -- todo <br />
+		sphereSize -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+		
+
+		<h3>.[page:Mesh lightSphere]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:DirectionalLight light]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Line targetLine]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+		<h3>.update() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/GridHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/GridHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/GridHelper.html	(revision 28897)
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Example</h2>
+
+		<code>var size = 10;
+		var step = 1;
+		var gridHelper = new THREE.GridHelper( size, step );
+		
+		gridHelper.position = new THREE.Vector3( 10, 10, 0 );
+		gridHelper.rotation = new THREE.Euler( 15, 0, 0 );
+		
+		scene.add( gridHelper );
+		</code>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]([page:todo size], [page:todo step])</h3>
+		<div>
+		size -- todo <br />
+		step -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/HemisphereLightHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/HemisphereLightHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/HemisphereLightHelper.html	(revision 28897)
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]([page:todo light], [page:todo sphereSize], [page:todo arrowLength], [page:todo domeSize])</h3>
+		<div>
+		light -- todo <br />
+		sphereSize -- todo <br />
+		arrowLength -- todo <br />
+		domeSize -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Mesh lightSphere]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:HemisphereLight light]</h3>
+		<div>
+		todo
+		</div> 
+
+
+		<h2>Methods</h2>
+
+		<h3>.update() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/PointLightHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/PointLightHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/PointLightHelper.html	(revision 28897)
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">This display a helper for a pointLight </div>
+
+
+		<h2>Example</h2>
+
+		<code>var pointLight = new THREE.PointLight( 0xff0000, 1, 100 );
+		pointLight.position.set( 10, 10, 10 );
+		scene.add( pointLight );
+		
+		var sphereSize = 1;
+		var pointLightHelper = new THREE.PointLightHelper( pointLight, sphereSize );
+		scene.add( pointLightHelper );
+		</code>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]([page:todo light], [page:todo sphereSize])</h3>
+		<div>
+		light -- todo <br />
+		sphereSize -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Mesh lightSphere]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:PointLight light]</h3>
+		<div>
+		todo
+		</div> 
+
+
+		<h2>Methods</h2>
+
+		<h3>.update() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/SpotLightHelper.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/SpotLightHelper.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/SpotLightHelper.html	(revision 28897)
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo light], [page:todo sphereSize])</h3>
+		<div>
+		light -- todo <br />
+		sphereSize -- todo
+		</div>
+		<div>
+		todo
+		</div>
+		
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:Mesh lightSphere]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:SpotLight light]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Mesh lightCone]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+		
+
+		<h3>.update() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/helpers/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/ImmediateRenderObject.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/ImmediateRenderObject.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/ImmediateRenderObject.html	(revision 28897)
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Methods</h2>
+
+
+		<h3>.render([page:todo renderCallback]) [page:todo]</h3>
+		<div>
+		renderCallback -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/LensFlare.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/LensFlare.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/LensFlare.html	(revision 28897)
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo texture], [page:todo size], [page:todo distance], [page:todo blending], [page:todo color])</h3>
+		<div>
+		texture -- todo <br />
+		size -- todo <br />
+		distance -- todo <br />
+		blending -- todo <br />
+		color -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:array lensFlares]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 positionScreen]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:todo customUpdateCallback]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.updateLensFlares() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/MorphBlendMesh.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/MorphBlendMesh.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/MorphBlendMesh.html	(revision 28897)
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo geometry], [page:todo material])</h3>
+		<div>
+		geometry -- todo <br />
+		material -- todo
+		</div>
+		<div>
+		todo
+		</div>
+		
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:object animationsMap]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:array animationsList]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.setAnimationWeight([page:todo name], [page:todo weight]) [page:todo]</h3>
+		<div>
+		name -- todo <br />
+		weight -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setAnimationFPS([page:todo name], [page:todo fps]) [page:todo]</h3>
+		<div>
+		name -- todo <br />
+		fps -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.createAnimation([page:todo name], [page:todo start], [page:todo end], [page:todo fps]) [page:todo]</h3>
+		<div>
+		name -- todo <br />
+		start -- todo <br />
+		end -- todo <br />
+		fps -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.playAnimation([page:todo name]) [page:todo]</h3>
+		<div>
+		name -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.update([page:todo delta]) [page:todo]</h3>
+		<div>
+		delta -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.autoCreateAnimations([page:todo fps]) [page:todo]</h3>
+		<div>
+		fps -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setAnimationDuration([page:todo name], [page:todo duration]) [page:todo]</h3>
+		<div>
+		name -- todo <br />
+		duration -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setAnimationDirectionForward([page:todo name]) [page:todo]</h3>
+		<div>
+		name -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getAnimationDuration([page:todo name]) [page:todo]</h3>
+		<div>
+		name -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getAnimationTime([page:todo name]) [page:todo]</h3>
+		<div>
+		name -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setAnimationDirectionBackward([page:todo name]) [page:todo]</h3>
+		<div>
+		name -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setAnimationTime([page:todo name], [page:todo time]) [page:todo]</h3>
+		<div>
+		name -- todo <br />
+		time -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.stopAnimation([page:todo name]) [page:todo]</h3>
+		<div>
+		name -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/objects/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/DepthPassPlugin.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/DepthPassPlugin.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/DepthPassPlugin.html	(revision 28897)
@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../../list.js"></script>
+		<script src="../../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:boolean enabled]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object renderTarget]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.init([page:todo renderer]) [page:todo]</h3>
+		<div>
+		renderer -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.update([page:todo scene], [page:todo camera]) [page:todo]</h3>
+		<div>
+		scene -- todo <br />
+		camera -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.render([page:todo scene], [page:todo camera]) [page:todo]</h3>
+		<div>
+		scene -- todo <br />
+		camera -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/LensFlarePlugin.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/LensFlarePlugin.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/LensFlarePlugin.html	(revision 28897)
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../../list.js"></script>
+		<script src="../../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.init([page:todo renderer]) [page:todo]</h3>
+		<div>
+		renderer -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.render([page:todo scene], [page:todo camera], [page:todo viewportWidth], [page:todo viewportHeight]) [page:todo]</h3>
+		<div>
+		scene -- todo <br />
+		camera -- todo <br />
+		viewportWidth -- todo <br />
+		viewportHeight -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/ShadowMapPlugin.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/ShadowMapPlugin.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/ShadowMapPlugin.html	(revision 28897)
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../../list.js"></script>
+		<script src="../../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.init([page:todo renderer]) [page:todo]</h3>
+		<div>
+		renderer -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.update([page:todo scene], [page:todo camera]) [page:todo]</h3>
+		<div>
+		scene -- todo <br />
+		camera -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.render([page:todo scene], [page:todo camera]) [page:todo]</h3>
+		<div>
+		scene -- todo <br />
+		camera -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/SpritePlugin.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/SpritePlugin.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/SpritePlugin.html	(revision 28897)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../../list.js"></script>
+		<script src="../../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.init([page:todo renderer]) [page:todo]</h3>
+		<div>
+		renderer -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.render([page:todo scene], [page:todo camera], [page:todo viewportWidth], [page:todo viewportHeight]) [page:todo]</h3>
+		<div>
+		scene -- todo <br />
+		camera -- todo <br />
+		viewportWidth -- todo <br />
+		viewportHeight -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/renderers/plugins/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/ShaderFlares.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/ShaderFlares.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/ShaderFlares.html	(revision 28897)
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:object lensFlare]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object lensFlareVertexTexture]</h3>
+		<div>
+		todo
+		</div> 
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/ShaderSprite.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/ShaderSprite.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/ShaderSprite.html	(revision 28897)
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:object sprite]</h3>
+		<div>
+		todo
+		</div> 
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/extras/shaders/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/AmbientLight.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/AmbientLight.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/AmbientLight.html	(revision 28897)
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr; [page:Light] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">
+			This light's color gets applied to all the objects in the scene globally.
+		</div>
+
+
+		<h2>Example</h2>
+
+<code>var light = new THREE.AmbientLight( 0x404040 ); // soft white light
+scene.add( light );</code>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Integer hex] )</h3>
+		<div>
+		[page:Integer hex] â Numeric value of the RGB component of the color.
+		</div>
+		<div>
+		This creates an Ambientlight with a color.
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/AreaLight.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/AreaLight.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/AreaLight.html	(revision 28897)
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">This illuminates the scene from a complete surface. This light only works in the [page:WebGLDeferredRenderer deferredrenderer]. </div>
+
+
+		<h2>Example</h2>
+		<code>areaLight1 = new THREE.AreaLight( 0xffffff, 1 );
+areaLight1.position.set( 0.0001, 10.0001, -18.5001 );
+areaLight1.rotation.set( -0.74719, 0.0001, 0.0001 );
+areaLight1.width = 10;
+areaLight1.height = 1;
+
+scene.add( areaLight1 );</code>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Integer hex], [page:Float intensity])</h3>
+		<div>
+		[page:Integer hex] â Numeric value of the RGB component of the color.<br />
+		[page:Float intensity] â Numeric value of the light's strength/intensity.
+		</div>
+		<div>
+		This creates a arealight with color.
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Vector3 right]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 normal]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number quadraticAttenuation]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number height]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number linearAttenuation]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number width]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Float intensity]</h3>
+		<div>
+			Light's intensity.<br />
+			Default â *1.0*.
+		</div>
+
+		<h3>.[page:number constantAttenuation]</h3>
+		<div>
+		todo
+		</div> 
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/DirectionalLight.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/DirectionalLight.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/DirectionalLight.html	(revision 28897)
@@ -0,0 +1,213 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr; [page:Light] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">
+			Affects objects using [page:MeshLambertMaterial] or [page:MeshPhongMaterial].
+		</div>
+
+
+		<h2>Example</h2>
+
+		<code>// White directional light at half intensity shining from the top.
+
+var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.5 );
+directionalLight.position.set( 0, 1, 0 );
+scene.add( directionalLight );</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Integer hex], [page:Float intensity])</h3>
+		<div>
+		[page:Integer hex] -- Numeric value of the RGB component of the color. <br />
+		[page:Float intensity] -- Numeric value of the light's strength/intensity.
+		</div>
+		<div>
+		Creates a light that shines from a specific direction not from a specific position.  This light will behave
+		as though it is infinitely far away and the rays produced from it are all parallel.  The best
+		analogy would be a light source that acts like the sun: the sun is so far away that all sunlight
+		hitting objects comes from the same angle.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Object3D target]</h3>
+		<div>
+			Target used for shadow camera orientation.
+		</div>
+
+		<h3>.[page:Float intensity]</h3>
+		<div>
+			Light's intensity.<br />
+			Default â *1.0*.
+		</div>
+
+		<h3>.[page:Boolean onlyShadow]</h3>
+		<div>
+			If set to *true* light will only cast shadow but not contribute any lighting (as if *intensity* was 0 but cheaper to compute).<br />
+			Default â *false*.
+		</div>
+
+		<h3>.[page:Float shadowCameraNear]</h3>
+		<div>
+			Orthographic shadow camera frustum parameter.<br />
+			Default â *50*.
+		</div>
+
+		<h3>.[page:Float shadowCameraFar]</h3>
+		<div>
+			Orthographic shadow camera frustum parameter.<br />
+			Default â *5000*.
+		</div>
+
+		<h3>.[page:Float shadowCameraLeft]</h3>
+		<div>
+			Orthographic shadow camera frustum parameter.<br />
+			Default â *-500*.
+		</div>
+
+		<h3>.[page:Float shadowCameraRight]</h3>
+		<div>
+			Orthographic shadow camera frustum parameter.<br />
+			Default â *500*.
+		</div>
+
+		<h3>.[page:Float shadowCameraTop]</h3>
+		<div>
+			Orthographic shadow camera frustum parameter.<br />
+			Default â *500*.
+		</div>
+
+		<h3>.[page:Float shadowCameraBottom]</h3>
+		<div>
+			Orthographic shadow camera frustum parameter.<br />
+			Default â *-500*.
+		</div>
+
+		<h3>.[page:Boolean shadowCameraVisible]</h3>
+		<div>
+			Show debug shadow camera frustum.<br />
+			Default â *false*.
+		</div>
+
+		<h3>.[page:Float shadowBias]</h3>
+		<div>
+			Shadow map bias.<br />
+			Default â *0*.
+		</div>
+
+		<h3>.[page:Float shadowDarkness]</h3>
+		<div>
+			Darkness of shadow casted by this light (from *0* to *1*).<br />
+			Default â *0.5*.
+		</div>
+
+		<h3>.[page:Integer shadowMapWidth]</h3>
+		<div>
+			Shadow map texture width in pixels.<br />
+			Default â *512*.
+		</div>
+
+		<h3>.[page:Integer shadowMapHeight]</h3>
+		<div>
+			Shadow map texture height in pixels.<br />
+			Default â *512*.
+		</div>
+
+		<h3>.[page:Boolean shadowCascade]</h3>
+		<div>
+			??? <br />
+			Default â *false*.
+		</div>
+
+		<h3>.[page:Vector3 shadowCascadeOffset]</h3>
+		<div>
+			??? <br />
+			Default â *Three.Vector3( 0, 0, -1000 )*.
+		</div>
+
+		<h3>.[page:Integer shadowCascadeCount]</h3>
+		<div>
+			??? <br />
+			Default â *2*.
+		</div>
+
+		<h3>.[page:Array shadowCascadeBias]</h3>
+		<div>
+			??? <br />
+			Default â <strong>[ 0, 0, 0 ]</strong>.
+		</div>
+
+		<h3>.[page:Array shadowCascadeWidth]</h3>
+		<div>
+			??? <br />
+			Default â <strong>[ 512, 512, 512 ]</strong>.
+		</div>
+
+		<h3>.[page:Array shadowCascadeHeight]</h3>
+		<div>
+			??? <br />
+			Default â <strong>[ 512, 512, 512 ]</strong>.
+		</div>
+
+		<h3>.[page:Array shadowCascadeNearZ]</h3>
+		<div>
+			??? <br />
+			Default â <strong>[ -1.000, 0.990, 0.998 ]</strong>.
+		</div>
+
+		<h3>.[page:Array shadowCascadeFarZ]</h3>
+		<div>
+			??? <br />
+			Default â <strong>[ 0.990, 0.998, 1.000 ]</strong>.
+		</div>
+
+		<h3>.[page:Array shadowCascadeArray]</h3>
+		<div>
+			??? <br />
+			Default â <strong>[ ]</strong>.
+		</div>
+
+		<h3>.[page:RenderTarget shadowMap]</h3>
+		<div>
+			??? <br />
+			Default â *null*.
+		</div>
+
+		<h3>.[page:Integer shadowMapSize]</h3>
+		<div>
+			??? <br />
+			Default â *null*.
+		</div>
+
+		<h3>.[page:Camera shadowCamera]</h3>
+		<div>
+			??? <br />
+			Default â *null*.
+		</div>
+
+		<h3>.[page:Matrix shadowMatrix]</h3>
+		<div>
+			??? <br />
+			Default â *null*.
+		</div>
+		
+		<h2>Methods</h2>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/HemisphereLight.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/HemisphereLight.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/HemisphereLight.html	(revision 28897)
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+        [page:Object3D] &rarr; [page:Light] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A light source positioned directly above the scene.</div>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Integer skyColorHex], [page:Integer groundColorHex], [page:Float intensity])</h3>
+        <div>
+		[page:Integer skyColorHex] â Numeric value of the RGB sky color.<br />
+		[page:Integer groundColorHex] â Numeric value of the RGB ground color.<br />
+		[page:Float intensity] â Numeric value of the light's strength/intensity.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float groundColor]</h3>
+
+		<div>
+			Light's ground color.<br />
+		</div>
+
+		<h3>.[page:Float intensity]</h3>
+		<div>
+			Light's intensity.<br />
+			Default â *1.0*.
+		</div>
+
+		<h2>Methods</h2>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/Light.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/Light.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/Light.html	(revision 28897)
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">Abstract base class for lights.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Integer hex] )</h3>
+		<div>
+		[page:Integer hex] â Numeric value of the RGB component of the color.
+		</div>
+		<div>
+		This creates a light with color.<br />
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Color color]</h3>
+		<div>
+		Color of the light.<br />
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/PointLight.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/PointLight.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/PointLight.html	(revision 28897)
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr; [page:Light] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">
+			Affects objects using [page:MeshLambertMaterial] or [page:MeshPhongMaterial].
+		</div>
+
+
+		<h2>Example</h2>
+
+		<code>var light = new THREE.PointLight( 0xff0000, 1, 100 );
+light.position.set( 50, 50, 50 );
+scene.add( light );</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Integer hex], [page:Float intensity], [page:todo distance])</h3>
+		<div>
+		[page:Integer hex] â Numeric value of the RGB component of the color. <br />
+		[page:Float intensity] â Numeric value of the light's strength/intensity. <br />
+		distance -- todo
+		</div>
+		<div>
+		Creates a light at a specific position in the scene.  The light shines in all directions (roughly similar to a light bulb.)
+	
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float intensity]</h3>
+		<div>
+			Light's intensity.<br />
+			Default - *1.0*.
+		</div>
+
+		<h3>.[page:Float distance]</h3>
+		<div>
+			If non-zero, light will attenuate linearly from maximum intensity at light *position* down to zero at *distance*.<br />
+			Default â *0.0*.
+		</div>
+		
+		<h2>Methods</h2>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/SpotLight.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/SpotLight.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/SpotLight.html	(revision 28897)
@@ -0,0 +1,197 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr; [page:Light] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A point light that can cast shadow in one direction.</div>
+		
+		<div class="desc">Affects objects using [page:MeshLambertMaterial] or [page:MeshPhongMaterial].</div>
+
+
+		<h2>Example</h2>
+
+		<code>// white spotlight shining from the side, casting shadow
+
+var spotLight = new THREE.SpotLight( 0xffffff );
+spotLight.position.set( 100, 1000, 100 );
+
+spotLight.castShadow = true;
+
+spotLight.shadowMapWidth = 1024;
+spotLight.shadowMapHeight = 1024;
+
+spotLight.shadowCameraNear = 500;
+spotLight.shadowCameraFar = 4000;
+spotLight.shadowCameraFov = 30;
+
+scene.add( spotLight );</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Integer hex], [page:Float intensity], [page:todo distance], [page:todo angle], [page:todo exponent])</h3>
+		<div>
+		[page:Integer hex] â Numeric value of the RGB component of the color. <br />
+		[page:Float intensity] â Numeric value of the light's strength/intensity. <br />
+		distance -- todo <br />
+		angle -- todo <br />
+		exponent -- todo
+		</div>
+		<div>
+		todo
+		</div>
+		
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Object3D target]</h3>
+		<div>
+			Spotlight focus points at target.position.<br />
+			Default position â *(0,0,0)*.
+		</div>
+	
+		<h3>.[page:Float intensity]</h3>
+		<div>
+			Light's intensity.<br />
+			Default â *1.0*.
+		</div>
+	
+		<h3>.[page:Float distance]</h3>
+		<div>
+			If non-zero, light will attenuate linearly from maximum intensity at light *position* down to zero at *distance*.<br />
+			Default â *0.0*.
+		</div>
+	
+		<h3>.[page:Float angle]</h3>
+		<div>
+			Maximum extent of the spotlight, in radians, from its direction. Should be no more than *Math.PI/2*.<br />
+			Default â *Math.PI/3*.
+		</div>
+	
+		<h3>.[page:Float exponent]</h3>
+		<div>
+			Rapidity of the falloff of light from its target direction.<br />
+			Default â *10.0*.
+		</div>
+	
+		<h3>.[page:Boolean castShadow]</h3>
+		<div>
+			If set to *true* light will cast dynamic shadows. *Warning*: This is expensive and requires tweaking to get shadows looking right.<br />
+			Default â *false*.
+		</div>
+		
+		<h3>.[page:Boolean onlyShadow]</h3>
+		<div>
+			If set to *true* light will only cast shadow but not contribute any lighting (as if *intensity* was 0 but cheaper to compute).<br />
+			Default â *false*.
+		</div>
+		
+		<h3>.[page:Float shadowCameraNear]</h3>
+		<div>
+			Perspective shadow camera frustum <em>near</em> parameter.<br />
+			Default â *50*.
+		</div>
+
+		<h3>.[page:Float shadowCameraFar]</h3>
+		<div>
+			Perspective shadow camera frustum <em>far</em> parameter.<br />
+			Default â *5000*.
+		</div>
+
+		<h3>.[page:Float shadowCameraFov]</h3>
+		<div>
+			Perspective shadow camera frustum <em>field of view</em> parameter.<br />
+			Default â *50*.
+		</div>
+		
+		<h3>.[page:Boolean shadowCameraVisible]</h3>
+		<div>
+			Show debug shadow camera frustum.<br />
+			Default â *false*.
+		</div>
+		
+		<h3>.[page:Float shadowBias]</h3>
+		<div>
+			Shadow map bias.<br />
+			Default â *0*.
+		</div>
+
+		<h3>.[page:Float shadowDarkness]</h3>
+		<div>
+			Darkness of shadow casted by this light (from *0* to *1*).<br />
+			Default â *0.5*.
+		</div>
+
+		<h3>.[page:Integer shadowMapWidth]</h3>
+		<div>
+			Shadow map texture width in pixels.<br />
+			Default â *512*.
+		</div>
+
+		<h3>.[page:Integer shadowMapHeight]</h3>
+		<div>
+			Shadow map texture height in pixels.<br />
+			Default â *512*.
+		</div>
+		
+		<h3>.[page:Float shadowBias]</h3>
+		<div>
+			Shadow map bias.<br />
+			Default â *0*.
+		</div>
+
+		<h3>.[page:Float shadowDarkness]</h3>
+		<div>
+			Darkness of shadow casted by this light (from *0* to *1*).<br />
+			Default â *0.5*.
+		</div>
+
+		<h3>.[page:Integer shadowMapWidth]</h3>
+		<div>
+			Shadow map texture width in pixels.<br />
+			Default â *512*.
+		</div>
+
+		<h3>.[page:Integer shadowMapHeight]</h3>
+		<div>
+			Shadow map texture height in pixels.<br />
+			Default â *512*.
+		</div>
+
+
+		<h3>.[page:object shadowMatrix]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object shadowMapSize]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object shadowCamera]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object shadowMap]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>		
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/lights/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/GeometryLoader.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/GeometryLoader.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/GeometryLoader.html	(revision 28897)
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A loader for loading a geometry out of a JSON file.</div>
+		<div class="desc">Unlike other loaders, this one emits events instead of using predefined callbacks. So if you're interested in getting notified when <em>things</em> happen, you need to add listeners to the object.</div>
+		<div class="desc">Possible events:<br />
+			<ul>
+				<li>type: error, message: error message</li>
+				<li>type: load, content: loaded geometry</li>
+			</ul>
+		</div>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+		
+		
+		<h2>Methods</h2>
+
+		<h3>.load( [page:String url] )</h3>
+		<div>
+		url â required
+		</div>
+		
+		<div class="desc">Begin loading from <em>url</em></div>
+
+
+		<h3>.parse( [page:Object data], [page:LoadingMonitor monitor] )</h3>
+		<div>
+		data â required<br />
+		monitor â optional
+		</div>
+		
+		<div class="desc">Parses the geometry in the <em>data</em> object, optionally monitoring its progress via the <em>monitor</em> object, if provided.</div>
+
+		<h3>.dispatchEvent([page:todo event]) [page:todo]</h3>
+		<div>
+		event -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.hasEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.removeEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/ImageLoader.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/ImageLoader.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/ImageLoader.html	(revision 28897)
@@ -0,0 +1,97 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A loader for loading an [page:Image image].</div>
+		<div class="desc">Unlike other loaders, this one emits events instead of using predefined callbacks. So if you're interested in getting notified when <em>things</em> happen, you need to add listeners to the object.</div>
+		
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+		
+		<h2>Events</h2>
+		
+		<h3>load</h3>
+		<div class="desc">
+		Dispatched when the image has completed loading
+		</div>
+		<div>
+		content â loaded image
+		</div>
+		
+		<h3>error</h3>
+		<div class="desc">
+		Dispatched when the image can't be loaded
+		</div>
+		<div>
+		message â error message
+		</div>
+		
+		<h2>Properties</h2>
+
+		<h3>.[page:object crossOrigin]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h3>.load( [page:String url] )</h3>
+		<div>
+		url â required
+		</div>
+		
+		<div class="desc">Begin loading from <em>url</em></div>
+
+		<h3>.dispatchEvent([page:todo event]) [page:todo]</h3>
+		<div>
+		event -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.hasEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.removeEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/JSONLoader.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/JSONLoader.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/JSONLoader.html	(revision 28897)
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Loader] &rarr;
+		<h1>[name]</h1>
+
+		<div class="desc">A loader for loading objects in JSON format.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Boolean showStatus])</h3>
+		<div>
+		showStatus -- todo
+		</div>
+		<div>
+		todo
+		</div>
+		
+		<h2>Properties</h2>
+
+
+		<h3>.[page:boolean withCredentials]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h3>.load( [page:String url], [page:Function callback], [page:String texturePath] )</h3>
+		<div>
+		url â required<br />
+		callback â required. This function will be called with the loaded model as an instance of [page:Geometry geometry] when the load is completed.<br />
+		texturePath â optional. If not specified, textures will be assumed to be in the same folder as the Javascript model file.
+		</div>
+
+
+		<h3>.parse([page:todo json], [page:todo texturePath]) [page:todo]</h3>
+		<div>
+		json -- todo <br />
+		texturePath -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.loadAjaxJSON([page:todo context], [page:todo url], [page:todo callback], [page:todo texturePath], [page:todo callbackProgress]) [page:todo]</h3>
+		<div>
+		context -- todo <br />
+		url -- todo <br />
+		callback -- todo <br />
+		texturePath -- todo <br />
+		callbackProgress -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/Loader.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/Loader.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/Loader.html	(revision 28897)
@@ -0,0 +1,106 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Base class for implementing loaders.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Boolean showStatus])</h3>
+		<div>
+		showStatus -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Boolean showStatus]</h3>
+		<div>If true, show loading status in the statusDomElement.</div>
+		
+		<h3>.[page:DOMElement statusDomElement]</h3>
+		<div>This is the recipient of status messages.</div>
+		
+		<h3>.[page:Function onLoadStart]</h3>
+		<div>Will be called when load starts.</div>
+		<div>The default is a function with empty body.</div>
+
+		<h3>.[page:Function onLoadProgress]</h3>
+		<div>Will be called while load progresses.</div>
+		<div>The default is a function with empty body.</div>
+		
+		<h3>.[page:Function onLoadComplete]</h3>
+		<div>Will be called when load completes.</div>
+		<div>The default is a function with empty body.</div>
+		
+		<h3>.[page:string crossOrigin]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h3>.needsTangents([page:todo materials]) [page:todo]</h3>
+		<div>
+		materials -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.updateProgress([page:todo progress]) [page:todo]</h3>
+		<div>
+		progress -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.createMaterial([page:todo m], [page:todo texturePath]) [page:todo]</h3>
+		<div>
+		m -- todo <br />
+		texturePath -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.initMaterials([page:todo materials], [page:todo texturePath]) [page:todo]</h3>
+		<div>
+		materials -- todo <br />
+		texturePath -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.extractUrlBase([page:todo url]) [page:todo]</h3>
+		<div>
+		url -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addStatusElement() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/LoadingManager.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/LoadingManager.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/LoadingManager.html	(revision 28897)
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Handles and keeps track of loaded and pending data.</div>
+		<code>
+var manager = new THREE.LoadingManager();
+manager.onProgress = function ( item, loaded, total ) {
+
+	console.log( item, loaded, total );
+
+};
+
+var loader = new THREE.OBJLoader( manager );
+loader.load( 'file.obj', function ( object ) {
+
+	//
+
+} );
+		</code>
+
+		<h2>Constructor</h2>
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/MaterialLoader.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/MaterialLoader.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/MaterialLoader.html	(revision 28897)
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.load([page:todo url]) [page:todo]</h3>
+		<div>
+		url -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.removeEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.parse([page:todo json]) [page:todo]</h3>
+		<div>
+		json -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.dispatchEvent([page:todo event]) [page:todo]</h3>
+		<div>
+		event -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.hasEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/SceneLoader.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/SceneLoader.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/SceneLoader.html	(revision 28897)
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A loader for loading a complete scene out of a JSON file.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+		
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Function onLoadStart]</h3>
+		<div>Will be called when load starts.</div>
+		<div>The default is a function with empty body.</div>
+
+		<h3>.[page:Function onLoadProgress]</h3>
+		<div>Will be called while load progresses.</div>
+		<div>The default is a function with empty body.</div>
+		
+		<h3>.[page:Function onLoadComplete]</h3>
+		<div>Will be called when each element in the scene completes loading.</div>
+		<div>The default is a function with empty body.</div>
+		
+		<h3>.[page:Function callbackSync]</h3>
+		<div>Will be called when load completes.</div>
+		<div>The default is a function with empty body.</div>
+		
+		<h3>.[page:Function callbackProgress]</h3>
+		<div>Will be called as load progresses.</div>
+		<div>The default is a function with empty body.</div>
+
+
+		<h3>.[page:object hierarchyHandlerMap]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object geometryHandlerMap]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h3>.load( [page:String url], [page:Function callbackFinished] )</h3>
+		<div>
+		url â required<br />
+		callbackFinished â required. This function will be called with the loaded model as an instance of [page:Scene scene] when the load is completed.
+		</div>
+
+
+		<h3>.addHierarchyHandler([page:todo typeID], [page:todo loaderClass]) [page:todo]</h3>
+		<div>
+		typeID -- todo <br />
+		loaderClass -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.parse([page:todo json], [page:todo callbackFinished], [page:todo url]) [page:todo]</h3>
+		<div>
+		json -- todo <br />
+		callbackFinished -- todo <br />
+		url -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addGeometryHandler([page:todo typeID], [page:todo loaderClass]) [page:todo]</h3>
+		<div>
+		typeID -- todo <br />
+		loaderClass -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/TextureLoader.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/TextureLoader.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/TextureLoader.html	(revision 28897)
@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Class for loading a [page:Texture texture].</div>
+		<div class="desc">Unlike other loaders, this one emits events instead of using predefined callbacks. So if you're interested in getting notified when <em>things</em> happen, you need to add listeners to the object.</div>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Events</h2>
+		
+		<h3>load</h3>
+		<div class="desc">
+		Dispatched when the texture has completed loading
+		</div>
+		<div>
+		content â loaded texture object
+		</div>
+		
+		<h3>error</h3>
+		<div class="desc">
+		Dispatched when the texture can't be loaded
+		</div>
+		<div>
+		message â error message
+		</div>
+		
+		<h2>Properties</h2>
+
+		<h3>.[page:todo crossOrigin]</h3>
+		<div>
+		default â *null*.<br />
+		If set, assigns the *crossOrigin* attribute of the image to the value of *crossOrigin*, prior to starting the load.
+		</div>
+		
+		<h2>Methods</h2>
+
+		<h3>.load( [page:String url] )</h3>
+		<div>
+		url â required
+		</div>
+		
+		<div class="desc">Begin loading from <em>url</em></div>
+
+		<h3>.dispatchEvent([page:todo event]) [page:todo]</h3>
+		<div>
+		event -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.hasEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.removeEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.addEventListener([page:todo type], [page:todo listener]) [page:todo]</h3>
+		<div>
+		type -- todo <br />
+		listener -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/loaders/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/LineBasicMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/LineBasicMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/LineBasicMaterial.html	(revision 28897)
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A material for drawing wireframe-style geometries.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Object parameters] )</h3>
+
+		<div>parameters is an object with one or more properties defining the material's appearance.</div>
+		<div>
+		color â Line color in hexadecimal. Default is 0xffffff.<br />
+		linewidth â Line thickness. Default is 1.<br />
+		linecap â Define appearance of line ends. Default is 'round'.<br />
+		linejoin â Define appearance of line joints. Default is 'round'.<br />
+		vertexColors â Define how the vertices gets colored. Default is THREE.NoColors.<br />
+		fog â Define whether the material color is affected by global fog settings. Default is false.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Integer color]</h3>
+		<div>Sets the color of the line. Default is 0xffffff.</div>
+
+		<h3>.[page:Float linewidth]</h3>
+		<div>Controls line thickness. Default is 1.</div>
+		<div>Due to limitations in the <a href="https://code.google.com/p/angleproject/" target="_blank">ANGLE layer</a>, on Windows platforms linewidth will always be 1 regardless of the set value.</div>
+
+		<h3>.[page:String linecap]</h3>
+		<div>Define appearance of line ends. Possible values are "butt", "round" and "square". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>.[page:String linejoin]</h3>
+		<div>Define appearance of line joints. Possible values are "round", "bevel" and "miter". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>.[page:Integer vertexColors]</h3>
+		<div>Define how the vertices gets colored. Possible values are THREE.NoColors, THREE.FaceColors and THREE.VertexColors. Default is THREE.NoColors.</div>
+		<div>This setting might not have any effect when used with certain renderers.</div>
+
+		<h3>.[page:Boolean fog]</h3>
+		<div>Define whether the material color is affected by global fog settings.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/LineDashedMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/LineDashedMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/LineDashedMaterial.html	(revision 28897)
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo parameters])</h3>
+		<div>
+		parameters -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:number scale]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Color color]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean vertexColors]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number dashSize]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean fog]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number gapSize]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number linewidth]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/Material.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/Material.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/Material.html	(revision 28897)
@@ -0,0 +1,165 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Materials describe the appearance of [page:Object objects]. They are defined in a (mostly) renderer-independent way, so you don't have to rewrite materials if you decide to use a different renderer.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		This creates a generic material.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Integer id]</h3>
+		<div>
+		Unique number of this material instance.
+		</div>
+
+		<h3>.[page:String name]</h3>
+		<div>
+		Material name. Default is an empty string.
+		</div>
+
+		<h3>.[page:Float opacity]</h3>
+		<div>
+		Float in the range of 0.0 - 1.0 indicating how transparent the material is.
+		A value of 0.0 indicates fully transparent, 1.0 is fully opaque. If
+		*transparent* is not set to true for the material, the material will remain
+		fully opaque and this value will only affect its color.
+		</div>
+		<div>Default is *1.0*.</div>
+
+		<h3>.[page:Boolean transparent]</h3>
+		<div>
+		Defines whether this material is transparent. This has an effect on rendering
+		as transparent objects need special treatment and are rendered after 
+		non-transparent objects. For a working example of this behaviour, check the
+		[page:WebGLRenderer WebGLRenderer] code.<br />
+		When set to true, the extent to which the material is transparent is
+		controlled by setting *opacity*.
+		</div>
+		<div>Default is *false*.</div>
+
+		<h3>.[page:Blending blending]</h3>
+		<div>
+		Which blending to use when displaying objects with this material. Default is [page:Materials NormalBlending].
+		</div>
+
+		<h3>.[page:Integer blendSrc]</h3>
+		<div>
+		Blending source. It's one of the blending mode constants defined in [page:Three Three.js]. Default is [page:CustomBlendingEquation SrcAlphaFactor]
+		</div>
+
+		<h3>.[page:Integer blendDst]</h3>
+		<div>
+		Blending destination. It's one of the blending mode constants defined in [page:Three Three.js]. Default is [page:CustomBlendingEquation OneMinusSrcAlphaFactor].
+		</div>
+
+		<h3>.[page:Integer blendEquation]</h3>
+		<div>
+		Blending equation to use when applying blending. It's one of the constants defined in [page:Three Three.js]. Default is [page:CustomBlendingEquation AddEquation.]
+		</div>
+
+		<h3>.[page:Boolean depthTest]</h3>
+		<div>
+		Whether to have depth test enabled when rendering this material. Default is *true*.
+		</div>
+
+		<h3>.[page:Boolean depthWrite]</h3>
+		<div>
+		Whether rendering this material has any effect on the depth buffer. Default is *true*.
+		</div>
+		<div>
+		When drawing 2D overlays it can be useful to disable the depth writing in order to layer several things together without creating z-index artifacts.
+		</div>
+
+		<h3>.[page:Boolean polygonOffset]</h3>
+		<div>
+		Whether to use polygon offset. Default is *false*. This corresponds to the *POLYGON_OFFSET_FILL* WebGL feature.
+		</div>
+
+		<h3>.[page:Integer polygonOffsetFactor]</h3>
+		<div>
+		Sets the polygon offset factor. Default is *0*.
+		</div>
+
+		<h3>.[page:Integer polygonOffsetUnits]</h3>
+		<div>
+		Sets the polygon offset units. Default is *0*.
+		</div>
+
+		<h3>.[page:Number alphaTest]</h3>
+		<div>
+		Sets the alpha value to be used when running an alpha test. Default is *0*.
+		</div>
+
+		<h3>.[page:Boolean overdraw]</h3>
+		<div>
+		Enables/disables overdraw. If enabled, polygons are drawn slightly bigger in order to fix antialiasing gaps when using the [page:CanvasRenderer]. Default is *false*.
+		</div>
+
+		<h3>.[page:Boolean visible]</h3>
+		<div>
+		Defines whether this material is visible. Default is *true*.
+		</div>
+
+		<h3>.[page:Enum side]</h3>
+		<div>
+		Defines which of the face sides will be rendered - front, back or both.
+		</div>
+		<div>
+		Default is [page:Materials THREE.FrontSide]. Other options are [page:Materials THREE.BackSide] and [page:Materials THREE.DoubleSide].
+		</div>
+
+		<h3>.[page:Boolean needsUpdate]</h3>
+		<div>
+		Specifies that the material needs to be updated at the WebGL level. Set it to true if you made changes that need to be reflected in WebGL.
+		</div>
+		<div>
+		This property is automatically set to *true* when instancing a new material.
+		</div>
+
+		
+		<h2>Methods</h2>
+		
+		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
+		
+		<h3>.clone([page:todo material]) [page:todo]</h3>
+		<div>
+		material -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.dispose() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.setValues([page:todo values]) [page:todo]</h3>
+		<div>
+		values -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshBasicMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshBasicMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshBasicMaterial.html	(revision 28897)
@@ -0,0 +1,112 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A material for drawing geometries in a simple shaded (flat or wireframe) way.</div>
+		<div class="desc">The default will render as flat polygons. To draw the mesh as wireframe, simply set the 'wireframe' property to true.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Object parameters] )</h3>
+
+		<div>parameters is an object with one or more properties defining the material's appearance.</div>
+		<div>
+		color â geometry color in hexadecimal. Default is 0xffffff.<br />
+		wireframe â render geometry as wireframe. Default is false.<br />
+		wireframeLinewidth â Line thickness. Default is 1.<br />
+		wireframeLinecap â Define appearance of line ends. Default is 'round'.<br />
+		wireframeLinejoin â Define appearance of line joints. Default is 'round'.<br />
+		shading â Define shading type. Default is THREE.SmoothShading.<br />
+		vertexColors â Define how the vertices gets colored. Default is THREE.NoColors.<br />
+		fog â Define whether the material color is affected by global fog settings. Default is true.<br />
+		lightMap â Set light map. Default is null.<br />
+		specularMap â Set specular map. Default is null.<br />
+		envMap â Set env map. Default is null.<br />
+		skinning â Define whether the material uses skinning. Default is false.<br />
+		morphTargets â Define whether the material uses morphTargets. Default is false.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Integer color]</h3>
+		<div>Sets the color of the geometry. Default is 0xffffff.</div>
+		
+		<h3>.[page:Boolean wireframe]</h3>
+		<div>Render geometry as wireframe. Default is false (i.e. render as flat polygons).</div>
+
+		<h3>.[page:Float wireframeLinewidth]</h3>
+		<div>Controls wireframe thickness. Default is 1.</div>
+		<div>Due to limitations in the <a href="https://code.google.com/p/angleproject/" target="_blank">ANGLE layer</a>, on Windows platforms linewidth will always be 1 regardless of the set value.</div>
+		
+		<h3>.[page:String wireframeLinecap]</h3>
+		<div>Define appearance of line ends. Possible values are "butt", "round" and "square". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>.[page:String wireframeLinejoin]</h3>
+		<div>Define appearance of line joints. Possible values are "round", "bevel" and "miter". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+		
+		<h3>.[page:String shading]</h3>
+		<div>Define shading type. Default is THREE.SmoothShading.</div>
+
+		<h3>.[page:Integer vertexColors]</h3>
+		<div>Define how the vertices gets colored. Possible values are THREE.NoColors, THREE.FaceColors and THREE.VertexColors. Default is THREE.NoColors.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+		<h3>.[page:Boolean fog]</h3>
+		<div>Define whether the material color is affected by global fog settings.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+		<h3>.[page:Texture lightMap]</h3>
+		<div>Set light map. Default is null.</div>
+
+		<h3>.[page:Texture specularMap]</h3>
+		<div>Set specular map. Default is null.</div>
+
+		<h3>.[page:TextureCube envMap]</h3>
+		<div>Set env map. Default is null.</div>
+
+		<h3>.[page:Boolean skinning]</h3>
+		<div>Define whether the material uses skinning. Default is false.</div>
+	
+		<h3>.[page:Boolean morphTargets]</h3>
+		<div>Define whether the material uses morphTargets. Default is false.</div>	
+		
+		<h3>.[page:object map]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number combine]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number reflectivity]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number refractionRatio]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshDepthMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshDepthMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshDepthMaterial.html	(revision 28897)
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A material for drawing geometry by depth. Depth is based off of the camera near and far plane. White is nearest, black is farthest.</div>
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo parameters])</h3>
+		<div>
+		parameters is an object with one or more properties defining the material's appearance. 
+		</div>
+		<div>
+		wireframe -- Render geometry as wireframe. Default is false (i.e. render as smooth shaded).<br/>
+		wireframeLinewidth -- Controls wireframe thickness. Default is 1.<br/>
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:boolean wireframe]</h3>
+		<div>Render geometry as wireframe. Default is false (i.e. render as smooth shaded).</div> 
+		
+		<h3>.[page:number wireframeLinewidth]</h3>
+		<div>
+			Controls wireframe thickness. Default is 1.<br/><br/>
+			Due to limitations in the ANGLE layer, on Windows platforms linewidth will always be 1 regardless of the set value.
+		</div> 
+		
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshFaceMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshFaceMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshFaceMaterial.html	(revision 28897)
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo materials])</h3>
+		<div>
+		materials -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:array materials]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshLambertMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshLambertMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshLambertMaterial.html	(revision 28897)
@@ -0,0 +1,126 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A material for non-shiny (Lambertian) surfaces, evaluated per vertex.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo parameters])</h3>
+		<div>
+		parameters -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+		<div>See the base [page:Material] class for common parameters.</div>
+
+		<h3>.[page:Color color]</h3>
+		<div>
+		Diffuse color of the material. Default is white.<br />
+		</div>
+
+		<h3>.[page:Color ambient]</h3>
+		<div>
+		Ambient color of the material, multiplied by the color of the [page:AmbientLight]. Default is white.<br />
+		</div>
+
+		<h3>.[page:Color emissive]</h3>
+		<div>
+		Emissive (light) color of the material, essentially a solid color unaffected by other lighting. Default is black.<br />
+		</div>
+
+		<h3>.[page:Integer shading]</h3>
+		<div>How the triangles of a curved surface are rendered: as a smooth surface, as flat separate facets, or no shading at all.</div>
+
+		<div>Options are [page:Materials THREE.SmoothShading] (default), [page:Materials THREE.FlatShading], [page:Materials THREE.NoShading].</div>
+		
+		<h3>.[page:Boolean wireframe]</h3>
+		<div>Whether the triangles' edges are displayed instead of surfaces. Default is *false*.</div>
+		
+		<h3>.[page:Float wireframeLinewidth]</h3>
+		<div>Line thickness for wireframe mode. Default is *1.0*.</div>
+		<div>Due to limitations in the <a href="https://code.google.com/p/angleproject/" target="_blank">ANGLE layer</a>, on Windows platforms linewidth will always be 1 regardless of the set value.</div>
+
+		<h3>.[page:String wireframeLinecap]</h3>
+		<div>Define appearance of line ends. Possible values are "butt", "round" and "square". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>.[page:String wireframeLinejoin]</h3>
+		<div>Define appearance of line joints. Possible values are "round", "bevel" and "miter". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>.[page:Integer vertexColors]</h3>
+		<div>Define how the vertices gets colored. Possible values are THREE.NoColors, THREE.FaceColors and THREE.VertexColors. Default is THREE.NoColors.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+		<h3>.[page:Boolean fog]</h3>
+		<div>Define whether the material color is affected by global fog settings. Default is *true*.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+		<h3>.[page:Texture map]</h3>
+		<div>Set color texture map. Default is null.</div>
+
+		<h3>.[page:Texture lightMap]</h3>
+		<div>Set light map. Default is null.</div>
+
+		<h3>.[page:Texture specularMap]</h3>
+		<div>Since this material does not have a specular component, the specular value affects only how much of the environment map affects the surface. Default is null.</div>
+
+		<h3>.[page:TextureCube envMap]</h3>
+		<div>Set env map. Default is null.</div>
+
+		<h3>.[page:Float reflectivity]</h3>
+		<div>How much the environment map affects the surface; also see "combine".</div> 
+
+		<h3>.[page:Float refractionRatio]</h3>
+		<div>The index of refraction for an environment map using [page:Textures THREE.CubeRefractionMapping]. Default is *0.98*.</div>
+
+		<h3>.[page:Integer combine]</h3>
+		<div>How to combine the result of the surface's color with the environment map, if any.</div> 
+		 
+		<div>Options are [page:Textures THREE.Multiply] (default), [page:Textures THREE.MixOperation], [page:Textures THREE.AddOperation]. If mix is chosen, the reflectivity is used to blend between the two colors.</div>
+		 
+		<h3>.[page:Boolean skinning]</h3>
+		<div>Define whether the material uses skinning. Default is *false*.</div>
+	
+		<h3>.[page:Boolean morphTargets]</h3>
+		<div>Define whether the material uses morphTargets. Default is *false*.</div>	
+
+
+		<h3>.[page:Vector3 wrapRGB]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean morphNormals]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean wrapAround]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshNormalMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshNormalMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshNormalMaterial.html	(revision 28897)
@@ -0,0 +1,65 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A material that maps the normal vectors to RGB colors.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo parameters])</h3>
+		<div>
+		parameters is an object with one or more properties defining the material's appearance. 
+		</div>
+		<div>
+		morphTargets -- Define whether the material uses morphTargets. Default is false.<br/>
+		shading --  How the triangles of a curved surface are rendered. Default is [page:Materials THREE.SmoothShading].<br/>
+		wireframe -- Render geometry as wireframe. Default is false (i.e. render as smooth shaded).<br/>
+		wireframeLinewidth -- Controls wireframe thickness. Default is 1.<br/>
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:boolean morphTargets]</h3>
+		<div>Define whether the material uses morphTargets. Default is false.</div> 
+
+		<h3>.[page:number shading]</h3>
+		<div>
+			How the triangles of a curved surface are rendered: as a smooth surface, as flat separate facets, or no shading at all.<br/><br/>
+			Options are [page:Materials THREE.SmoothShading] (default), [page:Materials THREE.FlatShading]
+		</div>
+		
+
+		<h3>.[page:boolean wireframe]</h3>
+		<div>
+			Render geometry as wireframe. Default is false (i.e. render as smooth shaded). 
+		</div> 
+		
+		<h3>.[page:number wireframeLinewidth]</h3>
+		<div>
+			Controls wireframe thickness. Default is 1.<br/><br/>
+			Due to limitations in the ANGLE layer, on Windows platforms linewidth will always be 1 regardless of the set value.
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshPhongMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshPhongMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/MeshPhongMaterial.html	(revision 28897)
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A material for shiny surfaces, evaluated per pixel.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo parameters])</h3>
+		<div>
+		parameters -- A JSON object with settable parameters.
+		</div>
+		<div>
+		Example:<br>
+		materials.push( new THREE.MeshPhongMaterial( { ambient: 0x030303, color: 0xdddddd, specular: 0x009900, shininess: 30, shading: THREE.FlatShading } ) );
+	
+		</div>
+
+
+		<h2>Properties</h2>
+		<div>See the base [page:Material] class for common parameters.</div>
+
+		<h3>.[page:Color color]</h3>
+		<div>
+		Diffuse color of the material. Default is white.<br />
+		</div>
+
+		<h3>.[page:Color ambient]</h3>
+		<div>
+		Ambient color of the material, multiplied by the color of the [page:AmbientLight]. Default is white.<br />
+		</div>
+
+		<h3>.[page:Color emissive]</h3>
+		<div>
+		Emissive (light) color of the material, essentially a solid color unaffected by other lighting. Default is black.<br />
+		</div>
+
+		<h3>.[page:Color specular]</h3>
+		<div>
+		Specular color of the material, i.e., how shiny the material is and the color of its shine. Setting this the same color as the diffuse value (times some intensity) makes the material more metallic-looking; setting this to some gray makes the material look more plastic. Default is dark gray.<br />
+		</div>
+
+		<h3>.[page:Float shininess]</h3>
+		<div>How shiny the specular highlight is; a higher value gives a sharper highlight. Default is *30*.</div>
+
+		<h3>.[page:Integer shading]</h3>
+		<div>How the triangles of a curved surface are rendered: as a smooth surface, as flat separate facets, or no shading at all.</div>
+
+		<div>Options are [page:Materials THREE.SmoothShading] (default), [page:Materials THREE.FlatShading], [page:Materials THREE.NoShading].</div>
+		
+		<h3>.[page:Boolean wireframe]</h3>
+		<div>Whether the triangles' edges are displayed instead of surfaces. Default is *false*.</div>
+		
+		<h3>.[page:Float wireframeLinewidth]</h3>
+		<div>Line thickness for wireframe mode. Default is *1.0*.</div>
+		<div>Due to limitations in the <a href="https://code.google.com/p/angleproject/" target="_blank">ANGLE layer</a>, on Windows platforms linewidth will always be 1 regardless of the set value.</div>
+
+		<h3>.[page:String wireframeLinecap]</h3>
+		<div>Define appearance of line ends. Possible values are "butt", "round" and "square". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>.[page:String wireframeLinejoin]</h3>
+		<div>Define appearance of line joints. Possible values are "round", "bevel" and "miter". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>.[page:Integer vertexColors]</h3>
+		<div>Define how the vertices gets colored. Possible values are THREE.NoColors, THREE.FaceColors and THREE.VertexColors. Default is THREE.NoColors.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+		<h3>.[page:Boolean fog]</h3>
+		<div>Define whether the material color is affected by global fog settings. Default is *true*.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+		<h3>.[page:Texture map]</h3>
+		<div>Set color texture map. Default is null.</div>
+
+		<h3>.[page:Texture lightMap]</h3>
+		<div>Set light map. Default is null.</div>
+
+		<h3>.[page:Texture specularMap]</h3>
+		<div>The specular map value affects both how much the specular surface highlight contributes and how much of the environment map affects the surface. Default is null.</div>
+
+		<h3>.[page:TextureCube envMap]</h3>
+		<div>Set env map. Default is null.</div>
+
+		<h3>.[page:Float reflectivity]</h3>
+		<div>How much the environment map affects the surface; also see "combine".</div> 
+
+		<h3>.[page:Float refractionRatio]</h3>
+		<div>The index of refraction for an environment map using [page:Textures THREE.CubeRefractionMapping]. Default is *0.98*.</div>
+
+		<h3>.[page:Integer combine]</h3>
+		<div>How to combine the result of the surface's color with the environment map, if any.</div> 
+		 
+		<div>Options are [page:Textures THREE.MultiplyOperation] (default), [page:Textures THREE.MixOperation], [page:Textures THREE.AddOperation]. If mix is chosen, the reflectivity is used to blend between the two colors.</div>
+		 
+		<h3>.[page:Boolean skinning]</h3>
+		<div>Define whether the material uses skinning. Default is *false*.</div>
+	
+		<h3>.[page:Boolean morphTargets]</h3>
+		<div>Define whether the material uses morphTargets. Default is *false*.</div>	
+
+
+		<h3>.[page:Vector2 normalScale]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean morphNormals]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean metal]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number bumpScale]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean wrapAround]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean perPixel]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object normalMap]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object bumpMap]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 wrapRGB]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/ParticleSystemMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/ParticleSystemMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/ParticleSystemMaterial.html	(revision 28897)
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">The default material used by [page:ParticleSystem particle] systems.</div>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [page:Object parameters] )</h3>
+
+		<div>parameters is an object with one or more properties defining the material's appearance.</div>
+		
+		<div>
+		color â Particle color in hexadecimal. Default is 0xffffff.<br />
+		map â a [page:Texture texture]. Default is null. // TODO: specify what is its purpose<br />
+		size â Define size of particles. Default is 1.0.<br />
+		sizeAttenuation â Enable/disable size attenuation with distance.<br />
+		vertexColors â Define whether the material uses vertex colors, or not. Default is false.<br />
+		fog â Define whether the material color is affected by global fog settings. Default is true.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Number color]</h3>
+		
+		<div>Sets the color of the particles. Default is 0xffffff.</div>
+		
+		<h3>.[page:Texture map]</h3>
+		
+		<div>Sets the color of the particles using data from a texture (?).</div>
+		
+		<h3>.[page:Number size]</h3>
+		
+		<div>Sets the size of the particles. Default is 1.0.</div>
+
+		<h3>.[page:Boolean sizeAttenuation]</h3>
+		
+		<div>Specify whether particles' size will get smaller with the distance. Default is true.</div>
+
+		<h3>.[page:Boolean vertexColors]</h3>
+
+		<h3>.[page:Boolean fog]</h3>
+		<div>Define whether the material color is affected by global fog settings.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+
+		<h2>Methods</h2>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/ShaderMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/ShaderMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/ShaderMaterial.html	(revision 28897)
@@ -0,0 +1,131 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">Material rendered with custom shaders</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Object parameters])</h3>
+		<div>
+		parameters -- An object containing various parameters setting up shaders and their uniforms.
+		</div>
+		<div>
+		<br>
+		Example:<br>
+		<br>
+		uniforms = {
+			time: { type: "f", value: 1.0 },
+			resolution: { type: "v2", value: new THREE.Vector2() }
+		};
+
+		material = new THREE.ShaderMaterial( {
+
+			uniforms: uniforms,
+			vertexShader: document.getElementById( 'vertexShader' ).textContent,
+			fragmentShader: document.getElementById( 'fragmentShader' ).textContent
+
+		} );
+
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:object uniforms]</h3>
+		<div>
+		Uniforms defined inside GLSL shader code.
+		</div> 
+
+		<h3>.[page:string fragmentShader]</h3>
+		<div>
+		Fragment shader GLSL code.  This is the actual code for the shader.  In the example above the code is retrieved from DOM elements emnbedded directly in the page although other methods can be used including specifying a string directly.  
+		</div> 
+
+		<h3>.[page:string vertexShader]</h3>
+		<div>
+		Vertex shader GLSL code.  This is the actual code for the shader.  In the example above the code is retrieved from DOM elements embedded directly in the page although other methods can be used including specifying a string directly.  
+		</div> 
+
+		<h3>.[page:boolean morphTargets]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean lights]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean morphNormals]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean wireframe]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number vertexColors]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean skinning]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean fog]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object attributes]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number shading]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number linewidth]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number wireframeLinewidth]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object defines]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/SpriteCanvasMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/SpriteCanvasMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/SpriteCanvasMaterial.html	(revision 28897)
@@ -0,0 +1,55 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo parameters])</h3>
+		<div>
+		parameters -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Color color]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.program([page:todo context], [page:todo color]) [page:todo]</h3>
+		<div>
+		context -- todo <br />
+		color -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/SpriteMaterial.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/SpriteMaterial.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/SpriteMaterial.html	(revision 28897)
@@ -0,0 +1,83 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo parameters])</h3>
+		<div>
+		parameters -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Texture map]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector2 uvScale]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean sizeAttenuation]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Color color]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector2 uvOffset]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean fog]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean useScreenCoordinates]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean scaleByViewport]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector2 alignment]</h3>
+		<div>
+		todo
+		</div> 
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/materials/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Box2.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Box2.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Box2.html	(revision 28897)
@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Represents a boundary box in 2D space.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Vector2 min], [page:Vector2 max])</h3>
+		<div>
+		min -- Lower (x, y) boundary of the box.<br />
+		max -- Upper (x, y) boundary of the box.
+		</div>
+		<div>
+		Creates a box bounded by min and max. 
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Vector2 max]</h3>
+		<div>
+		Upper (x, y) boundary of this box.
+		</div> 
+
+		<h3>.[page:Vector2 min]</h3>
+		<div>
+		Lower (x, y) boundary of this box.
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.set([page:Vector2 min], [page:Vector2 max]) [page:Box2 this]</h3>
+		<div>
+		min -- Lower (x, y) boundary of the box. <br />
+		max -- Upper (x, y) boundary of the box.
+		</div>
+		<div>
+		Sets the lower and upper (x, y) boundaries of this box.
+		</div>
+
+		<h3>.expandByPoint([page:Vector2 point]) [page:Box2 this]</h3>
+		<div>
+		point -- Point that should be included in the box.
+		</div>
+		<div>
+		Expands the boundaries of this box to include *point*.
+		</div>
+
+		<h3>.clampPoint([page:Vector2 point], [page:Vector2 optionalTarget]) [page:Vector2]</h3>
+		<div>
+		point -- Position to clamp. <br />
+		optionalTarget -- If specified, the clamped result will be copied here.
+		</div>
+		<div>
+		Clamps *point* within the bounds of this box.
+		</div>
+
+		<h3>.isIntersectionBox([page:Box2 box]) [page:Boolean]</h3>
+		<div>
+		box -- Box to check for intersection against.
+		</div>
+		<div>
+		Determines whether or not this box intersects *box*.
+		</div>
+
+		<h3>.setFromPoints([page:Array points]) [page:Box2 this]</h3>
+		<div>
+		points -- Set of points that the resulting box will envelop.
+		</div>
+		<div>
+		Sets the upper and lower bounds of this box to include all of the points in *points*.
+		</div>
+
+		<h3>.size([page:Vector2 optionalTarget]) [page:Vector2]</h3>
+		<div>
+		optionalTarget -- If specified, the result will be copied here.
+		</div>
+		<div>
+		Returns the width and height of this box.
+		</div>
+
+		<h3>.union([page:Box2 box]) [page:Box2 this]</h3>
+		<div>
+		box -- Box that will be unioned with this box.
+		</div>
+		<div>
+		Unions this box with *box* setting the upper bound of this box to the greater of the 
+		two boxes' upper bounds and the lower bound of this box to the lesser of the two boxes'
+		lower bounds.
+		</div>
+
+		<h3>.getParameter([page:Vector2 point], [page:Vector2 optionalTarget]) [page:Vector2]</h3>
+		<div>
+		point -- [page:Vector2]<br/>
+		optionalTarget -- [page:Vector2]<br/>
+		
+		</div>
+		<div>
+		Returns a point as a proportion of this box's width and height.
+		</div>
+
+		<h3>.expandByScalar([page:float scalar]) [page:Box2 this]</h3>
+		<div>
+		scalar -- Distance to expand.
+		</div>
+		<div>
+		Expands each dimension of the box by *scalar*. If negative, the dimensions of the box </br>
+		will be contracted.
+		</div>
+
+		<h3>.intersect([page:Box2 box]) [page:Box2 this]</h3>
+		<div>
+		box -- Box to intersect with.
+		</div>
+		<div>
+		Returns the intersection of this and *box*, setting the upper bound of this box to the lesser </br>
+		of the two boxes' upper bounds and the lower bound of this box to the greater of the two boxes' </br>
+		lower bounds.
+		</div>
+
+		<h3>.containsBox([page:Box2 box]) [page:Boolean]</h3>
+		<div>
+		box -- Box to test for inclusion.
+		</div>
+		<div>
+		Returns true if this box includes the entirety of *box*. If this and *box* overlap exactly,</br>
+		this function also returns true. 
+		</div>
+
+		<h3>.translate([page:Vector2 offset]) [page:Box2 this]</h3>
+		<div>
+		offset -- Direction and distance of offset.
+		</div>
+		<div>
+		Adds *offset* to both the upper and lower bounds of this box, effectively moving this box </br>
+		*offset* units in 2D space.
+		</div>
+
+		<h3>.empty() [page:Boolean]</h3>
+		<div>
+		Returns true if this box includes zero points within its bounds.</br>
+		Note that a box with equal lower and upper bounds still includes one point, the
+		one both bounds share.
+		</div>
+
+		<h3>.clone() [page:Box2]</h3>
+		<div>
+		Returns a copy of this box.
+		</div>
+
+		<h3>.equals([page:Box2 box]) [page:Boolean]</h3>
+		<div>
+		box -- Box to compare.
+		</div>
+		<div>
+		Returns true if this box and *box* share the same lower and upper bounds.
+		</div>
+
+		<h3>.expandByVector([page:Vector2 vector]) [page:Box2 this]</h3>
+		<div>
+		vector -- Amount to expand this box in each dimension.
+		</div>
+		<div>
+		Expands this box equilaterally by *vector*. The width of this box will be
+		expanded by the x component of *vector* in both directions. The height of 
+		this box will be expanded by the y component of *vector* in both directions.
+		</div>
+
+		<h3>.copy([page:Box2 box]) [page:Box2 this]</h3>
+		<div>
+		box -- Box to copy.
+		</div>
+		<div>
+		Copies the values of *box* to this box.
+		</div>
+
+		<h3>.makeEmpty() [page:Box2 this]</h3>
+		<div>
+		Makes this box empty.
+		</div>
+
+		<h3>.center([page:Vector2 optionalTarget]) [page:Vector2]</h3>
+		<div>
+		optionalTarget -- If specified, the result will be copied here.
+		</div>
+		<div>
+		Returns the center point of this box.
+		</div>
+
+		<h3>.distanceToPoint([page:Vector2 point]) [page:Float]</h3>
+		<div>
+		point -- Point to measure distance to.
+		</div>
+		<div>
+		Returns the distance from any edge of this box to the specified point. </br>
+		If the point lies inside of this box, the distance will be 0.
+		</div>
+
+		<h3>.containsPoint([page:Vector2 point]) [page:Boolean]</h3>
+		<div>
+		point -- Point to check for inclusion.
+		</div>
+		<div>
+		Returns true if the specified point lies within the boundaries of this box.
+		</div>
+
+		<h3>.setFromCenterAndSize([page:Vector2 center], [page:Vector2 size]) [page:Box2 this]</h3>
+		<div>
+		center -- Desired center position of the box. <br />
+		size -- Desired x and y dimensions of the box. 
+		</div>
+		<div>
+		Centers this box on *center* and sets this box's width and height to the values specified
+		in *size*.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Box3.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Box3.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Box3.html	(revision 28897)
@@ -0,0 +1,235 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Represents a boundary box in 3d space.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Vector3 min], [page:Vector3 max])</h3>
+		<div>
+		min -- Lower (x, y, z) boundary of the box.  <br />
+		max -- Upper (x, y, z) boundary of the box.
+		</div>
+		<div>
+		Creates a box bounded by min and max
+		</div>
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Vector3 max]</h3>
+		<div>
+		Upper (x, y, z) boundary of this box.
+		</div> 
+
+		<h3>.[page:Vector3 min]</h3>
+		<div>
+		Lower (x, y, z) boundary of this box.
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.set([page:Vector3 min], [page:Vector3 max]) [page:Box3 this]</h3>
+		<div>
+		min -- Upper (x, y, z) boundary of this box. <br />
+		max -- Lower (x, y, z) boundary of this box.
+		</div>
+		<div>
+		Sets the lower and upper (x, y, z) boundaries of this box.
+		</div>
+
+		<h3>.applyMatrix4([page:Matrix4 matrix]) [page:Box3 this]</h3>
+		<div>
+		matrix -- [page:Matrix4].
+		</div>
+		<div>
+		Transform the box with a Matrix4.
+		</div>
+
+		<h3>.expandByPoint([page:Vector3 point]) [page:Box3 this]</h3>
+		<div>
+		Expands the box outwards by the point. It takes the min and max values of the box.
+		</div>
+
+		<h3>.clampPoint([page:Vector3 point], [page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		point -- [page:Vector3] <br />
+		optionalTarget -- [page:Vector3] An optional target point.
+		</div>
+		<div>
+		Clamp a point within the min and max boundaries of the box. Returns either a new point or the modified passed target.
+		</div>
+
+		<h3>.isIntersectionBox([page:Box3 box]) [page:Boolean]</h3>
+		<div>
+		box -- [page:Box3]
+		</div>
+		<div>
+		Return whether or not the passed box intersects with this Box3.
+		</div>
+
+		<h3>.setFromPoints([page:Array points])</h3>
+		<div>
+		points -- [page:Array] of [page:Vector3] points
+		</div>
+		<div>
+		Sets this box to the min value and max value of all the points in the array. If the array is empty, then it sets the box as empty.
+		</div>
+
+		<h3>.size([page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		optionalTarget -- [page:Vector3] (optional)
+		</div>
+		<div>
+		Returns either a new Vector3 or the target Vector3 that represents the size of the box from the min point to max point.
+		</div>
+
+		<h3>.union([page:Box3 box]) [page:Box3 this]</h3>
+		<div>
+		box -- [page:Box3]
+		</div>
+		<div>
+		Sets the box's min and max boundaries to union of the two boxes.
+		</div>
+
+		<h3>.getParameter([page:Vector3 point], [page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		point -- [page:Vector3]<br/>
+		optionalTarget -- [page:Vector3]
+		</div>
+		<div>
+		Returns a point as a proportion of this box's width, height, and depth. If the point is contained by the box the x, y, and z of the returned vector will be between 0 and 1.
+		</div>
+
+		<h3>.expandByScalar([page:Float scalar]) [page:Box3 this]</h3>
+		<div>
+		scalar -- [page:Float] representing the distance to expand
+		</div>
+		<div>
+		Expands or contracts the box by adding the scalar value to the box's min and max vectors.
+		</div>
+
+		<h3>.intersect([page:Box3 box]) [page:Box3 this]</h3>
+		<div>
+		box -- [page:Box3]
+		</div>
+		<div>
+		Returns the intersection of the two boxes, setting the upper bound of this box to the lesser of the two boxes' upper bounds and the lower bound of this box to the greater of the two boxes' lower bounds.
+ 
+		</div>
+
+		<h3>.containsBox([page:Box3 box]) [page:Boolean]</h3>
+		<div>
+		box -- [page:Box3]
+		</div>
+		<div>
+		Returns true if this box includes the entirety of box. If this and box overlap exactly, this function also returns true.
+ 
+		</div>
+
+		<h3>.translate([page:Vector3 offset]) [page:Box3 this]</h3>
+		<div>
+		offset -- [page:Vector3]
+		</div>
+		<div>
+		Translates the box the distance of the offset.
+		</div>
+
+		<h3>.empty() [page:Boolean]</h3>
+		<div>
+		Checks to see if the box is empty. If the lower and upper bounds of the box are equal, this function still returns false as the box would still contain one point.
+		</div>
+
+		<h3>.clone() [page:Box3]</h3>
+		<div>
+		Returns a copy of this box.
+		</div>
+
+		<h3>.equals([page:Box3 box]) [page:Boolean]</h3>
+		<div>
+		box -- [page:Box3]
+		</div>
+		<div>
+		Returns true if the boes share the same lower and upper bounds. 
+		</div>
+
+		<h3>.expandByVector([page:Vector3 vector]) [page:Box3 this]</h3>
+		<div>
+		vector -- [page:Vector3] representing the amount to expand this box in each dimension.
+		</div>
+		<div>
+		Adds the passed vector to the upper boundary, and subtracts it from the lower boundary.
+		</div>
+
+		<h3>.copy([page:Box3 box]) [page:Box3 this]</h3>
+		<div>
+		box -- [page:Box3] to copy.
+		</div>
+		<div>
+		Copies the values of the passed box to this box.
+		</div>
+
+		<h3>.makeEmpty() [page:Box3 this]</h3>
+		<div>
+		Makes this box empty so that it cannot contain any points.
+		</div>
+
+		<h3>.center([page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		optionalTarget -- [page:Vector3] If specified, the result will be copied here.
+		</div>
+		<div>
+		Returns the center point of this box. 
+		</div>
+
+		<h3>.getBoundingSphere([page:Sphere optionalTarget]) [page:Sphere]</h3>
+		<div>
+		optionalTarget -- [page:Sphere]
+		</div>
+		<div>
+		Returns a bounding sphere.
+		</div>
+
+		<h3>.distanceToPoint([page:Vector3 point]) [page:Float]</h3>
+		<div>
+		point -- [page:Vector3]
+		</div>
+		<div>
+		Returns the distance from any edge of this box to the specified point. If the point lies inside of this box, the distance will be 0.
+		</div>
+
+		<h3>.containsPoint([page:Vector3 point]) [page:Boolean]</h3>
+		<div>
+		point -- [page:Vector3]
+		</div>
+		<div>
+		Returns true if the specified point lies within the boundaries of this box.
+		</div>
+
+		<h3>.setFromCenterAndSize([page:Vector3 center], [page:Vector3 size]) [page:Box3 this]</h3>
+		<div>
+		center -- [page:Vector3] representing the center position.<br />
+		size -- [page:Vector3] representing the dimensions of the box.
+		</div>
+		<div>
+		Sets this box based on a center vector, and width, height, and depth vector.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Color.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Color.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Color.html	(revision 28897)
@@ -0,0 +1,200 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">
+		Represents a color.
+		</div>
+
+
+		<h2>Example</h2>
+		<code>var color = new THREE.Color();</code>
+		<code>var color = new THREE.Color( 0xff0000 );</code>
+		<code>var color = new THREE.Color("rgb(255,0,0)");</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( value )</h3>
+		<div>
+		value â optional argument that sets initial color.  Can be a hexadecimal or a CSS-style string, for example, "rgb(250, 0,0)", "rgb(100%,0%,0%)", "#ff0000", "#f00", or "red"
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float r]</h3>
+		<div>
+		Red channel value between 0 and 1. Default is 1.
+		</div>
+
+		<h3>.[page:Float g]</h3>
+		<div>
+		Green channel value between 0 and 1. Default is 1.
+		</div>
+
+		<h3>.[page:Float b]</h3>
+		<div>
+		Blue channel value between 0 and 1. Default is 1.
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>.copy( [page:Color color] ) [page:Color this]</h3>
+		<div>
+		color â Color to copy.
+		</div>
+		<div>
+		Copies given color.
+		</div>
+
+		<h3>.copyGammaToLinear( [page:Color color] ) [page:Color this]</h3>
+		<div>
+		color â Color to copy.
+		</div>
+		<div>
+		Copies given color making conversion from gamma to linear space.
+		</div>
+
+		<h3>.copyLinearToGamma( [page:Color color] ) [page:Color this]</h3>
+		<div>
+		color â Color to copy.
+		</div>
+		<div>
+		Copies given color making conversion from linear to gamma space.
+		</div>
+
+		<h3>.convertGammaToLinear() [page:Color this]</h3>
+		<div>
+		Converts this color from gamma to linear space.
+		</div>
+
+		<h3>.convertLinearToGamma() [page:Color this]</h3>
+		<div>
+		Converts this color from linear to gamma space.
+		</div>
+
+		<h3>.setRGB( [page:Float r], [page:Float g], [page:Float b] ) [page:Color this]</h3>
+		<div>
+		r â Red channel value between 0 and 1.<br />
+		g â Green channel value between 0 and 1.<br />
+		b â Blue channel value between 0 and 1.
+		</div>
+		<div>
+		Sets this color from RGB values.
+		</div>
+
+		<h3>.getHex() [page:Integer]</h3>
+		<div>
+		Returns the hexadecimal value of this color.
+		</div>
+
+		<h3>.getHexString() [page:String]</h3>
+		<div>
+		Returns the string formated hexadecimal value of this color.
+		</div>
+
+		<h3>.setHex( [page:Integer hex] ) [page:Color this]</h3>
+		<div>
+		hex â Color in hexadecimal.<br />
+		</div>
+		<div>
+		Sets this color from a hexadecimal value.
+		</div>
+
+		<h3>.setStyle( [page:String style] ) [page:Color this]</h3>
+		<div>
+		style â color as a CSS-style string, for example, "rgb(250, 0,0)", "rgb(100%,0%,0%)", "#ff0000", "#f00", or "red"
+		</div>
+		<div>
+		Sets this color	from a CSS-style string.
+		</div>
+
+		<h3>.getStyle() [page:String]</h3>
+		<div>
+		Returns the value of this color as a CSS-style string. Example: rgb(255,0,0)
+		</div>
+
+		<h3>.setHSL( [page:Float h], [page:Float s], [page:Float l] ) [page:Color this]</h3>
+		<div>
+		h â hue value between 0.0 and 1.0 <br />
+		s â saturation value between 0.0 and 1.0 <br />
+		l â lightness value between 0.0 and 1.0
+		</div>
+		<div>
+		Sets color from hsl
+		</div>
+
+		<h3>.getHSL() [page:Object hsl]</h3>
+		<div>
+		Returns an object with properties h, s, and l.
+		</div>
+
+		<h3>.offsetHSL( [page:Float h], [page:Float s], [page:Float l] ) [page:Color this]</h3>
+		<div>
+		Adds given h, s, and l to this color's existing h, s, and l values.
+		</div>
+
+		<h3>.add ( [page:Color color] ) [page:Color this]</h3>
+		<div>
+		Adds rgb values of given color to rgb values of this color
+		</div>
+
+		<h3>.addColors( [page:Color color1], [page:Color color2] ) [page:Color this]</h3>
+		<div>
+		Sets this color to the sum of color1 and color2
+		</div>
+
+		<h3>.addScalar( [page:Number s] ) [page:Color this]</h3>
+		<div>
+		Adds s to the rgb values of this color
+		</div>
+
+		<h3>.multiply( [page:Color color] ) [page:Color this]</h3>
+		<div>
+		Multiplies this color's rgb values by given color's rgb values
+		</div>
+
+		<h3>.multiplyScalar( [page:Number s] ) [page:Color this]</h3>
+		<div>
+		Multiplies this color's rgb values by s
+		</div>
+
+		<h3>.lerp( [page:Color color], alpha ) [page:Color this]</h3>
+		<div>
+		alpha -- a number between 0 and 1.
+		</div>
+		<div>
+		Linear interpolation of this colors rgb values and the rgb values of the first argument. The alpha argument can be thought of as the percent between the two colors, where 0 is this color and 1 is the first argument.
+		</div>
+
+		<h3>.equals( [page:Color c] ) [page:Color this]</h3>
+		<div>
+		Compares this color and c and returns true if they are the same, false otherwise.
+		</div>
+
+		<h3>.clone() [page:Color]</h3>
+		<div>
+		Clones this color.
+		</div>
+
+		<h3>.set( value ) [page:Color this]</h3>
+		<div>
+		value -- either an instance of [page:Color], a [page:Integer hexadecimal] value, or a css style [page:String string]
+		</div>
+		<div>
+		Delegates to .copy, .setStyle, or .setHex depending on input type.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Euler.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Euler.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Euler.html	(revision 28897)
@@ -0,0 +1,127 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <script src="../../list.js"></script>
+    <script src="../../page.js"></script>
+    <link type="text/css" rel="stylesheet" href="../../page.css" />
+  </head>
+  <body>
+    <h1>[name]</h1>
+
+    <div class="desc">Euler Angles. <br/><br/>
+    
+    Euler angles describe a rotation transformation by rotating an object on its various axes in specified amounts per axis, and a specified axis order.
+    (More information on <a href='http://en.wikipedia.org/wiki/Euler_angles' target='blank'>Wikipedia</a>)</div>
+
+    <h2>Example</h2>
+
+    <code>var a = new THREE.Euler( 0, 1, 1.57, 'XYZ' );
+    var b = new THREE.Vector3( 1, 0, 1 );
+    b.applyEuler(a);
+    </code>
+
+
+    <h2>Constructor</h2>
+
+
+    <h3>[name]( [page:Float x], [page:Float y], [page:Float z], [page:String order] )</h3>
+    <div>
+    x -- [page:Float] the angle of the x axis in radians<br />
+    y -- [page:Float] the angle of the y axis in radians<br />
+    z -- [page:Float] the angle of the z axis in radians<br />
+    order -- [page:String] A string representing the order that the rotations are applied, defaults to 'XYZ' (must be upper case).
+    </div>
+    <div>
+    A euler angle for transforming
+    </div>
+
+
+    <h2>Properties</h2>
+
+    <h3>.[page:Float x]</h3>
+
+    <h3>.[page:Float y]</h3>
+
+    <h3>.[page:Float z]</h3>
+
+    <h3>.[page:String order]</h3>
+
+
+
+    <h2>Methods</h2>
+
+    <h3>.set( [page:Float x], [page:Float y], [page:Float z], [page:String order] ) [page:Euler this]</h3>
+    <div>
+    x -- [page:Float] Angle in x axis in radians<br />
+    x -- [page:Float] Angle in x axis in radians<br />
+    x -- [page:Float] Angle in x axis in radians<br />
+    order -- [page:string] Order of axes, defaults to 'XYZ' (must be upper case)
+    </div>
+    <div>
+    Sets the angles of this euler transform.
+    </div>
+
+    <h3>.copy( [page:Euler euler] ) [page:Euler this]</h3>
+    <div>
+    Copies value of *euler* to this euler.
+    </div>
+
+    <h3>.setFromRotationMatrix( [page:Matrix4 m], [page:String order] ) [page:Euler this]</h3>
+    <div>
+    m -- [page:Matrix4] assumes upper 3x3 of matrix is a pure rotation matrix (i.e. unscaled)<br />
+    order -- [page:string] Order of axes, defaults to 'XYZ' (must be upper case)
+    </div>
+    <div>
+    Sets the angles of this euler transform from a pure rotation matrix based on the orientation specified by order.
+    </div>
+
+    <h3>.setFromQuaternion( [page:Quaternion q], [page:String order] ) [page:Euler this]</h3>
+    <div>
+    q -- [page:Quaternion] quaternion must be normalized<br />
+    order -- [page:string] Order of axes, defaults to 'XYZ' (must be upper case)
+    </div>
+    <div>
+    Sets the angles of this euler transform from a normalized quaternion based on the orientation specified by order.
+    </div>
+
+    <h3>.reorder( [page:String newOrder] ) [page:Euler this]</h3>
+    <div>
+    Resets the euler angle with a new order by creating a quaternion from this euler angle and then setting this euler angle with the quaternion and the new order. <br />
+    WARNING: this discards revolution information.
+    </div>
+
+    <h3>.fromArray([page:Array array]) [page:Euler this]</h3>
+    <div>
+    array -- [page:Array] of length 3 or 4. array[3] is an optional order argument.
+    </div>
+    <div>
+    Assigns this euler's x angle to array[0]. <br />
+    Assigns this euler's y angle to array[1]. <br />
+    Assigns this euler's z angle to array[2]. <br />
+    Optionally assigns this euler's order to array[3].
+    </div>
+
+    <h3>.toArray() [page:Array]</h3>
+    <div>
+    Returns an array [x, y, z, order].
+    </div>
+
+    <h3>.equals( [page:Euler euler] ) [page:Boolean]</h3>
+    <div>
+    Checks for strict equality of this euler and *euler*.
+    </div>
+
+    <h3>.clone() [page:Euler]</h3>
+    <div>
+    Returns a new euler created from this euler.
+    </div>
+
+
+
+
+    <h2>Source</h2>
+
+    [link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+  </body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Frustum.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Frustum.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Frustum.html	(revision 28897)
@@ -0,0 +1,95 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc"><a href="http://en.wikipedia.org/wiki/Frustum">Frustums</a> are used to determine what is inside the camera's field of view. They help speed up the rendering process.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Plane p0], [page:Plane p1], [page:Plane p2], [page:Plane p3], [page:Plane p4], [page:Plane p5])</h3>
+		<div>
+		p0 -- [page:Plane] <br />
+		p1 -- [page:Plane] <br />
+		p2 -- [page:Plane] <br />
+		p3 -- [page:Plane] <br />
+		p4 -- [page:Plane] <br />
+		p5 -- [page:Plane]
+		</div>
+		<div>
+		Creates a frustum from the designated planes.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Array planes]</h3>
+		<div>
+		Array of 6 [page:Plane planes].
+		</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.setFromMatrix( [page:Matrix4 matrix] )</h3>
+
+		<h3>.intersectsObject( [page:Object3D object] ) [page:Boolean]</h3>
+		<div>
+		Checks whether the object is inside the Frustum.
+		</div>
+
+		<h3>.clone() [page:Frustum]</h3>
+		<div>
+		Return a copy of this Frustum
+		</div>
+
+		<h3>.set([page:Plane p0], [page:Plane p1], [page:Plane p2], [page:Plane p3], [page:Plane p4], [page:Plane p5]) [page:Boolean]</h3>
+		<div>
+		p0 -- [page:Plane] <br />
+		p1 -- [page:Plane] <br />
+		p2 -- [page:Plane] <br />
+		p3 -- [page:Plane] <br />
+		p4 -- [page:Plane] <br />
+		p5 -- [page:Plane]
+		</div>
+		<div>
+		Sets the current frustum from the passed planes. No plane order is implicitely implied.
+		</div>
+
+		<h3>.copy([page:Frustum frustum]) [page:Frustum this]</h3>
+		<div>
+		frustum -- The frustum to copy
+		</div>
+		<div>
+		Copies the values of the passed frustum.
+		</div>
+
+		<h3>.containsPoint([page:Vector3 point]) [page:Boolean]</h3>
+		<div>
+		point -- [page:Vector3] to test
+		</div>
+		<div>
+		Checks to see if the frustum contains the point.
+		</div>
+
+		<h3>.intersectsSphere([page:Sphere sphere]) [page:Boolean]</h3>
+		<div>
+		sphere -- [page:Sphere]
+		</div>
+		<div>
+		Check to see if the sphere intersects with the frustum.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Line3.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Line3.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Line3.html	(revision 28897)
@@ -0,0 +1,136 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A geometric line segment represented by a start and end point.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Vector3 start], [page:Vector3 end])</h3>
+		<div>
+		start -- [page:Vector3] Start of the line segment<br />
+		end -- [page:Vector3] End of the line segment
+		</div>
+		<div>
+		The start and end vectors default to origin vectors if none are set.
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Vector3 start]</h3>
+
+		<h3>.[page:Vector3 end]</h3>
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.set([page:Vector3 start], [page:Vector3 end]) [page:Line3]</h3>
+		<div>
+		start -- [page:Vector3] <br />
+		end -- [page:Vector3]
+		</div>
+		<div>
+		Sets the start and end values by copying the provided vectors.
+		</div>
+
+		<h3>.copy([page:Line3 line]) [page:Line3]</h3>
+		<div>
+		line -- [page:Line3]
+		</div>
+		<div>
+		Copies the passed line's start and end vectors to this line.
+		</div>
+
+		<h3>.clone() [page:Line3]</h3>
+		<div>
+		Return a new copy of this [page:Line3].
+		</div>
+
+		<h3>.equals([page:Line3 line]) [page:Boolean]</h3>
+		<div>
+		line -- [page:Line3]
+		</div>
+		<div>
+
+		<h3>.distance() [page:Float]</h3>
+		<div>
+		Returns the length of the line segment.
+		</div>
+		Returns true if both line's start and end points are equal.
+		</div>
+		
+		<h3>.distanceSq() [page:Float]</h3>
+		<div>
+		Returns the line segment's length squared.
+		</div>
+
+		<h3>.applyMatrix4([page:Matrix4 matrix]) [page:Line3 this]</h3>
+		<div>
+		matrix -- [page:Matrix4]
+		</div>
+		<div>
+		Apply a matrix transform to the line segment.
+		</div>
+
+		<h3>.at([page:Float t], [page:Vector3 optionalTarget]) [page:Vector]</h3>
+		<div>
+		t -- [page:Float] Use values 0-1 to return a result on the line segment. <br />
+		optionalTarget -- [page:Vector] Optional target to set the result.
+		</div>
+		<div>
+		Return a vector at a certain position along the line. When t = 0, it returns the start vector, and when t=1 it returns the end vector.
+		</div>
+		
+		<h3>.center([page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		optionalTarget -- [page:Vector3] Optional target to set the result.
+		</div>
+		<div>
+		Return the center of the line segment.
+		</div>
+
+		<h3>.delta([page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		optionalTarget -- [page:Vector3] Optional target to set the result.
+		</div>
+		<div>
+		Returns the delta vector of the line segment, or the end vector minus the start vector.
+		</div>
+
+		<h3>.closestPointToPoint([page:Vector3 point], [page:Boolean clampToLine], [page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		point -- [page:Vector3] <br />
+		clampToLine -- [page:Boolean] <br />
+		optionalTarget -- [page:Vector3] Optional target to set the result.
+		</div>
+		<div>
+		Returns the closets point on the line. If clamp to line is true, then the returned value will be clamped to the line segment.
+		</div>
+
+		<h3>.closestPointToPointParameter([page:Vector3 point], [page:Boolean clampToLine]) [page:Float]</h3>
+		<div>
+		point -- [page:Vector3] <br />
+		clampToLine -- [page:Boolean]
+		</div>
+		<div>
+		Returns a point parameter based on the closest point as projected on the line segement. If clamp to line is true, then the returned value will be between 0 and 1.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Math.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Math.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Math.html	(revision 28897)
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Math utility functions</div>
+
+		<h2>Properties</h2>
+
+		<h2>Methods</h2>
+
+		<h3>.clamp( [page:Float x], [page:Float a], [page:Float b] ) [page:Float]</h3>
+		<div>
+		x â Value to be clamped.<br />
+		a â Minimum value<br />
+		b â Maximum value.
+		</div>
+		<div>
+		Clamps the *x* to be between *a* and *b*.
+		</div>
+
+		<h3>.clampBottom( [page:Float x], [page:Float a] ) [page:Float]</h3>
+		<div>
+		x â Value to be clamped.<br />
+		a â Minimum value
+		</div>
+		<div>
+		Clamps the *x* to be larger than *a*.
+		</div>
+
+		<h3>.mapLinear( [page:Float x], [page:Float a1], [page:Float a2], [page:Float b1], [page:Float b2] ) [page:Float]</h3>
+		<div>
+		x â Value to be mapped.<br />
+		a1 â Minimum value for range A.<br />
+		a2 â Maximum value for range A.<br />
+		b1 â Minimum value for range B.<br />
+		b2 â Maximum value for range B.
+		</div>
+		<div>
+		Linear mapping of *x* from range [*a1*, *a2*] to range [*b1*, *b2*].
+		</div>
+
+		<h3>.random16() [page:Float]</h3>
+		<div>
+		Random float from 0 to 1 with 16 bits of randomness.<br />
+		Standard Math.random() creates repetitive patterns when applied over larger space.
+		</div>
+
+		<h3>.randInt( [page:Integer low], [page:Integer high] ) [page:Integer]</h3>
+		<div>
+		Random integer from *low* to *high* interval.
+		</div>
+
+		<h3>.randFloat( [page:Float low], [page:Float high] ) [page:Float]</h3>
+		<div>
+		Random float from *low* to *high* interval.
+		</div>
+
+		<h3>.randFloatSpread( [page:Float range] ) [page:Float]</h3>
+		<div>
+		Random float from *- range / 2* to *range / 2* interval.
+		</div>
+
+		<h3>.sign( [page:Float x] ) [page:Float]</h3>
+		<div>
+		Returns -1 if *x* is less than 0, 1 if *x* is greater than 0, and 0 if *x* is zero.
+		</div>
+
+		<h3>.degToRad([page:Float degrees]) [page:Float]</h3>
+		<div>
+		degrees -- [page:Float]
+		</div>
+		<div>
+		Converts degrees to radians.
+		</div>
+
+		<h3>.radToDeg([page:Float radians]) [page:Float]</h3>
+		<div>
+		radians -- [page:Float]
+		</div>
+		<div>
+		Converts radians to degrees
+		</div>
+
+		<h3>.smoothstep([page:Float x], [page:Float min], [page:Float max]) [page:Float]</h3>
+		<div>
+		x -- The value to evaluate based on its position between min and max. <br />
+		min -- Any x value below min will be 0 <br />
+		max -- Any x value above max will be 1
+		</div>
+		<div>
+		Returns a value between 0-1 that represents the percentage that x has moved between min and max, but smoothed or slowed down the closer X is to the min and max.<br/><br/>
+		
+		[link:http://en.wikipedia.org/wiki/Smoothstep Wikipedia]
+		</div>
+
+		<h3>.smootherstep([page:Float x], [page:Float min], [page:Float max]) [page:Float]</h3>
+		<div>
+		x -- The value to evaluate based on its position between min and max. <br />
+		min -- Any x value below min will be 0 <br />
+		max --  Any x value above max will be 1
+		</div>
+		<div>
+		Returns a value between 0-1. It works the same as smoothstep, but more smooth.
+		</div>
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Matrix3.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Matrix3.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Matrix3.html	(revision 28897)
@@ -0,0 +1,146 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A 3x3 matrix.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Float n11], [page:Float n12], [page:Float n13], [page:Float n21], [page:Float n22], [page:Float n23], [page:Float n31], [page:Float n32], [page:Float n33])</h3>
+		<div>
+		n11 -- [page:Float] <br />
+		n12 -- [page:Float] <br />
+		n13 -- [page:Float] <br />
+		n21 -- [page:Float] <br />
+		n22 -- [page:Float] <br />
+		n23 -- [page:Float] <br />
+		n31 -- [page:Float] <br />
+		n32 -- [page:Float] <br />
+		n33 -- [page:Float]
+		</div>
+		<div>
+		Initialize the 3x3 matrix with a row-major sequence of values.<br/><br/>
+		
+		n11, n12, n13,<br/>
+		n21, n22, n23,<br/>
+		n31, n32, n33<br/><br/>
+		
+		If no values are sent the matrix will be initialized as an identity matrix.
+		</div>
+
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float32Array elements]</h3>
+		<div>
+		Float32Array with column-major matrix values.
+		</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.transpose() [page:Matrix3]</h3>
+		<div>
+		Transposes this matrix in place.
+		</div>
+
+		<h3>.transposeIntoArray( [page:Array array] ) [page:Matrix3]</h3>
+		<div>
+		array -- [page:Array] <br />
+		</div>
+		<div>
+		Transposes this matrix into the supplied array, and returns itself.
+		</div>
+
+
+		<h3>.determinant() [page:Float]</h3>
+		<div>
+		Returns the matrix's determinant.
+		</div>
+
+		<h3>.set([page:Float n11], [page:Float n12], [page:Float n13], [page:Float n21], [page:Float n22], [page:Float n23], [page:Float n31], [page:Float n32], [page:Float n33]) [page:Matrix3 this]</h3>
+		<div>
+		n11 -- [page:Float] <br />
+		n12 -- [page:Float] <br />
+		n13 -- [page:Float] <br />
+		n21 -- [page:Float] <br />
+		n22 -- [page:Float] <br />
+		n23 -- [page:Float] <br />
+		n31 -- [page:Float] <br />
+		n32 -- [page:Float] <br />
+		n33 -- [page:Float]
+		</div>
+		<div>
+		Set the 3x3 matrix values to the given row-major sequence of values.
+		</div>
+
+		<h3>.multiplyScalar([page:Float scalar]) [page:Matrix3 this]</h3>
+		<div>
+		scalar -- [page:Float]
+		</div>
+		<div>
+		Multiply every component of the matrix by a scalar value.
+		</div>
+
+		<h3>.multiplyVector3Array([page:Array array]) [page:Array]</h3>
+		<div>
+		array -- An array in the form [vector1x, vector1y, vector1z, vector2x, vector2y, vector2z, ...]
+		</div>
+		<div>
+		Multiply (apply) this matrix against every vector3 in the array.
+		</div>
+
+		<h3>.getNormalMatrix([page:Matrix4 matrix4]) [page:Matrix3 this]</h3>
+		<div>
+		matrix4 -- [page:Matrix4]
+		</div>
+		<div>
+		Set this matrix as the normal matrix of the passed [page:Matrix4 matrix4]. The normal matrix is the inverse transpose of the matrix.
+		</div>
+
+		<h3>.getInverse([page:Matrix4 matrix4], [page:Boolean throwOnInvertible]) [page:Matrix3 this]</h3>
+		<div>
+		matrix4 -- [page:Matrix4] <br />
+		throwOnInvertible -- [Page:Boolean] If true, throw an error if the matrix is invertible.
+		</div>
+		<div>
+		Set this matrix to the inverse of the passed matrix.
+		</div>
+
+		<h3>.copy([page:Matrix3 matrix]) [page:Matrix3 this]</h3>
+		<div>
+		matrix -- [page:Matrix3]
+		</div>
+		<div>
+		Copy the values of the passed matrix.
+		</div>
+
+		<h3>.clone() [page:Matrix3]</h3>
+		<div>
+		Create a copy of the matrix.
+		</div>
+
+		<h3>.identity() [page:Matrix3 this]</h3>
+		<div>
+		Set as an identity matrix.<br/><br/>
+		
+		1, 0, 0<br/>
+		0, 1, 0<br/>
+		0, 0, 1<br/>
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Matrix4.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Matrix4.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Matrix4.html	(revision 28897)
@@ -0,0 +1,249 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A 4x4 Matrix.</div>
+
+
+		<h2>Example</h2>
+
+		<code>// Simple rig for rotating around 3 axes
+
+		var m = new THREE.Matrix4();
+
+		var m1 = new THREE.Matrix4();
+		var m2 = new THREE.Matrix4();
+		var m3 = new THREE.Matrix4();
+
+		var alpha = 0;
+		var beta = Math.PI;
+		var gamma = Math.PI/2;
+
+		m1.makeRotationX( alpha );
+		m2.makeRotationY( beta );
+		m3.makeRotationZ( gamma );
+
+		m.multiplyMatrices( m1, m2 );
+		m.multiply( m3 );
+		</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Float n11], [page:Float n12], [page:Float n13], [page:Float n14], [page:Float n21], [page:Float n22], [page:Float n23], [page:Float n24], [page:Float n31], [page:Float n32], [page:Float n33], [page:Float n34], [page:Float n41], [page:Float n42], [page:Float n43], [page:Float n44] )</h3>
+
+		<div>
+		Initialises the matrix with the supplied row-major values n11..n44, or just creates an identity matrix if no values are passed.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float32Array elements]</h3>
+		<div>A column-major list of matrix values.</div>
+
+		<h2>Methods</h2>
+
+		<h3>.set( [page:Float n11], [page:Float n12], [page:Float n13], [page:Float n14], [page:Float n21], [page:Float n22], [page:Float n23], [page:Float n24], [page:Float n31], [page:Float n32], [page:Float n33], [page:Float n34], [page:Float n41], [page:Float n42], [page:Float n43], [page:Float n44] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets all fields of this matrix to the supplied row-major values n11..n44.
+		</div>
+
+		<h3>.identity() [page:Matrix4 this]</h3>
+		<div>
+		Resets this matrix to identity.
+		</div>
+
+		<h3>.copy( [page:Matrix4 m] ) [page:Matrix4 this]</h3>
+		<div>
+		Copies a matrix *m* into this matrix.
+		</div>
+
+		<h3>.copyPosition( [page:Matrix4 m] ) [page:Matrix4 this]</h3>
+		<div>
+		Copies the translation component of the supplied matrix *m* into this matrix translation component.
+		</div>
+
+		<h3>.extractRotation( [page:Matrix4 m] ) [page:Matrix4 this]</h3>
+		<div>
+		Extracts the rotation of the supplied matrix *m* into this matrix rotation component.
+		</div>
+
+		<h3>.lookAt( [page:Vector3 eye], [page:Vector3 center], [page:Vector3 up], ) [page:Matrix4 this]</h3>
+		<div>
+		Constructs a rotation matrix, looking from *eye* towards *center* with defined *up* vector.
+		</div>
+
+		<h3>.multiply( [page:Matrix4 m] ) [page:Matrix4 this]</h3>
+		<div>
+		Multiplies this matrix by *m*.
+		</div>
+
+		<h3>.multiplyMatrices( [page:Matrix4 a], [page:Matrix4 b] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets this matrix to *a x b*.
+		</div>
+
+		<h3>.multiplyToArray( [page:Matrix4 a], [page:Matrix4 b], [page:Array r] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets this matrix to *a x b* and stores the result into the flat array *r*.<br />
+		*r* can be either a regular Array or a TypedArray.
+		</div>
+
+		<h3>.multiplyScalar( [page:Float s] ) [page:Matrix4 this]</h3>
+		<div>
+		Multiplies this matrix by *s*.
+		</div>
+
+		<h3>.determinant() [page:Float]</h3>
+		<div>
+		Computes determinant of this matrix.<br />
+		Based on [link:http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm]
+		</div>
+
+		<h3>.transpose() [page:Matrix4 this]</h3>
+		<div>
+		Transposes this matrix.
+		</div>
+
+		<h3>.flattenToArray( [page:Array flat] ) [page:Array]</h3>
+		<div>
+		Flattens this matrix into supplied *flat* array.
+		</div>
+
+		<h3>.flattenToArrayOffset( [page:Array flat], [page:Integer offset] ) [page:Array]</h3>
+		<div>
+		Flattens this matrix into supplied *flat* array starting from *offset* position in the array.
+		</div>
+
+		<h3>.setPosition( [page:Vector3 v] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets the position component for this matrix from vector *v*.
+		</div>
+
+		<h3>.getInverse( [page:Matrix4 m] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets this matrix to the inverse of matrix *m*.<br />
+		Based on [link:http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm].
+		</div>
+
+		<h3>.makeRotationFromEuler( [page:Vector3 v], [page:String order] ) [page:Matrix4 this]</h3>
+		<div>
+		v â Rotation vector.
+		order â The order of rotations. Eg. "XYZ".
+		</div>
+		<div>
+		Sets the rotation submatrix of this matrix to the rotation specified by Euler angles, the rest of the matrix is identity.<br />
+		Default order is *"XYZ"*.
+		</div>
+
+		<h3>.makeRotationFromQuaternion( [page:Quaternion q] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets the rotation submatrix of this matrix to the rotation specified by *q*. The rest of the matrix is identity.
+		</div>
+
+		<h3>.scale( [page:Vector3 v] ) [page:Matrix4 this]</h3>
+		<div>
+		Multiplies the columns of this matrix by vector *v*.
+		</div>
+
+		<h3>.compose( [page:Vector3 translation], [page:Quaternion quaternion], [page:Vector3 scale] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets this matrix to the transformation composed of *translation*, *quaternion* and *scale*.
+		</div>
+
+		<h3>.decompose( [page:Vector3 translation], [page:Quaternion quaternion], [page:Vector3 scale] ) [page:Array]</h3>
+		<div>
+		Decomposes this matrix into the *translation*, *quaternion* and *scale* components.<br />
+		If parameters are not passed, new instances will be created.
+		</div>
+
+		<h3>.makeTranslation( [page:Float x], [page:Float y], [page:Float z] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets this matrix as translation transform.
+		</div>
+
+		<h3>.makeRotationX( [page:Float theta] ) [page:Matrix4 this]</h3>
+		<div>
+		theta â Rotation angle in radians.
+		</div>
+		<div>
+		Sets this matrix as rotation transform around x axis by *theta* radians.
+		</div>
+
+		<h3>.makeRotationY( [page:Float theta] ) [page:Matrix4 this]</h3>
+		<div>
+		theta â Rotation angle in radians.
+		</div>
+		<div>
+		Sets this matrix as rotation transform around y axis by *theta* radians.
+		</div>
+
+		<h3>.makeRotationZ( [page:Float theta] ) [page:Matrix4 this]</h3>
+		<div>
+		theta â Rotation angle in radians.
+		</div>
+		<div>
+		Sets this matrix as rotation transform around z axis by *theta* radians.
+		</div>
+
+		<h3>.makeRotationAxis( [page:Vector3 axis], [page:Float theta] ) [page:Matrix4 this]</h3>
+		<div>
+		axis â Rotation axis, should be normalized.
+		theta â Rotation angle in radians.
+		</div>
+		<div>
+		Sets this matrix as rotation transform around *axis* by *angle* radians.<br />
+		Based on [link:http://www.gamedev.net/reference/articles/article1199.asp].
+		</div>
+
+		<h3>.makeScale( [page:Float x], [page:Float y], [page:Float z] ) [page:Matrix4 this]</h3>
+		<div>
+		Sets this matrix as scale transform.
+		</div>
+
+		<h3>.makeFrustum( [page:Float left], [page:Float right], [page:Float bottom], [page:Float top], [page:Float near], [page:Float far] ) [page:Matrix4 this]</h3>
+		<div>
+		Creates a [page:Frustum frustum] matrix.
+		</div>
+
+		<h3>.makePerspective( [page:Float fov], [page:Float aspect], [page:Float near], [page:Float far] ) [page:Matrix4 this]</h3>
+		<div>
+		Creates a perspective projection matrix.
+		</div>
+
+		<h3>.makeOrthographic( [page:Float left], [page:Float right], [page:Float bottom], [page:Float top], [page:Float near], [page:Float far] ) [page:Matrix4 this]</h3>
+		<div>
+		Creates an orthographic projection matrix.
+		</div>
+
+		<h3>.clone() [page:Matrix4]</h3>
+		<div>
+		Clones this matrix.
+		</div>
+
+		<h3>.multiplyVector3Array([page:Array a]) [page:Array]</h3>
+		<div>
+		array -- An array in the form [vector1x, vector1y, vector1z, vector2x, vector2y, vector2z, ...]
+		</div>
+		<div>
+		Multiply (apply) this matrix against every vector3 in the array.
+		</div>
+
+		<h3>.getMaxScaleOnAxis() [page:Float]</h3>
+		<div>
+		Gets the max scale value of the 3 axes.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Plane.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Plane.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Plane.html	(revision 28897)
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A two dimensional surface that extends infinitely in 3d space.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Vector3 normal], [page:Float constant])</h3>
+		<div>
+		normal -- ([Page:Vector3]) normal vector defining the plane pointing towards the origin <br />
+		constant -- ([Page:Float]) the negative distance from the origin to the plane along the normal vector
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Vector3 normal]</h3>
+
+		<h3>.[page:Float constant]</h3>
+		
+		<h2>Methods</h2>
+
+
+
+		<h3>.normalize() [page:Plane this]</h3>
+		<div>
+		Normalizes the normal vector, and adjusts the constant value accordingly.
+		</div>
+
+		<h3>.set([page:Vector3 normal], [page:Float constant]) [page:Plane this]</h3>
+		<div>
+		normal -- [Page:Vector3] <br />
+		constant -- [Page:Float]
+		</div>
+		<div>
+		Sets the plane's values.
+		</div>
+
+		<h3>.copy([page:Plane plane]) [page:Plane this]</h3>
+		<div>
+		plane -- [page:Plane] to copy
+		</div>
+		<div>
+		Copies the values of the passed plane to this plane.
+		</div>
+
+		<h3>.applyMatrix4([page:Matrix4 matrix], [page:Matrix3 optionalNormalMatrix]) [page:Plane this]</h3>
+		<div>
+		matrix -- [Page:Matrix4] to apply <br />
+		optionalNormalMatrix -- (optional) pre-computed normal [Page:Matrix3] of the Matrix4 to apply
+		</div>
+		<div>
+		Apply a Matrix4 to the plane. The second parameter is optional.
+		
+		<code>
+		var optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ) 
+		</code>
+		</div>
+
+		<h3>.orthoPoint([page:Vector3 point], [page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		point -- [page:Vector3] <br />
+		optionalTarget -- [page:Vector3]
+		</div>
+		<div>
+		Returns a vector in the same direction as the Plane's normal, but the magnitude is passed point's original distance to the plane.
+		</div>
+
+		<h3>.isIntersectionLine([page:Line3 line]) [page:Boolean]</h3>
+		<div>
+		line -- [page:Line3]
+		</div>
+		<div>
+		Tests whether a line segment intersects with the plane. (Do not mistake this for a collinear check.)
+		</div>
+
+		<h3>.intersectLine([page:Line3 line], [page:Vector3 optionalTarget]) [page:Vector3] or [page:undefined]</h3>
+		<div>
+		line -- [page:Line3] <br />
+		optionalTarget -- [page:Vector3]
+		</div>
+		<div>
+		Returns the intersection point of the passed line and the plane. Returns undefined if the line does not intersect. Returns the line's starting point if the line is coplanar with the plane.
+		</div>
+
+		<h3>.setFromNormalAndCoplanarPoint([page:Vector3 normal], [page:Vector3 point]) [page:Vector3 this]</h3>
+		<div>
+		normal -- [page:Vector3] <br />
+		point -- [page:Vector3]
+		</div>
+		<div>
+		Sets the plane's values as defined by a normal and arbitrary coplanar point.
+		</div>
+
+		<h3>.clone() [page:Plane]</h3>
+		<div>
+		Returns a new copy of this plane.
+		</div>
+
+		<h3>.distanceToPoint([page:Vector3 point]) [page:Float]</h3>
+		<div>
+		point -- [page:Vector3]
+		</div>
+		<div>
+		Returns the smallest distance from the point to the plane.
+		</div>
+
+		<h3>.equals([page:Plane plane]) [page:Boolean]</h3>
+		<div>
+		plane -- [page:Planen]
+		</div>
+		<div>
+		Checks to see if two planes are equal (their normals and constants match)
+		</div>
+
+		<h3>.setComponents([page:Float x], [page:Float y], [page:Float z], [page:Float w]) [page:Plane this]</h3>
+		<div>
+		x -- [page:Float] x of the normal vector <br />
+		y -- [page:Float] y of the normal vector<br />
+		z -- [page:Float] z of the normal vector<br />
+		w -- [page:Float] distance of the plane from the origin along the normal vector
+		</div>
+		<div>
+		Set the individual components that make up the plane.
+		</div>
+
+		<h3>.distanceToSphere([page:Sphere sphere]) [page:Float]</h3>
+		<div>
+		sphere -- [Page:Sphere]
+		</div>
+		<div>
+		Returns the smallest distance from an edge of the sphere to the plane.
+		</div>
+
+		<h3>.setFromCoplanarPoints([page:Vector3 a], [page:Vector3 b], [page:Vector3 c]) [page:Plane this]</h3>
+		<div>
+		a -- [page:Vector3] <br />
+		b -- [page:Vector3] <br />
+		c -- [page:Vector3]
+		</div>
+		<div>
+		Defines the plane based on the 3 provided points. The winding order is counter clockwise, and determines which direction the normal will point.
+		</div>
+
+		<h3>.projectPoint([page:Vector3 point], [page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		point -- [page:Vector3] <br />
+		optionalTarget -- [page:Vector3]
+		</div>
+		<div>
+		Projects a point onto the plane. The projected point is the closest point on the plane to the passed point, so a line drawn from the projected point and the passed point would be orthogonal to the plane.
+		</div>
+
+		<h3>.negate() [page:Plane this]</h3>
+		<div>
+		Negates both the normal vector and constant, effectively mirroring the plane across the origin.
+		</div>
+
+		<h3>.translate([page:Vector3 offset]) [page:Plane this]</h3>
+		<div>
+		offset -- [page:Vector3]
+		</div>
+		<div>
+		Translates the plane the distance defined by the vector. Note that this only affects the constant (distance from origin) and will not affect the normal vector.
+		</div>
+
+		<h3>.coplanarPoint([page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		optionalTarget -- [page:Vector3]
+		</div>
+		<div>
+		Returns a coplanar point. (The projection of the normal vector at the origin onto the plane.)
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Quaternion.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Quaternion.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Quaternion.html	(revision 28897)
@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Implementation of a <a href="http://en.wikipedia.org/wiki/Quaternion">quaternion</a>. This is used for rotating things without encountering the dreaded <a href="http://en.wikipedia.org/wiki/Gimbal_lock">gimbal lock</a> issue, amongst other advantages.</div>
+
+
+		<h2>Example</h2>
+
+		<code>var quaternion = new THREE.Quaternion();
+		quaternion.setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), Math.PI / 2 );
+
+		var vector = new THREE.Vector3( 1, 0, 0 );
+		vector.applyQuaternion( quaternion );
+		</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Float x], [page:Float y], [page:Float z], [page:Float w] )</h3>
+		<div>
+		x - x coordinate<br />
+		y - y coordinate<br />
+		z - z coordinate<br />
+		w - w coordinate
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float x]</h3>
+
+		<h3>.[page:Float y]</h3>
+
+		<h3>.[page:Float z]</h3>
+
+		<h3>.[page:Float w]</h3>
+
+
+		<h2>Methods</h2>
+
+		<h3>.set( [page:Float x], [page:Float y], [page:Float z], [page:Float w] ) [page:Quaternion]</h3>
+		<div>
+		Sets values of this quaternion.
+		</div>
+
+		<h3>.copy( [page:Quaternion q] ) [page:Quaternion]</h3>
+		<div>
+		Copies values of *q* to this quaternion.
+		</div>
+
+		<h3>.setFromEuler( [page:Vector3 vector] ) [page:Quaternion]</h3>
+		<div>
+		Sets this quaternion from rotation specified by Euler angles.
+		</div>
+
+		<h3>.setFromAxisAngle( [page:Vector3 axis], [page:Float angle] ) [page:Quaternion]</h3>
+		<div>
+		Sets this quaternion from rotation specified by axis and angle.<br />
+		Adapted from [link:http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm].<br />
+		*Axis* have to be normalized, *angle* is in radians.
+		</div>
+
+		<h3>.setFromRotationMatrix( [page:Matrix4 m] ) [page:Quaternion]</h3>
+		<div>
+		Sets this quaternion from rotation component of *m*.
+		Adapted from [link:http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm].
+		</div>
+
+		<h3>.inverse() [page:Quaternion]</h3>
+		<div>
+		Inverts this quaternion.
+		</div>
+
+		<h3>.length() [page:Float]</h3>
+		<div>
+		Computes length of this quaternion.
+		</div>
+
+		<h3>.normalize() [page:Quaternion]</h3>
+		<div>
+		Normalizes this quaternion.
+		</div>
+
+		<h3>.multiply( [page:Quaternion b] ) [page:Quaternion]</h3>
+		<div>
+		Multiplies this quaternion by *b*.
+		</div>
+
+		<h3>.multiplyQuaternions( [page:Quaternion a], [page:Quaternion b] ) [page:Quaternion]</h3>
+		<div>
+		Sets this quaternion to *a x b*<br />
+		Adapted from [link:http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm].
+		</div>
+
+		<h3>.multiplyVector3( [page:Vector3 vector], [page:Vector3 dest] ) [page:Quaternion]</h3>
+		<div>
+		Rotates *vector* by this quaternion into *dest*.<br />
+		If *dest* is not specified, result goes to *vec*.
+		</div>
+
+		<h3>.clone() [page:Quaternion]</h3>
+		<div>
+		Clones this quaternion.
+		</div>
+
+
+		<h2>Static methods</h2>
+
+		<h3>.slerp( [page:Quaternion qa], [page:Quaternion qb], [page:Quaternion qm], [page:Float t] ) [page:Quaternion]</h3>
+		<div>
+		Adapted from [link:http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/].
+		</div>
+
+
+		<h3>.slerp([page:Quaternion qb], [page:float t]) [page:Quaternion]</h3>
+		<div>
+		qb -- Target quaternion rotation.<br />
+		t -- Normalized [0..1] interpolation factor. 
+		</div>
+		<div>
+		Handles the spherical linear interpolation between this quaternion's configuration
+		and that of *qb*. *t* represents how close to the current (0) or target (1) rotation the 
+		result should be. 
+		</div>
+
+		<h3>.toArray() [page: Array]</h3>
+		<div>
+		Returns the numerical elements of this quaternion in an array of format (x, y, z, w).
+		</div>
+
+		<h3>.equals([page:Quaternion v]) [page:Boolean]</h3>
+		<div>
+		v -- Quaternion that this quaternion will be compared to.
+		</div>
+		<div>
+		Compares each component of *v* to each component of this quaternion to determine if they
+		represent the same rotation.
+		</div>
+
+		<h3>.lengthSq() [page:Float]</h3>
+		<div>
+		Calculates the squared length of the quaternion. 
+		</div>
+
+		<h3>.fromArray([page:Array array]) [page:Quaternion]</h3>
+		<div>
+		array -- Array of format (x, y, z, w) used to construct the quaternion.
+		</div>
+		<div>
+		Sets this quaternion's component values from an array.
+		</div>
+
+		<h3>.conjugate() [page:Quaternion]</h3>
+		<div>
+		Returns the rotational conjugate of this quaternion. The conjugate of a quaternion
+		represents the same rotation in the opposite direction about the rotational axis.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Ray.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Ray.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Ray.html	(revision 28897)
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A ray that emits from an origin in a certain direction.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Vector3 origin], [page:Vector3 direction])</h3>
+		<div>
+		origin -- [page:Vector3] The origin of the [page:Ray].<br />
+		direction -- [page:Vector3] The direction of the [page:Ray]. This must be normalized (with [page:Vector3].normalize) for the methods to operate properly.
+		</div>
+		<div>
+		Initialises the origin and direction properties to the provided values.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Vector3 origin]</h3>
+		<div>
+		The origin of the [page:Ray].
+		</div> 
+
+		<h3>.[page:Vector3 direction]</h3>
+		<div>
+		The direction of the [page:Ray]. This must be normalized (with [page:Vector3].normalize) for the methods to operate properly.
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h3>.applyMatrix4([page:Matrix4 matrix4]) [page:Ray]</h3>
+		<div>
+		matrix4 -- [page:Matrix4] The [page:Matrix4] to transform this [page:Ray] by.
+		</div>
+		<div>
+		Transform this [page:Ray] by the [page:Matrix4].
+		</div>
+
+		<h3>.at([page:Float t], [page:Vector3 optionalTarget] = null) [page:Vector3]</h3>
+		<div>
+		t -- [page:Float] The distance along the [page:Ray] to retrieve a position for.<br />
+		optionalTarget -- [page:Vector3] Receives the position along the [page:Ray] if passed; otherwise a new [page:Vector3] is created.
+		</div>
+		<div>
+		Get a [page:Vector3] that is a given distance along this [page:Ray].
+		</div>
+
+		<h3>.clone() [page:Ray]</h3>
+		<div>
+		Create a clone of this [page:Ray].
+		</div>
+
+		<h3>.closestPointToPoint([page:Vector3 point], [page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		point -- [page:Vector3] The point to get the closest approach to. <br />
+		optionalTarget -- [page:Vector3] Receives the return value if passed; otherwise a new [page:Vector3] is created.
+		</div>
+		<div>
+		Get the point along this [page:Ray] that is closest to the [page:Vector3] provided.
+		</div>
+
+		<h3>.copy([page:Ray ray]) [page:Ray]</h3>
+		<div>
+		ray -- [page:Ray] The [page:Ray] to copy values from.
+		</div>
+		<div>
+		Copy the properties of the provided [page:Ray], then return this [page:Ray].
+		</div>
+		
+		<h3>.distanceSqToSegment([page:Vector3 v0], [page:Vector3 v1], [page:Vector3 optionalPointOnRay] = null, [page:Vector3 optionalPointOnSegment] = null) [page:Float]</h3>
+		<div>
+		v0 -- [page:Vector3] The start of the line segment.
+		v1 -- [page:Vector3] The end of the line segment.
+		optionalPointOnRay -- [page:Vector3] If this is provided, it receives the point on this [page:Ray] that is closest to the segment.
+		optionalPointOnSegment -- [page:Vector3] If this is provided, it receives the point on the line segment that is closest to this [page:Ray].
+		</div>
+		<div>
+		Get the squared distance between this [page:Ray] and a line segment.
+		</div>
+
+		<h3>.distanceToPlane([page:Plane plane]) [page:Float]</h3>
+		<div>
+		plane -- [page:Plane] The [page:Plane] to get the distance to.
+		</div>
+		<div>
+		Get the distance from the origin to the [page:Plane], or *null* if the [page:Ray] doesn't intersect the [page:Plane].
+		</div>
+
+		<h3>.distanceToPoint([page:Vector3 point]) [page:Float]</h3>
+		<div>
+		point -- [page:Vector3] The [page:Vector3] to compute a distance to.
+		</div>
+		<div>
+		Get the distance of the closest approach between the [page:Ray] and the [page:Vector3].
+		</div>
+
+		<h3>.equals([page:Ray ray]) [page:Boolean]</h3>
+		<div>
+		ray -- [page:Ray] The [page:Ray] to compare to.
+		</div>
+		<div>
+		Return whether this and the other [page:Ray] have equal offsets and directions.
+		</div>
+
+		<h3>.intersectBox([page:Box3 box], [page:Vector3 optionalTarget] = null) [page:Vector3]?</h3>
+		<div>
+		box  -- [page:Box3] The [page:Box3] to intersect with.<br />
+		optionalTarget -- [page:Vector3] The [page:Vector3] to store the result in, or *null* to create a new [page:Vector3].
+		</div>
+		<div>
+		Intersect this [page:Ray] with a [page:Box3], returning the intersection point or *null* if there is no intersection.
+		</div>
+		
+		<h3>.intersectPlane([page:Plane plane], [page:Vector3 optionalTarget] = null) [page:Vector3]?</h3>
+		<div>
+		plane -- [page:Plane] The [page:Plane] to intersect with.<br />
+		optionalTarget -- [page:Vector3] The [page:Vector3] to store the result in, or *null* to create a new [page:Vector3].
+		</div>
+		<div>
+		Intersect this [page:Ray] with a [page:Plane], returning the intersection point or *null* if there is no intersection.
+		</div>
+		function ( a, b, c, backfaceCulling, optionalTarget )
+		
+		<h3>.intersectTriangle([page:Vector3 a], [page:Vector3 b], [page:Vector3 c], [page:Boolean backfaceCulling], [page:Vector3 optionalTarget] = null) [page:Vector3]?</h3>
+		<div>
+		a, b, c -- [page:Vector3] The [page:Vector3] points on the triangle.<br />
+		backfaceCulling -- [page:Boolean] Whether to use backface culling.<br />
+		optionalTarget -- [page:Vector3] The [page:Vector3] to store the result in, or *null* to create a new [page:Vector3].
+		</div>
+		<div>
+		Intersect this [page:Ray] with a triangle, returning the intersection point or *null* if there is no intersection.
+		</div>
+		
+		<h3>.isIntersectionBox([page:Box3 box]) [page:Boolean]</h3>
+		<div>
+		box -- [page:Box3] The [page:Box3] to intersect with.
+		</div>
+		<div>
+		Return whether or not this [page:Ray] intersects with the [page:Box3].
+		</div>
+
+		<h3>.isIntersectionPlane([page:Plane plane]) [page:Boolean]</h3>
+		<div>
+		plane -- [page:Plane] The [page:Plane] to intersect with.
+		</div>
+		<div>
+		Return whether or not this [page:Ray] intersects with the [page:Plane].
+		</div>
+
+		<h3>.isIntersectionSphere([page:Sphere sphere]) [page:Boolean]</h3>
+		<div>
+		sphere -- [page:Sphere] The [page:Sphere] to intersect with.
+		</div>
+		<div>
+		Return whether or not this [page:Ray] intersects with the [page:Sphere].
+		</div>
+
+		<h3>.recast([page:Float t])</h3>
+		<div>
+		t -- The distance along the [page:Ray] to interpolate.
+		</div>
+		<div>
+		Shift the origin of this [page:Ray] along its direction by the distance given.
+		</div>
+
+		<h3>.set([page:Vector3 origin], [page:Vector3 direction]) [page:Ray]</h3>
+		<div>
+		origin -- [page:Vector3] The origin of the [page:Ray].<br />
+		direction -- [page:Vector3] The direction of the [page:Ray]. This must be normalized (with [page:Vector3].normalize) for the methods to operate properly.
+		</div>
+		<div>
+		Copy the parameters to the origin and direction properties.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Sphere.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Sphere.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Sphere.html	(revision 28897)
@@ -0,0 +1,142 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A geometric sphere defined by a center position and radius.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Vector3 center], [page:Float radius])</h3>
+		<div>
+		center -- [page:Vector3] <br />
+		radius -- [page:Float]
+		</div>
+		
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:Vector3 center]</h3>
+		
+		<h3>.[page:Float radius]</h3>
+		
+		
+		<h2>Methods</h2>
+
+
+
+		<h3>.set([page:Vector3 center], [page:Float radius]) [page:this:Sphere]</h3>
+		<div>
+		center -- [page:Vector3] <br />
+		radius -- [page:Float]
+		</div>
+		<div>
+		Sets the center and radius.
+		</div>
+
+		<h3>.applyMatrix4([page:Matrix4 matrix]) [page:Sphere this]</h3>
+		<div>
+		matrix -- [page:Matrix4]
+		</div>
+		<div>
+		Transforms this sphere with the provided [page:Matrix4].
+		</div>
+
+		<h3>.clampPoint([page:Vector3 point], [page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		point -- [page:Vector3] The point to clamp <br />
+		optionalTarget -- [page:Vector3] The optional target point to return
+		</div>
+		<div>
+		Clamps a point within the sphere. If the point is is outside the sphere, it will clamp it to the closets point on the edge of the sphere.
+		</div>
+
+		<h3>.translate([page:Vector3 offset]) [page:Sphere this]</h3>
+		<div>
+		offset -- [page:Vector3]
+		</div>
+		<div>
+		Translate the sphere's center by the provided offset vector.
+		</div>
+
+		<h3>.clone() [page:Sphere]</h3>
+		<div>
+		Provides a new copy of the sphere.
+		</div>
+
+		<h3>.equals([page:Sphere sphere]) [page:Boolean]</h3>
+		<div>
+		sphere -- [page:Sphere]
+		</div>
+		<div>
+		Checks to see if the two spheres' centers and radii are equal.
+		</div>
+
+		<h3>.setFromPoints([page:Array points], [page:Vector3 optionalCenter]) [page:Sphere this]</h3>
+		<div>
+		points -- [page:Array] of [page:Vector3] positions.<br />  
+		optionalCenter -- Optional [page:Vector3] position for the sphere's center.<br />
+		</div>
+		<div>
+		Computes the minimum bounding sphere for *points*. If *optionalCenter* is given, it is used as the sphere's center. Otherwise, the center of the axis-aligned bounding box encompassing *points* is calculated.
+		</div>
+
+		<h3>.distanceToPoint([page:Vector3 point]) [page:Float]</h3>
+		<div>
+		point -- [page:Vector3]
+		</div>
+		<div>
+		Returns the closest distance from the boundary of the sphere to the point. If the sphere contains the point, the distance will be negative.
+		</div>
+
+		<h3>.getBoundingBox([page:Box optionalTarget]) [page:Box]</h3>
+		<div>
+		optionalTarget -- [page:Box]
+		</div>
+		<div>
+		Returns a bounding box for the sphere, optionally setting a provided box target.
+		</div>
+
+		<h3>.containsPoint([page:Vector3 point]) [page:Boolean]</h3>
+		<div>
+		point -- [page:Vector3]
+		</div>
+		<div>
+		Checks to see if the sphere contains the provided point inclusive of the edge of the sphere.
+		</div>
+
+		<h3>.copy([page:Sphere sphere]) [page:Sphere]</h3>
+		<div>
+		sphere -- [page:Sphere] to copy
+		</div>
+		<div>
+		Copies the values of the passed sphere to this sphere.
+		</div>
+
+		<h3>.intersectsSphere([page:Sphere sphere]) [page:Boolean]</h3>
+		<div>
+		sphere -- [page:Sphere]
+		</div>
+		<div>
+		Checks to see if two spheres intersect.
+		</div>
+
+		<h3>.empty() [page:Boolean]</h3>
+		<div>
+		Checks to see if the sphere is empty (the radius set to 0).
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Spline.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Spline.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Spline.html	(revision 28897)
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Represents a spline.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Array points] )</h3>
+		
+		<div>Initialises the spline with *points*, which are the places through which the spline will go.</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Array points]</h3>
+
+
+		<h2>Methods</h2>
+
+		<h3>.initFromArray( [page:Array a] )</h3>
+		<div>
+		a â array of triplets containing x, y, z coordinates<br />
+		</div>
+		<div>Initialises using the data in the array as a series of points. Each value in *a* must be another array with three values, where a[n] is v, the value for the *nth* point, and v[0], v[1] and v[2] are the x, y and z coordinates of that point n, respectively.
+		</div>
+
+		<h3>.getPoint( [page:Integer k] )</h3>
+		<div>
+		k â point index
+		</div>
+		<div>Return the interpolated point at *k*.</div>
+		
+		<h3>.getControlPointsArray( )</h3>
+		<div>Returns an array with triplets of x, y, z coordinates that correspond to the current control points.
+		</div>
+		
+		<h3>.getLength( [page:Integer nSubDivisions] )</h3>
+		<div>
+		nSubDivisions â number of subdivisions between control points. Default is *100*.
+		</div>
+		<div>Returns the length of the spline when using nSubDivisions.</div>
+		
+		<h3>.reparametrizeByArcLength( [page:Float samplingCoef] )</h3>
+		<div>
+		samplingCoef â how many intermediate values to use between spline points
+		</div>
+		<div>Modifies the spline so that it looks similar to the original but has its points distributed in such way that moving along the spline it's done at a more or less constant speed. The points should also appear more uniformly spread along the curve.</div>
+		<div>This is done by resampling the original spline, with the density of sampling controlled by *samplingCoef*. Here it's interesting to note that denser sampling is not necessarily better: if sampling is too high, you may get weird kinks in curvature.</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Triangle.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Triangle.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Triangle.html	(revision 28897)
@@ -0,0 +1,145 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">A geometric triangle as defined by three vectors.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:Vector3 a], [page:Vector3 b], [page:Vector3 c])</h3>
+		<div>
+		a -- [page:Vector3] <br />
+		b -- [page:Vector3] <br />
+		c -- [page:Vector3]
+		</div>
+		<div>
+		Sets the triangle's vectors to the passed vectors.
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Vector3 a]</h3>
+		<div>
+		The first [page:Vector3] of the triangle.
+		</div>
+		
+		<h3>.[page:Vector3 c]</h3>
+		<div>
+		The second [page:Vector3] of the triangle.
+		</div>
+
+		<h3>.[page:Vector3 b]</h3>
+		<div>
+		The third [page:Vector3] of the triangle.
+		</div>
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.setFromPointsAndIndices([page:Array points], [page:Integer i0], [page:Integer i1], [page:Integer i2]) [page:Triangle this]</h3>
+		<div>
+		points -- [page:Array] of [page:Vector3]s <br />
+		i0 -- [page:Integer] index <br />
+		i1 -- [page:Integer] index <br />
+		i2 -- [page:Integer] index
+		</div>
+		<div>
+		Sets the triangle's vectors to the vectors in the array.
+		</div>
+
+		<h3>.set([page:Vector3 a], [page:Vector3 b], [page:Vector3 c]) [page:Triangle this]</h3>
+		<div>
+		a -- [page:Vector3] <br />
+		b -- [page:Vector3] <br />
+		c -- [page:Vector3]
+		</div>
+		<div>
+		Sets the triangle's vectors to the passed vectors.
+		</div>
+
+		<h3>.normal([page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		optionalTarget -- Optional [page:Vector3] target to set the result.
+		</div>
+		<div>
+		Return the calculated normal of the triangle.
+		</div>
+
+		<h3>.barycoordFromPoint([page:Vector3 point], [page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		point -- [page:Vector3] <br />
+		optionalTarget -- Optional [page:Vector3] target to set the result.
+		</div>
+		<div>
+		Return a barycentric coordinate from the given vector. <br/><br/>
+		[link:http://commons.wikimedia.org/wiki/File:Barycentric_coordinates_1.png](Picture of barycentric coordinates)
+		</div>
+
+		<h3>.clone() [page:Triangle]</h3>
+		<div>
+		Return a new copy of this triangle.
+		</div>
+
+		<h3>.area() [page:Float]</h3>
+		<div>
+		Return the area of the triangle.
+		</div>
+
+		<h3>.midpoint([page:Vector3 optionalTarget]) [page:Vector3]</h3>
+		<div>
+		optionalTarget -- Optional [page:Vector3] target to set the result.
+		</div>
+		<div>
+		Return the midpoint of the triangle. Optionally sets a target vector.
+		</div>
+
+		<h3>.equals([page:Triangle triangle]) [page:Boolean]</h3>
+		<div>
+		triangle -- [page:Triangle]
+		</div>
+		<div>
+		Checks to see if two triangles are equal (share the same vectors).
+		</div>
+
+		<h3>.plane([page:Plane optionalTarget]) [page:Plane]</h3>
+		<div>
+		optionalTarget -- Optional [page:Plane] target to set the result.
+		</div>
+		<div>
+		Return a [page:Plane plane] based on the triangle. Optionally sets a target plane. 
+		</div>
+
+		<h3>.containsPoint([page:Vector3 point]) [page:Boolean]</h3>
+		<div>
+		point -- [page:Vector3]
+		</div>
+		<div>
+		Checks to see if the passed vector is within the triangle.
+		</div>
+
+		<h3>.copy([page:Triangle triangle]) [page:Triangle]</h3>
+		<div>
+		triangle -- [page:Triangle]
+		</div>
+		<div>
+		Copies the values of the vertices of the passed triangle to this triangle.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector2.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector2.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector2.html	(revision 28897)
@@ -0,0 +1,231 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">2D vector.</div>
+
+		<h2>Example</h2>
+
+		<code>var a = new THREE.Vector2( 0, 1 );
+		var b = new THREE.Vector2( 1, 0 );
+
+		var d = a.distanceTo( b );
+		</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Float x], [page:Float y] )</h3>
+		<div>
+		x -- [page:Float] representing the x value of the vector <br />
+		y -- [page:Float] representing the y value of the vector
+		</div>
+		<div>
+		A vector in 2 dimensional space
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float x]</h3>
+
+		<h3>.[page:Float y]</h3>
+
+
+		<h2>Methods</h2>
+
+		<h3>.set( [page:Float x], [page:Float y] ) [page:Vector2 this]</h3>
+		<div>
+		Sets value of this vector.
+		</div>
+
+		<h3>.copy( [page:Vector2 v] ) [page:Vector2 this]</h3>
+		<div>
+		Copies value of *v* to this vector.
+		</div>
+
+		<h3>.add( [page:Vector2 v] ) [page:Vector2 this]</h3>
+		<div>
+		Adds *v* to this vector.
+		</div>
+
+		<h3>.addVectors( [page:Vector2 a], [page:Vector2 b] ) [page:Vector2 this]</h3>
+		<div>
+		Sets this vector to *a + b*.
+		</div>
+
+		<h3>.sub( [page:Vector2 v] ) [page:Vector2 this]</h3>
+		<div>
+		Subtracts *v* from this vector.
+		</div>
+
+		<h3>.subVectors( [page:Vector2 a], [page:Vector2 b] ) [page:Vector2 this]</h3>
+		<div>
+		Sets this vector to *a - b*.
+		</div>
+
+		<h3>.multiplyScalar( [page:Float s] ) [page:Vector2 this]</h3>
+		<div>
+		Multiplies this vector by scalar *s*.
+		</div>
+
+		<h3>.divideScalar( [page:Float s] ) [page:Vector2 this]</h3>
+		<div>
+		Divides this vector by scalar *s*.<br />
+		Set vector to *( 0, 0 )* if *s == 0*.
+		</div>
+
+		<h3>.negate() [page:Vector2 this]</h3>
+		<div>
+		Inverts this vector.
+		</div>
+
+		<h3>.dot( [page:Vector2 v] ) [page:Float]</h3>
+		<div>
+		Computes dot product of this vector and *v*.
+		</div>
+
+		<h3>.lengthSq() [page:Float]</h3>
+		<div>
+		Computes squared length of this vector.
+		</div>
+
+		<h3>.length() [page:Float]</h3>
+		<div>
+		Computes length of this vector.
+		</div>
+
+		<h3>.normalize() [page:Vector2 this]</h3>
+		<div>
+		Normalizes this vector.
+		</div>
+
+		<h3>.distanceTo( [page:Vector2 v] ) [page:Float]</h3>
+		<div>
+		Computes distance of this vector to *v*.
+		</div>
+
+		<h3>.distanceToSquared( [page:Vector2 v] ) [page:Float]</h3>
+		<div>
+		Computes squared distance of this vector to *v*.
+		</div>
+
+		<h3>.setLength( [page:Float l] ) [page:Vector2 this]</h3>
+		<div>
+		Normalizes this vector and multiplies it by *l*.
+		</div>
+
+		<h3>.equals( [page:Vector2 v] ) [page:Boolean]</h3>
+		<div>
+		Checks for strict equality of this vector and *v*.
+		</div>
+
+		<h3>.clone() [page:Vector2]</h3>
+		<div>
+		Clones this vector.
+		</div>
+
+
+		<h3>.clamp([page:Vector2 min], [page:Vector2 max]) [page:Vector2 this]</h3>
+		<div>
+		min -- [page:Vector2] containing the min x and y values in the desired range <br />
+		max -- [page:Vector2] containing the max x and y values in the desired range
+		</div>
+		<div>
+		If this vector's x or y value is greater than the max vector's x or y value, it is replaced by the corresponding value. <br />  If this vector's x or y value is less than the min vector's x or y value, it is replace by the corresponding value.
+		</div>
+
+		<h3>.lerp([page:Vector2 v], [page:Float alpha]) [page:Vector2 this]</h3>
+		<div>
+		v -- [page:Vector2] <br />
+		alpha -- [page:Float] between 0 and 1;
+		</div>
+		<div>
+		Linear interpolation between this vector and v, where alpha is the percent along the line.
+		</div>
+
+		<h3>.setComponent([page:Integer index], [page:Float value]) [page:undefined]</h3>
+		<div>
+		index -- 0 or 1 <br />
+		value -- [page:Float]
+		</div>
+		<div>
+		if index equals 0 method replaces this.x with value. <br />
+		if index equals 1 method replaces this.y with value.
+		</div>
+
+		<h3>.addScalar([page:Float s]) [page:Vector2 this]</h3>
+		<div>
+		s -- [page:Float]
+		</div>
+		<div>
+		Add the scalar value s to this vector's x and y values.
+		</div>
+
+		<h3>.getComponent([page:Integer index]) [page:Float]</h3>
+		<div>
+		index -- 0 or 1
+		</div>
+		<div>
+		if index equals 0 returns the x value. <br />
+		if index equals 1 returns the y value.
+		</div>
+
+		<h3>.fromArray([page:Array array]) [page:Vector2 this]</h3>
+		<div>
+		array -- [page:Array] of length 2
+		</div>
+		<div>
+		Sets this vector's x value to be array[0] and y value to be array[1].
+		</div>
+
+		<h3>.toArray() [page:Array]</h3>
+		<div>
+		Returns an array [x, y].
+		</div>
+
+		<h3>.min([page:Vector2 v]) [page:Vector2 this]</h3>
+		<div>
+		v -- [page:Vector2]
+		</div>
+		<div>
+		If this vector's x or y value is less than v's x or y value, replace that value with the corresponding min value.
+		</div>
+
+		<h3>.max([page:Vector2 v]) [page:Vector2 this]</h3>
+		<div>
+		v -- [page:Vector2]
+		</div>
+		<div>
+		If this vector's x or y value is greater than v's x or y value, replace that value with the corresponding max value.
+		</div>
+
+		<h3>.setX([page:Float x]) [page:Vector2 this]</h3>
+		<div>
+		x -- [page:Float]
+		</div>
+		<div>
+		replace this vector's x value with x.
+		</div>
+
+		<h3>.setY([page:Float y]) [page:Vector2 this]</h3>
+		<div>
+		y -- [page:Float]
+		</div>
+		<div>
+		replace this vector's y value with y.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector3.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector3.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector3.html	(revision 28897)
@@ -0,0 +1,393 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">3D vector.</div>
+
+
+		<h2>Example</h2>
+
+		<code>var a = new THREE.Vector3( 1, 0, 0 );
+		var b = new THREE.Vector3( 0, 1, 0 );
+
+		var c = new THREE.Vector3();
+		c.crossVectors( a, b );
+		</code>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Float x], [page:Float y], [page:Float z] )</h3>
+		<div>
+		x -- [page:Float] the vector's x value <br />
+		y -- [page:Float] the vector's y value <br />
+		z -- [page:Float] the vector's z value
+		</div>
+		<div>
+		A 3 dimensional vector
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float x]</h3>
+
+		<h3>.[page:Float y]</h3>
+
+		<h3>.[page:Float z]</h3>
+
+
+		<h2>Methods</h2>
+
+		<h3>.set( [page:Float x], [page:Float y], [page:Float z] ) [page:Vector3 this]</h3>
+		<div>
+		Sets value of this vector.
+		</div>
+
+		<h3>.setX( [page:Float x] ) [page:Vector3 this]</h3>
+		<div>
+		Sets x value of this vector.
+		</div>
+
+		<h3>.setY( [page:Float y] ) [page:Vector3 this]</h3>
+		<div>
+		Sets y value of this vector.
+		</div>
+
+		<h3>.setZ( [page:Float z] ) [page:Vector3 this]</h3>
+		<div>
+		Sets z value of this vector.
+		</div>
+
+		<h3>.copy( [page:Vector3 v] ) [page:Vector3 this]</h3>
+		<div>
+		Copies value of *v* to this vector.
+		</div>
+
+		<h3>.add( [page:Vector3 v] ) [page:Vector3 this]</h3>
+		<div>
+		Adds *v* to this vector.
+		</div>
+
+		<h3>.addVectors( [page:Vector3 a], [page:Vector3 b] ) [page:Vector3 this]</h3>
+		<div>
+		Sets this vector to *a + b*.
+		</div>
+
+		<h3>.sub( [page:Vector3 v] ) [page:Vector3 this]</h3>
+		<div>
+		Subtracts *v* from this vector.
+		</div>
+
+		<h3>.subVectors( [page:Vector3 a], [page:Vector3 b] ) [page:Vector3 this]</h3>
+		<div>
+		Sets this vector to *a - b*.
+		</div>
+
+		<h3>.multiplyScalar( [page:Float s] ) [page:Vector3 this]</h3>
+		<div>
+		Multiplies this vector by scalar *s*.
+		</div>
+
+		<h3>.divideScalar( [page:Float s] ) [page:Vector3 this]</h3>
+		<div>
+		Divides this vector by scalar *s*.<br />
+		Set vector to *( 0, 0, 0 )* if *s == 0*.
+		</div>
+
+		<h3>.negate() [page:Vector3 this]</h3>
+		<div>
+		Inverts this vector.
+		</div>
+
+		<h3>.dot( [page:Vector3 v] ) [page:Float]</h3>
+		<div>
+		Computes dot product of this vector and *v*.
+		</div>
+
+		<h3>.lengthSq() [page:Float]</h3>
+		<div>
+		Computes squared length of this vector.
+		</div>
+
+		<h3>.length() [page:Float]</h3>
+		<div>
+		Computes length of this vector.
+		</div>
+
+		<h3>.lengthManhattan() [page:Float]</h3>
+		<div>
+		Computes Manhattan length of this vector.<br />
+		[link:http://en.wikipedia.org/wiki/Taxicab_geometry]
+		</div>
+
+		<h3>.normalize() [page:Vector3 this]</h3>
+		<div>
+		Normalizes this vector. Transforms this Vector into a Unit vector by dividing the vector by it's length.
+		</div>
+
+		<h3>.distanceTo( [page:Vector3 v] ) [page:Float]</h3>
+		<div>
+		Computes distance of this vector to *v*.
+		</div>
+
+		<h3>.distanceToSquared( [page:Vector3 v] ) [page:Float]</h3>
+		<div>
+		Computes squared distance of this vector to *v*.
+		</div>
+
+		<h3>.setLength( [page:Float l] ) [page:Vector3 this]</h3>
+		<div>
+		Normalizes this vector and multiplies it by *l*.
+		</div>
+
+		<h3>.cross( [page:Vector3 v] ) [page:Vector3 this]</h3>
+		<div>
+		Sets this vector to cross product of itself and *v*.
+		</div>
+
+		<h3>.crossVectors( [page:Vector3 a], [page:Vector3 b] ) [page:Vector3 this]</h3>
+		<div>
+		Sets this vector to cross product of *a* and *b*.
+		</div>
+
+		<h3>.setFromMatrixPosition( [page:Matrix4 m] ) [page:Vector3 this]</h3>
+		<div>
+		Sets this vector extracting position from matrix transform.
+		</div>
+
+		<h3>.setFromMatrixScale( [page:Matrix4 m] ) [page:Vector3 this]</h3>
+		<div>
+		Sets this vector extracting scale from matrix transform.
+		</div>
+
+		<h3>.equals( [page:Vector3 v] ) [page:Boolean]</h3>
+		<div>
+		Checks for strict equality of this vector and *v*.
+		</div>
+
+		<h3>.clone() [page:Vector3]</h3>
+		<div>
+		Clones this vector.
+		</div>
+
+
+		<h3>.clamp([page:Vector3 min], [page:Vector3 max]) [page:Vector3 this]</h3>
+		<div>
+		min -- [page:Vector3] <br />
+		max -- [page:Vector3]
+		</div>
+		<div>
+		If this vector's x, y or z value is greater than the max vector's x, y or z value, it is replaced by the corresponding value. <br /><br />
+		If this vector's x, y or z value is less than the min vector's x, y or z value, it is replace by the corresponding value.
+		</div>
+
+		<h3>.applyMatrix3([page:Matrix3 m]) [page:Vector3 this]</h3>
+		<div>
+		m -- [page:Matrix3]
+		</div>
+		<div>
+		Multiplies this vector times a 3 x 3 matrix.
+		</div>
+
+		<h3>.applyMatrix4([page:Matrix3 m]) [page:Vector3 this]</h3>
+		<div>
+		m -- [page:Matrix4]
+		</div>
+		<div>
+		Multiplies this vector by 4 x 3 subset of a Matrix4.
+		</div>
+
+		<h3>.projectOnPlane([page:Vector3 planeNormal]) [page:Vector3 this]</h3>
+		<div>
+		planeNormal -- [page:Vector3 planeNormal] A vector representing a plane normal.
+		</div>
+		<div>
+		Projects this vector onto a plane by subtracting this vector projected onto the plane's normal from this vector.
+		</div>
+
+		<h3>.projectOnVector([page:Vector3]) [page:Vector3 this]</h3>
+		<div>
+		vector -- [page:Vector3]
+		</div>
+		<div>
+		Projects this vector onto another vector.
+		</div>
+
+		<h3>.addScalar([page:Float]) [page:Vector3 this]</h3>
+		<div>
+		s -- [page:Float]
+		</div>
+		<div>
+		Adds a s to this vector.
+		</div>
+
+		<h3>.divide([page:Vector3 v]) [page:Vector3 this]</h3>
+		<div>
+		v -- [page:Vector3]
+		</div>
+		<div>
+		Divides this vector by vector v.
+		</div>
+
+		<h3>.min([page:Vector3 v]) [page:Vector3 this]</h3>
+		<div>
+		v -- [page:Vector3]
+		</div>
+		<div>
+		If this vector's x, y, or z value is less than vector v's x, y, or z value, that value is replaced by the corresponding vector v value.
+		</div>
+
+		<h3>.max([page:Vector3 v]) [page:Vector3 this]</h3>
+		<div>
+		v -- [page:Vector3]
+		</div>
+		<div>
+		If this vector's x, y, or z value is greater than vector v's x, y, or z value, that value is replaced by the corresponding vector v value.
+		</div>
+
+		<h3>.setComponent([page:Integer index], [page:Float value]) [page:Vector3 this]</h3>
+		<div>
+		index -- 0, 1, or 2 <br />
+		value -- [page:Float]
+		</div>
+		<div>
+		If index equals 0 the method sets this vector's x value to value <br />
+		If index equals 1 the method sets this vector's y value to value <br />
+		If index equals 2 the method sets this vector's z value to value
+		</div>
+
+		<h3>.transformDirection([page:Matrix4 m]) [page:Vector3 this]</h3>
+		<div>
+		m -- [page:Matrix4]
+		</div>
+		<div>
+		Transforms the direction of this vector by a matrix (a 3 x 3 subset of a Matrix4) and then normalizes the result.
+		</div>
+
+		<h3>.multiplyVectors([page:Vector3 a], [page:Vector3 b]) [page:Vector3 this]</h3>
+		<div>
+		a -- [page:Vector3] <br />
+		b -- [page:Vector3]
+		</div>
+		<div>
+		Sets this vector equal to the result of multiplying vector a by vector b.
+		</div>
+
+		<h3>.getComponent([page:Integer index]) [page:Float]</h3>
+		<div>
+		index -- [page:Integer] 0, 1, or 2
+		</div>
+		<div>
+		
+		Returns the value of the vector component x, y, or z by an index. <br /><br />
+		
+		Index 0: x <br />
+		Index 1: y <br />
+		Index 2: z <br />
+		</div>
+
+		<h3>.applyAxisAngle([page:Vector3 axis], [page:Float angle]) [page:Vector3 this]</h3>
+		<div>
+		axis -- A normalized [page:Vector3] <br />
+		angle -- An angle in radians
+		</div>
+		<div>
+		Applies a rotation specified by an axis and an angle to this vector.
+		</div>
+
+		<h3>.lerp([page:Vector3 v], [page:Float alpha]) [page:Vector3 this]</h3>
+		<div>
+		v -- [page:Vector3] <br />
+		alpha -- [page:Float] between 0 and 1.
+		</div>
+		<div>
+		Linear Interpolation between this vector and vector v, where alpha is the percent along the line.
+		</div>
+
+		<h3>.angleTo([page:Vector3 v]) [page:Float]</h3>
+		<div>
+		v -- [page:Vector3]
+		</div>
+		<div>
+		Returns the angle between this vector and vector v in radians.
+		</div>
+
+		<h3>.setFromMatrixColumn([page:Integer index], [page:Matrix4 matrix]) [page:Vector3 this]</h3>
+		<div>
+		index -- 0, 1, 2, or 3 <br />
+		matrix -- [page:Matrix4]
+		</div>
+		<div>
+		Sets this vector's x, y, and z equal to the column of the matrix specified by the index.
+		</div>
+
+		<h3>.reflect([page:Vector3 vector]) [page:Vector3 this]</h3>
+		<div>
+		vector -- [page:Vector3] the vector to reflect about
+		</div>
+		<div>
+		Reflects this vector about a vector.
+		</div>
+
+		<h3>.fromArray([page:Array array]) [page:Vector3 this]</h3>
+		<div>
+		array -- [page:Array] [x, y, z]
+		</div>
+		<div>
+		Sets the vector's components based on an array formatted like [x, y, z]
+		</div>
+
+		<h3>.multiply([page:Vector3 v]) [page:Vector3 this]</h3>
+		<div>
+		v -- [page:Vector3] <br />
+		</div>
+		<div>
+		Multipies this vector by vector v.
+		</div>
+
+		<h3>.applyProjection([page:Matrix4 m]) [page:Vector3 this]</h3>
+		<div>
+		m -- [page:Matrix4] projection matrix.
+		</div>
+		<div>
+		Multiplies this vector and m, and divides by perspective.
+		</div>
+
+		<h3>.toArray() [page:Array]</h3>
+		<div>
+		Assigns this vector's x value to array[0]. <br />
+		Assigns this vector's y value to array[1]. <br />
+		Assigns this vector's z value to array[2]. <br />
+		Returns the created array.
+		</div>
+
+		<h3>.applyEuler([page:Euler euler]) [page:Vector3 this]</h3>
+		<div>
+		euler -- [page:Euler]
+		</div>
+		<div>
+		Applies euler transform to this vector by converting the [page:Euler] obect to a [page:Quaternion] and applying.
+		</div>
+
+		<h3>.applyQuaternion([page:Quaternion quaternion]) [page:Vector3 this]</h3>
+		<div>
+		quaternion -- [page:Quaternion]
+		</div>
+		<div>
+		Applies a [page:Quaternion] transform to this vector.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector4.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector4.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/Vector4.html	(revision 28897)
@@ -0,0 +1,277 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">4D vector.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Float x], [page:Float y], [page:Float z], [page:Float w] )</h3>
+		<div>
+		x -- [page:Float] <br />
+		y -- [page:Float] <br />
+		z -- [page:Float] <br />
+		w -- [page:Float]
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Float x]</h3>
+
+		<h3>.[page:Float y]</h3>
+
+		<h3>.[page:Float z]</h3>
+
+		<h3>.[page:Float w]</h3>
+
+
+		<h2>Methods</h2>
+
+		<h3>.set( [page:Float x], [page:Float y], [page:Float z], [page:Float w] ) [page:Vector4 this]</h3>
+		<div>
+		Sets value of this vector.
+		</div>
+
+		<h3>.copy( [page:Vector4 v] ) [page:Vector4 this]</h3>
+		<div>
+		Copies value of *v* to this vector.
+		</div>
+
+		<h3>.add( [page:Vector4 v] ) [page:Vector4 this]</h3>
+		<div>
+		Adds *v* to this vector.
+		</div>
+
+		<h3>.addVectors( [page:Vector4 a], [page:Vector4 b] ) [page:Vector4 this]</h3>
+		<div>
+		Sets this vector to *a + b*.
+		</div>
+
+		<h3>.sub( [page:Vector4 v] ) [page:Vector4]</h3>
+		<div>
+		Subtracts *v* from this vector.
+		</div>
+
+		<h3>.subVectors( [page:Vector4 a], [page:Vector4 b] ) [page:Vector4 this]</h3>
+		<div>
+		Sets this vector to *a - b*.
+		</div>
+
+		<h3>.multiplyScalar( [page:Float s] ) [page:Vector4 this]</h3>
+		<div>
+		Multiplies this vector by scalar *s*.
+		</div>
+
+		<h3>.divideScalar( [page:Float s] ) [page:Vector4 this]</h3>
+		<div>
+		Divides this vector by scalar *s*.<br />
+		Set vector to *( 0, 0, 0 )* if *s == 0*.
+		</div>
+
+		<h3>.negate() [page:Vector4 this]</h3>
+		<div>
+		Inverts this vector.
+		</div>
+
+		<h3>.dot( [page:Vector4 v] ) [page:Float]</h3>
+		<div>
+		Computes dot product of this vector and *v*.
+		</div>
+
+		<h3>.lengthSq() [page:Float]</h3>
+		<div>
+		Computes squared length of this vector.
+		</div>
+
+		<h3>.length() [page:Float]</h3>
+		<div>
+		Computes length of this vector.
+		</div>
+
+		<h3>.normalize() [page:Vector4 this]</h3>
+		<div>
+		Normalizes this vector.
+		</div>
+
+		<h3>.setLength( [page:Float l] ) [page:Vector4 this]</h3>
+		<div>
+		Normalizes this vector and multiplies it by *l*.
+		</div>
+
+		<h3>.lerp( [page:Vector4 v], [page:Float alpha] ) [page:Vector4 this]</h3>
+		<div>
+		Linearly interpolate between this vector and *v* with *alpha* factor.
+		</div>
+
+		<h3>.clone() [page:Vector4]</h3>
+		<div>
+		Clones this vector.
+		</div>
+
+
+		<h3>.clamp([page:Vector4 min], [page:Vector4 max]) [page:Vector4 this]</h3>
+		<div>
+		min -- [page:Vector4] <br />
+		max -- [page:Vector4]
+		</div>
+		<div>
+		If this vector's x, y, z, or w value is greater than the max vector's x, y, z, or w value, it is replaced by the corresponding value.<br/><br/>
+
+		If this vector's x, y, z, or w value is less than the min vector's x, y, z, or w value, it is replace by the corresponding value.
+		</div>
+
+		<h3>.applyMatrix4([page:Matrix4 m]) [page:Vector4 this]</h3>
+		<div>
+		m -- [page:Matrix4]
+		</div>
+		<div>
+		Transforms the vector by the matrix.
+		</div>
+
+		<h3>.min([page:Vector4 v]) [page:Vector4 this]</h3>
+		<div>
+		v -- [page:Vector4]
+		</div>
+		<div>
+		If this vector's x, y, z, or w value is less than vector v's x, y, z, or w value, that value is replaced by the corresponding vector v value.
+		</div>
+
+		<h3>.max([page:Vector4 v]) [page:Vector4 this]</h3>
+		<div>
+		v -- [page:Vector4]
+		</div>
+		<div>
+		If this vector's x, y, z, or w value is greater than vector v's x, y, z, or w value, that value is replaced by the corresponding vector v value. 
+		</div>
+
+		<h3>.addScalar([page:Float s]) [page:Vector4 this]</h3>
+		<div>
+		s -- [page:Float]
+		</div>
+		<div>
+		Adds a scalar value to all of the vector's components.
+		</div>
+
+		<h3>.equals([page:Vector4 v]) [page:Boolean]</h3>
+		<div>
+		v -- [page:Vector4]
+		</div>
+		<div>
+		Checks to see if this vector matches vector v.
+		</div>
+		
+		<h3>.setAxisAngleFromRotationMatrix([page:Matrix4 m]) [page:Vector4 this]</h3>
+		<div>
+		m -- [page:Matrix4]
+		</div>
+		<div>
+		Sets this Vector4 to the computed <a href='http://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation' target='_blank'>axis-angle representation</a> of the rotation defined by Matrix4 m. Assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled).<br/><br/>
+
+		The axis is stored in components (x, y, z) of the vector, and the rotation in radians is stored in component w
+		</div>
+
+		<h3>.setAxisAngleFromQuaternion([page:Quaternion q]) [page:Vector4 this]</h3>
+		<div>
+		q -- [page:Quaternion]
+		</div>
+		<div>
+		Sets this Vector4 to the computed <a href='http://en.wikipedia.org/wiki/Axis%E2%80%93angle_representation' target='_blank'>axis-angle representation</a> of the rotation defined by Quaternion q.<br/><br/>
+
+		The axis is stored in components (x, y, z) of the vector, and the rotation in radians is stored in component w
+		</div>
+
+		<h3>.getComponent([page:Integer index]) [page:Float]</h3>
+		<div>
+		index -- [page:Integer] 0, 1, 2, or 3
+		</div>
+		<div>
+		Returns the value of the vector component x, y, or z by an index.<br/><br/>
+
+		Index 0: x<br/>
+		Index 1: y<br/>
+		Index 2: z<br/>
+		Index 3: w<br/>
+ 
+		</div>
+
+		<h3>.setComponent([page:Integer index], [page:Float value])</h3>
+		<div>
+		index -- [page:Integer] 0 - 3 <br />
+		value -- [page:Float]
+		</div>
+		<div>
+		Sets the value of the vector component  x, y, or z by an index.<br/><br/>
+		
+		Index 0: x<br/>
+		Index 1: y<br/>
+		Index 2: z<br/>
+		Index 3: w<br/>
+		</div>
+		
+		<h3>.fromArray([page:Array array]) [page:Vector4 this]</h3>
+		<div>
+		array -- [page:Array] An array formatted [x, y, z, w]
+		</div>
+		<div>
+		Sets the vector's components based on an array formatted like [x, y, z, w]
+		</div>
+
+		<h3>.toArray() [page:Array]</h3>
+		<div>
+		Returns an array in the format [x, y, z, w]
+		</div>
+
+		<h3>.lengthManhattan() [page:Float]</h3>
+		<div>
+		Computes Manhattan length of this vector.<br />
+		[link:http://en.wikipedia.org/wiki/Taxicab_geometry]
+		</div>
+
+
+		<h3>.setX([page:Float x]) [page:Vector4 this]</h3>
+		<div>
+		x -- [page:Float]
+		</div>
+		<div>
+		Sets the x component of the vector.
+		</div>
+
+		<h3>.setY([page:Float y]) [page:Vector4 this]</h3>
+		<div>
+		y -- [page:Float]
+		</div>
+		<div>
+		Sets the y component of the vector.
+		</div>
+
+		<h3>.setZ([page:Float z]) [page:Vector4 this]</h3>
+		<div>
+		z -- [page:Float]
+		</div>
+		<div>
+		Sets the z component of the vector.
+		</div>
+
+		<h3>.setW([page:Float w]) [page:Vector4 this]</h3>
+		<div>
+		w -- [page:Float]
+		</div>
+		<div>
+		Sets the w component of the vector.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/math/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Bone.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Bone.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Bone.html	(revision 28897)
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo belongsToSkin])</h3>
+		<div>
+		belongsToSkin -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Matrix4 skinMatrix]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:todo skin]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.update([page:todo parentSkinMatrix], [page:todo forceUpdate]) [page:todo]</h3>
+		<div>
+		parentSkinMatrix -- todo <br />
+		forceUpdate -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/LOD.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/LOD.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/LOD.html	(revision 28897)
@@ -0,0 +1,68 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:array objects]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.addLevel([page:todo object], [page:todo distance]) [page:todo]</h3>
+		<div>
+		object -- todo <br />
+		distance -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.getObjectForDistance([page:todo distance]) [page:todo]</h3>
+		<div>
+		distance -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.update([page:todo camera]) [page:todo]</h3>
+		<div>
+		camera -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Line.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Line.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Line.html	(revision 28897)
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A line or a series of lines.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Geometry geometry], [page:Material material], [page:Integer type] )</h3>
+
+		<div>
+		geometry â Vertices representing the line segment(s).<br />
+		material â Material for the line. Default is [page:LineBasicMaterial LineBasicMaterial].<br />
+		type â Connection type between vertices. Default is THREE.LineStrip.
+		</div>
+
+		<div>If no material is supplied, a randomized line material will be created and assigned to the object.</div>
+
+		<div>Also, if no type is supplied, the default (THREE.LineStrip) will be used).</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Geometry geometry]</h3>
+		<div>
+		Vertices representing the line segment(s).
+		</div>
+
+		<h3>.[page:Material material]</h3>
+		<div>
+		Material for the line.
+		</div>
+
+		<h3>.[page:Integer type]</h3>
+		<div>
+		Possible values: THREE.LineStrip or THREE.LinePieces. THREE.LineStrip will draw a series of segments connecting each point (first connected to the second, the second connected to the third, and so on and so forth); and THREE.LinePieces will draw a series of pairs of segments (first connected to the second, the third connected to the fourth, and so on and so forth).</div>
+
+		<div>
+		In OpenGL terms, LineStrip is the classic GL_LINE_STRIP and LinePieces is the equivalent to GL_LINES.
+		</div>
+
+		<h2>Methods</h2>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Mesh.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Mesh.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Mesh.html	(revision 28897)
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">Base class for Mesh objects, such as [page:MorphAnimMesh] and [page:SkinnedMesh].</div>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [page:Geometry geometry], [page:Material material] )</h3>
+
+		<div>
+		geometry â An instance of [page:Geometry].<br />
+		material â An instance of [page:Material] (optional).
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Geometry geometry]</h3>
+
+		<div>An instance of [page:Geometry], defining the object's structure.</div>
+
+		<h3>.[page:Material material]</h3>
+
+		<div>An instance of [page:Material], defining the object's appearance. Default is a [page:MeshBasicMaterial] with wireframe mode enabled and randomised colour.</div>
+
+
+		<h2>Methods</h2>
+
+		<h3>.getMorphTargetIndexByName( [page:String name] )</h3>
+		<div>
+		name â a morph target name<br />
+		</div>
+
+		Returns the index of a morph target defined by name.
+
+		<h3>.updateMorphTargets() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/MorphAnimMesh.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/MorphAnimMesh.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/MorphAnimMesh.html	(revision 28897)
@@ -0,0 +1,144 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo geometry], [page:todo material])</h3>
+		<div>
+		geometry -- todo <br />
+		material -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:boolean directionBackwards]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number direction]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number endKeyframe]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean mirroredLoop]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number startKeyframe]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number lastKeyframe]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number length]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number time]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number duration]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number currentKeyframe]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.setDirectionForward() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.playAnimation([page:todo label], [page:todo fps]) [page:todo]</h3>
+		<div>
+		label -- todo <br />
+		fps -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setFrameRange([page:todo start], [page:todo end]) [page:todo]</h3>
+		<div>
+		start -- todo <br />
+		end -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setDirectionBackward() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.parseAnimations() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.updateAnimation([page:todo delta]) [page:todo]</h3>
+		<div>
+		delta -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setAnimationLabel([page:todo label], [page:todo start], [page:todo end]) [page:todo]</h3>
+		<div>
+		label -- todo <br />
+		start -- todo <br />
+		end -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/ParticleSystem.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/ParticleSystem.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/ParticleSystem.html	(revision 28897)
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A class for displaying particles in the form of variable size points. For example, if using the [page:WebGLRenderer], the particles are displayed using GL_POINTS.</div>
+
+
+		<h2>Constructor</h2>
+		
+
+		<h3>[name]( [page:Geometry geometry], [page:Material material] )</h3>
+
+		<div>
+		geometry â An instance of [page:Geometry].<br />
+		material â An instance of [page:Material] (optional).
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Geometry geometry]</h3>
+
+		<div>An instance of [page:Geometry], where each vertex designates the position of a particle in the system.</div>
+
+		<h3>.[page:Material material]</h3>
+
+		<div>An instance of [page:Material], defining the object's appearance. Default is a [page:ParticleSystemMaterial] with randomised colour.</div>
+
+		<h3>.[page:Boolean frustrumCulled]</h3>
+		
+		<div>Specifies whether the particle system will be culled if it's outside the camera's frustum. By default this is set to false.</div>
+
+		<h3>.[page:boolean sortParticles]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/SkinnedMesh.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/SkinnedMesh.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/SkinnedMesh.html	(revision 28897)
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo geometry], [page:todo material], [page:todo useVertexTexture])</h3>
+		<div>
+		geometry -- todo <br />
+		material -- todo <br />
+		useVertexTexture -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:array bones]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Matrix4 identityMatrix]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean useVertexTexture]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:array boneMatrices]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.pose() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.addBone([page:todo bone]) [page:todo]</h3>
+		<div>
+		bone -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Sprite.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Sprite.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/Sprite.html	(revision 28897)
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]([page:todo material])</h3>
+		<div>
+		material -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:SpriteMaterial material]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Float rotation]</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/objects/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/CanvasRenderer.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/CanvasRenderer.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/CanvasRenderer.html	(revision 28897)
@@ -0,0 +1,137 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">The Canvas renderer displays your beautifully crafted scenes <em>not</em> using WebGL,
+                  but draws it using the (slower) <a href="http://www.w3.org/html/wg/drafts/2dcontext/html5_canvas/">Canvas 2D Context</a> API.</div>
+		<div class="desc">This renderer can be a nice fallback from [page:WebGLRenderer] for simple scenes:</div>
+		<code>
+		if (window.WebGLRenderingContext)
+			renderer = new THREE.WebGLRenderer();
+		else
+			renderer = new THREE.CanvasRenderer();
+		</code>
+		<div class="desc">
+			Note: both WebGLRenderer and CanvasRenderer are embedded in the web page using an HTML5 &lt;canvas&gt; tag.
+			The "Canvas" in CanvasRenderer means it uses Canvas 2D instead of WebGL.<br />
+			Don't confuse either CanvasRenderer with the SoftwareRenderer example, which simulates a screen buffer in a Javascript array.
+		</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo parameters])</h3>
+		<div>
+		parameters -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+		<h3>.[page:object info]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object domElement]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number devicePixelRatio]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean autoClear]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean sortObjects]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:boolean sortElements]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+		<h3>.getMaxAnisotropy() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.render([page:todo scene], [page:todo camera]) [page:todo]</h3>
+		<div>
+		scene -- todo <br />
+		camera -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.clear() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.setClearColor([page:todo color], [page:todo alpha]) [page:todo]</h3>
+		<div>
+		color -- todo <br />
+		alpha -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setFaceCulling() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.supportsVertexTextures() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.setSize([page:todo width], [page:todo height], [page:todo updateStyle]) [page:todo]</h3>
+		<div>
+		width -- todo <br />
+		height -- todo <br />
+		updateStyle -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setClearColorHex([page:todo hex], [page:todo alpha]) [page:todo]</h3>
+		<div>
+		hex -- todo <br />
+		alpha -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderTarget.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderTarget.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderTarget.html	(revision 28897)
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo width], [page:todo height], [page:todo options])</h3>
+		<div>
+		width -- todo <br />
+		height -- todo <br />
+		options -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h3>[name]()</h3>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h2>Methods</h2>
+
+		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderTargetCube.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderTargetCube.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderTargetCube.html	(revision 28897)
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo width], [page:todo height], [page:todo options])</h3>
+		<div>
+		width -- todo <br />
+		height -- todo <br />
+		options -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h3>[name]()</h3>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderer.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderer.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/WebGLRenderer.html	(revision 28897)
@@ -0,0 +1,364 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">The WebGL renderer displays your beautifully crafted scenes using WebGL, if your device supports it.</div>
+		<div class="desc">This renderer has way better performance than [page:CanvasRenderer].</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Object parameters] )</h3>
+
+		<div>parameters is an optional object with properties defining the renderer's behaviour. The constructor also accepts no parameters at all. In all cases, it will assume sane defaults when parameters are missing.</div>
+
+		<div>
+		canvas â A [page:Canvas] where the renderer draws its output.<br />
+		precision â shader precision. Can be *"highp"*, *"mediump"* or *"lowp"*.<br />
+		alpha â [page:Boolean], default is *false*.<br />
+		premultipliedAlpha â [page:Boolean], default is *true*.<br />
+		antialias â [page:Boolean], default is *false*.<br />
+		stencil â [page:Boolean], default is *true*.<br />
+		preserveDrawingBuffer â [page:Boolean], default is *false*.<br />
+		maxLights â [page:Integer], default is *4*.<br />
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:DOMElement domElement]</h3>
+
+		<div>A [page:Canvas] where the renderer draws its output.<br />
+		This is automatically created by the renderer in the constructor (if not provided already); you just need to add it to your page.</div>
+
+		<h3>.[page:todo context]</h3>
+
+		<div>The HTML5 Canvas's 'webgl' context obtained from the canvas where the renderer will draw.</div>
+
+		<h3>.[page:Boolean autoClear]</h3>
+
+		<div>Defines whether the renderer should automatically clear its output before rendering.</div>
+
+
+		<h3>.[page:Boolean autoClearColor]</h3>
+
+		<div>If autoClear is true, defines whether the renderer should clear the color buffer. Default is true.</div>
+
+
+		<h3>.[page:Boolean autoClearDepth]</h3>
+
+		<div>If autoClear is true, defines whether the renderer should clear the depth buffer. Default is true.</div>
+
+
+		<h3>.[page:Boolean autoClearStencil]</h3>
+
+		<div>If autoClear is true, defines whether the renderer should clear the stencil buffer. Default is true.</div>
+
+
+		<h3>.[page:Boolean sortObjects]</h3>
+
+		<div>Defines whether the renderer should sort objects. Default is true.</div>
+
+		<div>Note: Sorting is used to attempt to properly render objects that have some degree of transparency.  By definition, sorting objects may not work in all cases.  Depending on the needs of application, it may be neccessary to turn off sorting and use other methods to deal with transparency rendering e.g. manually determining the object rendering order.</div>
+
+
+		<h3>.[page:Boolean autoUpdateObjects]</h3>
+
+		<div>Defines whether the renderer should auto update objects. Default is true.</div>
+
+		<div>TODO</div>
+
+		<!-- Physically based shading -->
+
+		<h3>.[page:Boolean gammaInput]</h3>
+
+		<div>Default is false. TODO</div>
+
+
+		<h3>.[page:Boolean gammaOutput]</h3>
+
+		<div>Default is false. TODO</div>
+
+
+		<h3>.[page:Boolean shadowMapEnabled]</h3>
+
+		<div>Default is false. TODO</div>
+
+
+		<h3>.[page:Boolean shadowMapAutoUpdate]</h3>
+
+		<div>Default is true. TODO</div>
+
+
+		<h3>.[page:Integer shadowMapType]</h3>
+
+		<div>Defines shadow map type (unfiltered, percentage close filtering, percentage close filtering with bilinear filtering in shader)</div>
+		<div>Options are THREE.BasicShadowMap, THREE.PCFShadowMap, THREE.PCFSoftShadowMap. Default is THREE.PCFShadowMap.</div>
+
+
+		<h3>.[page:Boolean shadowMapCullFace]</h3>
+
+		<div>Default is true. TODO</div>
+
+
+		<h3>.[page:Boolean shadowMapDebug]</h3>
+
+		<div>Default is false. TODO</div>
+
+
+		<h3>.[page:Boolean shadowMapCascade]</h3>
+
+		<div>Default is false. TODO</div>
+
+
+		<h3>.[page:Integer maxMorphTargets]</h3>
+
+		<div>Default is 8. TODO</div>
+
+
+		<h3>.[page:Integer maxMorphNormals]</h3>
+
+		<div>Default is 4. TODO</div>
+
+
+		<h3>.[page:Boolean autoScaleCubemaps]</h3>
+
+		<div>Default is true. TODO</div>
+
+
+		<h3>.[page:Boolean renderPluginsPre]</h3>
+
+		<div>An array with render plugins to be applied before rendering.</div>
+		<div>Default is an empty array, or [].</div>
+
+
+		<h3>.[page:Boolean renderPluginsPost]</h3>
+
+		<div>An array with render plugins to be applied after rendering.</div>
+		<div>Default is an empty array, or [].</div>
+
+
+		<h3>.[page:Object info]</h3>
+
+		<div>An object with a series of statistical information about the graphics board memory and the rendering process. Useful for debugging or just for the sake of curiosity. The object contains the following fields:</div>
+		<div>
+		<ul>
+			<li>memory:
+				<ul>
+					<li>programs</li>
+					<li>geometries</li>
+					<li>textures</li>
+				</ul>
+			</li>
+			<li>render:
+				<ul>
+					<li>calls</li>
+					<li>vertices</li>
+					<li>faces</li>
+					<li>points</li>
+				</ul>
+			</li>
+		</ul>
+		</div>
+
+		<h3>.[page:ShadowMapPlugin shadowMapPlugin]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number shadowMapCullFace]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number devicePixelRatio]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h3>.getContext()</h3>
+		<div>
+		Return the WebGL context.
+		</div>
+
+		<h3>.supportsVertexTextures()</h3>
+		<div>
+		Return a [page:Boolean] true if the context supports vertex textures.
+		</div>
+
+
+		<h3>.setSize( [page:Integer width], [page:Integer height] )</h3>
+		<div>Resizes the output canvas to (width, height), and also sets the viewport to fit that size, starting in (0, 0).</div>
+
+		<h3>.setViewport( [page:Integer x], [page:Integer y], [page:Integer width], [page:Integer height] )</h3>
+		<div>Sets the viewport to render from (x, y) to (x + width, y + height).</div>
+
+
+		<h3>.setScissor( [page:Integer x], [page:Integer y], [page:Integer width], [page:Integer height] )</h3>
+		<div>Sets the scissor area from (x, y) to (x + width, y + height).</div>
+
+		<h3>.enableScissorTest( [page:Boolean enable] )</h3>
+		<div>Enable the scissor test. When this is enabled, only the pixels within the defined scissor area will be affected by further renderer actions.</div>
+
+		<h3>.setClearColor( [page:Color color], [page:Float alpha] )</h3>
+		<div>Sets the clear color and opacity.</div>
+
+		<code>// Creates a renderer with red background
+		var renderer = new THREE.WebGLRenderer();
+		renderer.setSize(200, 100);
+		renderer.setClearColor(0xff0000, 1);
+		</code>
+
+		<h3>.getClearColor() [page:Color]</h3>
+		<div>Returns a [page:Color THREE.Color] instance with the current clear color.</div>
+
+		<h3>.getClearAlpha() [page:Float]</h3>
+		<div>Returns a [page:Float float] with the current clear alpha. Ranges from 0 to 1.</div>
+
+		<h3>.clear( [page:Boolean color], [page:Boolean depth], [page:Boolean stencil] )</h3>
+		<div>Tells the renderer to clear its color, depth or stencil drawing buffer(s).</div>
+		<div>If no parameters are passed, no buffer will be cleared.</div>
+
+		<h3>.addPostPlugin( plugin )</h3>
+		<div>Initialises the postprocessing plugin, and adds it to the renderPluginsPost array.</div>
+
+		<h3>.addPrePlugin( plugin )</h3>
+		<div>Initialises the preprocessing plugin, and adds it to the renderPluginsPre array.</div>
+
+		<h3>.updateShadowMap( [page:Scene scene], [page:Camera camera] )</h3>
+		<div>scene â an instance of [page:Scene]<br />
+		camera â an instance of [page:Camera]</div>
+		<div>Tells the shadow map plugin to update using the passed scene and camera parameters.</div>
+
+
+		<h3>.renderBufferImmediate( [page:Object3D object], [page:??? program], [page:??? shading] )</h3>
+		<div>object â an instance of [page:Object3D]]<br />
+		program â an instance of ???<br />
+		shading â an instance of ???<br />
+		</div>
+		<div>TODO.</div>
+
+
+		<h3>.renderBufferDirect(  camera, lights, fog, material, geometryGroup, object )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.renderBuffer( camera, lights, fog, material, geometryGroup, object )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.render( [page:Scene scene], [page:Camera camera], [page:WebGLRenderTarget renderTarget], [page:Boolean forceClear] )</h3>
+		<div>Render a scene using a camera.</div>
+		<div>The render is done to the renderTarget (if specified) or to the canvas as usual.</div>
+		<div>If forceClear is true, the canvas will be cleared before rendering, even if the renderer's autoClear property is false.</div>
+
+
+		<h3>.renderImmediateObject( camera, lights, fog, material, object )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.initWebGLObjects( [page:Scene scene] )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.initMaterial( material, lights, fog, object )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.setFaceCulling( cullFace, frontFace )</h3>
+		<div>
+		[page:String cullFace] â "back", "front", "front_and_back", or false.<br />
+		[page:String frontFace] â "ccw" or "cw<br />
+		</div>
+		<div>Used for setting the gl frontFace, cullFace states in the GPU, thus enabling/disabling face culling when rendering.</div>
+		<div>If cullFace is false, culling will be disabled.</div>
+
+
+		<h3>.setDepthTest( depthTest )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.setDepthWrite( depthWrite )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.setBlending( blending, blendEquation, blendSrc, blendDst )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.setTexture( texture, slot )</h3>
+		<div>TODO.</div>
+
+
+		<h3>.setRenderTarget( renderTarget )</h3>
+		<div>TODO.</div>
+
+
+
+		<h3>.supportsCompressedTextureS3TC() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.getMaxAnisotropy() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.getPrecision() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.setMaterialFaces([page:todo material]) [page:todo]</h3>
+		<div>
+		material -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.supportsStandardDerivatives() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.supportsFloatTextures() [page:todo]</h3>
+		<div>
+		todo
+		</div>
+
+		<h3>.clearTarget([page:todo renderTarget], [page:todo color], [page:todo depth], [page:todo stencil]) [page:todo]</h3>
+		<div>
+		renderTarget -- todo <br />
+		color -- todo <br />
+		depth -- todo <br />
+		stencil -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.setClearColorHex([page:todo hex], [page:todo alpha]) [page:todo]</h3>
+		<div>
+		hex -- todo <br />
+		alpha -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableFace3.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableFace3.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableFace3.html	(revision 28897)
@@ -0,0 +1,104 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h3>[name]()</h3>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:array vertexNormalsModelView]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 normalModel]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object color]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object material]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:array uvs]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:RenderableVertex v1]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:RenderableVertex v2]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:RenderableVertex v3]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 normalModelView]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 centroidModel]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:number vertexNormalsLength]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object z]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:array vertexNormalsModel]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableLine.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableLine.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableLine.html	(revision 28897)
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h3>[name]()</h3>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:RenderableVertex v1]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:RenderableVertex v2]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object z]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object material]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableObject.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableObject.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableObject.html	(revision 28897)
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h3>[name]()</h3>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:object object]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object z]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableParticle.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableParticle.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableParticle.html	(revision 28897)
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h3>[name]()</h3>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:Vector2 scale]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object material]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object object]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object y]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object x]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object rotation]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:object z]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableVertex.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableVertex.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/RenderableVertex.html	(revision 28897)
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h3>[name]()</h3>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h3>.[page:boolean visible]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector4 positionScreen]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h3>.[page:Vector3 positionWorld]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+
+
+		<h3>.copy([page:todo vertex]) [page:todo]</h3>
+		<div>
+		vertex -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/renderables/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/ShaderChunk.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/ShaderChunk.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/ShaderChunk.html	(revision 28897)
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Shader chunks for WebLG Shader library</div>
+
+
+
+		<h2>Properties</h2>
+
+
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/ShaderLib.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/ShaderLib.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/ShaderLib.html	(revision 28897)
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Webgl Shader Library for three.js</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/UniformsLib.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/UniformsLib.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/UniformsLib.html	(revision 28897)
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Uniforms library for shared webgl shaders</div>
+
+
+
+		<h2>Properties</h2>
+
+
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/UniformsUtils.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/UniformsUtils.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/UniformsUtils.html	(revision 28897)
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../../list.js"></script>
+		<script src="../../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Uniform Utilities. Support merging and cloning of uniform variables</div>
+
+		<h2>Properties</h2>
+
+
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/renderers/shaders/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/Fog.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/Fog.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/Fog.html	(revision 28897)
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">This class contains the parameters that define linear fog, i.e., that grows linearly denser with the distance.</div>
+
+
+		<h2>Constructor</h2>
+		
+
+		<h3>[name]( [page:Integer hex], [page:Float near], [page:Float far] )</h3>
+		<div>The hex parameter is passed to the [page:Color] constructor to set the color property.  Hex can be a hexadecimal integer or a CSS-style string.</div>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:String name]</h3>
+		<div>Default is the empty string.</div>
+
+		<h3>.[page:Color color]</h3>
+		<div>Fog color.  Example: If set to black, far away objects will be rendered black.</div>
+
+		<h3>.[page:Float near]</h3>
+		<div>The minimum distance to start applying fog. Objects that are less than 'near' units from the active camera won't be affected by fog.</div>
+		<div>Default is 1.</div>
+		
+		<h3>.[page:Float far]</h3>
+		<div>The maximum distance at which fog stops being calculated and applied. Objects that are more than 'far' units away from the active camera won't be affected by fog.</div>
+		<div>Default is 1000.</div>
+		
+		<h2>Methods</h2>
+
+		<h3>.clone() [page:Fog]</h3>
+		<div>Returns a copy of this.</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/FogExp2.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/FogExp2.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/FogExp2.html	(revision 28897)
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">This class contains the parameters that define exponential fog, i.e., that grows exponentially denser with the distance.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Integer hex], [page:Float density])</h3>
+
+		<div>The hex parameter is passed to the [page:Color] constructor to set the color property.  Hex can be a hexadecimal integer or a CSS-style string.</div>
+		<h2>Properties</h2>
+
+		<h3>.[page:String name]</h3>
+		<div>Default is the empty string.</div>
+
+		<h3>.[page:Color color]</h3>
+		<div>Fog color. Example: If set to black, far away objects will be rendered black.</div>
+
+		<h3>.[page:Float density]</h3>
+		<div>Defines how fast the fog will grow dense.</div>
+		<div>Default is 0.00025.</div>
+
+		<h2>Methods</h2>
+		
+		<h3>.clone() [page:FogExp2]</h3>
+		<div>Returns a copy of this.</div>
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/Scene.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/Scene.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/Scene.html	(revision 28897)
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Object3D] &rarr;
+		<h1>[name]</h1>
+
+		<div class="desc">Scenes allow you to set up what and where is to be rendered by three.js. This is where you place objects, lights and cameras.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Fog fog]</h3>
+		
+		<div>A [page:Fog fog] instance defining the type of fog that affects everything rendered in the scene. Default is null.</div>
+		
+		<h3>.[page:Material overrideMaterial]</h3>
+		
+		<div>If not null, it will force everything in the scene to be rendered with that material. Default is null.</div>
+		
+		<h3>.[page:Boolean matrixAutoUpdate]</h3>
+		
+		<div>??? - Default is false.</div>
+		
+		<h3>.[page:boolean autoUpdate]</h3>
+		<div>
+		todo
+		</div> 
+
+		<h2>Methods</h2>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/scenes/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/CompressedTexture.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/CompressedTexture.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/CompressedTexture.html	(revision 28897)
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		[page:Texture] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo mipmaps], [page:todo width], [page:todo height], [page:todo format], [page:todo type], [page:todo mapping], [page:todo wrapS], [page:todo wrapT], [page:todo magFilter], [page:todo minFilter], [page:todo anisotropy])</h3>
+		<div>
+		mipmaps -- todo <br />
+		width -- todo <br />
+		height -- todo <br />
+		format -- todo <br />
+		type -- todo <br />
+		mapping -- todo <br />
+		wrapS -- todo <br />
+		wrapT -- todo <br />
+		magFilter -- todo <br />
+		minFilter -- todo <br />
+		anisotropy -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/DataTexture.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/DataTexture.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/DataTexture.html	(revision 28897)
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">todo</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]([page:todo data], [page:todo width], [page:todo height], [page:todo format], [page:todo type], [page:todo mapping], [page:todo wrapS], [page:todo wrapT], [page:todo magFilter], [page:todo minFilter], [page:todo anisotropy])</h3>
+		<div>
+		data -- todo <br />
+		width -- todo <br />
+		height -- todo <br />
+		format -- todo <br />
+		type -- todo <br />
+		mapping -- todo <br />
+		wrapS -- todo <br />
+		wrapT -- todo <br />
+		magFilter -- todo <br />
+		minFilter -- todo <br />
+		anisotropy -- todo
+		</div>
+		<div>
+		todo
+		</div>
+
+
+		<h2>Properties</h2>
+
+
+
+		<h2>Methods</h2>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/Texture.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/Texture.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/Texture.html	(revision 28897)
@@ -0,0 +1,152 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">Create a texture to apply to a surface or as a reflection or refraction map.</div>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( image, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy )</h3>
+
+		<h2>Example</h2>
+
+		<code>
+		// load a texture, set wrap mode to repeat
+		var texture = THREE.ImageUtils.loadTexture( "textures/water.jpg" );
+		texture.wrapS = THREE.RepeatWrapping;
+		texture.wrapT = THREE.RepeatWrapping;
+		texture.repeat.set( 4, 4 );
+		</code>
+
+		<h2>Properties</h2>
+
+		<h3>.[page:Image image]</h3>
+		<div>
+		An Image object, typically created using the ImageUtils or [page:ImageLoader ImageLoader] classes. The Image object can include an image (e.g., PNG, JPG, GIF, DDS), video (e.g., MP4, OGG/OGV), or set of six images for a cube map. To use video as a texture you need to have a playing HTML5 video element as a source for your texture image and continuously update this texture as long as video is playing.
+		</div>
+
+		<h3>.[page:object mapping]</h3>
+		<div>
+		How the image is applied to the object. An object type of THREE.UVMapping() is the default, where the U,V coordinates are used to apply the map, and a single texture is expected. The other types are THREE.CubeReflectionMapping, for cube maps used as a reflection map; THREE.CubeRefractionMapping, refraction mapping; THREE.SphericalReflectionMapping, a spherical reflection map projection; and THREE.SphericalRefractionMapping.
+		</div>
+
+		<h3>.[page:number wrapS]</h3>
+		<div>
+		The default is THREE.ClampToEdgeWrapping, where the edge is clamped to the outer edge texels. The other two choices are THREE.RepeatWrapping and THREE.MirroredRepeatWrapping.
+		</div>
+
+		<h3>.[page:number wrapT]</h3>
+		<div>
+		The default is THREE.ClampToEdgeWrapping, where the edge is clamped to the outer edge texels. The other two choices are THREE.RepeatWrapping and THREE.MirroredRepeatWrapping.
+		</div>
+
+		<h3>.[page:number magFilter]</h3>
+		<div>
+		How the texture is sampled when a texel covers more than one pixel. The default is THREE.LinearFilter, which takes the four closest texels and bilinearly interpolates among them. The other option is THREE.NearestFilter, which uses the value of the closest texel.
+		</div>
+
+		<h3>.[page:number minFilter]</h3>
+		<div>
+		How the texture is sampled when a texel covers less than one pixel. The default is THREE.LinearMipMapLinearFilter, which uses mipmapping and a trilinear filter. Other choices are THREE.NearestFilter, THREE.NearestMipMapNearestFilter, THREE.NearestMipMapLinearFilter, THREE.LinearFilter, and THREE.LinearMipMapNearestFilter. These vary whether the nearest texel or nearest four texels are retrieved on the nearest mipmap or nearest two mipmaps. Interpolation occurs among the samples retrieved.
+		</div>
+
+		<h3>.[page:number format]</h3>
+		<div>
+		The default is THREE.RGBAFormat for the texture. Other formats are: THREE.AlphaFormat, THREE.RGBFormat, THREE.LuminanceFormat, and THREE.LuminanceAlphaFormat. There are also compressed texture formats, if the S3TC extension is supported: THREE.RGB_S3TC_DXT1_Format, THREE.RGBA_S3TC_DXT1_Format, THREE.RGBA_S3TC_DXT3_Format, and THREE.RGBA_S3TC_DXT5_Format.
+		</div>
+
+		<h3>.[page:number type]</h3>
+		<div>
+		The default is THREE.UnsignedByteType. Other valid types (as WebGL allows) are THREE.ByteType, THREE.ShortType, THREE.UnsignedShortType, THREE.IntType, THREE.UnsignedIntType, THREE.FloatType, THREE.UnsignedShort4444Type, THREE.UnsignedShort5551Type, and THREE.UnsignedShort565Type.
+		</div>
+
+		<h3>.[page:number anisotropy]</h3>
+		<div>
+		The number of samples taken along the axis through the pixel that has the highest density of texels. By default, this value is 1. A higher value gives a less blurry result than a basic mipmap, at the cost of more texture samples being used. Use renderer.getMaxAnisotropy() to find the maximum valid anisotropy value for the GPU; this value is usually a power of 2.
+		</div>
+
+		<h3>.[page:boolean needsUpdate]</h3>
+		<div>
+		If a texture is changed after creation, set this flag to true so that the texture is properly set up. Particularly important for setting the wrap mode.
+		</div>
+
+		<h3>.[page:Vector2 repeat]</h3>
+		<div>
+		How many times the texture is repeated across the surface, in each direction U and V.
+		</div>
+
+		<h3>.[page:Vector2 offset]</h3>
+		<div>
+		How much a single repetition of the texture is offset from the beginning, in each direction U and V. Typical range is 0.0 to 1.0.
+		</div>
+
+		<h3>.[page:string name]</h3>
+		<div>
+		Given name of the texture, empty string by default.
+		</div>
+
+		<h3>.[page:boolean generateMipmaps]</h3>
+		<div>
+		Whether to generate mipmaps (if possible) for a texture. True by default.
+		</div>
+
+		<h3>.[page:boolean flipY]</h3>
+		<div>
+		False by default, set this to true if the texture needs to be flipped vertically.
+		</div>
+
+		<h3>.[page:array mipmaps]</h3>
+		<div>
+		Array of mipmaps generated.
+		</div>
+
+		<h3>.[page:number unpackAlignment]</h3>
+		<div>
+		4 by default. Specifies the alignment requirements for the start of each pixel row in memory. The allowable values are 1 (byte-alignment), 2 (rows aligned to even-numbered bytes), 4 (word-alignment), and 8 (rows start on double-word boundaries). See <a href="http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml">glPixelStorei</a> for more information.
+		</div>
+
+		<h3>.[page:boolean premultiplyAlpha]</h3>
+		<div>
+		False by default, which is the norm for PNG images. Set to true if the RGB values have been stored premultiplied by alpha.
+		</div>
+
+		<h3>.[page:object onUpdate]</h3>
+		<div>
+		A callback function, called when the texture is updated (e.g., when needsUpdate has been set to true and then the texture is used).
+		</div>
+
+		<h3>.[page:number id]</h3>
+		<div>
+		An internal ID number, starting from 0; should not be modified. THREE.TextureIdCount is the number of textures created so far.
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
+
+		<h3>.clone([page:todo texture]) [page:todo]</h3>
+		<div>
+		Make copy of texture. Note this is not a "deep copy", the image is shared.
+		</div>
+		<div>
+		todo
+		</div>
+
+		<h3>.dispose() [page:todo]</h3>
+		<div>
+		Call [page:EventDispatcher EventDispatcher].dispatchEvent with a 'dispose' event type.
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/api/textures/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/files/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/files/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/files/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/index.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/index.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/index.html	(revision 28897)
@@ -0,0 +1,183 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<title>three.js - documentation</title>
+		<style>
+			@font-face {
+				font-family: 'inconsolata';
+				src: url('files/inconsolata.woff') format('woff');
+				font-weight: normal;
+				font-style: normal;
+			}
+
+			html {
+				height: 100%;
+			}
+
+			body {
+				margin: 0;
+				padding: 0;
+				height: 100%;
+				color: #555;
+				font-family: 'inconsolata';
+				font-size: 15px;
+				line-height: 18px;
+				overflow: hidden;
+			}
+
+			a {
+				color: #2194CE;
+				text-decoration: none;
+			}
+
+			#panel	{
+				position: fixed;
+				width: 260px;
+				height: 100%;
+				overflow: auto;
+			}
+
+				#panel h1 {
+					margin-top: 20px;
+					margin-bottom: 40px;
+					margin-left: 20px;
+					font-size: 25px;
+					font-weight: normal;
+				}
+
+				#panel h2 {
+					color: #454545;
+					font-size: 18px;
+					font-weight: normal;
+
+					margin-top: 20px;
+					margin-left: 20px;
+				}
+
+				#panel h3 {
+					color: #666;
+					font-size: 16px;
+					font-weight: normal;
+
+					margin-top: 20px;
+					margin-left: 20px;
+				}
+
+				#panel ul {
+					list-style-type: none;
+					padding: 0px;
+					margin-left: 20px;
+				}
+
+			#viewer {
+				border: 0px;
+				margin-left: 260px;
+				width: -webkit-calc(100% - 260px);
+				width: -moz-calc(100% - 260px);
+				width: calc(100% - 260px);
+				height: 100%;
+				overflow: auto;
+			}
+
+		</style>
+	</head>
+	<body>
+		<div id="panel"></div>
+		<iframe id="viewer"></iframe>
+
+		<script src="list.js"></script>
+		<script>
+			var panel = document.getElementById( 'panel' );
+			var viewer = document.getElementById( 'viewer' );
+
+			var html = '<h1><a href="http://threejs.org">three.js</a> / docs</h1>';
+
+			var DELIMITER = '/';
+			var nameCategoryMap = {};
+
+			for ( var section in list ) {
+
+				html += '<h2>' + section + '</h2>';
+
+				html += '<ul>';
+
+				for ( var category in list[ section ] ) {
+
+					html += '<h3>' + category + '</h3>';
+
+					html += '<ul>';
+
+					for ( var i = 0; i < list[ section ][ category ].length; i ++ ) {
+
+						var page = list[ section ][ category ][ i ];
+
+						html += '<li><a href="javascript:goTo(\'' + section + '\', \'' + category + '\', \'' + page[ 0 ] + '\')">' + page[ 0 ] + '</a></li>';
+
+						nameCategoryMap[page[0]] = {
+							section: section,
+							category: category,
+							name: page[0]
+						};
+
+					}
+
+					html += '</ul>';
+
+				}
+
+				html += '</ul>';
+
+			}
+
+			panel.innerHTML += html;
+
+			function encodeUrl( path ) {
+
+				return path.replace(/\ \/\ /g, '.').replace(/\ /g, '_');
+
+			}
+
+			function decodeUrl( path ) {
+
+				return path.replace(/_/g, ' ').replace(/\./g, ' / ');
+
+			}
+
+			// Page loading
+
+			function goTo( section, category, name ) {
+
+				// Fully resolve links that only provide a name
+				if(arguments.length == 1) {
+					var location = nameCategoryMap[section];
+					section = location.section;
+					category = location.category;
+					name = location.name;
+				}
+
+				var title = 'three.js - documentation - ' + section + ' - ' + name;
+				var url = encodeUrl(section) + DELIMITER + encodeUrl( category ) + DELIMITER + encodeUrl(name);
+
+				window.location.hash = url;
+				window.document.title = title;
+
+
+				viewer.src = pages[ section ][ category ][ name ] + '.html';
+
+			}
+
+			function goToHash() {
+
+				var hash = window.location.hash.substring( 1 ).split(DELIMITER);
+				goTo( decodeUrl(hash[0]), decodeUrl(hash[1]), decodeUrl(hash[2]) );
+
+			}
+
+			window.addEventListener( 'hashchange', goToHash, false );
+
+			if ( window.location.hash.length > 0 ) goToHash();
+
+		</script>
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/list.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/list.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/list.js	(revision 28897)
@@ -0,0 +1,238 @@
+var list = {
+
+	"Manual": {
+		"Introduction": [
+			[ "Creating a scene", "manual/introduction/Creating-a-scene" ]
+		]
+	},
+
+	"Reference": {
+    "Constants": [
+      [ "CustomBlendingEquation", "api/constants/CustomBlendingEquations"],
+      [ "GLState", "api/constants/GLState"],
+      [ "Materials", "api/constants/Materials"],
+      [ "ShadowingTypes", "api/constants/ShadowingTypes"],
+      [ "Textures", "api/constants/Textures"]
+    ],
+
+		"Cameras": [
+			[ "Camera", "api/cameras/Camera" ],
+			[ "OrthographicCamera", "api/cameras/OrthographicCamera" ],
+			[ "PerspectiveCamera", "api/cameras/PerspectiveCamera" ]
+		],
+
+		"Core": [
+			[ "BufferGeometry", "api/core/BufferGeometry" ],
+			[ "Clock", "api/core/Clock" ],
+			[ "EventDispatcher", "api/core/EventDispatcher" ],
+			[ "Face3", "api/core/Face3" ],
+			[ "Geometry", "api/core/Geometry" ],
+			[ "Object3D", "api/core/Object3D" ],
+			[ "Projector", "api/core/Projector" ],
+			[ "Raycaster", "api/core/Raycaster" ],
+		],
+
+		"Lights": [
+			[ "Light", "api/lights/Light" ],
+			[ "AmbientLight", "api/lights/AmbientLight" ],
+			[ "AreaLight", "api/lights/AreaLight" ],
+			[ "DirectionalLight", "api/lights/DirectionalLight" ],
+			[ "HemisphereLight", "api/lights/HemisphereLight" ],
+			[ "PointLight", "api/lights/PointLight" ],
+			[ "SpotLight", "api/lights/SpotLight" ]
+		],
+
+
+		"Loaders": [
+			[ "Loader", "api/loaders/Loader" ],
+			[ "GeometryLoader", "api/loaders/GeometryLoader" ],
+			[ "ImageLoader", "api/loaders/ImageLoader" ],
+			[ "JSONLoader", "api/loaders/JSONLoader" ],
+			[ "LoadingManager", "api/loaders/LoadingManager" ],
+			[ "SceneLoader", "api/loaders/SceneLoader" ],
+			[ "TextureLoader", "api/loaders/TextureLoader" ],
+		],
+
+		"Materials": [
+			[ "Material", "api/materials/Material" ],
+			[ "LineBasicMaterial", "api/materials/LineBasicMaterial" ],
+			[ "LineDashedMaterial", "api/materials/LineDashedMaterial" ],
+			[ "MeshBasicMaterial", "api/materials/MeshBasicMaterial" ],
+			[ "MeshDepthMaterial", "api/materials/MeshDepthMaterial" ],
+			[ "MeshFaceMaterial", "api/materials/MeshFaceMaterial" ],
+			[ "MeshLambertMaterial", "api/materials/MeshLambertMaterial" ],
+			[ "MeshNormalMaterial", "api/materials/MeshNormalMaterial" ],
+			[ "MeshPhongMaterial", "api/materials/MeshPhongMaterial" ],
+			[ "ParticleSystemMaterial", "api/materials/ParticleSystemMaterial" ],
+			[ "ShaderMaterial", "api/materials/ShaderMaterial" ],
+			[ "SpriteMaterial", "api/materials/SpriteMaterial" ],
+			[ "SpriteCanvasMaterial", "api/materials/SpriteCanvasMaterial" ]
+		],
+
+		"Math": [
+			[ "Box2", "api/math/Box2" ],
+			[ "Box3", "api/math/Box3" ],
+			[ "Color", "api/math/Color" ],
+			[ "Euler", "api/math/Euler" ],
+			[ "Frustum", "api/math/Frustum" ],
+			[ "Line3", "api/math/Line3" ],
+			[ "Math", "api/math/Math" ],
+			[ "Matrix3", "api/math/Matrix3" ],
+			[ "Matrix4", "api/math/Matrix4" ],
+			[ "Plane", "api/math/Plane" ],
+			[ "Quaternion", "api/math/Quaternion" ],
+			[ "Ray", "api/math/Ray" ],
+			[ "Sphere", "api/math/Sphere" ],
+			[ "Spline", "api/math/Spline" ],
+			[ "Triangle", "api/math/Triangle" ],
+			[ "Vector2", "api/math/Vector2" ],
+			[ "Vector3", "api/math/Vector3" ],
+			[ "Vector4", "api/math/Vector4" ]
+		],
+
+		"Objects": [
+			[ "Bone", "api/objects/Bone" ],
+			[ "Line", "api/objects/Line" ],
+			[ "LOD", "api/objects/LOD" ],
+			[ "Mesh", "api/objects/Mesh" ],
+			[ "MorphAnimMesh", "api/objects/MorphAnimMesh" ],
+			[ "ParticleSystem", "api/objects/ParticleSystem" ],
+			[ "SkinnedMesh", "api/objects/SkinnedMesh" ],
+			[ "Sprite", "api/objects/Sprite" ]
+
+		],
+
+		"Renderers": [
+			[ "CanvasRenderer", "api/renderers/CanvasRenderer" ],
+			[ "WebGLRenderer", "api/renderers/WebGLRenderer" ],
+			[ "WebGLRenderTarget", "api/renderers/WebGLRenderTarget" ],
+			[ "WebGLRenderTargetCube", "api/renderers/WebGLRenderTargetCube" ]
+		],
+
+		"Renderers / Renderables": [
+			[ "RenderableFace3", "api/renderers/renderables/RenderableFace3" ],
+			[ "RenderableLine", "api/renderers/renderables/RenderableLine" ],
+			[ "RenderableObject", "api/renderers/renderables/RenderableObject" ],
+			[ "RenderableParticle", "api/renderers/renderables/RenderableParticle" ],
+			[ "RenderableVertex", "api/renderers/renderables/RenderableVertex" ]
+		],
+
+		"Renderers / Shaders": [
+			[ "ShaderChunk", "api/renderers/shaders/ShaderChunk" ],
+			[ "UniformsUtils", "api/renderers/shaders/UniformsUtils" ],
+			[ "UniformsLib", "api/renderers/shaders/UniformsLib" ],
+			[ "ShaderLib", "api/renderers/shaders/ShaderLib" ]
+		],
+
+		"Scenes": [
+			[ "Fog", "api/scenes/Fog" ],
+			[ "FogExp2", "api/scenes/FogExp2" ],
+			[ "Scene", "api/scenes/Scene" ]
+		],
+
+		"Textures": [
+			[ "CompressedTexture", "api/textures/CompressedTexture" ],
+			[ "DataTexture", "api/textures/DataTexture" ],
+			[ "Texture", "api/textures/Texture" ]
+		],
+
+		"Extras": [
+			[ "FontUtils", "api/extras/FontUtils" ],
+			[ "GeometryUtils", "api/extras/GeometryUtils" ],
+			[ "ImageUtils", "api/extras/ImageUtils" ],
+			[ "SceneUtils", "api/extras/SceneUtils" ]
+		],
+
+		"Extras / Animation": [
+			[ "Animation", "api/extras/animation/Animation" ],
+			[ "AnimationHandler", "api/extras/animation/AnimationHandler" ],
+			[ "AnimationMorphTarget", "api/extras/animation/AnimationMorphTarget" ],
+			[ "KeyFrameAnimation", "api/extras/animation/KeyFrameAnimation" ]
+		],
+
+		"Extras / Cameras": [
+			[ "CombinedCamera", "api/extras/cameras/CombinedCamera" ],
+			[ "CubeCamera", "api/extras/cameras/CubeCamera" ]
+		],
+
+		"Extras / Core": [
+			[ "Curve", "api/extras/core/Curve" ],
+			[ "CurvePath", "api/extras/core/CurvePath" ],
+			[ "Gyroscope", "api/extras/core/Gyroscope" ],
+			[ "Path", "api/extras/core/Path" ],
+			[ "Shape", "api/extras/core/Shape" ]
+		],
+
+		"Extras / Geometries": [
+			[ "CircleGeometry", "api/extras/geometries/CircleGeometry" ],
+			[ "ConvexGeometry", "api/extras/geometries/ConvexGeometry" ],
+			[ "CubeGeometry", "api/extras/geometries/CubeGeometry" ],
+			[ "CylinderGeometry", "api/extras/geometries/CylinderGeometry" ],
+			[ "ExtrudeGeometry", "api/extras/geometries/ExtrudeGeometry" ],
+			[ "IcosahedronGeometry", "api/extras/geometries/IcosahedronGeometry" ],
+			[ "LatheGeometry", "api/extras/geometries/LatheGeometry" ],
+			[ "OctahedronGeometry", "api/extras/geometries/OctahedronGeometry" ],
+			[ "ParametricGeometry", "api/extras/geometries/ParametricGeometry" ],
+			[ "PlaneGeometry", "api/extras/geometries/PlaneGeometry" ],
+			[ "PolyhedronGeometry", "api/extras/geometries/PolyhedronGeometry" ],
+			[ "ShapeGeometry", "api/extras/geometries/ShapeGeometry" ],
+			[ "SphereGeometry", "api/extras/geometries/SphereGeometry" ],
+			[ "TetrahedronGeometry", "api/extras/geometries/TetrahedronGeometry" ],
+			[ "TextGeometry", "api/extras/geometries/TextGeometry" ],
+			[ "TorusGeometry", "api/extras/geometries/TorusGeometry" ],
+			[ "TorusKnotGeometry", "api/extras/geometries/TorusKnotGeometry" ],
+			[ "TubeGeometry", "api/extras/geometries/TubeGeometry" ]
+		],
+
+		"Extras / Helpers": [
+			[ "ArrowHelper", "api/extras/helpers/ArrowHelper" ],
+			[ "AxisHelper", "api/extras/helpers/AxisHelper" ],
+			[ "CameraHelper", "api/extras/helpers/CameraHelper" ],
+			[ "DirectionalLightHelper", "api/extras/helpers/DirectionalLightHelper" ],
+			[ "GridHelper", "api/extras/helpers/GridHelper" ],
+			[ "HemisphereLightHelper", "api/extras/helpers/HemisphereLightHelper" ],
+			[ "PointLightHelper", "api/extras/helpers/PointLightHelper" ],
+			[ "SpotLightHelper", "api/extras/helpers/SpotLightHelper" ]
+		],
+
+		"Extras / Objects": [
+			[ "ImmediateRenderObject", "api/extras/objects/ImmediateRenderObject" ],
+			[ "LensFlare", "api/extras/objects/LensFlare" ],
+			[ "MorphBlendMesh", "api/extras/objects/MorphBlendMesh" ]
+		],
+
+		"Extras / Renderers / Plugins": [
+			[ "DepthPassPlugin", "api/extras/renderers/plugins/DepthPassPlugin" ],
+			[ "LensFlarePlugin", "api/extras/renderers/plugins/LensFlarePlugin" ],
+			[ "ShadowMapPlugin", "api/extras/renderers/plugins/ShadowMapPlugin" ],
+			[ "SpritePlugin", "api/extras/renderers/plugins/SpritePlugin" ]
+		],
+
+		"Extras / Shaders": [
+			[ "ShaderFlares", "api/extras/shaders/ShaderFlares" ],
+			[ "ShaderSprite", "api/extras/shaders/ShaderSprite" ]
+		]
+	}
+
+};
+
+var pages = {};
+
+for ( var section in list ) {
+
+	pages[ section ] = {};
+
+	for ( var category in list[ section ] ) {
+
+		pages[ section ][ category ] = {};
+
+		for ( var i = 0; i < list[ section ][ category ].length; i ++ ) {
+
+			var page = list[ section ][ category ][ i ];
+			pages[ section ][ category ][ page[ 0 ] ] = page[ 1 ];
+
+		}
+
+	}
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/introduction/Creating-a-scene.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/introduction/Creating-a-scene.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/introduction/Creating-a-scene.html	(revision 28897)
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<script src="../../list.js"></script>
+		<script src="../../page.js"></script>
+		<link type="text/css" rel="stylesheet" href="../../page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div>The goal of this section is to give a brief introduction to Three.js. We will start by setting up a scene, with a spinning cube. A working example is provided at the bottom of the page, if you get stuck, and need help.</div>
+
+		<h2>What is Three.js?</h2>
+
+		<div>If you're reading this, you probably have some understanding of what Three.js is, and what it helps you with, but let's try to describe it briefly anyway.</div>
+
+		<div>Three.js is a library that makes WebGL - 3D in the browser - very easy. While a simple cube in raw WebGL would turn out hundreds of lines of Javascript and shader code, a Three.js equivalent is only a fraction of that.</div>
+
+		<h2>Before we start</h2>
+		<div>Before you can use Three.js, you need somewhere to display it. Save the following HTML to a file on your computer, and open it in your browser.</div>
+
+		<code>
+		&lt;html&gt;
+			&lt;head&gt;
+				&lt;title&gt;My first Three.js app&lt;/title&gt;
+				&lt;style&gt;canvas { width: 100%; height: 100% }&lt;/style&gt;
+			&lt;/head&gt;
+			&lt;body&gt;
+				&lt;script src="https://rawgithub.com/mrdoob/three.js/master/build/three.js"&gt;&lt;/script&gt;
+				&lt;script&gt;
+					// Our Javascript will go here.
+				&lt;/script&gt;
+			&lt;/body&gt;
+		&lt;/html&gt;
+		</code>
+
+		<div>That's all. All the code below goes into the empty &lt;script&gt; tag.</div>
+
+		<h2>Creating the scene</h2>
+
+		<div>To actually be able to display anything with Three.js, we need three things: A scene, a camera, and a renderer so we can render the scene with the camera.</div>
+
+		<code>
+		var scene = new THREE.Scene();
+		var camera = new THREE.PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 1000 );
+
+		var renderer = new THREE.WebGLRenderer();
+		renderer.setSize( window.innerWidth, window.innerHeight );
+		document.body.appendChild( renderer.domElement );
+		</code>
+
+		<div>Let's take a moment to explain what's going on here. We have now set up the scene, our camera and the renderer. There are a few different cameras in Three.js, but we'll go more into that later. For now, let's use a PerspectiveCamera. The first attribute is the <strong>field of view</strong>.</div>
+
+		<div>The second one is the <strong>aspect ratio</strong>. You almost always want to use the width of the element divided by the height, or you'll get the same result as when you play old movies on a widescreen TV - the image looks squished.</div>
+
+		<div>The next two attributes are the <strong>near</strong> and <strong>far</strong> clipping plane. What that means, is that objects further away from the camera than the value of <strong>far</strong> or closer than <strong>near</strong> won't be rendered. You don't have to worry about this now, but you may want to use other values in your games to get better performance.</div>
+
+		<div>Next up is the renderer. This is where the magic happens. In addition to the WebGLRenderer we use here, Three.js comes with a few others, often used as fallbacks for users with older browsers or for those who don't have WebGL support for some reason.</div>
+
+		<div>In addition to creating the renderer instance, we also need to set the size at which we want it to render our app. It's a good idea to use the width and height of the area we want to fill with our game - in this case, the width and height of the browser window. For performance intensive games, you can also give <strong>setSize</strong> smaller values, like <strong>window.innerWidth/2</strong> and <strong>window.innerHeight/2</strong>, for half the resolution. This does not mean that the game will only fill half the window, but rather look a bit blurry and scaled up.</div>
+
+		<div>Last but not least, we add the <strong>renderer</strong> element to our HTML document. This is a &lt;canvas&gt; element the renderer uses to display the scene to us.</div>
+
+		<div><em>"That's all good, but where's that cube you promised?"</em> Let's add it now.</div>
+
+		<code>
+		var geometry = new THREE.CubeGeometry(1,1,1);
+		var material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } );
+		var cube = new THREE.Mesh( geometry, material );
+		scene.add( cube );
+
+		camera.position.z = 5;
+		</code>
+
+		<div>To create a cube, we need a <strong>CubeGeometry</strong>. This is an object that contains all the points (<strong>vertices</strong>) and fill (<strong>faces</strong>) of the cube. We'll explore this more in the future.</div>
+
+		<div>In addition to the geometry, we need a material to color it. Three.js comes with several materials, but we'll stick to the <strong>MeshBasicMaterial</strong> for now. All materials take an object of properties which will be applied to them. To keep things very simple, we only supply a color attribute of <strong>0x00ff00</strong>, which is green. This works the same way that colors work in CSS or Photoshop (<strong>hex colors</strong>).</div>
+
+		<div>The third thing we need is a <strong>Mesh</strong>. A mesh is an object that takes a geometry, and applies a material to it, which we then can insert to our scene, and move freely around.</div>
+
+		<div>By default, when we call <strong>scene.add()</strong>, the thing we add will be added to the coordinates <strong>(0,0,0)</strong>. This would cause both the camera and the cube to be inside each other. To avoid this, we simply move the camera out a bit.</div>
+
+		<h2>Rendering the scene</h2>
+
+		<div>If you copied the code from above into the HTML file we created earlier, you wouldn't be able to see anything. This is because we're not actually rendering anything yet. For that, we need what's called a <strong>render loop</strong>.</div>
+
+		<code>
+		function render() {
+			requestAnimationFrame(render);
+			renderer.render(scene, camera);
+		}
+		render();
+		</code>
+
+		<div>This will create a loop that causes the renderer to draw the scene 60 times per second. If you're new to writing games in the browser, you might say "why don't we just  create a <strong>setInterval</strong>? The thing is - we could, but <strong>requestAnimationFrame</strong> has a number of advantages. Perhaps the most important one is that it pauses when the user navigates to another browser tab, hence not wasting their precious processing power and battery life.</div>
+
+		<h2>Animating the cube</h2>
+
+		<div>If you insert all the code above into the file you created before we began, you should see a green box. Let's make it all a little more interesting by rotating it.</div>
+
+		<div>Add the following right above the <strong>renderer.render</strong> call in your <strong>render</strong> function:</div>
+
+		<code>
+		cube.rotation.x += 0.1;
+		cube.rotation.y += 0.1;
+		</code>
+
+		<div>This will be run every frame (60 times per second), and give the cube a nice rotation animation. Basically, anything you want to move or change while the game / app is running has to go through the render loop. You can of course call other functions from there, so that you don't end up with a <strong>render</strong> function that's hundreds of lines.
+		</div>
+
+		<h2>The result</h2>
+		<div>Congratulations! You have now completed your first Three.js application. It's simple, you have to start somewhere.</div>
+
+		<div>The full code is available below. Play around with it to get a better understanding of how it works.</div>
+
+		<code>
+		&lt;html&gt;
+			&lt;head&gt;
+				&lt;title&gt;My first Three.js app&lt;/title&gt;
+				&lt;style&gt;canvas { width: 100%; height: 100% }&lt;/style&gt;
+			&lt;/head&gt;
+			&lt;body&gt;
+				&lt;script src="three.min.js"&gt;&lt;/script&gt;
+				&lt;script&gt;
+					var scene = new THREE.Scene();
+					var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
+
+					var renderer = new THREE.WebGLRenderer();
+					renderer.setSize(window.innerWidth, window.innerHeight);
+					document.body.appendChild(renderer.domElement);
+
+					var geometry = new THREE.CubeGeometry(1,1,1);
+					var material = new THREE.MeshBasicMaterial({color: 0x00ff00});
+					var cube = new THREE.Mesh(geometry, material);
+					scene.add(cube);
+
+					camera.position.z = 5;
+
+					var render = function () {
+						requestAnimationFrame(render);
+
+						cube.rotation.x += 0.1;
+						cube.rotation.y += 0.1;
+
+						renderer.render(scene, camera);
+					};
+
+					render();
+				&lt;/script&gt;
+			&lt;/body&gt;
+		&lt;/html&gt;
+		</code>
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/introduction/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/introduction/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/manual/introduction/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/page.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/page.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/page.css	(revision 28897)
@@ -0,0 +1,87 @@
+@font-face {
+	font-family: 'inconsolata';
+	src: url('files/inconsolata.woff') format('woff');
+	font-weight: normal;
+	font-style: normal;
+}
+
+body {
+	margin: 25px 20px;
+	color: #555;
+	font-family: 'inconsolata';
+	font-size: 15px;
+	line-height: 18px;
+	overflow: auto;
+}
+
+a {
+	color: #1184CE;
+}
+
+h1 {
+	color: #333;
+	font-size: 25px;
+	font-weight: normal;
+
+	margin-top: 10px;
+}
+
+h2 {
+	color: #4B0;
+	font-size: 18px;
+	font-weight: normal;
+
+	margin-top: 40px;
+}
+
+h3 {
+	color: #000;
+	font-size: 16px;
+	font-weight: normal;
+
+	margin-top: 30px;
+}
+
+div {
+	/* padding-left: 30px; */
+	margin-bottom: 20px;
+}
+
+div.desc {
+	padding-left: 0px;
+}
+
+pre, code {
+	margin-top: 20px;
+	margin-bottom: 20px;
+}
+code {
+	display: block;
+	width: -webkit-calc( 100% - 40px );
+	width: -moz-calc( 100% - 40px );
+	width: calc( 100% - 40px );
+	padding: 20px;
+	white-space: pre-wrap;
+	background-color: #f9f9f9;
+	overflow: auto;
+}
+
+strong {
+	color: #000;
+	font-weight: normal;
+}
+
+#button {
+	position: fixed;
+	top: 20px;
+	right: 20px;
+	padding: 8px;
+	color: #fff;
+	background-color: #555;
+	opacity: 0.5;
+}
+
+	#button:hover {
+		cursor: pointer;
+		opacity: 1;
+	}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/page.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/page.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/page.js	(revision 28897)
@@ -0,0 +1,97 @@
+var onDocumentLoad = function ( event ) {
+
+	var path;
+	var pathname = window.location.pathname;
+	var section = /\/(manual|api)\//.exec( pathname )[ 1 ].toString().split( '.html' )[ 0 ];
+	var name = /[\-A-z0-9]+\.html/.exec( pathname ).toString().split( '.html' )[ 0 ];
+
+	if ( section == 'manual' ) {
+
+		name = name.replace(/\-/g, ' ');
+
+		path = pathname.replace( /\ /g, '-' );
+		path = /\/manual\/[-A-z0-9\/]+/.exec( path ).toString().substr( 8 );
+
+	} else {
+
+		path = /\/api\/[A-z0-9\/]+/.exec( pathname ).toString().substr( 5 );
+
+	}
+
+	var text = document.body.innerHTML;
+
+	text = text.replace(/\[name\]/gi, name);
+	text = text.replace(/\[path\]/gi, path);
+	text = text.replace(/\[page:(\w+)\]/gi, "[page:$1 $1]" ); // [page:name] to [page:name title]
+	text = text.replace(/\[page:(\w+) ([\w|\.]+)\]/gi, "<a href=\"javascript:window.parent.goTo('$1')\" title=\"$1\">$2</a>" ); // [page:name title]
+	text = text.replace(/\[link:([\w|\:|\/|\.|\-|\_]+)\]/gi, "[link:$1 $1]" ); // [link:url] to [link:url title]
+	text = text.replace(/\[link:([\w|\:|\/|\.|\-|\_|\(|\)]+) ([\w|\:|\/|\.|\-|\_ ]+)\]/gi, "<a href=\"$1\"  target=\"_blank\">$2</a>" ); // [link:url title]
+	text = text.replace(/\*([\w|\d|\"|\-|\(][\w|\d|\ |\-|\/|\+|\-|\(|\)|\=|\,|\.\"]*[\w|\d|\"|\)]|\w)\*/gi, "<strong>$1</strong>" ); // *
+
+	document.body.innerHTML = text;
+
+	// handle code snippets formatting
+
+	var elements = document.getElementsByTagName( 'code' );
+
+	for ( var i = 0; i < elements.length; i ++ ) {
+
+		var element = elements[ i ];
+
+		text = element.textContent.trim();
+		text = text.replace( /^\t\t/gm, '' );
+
+		element.textContent = text;
+
+	}
+
+	// Edit button
+
+	var button = document.createElement( 'div' );
+	button.id = 'button';
+	button.textContent = 'Edit this page';
+
+	button.addEventListener( 'click', function ( event ) {
+
+		window.open( 'https://github.com/mrdoob/three.js/blob/dev/docs/' + section + '/' + path + '.html' );
+
+	}, false );
+
+	document.body.appendChild( button );
+
+	// Syntax highlighting
+
+	var styleBase = document.createElement( 'link' );
+	styleBase.href = '../../prettify/prettify.css';
+	styleBase.rel = 'stylesheet';
+
+	var styleCustom = document.createElement( 'link' );
+	styleCustom.href = '../../prettify/threejs.css';
+	styleCustom.rel = 'stylesheet';
+
+	document.head.appendChild( styleBase );
+	document.head.appendChild( styleCustom );
+
+	var prettify = document.createElement( 'script' );
+	prettify.src = '../../prettify/prettify.js';
+
+	prettify.onload = function () {
+
+		var elements = document.getElementsByTagName( 'code' );
+
+		for ( var i = 0; i < elements.length; i ++ ) {
+
+			var e = elements[ i ];
+			e.className += ' prettyprint';
+
+		}
+
+		prettyPrint();
+
+	}
+
+	document.head.appendChild( prettify );
+
+};
+
+document.addEventListener( 'DOMContentLoaded', onDocumentLoad, false );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/prettify.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/prettify.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/prettify.css	(revision 28897)
@@ -0,0 +1,1 @@
+.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/prettify.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/prettify.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/prettify.js	(revision 28897)
@@ -0,0 +1,28 @@
+var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
+(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
+[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
+f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
+(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
+{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
+t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
+"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
+l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
+q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
+q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
+"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
+a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
+for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
+m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
+a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
+j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
+H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
+J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
+I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
+["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
+/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
+["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
+hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
+!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
+250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
+PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/readme.txt
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/readme.txt	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/readme.txt	(revision 28897)
@@ -0,0 +1,5 @@
+Prettify syntax highlighter:
+http://code.google.com/p/google-code-prettify/
+
+Code license - Apache License 2.0
+http://www.apache.org/licenses/LICENSE-2.0
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/threejs.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/threejs.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/docs/prettify/threejs.css	(revision 28897)
@@ -0,0 +1,15 @@
+pre .str, code .str { color: #8000ff; } /* string */
+pre .kwd, code .kwd { color: #30b030; } /* keyword */
+pre .com, code .com { color: #999999; } /* comment */
+pre .typ, code .typ { color: #2194ce; } /* type */
+pre .lit, code .lit { color: #ff0080; } /* literal */
+pre .pun, code .pun { color: #888888; } /* punctuation */
+pre .pln, code .pln { color: #444444; } /* plaintext */
+pre .dec, code .dec { color: #22c0c4; } /* decimal */
+
+
+
+pre.prettyprint, code.prettyprint {
+	background-color: #f9f9f9;
+	font-family: 'inconsolata';
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/dark.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/dark.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/dark.css	(revision 28897)
@@ -0,0 +1,199 @@
+﻿* {
+	vertical-align: middle;
+}
+
+	/* Webkit micro scrollbars */
+
+	::-webkit-scrollbar {
+		width:9px;
+	}
+
+	::-webkit-scrollbar-track {
+		-webkit-border-radius:5px;
+		border-radius:5px;
+		background:rgba(140,140,140,0.1);
+	}
+
+	::-webkit-scrollbar-thumb {
+		-webkit-border-radius:5px;
+		border-radius:5px;
+		background:rgba(140,140,140,0.2);
+	}
+
+	::-webkit-scrollbar-thumb:hover {
+		background:rgba(140,140,140,0.4);
+	}
+
+	::-webkit-scrollbar-thumb:window-inactive {
+		background:rgba(140,140,140,0.5);
+	}
+
+body {
+	font-family: Arial, sans-serif;
+	font-size: 14px;
+	margin: 0;
+	overflow: hidden;
+}
+
+hr {
+	border: 0px;
+	border-top: 1px solid #ccc;
+}
+
+button {
+	position: relative;
+}
+
+.Panel {
+
+	-moz-user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
+
+	/* No support for these yet */
+	-o-user-select: none;
+	user-select: none;
+}
+
+.FancySelect {
+	background: #222;
+	border: 1px solid #3C3C3C;
+	padding: 0;
+	cursor: default;
+	overflow: auto;
+	outline: none;
+}
+
+	.FancySelect .option {
+		padding: 4px;
+		white-space: nowrap;
+	}
+
+	.FancySelect .option.active {
+		background-color: #153C5E;
+	}
+
+input.Number {
+	color: #2A75B7;
+	font-size: 12px;							/** TODO: Use of !imporant is not ideal **/
+	background-color: transparent!important;	/* For now this is a quick fix a rendering issue due to inherited background */
+	border: 1px solid transparent;
+	padding: 2px;
+	cursor: col-resize;
+}
+
+#viewport {
+	position: absolute;
+	top: 32px;
+	left: 0px;
+	right: 300px;
+	bottom: 32px;
+}
+
+#menubar {
+	position: absolute;
+	width: 100%;
+	height: 32px;
+	background-color: #111;
+	padding: 0px;
+	margin: 0px;
+}
+
+	#menubar .menu {
+		float: left;
+		width: 50px;
+		cursor: pointer;
+	}
+
+	#menubar .Panel {
+		color: #888;
+	}
+
+		#menubar .menu .options {
+			display: none;
+			padding: 5px 0px;
+			background-color: #111;
+			width: 140px;
+			border-top: solid 1px #1D1D1D;
+		}
+
+		#menubar .menu:hover .options {
+			display: block;
+		}
+
+			#menubar .menu .options hr {
+				border-color: #444;
+			}
+
+			#menubar .menu .options .option {
+				color: #666;
+				background-color: transparent;
+				padding: 5px 10px;
+				margin: 0px !important;
+			}
+
+				#menubar .menu .options .option:hover {
+					color: #fff;
+					background-color: #08f;
+				}
+
+
+#sidebar {
+	position: absolute;
+	right: 0px;
+	top: 32px;
+	bottom: 0px;
+	width: 300px;
+	background-color: #111;
+	overflow: auto;
+}
+
+	#sidebar input,
+	#sidebar textarea,
+	#sidebar select {
+		background: #ccc;
+	}
+
+	#sidebar .Panel {
+		margin-bottom: 10px;
+	}
+
+	#sidebar > .Panel {
+		color: #888;
+		padding: 10px;
+		border-top: 1px solid #333;
+	}
+
+	#sidebar #outliner {
+		width: 100%;
+		height: 140px;
+		color: #868686;
+		font-size: 12px;
+	}
+
+	#sidebar .Panel.Material canvas {
+
+		border: solid 1px #5A5A5A;
+
+	}
+
+#toolbar {
+	position: absolute;
+	left: 0px;
+	right: 300px;
+	bottom: 0px;
+	height: 32px;
+	background-color: #111;
+	color: #333;
+}
+
+	#toolbar .Panel {
+		padding: 4px;
+		color: #888;
+	}
+
+	#toolbar button {
+		margin-right: 6px;
+	}
+
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/light.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/light.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/light.css	(revision 28897)
@@ -0,0 +1,166 @@
+﻿* {
+	vertical-align: middle;
+}
+
+body {
+	font-family: Arial, sans-serif;
+	font-size: 14px;
+	margin: 0;
+	overflow: hidden;
+}
+
+hr {
+	border: 0px;
+	border-top: 1px solid #ccc;
+}
+
+button {
+	position: relative;
+}
+
+.Panel {
+	-moz-user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
+
+	/* No support for these yet */
+	-o-user-select: none;
+	user-select: none;
+}
+
+.FancySelect {
+	background: #fff;
+	border: 1px solid #ccc;
+	padding: 0;
+	cursor: default;
+	overflow: auto;
+	outline: none;
+}
+
+	.FancySelect .option {
+		padding: 4px;
+		color: #666;
+		white-space: nowrap;
+	}
+
+	.FancySelect .option.active {
+		background-color: #f8f8f8;
+	}
+
+input.Number {
+	color: #0080f0;
+	font-size: 12px;							/** TODO: Use of !imporant is not ideal **/
+	background-color: transparent!important;	/* For now this is a quick fix a rendering issue due to inherited background */
+	border: 1px solid transparent;
+	padding: 2px;
+	cursor: col-resize;
+}
+
+#viewport {
+	position: absolute;
+	top: 32px;
+	left: 0px;
+	right: 300px;
+	bottom: 32px;
+}
+
+#menubar {
+	position: absolute;
+	width: 100%;
+	height: 32px;
+	background: #eee;
+	padding: 0px;
+	margin: 0px;
+}
+
+	#menubar .menu {
+		float: left;
+		width: 50px;
+		cursor: pointer;
+	}
+
+	#menubar .Panel {
+		color: #888;
+	}
+
+		#menubar .menu .options {
+			display: none;
+			padding: 5px 0px;
+			background: #eee;
+			width: 140px;
+		}
+
+		#menubar .menu:hover .options {
+			display: block;
+		}
+
+			#menubar .menu .options hr {
+				border-color: #ddd;
+			}
+
+			#menubar .menu .options .option {
+				color: #666;
+				background-color: transparent;
+				padding: 5px 10px;
+				margin: 0px !important;
+			}
+
+				#menubar .menu .options .option:hover {
+					color: #fff;
+					background-color: #08f;
+				}
+
+
+#sidebar {
+	position: absolute;
+	right: 0px;
+	top: 32px;
+	bottom: 0px;
+	width: 300px;
+	background: #eee;
+	overflow: auto;
+}
+
+	#sidebar input,
+	#sidebar textarea,
+	#sidebar select {
+		/* background: #ccc; */
+	}
+
+	#sidebar .Panel {
+		margin-bottom: 10px;
+	}
+
+	#sidebar > .Panel {
+		color: #888;
+		padding: 10px;
+		border-top: 1px solid #ccc;
+	}
+
+	#sidebar #outliner {
+		width: 100%;
+		height: 140px;
+		color: #444;
+		font-size: 12px;
+	}
+
+#toolbar {
+	position: absolute;
+	left: 0px;
+	right: 300px;
+	bottom: 0px;
+	height: 32px;
+	background: #eee;
+	color: #333;
+}
+
+	#toolbar .Panel {
+		padding: 4px;
+		color: #888;
+	}
+
+	#toolbar button {
+		margin-right: 6px;
+	}
+
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/types.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/types.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/css/types.css	(revision 28897)
@@ -0,0 +1,50 @@
+﻿.type {
+	position:relative;
+	top:-2px;
+	padding: 0px 2px;
+	color: #ddd;
+}
+.type:after {
+	content: '■';
+}
+
+.Scene {
+	color: #ccccff;
+}
+
+.Object3D {
+	color: #aaaaee;
+}
+
+.Mesh {
+	color: #8888ee;
+}
+
+/* */
+
+.PointLight {
+	color: #dddd00;
+}
+
+/* */
+
+.Geometry {
+	color: #88ff88;
+}
+
+.CubeGeometry {
+	color: #bbeebb;
+}
+.TorusGeometry {
+	color: #aaeeaa;	
+}
+
+/* */
+
+.Material {
+	color: #ff8888;
+}
+
+.MeshPhongMaterial {
+	color: #ffaa88;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/index.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/index.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/index.html	(revision 28897)
@@ -0,0 +1,193 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js editor</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+	</head>
+	<body>
+		<link href="css/types.css" rel="stylesheet" />
+		<link id="theme" href="css/light.css" rel="stylesheet" />
+
+		<script src="../build/three.min.js"></script>
+		<script src="../examples/js/libs/system.min.js"></script>
+
+		<script src="../examples/js/controls/EditorControls.js"></script>
+		<script src="../examples/js/controls/TransformControls.js"></script>
+		<script src="../examples/js/loaders/BabylonLoader.js"></script>
+		<script src="../examples/js/loaders/ColladaLoader.js"></script>
+		<script src="../examples/js/loaders/OBJLoader.js"></script>
+		<script src="../examples/js/loaders/PLYLoader.js"></script>
+		<script src="../examples/js/loaders/STLLoader.js"></script>
+		<script src="../examples/js/loaders/UTF8Loader.js"></script>
+		<script src="../examples/js/loaders/VRMLLoader.js"></script>
+		<script src="../examples/js/loaders/VTKLoader.js"></script>
+		<script src="../examples/js/loaders/ctm/lzma.js"></script>
+		<script src="../examples/js/loaders/ctm/ctm.js"></script>
+		<script src="../examples/js/loaders/ctm/CTMLoader.js"></script>
+		<script src="../examples/js/exporters/SceneExporter.js"></script>
+		<script src="../examples/js/exporters/OBJExporter.js"></script>
+		<script src="../examples/js/renderers/SoftwareRenderer.js"></script>
+		<script src="../examples/js/renderers/SVGRenderer.js"></script>
+
+		<!-- WIP -->
+
+		<script src="../examples/js/BufferGeometryUtils.js"></script>
+
+		<script src="../examples/js/exporters/BufferGeometryExporter.js"></script>
+		<script src="../examples/js/exporters/GeometryExporter.js"></script>
+		<script src="../examples/js/exporters/MaterialExporter.js"></script>
+		<script src="../examples/js/exporters/ObjectExporter.js"></script>
+		<script src="../examples/js/renderers/WebGLRenderer3.js"></script>
+
+		<script src="js/libs/signals.min.js"></script>
+		<script src="js/libs/ui.js"></script>
+		<script src="js/libs/ui.three.js"></script>
+
+		<script src="js/Storage.js"></script>
+
+		<script src="js/Editor.js"></script>
+		<script src="js/Config.js"></script>
+		<script src="js/Loader.js"></script>
+		<script src="js/Menubar.js"></script>
+		<script src="js/Menubar.File.js"></script>
+		<script src="js/Menubar.Edit.js"></script>
+		<script src="js/Menubar.Add.js"></script>
+		<script src="js/Menubar.View.js"></script>
+		<script src="js/Menubar.Help.js"></script>
+		<script src="js/Sidebar.js"></script>
+		<script src="js/Sidebar.Renderer.js"></script>
+		<script src="js/Sidebar.Scene.js"></script>
+		<script src="js/Sidebar.Object3D.js"></script>
+		<script src="js/Sidebar.Geometry.js"></script>
+		<script src="js/Sidebar.Animation.js"></script>
+		<script src="js/Sidebar.Geometry.CircleGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.CubeGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.CylinderGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.IcosahedronGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.PlaneGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.SphereGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.TorusGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.TorusKnotGeometry.js"></script>
+		<script src="js/Sidebar.Material.js"></script>
+		<script src="js/Toolbar.js"></script>
+		<script src="js/Viewport.js"></script>
+
+		<script>
+
+			window.URL = window.URL || window.webkitURL;
+			window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder;
+
+			var editor = new Editor();
+
+			var viewport = new Viewport( editor ).setId( 'viewport' );
+			document.body.appendChild( viewport.dom );
+
+			var toolbar = new Toolbar( editor ).setId( 'toolbar' )
+			document.body.appendChild( toolbar.dom );
+
+			var menubar = new Menubar( editor ).setId( 'menubar' );
+			document.body.appendChild( menubar.dom );
+
+			var sidebar = new Sidebar( editor ).setId( 'sidebar' );
+			document.body.appendChild( sidebar.dom );
+
+			//
+
+			editor.setTheme( editor.config.getKey( 'theme' ) );
+
+			editor.storage.init( function () {
+
+				editor.storage.get( function ( state ) {
+
+					if ( state !== undefined ) {
+
+						var loader = new THREE.ObjectLoader();
+						var scene = loader.parse( state );
+
+						editor.setScene( scene );
+
+					}
+
+					var selected = editor.config.getKey( 'selected' );
+
+					if ( selected !== undefined ) {
+
+						editor.selectByUuid( selected );
+
+					}
+
+				} );
+
+				//
+
+				var timeout;
+				var exporter = new THREE.ObjectExporter();
+
+				var saveState = function ( scene ) {
+
+					clearTimeout( timeout );
+
+					timeout = setTimeout( function () {
+
+						editor.storage.set( exporter.parse( editor.scene ) );
+
+					}, 1000 );
+
+				};
+
+				var signals = editor.signals;
+
+				signals.objectAdded.add( saveState );
+				signals.objectChanged.add( saveState );
+				signals.objectRemoved.add( saveState );
+				signals.materialChanged.add( saveState );
+				signals.sceneGraphChanged.add( saveState );
+
+			} );
+
+			//
+
+			document.addEventListener( 'dragover', function ( event ) {
+
+				event.preventDefault();
+				event.dataTransfer.dropEffect = 'copy';
+
+			}, false );
+
+			document.addEventListener( 'drop', function ( event ) {
+
+				event.preventDefault();
+				editor.loader.loadFile( event.dataTransfer.files[ 0 ] );
+
+			}, false );
+
+			document.addEventListener( 'keydown', function ( event ) {
+
+				switch ( event.keyCode ) {
+
+					case 8: // prevent browser back 
+						event.preventDefault();
+						break;
+					case 46: // delete
+						editor.removeObject( editor.selected );
+						editor.deselect();
+						break;
+
+				}
+
+			}, false );
+
+			var onWindowResize = function ( event ) {
+
+				editor.signals.windowResize.dispatch();
+
+			};
+
+			window.addEventListener( 'resize', onWindowResize, false );
+
+			onWindowResize();
+
+		</script>
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Config.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Config.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Config.js	(revision 28897)
@@ -0,0 +1,51 @@
+var Config = function () {
+
+	var name = 'threejs-editor';
+
+	var storage = {
+		theme: 'css/light.css',
+		camera: {
+			position: [ 500, 250, 500 ],
+			target: [ 0, 0, 0 ] 
+		}
+	};
+
+	if ( window.localStorage[ name ] !== undefined ) {
+
+		var data = JSON.parse( window.localStorage[ name ] );
+
+		for ( var key in data ) {
+
+			storage[ key ] = data[ key ];
+
+		}
+
+	}
+
+	return {
+
+		getKey: function ( key ) {
+
+			return storage[ key ];
+
+		},
+
+		setKey: function ( key, value ) {
+
+			storage[ key ] = value;
+
+			window.localStorage[ name ] = JSON.stringify( storage );
+
+			console.log( '[' + /\d\d\:\d\d\:\d\d/.exec( new Date() )[ 0 ] + ']', 'Saved config to LocalStorage.' );
+
+		},
+
+		clear: function () {
+
+			delete window.localStorage[ name ];
+
+		}
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Editor.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Editor.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Editor.js	(revision 28897)
@@ -0,0 +1,437 @@
+var Editor = function () {
+
+	var SIGNALS = signals;
+
+	this.signals = {
+
+		// actions
+
+		playAnimations: new SIGNALS.Signal(),
+
+		// notifications
+
+		themeChanged: new SIGNALS.Signal(),
+
+		transformModeChanged: new SIGNALS.Signal(),
+		snapChanged: new SIGNALS.Signal(),
+		spaceChanged: new SIGNALS.Signal(),
+		rendererChanged: new SIGNALS.Signal(),
+
+		sceneGraphChanged: new SIGNALS.Signal(),
+
+		cameraChanged: new SIGNALS.Signal(),
+
+		objectSelected: new SIGNALS.Signal(),
+		objectAdded: new SIGNALS.Signal(),
+		objectChanged: new SIGNALS.Signal(),
+		objectRemoved: new SIGNALS.Signal(),
+
+		helperAdded: new SIGNALS.Signal(),
+		helperRemoved: new SIGNALS.Signal(),
+
+		materialChanged: new SIGNALS.Signal(),
+		fogTypeChanged: new SIGNALS.Signal(),
+		fogColorChanged: new SIGNALS.Signal(),
+		fogParametersChanged: new SIGNALS.Signal(),
+		windowResize: new SIGNALS.Signal()
+
+	};
+	
+	this.config = new Config();
+	this.storage = new Storage();
+	this.loader = new Loader( this );
+
+	this.scene = new THREE.Scene();
+	this.sceneHelpers = new THREE.Scene();
+
+	this.object = {};
+	this.geometries = {};
+	this.materials = {};
+	this.textures = {};
+
+	this.selected = null;
+	this.helpers = {};
+
+};
+
+Editor.prototype = {
+
+	setTheme: function ( value ) {
+
+		document.getElementById( 'theme' ).href = value;
+
+		this.signals.themeChanged.dispatch( value );
+
+	},
+
+	setScene: function ( scene ) {
+
+		this.scene.name = scene.name;
+		this.scene.userData = JSON.parse( JSON.stringify( scene.userData ) );
+
+		// avoid render per object
+
+		this.signals.sceneGraphChanged.active = false;
+
+		while ( scene.children.length > 0 ) {
+
+			this.addObject( scene.children[ 0 ] );
+
+		}
+
+		this.signals.sceneGraphChanged.active = true;
+		this.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	//
+
+	addObject: function ( object ) {
+
+		var scope = this;
+
+		object.traverse( function ( child ) {
+
+			if ( child.geometry !== undefined ) scope.addGeometry( child.geometry );
+			if ( child.material !== undefined ) scope.addMaterial( child.material );
+
+			scope.addHelper( child );
+
+		} );
+
+		this.scene.add( object );
+
+		this.signals.objectAdded.dispatch( object );
+		this.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	setObjectName: function ( object, name ) {
+
+		object.name = name;
+		this.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	removeObject: function ( object ) {
+
+		if ( object.parent === undefined ) return; // avoid deleting the camera or scene
+
+		if ( confirm( 'Delete ' + object.name + '?' ) === false ) return;
+
+		var scope = this;
+
+		object.traverse( function ( child ) {
+
+			scope.removeHelper( child );
+
+		} );
+
+		object.parent.remove( object );
+
+		this.signals.objectRemoved.dispatch( object );
+		this.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	addGeometry: function ( geometry ) {
+
+		this.geometries[ geometry.uuid ] = geometry;
+
+	},
+
+	setGeometryName: function ( geometry, name ) {
+
+		geometry.name = name;
+		this.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	addMaterial: function ( material ) {
+
+		this.materials[ material.uuid ] = material;
+
+	},
+
+	setMaterialName: function ( material, name ) {
+
+		material.name = name;
+		this.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	addTexture: function ( texture ) {
+
+		this.textures[ texture.uuid ] = texture;
+
+	},
+
+	//
+
+	addHelper: function () {
+
+		var geometry = new THREE.SphereGeometry( 20, 4, 2 );
+		var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
+
+		return function ( object ) {
+
+			if ( object instanceof THREE.Camera ) {
+
+				var picker = new THREE.Mesh( geometry, material );
+				picker.name = 'picker';
+				picker.userData.object = object;
+				picker.visible = false;
+
+				var helper = new THREE.CameraHelper( object, 10 );
+				helper.add( picker );
+			
+				this.sceneHelpers.add( helper );
+				this.helpers[ object.id ] = helper;
+
+				this.signals.helperAdded.dispatch( helper );
+
+			} else if ( object instanceof THREE.PointLight ) {
+
+				var picker = new THREE.Mesh( geometry, material );
+				picker.name = 'picker';
+				picker.userData.object = object;
+				picker.visible = false;
+
+				var helper = new THREE.PointLightHelper( object, 10 );
+				helper.add( picker );
+			
+				this.sceneHelpers.add( helper );
+				this.helpers[ object.id ] = helper;
+
+				this.signals.helperAdded.dispatch( helper );
+
+			} else if ( object instanceof THREE.DirectionalLight ) {
+
+				var picker = new THREE.Mesh( geometry, material );
+				picker.name = 'picker';
+				picker.userData.object = object;
+				picker.visible = false;
+
+				var helper = new THREE.DirectionalLightHelper( object, 20 );
+				helper.add( picker );
+
+				this.sceneHelpers.add( helper );
+				this.helpers[ object.id ] = helper;
+
+				this.signals.helperAdded.dispatch( helper );
+
+			} else if ( object instanceof THREE.SpotLight ) {
+
+				var picker = new THREE.Mesh( geometry, material );
+				picker.name = 'picker';
+				picker.userData.object = object;
+				picker.visible = false;
+
+				var helper = new THREE.SpotLightHelper( object, 10 );
+				helper.add( picker );
+
+				this.sceneHelpers.add( helper );
+				this.helpers[ object.id ] = helper;
+
+				this.signals.helperAdded.dispatch( helper );
+
+			} else if ( object instanceof THREE.HemisphereLight ) {
+
+				var picker = new THREE.Mesh( geometry, material );
+				picker.name = 'picker';
+				picker.userData.object = object;
+				picker.visible = false;
+
+				var helper = new THREE.HemisphereLightHelper( object, 10 );
+				helper.add( picker );
+
+				this.sceneHelpers.add( helper );
+				this.helpers[ object.id ] = helper;
+
+				this.signals.helperAdded.dispatch( helper );
+
+			}
+
+		};
+
+	}(),
+
+	removeHelper: function ( object ) {
+
+		if ( this.helpers[ object.id ] !== undefined ) {
+
+			var helper = this.helpers[ object.id ];
+			helper.parent.remove( helper );
+
+			delete this.helpers[ object.id ];
+
+			this.signals.helperRemoved.dispatch( helper );
+
+		}
+
+	},
+
+	//
+
+	parent: function ( object, parent ) {
+
+		if ( parent === undefined ) {
+
+			parent = this.scene;
+
+		}
+
+		parent.add( object );
+
+		this.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	//
+
+	select: function ( object ) {
+
+		this.selected = object;
+
+		if ( object !== null ) {
+
+			this.config.setKey( 'selected', object.uuid );
+
+		} else {
+
+			this.config.setKey( 'selected', null );
+
+		}
+
+		this.signals.objectSelected.dispatch( object );
+
+	},
+
+	selectById: function ( id ) {
+
+		var scope = this;
+
+		this.scene.traverse( function ( child ) {
+
+			if ( child.id === id ) {
+
+				scope.select( child );
+
+			}
+
+		} );
+
+	},
+
+	selectByUuid: function ( uuid ) {
+
+		var scope = this;
+
+		this.scene.traverse( function ( child ) {
+
+			if ( child.uuid === uuid ) {
+
+				scope.select( child );
+
+			}
+
+		} );
+
+	},
+
+	deselect: function () {
+
+		this.select( null );
+
+	},
+
+	// utils
+
+	getObjectType: function ( object ) {
+
+		var types = {
+
+			'Scene': THREE.Scene,
+			'PerspectiveCamera': THREE.PerspectiveCamera,
+			'AmbientLight': THREE.AmbientLight,
+			'DirectionalLight': THREE.DirectionalLight,
+			'HemisphereLight': THREE.HemisphereLight,
+			'PointLight': THREE.PointLight,
+			'SpotLight': THREE.SpotLight,
+			'Mesh': THREE.Mesh,
+			'Sprite': THREE.Sprite,
+			'Object3D': THREE.Object3D
+
+		};
+
+		for ( var type in types ) {
+
+			if ( object instanceof types[ type ] ) return type;
+
+		}
+
+	},
+
+	getGeometryType: function ( geometry ) {
+
+		var types = {
+
+			'CircleGeometry': THREE.CircleGeometry,
+			'CubeGeometry': THREE.CubeGeometry,
+			'CylinderGeometry': THREE.CylinderGeometry,
+			'ExtrudeGeometry': THREE.ExtrudeGeometry,
+			'IcosahedronGeometry': THREE.IcosahedronGeometry,
+			'LatheGeometry': THREE.LatheGeometry,
+			'OctahedronGeometry': THREE.OctahedronGeometry,
+			'ParametricGeometry': THREE.ParametricGeometry,
+			'PlaneGeometry': THREE.PlaneGeometry,
+			'PolyhedronGeometry': THREE.PolyhedronGeometry,
+			'ShapeGeometry': THREE.ShapeGeometry,
+			'SphereGeometry': THREE.SphereGeometry,
+			'TetrahedronGeometry': THREE.TetrahedronGeometry,
+			'TextGeometry': THREE.TextGeometry,
+			'TorusGeometry': THREE.TorusGeometry,
+			'TorusKnotGeometry': THREE.TorusKnotGeometry,
+			'TubeGeometry': THREE.TubeGeometry,
+			'Geometry': THREE.Geometry,
+			'BufferGeometry': THREE.BufferGeometry
+
+		};
+
+		for ( var type in types ) {
+
+			if ( geometry instanceof types[ type ] ) return type;
+
+		}
+
+	},
+
+	getMaterialType: function ( material ) {
+
+		var types = {
+
+			'LineBasicMaterial': THREE.LineBasicMaterial,
+			'LineDashedMaterial': THREE.LineDashedMaterial,
+			'MeshBasicMaterial': THREE.MeshBasicMaterial,
+			'MeshDepthMaterial': THREE.MeshDepthMaterial,
+			'MeshFaceMaterial': THREE.MeshFaceMaterial,
+			'MeshLambertMaterial': THREE.MeshLambertMaterial,
+			'MeshNormalMaterial': THREE.MeshNormalMaterial,
+			'MeshPhongMaterial': THREE.MeshPhongMaterial,
+			'ParticleSystemMaterial': THREE.ParticleSystemMaterial,
+			'ShaderMaterial': THREE.ShaderMaterial,
+			'SpriteCanvasMaterial': THREE.SpriteCanvasMaterial,
+			'SpriteMaterial': THREE.SpriteMaterial,
+			'Material': THREE.Material
+
+		};
+
+		for ( var type in types ) {
+
+			if ( material instanceof types[ type ] ) return type;
+
+		}
+
+	}
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Loader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Loader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Loader.js	(revision 28897)
@@ -0,0 +1,354 @@
+var Loader = function ( editor ) {
+
+	var scope = this;
+	var signals = editor.signals;
+
+	this.loadFile = function ( file ) {
+
+		var filename = file.name;
+		var extension = filename.split( '.' ).pop().toLowerCase();
+
+		switch ( extension ) {
+
+			case 'babylon':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+					var json = JSON.parse( contents );
+
+					var loader = new THREE.BabylonLoader();
+					var scene = loader.parse( json );
+
+					editor.setScene( scene );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			case 'ctm':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					var stream = new CTM.Stream( contents );
+					stream.offset = 0;
+
+					var loader = new THREE.CTMLoader();
+					loader.createModelClassic( new CTM.File( stream ), function( geometry ) {
+
+						geometry.sourceType = "ctm";
+						geometry.sourceFile = file.name;
+
+						var material = new THREE.MeshPhongMaterial();
+
+						var mesh = new THREE.Mesh( geometry, material );
+						mesh.name = filename;
+
+						editor.addObject( mesh );
+
+					} );
+
+				}, false );
+				reader.readAsBinaryString( file );
+
+				break;
+
+			case 'dae':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					var parser = new DOMParser();
+					var xml = parser.parseFromString( contents, 'text/xml' );
+
+					var loader = new THREE.ColladaLoader();
+					loader.parse( xml, function ( collada ) {
+
+						collada.scene.name = filename;
+
+						editor.addObject( collada.scene );
+
+					} );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			case 'js':
+			case 'json':
+
+			case '3geo':
+			case '3mat':
+			case '3obj':
+			case '3scn':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					// 2.0
+
+					if ( contents.indexOf( 'postMessage' ) !== -1 ) {
+
+						var blob = new Blob( [ contents ], { type: 'text/javascript' } );
+						var url = URL.createObjectURL( blob );
+
+						var worker = new Worker( url );
+
+						worker.onmessage = function ( event ) {
+
+							event.data.metadata = { version: 2 };
+							handleJSON( event.data, file, filename );
+
+						};
+
+						worker.postMessage( Date.now() );
+
+						return;
+
+					}
+
+					// >= 3.0
+
+					var data;
+
+					try {
+
+						data = JSON.parse( contents );
+
+					} catch ( error ) {
+
+						alert( error );
+						return;
+
+					}
+
+					handleJSON( data, file, filename );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			case 'obj':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					var object = new THREE.OBJLoader().parse( contents );
+					object.name = filename;
+
+					editor.addObject( object );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			case 'ply':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					console.log( contents );
+
+					var geometry = new THREE.PLYLoader().parse( contents );
+					geometry.sourceType = "ply";
+					geometry.sourceFile = file.name;
+
+					var material = new THREE.MeshPhongMaterial();
+
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.name = filename;
+
+					editor.addObject( mesh );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			case 'stl':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					var geometry = new THREE.STLLoader().parse( contents );
+					geometry.sourceType = "stl";
+					geometry.sourceFile = file.name;
+
+					var material = new THREE.MeshPhongMaterial();
+
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.name = filename;
+
+					editor.addObject( mesh );
+
+				}, false );
+
+				if ( reader.readAsBinaryString !== undefined ) {
+
+					reader.readAsBinaryString( file );
+
+				} else {
+
+					reader.readAsArrayBuffer( file );
+
+				}
+
+				break;
+
+			/*
+			case 'utf8':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					var geometry = new THREE.UTF8Loader().parse( contents );
+					var material = new THREE.MeshLambertMaterial();
+
+					var mesh = new THREE.Mesh( geometry, material );
+
+					editor.addObject( mesh );
+
+				}, false );
+				reader.readAsBinaryString( file );
+
+				break;
+			*/
+
+			case 'vtk':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					var geometry = new THREE.VTKLoader().parse( contents );
+					geometry.sourceType = "vtk";
+					geometry.sourceFile = file.name;
+
+					var material = new THREE.MeshPhongMaterial();
+
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.name = filename;
+
+					editor.addObject( mesh );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			case 'wrl':
+
+				var reader = new FileReader();
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					var result = new THREE.VRMLLoader().parse( contents );
+
+					editor.setScene( result );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			default:
+
+				alert( 'Unsupported file format.' );
+
+				break;
+
+		}
+
+	}
+
+	var handleJSON = function ( data, file, filename ) {
+
+		if ( data.metadata === undefined ) { // 2.0
+
+			data.metadata = { type: 'Geometry' };
+
+		}
+
+		if ( data.metadata.type === undefined ) { // 3.0
+
+			data.metadata.type = 'Geometry';
+
+		}
+
+		if ( data.metadata.version === undefined ) {
+
+			data.metadata.version = data.metadata.formatVersion;
+
+		}
+
+		if ( data.metadata.type.toLowerCase() === 'geometry' ) {
+
+			var loader = new THREE.JSONLoader();
+			var result = loader.parse( data );
+
+			var geometry = result.geometry;
+			var material = result.materials !== undefined
+						? new THREE.MeshFaceMaterial( result.materials )
+						: new THREE.MeshPhongMaterial();
+
+			geometry.sourceType = "ascii";
+			geometry.sourceFile = file.name;
+
+			var mesh = new THREE.Mesh( geometry, material );
+			mesh.name = filename;
+
+			editor.addObject( mesh );
+
+		} else if ( data.metadata.type.toLowerCase() === 'object' ) {
+
+			var loader = new THREE.ObjectLoader();
+			var result = loader.parse( data );
+
+			if ( result instanceof THREE.Scene ) {
+
+				editor.setScene( result );
+
+			} else {
+
+				editor.addObject( result );
+
+			}
+
+		} else if ( data.metadata.type.toLowerCase() === 'scene' ) {
+
+			// DEPRECATED
+
+			var loader = new THREE.SceneLoader();
+			loader.parse( data, function ( result ) {
+
+				editor.setScene( result.scene );
+
+			}, '' );
+
+		}
+
+	};
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Add.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Add.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Add.js	(revision 28897)
@@ -0,0 +1,340 @@
+Menubar.Add = function ( editor ) {
+
+	var container = new UI.Panel();
+	container.setClass( 'menu' );
+
+	var title = new UI.Panel();
+	title.setTextContent( 'Add' );
+	title.setMargin( '0px' );
+	title.setPadding( '8px' );
+	container.add( title );
+
+	//
+
+	var options = new UI.Panel();
+	options.setClass( 'options' );
+	container.add( options );
+
+	var meshCount = 0;
+	var lightCount = 0;
+
+	// add plane
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Plane' );
+	option.onClick( function () {
+
+		var width = 200;
+		var height = 200;
+
+		var widthSegments = 1;
+		var heightSegments = 1;
+
+		var geometry = new THREE.PlaneGeometry( width, height, widthSegments, heightSegments );
+		var material = new THREE.MeshPhongMaterial();
+		var mesh = new THREE.Mesh( geometry, material );
+		mesh.name = 'Plane ' + ( ++ meshCount );
+
+		mesh.rotation.x = - Math.PI/2;
+
+		editor.addObject( mesh );
+		editor.select( mesh );
+
+	} );
+	options.add( option );
+
+	// add cube
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Cube' );
+	option.onClick( function () {
+
+		var width = 100;
+		var height = 100;
+		var depth = 100;
+
+		var widthSegments = 1;
+		var heightSegments = 1;
+		var depthSegments = 1;
+
+		var geometry = new THREE.CubeGeometry( width, height, depth, widthSegments, heightSegments, depthSegments );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		mesh.name = 'Cube ' + ( ++ meshCount );
+
+		editor.addObject( mesh );
+		editor.select( mesh );
+
+	} );
+	options.add( option );
+
+	// add circle
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Circle' );
+	option.onClick( function () {
+
+		var radius = 20;
+		var segments = 8;
+
+		var geometry = new THREE.CircleGeometry( radius, segments );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		mesh.name = 'Circle ' + ( ++ meshCount );
+
+		editor.addObject( mesh );
+		editor.select( mesh );
+
+	} );
+	options.add( option );
+
+	// add cylinder
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Cylinder' );
+	option.onClick( function () {
+
+		var radiusTop = 20;
+		var radiusBottom = 20;
+		var height = 100;
+		var radiusSegments = 8;
+		var heightSegments = 1;
+		var openEnded = false;
+
+		var geometry = new THREE.CylinderGeometry( radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		mesh.name = 'Cylinder ' + ( ++ meshCount );
+
+		editor.addObject( mesh );
+		editor.select( mesh );
+
+	} );
+	options.add( option );
+
+	// add sphere
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Sphere' );
+	option.onClick( function () {
+
+		var radius = 75;
+		var widthSegments = 32;
+		var heightSegments = 16;
+
+		var geometry = new THREE.SphereGeometry( radius, widthSegments, heightSegments );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		mesh.name = 'Sphere ' + ( ++ meshCount );
+
+		editor.addObject( mesh );
+		editor.select( mesh );
+
+	} );
+	options.add( option );
+
+	// add icosahedron
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Icosahedron' );
+	option.onClick( function () {
+
+		var radius = 75;
+		var detail = 2;
+
+		var geometry = new THREE.IcosahedronGeometry ( radius, detail );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		mesh.name = 'Icosahedron ' + ( ++ meshCount );
+
+		editor.addObject( mesh );
+		editor.select( mesh );
+
+	} );
+	options.add( option );
+
+	// add torus
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Torus' );
+	option.onClick( function () {
+
+		var radius = 100;
+		var tube = 40;
+		var radialSegments = 8;
+		var tubularSegments = 6;
+		var arc = Math.PI * 2;
+
+		var geometry = new THREE.TorusGeometry( radius, tube, radialSegments, tubularSegments, arc );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		mesh.name = 'Torus ' + ( ++ meshCount );
+
+		editor.addObject( mesh );
+		editor.select( mesh );
+
+	} );
+	options.add( option );
+
+	// add torus knot
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'TorusKnot' );
+	option.onClick( function () {
+
+		var radius = 100;
+		var tube = 40;
+		var radialSegments = 64;
+		var tubularSegments = 8;
+		var p = 2;
+		var q = 3;
+		var heightScale = 1;
+
+		var geometry = new THREE.TorusKnotGeometry( radius, tube, radialSegments, tubularSegments, p, q, heightScale );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		mesh.name = 'TorusKnot ' + ( ++ meshCount );
+
+		editor.addObject( mesh );
+		editor.select( mesh );
+
+	} );
+	options.add( option );
+
+	// divider
+
+	options.add( new UI.HorizontalRule() );
+
+	// add sprite
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Sprite' );
+	option.onClick( function () {
+
+		var sprite = new THREE.Sprite( new THREE.SpriteMaterial() );
+		sprite.name = 'Sprite ' + ( ++ meshCount );
+
+		editor.addObject( sprite );
+		editor.select( sprite );
+
+	} );
+	options.add( option );
+
+	// divider
+
+	options.add( new UI.HorizontalRule() );
+
+	// add point light
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Point light' );
+	option.onClick( function () {
+
+		var color = 0xffffff;
+		var intensity = 1;
+		var distance = 0;
+
+		var light = new THREE.PointLight( color, intensity, distance );
+		light.name = 'PointLight ' + ( ++ lightCount );
+
+		editor.addObject( light );
+		editor.select( light );
+
+	} );
+	options.add( option );
+
+	// add spot light
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Spot light' );
+	option.onClick( function () {
+
+		var color = 0xffffff;
+		var intensity = 1;
+		var distance = 0;
+		var angle = Math.PI * 0.1;
+		var exponent = 10;
+
+		var light = new THREE.SpotLight( color, intensity, distance, angle, exponent );
+		light.name = 'SpotLight ' + ( ++ lightCount );
+		light.target.name = 'SpotLight ' + ( lightCount ) + ' Target';
+
+		light.position.set( 0, 1, 0 ).multiplyScalar( 200 );
+
+		editor.addObject( light );
+		editor.select( light );
+
+	} );
+	options.add( option );
+
+	// add directional light
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Directional light' );
+	option.onClick( function () {
+
+		var color = 0xffffff;
+		var intensity = 1;
+
+		var light = new THREE.DirectionalLight( color, intensity );
+		light.name = 'DirectionalLight ' + ( ++ lightCount );
+		light.target.name = 'DirectionalLight ' + ( lightCount ) + ' Target';
+
+		light.position.set( 1, 1, 1 ).multiplyScalar( 200 );
+
+		editor.addObject( light );
+		editor.select( light );
+
+	} );
+	options.add( option );
+
+	// add hemisphere light
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Hemisphere light' );
+	option.onClick( function () {
+
+		var skyColor = 0x00aaff;
+		var groundColor = 0xffaa00;
+		var intensity = 1;
+
+		var light = new THREE.HemisphereLight( skyColor, groundColor, intensity );
+		light.name = 'HemisphereLight ' + ( ++ lightCount );
+
+		light.position.set( 1, 1, 1 ).multiplyScalar( 200 );
+
+		editor.addObject( light );
+		editor.select( light );
+
+	} );
+	options.add( option );
+
+	// add ambient light
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Ambient light' );
+	option.onClick( function () {
+
+		var color = 0x222222;
+
+		var light = new THREE.AmbientLight( color );
+		light.name = 'AmbientLight ' + ( ++ lightCount );
+
+		editor.addObject( light );
+		editor.select( light );
+
+	} );
+	options.add( option );
+
+	//
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Edit.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Edit.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Edit.js	(revision 28897)
@@ -0,0 +1,112 @@
+Menubar.Edit = function ( editor ) {
+
+	var container = new UI.Panel();
+	container.setClass( 'menu' );
+
+	var title = new UI.Panel();
+	title.setTextContent( 'Edit' );
+	title.setMargin( '0px' );
+	title.setPadding( '8px' );
+	container.add( title );
+
+	//
+
+	var options = new UI.Panel();
+	options.setClass( 'options' );
+	container.add( options );
+
+	// clone
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Clone' );
+	option.onClick( function () {
+
+		var object = editor.selected;
+
+		if ( object.parent === undefined ) return; // avoid cloning the camera or scene
+
+		object = object.clone();
+
+		editor.addObject( object );
+		editor.select( object );
+
+	} );
+	options.add( option );
+
+	// delete
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Delete' );
+	option.onClick( function () {
+
+		editor.removeObject( editor.selected );
+		editor.deselect();
+
+	} );
+	options.add( option );
+
+	options.add( new UI.HorizontalRule() );
+
+	// convert to BufferGeometry
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Convert' );
+	option.onClick( function () {
+
+		var object = editor.selected;
+
+		if ( object.geometry instanceof THREE.Geometry ) {
+
+			if ( object.parent === undefined ) return; // avoid flattening the camera or scene
+
+			if ( confirm( 'Convert ' + object.name + ' to BufferGeometry?' ) === false ) return;
+
+			delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+			object.geometry = THREE.BufferGeometryUtils.fromGeometry( object.geometry );
+
+			editor.signals.objectChanged.dispatch( object );
+
+		}
+
+	} );
+	options.add( option );
+
+	// flatten
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Flatten' );
+	option.onClick( function () {
+
+		var object = editor.selected;
+
+		if ( object.parent === undefined ) return; // avoid flattening the camera or scene
+
+		if ( confirm( 'Flatten ' + object.name + '?' ) === false ) return;
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		var geometry = object.geometry.clone();
+		geometry.applyMatrix( object.matrix );
+
+		object.geometry = geometry;
+
+		object.position.set( 0, 0, 0 );
+		object.rotation.set( 0, 0, 0 );
+		object.scale.set( 1, 1, 1 );
+
+		editor.signals.objectChanged.dispatch( object );
+
+	} );
+	options.add( option );
+
+
+	//
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.File.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.File.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.File.js	(revision 28897)
@@ -0,0 +1,206 @@
+Menubar.File = function ( editor ) {
+
+	var container = new UI.Panel();
+	container.setClass( 'menu' );
+
+	var title = new UI.Panel();
+	title.setTextContent( 'File' );
+	title.setMargin( '0px' );
+	title.setPadding( '8px' );
+	container.add( title );
+
+	//
+
+	var options = new UI.Panel();
+	options.setClass( 'options' );
+	container.add( options );
+
+	// new
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'New' );
+	option.onClick( function () {
+
+		if ( confirm( 'Are you sure?' ) ) {
+
+			editor.config.clear();
+			editor.storage.clear( function () {
+
+				location.href = location.pathname;
+
+			} );
+
+		}
+
+	} );
+	options.add( option );
+
+	options.add( new UI.HorizontalRule() );
+
+
+	// import
+
+	var input = document.createElement( 'input' );
+	input.type = 'file';
+	input.addEventListener( 'change', function ( event ) {
+
+		editor.loader.loadFile( input.files[ 0 ] );
+
+	} );
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Import' );
+	option.onClick( function () {
+
+		input.click();
+
+	} );
+	options.add( option );
+
+	options.add( new UI.HorizontalRule() );
+
+
+	// export geometry
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Export Geometry' );
+	option.onClick( function () {
+
+		var object = editor.selected;
+
+		if ( object === null ) {
+
+			alert( 'No object selected.' );
+			return;
+
+		}
+
+		var geometry = object.geometry;
+
+		if ( geometry === undefined ) {
+
+			alert( 'The selected object doesn\'t have geometry.' );
+			return;
+
+		}
+
+		if ( geometry instanceof THREE.BufferGeometry ) {
+
+			exportGeometry( THREE.BufferGeometryExporter );
+
+		} else if ( geometry instanceof THREE.Geometry ) {
+
+			exportGeometry( THREE.GeometryExporter );
+
+		}
+
+	} );
+	options.add( option );
+
+	// export object
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Export Object' );
+	option.onClick( function () {
+
+		if ( editor.selected === null ) {
+
+			alert( 'No object selected' );
+			return;
+
+		}
+
+		exportObject( THREE.ObjectExporter );
+
+	} );
+	options.add( option );
+
+	// export scene
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Export Scene' );
+	option.onClick( function () {
+
+		exportScene( THREE.ObjectExporter );
+
+	} );
+	options.add( option );
+
+	// export OBJ
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Export OBJ' );
+	option.onClick( function () {
+
+		exportGeometry( THREE.OBJExporter );
+
+	} );
+	options.add( option );
+
+	var exportGeometry = function ( exporterClass ) {
+
+		var object = editor.selected;
+		var exporter = new exporterClass();
+
+		var output;
+
+		if ( exporter instanceof THREE.BufferGeometryExporter ||
+		     exporter instanceof THREE.GeometryExporter ) {
+
+			output = JSON.stringify( exporter.parse( object.geometry ), null, '\t' );
+			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
+		} else {
+
+			output = exporter.parse( object.geometry );
+
+		}
+
+		var blob = new Blob( [ output ], { type: 'text/plain' } );
+		var objectURL = URL.createObjectURL( blob );
+
+		window.open( objectURL, '_blank' );
+		window.focus();
+
+	};
+
+	var exportObject = function ( exporterClass ) {
+
+		var object = editor.selected;
+		var exporter = new exporterClass();
+
+		var output = JSON.stringify( exporter.parse( object ), null, '\t' );
+		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
+		var blob = new Blob( [ output ], { type: 'text/plain' } );
+		var objectURL = URL.createObjectURL( blob );
+
+		window.open( objectURL, '_blank' );
+		window.focus();
+
+	};
+
+	var exportScene = function ( exporterClass ) {
+
+		var exporter = new exporterClass();
+
+		var output = JSON.stringify( exporter.parse( editor.scene ), null, '\t' );
+		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
+		var blob = new Blob( [ output ], { type: 'text/plain' } );
+		var objectURL = URL.createObjectURL( blob );
+
+		window.open( objectURL, '_blank' );
+		window.focus();
+
+	};
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Help.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Help.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.Help.js	(revision 28897)
@@ -0,0 +1,38 @@
+Menubar.Help = function ( editor ) {
+
+	var container = new UI.Panel();
+	container.setClass( 'menu' );
+
+	var title = new UI.Panel();
+	title.setTextContent( 'Help' );
+	title.setMargin( '0px' );
+	title.setPadding( '8px' );
+	container.add( title );
+
+	//
+
+	var options = new UI.Panel();
+	options.setClass( 'options' );
+	container.add( options );
+
+	// source code
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Source code' );
+	option.onClick( function () { window.open( 'https://github.com/mrdoob/three.js/tree/master/editor', '_blank' ) } );
+	options.add( option );
+
+	// about
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'About' );
+	option.onClick( function () { window.open( 'http://threejs.org', '_blank' ) } );
+	options.add( option );
+
+	//
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.View.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.View.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.View.js	(revision 28897)
@@ -0,0 +1,48 @@
+Menubar.View = function ( editor ) {
+
+	var container = new UI.Panel();
+	container.setClass( 'menu' );
+
+	var title = new UI.Panel();
+	title.setTextContent( 'View' );
+	title.setMargin( '0px' );
+	title.setPadding( '8px' );
+	container.add( title );
+
+	//
+
+	var options = new UI.Panel();
+	options.setClass( 'options' );
+	container.add( options );
+
+	// themes
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Light theme' );
+	option.onClick( function () {
+
+		editor.setTheme( 'css/light.css' );
+		editor.config.setKey( 'theme', 'css/light.css' );
+
+	} );
+	options.add( option );
+
+	// about
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Dark theme' );
+	option.onClick( function () {
+
+		editor.setTheme( 'css/dark.css' );
+		editor.config.setKey( 'theme', 'css/dark.css' );
+
+	} );
+	options.add( option );
+
+	//
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Menubar.js	(revision 28897)
@@ -0,0 +1,13 @@
+var Menubar = function ( editor ) {
+
+	var container = new UI.Panel();
+
+	container.add( new Menubar.File( editor ) );
+	container.add( new Menubar.Edit( editor ) );
+	container.add( new Menubar.Add( editor ) );
+	container.add( new Menubar.View( editor ) );
+	container.add( new Menubar.Help( editor ) );
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Animation.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Animation.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Animation.js	(revision 28897)
@@ -0,0 +1,94 @@
+Sidebar.Animation = function ( editor ) {
+
+	var signals = editor.signals;
+
+	var options = {};
+	var possibleAnimations = {};
+
+	var container = new UI.Panel();
+	container.setDisplay( 'none' );
+
+	container.add( new UI.Text( 'Animation' ) );
+	container.add( new UI.Break(), new UI.Break() );
+
+	var AnimationsRow = new UI.Panel();
+	var Animations = new UI.Select().setOptions( options ).setWidth( '130px' ).setColor( '#444' ).setFontSize( '12px' );
+	AnimationsRow.add( new UI.Text( 'animations' ).setWidth( '90px' ) );
+	AnimationsRow.add( Animations );
+	container.add( AnimationsRow );
+	container.add( new UI.Break() );
+
+	var PlayRow = new UI.Panel();
+	var playButton = new UI.Button().setLabel("Play").onClick(play);
+	PlayRow.add( playButton );
+	container.add( PlayRow );
+	container.add( new UI.Break() );
+
+	function play() {
+
+		var value = Animations.getValue();
+
+		if ( possibleAnimations[ value ] ) {
+
+			var anims = possibleAnimations[value]
+
+			for ( var i = 0; i < anims.length; i ++ ) {
+
+				anims[ i ].play();
+
+			}
+
+			signals.playAnimations.dispatch( anims );
+
+		};
+
+	}
+
+	signals.objectAdded.add( function ( object ) {
+
+		if ( object instanceof THREE.Mesh ) {
+
+			if ( object.geometry && object.geometry.animation ) {
+
+				var name = object.geometry.animation.name;
+				options[name] = name
+
+				Animations.setOptions( options );
+
+				THREE.AnimationHandler.add( object.geometry.animation );
+
+				var animation = new THREE.Animation( object, name, THREE.AnimationHandler.CATMULLROM );
+
+				if ( possibleAnimations[ name ] ){
+
+					possibleAnimations[ name ].push( animation );
+
+				} else {
+
+					possibleAnimations[ name ] = [ animation ];
+
+				}
+
+			}
+
+		}
+
+	} );
+
+	signals.objectSelected.add( function ( object ) {
+
+		if ( object && object.geometry && object.geometry.animation ) {
+
+			container.setDisplay( 'block' );
+
+		} else {
+
+			container.setDisplay( 'none' );
+
+		}
+
+	} );
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CircleGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CircleGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CircleGeometry.js	(revision 28897)
@@ -0,0 +1,48 @@
+Sidebar.Geometry.CircleGeometry = function ( signals, object ) {
+
+	var container = new UI.Panel();
+
+	var geometry = object.geometry;
+
+	// radius
+
+	var radiusRow = new UI.Panel();
+	var radius = new UI.Number( geometry.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// segments
+
+	var segmentsRow = new UI.Panel();
+	var segments = new UI.Integer( geometry.segments ).onChange( update );
+
+	segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
+	segmentsRow.add( segments );
+
+	container.add( segmentsRow );
+
+	//
+
+	function update() {
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		object.geometry.dispose();
+
+		object.geometry = new THREE.CircleGeometry(
+			radius.getValue(),
+			segments.getValue()
+		);
+
+		object.geometry.computeBoundingSphere();
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CubeGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CubeGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CubeGeometry.js	(revision 28897)
@@ -0,0 +1,92 @@
+Sidebar.Geometry.CubeGeometry = function ( signals, object ) {
+
+	var container = new UI.Panel();
+
+	var geometry = object.geometry;
+
+	// width
+
+	var widthRow = new UI.Panel();
+	var width = new UI.Number( geometry.width ).onChange( update );
+
+	widthRow.add( new UI.Text( 'Width' ).setWidth( '90px' ) );
+	widthRow.add( width );
+
+	container.add( widthRow );
+
+	// height
+
+	var heightRow = new UI.Panel();
+	var height = new UI.Number( geometry.height ).onChange( update );
+
+	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
+	heightRow.add( height );
+
+	container.add( heightRow );
+
+	// depth
+
+	var depthRow = new UI.Panel();
+	var depth = new UI.Number( geometry.depth ).onChange( update );
+
+	depthRow.add( new UI.Text( 'Depth' ).setWidth( '90px' ) );
+	depthRow.add( depth );
+
+	container.add( depthRow );
+
+	// widthSegments
+
+	var widthSegmentsRow = new UI.Panel();
+	var widthSegments = new UI.Integer( geometry.widthSegments ).setRange( 1, Infinity ).onChange( update );
+
+	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
+	widthSegmentsRow.add( widthSegments );
+
+	container.add( widthSegmentsRow );
+
+	// heightSegments
+
+	var heightSegmentsRow = new UI.Panel();
+	var heightSegments = new UI.Integer( geometry.heightSegments ).setRange( 1, Infinity ).onChange( update );
+
+	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
+	heightSegmentsRow.add( heightSegments );
+
+	container.add( heightSegmentsRow );
+
+	// depthSegments
+
+	var depthSegmentsRow = new UI.Panel();
+	var depthSegments = new UI.Integer( geometry.depthSegments ).setRange( 1, Infinity ).onChange( update );
+
+	depthSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
+	depthSegmentsRow.add( depthSegments );
+
+	container.add( depthSegmentsRow );
+
+	//
+
+	function update() {
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		object.geometry.dispose();
+
+		object.geometry = new THREE.CubeGeometry(
+			width.getValue(),
+			height.getValue(),
+			depth.getValue(),
+			widthSegments.getValue(),
+			heightSegments.getValue(),
+			depthSegments.getValue()
+		);
+
+		object.geometry.computeBoundingSphere();
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CylinderGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CylinderGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.CylinderGeometry.js	(revision 28897)
@@ -0,0 +1,92 @@
+Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
+
+	var container = new UI.Panel();
+
+	var geometry = object.geometry;
+
+	// radiusTop
+
+	var radiusTopRow = new UI.Panel();
+	var radiusTop = new UI.Number( geometry.radiusTop ).onChange( update );
+
+	radiusTopRow.add( new UI.Text( 'Radius top' ).setWidth( '90px' ) );
+	radiusTopRow.add( radiusTop );
+
+	container.add( radiusTopRow );
+
+	// radiusBottom
+
+	var radiusBottomRow = new UI.Panel();
+	var radiusBottom = new UI.Number( geometry.radiusBottom ).onChange( update );
+
+	radiusBottomRow.add( new UI.Text( 'Radius bottom' ).setWidth( '90px' ) );
+	radiusBottomRow.add( radiusBottom );
+
+	container.add( radiusBottomRow );
+
+	// height
+
+	var heightRow = new UI.Panel();
+	var height = new UI.Number( geometry.height ).onChange( update );
+
+	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
+	heightRow.add( height );
+
+	container.add( heightRow );
+
+	// radialSegments
+
+	var radialSegmentsRow = new UI.Panel();
+	var radialSegments = new UI.Integer( geometry.radialSegments ).setRange( 1, Infinity ).onChange( update );
+
+	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
+	radialSegmentsRow.add( radialSegments );
+
+	container.add( radialSegmentsRow );
+
+	// heightSegments
+
+	var heightSegmentsRow = new UI.Panel();
+	var heightSegments = new UI.Integer( geometry.heightSegments ).setRange( 1, Infinity ).onChange( update );
+
+	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
+	heightSegmentsRow.add( heightSegments );
+
+	container.add( heightSegmentsRow );
+
+	// openEnded
+
+	var openEndedRow = new UI.Panel();
+	var openEnded = new UI.Checkbox( geometry.openEnded ).onChange( update );
+
+	openEndedRow.add( new UI.Text( 'Open ended' ).setWidth( '90px' ) );
+	openEndedRow.add( openEnded );
+
+	container.add( openEndedRow );
+
+	//
+
+	function update() {
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		object.geometry.dispose();
+
+		object.geometry = new THREE.CylinderGeometry(
+			radiusTop.getValue(),
+			radiusBottom.getValue(),
+			height.getValue(),
+			radialSegments.getValue(),
+			heightSegments.getValue(),
+			openEnded.getValue()
+		);
+
+		object.geometry.computeBoundingSphere();
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.IcosahedronGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.IcosahedronGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.IcosahedronGeometry.js	(revision 28897)
@@ -0,0 +1,49 @@
+Sidebar.Geometry.IcosahedronGeometry = function ( signals, object ) {
+
+	var container = new UI.Panel();
+
+	var geometry = object.geometry;
+
+	// radius
+
+	var radiusRow = new UI.Panel();
+	var radius = new UI.Number( geometry.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// detail
+
+	var detailRow = new UI.Panel();
+	var detail = new UI.Integer( geometry.detail ).setRange( 0, Infinity ).onChange( update );
+
+	detailRow.add( new UI.Text( 'Detail' ).setWidth( '90px' ) );
+	detailRow.add( detail );
+
+	container.add( detailRow );
+
+
+	//
+
+	function update() {
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		object.geometry.dispose();
+
+		object.geometry = new THREE.IcosahedronGeometry(
+			radius.getValue(),
+			detail.getValue()
+		);
+
+		object.geometry.computeBoundingSphere();
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.PlaneGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.PlaneGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.PlaneGeometry.js	(revision 28897)
@@ -0,0 +1,71 @@
+Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
+
+	var container = new UI.Panel();
+
+	var geometry = object.geometry;
+
+	// width
+
+	var widthRow = new UI.Panel();
+	var width = new UI.Number( geometry.width ).onChange( update );
+
+	widthRow.add( new UI.Text( 'Width' ).setWidth( '90px' ) );
+	widthRow.add( width );
+
+	container.add( widthRow );
+
+	// height
+
+	var heightRow = new UI.Panel();
+	var height = new UI.Number( geometry.height ).onChange( update );
+
+	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
+	heightRow.add( height );
+
+	container.add( heightRow );
+
+	// widthSegments
+
+	var widthSegmentsRow = new UI.Panel();
+	var widthSegments = new UI.Integer( geometry.widthSegments ).setRange( 1, Infinity ).onChange( update );
+
+	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
+	widthSegmentsRow.add( widthSegments );
+
+	container.add( widthSegmentsRow );
+
+	// heightSegments
+
+	var heightSegmentsRow = new UI.Panel();
+	var heightSegments = new UI.Integer( geometry.heightSegments ).setRange( 1, Infinity ).onChange( update );
+
+	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
+	heightSegmentsRow.add( heightSegments );
+
+	container.add( heightSegmentsRow );
+
+
+	//
+
+	function update() {
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		object.geometry.dispose();
+
+		object.geometry = new THREE.PlaneGeometry(
+			width.getValue(),
+			height.getValue(),
+			widthSegments.getValue(),
+			heightSegments.getValue()
+		);
+
+		object.geometry.computeBoundingSphere();
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.SphereGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.SphereGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.SphereGeometry.js	(revision 28897)
@@ -0,0 +1,104 @@
+Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
+
+	var container = new UI.Panel();
+
+	var geometry = object.geometry;
+
+	// radius
+
+	var radiusRow = new UI.Panel();
+	var radius = new UI.Number( geometry.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// widthSegments
+
+	var widthSegmentsRow = new UI.Panel();
+	var widthSegments = new UI.Integer( geometry.widthSegments ).setRange( 1, Infinity ).onChange( update );
+
+	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
+	widthSegmentsRow.add( widthSegments );
+
+	container.add( widthSegmentsRow );
+
+	// heightSegments
+
+	var heightSegmentsRow = new UI.Panel();
+	var heightSegments = new UI.Integer( geometry.heightSegments ).setRange( 1, Infinity ).onChange( update );
+
+	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
+	heightSegmentsRow.add( heightSegments );
+
+	container.add( heightSegmentsRow );
+
+	// phiStart
+
+	var phiStartRow = new UI.Panel();
+	var phiStart = new UI.Number( geometry.phiStart ).onChange( update );
+
+	phiStartRow.add( new UI.Text( 'Phi start' ).setWidth( '90px' ) );
+	phiStartRow.add( phiStart );
+
+	container.add( phiStartRow );
+
+	// phiLength
+
+	var phiLengthRow = new UI.Panel();
+	var phiLength = new UI.Number( geometry.phiLength ).onChange( update );
+
+	phiLengthRow.add( new UI.Text( 'Phi length' ).setWidth( '90px' ) );
+	phiLengthRow.add( phiLength );
+
+	container.add( phiLengthRow );
+
+	// thetaStart
+
+	var thetaStartRow = new UI.Panel();
+	var thetaStart = new UI.Number( geometry.thetaStart ).onChange( update );
+
+	thetaStartRow.add( new UI.Text( 'Theta start' ).setWidth( '90px' ) );
+	thetaStartRow.add( thetaStart );
+
+	container.add( thetaStartRow );
+
+	// thetaLength
+
+	var thetaLengthRow = new UI.Panel();
+	var thetaLength = new UI.Number( geometry.thetaLength ).onChange( update );
+
+	thetaLengthRow.add( new UI.Text( 'Theta length' ).setWidth( '90px' ) );
+	thetaLengthRow.add( thetaLength );
+
+	container.add( thetaLengthRow );
+
+
+	//
+
+	function update() {
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		object.geometry.dispose();
+
+		object.geometry = new THREE.SphereGeometry(
+			radius.getValue(),
+			widthSegments.getValue(),
+			heightSegments.getValue(),
+			phiStart.getValue(),
+			phiLength.getValue(),
+			thetaStart.getValue(),
+			thetaLength.getValue()
+		);
+
+		object.geometry.computeBoundingSphere();
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.TorusGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.TorusGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.TorusGeometry.js	(revision 28897)
@@ -0,0 +1,82 @@
+Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
+
+	var container = new UI.Panel();
+
+	var geometry = object.geometry;
+
+	// radius
+
+	var radiusRow = new UI.Panel();
+	var radius = new UI.Number( geometry.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// tube
+
+	var tubeRow = new UI.Panel();
+	var tube = new UI.Number( geometry.tube ).onChange( update );
+
+	tubeRow.add( new UI.Text( 'Tube' ).setWidth( '90px' ) );
+	tubeRow.add( tube );
+
+	container.add( tubeRow );
+
+	// radialSegments
+
+	var radialSegmentsRow = new UI.Panel();
+	var radialSegments = new UI.Integer( geometry.radialSegments ).setRange( 1, Infinity ).onChange( update );
+
+	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
+	radialSegmentsRow.add( radialSegments );
+
+	container.add( radialSegmentsRow );
+
+	// tubularSegments
+
+	var tubularSegmentsRow = new UI.Panel();
+	var tubularSegments = new UI.Integer( geometry.tubularSegments ).setRange( 1, Infinity ).onChange( update );
+
+	tubularSegmentsRow.add( new UI.Text( 'Tubular segments' ).setWidth( '90px' ) );
+	tubularSegmentsRow.add( tubularSegments );
+
+	container.add( tubularSegmentsRow );
+
+	// arc
+
+	var arcRow = new UI.Panel();
+	var arc = new UI.Number( geometry.arc ).onChange( update );
+
+	arcRow.add( new UI.Text( 'Arc' ).setWidth( '90px' ) );
+	arcRow.add( arc );
+
+	container.add( arcRow );
+
+
+	//
+
+	function update() {
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		object.geometry.dispose();
+
+		object.geometry = new THREE.TorusGeometry(
+			radius.getValue(),
+			tube.getValue(),
+			radialSegments.getValue(),
+			tubularSegments.getValue(),
+			arc.getValue()
+		);
+
+		object.geometry.computeBoundingSphere();
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.TorusKnotGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.TorusKnotGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.TorusKnotGeometry.js	(revision 28897)
@@ -0,0 +1,104 @@
+Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
+
+	var container = new UI.Panel();
+
+	var geometry = object.geometry;
+
+	// radius
+
+	var radiusRow = new UI.Panel();
+	var radius = new UI.Number( geometry.radius ).onChange( update );
+
+	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
+	radiusRow.add( radius );
+
+	container.add( radiusRow );
+
+	// tube
+
+	var tubeRow = new UI.Panel();
+	var tube = new UI.Number( geometry.tube ).onChange( update );
+
+	tubeRow.add( new UI.Text( 'Tube' ).setWidth( '90px' ) );
+	tubeRow.add( tube );
+
+	container.add( tubeRow );
+
+	// radialSegments
+
+	var radialSegmentsRow = new UI.Panel();
+	var radialSegments = new UI.Integer( geometry.radialSegments ).setRange( 1, Infinity ).onChange( update );
+
+	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
+	radialSegmentsRow.add( radialSegments );
+
+	container.add( radialSegmentsRow );
+
+	// tubularSegments
+
+	var tubularSegmentsRow = new UI.Panel();
+	var tubularSegments = new UI.Integer( geometry.tubularSegments ).setRange( 1, Infinity ).onChange( update );
+
+	tubularSegmentsRow.add( new UI.Text( 'Tubular segments' ).setWidth( '90px' ) );
+	tubularSegmentsRow.add( tubularSegments );
+
+	container.add( tubularSegmentsRow );
+
+	// p
+
+	var pRow = new UI.Panel();
+	var p = new UI.Number( geometry.p ).onChange( update );
+
+	pRow.add( new UI.Text( 'P' ).setWidth( '90px' ) );
+	pRow.add( p );
+
+	container.add( pRow );
+
+	// q
+
+	var qRow = new UI.Panel();
+	var q = new UI.Number( geometry.q ).onChange( update );
+
+	pRow.add( new UI.Text( 'Q' ).setWidth( '90px' ) );
+	pRow.add( q );
+
+	container.add( qRow );
+
+	// heightScale
+
+	var heightScaleRow = new UI.Panel();
+	var heightScale = new UI.Number( geometry.heightScale ).onChange( update );
+
+	pRow.add( new UI.Text( 'Height scale' ).setWidth( '90px' ) );
+	pRow.add( heightScale );
+
+	container.add( heightScaleRow );
+
+
+	//
+
+	function update() {
+
+		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+
+		object.geometry.dispose();
+
+		object.geometry = new THREE.TorusKnotGeometry(
+			radius.getValue(),
+			tube.getValue(),
+			radialSegments.getValue(),
+			tubularSegments.getValue(),
+			p.getValue(),
+			q.getValue(),
+			heightScale.getValue()
+		);
+
+		object.geometry.computeBoundingSphere();
+
+		signals.objectChanged.dispatch( object );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Geometry.js	(revision 28897)
@@ -0,0 +1,189 @@
+Sidebar.Geometry = function ( editor ) {
+
+	var signals = editor.signals;
+
+	var container = new UI.Panel();
+	container.setDisplay( 'none' );
+
+	container.add( new UI.Text().setValue( 'GEOMETRY' ) );
+	container.add( new UI.Break(), new UI.Break() );
+
+	// uuid
+
+	var geometryUUIDRow = new UI.Panel();
+	var geometryUUID = new UI.Input().setWidth( '115px' ).setColor( '#444' ).setFontSize( '12px' ).setDisabled( true );
+	var geometryUUIDRenew = new UI.Button( 'â³' ).setMarginLeft( '7px' ).onClick( function () {
+
+		geometryUUID.setValue( THREE.Math.generateUUID() );
+
+		editor.selected.geometry.uuid = geometryUUID.getValue();
+
+	} );
+
+	geometryUUIDRow.add( new UI.Text( 'UUID' ).setWidth( '90px' ) );
+	geometryUUIDRow.add( geometryUUID );
+	geometryUUIDRow.add( geometryUUIDRenew );
+
+	container.add( geometryUUIDRow );
+
+	// name
+
+	var geometryNameRow = new UI.Panel();
+	var geometryName = new UI.Input().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( function () {
+
+		editor.setGeometryName( editor.selected.geometry, geometryName.getValue() );
+
+	} );
+
+	geometryNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ) );
+	geometryNameRow.add( geometryName );
+
+	container.add( geometryNameRow );
+
+	// class
+
+	var geometryTypeRow = new UI.Panel();
+	var geometryType = new UI.Text().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' );
+
+	geometryTypeRow.add( new UI.Text( 'Type' ).setWidth( '90px' ) );
+	geometryTypeRow.add( geometryType );
+
+	container.add( geometryTypeRow );
+
+	// vertices
+
+	var geometryVerticesRow = new UI.Panel();
+	var geometryVertices = new UI.Text().setColor( '#444' ).setFontSize( '12px' );
+
+	geometryVerticesRow.add( new UI.Text( 'Vertices' ).setWidth( '90px' ) );
+	geometryVerticesRow.add( geometryVertices );
+
+	container.add( geometryVerticesRow );
+
+	// faces
+
+	var geometryFacesRow = new UI.Panel();
+	var geometryFaces = new UI.Text().setColor( '#444' ).setFontSize( '12px' );
+
+	geometryFacesRow.add( new UI.Text( 'Faces' ).setWidth( '90px' ) );
+	geometryFacesRow.add( geometryFaces );
+
+	container.add( geometryFacesRow );
+
+	// parameters
+
+	var parameters;
+
+
+	//
+
+	function build() {
+
+		var object = editor.selected;
+
+		if ( object && object.geometry ) {
+
+			var geometry = object.geometry;
+
+			container.setDisplay( 'block' );
+
+			geometryType.setValue( editor.getGeometryType( object.geometry ) );
+
+			updateFields( geometry );
+
+			//
+
+			if ( parameters !== undefined ) {
+
+				container.remove( parameters );
+				parameters = undefined;
+
+			}
+
+			if ( geometry instanceof THREE.CircleGeometry ) {
+
+				parameters = new Sidebar.Geometry.CircleGeometry( signals, object );
+				container.add( parameters );
+
+			} else if ( geometry instanceof THREE.CubeGeometry ) {
+
+				parameters = new Sidebar.Geometry.CubeGeometry( signals, object );
+				container.add( parameters );
+
+			} else if ( geometry instanceof THREE.CylinderGeometry ) {
+
+				parameters = new Sidebar.Geometry.CylinderGeometry( signals, object );
+				container.add( parameters );
+
+			} else if ( geometry instanceof THREE.SphereGeometry ) {
+
+				parameters = new Sidebar.Geometry.SphereGeometry( signals, object );
+				container.add( parameters );
+
+			} else if ( geometry instanceof THREE.IcosahedronGeometry ) {
+
+				parameters = new Sidebar.Geometry.IcosahedronGeometry( signals, object );
+				container.add( parameters );
+
+			} else if ( geometry instanceof THREE.PlaneGeometry ) {
+
+				parameters = new Sidebar.Geometry.PlaneGeometry( signals, object );
+				container.add( parameters );
+
+			} else if ( geometry instanceof THREE.TorusGeometry ) {
+
+				parameters = new Sidebar.Geometry.TorusGeometry( signals, object );
+				container.add( parameters );
+
+			} else if ( geometry instanceof THREE.TorusKnotGeometry ) {
+
+				parameters = new Sidebar.Geometry.TorusKnotGeometry( signals, object );
+				container.add( parameters );
+
+			}
+
+		} else {
+
+			container.setDisplay( 'none' );
+
+		}
+
+	}
+
+	signals.objectSelected.add( build );
+	signals.objectChanged.add( build );
+
+	//
+
+	function updateFields( geometry ) {
+
+		geometryUUID.setValue( geometry.uuid );
+		geometryName.setValue( geometry.name );
+
+		if ( geometry instanceof THREE.Geometry ) {
+
+			geometryVertices.setValue( geometry.vertices.length );
+			geometryFaces.setValue( geometry.faces.length );
+
+		} else if ( geometry instanceof THREE.BufferGeometry ) {
+
+			geometryVertices.setValue( geometry.attributes.position.numItems / 3 );
+
+			if ( geometry.attributes.index !== undefined ) {
+
+				geometryFaces.setValue( geometry.attributes.index.numItems / 3 );
+
+			} else {
+
+				geometryFaces.setValue( geometry.attributes.position.numItems / 3 / 3 );
+
+			}
+
+
+		}
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Material.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Material.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Material.js	(revision 28897)
@@ -0,0 +1,738 @@
+Sidebar.Material = function ( editor ) {
+
+	var signals = editor.signals;
+
+	var materialClasses = {
+
+		'LineBasicMaterial': THREE.LineBasicMaterial,
+		'LineDashedMaterial': THREE.LineDashedMaterial,
+		'MeshBasicMaterial': THREE.MeshBasicMaterial,
+		'MeshDepthMaterial': THREE.MeshDepthMaterial,
+		'MeshFaceMaterial': THREE.MeshFaceMaterial,
+		'MeshLambertMaterial': THREE.MeshLambertMaterial,
+		'MeshNormalMaterial': THREE.MeshNormalMaterial,
+		'MeshPhongMaterial': THREE.MeshPhongMaterial,
+		'ParticleSystemMaterial': THREE.ParticleSystemMaterial,
+		'ShaderMaterial': THREE.ShaderMaterial,
+		'SpriteMaterial': THREE.SpriteMaterial,
+		'SpriteCanvasMaterial': THREE.SpriteCanvasMaterial,
+		'Material': THREE.Material
+
+	};
+
+	var container = new UI.Panel();
+	container.setDisplay( 'none' );
+	container.dom.classList.add( 'Material' );
+
+	container.add( new UI.Text().setValue( 'MATERIAL' ) );
+	container.add( new UI.Break(), new UI.Break() );
+
+	// uuid
+
+	var materialUUIDRow = new UI.Panel();
+	var materialUUID = new UI.Input().setWidth( '115px' ).setColor( '#444' ).setFontSize( '12px' ).setDisabled( true );
+	var materialUUIDRenew = new UI.Button( 'â³' ).setMarginLeft( '7px' ).onClick( function () {
+
+		materialUUID.setValue( THREE.Math.generateUUID() );
+		update();
+
+	} );
+
+	materialUUIDRow.add( new UI.Text( 'UUID' ).setWidth( '90px' ) );
+	materialUUIDRow.add( materialUUID );
+	materialUUIDRow.add( materialUUIDRenew );
+
+	container.add( materialUUIDRow );
+
+	// name
+
+	var materialNameRow = new UI.Panel();
+	var materialName = new UI.Input().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( function () {
+
+		editor.setMaterialName( editor.selected.material, materialName.getValue() );
+
+	} );
+
+	materialNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ) );
+	materialNameRow.add( materialName );
+
+	container.add( materialNameRow );
+
+	// class
+
+	var materialClassRow = new UI.Panel();
+	var materialClass = new UI.Select().setOptions( {
+
+		'LineBasicMaterial': 'LineBasicMaterial',
+		'LineDashedMaterial': 'LineDashedMaterial',
+		'MeshBasicMaterial': 'MeshBasicMaterial',
+		'MeshDepthMaterial': 'MeshDepthMaterial',
+		'MeshFaceMaterial': 'MeshFaceMaterial',
+		'MeshLambertMaterial': 'MeshLambertMaterial',
+		'MeshNormalMaterial': 'MeshNormalMaterial',
+		'MeshPhongMaterial': 'MeshPhongMaterial',
+		'SpriteMaterial': 'SpriteMaterial'
+
+	} ).setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+
+	materialClassRow.add( new UI.Text( 'Type' ).setWidth( '90px' ) );
+	materialClassRow.add( materialClass );
+
+	container.add( materialClassRow );
+
+	// color
+
+	var materialColorRow = new UI.Panel();
+	var materialColor = new UI.Color().onChange( update );
+
+	materialColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ) );
+	materialColorRow.add( materialColor );
+
+	container.add( materialColorRow );
+
+	// ambient
+
+	var materialAmbientRow = new UI.Panel();
+	var materialAmbient = new UI.Color().onChange( update );
+
+	materialAmbientRow.add( new UI.Text( 'Ambient' ).setWidth( '90px' ) );
+	materialAmbientRow.add( materialAmbient );
+
+	container.add( materialAmbientRow );
+
+	// emissive
+
+	var materialEmissiveRow = new UI.Panel();
+	var materialEmissive = new UI.Color().onChange( update );
+
+	materialEmissiveRow.add( new UI.Text( 'Emissive' ).setWidth( '90px' ) );
+	materialEmissiveRow.add( materialEmissive );
+
+	container.add( materialEmissiveRow );
+
+	// specular
+
+	var materialSpecularRow = new UI.Panel();
+	var materialSpecular = new UI.Color().onChange( update );
+
+	materialSpecularRow.add( new UI.Text( 'Specular' ).setWidth( '90px' ) );
+	materialSpecularRow.add( materialSpecular );
+
+	container.add( materialSpecularRow );
+
+	// shininess
+
+	var materialShininessRow = new UI.Panel();
+	var materialShininess = new UI.Number( 30 ).onChange( update );
+
+	materialShininessRow.add( new UI.Text( 'Shininess' ).setWidth( '90px' ) );
+	materialShininessRow.add( materialShininess );
+
+	container.add( materialShininessRow );
+
+	// vertex colors
+
+	var materialVertexColorsRow = new UI.Panel();
+	var materialVertexColors = new UI.Select().setOptions( {
+
+		0: 'No',
+		1: 'Face',
+		2: 'Vertex'
+
+	} ).onChange( update );
+
+	materialVertexColorsRow.add( new UI.Text( 'Vertex Colors' ).setWidth( '90px' ) );
+	materialVertexColorsRow.add( materialVertexColors );
+
+	container.add( materialVertexColorsRow );
+
+
+	// map
+
+	var materialMapRow = new UI.Panel();
+	var materialMapEnabled = new UI.Checkbox( false ).onChange( update );
+	var materialMap = new UI.Texture().setColor( '#444' ).onChange( update );
+
+	materialMapRow.add( new UI.Text( 'Map' ).setWidth( '90px' ) );
+	materialMapRow.add( materialMapEnabled );
+	materialMapRow.add( materialMap );
+
+	container.add( materialMapRow );
+
+
+	// light map
+
+	var materialLightMapRow = new UI.Panel();
+	var materialLightMapEnabled = new UI.Checkbox( false ).onChange( update );
+	var materialLightMap = new UI.Texture().setColor( '#444' ).onChange( update );
+
+	materialLightMapRow.add( new UI.Text( 'Light Map' ).setWidth( '90px' ) );
+	materialLightMapRow.add( materialLightMapEnabled );
+	materialLightMapRow.add( materialLightMap );
+
+	container.add( materialLightMapRow );
+
+
+	// bump map
+
+	var materialBumpMapRow = new UI.Panel();
+	var materialBumpMapEnabled = new UI.Checkbox( false ).onChange( update );
+	var materialBumpMap = new UI.Texture().setColor( '#444' ).onChange( update );
+	var materialBumpScale = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
+
+	materialBumpMapRow.add( new UI.Text( 'Bump Map' ).setWidth( '90px' ) );
+	materialBumpMapRow.add( materialBumpMapEnabled );
+	materialBumpMapRow.add( materialBumpMap );
+	materialBumpMapRow.add( materialBumpScale );
+
+	container.add( materialBumpMapRow );
+
+
+	// normal map
+
+	var materialNormalMapRow = new UI.Panel();
+	var materialNormalMapEnabled = new UI.Checkbox( false ).onChange( update );
+	var materialNormalMap = new UI.Texture().setColor( '#444' ).onChange( update );
+
+	materialNormalMapRow.add( new UI.Text( 'Normal Map' ).setWidth( '90px' ) );
+	materialNormalMapRow.add( materialNormalMapEnabled );
+	materialNormalMapRow.add( materialNormalMap );
+
+	container.add( materialNormalMapRow );
+
+
+	// specular map
+
+	var materialSpecularMapRow = new UI.Panel();
+	var materialSpecularMapEnabled = new UI.Checkbox( false ).onChange( update );
+	var materialSpecularMap = new UI.Texture().setColor( '#444' ).onChange( update );
+
+	materialSpecularMapRow.add( new UI.Text( 'Specular Map' ).setWidth( '90px' ) );
+	materialSpecularMapRow.add( materialSpecularMapEnabled );
+	materialSpecularMapRow.add( materialSpecularMap );
+
+	container.add( materialSpecularMapRow );
+
+
+	// env map
+
+	var materialEnvMapRow = new UI.Panel();
+	var materialEnvMapEnabled = new UI.Checkbox( false ).onChange( update );
+	var materialEnvMap = new UI.CubeTexture().setColor( '#444' ).onChange( update );
+	var materialReflectivity = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
+
+	materialEnvMapRow.add( new UI.Text( 'Env Map' ).setWidth( '90px' ) );
+	materialEnvMapRow.add( materialEnvMapEnabled );
+	materialEnvMapRow.add( materialEnvMap );
+	materialEnvMapRow.add( materialReflectivity );
+
+	container.add( materialEnvMapRow );
+
+
+	// blending
+
+	var materialBlendingRow = new UI.Panel();
+	var materialBlending = new UI.Select().setOptions( {
+
+		0: 'No',
+		1: 'Normal',
+		2: 'Additive',
+		3: 'Subtractive',
+		4: 'Multiply',
+		5: 'Custom'
+
+	} ).setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+
+	materialBlendingRow.add( new UI.Text( 'Blending' ).setWidth( '90px' ) );
+	materialBlendingRow.add( materialBlending );
+
+	container.add( materialBlendingRow );
+
+
+	// side
+
+	var materialSideRow = new UI.Panel();
+	var materialSide = new UI.Select().setOptions( {
+
+		0: 'Front',
+		1: 'Back',
+		2: 'Double'
+
+	} ).setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+
+	materialSideRow.add( new UI.Text( 'Side' ).setWidth( '90px' ) );
+	materialSideRow.add( materialSide );
+
+	container.add( materialSideRow );
+
+
+	// opacity
+
+	var materialOpacityRow = new UI.Panel();
+	var materialOpacity = new UI.Number().setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
+
+	materialOpacityRow.add( new UI.Text( 'Opacity' ).setWidth( '90px' ) );
+	materialOpacityRow.add( materialOpacity );
+
+	container.add( materialOpacityRow );
+
+
+	// transparent
+
+	var materialTransparentRow = new UI.Panel();
+	var materialTransparent = new UI.Checkbox().setLeft( '100px' ).onChange( update );
+
+	materialTransparentRow.add( new UI.Text( 'Transparent' ).setWidth( '90px' ) );
+	materialTransparentRow.add( materialTransparent );
+
+	container.add( materialTransparentRow );
+
+
+	// wireframe
+
+	var materialWireframeRow = new UI.Panel();
+	var materialWireframe = new UI.Checkbox( false ).onChange( update );
+	var materialWireframeLinewidth = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, 100 ).onChange( update );
+
+	materialWireframeRow.add( new UI.Text( 'Wireframe' ).setWidth( '90px' ) );
+	materialWireframeRow.add( materialWireframe );
+	materialWireframeRow.add( materialWireframeLinewidth );
+
+	container.add( materialWireframeRow );
+
+
+	//
+
+	function update() {
+
+		var object = editor.selected;
+		var geometry = object.geometry;
+		var material = object.material;
+		var textureWarning = false;
+		var objectHasUvs = false;
+
+		if ( object instanceof THREE.Sprite ) objectHasUvs = true;
+		if ( geometry instanceof THREE.Geometry && geometry.faceVertexUvs[ 0 ].length > 0 ) objectHasUvs = true;
+		if ( geometry instanceof THREE.BufferGeometry && geometry.attributes.uv !== undefined ) objectHasUvs = true;
+
+		if ( material ) {
+
+			if ( material.uuid !== undefined ) {
+
+				material.uuid = materialUUID.getValue();
+
+			}
+
+			if ( material instanceof materialClasses[ materialClass.getValue() ] === false ) {
+
+				material = new materialClasses[ materialClass.getValue() ]();
+				object.material = material;
+
+			}
+
+			if ( material.color !== undefined ) {
+
+				material.color.setHex( materialColor.getHexValue() );
+
+			}
+
+			if ( material.ambient !== undefined ) {
+
+				material.ambient.setHex( materialAmbient.getHexValue() );
+
+			}
+
+			if ( material.emissive !== undefined ) {
+
+				material.emissive.setHex( materialEmissive.getHexValue() );
+
+			}
+
+			if ( material.specular !== undefined ) {
+
+				material.specular.setHex( materialSpecular.getHexValue() );
+
+			}
+
+			if ( material.shininess !== undefined ) {
+
+				material.shininess = materialShininess.getValue();
+
+			}
+
+			if ( material.vertexColors !== undefined ) {
+
+				geometry.buffersNeedUpdate = true;
+				geometry.colorsNeedUpdate = true;
+
+				material.vertexColors = parseInt( materialVertexColors.getValue() );
+				material.needsUpdate = true;
+
+			}
+
+			if ( material.map !== undefined ) {
+
+				var mapEnabled = materialMapEnabled.getValue() === true;
+
+				if ( objectHasUvs )  {
+
+					if ( geometry !== undefined ) {
+
+						geometry.buffersNeedUpdate = true;
+						geometry.uvsNeedUpdate = true;
+
+					}
+
+					material.map = mapEnabled ? materialMap.getValue() : null;
+					material.needsUpdate = true;
+
+				} else {
+
+					if ( mapEnabled ) textureWarning = true;
+
+				}
+
+			}
+
+			/*
+			if ( material.lightMap !== undefined ) {
+
+				var lightMapEnabled = materialLightMapEnabled.getValue() === true;
+
+				if ( objectHasUvs )  {
+
+					geometry.buffersNeedUpdate = true;
+					geometry.uvsNeedUpdate = true;
+
+					material.lightMap = lightMapEnabled ? materialLightMap.getValue() : null;
+					material.needsUpdate = true;
+
+				} else {
+
+					if ( lightMapEnabled ) textureWarning = true;
+
+				}
+
+			}
+			*/
+
+			if ( material.bumpMap !== undefined ) {
+
+				var bumpMapEnabled = materialBumpMapEnabled.getValue() === true;
+
+				if ( objectHasUvs )  {
+
+					geometry.buffersNeedUpdate = true;
+					geometry.uvsNeedUpdate = true;
+
+					material.bumpMap = bumpMapEnabled ? materialBumpMap.getValue() : null;
+					material.bumpScale = materialBumpScale.getValue();
+					material.needsUpdate = true;
+
+				} else {
+
+					if ( bumpMapEnabled ) textureWarning = true;
+
+				}
+
+			}
+
+			if ( material.normalMap !== undefined ) {
+
+				var normalMapEnabled = materialNormalMapEnabled.getValue() === true;
+
+				if ( objectHasUvs )  {
+
+					geometry.buffersNeedUpdate = true;
+					geometry.uvsNeedUpdate = true;
+
+					material.normalMap = normalMapEnabled ? materialNormalMap.getValue() : null;
+					material.needsUpdate = true;
+
+				} else {
+
+					if ( normalMapEnabled ) textureWarning = true;
+
+				}
+
+			}
+
+			if ( material.specularMap !== undefined ) {
+
+				var specularMapEnabled = materialSpecularMapEnabled.getValue() === true;
+
+				if ( objectHasUvs )  {
+
+					geometry.buffersNeedUpdate = true;
+					geometry.uvsNeedUpdate = true;
+
+					material.specularMap = specularMapEnabled ? materialSpecularMap.getValue() : null;
+					material.needsUpdate = true;
+
+				} else {
+
+					if ( specularMapEnabled ) textureWarning = true;
+
+				}
+
+			}
+
+			if ( material.envMap !== undefined ) {
+
+				var envMapEnabled = materialEnvMapEnabled.getValue() === true;
+
+				if ( objectHasUvs )  {
+
+					geometry.buffersNeedUpdate = true;
+					geometry.uvsNeedUpdate = true;
+
+					material.envMap = envMapEnabled ? materialEnvMap.getValue() : null;
+					material.reflectivity = materialReflectivity.getValue();
+					material.needsUpdate = true;
+
+				} else {
+
+					if ( envMapEnabled ) textureWarning = true;
+
+				}
+
+			}
+
+			if ( material.blending !== undefined ) {
+
+				material.blending = parseInt( materialBlending.getValue() );
+
+			}
+
+			if ( material.side !== undefined ) {
+
+				material.side = parseInt( materialSide.getValue() );
+
+			}
+
+			if ( material.opacity !== undefined ) {
+
+				material.opacity = materialOpacity.getValue();
+
+			}
+
+			if ( material.transparent !== undefined ) {
+
+				material.transparent = materialTransparent.getValue();
+
+			}
+
+			if ( material.wireframe !== undefined ) {
+
+				material.wireframe = materialWireframe.getValue();
+
+			}
+
+			if ( material.wireframeLinewidth !== undefined ) {
+
+				material.wireframeLinewidth = materialWireframeLinewidth.getValue();
+
+			}
+
+			updateRows();
+
+			signals.materialChanged.dispatch( material );
+
+		}
+
+		if ( textureWarning ) {
+
+			console.warn( "Can't set texture, model doesn't have texture coordinates" );
+
+		}
+
+	};
+
+	function updateRows() {
+
+		var properties = {
+			'name': materialNameRow,
+			'color': materialColorRow,
+			'ambient': materialAmbientRow,
+			'emissive': materialEmissiveRow,
+			'specular': materialSpecularRow,
+			'shininess': materialShininessRow,
+			'vertexColors': materialVertexColorsRow,
+			'map': materialMapRow,
+			'lightMap': materialLightMapRow,
+			'bumpMap': materialBumpMapRow,
+			'normalMap': materialNormalMapRow,
+			'specularMap': materialSpecularMapRow,
+			'envMap': materialEnvMapRow,
+			'blending': materialBlendingRow,
+			'side': materialSideRow,
+			'opacity': materialOpacityRow,
+			'transparent': materialTransparentRow,
+			'wireframe': materialWireframeRow
+		};
+
+		var material = editor.selected.material;
+
+		for ( var property in properties ) {
+
+			properties[ property ].setDisplay( material[ property ] !== undefined ? '' : 'none' );
+
+		}
+
+	};
+
+	// events
+
+	signals.objectSelected.add( function ( object ) {
+
+		if ( object && object.material ) {
+
+			container.setDisplay( '' );
+
+			var material = object.material;
+
+			if ( material.uuid !== undefined ) {
+
+				materialUUID.setValue( material.uuid );
+
+			}
+
+			if ( material.name !== undefined ) {
+
+				materialName.setValue( material.name );
+
+			}
+
+			materialClass.setValue( editor.getMaterialType( material ) );
+
+			if ( material.color !== undefined ) {
+
+				materialColor.setHexValue( material.color.getHexString() );
+
+			}
+
+			if ( material.ambient !== undefined ) {
+
+				materialAmbient.setHexValue( material.ambient.getHexString() );
+
+			}
+
+			if ( material.emissive !== undefined ) {
+
+				materialEmissive.setHexValue( material.emissive.getHexString() );
+
+			}
+
+			if ( material.specular !== undefined ) {
+
+				materialSpecular.setHexValue( material.specular.getHexString() );
+
+			}
+
+			if ( material.shininess !== undefined ) {
+
+				materialShininess.setValue( material.shininess );
+
+			}
+
+			if ( material.vertexColors !== undefined ) {
+
+				materialVertexColors.setValue( material.vertexColors );
+
+			}
+
+			if ( material.map !== undefined ) {
+
+				materialMapEnabled.setValue( material.map !== null );
+				materialMap.setValue( material.map );
+
+			}
+
+			/*
+			if ( material.lightMap !== undefined ) {
+
+				materialLightMapEnabled.setValue( material.lightMap !== null );
+				materialLightMap.setValue( material.lightMap );
+
+			}
+			*/
+
+			if ( material.bumpMap !== undefined ) {
+
+				materialBumpMapEnabled.setValue( material.bumpMap !== null );
+				materialBumpMap.setValue( material.bumpMap );
+				materialBumpScale.setValue( material.bumpScale );
+
+			}
+
+			if ( material.normalMap !== undefined ) {
+
+				materialNormalMapEnabled.setValue( material.normalMap !== null );
+				materialNormalMap.setValue( material.normalMap );
+
+			}
+
+			if ( material.specularMap !== undefined ) {
+
+				materialSpecularMapEnabled.setValue( material.specularMap !== null );
+				materialSpecularMap.setValue( material.specularMap );
+
+			}
+
+			if ( material.envMap !== undefined ) {
+
+				materialEnvMapEnabled.setValue( material.envMap !== null );
+				materialEnvMap.setValue( material.envMap );
+				materialReflectivity.setValue( material.reflectivity );
+
+			}
+
+			if ( material.blending !== undefined ) {
+
+				materialBlending.setValue( material.blending );
+
+			}
+
+			if ( material.side !== undefined ) {
+
+				materialSide.setValue( material.side );
+
+			}
+
+			if ( material.opacity !== undefined ) {
+
+				materialOpacity.setValue( material.opacity );
+
+			}
+
+			if ( material.transparent !== undefined ) {
+
+				materialTransparent.setValue( material.transparent );
+
+			}
+
+			if ( material.wireframe !== undefined ) {
+
+				materialWireframe.setValue( material.wireframe );
+
+			}
+
+			if ( material.wireframeLinewidth !== undefined ) {
+
+				materialWireframeLinewidth.setValue( material.wireframeLinewidth );
+
+			}
+
+			updateRows();
+
+		} else {
+
+			container.setDisplay( 'none' );
+
+		}
+
+	} );
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Object3D.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Object3D.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Object3D.js	(revision 28897)
@@ -0,0 +1,572 @@
+Sidebar.Object3D = function ( editor ) {
+
+	var signals = editor.signals;
+
+	var container = new UI.Panel();
+	container.setDisplay( 'none' );
+
+	var objectType = new UI.Text().setTextTransform( 'uppercase' );
+	container.add( objectType );
+	container.add( new UI.Break(), new UI.Break() );
+
+	// uuid
+
+	var objectUUIDRow = new UI.Panel();
+	var objectUUID = new UI.Input().setWidth( '115px' ).setColor( '#444' ).setFontSize( '12px' ).setDisabled( true );
+	var objectUUIDRenew = new UI.Button( 'â³' ).setMarginLeft( '7px' ).onClick( function () {
+
+		objectUUID.setValue( THREE.Math.generateUUID() );
+
+		editor.selected.uuid = objectUUID.getValue();
+
+	} );
+
+	objectUUIDRow.add( new UI.Text( 'UUID' ).setWidth( '90px' ) );
+	objectUUIDRow.add( objectUUID );
+	objectUUIDRow.add( objectUUIDRenew );
+
+	container.add( objectUUIDRow );
+
+	// name
+
+	var objectNameRow = new UI.Panel();
+	var objectName = new UI.Input().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( function () {
+
+			editor.setObjectName( editor.selected, objectName.getValue() );
+
+	} );
+
+	objectNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ) );
+	objectNameRow.add( objectName );
+
+	container.add( objectNameRow );
+
+	// parent
+
+	var objectParentRow = new UI.Panel();
+	var objectParent = new UI.Select().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+
+	objectParentRow.add( new UI.Text( 'Parent' ).setWidth( '90px' ) );
+	objectParentRow.add( objectParent );
+
+	container.add( objectParentRow );
+
+	// position
+
+	var objectPositionRow = new UI.Panel();
+	var objectPositionX = new UI.Number().setWidth( '50px' ).onChange( update );
+	var objectPositionY = new UI.Number().setWidth( '50px' ).onChange( update );
+	var objectPositionZ = new UI.Number().setWidth( '50px' ).onChange( update );
+
+	objectPositionRow.add( new UI.Text( 'Position' ).setWidth( '90px' ) );
+	objectPositionRow.add( objectPositionX, objectPositionY, objectPositionZ );
+
+	container.add( objectPositionRow );
+
+	// rotation
+
+	var objectRotationRow = new UI.Panel();
+	var objectRotationX = new UI.Number().setWidth( '50px' ).onChange( update );
+	var objectRotationY = new UI.Number().setWidth( '50px' ).onChange( update );
+	var objectRotationZ = new UI.Number().setWidth( '50px' ).onChange( update );
+
+	objectRotationRow.add( new UI.Text( 'Rotation' ).setWidth( '90px' ) );
+	objectRotationRow.add( objectRotationX, objectRotationY, objectRotationZ );
+
+	container.add( objectRotationRow );
+
+	// scale
+
+	var objectScaleRow = new UI.Panel();
+	var objectScaleLock = new UI.Checkbox().setPosition( 'absolute' ).setLeft( '75px' );
+	var objectScaleX = new UI.Number( 1 ).setWidth( '50px' ).onChange( updateScaleX );
+	var objectScaleY = new UI.Number( 1 ).setWidth( '50px' ).onChange( updateScaleY );
+	var objectScaleZ = new UI.Number( 1 ).setWidth( '50px' ).onChange( updateScaleZ );
+
+	objectScaleRow.add( new UI.Text( 'Scale' ).setWidth( '90px' ) );
+	objectScaleRow.add( objectScaleLock );
+	objectScaleRow.add( objectScaleX, objectScaleY, objectScaleZ );
+
+	container.add( objectScaleRow );
+
+	// fov
+
+	var objectFovRow = new UI.Panel();
+	var objectFov = new UI.Number().onChange( update );
+
+	objectFovRow.add( new UI.Text( 'Fov' ).setWidth( '90px' ) );
+	objectFovRow.add( objectFov );
+
+	container.add( objectFovRow );
+
+	// near
+
+	var objectNearRow = new UI.Panel();
+	var objectNear = new UI.Number().onChange( update );
+
+	objectNearRow.add( new UI.Text( 'Near' ).setWidth( '90px' ) );
+	objectNearRow.add( objectNear );
+
+	container.add( objectNearRow );
+
+	// far
+
+	var objectFarRow = new UI.Panel();
+	var objectFar = new UI.Number().onChange( update );
+
+	objectFarRow.add( new UI.Text( 'Far' ).setWidth( '90px' ) );
+	objectFarRow.add( objectFar );
+
+	container.add( objectFarRow );
+
+	// intensity
+
+	var objectIntensityRow = new UI.Panel();
+	var objectIntensity = new UI.Number().setRange( 0, Infinity ).onChange( update );
+
+	objectIntensityRow.add( new UI.Text( 'Intensity' ).setWidth( '90px' ) );
+	objectIntensityRow.add( objectIntensity );
+
+	container.add( objectIntensityRow );
+
+	// color
+
+	var objectColorRow = new UI.Panel();
+	var objectColor = new UI.Color().onChange( update );
+
+	objectColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ) );
+	objectColorRow.add( objectColor );
+
+	container.add( objectColorRow );
+
+	// ground color
+
+	var objectGroundColorRow = new UI.Panel();
+	var objectGroundColor = new UI.Color().onChange( update );
+
+	objectGroundColorRow.add( new UI.Text( 'Ground color' ).setWidth( '90px' ) );
+	objectGroundColorRow.add( objectGroundColor );
+
+	container.add( objectGroundColorRow );
+
+	// distance
+
+	var objectDistanceRow = new UI.Panel();
+	var objectDistance = new UI.Number().setRange( 0, Infinity ).onChange( update );
+
+	objectDistanceRow.add( new UI.Text( 'Distance' ).setWidth( '90px' ) );
+	objectDistanceRow.add( objectDistance );
+
+	container.add( objectDistanceRow );
+
+	// angle
+
+	var objectAngleRow = new UI.Panel();
+	var objectAngle = new UI.Number().setPrecision( 3 ).setRange( 0, Math.PI / 2 ).onChange( update );
+
+	objectAngleRow.add( new UI.Text( 'Angle' ).setWidth( '90px' ) );
+	objectAngleRow.add( objectAngle );
+
+	container.add( objectAngleRow );
+
+	// exponent
+
+	var objectExponentRow = new UI.Panel();
+	var objectExponent = new UI.Number().setRange( 0, Infinity ).onChange( update );
+
+	objectExponentRow.add( new UI.Text( 'Exponent' ).setWidth( '90px' ) );
+	objectExponentRow.add( objectExponent );
+
+	container.add( objectExponentRow );
+
+	// visible
+
+	var objectVisibleRow = new UI.Panel();
+	var objectVisible = new UI.Checkbox().onChange( update );
+
+	objectVisibleRow.add( new UI.Text( 'Visible' ).setWidth( '90px' ) );
+	objectVisibleRow.add( objectVisible );
+
+	container.add( objectVisibleRow );
+
+	// user data
+
+	var objectUserDataRow = new UI.Panel();
+	var objectUserData = new UI.TextArea().setWidth( '150px' ).setHeight( '40px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+	objectUserData.onKeyUp( function () {
+
+		try {
+
+			JSON.parse( objectUserData.getValue() );
+			objectUserData.setBorderColor( '#ccc' );
+			objectUserData.setBackgroundColor( '' );
+
+		} catch ( error ) {
+
+			objectUserData.setBorderColor( '#f00' );
+			objectUserData.setBackgroundColor( 'rgba(255,0,0,0.25)' );
+
+		}
+
+	} );
+
+	objectUserDataRow.add( new UI.Text( 'User data' ).setWidth( '90px' ) );
+	objectUserDataRow.add( objectUserData );
+
+	container.add( objectUserDataRow );
+
+
+	//
+
+	function updateScaleX() {
+
+		var object = editor.selected;
+
+		if ( objectScaleLock.getValue() === true ) {
+
+			var scale = objectScaleX.getValue() / object.scale.x;
+
+			objectScaleY.setValue( objectScaleY.getValue() * scale );
+			objectScaleZ.setValue( objectScaleZ.getValue() * scale );
+
+		}
+
+		update();
+
+	}
+
+	function updateScaleY() {
+
+		var object = editor.selected;
+
+		if ( objectScaleLock.getValue() === true ) {
+
+			var scale = objectScaleY.getValue() / object.scale.y;
+
+			objectScaleX.setValue( objectScaleX.getValue() * scale );
+			objectScaleZ.setValue( objectScaleZ.getValue() * scale );
+
+		}
+
+		update();
+
+	}
+
+	function updateScaleZ() {
+
+		var object = editor.selected;
+
+		if ( objectScaleLock.getValue() === true ) {
+
+			var scale = objectScaleZ.getValue() / object.scale.z;
+
+			objectScaleX.setValue( objectScaleX.getValue() * scale );
+			objectScaleY.setValue( objectScaleY.getValue() * scale );
+
+		}
+
+		update();
+
+	}
+
+	function update() {
+
+		var object = editor.selected;
+
+		if ( object !== null ) {
+
+			if ( object.parent !== undefined ) {
+
+				var newParentId = parseInt( objectParent.getValue() );
+
+				if ( object.parent.id !== newParentId && object.id !== newParentId ) {
+
+					editor.parent( object, editor.scene.getObjectById( newParentId, true ) );
+
+				}
+
+			}
+
+			object.position.x = objectPositionX.getValue();
+			object.position.y = objectPositionY.getValue();
+			object.position.z = objectPositionZ.getValue();
+
+			object.rotation.x = objectRotationX.getValue();
+			object.rotation.y = objectRotationY.getValue();
+			object.rotation.z = objectRotationZ.getValue();
+
+			object.scale.x = objectScaleX.getValue();
+			object.scale.y = objectScaleY.getValue();
+			object.scale.z = objectScaleZ.getValue();
+
+			if ( object.fov !== undefined ) {
+
+				object.fov = objectFov.getValue();
+				object.updateProjectionMatrix();
+
+			}
+
+			if ( object.near !== undefined ) {
+
+				object.near = objectNear.getValue();
+
+			}
+
+			if ( object.far !== undefined ) {
+
+				object.far = objectFar.getValue();
+
+			}
+
+			if ( object.intensity !== undefined ) {
+
+				object.intensity = objectIntensity.getValue();
+
+			}
+
+			if ( object.color !== undefined ) {
+
+				object.color.setHex( objectColor.getHexValue() );
+
+			}
+
+			if ( object.groundColor !== undefined ) {
+
+				object.groundColor.setHex( objectGroundColor.getHexValue() );
+
+			}
+
+			if ( object.distance !== undefined ) {
+
+				object.distance = objectDistance.getValue();
+
+			}
+
+			if ( object.angle !== undefined ) {
+
+				object.angle = objectAngle.getValue();
+
+			}
+
+			if ( object.exponent !== undefined ) {
+
+				object.exponent = objectExponent.getValue();
+
+			}
+
+			object.visible = objectVisible.getValue();
+
+			try {
+
+				object.userData = JSON.parse( objectUserData.getValue() );
+
+			} catch ( exception ) {
+
+				console.warn( exception );
+
+			}
+
+			signals.objectChanged.dispatch( object );
+
+		}
+
+	}
+
+	function updateRows() {
+
+		var object = editor.selected;
+
+		var properties = {
+			'parent': objectParentRow,
+			'fov': objectFovRow,
+			'near': objectNearRow,
+			'far': objectFarRow,
+			'intensity': objectIntensityRow,
+			'color': objectColorRow,
+			'groundColor': objectGroundColorRow,
+			'distance' : objectDistanceRow,
+			'angle' : objectAngleRow,
+			'exponent' : objectExponentRow
+		};
+
+		for ( var property in properties ) {
+
+			properties[ property ].setDisplay( object[ property ] !== undefined ? '' : 'none' );
+
+		}
+
+	}
+
+	function updateTransformRows() {
+
+		var object = editor.selected;
+
+		if ( object instanceof THREE.Light ||
+		   ( object instanceof THREE.Object3D && object.userData.targetInverse ) ) {
+
+			objectRotationRow.setDisplay( 'none' );
+			objectScaleRow.setDisplay( 'none' );
+
+		} else {
+
+			objectRotationRow.setDisplay( '' );
+			objectScaleRow.setDisplay( '' );
+
+		}
+
+	}
+
+	// events
+
+	signals.objectSelected.add( function ( object ) {
+
+		updateUI();
+
+	} );
+
+	signals.sceneGraphChanged.add( function () {
+
+		var scene = editor.scene;
+
+		var options = {};
+
+		options[ scene.id ] = 'Scene';
+
+		( function addObjects( objects ) {
+
+			for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+				var object = objects[ i ];
+
+				options[ object.id ] = object.name;
+
+				addObjects( object.children );
+
+			}
+
+		} )( scene.children );
+
+		objectParent.setOptions( options );
+
+	} );
+
+	signals.objectChanged.add( function ( object ) {
+
+		if ( object !== editor.selected ) return;
+
+		updateUI();
+
+	} );
+
+	function updateUI() {
+
+		container.setDisplay( 'none' );
+
+		var object = editor.selected;
+
+		if ( object !== null ) {
+
+			container.setDisplay( 'block' );
+
+			objectType.setValue( editor.getObjectType( object ) );
+
+			objectUUID.setValue( object.uuid );
+			objectName.setValue( object.name );
+
+			if ( object.parent !== undefined ) {
+
+				objectParent.setValue( object.parent.id );
+
+			}
+
+			objectPositionX.setValue( object.position.x );
+			objectPositionY.setValue( object.position.y );
+			objectPositionZ.setValue( object.position.z );
+
+			objectRotationX.setValue( object.rotation.x );
+			objectRotationY.setValue( object.rotation.y );
+			objectRotationZ.setValue( object.rotation.z );
+
+			objectScaleX.setValue( object.scale.x );
+			objectScaleY.setValue( object.scale.y );
+			objectScaleZ.setValue( object.scale.z );
+
+			if ( object.fov !== undefined ) {
+
+				objectFov.setValue( object.fov );
+
+			}
+
+			if ( object.near !== undefined ) {
+
+				objectNear.setValue( object.near );
+
+			}
+
+			if ( object.far !== undefined ) {
+
+				objectFar.setValue( object.far );
+
+			}
+
+			if ( object.intensity !== undefined ) {
+
+				objectIntensity.setValue( object.intensity );
+
+			}
+
+			if ( object.color !== undefined ) {
+
+				objectColor.setHexValue( object.color.getHexString() );
+
+			}
+
+			if ( object.groundColor !== undefined ) {
+
+				objectGroundColor.setHexValue( object.groundColor.getHexString() );
+
+			}
+
+			if ( object.distance !== undefined ) {
+
+				objectDistance.setValue( object.distance );
+
+			}
+
+			if ( object.angle !== undefined ) {
+
+				objectAngle.setValue( object.angle );
+
+			}
+
+			if ( object.exponent !== undefined ) {
+
+				objectExponent.setValue( object.exponent );
+
+			}
+
+			objectVisible.setValue( object.visible );
+
+			try {
+
+				objectUserData.setValue( JSON.stringify( object.userData, null, '  ' ) );
+
+			} catch ( error ) {
+
+				console.log( error );
+
+			}
+
+			objectUserData.setBorderColor( '#ccc' );
+			objectUserData.setBackgroundColor( '' );
+
+			updateRows();
+			updateTransformRows();
+
+		}
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Renderer.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Renderer.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Renderer.js	(revision 28897)
@@ -0,0 +1,57 @@
+Sidebar.Renderer = function ( editor ) {
+
+	var signals = editor.signals;
+
+	var rendererTypes = {
+
+		'WebGLRenderer': THREE.WebGLRenderer,
+		'WebGLRenderer3': THREE.WebGLRenderer3,
+		'CanvasRenderer': THREE.CanvasRenderer,
+		'SoftwareRenderer': THREE.SoftwareRenderer,
+		'SVGRenderer': THREE.SVGRenderer
+
+	};
+
+	var container = new UI.Panel();
+
+	container.add( new UI.Text( 'RENDERER' ) );
+	container.add( new UI.Break(), new UI.Break() );
+
+	// class
+
+	var options = {};
+
+	for ( var key in rendererTypes ) {
+
+		if ( key.indexOf( 'WebGL' ) >= 0 && System.support.webgl === false ) continue;
+
+		options[ key ] = key;
+
+	}
+
+	var rendererTypeRow = new UI.Panel();
+	var rendererType = new UI.Select().setOptions( options ).setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( updateRenderer );
+
+	rendererTypeRow.add( new UI.Text( 'Type' ).setWidth( '90px' ) );
+	rendererTypeRow.add( rendererType );
+
+	container.add( rendererTypeRow );
+
+	if ( editor.config.getKey( 'renderer' ) !== undefined ) {
+
+		rendererType.setValue( editor.config.getKey( 'renderer' ) );
+
+	}
+
+	//
+
+	function updateRenderer() {
+
+		signals.rendererChanged.dispatch( rendererType.getValue() );
+		editor.config.setKey( 'renderer', rendererType.getValue() );
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Scene.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Scene.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.Scene.js	(revision 28897)
@@ -0,0 +1,203 @@
+Sidebar.Scene = function ( editor ) {
+
+	var signals = editor.signals;
+
+	var container = new UI.Panel();
+
+	container.add( new UI.Text( 'SCENE' ) );
+	container.add( new UI.Break(), new UI.Break() );
+
+	var outliner = new UI.FancySelect().setId( 'outliner' );
+	outliner.onChange( function () {
+
+		editor.selectById( parseInt( outliner.getValue() ) );
+
+	} );
+	container.add( outliner );
+	container.add( new UI.Break() );
+
+	// fog
+
+	var updateFogParameters = function () {
+
+		var near = fogNear.getValue();
+		var far = fogFar.getValue();
+		var density = fogDensity.getValue();
+
+		signals.fogParametersChanged.dispatch( near, far, density );
+
+	};
+
+	var fogTypeRow = new UI.Panel();
+	var fogType = new UI.Select().setOptions( {
+
+		'None': 'None',
+		'Fog': 'Linear',
+		'FogExp2': 'Exponential'
+
+	} ).setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' )
+	fogType.onChange( function () {
+
+		var type = fogType.getValue();
+		signals.fogTypeChanged.dispatch( type );
+
+		refreshFogUI();
+
+	} );
+
+	fogTypeRow.add( new UI.Text( 'Fog' ).setWidth( '90px' ) );
+	fogTypeRow.add( fogType );
+
+	container.add( fogTypeRow );
+
+	// fog color
+
+	var fogColorRow = new UI.Panel();
+	fogColorRow.setDisplay( 'none' );
+
+	var fogColor = new UI.Color().setValue( '#aaaaaa' )
+	fogColor.onChange( function () {
+
+		signals.fogColorChanged.dispatch( fogColor.getHexValue() );
+
+	} );
+
+	fogColorRow.add( new UI.Text( 'Fog color' ).setWidth( '90px' ) );
+	fogColorRow.add( fogColor );
+
+	container.add( fogColorRow );
+
+	// fog near
+
+	var fogNearRow = new UI.Panel();
+	fogNearRow.setDisplay( 'none' );
+
+	var fogNear = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, Infinity ).onChange( updateFogParameters );
+
+	fogNearRow.add( new UI.Text( 'Fog near' ).setWidth( '90px' ) );
+	fogNearRow.add( fogNear );
+
+	container.add( fogNearRow );
+
+	var fogFarRow = new UI.Panel();
+	fogFarRow.setDisplay( 'none' );
+
+	// fog far
+
+	var fogFar = new UI.Number( 5000 ).setWidth( '60px' ).setRange( 0, Infinity ).onChange( updateFogParameters );
+
+	fogFarRow.add( new UI.Text( 'Fog far' ).setWidth( '90px' ) );
+	fogFarRow.add( fogFar );
+
+	container.add( fogFarRow );
+
+	// fog density
+
+	var fogDensityRow = new UI.Panel();
+	fogDensityRow.setDisplay( 'none' );
+
+	var fogDensity = new UI.Number( 0.00025 ).setWidth( '60px' ).setRange( 0, 0.1 ).setPrecision( 5 ).onChange( updateFogParameters );
+
+	fogDensityRow.add( new UI.Text( 'Fog density' ).setWidth( '90px' ) );
+	fogDensityRow.add( fogDensity );
+
+	container.add( fogDensityRow );
+
+	//
+
+	var refreshFogUI = function () {
+
+		var type = fogType.getValue();
+
+		fogColorRow.setDisplay( type === 'None' ? 'none' : '' );
+		fogNearRow.setDisplay( type === 'Fog' ? '' : 'none' );
+		fogFarRow.setDisplay( type === 'Fog' ? '' : 'none' );
+		fogDensityRow.setDisplay( type === 'FogExp2' ? '' : 'none' );
+
+	};
+
+	// events
+
+	signals.sceneGraphChanged.add( function () {
+
+		var scene = editor.scene;
+		var sceneType = editor.getObjectType( scene );
+
+		var options = {};
+
+		options[ scene.id ] = '<span class="type ' + sceneType + '"></span> ' + scene.name;
+
+		( function addObjects( objects, pad ) {
+
+			for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+				var object = objects[ i ];
+				var objectType = editor.getObjectType( object );
+
+				var option = pad + '<span class="type ' + objectType + '"></span> ' + object.name;
+
+				if ( object instanceof THREE.Mesh ) {
+
+					var geometry = object.geometry;
+					var material = object.material;
+
+					var geometryType = editor.getGeometryType( geometry );
+					var materialType = editor.getMaterialType( material );
+
+					option += ' <span class="type ' + geometryType + '"></span> ' + geometry.name;
+					option += ' <span class="type ' + materialType + '"></span> ' + material.name;
+
+				}
+
+				options[ object.id ] = option;
+
+				addObjects( object.children, pad + '&nbsp;&nbsp;&nbsp;' );
+
+			}
+
+		} )( scene.children, '&nbsp;&nbsp;&nbsp;' );
+
+		outliner.setOptions( options );
+
+		if ( editor.selected !== null ) {
+
+			outliner.setValue( editor.selected.id );
+
+		}
+
+		if ( scene.fog ) {
+
+			fogColor.setHexValue( scene.fog.color.getHex() );
+
+			if ( scene.fog instanceof THREE.Fog ) {
+
+				fogType.setValue( "Fog" );
+				fogNear.setValue( scene.fog.near );
+				fogFar.setValue( scene.fog.far );
+
+			} else if ( scene.fog instanceof THREE.FogExp2 ) {
+
+				fogType.setValue( "FogExp2" );
+				fogDensity.setValue( scene.fog.density );
+
+			}
+
+		} else {
+
+			fogType.setValue( "None" );
+
+		}
+
+		refreshFogUI();
+
+	} );
+
+	signals.objectSelected.add( function ( object ) {
+
+		outliner.setValue( object !== null ? object.id : null );
+
+	} );
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Sidebar.js	(revision 28897)
@@ -0,0 +1,14 @@
+var Sidebar = function ( editor ) {
+
+	var container = new UI.Panel();
+
+	container.add( new Sidebar.Renderer( editor ) );
+	container.add( new Sidebar.Scene( editor ) );
+	container.add( new Sidebar.Object3D( editor ) );
+	container.add( new Sidebar.Geometry( editor ) );
+	container.add( new Sidebar.Material( editor ) );
+	container.add( new Sidebar.Animation( editor ) );
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Storage.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Storage.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Storage.js	(revision 28897)
@@ -0,0 +1,83 @@
+var Storage = function () {
+
+	var indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
+
+	var name = 'threejs-editor';
+	var version = 1;
+
+	var database;
+
+	return {
+
+		init: function ( callback ) {
+
+			var request = indexedDB.open( name, version );
+			request.onupgradeneeded = function ( event ) {
+
+				var db = event.target.result;
+
+				if ( db.objectStoreNames.contains( 'states' ) === false ) {
+
+					db.createObjectStore( 'states' );
+
+				}
+
+			};
+			request.onsuccess = function ( event ) {
+
+				database = event.target.result;
+
+				callback();
+
+			};
+			request.onerror = function ( event ) {
+
+				console.error( 'IndexedDB', event );
+
+			};
+			
+
+		},
+
+		get: function ( callback ) {
+
+			var transaction = database.transaction( [ 'states' ], 'readwrite' );
+			var objectStore = transaction.objectStore( 'states' );
+			var request = objectStore.get( 0 );
+			request.onsuccess = function ( event ) {
+
+				callback( event.target.result );
+
+			};
+
+		},
+
+		set: function ( data, callback ) {
+
+			var transaction = database.transaction( [ 'states' ], 'readwrite' );
+			var objectStore = transaction.objectStore( 'states' );
+			var request = objectStore.put( data, 0 );
+			request.onsuccess = function ( event ) {
+
+				console.log( '[' + /\d\d\:\d\d\:\d\d/.exec( new Date() )[ 0 ] + ']', 'Saved state to IndexedDB.' );
+
+			};
+
+		},
+
+		clear: function ( callback ) {
+
+			var transaction = database.transaction( [ 'states' ], 'readwrite' );
+			var objectStore = transaction.objectStore( 'states' );
+			var request = objectStore.clear();
+			request.onsuccess = function ( event ) {
+
+				callback();
+			
+			};
+
+		}
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Toolbar.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Toolbar.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Toolbar.js	(revision 28897)
@@ -0,0 +1,59 @@
+var Toolbar = function ( editor ) {
+
+	var signals = editor.signals;
+
+	var container = new UI.Panel();
+
+	var buttons = new UI.Panel();
+	container.add( buttons );
+
+	// translate / rotate / scale
+
+	var translate = new UI.Button( 'translate' ).onClick( function () {
+
+		signals.transformModeChanged.dispatch( 'translate' );
+
+	} );
+	buttons.add( translate );
+
+	var rotate = new UI.Button( 'rotate' ).onClick( function () {
+
+		signals.transformModeChanged.dispatch( 'rotate' );
+
+	} );
+	buttons.add( rotate );
+
+	var scale = new UI.Button( 'scale' ).onClick( function () {
+
+		signals.transformModeChanged.dispatch( 'scale' );
+
+	} );
+	buttons.add( scale );
+
+	// grid
+
+	var grid = new UI.Number( 25 ).onChange( update );
+	grid.dom.style.width = '42px';
+	buttons.add( new UI.Text( 'Grid: ' ) );
+	buttons.add( grid );
+
+	var snap = new UI.Checkbox( false ).onChange( update );
+	buttons.add( snap );
+	buttons.add( new UI.Text( 'snap' ) );
+
+	var local = new UI.Checkbox( false ).onChange( update );
+	buttons.add( local );
+	buttons.add( new UI.Text( 'local' ) );
+
+	function update() {
+
+		signals.snapChanged.dispatch( snap.getValue() === true ? grid.getValue() : null );
+		signals.spaceChanged.dispatch( local.getValue() === true ? "local" : "world" );
+
+	}
+
+	update();
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Viewport.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Viewport.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/Viewport.js	(revision 28897)
@@ -0,0 +1,570 @@
+var Viewport = function ( editor ) {
+
+	var signals = editor.signals;
+
+	var container = new UI.Panel();
+	container.setPosition( 'absolute' );
+
+	var info = new UI.Text();
+	info.setPosition( 'absolute' );
+	info.setRight( '5px' );
+	info.setBottom( '5px' );
+	info.setFontSize( '12px' );
+	info.setColor( '#ffffff' );
+	info.setValue( 'objects: 0, vertices: 0, faces: 0' );
+	container.add( info );
+
+	var scene = editor.scene;
+	var sceneHelpers = editor.sceneHelpers;
+
+	var objects = [];
+
+	// helpers
+
+	var grid = new THREE.GridHelper( 500, 25 );
+	sceneHelpers.add( grid );
+
+	//
+
+	var camera = new THREE.PerspectiveCamera( 50, container.dom.offsetWidth / container.dom.offsetHeight, 1, 5000 );
+	camera.position.fromArray( editor.config.getKey( 'camera' ).position );
+	camera.lookAt( new THREE.Vector3().fromArray( editor.config.getKey( 'camera' ).target ) );
+
+	//
+
+	var selectionBox = new THREE.BoxHelper();
+	selectionBox.material.depthTest = false;
+	selectionBox.material.transparent = true;
+	selectionBox.visible = false;
+	sceneHelpers.add( selectionBox );
+
+	var transformControls = new THREE.TransformControls( camera, container.dom );
+	transformControls.addEventListener( 'change', function () {
+
+		controls.enabled = true;
+
+		if ( transformControls.axis !== undefined ) {
+
+			controls.enabled = false;
+
+		}
+
+		if ( editor.selected !== null ) {
+
+			signals.objectChanged.dispatch( editor.selected );
+
+		}
+
+	} );
+	sceneHelpers.add( transformControls );
+
+	// fog
+
+	var oldFogType = "None";
+	var oldFogColor = 0xaaaaaa;
+	var oldFogNear = 1;
+	var oldFogFar = 5000;
+	var oldFogDensity = 0.00025;
+
+	// object picking
+
+	var ray = new THREE.Raycaster();
+	var projector = new THREE.Projector();
+
+	// events
+
+	var getIntersects = function ( event, object ) {
+
+		var rect = container.dom.getBoundingClientRect();
+		x = ( event.clientX - rect.left ) / rect.width;
+		y = ( event.clientY - rect.top ) / rect.height;
+		var vector = new THREE.Vector3( ( x ) * 2 - 1, - ( y ) * 2 + 1, 0.5 );
+
+		projector.unprojectVector( vector, camera );
+
+		ray.set( camera.position, vector.sub( camera.position ).normalize() );
+
+		if ( object instanceof Array ) {
+
+			return ray.intersectObjects( object );
+
+		}
+
+		return ray.intersectObject( object );
+
+	};
+
+	var onMouseDownPosition = new THREE.Vector2();
+	var onMouseUpPosition = new THREE.Vector2();
+
+	var onMouseDown = function ( event ) {
+
+		event.preventDefault();
+
+		var rect = container.dom.getBoundingClientRect();
+		x = (event.clientX - rect.left) / rect.width;
+		y = (event.clientY - rect.top) / rect.height;
+		onMouseDownPosition.set( x, y );
+
+		document.addEventListener( 'mouseup', onMouseUp, false );
+
+	};
+
+	var onMouseUp = function ( event ) {
+
+		var rect = container.dom.getBoundingClientRect();
+		x = (event.clientX - rect.left) / rect.width;
+		y = (event.clientY - rect.top) / rect.height;
+		onMouseUpPosition.set( x, y );
+
+		if ( onMouseDownPosition.distanceTo( onMouseUpPosition ) == 0 ) {
+
+			var intersects = getIntersects( event, objects );
+
+			if ( intersects.length > 0 ) {
+
+				var object = intersects[ 0 ].object;
+
+				if ( object.userData.object !== undefined ) {
+
+					// helper
+
+					editor.select( object.userData.object );
+
+				} else {
+
+					editor.select( object );
+
+				}
+
+			} else {
+
+				editor.select( null );
+
+			}
+
+			render();
+
+		}
+
+		document.removeEventListener( 'mouseup', onMouseUp );
+
+	};
+
+	var onDoubleClick = function ( event ) {
+
+		var intersects = getIntersects( event, objects );
+
+		if ( intersects.length > 0 && intersects[ 0 ].object === editor.selected ) {
+
+			controls.focus( editor.selected );
+
+		}
+
+	};
+
+	container.dom.addEventListener( 'mousedown', onMouseDown, false );
+	container.dom.addEventListener( 'dblclick', onDoubleClick, false );
+
+	// controls need to be added *after* main logic,
+	// otherwise controls.enabled doesn't work.
+
+	var controls = new THREE.EditorControls( camera, container.dom );
+	controls.center.fromArray( editor.config.getKey( 'camera' ).target )
+	controls.addEventListener( 'change', function () {
+
+		transformControls.update();
+		signals.cameraChanged.dispatch( camera );
+
+	} );
+
+	// signals
+
+	signals.themeChanged.add( function ( value ) {
+
+		switch ( value ) {
+
+			case 'css/light.css':
+				grid.setColors( 0x444444, 0x888888 );
+				clearColor = 0xaaaaaa;
+				break;
+			case 'css/dark.css':
+				grid.setColors( 0xbbbbbb, 0x888888 );
+				clearColor = 0x333333;
+				break;
+
+		}
+		
+		renderer.setClearColor( clearColor );
+
+		render();
+
+	} );
+
+	signals.transformModeChanged.add( function ( mode ) {
+
+		transformControls.setMode( mode );
+
+	} );
+
+	signals.snapChanged.add( function ( dist ) {
+
+		transformControls.setSnap( dist );
+
+	} );
+
+	signals.spaceChanged.add( function ( space ) {
+
+		transformControls.setSpace( space );
+
+	} );
+
+	signals.rendererChanged.add( function ( type ) {
+
+		container.dom.removeChild( renderer.domElement );
+
+		renderer = new THREE[ type ]( { antialias: true } );
+		renderer.autoClear = false;
+		renderer.autoUpdateScene = false;
+		renderer.setClearColor( clearColor );
+		renderer.setSize( container.dom.offsetWidth, container.dom.offsetHeight );
+
+		container.dom.appendChild( renderer.domElement );
+
+		render();
+
+	} );
+
+	signals.sceneGraphChanged.add( function () {
+
+		render();
+		updateInfo();
+
+	} );
+
+	signals.cameraChanged.add( function () {
+
+		editor.config.setKey( 'camera', {
+			position: camera.position.toArray(),
+			target: controls.center.toArray()
+		} );
+
+		render();
+
+	} );
+
+	signals.objectSelected.add( function ( object ) {
+
+		selectionBox.visible = false;
+		transformControls.detach();
+
+		if ( object !== null ) {
+
+			if ( object.geometry !== undefined ) {
+
+				selectionBox.update( object );
+				selectionBox.visible = true;
+
+			}
+
+			if ( object instanceof THREE.PerspectiveCamera === false ) {
+
+				transformControls.attach( object );
+
+			}
+
+		}
+
+		render();
+
+	} );
+
+	signals.objectAdded.add( function ( object ) {
+
+		var materialsNeedUpdate = false;
+
+		object.traverse( function ( child ) {
+
+			if ( child instanceof THREE.Light ) materialsNeedUpdate = true;
+
+			objects.push( child );
+
+		} );
+
+		if ( materialsNeedUpdate === true ) updateMaterials();
+
+	} );
+
+	signals.objectChanged.add( function ( object ) {
+
+		transformControls.update();
+
+		if ( object !== camera ) {
+
+			if ( object.geometry !== undefined ) {
+
+				selectionBox.update( object );
+
+			}
+
+			if ( editor.helpers[ object.id ] !== undefined ) {
+
+				editor.helpers[ object.id ].update();
+
+			}
+
+			updateInfo();
+
+		}
+
+		render();
+
+	} );
+
+	signals.objectRemoved.add( function ( object ) {
+
+		var materialsNeedUpdate = false;
+
+		object.traverse( function ( child ) {
+
+			if ( child instanceof THREE.Light ) materialsNeedUpdate = true;
+
+			objects.splice( objects.indexOf( child ), 1 );
+
+		} );
+
+		if ( materialsNeedUpdate === true ) updateMaterials();
+
+	} );
+
+	signals.helperAdded.add( function ( object ) {
+
+		objects.push( object.getObjectByName( 'picker' ) );
+
+	} );
+
+	signals.helperRemoved.add( function ( object ) {
+
+		objects.splice( objects.indexOf( object.getObjectByName( 'picker' ) ), 1 );
+
+	} );
+
+	signals.materialChanged.add( function ( material ) {
+
+		render();
+
+	} );
+
+	signals.fogTypeChanged.add( function ( fogType ) {
+
+		if ( fogType !== oldFogType ) {
+
+			if ( fogType === "None" ) {
+
+				scene.fog = null;
+
+			} else if ( fogType === "Fog" ) {
+
+				scene.fog = new THREE.Fog( oldFogColor, oldFogNear, oldFogFar );
+
+			} else if ( fogType === "FogExp2" ) {
+
+				scene.fog = new THREE.FogExp2( oldFogColor, oldFogDensity );
+
+			}
+
+			updateMaterials();
+
+			oldFogType = fogType;
+
+		}
+
+		render();
+
+	} );
+
+	signals.fogColorChanged.add( function ( fogColor ) {
+
+		oldFogColor = fogColor;
+
+		updateFog( scene );
+
+		render();
+
+	} );
+
+	signals.fogParametersChanged.add( function ( near, far, density ) {
+
+		oldFogNear = near;
+		oldFogFar = far;
+		oldFogDensity = density;
+
+		updateFog( scene );
+
+		render();
+
+	} );
+
+	signals.windowResize.add( function () {
+
+		camera.aspect = container.dom.offsetWidth / container.dom.offsetHeight;
+		camera.updateProjectionMatrix();
+
+		renderer.setSize( container.dom.offsetWidth, container.dom.offsetHeight );
+
+		render();
+
+	} );
+
+	signals.playAnimations.add( function (animations) {
+		
+		function animate() {
+
+			requestAnimationFrame( animate );
+			
+			for ( var i = 0; i < animations.length ; i ++ ) {
+
+				animations[i].update(0.016);
+
+			} 
+
+			render();
+		}
+
+		animate();
+
+	} );
+
+	//
+
+	var clearColor, renderer;
+
+	if ( editor.config.getKey( 'renderer' ) !== undefined ) {
+
+		renderer = new THREE[ editor.config.getKey( 'renderer' ) ]( { antialias: true } );
+
+	} else {
+
+		if ( System.support.webgl === true ) {
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+
+		} else {
+
+			renderer = new THREE.CanvasRenderer();
+
+		}
+
+	}
+
+	renderer.autoClear = false;
+	renderer.autoUpdateScene = false;
+	container.dom.appendChild( renderer.domElement );
+
+	animate();
+
+	//
+
+	function updateInfo() {
+
+		var objects = 0;
+		var vertices = 0;
+		var faces = 0;
+
+		scene.traverse( function ( object ) {
+
+			if ( object instanceof THREE.Mesh ) {
+
+				objects ++;
+
+				var geometry = object.geometry;
+
+				if ( geometry instanceof THREE.Geometry ) {
+
+					vertices += geometry.vertices.length;
+					faces += geometry.faces.length;
+
+				} else if ( geometry instanceof THREE.BufferGeometry ) {
+
+					vertices += geometry.attributes.position.array.length / 3;
+
+					if ( geometry.attributes.index !== undefined ) {
+
+						faces += geometry.attributes.index.array.length / 3;
+
+					} else {
+
+						faces += vertices / 3;
+
+					}
+
+				}
+
+			}
+
+		} );
+
+		info.setValue( 'objects: ' + objects + ', vertices: ' + vertices + ', faces: ' + faces );
+
+	}
+
+	function updateMaterials() {
+
+		editor.scene.traverse( function ( node ) {
+
+			if ( node.material ) {
+
+				node.material.needsUpdate = true;
+
+				if ( node.material instanceof THREE.MeshFaceMaterial ) {
+
+					for ( var i = 0; i < node.material.materials.length; i ++ ) {
+
+						node.material.materials[ i ].needsUpdate = true;
+
+					}
+
+				}
+
+			}
+
+		} );
+
+	}
+
+	function updateFog( root ) {
+
+		if ( root.fog ) {
+
+			root.fog.color.setHex( oldFogColor );
+
+			if ( root.fog.near !== undefined ) root.fog.near = oldFogNear;
+			if ( root.fog.far !== undefined ) root.fog.far = oldFogFar;
+			if ( root.fog.density !== undefined ) root.fog.density = oldFogDensity;
+
+		}
+
+	}
+
+	function animate() {
+
+		requestAnimationFrame( animate );
+
+	}
+
+	function render() {
+
+		sceneHelpers.updateMatrixWorld();
+		scene.updateMatrixWorld();
+
+		renderer.clear();
+		renderer.render( scene, camera );
+		renderer.render( sceneHelpers, camera );
+
+		//console.trace();
+
+	}
+
+	return container;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/signals.min.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/signals.min.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/signals.min.js	(revision 28897)
@@ -0,0 +1,13 @@
+/*
+
+ JS Signals <http://millermedeiros.github.com/js-signals/>
+ Released under the MIT license
+ Author: Miller Medeiros
+ Version: 0.7.4 - Build: 252 (2012/02/24 10:30 PM)
+*/
+(function(h){function g(a,b,c,d,e){this._listener=b;this._isOnce=c;this.context=d;this._signal=a;this._priority=e||0}function f(a,b){if(typeof a!=="function")throw Error("listener is a required param of {fn}() and should be a Function.".replace("{fn}",b));}var e={VERSION:"0.7.4"};g.prototype={active:!0,params:null,execute:function(a){var b;this.active&&this._listener&&(a=this.params?this.params.concat(a):a,b=this._listener.apply(this.context,a),this._isOnce&&this.detach());return b},detach:function(){return this.isBound()?
+this._signal.remove(this._listener,this.context):null},isBound:function(){return!!this._signal&&!!this._listener},getListener:function(){return this._listener},_destroy:function(){delete this._signal;delete this._listener;delete this.context},isOnce:function(){return this._isOnce},toString:function(){return"[SignalBinding isOnce:"+this._isOnce+", isBound:"+this.isBound()+", active:"+this.active+"]"}};e.Signal=function(){this._bindings=[];this._prevParams=null};e.Signal.prototype={memorize:!1,_shouldPropagate:!0,
+active:!0,_registerListener:function(a,b,c,d){var e=this._indexOfListener(a,c);if(e!==-1){if(a=this._bindings[e],a.isOnce()!==b)throw Error("You cannot add"+(b?"":"Once")+"() then add"+(!b?"":"Once")+"() the same listener without removing the relationship first.");}else a=new g(this,a,b,c,d),this._addBinding(a);this.memorize&&this._prevParams&&a.execute(this._prevParams);return a},_addBinding:function(a){var b=this._bindings.length;do--b;while(this._bindings[b]&&a._priority<=this._bindings[b]._priority);
+this._bindings.splice(b+1,0,a)},_indexOfListener:function(a,b){for(var c=this._bindings.length,d;c--;)if(d=this._bindings[c],d._listener===a&&d.context===b)return c;return-1},has:function(a,b){return this._indexOfListener(a,b)!==-1},add:function(a,b,c){f(a,"add");return this._registerListener(a,!1,b,c)},addOnce:function(a,b,c){f(a,"addOnce");return this._registerListener(a,!0,b,c)},remove:function(a,b){f(a,"remove");var c=this._indexOfListener(a,b);c!==-1&&(this._bindings[c]._destroy(),this._bindings.splice(c,
+1));return a},removeAll:function(){for(var a=this._bindings.length;a--;)this._bindings[a]._destroy();this._bindings.length=0},getNumListeners:function(){return this._bindings.length},halt:function(){this._shouldPropagate=!1},dispatch:function(a){if(this.active){var b=Array.prototype.slice.call(arguments),c=this._bindings.length,d;if(this.memorize)this._prevParams=b;if(c){d=this._bindings.slice();this._shouldPropagate=!0;do c--;while(d[c]&&this._shouldPropagate&&d[c].execute(b)!==!1)}}},forget:function(){this._prevParams=
+null},dispose:function(){this.removeAll();delete this._bindings;delete this._prevParams},toString:function(){return"[Signal active:"+this.active+" numListeners:"+this.getNumListeners()+"]"}};typeof define==="function"&&define.amd?define(e):typeof module!=="undefined"&&module.exports?module.exports=e:h.signals=e})(this);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/ui.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/ui.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/ui.js	(revision 28897)
@@ -0,0 +1,947 @@
+var UI = {};
+
+UI.Element = function () {};
+
+UI.Element.prototype = {
+
+	setId: function ( id ) {
+
+		this.dom.id = id;
+		
+		return this;
+
+	},
+
+	setClass: function ( name ) {
+
+		this.dom.className = name;
+
+		return this;
+
+	},
+
+	setStyle: function ( style, array ) {
+
+		for ( var i = 0; i < array.length; i ++ ) {
+
+			this.dom.style[ style ] = array[ i ];
+
+		}
+
+	},
+
+	setDisabled: function ( value ) {
+
+		this.dom.disabled = value;
+
+		return this;
+
+	},
+
+	setTextContent: function ( value ) {
+
+		this.dom.textContent = value;
+
+		return this;
+
+	}
+
+}
+
+// properties
+
+var properties = [ 'position', 'left', 'top', 'right', 'bottom', 'width', 'height', 'border', 'borderLeft',
+'borderTop', 'borderRight', 'borderBottom', 'borderColor', 'display', 'overflow', 'margin', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'padding', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'color',
+'backgroundColor', 'opacity', 'fontSize', 'fontWeight', 'textTransform', 'cursor' ];
+
+properties.forEach( function ( property ) {
+
+	var method = 'set' + property.substr( 0, 1 ).toUpperCase() + property.substr( 1, property.length );
+
+	UI.Element.prototype[ method ] = function () {
+
+		this.setStyle( property, arguments );
+		return this;
+
+	};
+
+} );
+
+// events
+
+var events = [ 'KeyUp', 'KeyDown', 'MouseOver', 'MouseOut', 'Click', 'Change' ];
+
+events.forEach( function ( event ) {
+
+	var method = 'on' + event;
+
+	UI.Element.prototype[ method ] = function ( callback ) {
+
+		this.dom.addEventListener( event.toLowerCase(), callback.bind( this ), false );
+
+		return this;
+
+	};
+
+} );
+
+
+// Panel
+
+UI.Panel = function () {
+
+	UI.Element.call( this );
+
+	var dom = document.createElement( 'div' );
+	dom.className = 'Panel';
+
+	this.dom = dom;
+
+	return this;
+};
+
+UI.Panel.prototype = Object.create( UI.Element.prototype );
+
+UI.Panel.prototype.add = function () {
+
+	for ( var i = 0; i < arguments.length; i ++ ) {
+
+		this.dom.appendChild( arguments[ i ].dom );
+
+	}
+
+	return this;
+
+};
+
+
+UI.Panel.prototype.remove = function () {
+
+	for ( var i = 0; i < arguments.length; i ++ ) {
+
+		this.dom.removeChild( arguments[ i ].dom );
+
+	}
+
+	return this;
+
+};
+
+UI.Panel.prototype.clear = function () {
+
+	while ( this.dom.children.length ) {
+
+		this.dom.removeChild( this.dom.lastChild );
+
+	}
+
+};
+
+// Text
+
+UI.Text = function ( text ) {
+
+	UI.Element.call( this );
+
+	var dom = document.createElement( 'span' );
+	dom.className = 'Text';
+	dom.style.cursor = 'default';
+	dom.style.display = 'inline-block';
+	dom.style.verticalAlign = 'middle';
+
+	this.dom = dom;
+	this.setValue( text );
+
+	return this;
+
+};
+
+UI.Text.prototype = Object.create( UI.Element.prototype );
+
+UI.Text.prototype.setValue = function ( value ) {
+
+	if ( value !== undefined ) {
+
+		this.dom.textContent = value;
+
+	}
+
+	return this;
+
+};
+
+
+// Input
+
+UI.Input = function () {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'input' );
+	dom.className = 'Input';
+	dom.style.padding = '2px';
+	dom.style.border = '1px solid #ccc';
+
+	dom.addEventListener( 'keydown', function ( event ) {
+
+		event.stopPropagation();
+
+	}, false );
+
+	this.dom = dom;
+
+	return this;
+
+};
+
+UI.Input.prototype = Object.create( UI.Element.prototype );
+
+UI.Input.prototype.getValue = function () {
+
+	return this.dom.value;
+
+};
+
+UI.Input.prototype.setValue = function ( value ) {
+
+	this.dom.value = value;
+
+	return this;
+
+};
+
+
+// TextArea
+
+UI.TextArea = function () {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'textarea' );
+	dom.className = 'TextArea';
+	dom.style.padding = '2px';
+	dom.style.border = '1px solid #ccc';
+
+	dom.addEventListener( 'keydown', function ( event ) {
+
+		event.stopPropagation();
+
+	}, false );
+
+	this.dom = dom;
+
+	return this;
+
+};
+
+UI.TextArea.prototype = Object.create( UI.Element.prototype );
+
+UI.TextArea.prototype.getValue = function () {
+
+	return this.dom.value;
+
+};
+
+UI.TextArea.prototype.setValue = function ( value ) {
+
+	this.dom.value = value;
+
+	return this;
+
+};
+
+
+// Select
+
+UI.Select = function () {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'select' );
+	dom.className = 'Select';
+	dom.style.width = '64px';
+	dom.style.height = '16px';
+	dom.style.border = '0px';
+	dom.style.padding = '0px';
+
+	this.dom = dom;
+
+	return this;
+
+};
+
+UI.Select.prototype = Object.create( UI.Element.prototype );
+
+UI.Select.prototype.setMultiple = function ( boolean ) {
+
+	this.dom.multiple = boolean;
+
+	return this;
+
+};
+
+UI.Select.prototype.setOptions = function ( options ) {
+
+	var selected = this.dom.value;
+
+	while ( this.dom.children.length > 0 ) {
+
+		this.dom.removeChild( this.dom.firstChild );
+
+	}
+
+	for ( var key in options ) {
+
+		var option = document.createElement( 'option' );
+		option.value = key;
+		option.innerHTML = options[ key ];
+		this.dom.appendChild( option );
+
+	}
+
+	this.dom.value = selected;
+
+	return this;
+
+};
+
+UI.Select.prototype.getValue = function () {
+
+	return this.dom.value;
+
+};
+
+UI.Select.prototype.setValue = function ( value ) {
+
+	this.dom.value = value;
+
+	return this;
+
+};
+
+// FancySelect
+
+UI.FancySelect = function () {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'div' );
+	dom.className = 'FancySelect';
+	dom.tabIndex = 0;	// keyup event is ignored without setting tabIndex
+
+	// Broadcast for object selection after arrow navigation
+	var changeEvent = document.createEvent('HTMLEvents');
+	changeEvent.initEvent( 'change', true, true );
+
+	// Prevent native scroll behavior
+	dom.addEventListener( 'keydown', function (event) {
+
+		switch ( event.keyCode ) {
+			case 38: // up
+			case 40: // down
+				event.preventDefault();
+				event.stopPropagation();
+				break;
+		}
+
+	}, false);
+
+	// Keybindings to support arrow navigation
+	dom.addEventListener( 'keyup', function (event) {
+
+		switch ( event.keyCode ) {
+			case 38: // up
+			case 40: // down
+				scope.selectedIndex += ( event.keyCode == 38 ) ? -1 : 1;
+
+				if ( scope.selectedIndex >= 0 && scope.selectedIndex < scope.options.length ) {
+
+					// Highlight selected dom elem and scroll parent if needed
+					scope.setValue( scope.options[ scope.selectedIndex ].value );
+
+					// Invoke object/helper/mesh selection logic
+					scope.dom.dispatchEvent( changeEvent );
+
+				}
+
+				break;
+		}
+	}, false);
+
+	this.dom = dom;
+
+	this.options = [];
+	this.selectedIndex = -1;
+	this.selectedValue = null;
+
+	return this;
+
+};
+
+UI.FancySelect.prototype = Object.create( UI.Element.prototype );
+
+UI.FancySelect.prototype.setOptions = function ( options ) {
+
+	var scope = this;
+
+	var changeEvent = document.createEvent( 'HTMLEvents' );
+	changeEvent.initEvent( 'change', true, true );
+
+	while ( scope.dom.children.length > 0 ) {
+
+		scope.dom.removeChild( scope.dom.firstChild );
+
+	}
+
+	scope.options = [];
+
+	for ( var key in options ) {
+
+		var option = document.createElement('div');
+		option.className = 'option';
+		option.innerHTML = options[ key ];
+		option.value = key;
+		scope.dom.appendChild( option );
+
+		scope.options.push( option );
+
+		option.addEventListener( 'click', function ( event ) {
+
+			scope.setValue( this.value );
+			scope.dom.dispatchEvent( changeEvent );
+
+		}, false );
+
+	}
+
+	return scope;
+
+};
+
+UI.FancySelect.prototype.getValue = function () {
+
+	return this.selectedValue;
+
+};
+
+UI.FancySelect.prototype.setValue = function ( value ) {
+
+	if ( typeof value === 'number' ) value = value.toString();
+
+	for ( var i = 0; i < this.options.length; i ++ ) {
+
+		var element = this.options[ i ];
+
+		if ( element.value === value ) {
+
+			element.classList.add( 'active' );
+
+			// scroll into view
+
+			var y = element.offsetTop - this.dom.offsetTop;
+			var bottomY = y + element.offsetHeight;
+			var minScroll = bottomY - this.dom.offsetHeight;
+
+			if ( this.dom.scrollTop > y ) {
+
+				this.dom.scrollTop = y
+
+			} else if ( this.dom.scrollTop < minScroll ) {
+
+				this.dom.scrollTop = minScroll;
+
+			}
+
+			this.selectedIndex = i;
+
+		} else {
+
+			element.classList.remove( 'active' );
+
+		}
+
+	}
+
+	this.selectedValue = value;
+
+	return this;
+
+};
+
+
+// Checkbox
+
+UI.Checkbox = function ( boolean ) {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'input' );
+	dom.className = 'Checkbox';
+	dom.type = 'checkbox';
+
+	this.dom = dom;
+	this.setValue( boolean );
+
+	return this;
+
+};
+
+UI.Checkbox.prototype = Object.create( UI.Element.prototype );
+
+UI.Checkbox.prototype.getValue = function () {
+
+	return this.dom.checked;
+
+};
+
+UI.Checkbox.prototype.setValue = function ( value ) {
+
+	if ( value !== undefined ) {
+
+		this.dom.checked = value;
+
+	}
+
+	return this;
+
+};
+
+
+// Color
+
+UI.Color = function () {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'input' );
+	dom.className = 'Color';
+	dom.style.width = '64px';
+	dom.style.height = '16px';
+	dom.style.border = '0px';
+	dom.style.padding = '0px';
+	dom.style.backgroundColor = 'transparent';
+
+	try {
+
+		dom.type = 'color';
+		dom.value = '#ffffff';
+
+	} catch ( exception ) {}
+
+	this.dom = dom;
+
+	return this;
+
+};
+
+UI.Color.prototype = Object.create( UI.Element.prototype );
+
+UI.Color.prototype.getValue = function () {
+
+	return this.dom.value;
+
+};
+
+UI.Color.prototype.getHexValue = function () {
+
+	return parseInt( this.dom.value.substr( 1 ), 16 );
+
+};
+
+UI.Color.prototype.setValue = function ( value ) {
+
+	this.dom.value = value;
+
+	return this;
+
+};
+
+UI.Color.prototype.setHexValue = function ( hex ) {
+
+	this.dom.value = "#" + ( '000000' + hex.toString( 16 ) ).slice( -6 );
+
+	return this;
+
+};
+
+
+// Number
+
+UI.Number = function ( number ) {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'input' );
+	dom.className = 'Number';
+	dom.value = '0.00';
+
+	dom.addEventListener( 'keydown', function ( event ) {
+
+		event.stopPropagation();
+
+		if ( event.keyCode === 13 ) dom.blur();
+
+	}, false );
+
+	this.min = - Infinity;
+	this.max = Infinity;
+
+	this.precision = 2;
+	this.step = 1;
+
+	this.dom = dom;
+	this.setValue( number );
+
+	var changeEvent = document.createEvent( 'HTMLEvents' );
+	changeEvent.initEvent( 'change', true, true );
+
+	var distance = 0;
+	var onMouseDownValue = 0;
+
+	var pointer = new THREE.Vector2();
+	var prevPointer = new THREE.Vector2();
+
+	var onMouseDown = function ( event ) {
+
+		event.preventDefault();
+
+		distance = 0;
+
+		onMouseDownValue = parseFloat( dom.value );
+
+		prevPointer.set( event.clientX, event.clientY );
+
+		document.addEventListener( 'mousemove', onMouseMove, false );
+		document.addEventListener( 'mouseup', onMouseUp, false );
+
+	};
+
+	var onMouseMove = function ( event ) {
+
+		var currentValue = dom.value;
+
+		pointer.set( event.clientX, event.clientY );
+
+		distance += ( pointer.x - prevPointer.x ) - ( pointer.y - prevPointer.y );
+
+		var number = onMouseDownValue + ( distance / ( event.shiftKey ? 5 : 50 ) ) * scope.step;
+
+		dom.value = Math.min( scope.max, Math.max( scope.min, number ) ).toFixed( scope.precision );
+
+		if ( currentValue !== dom.value ) dom.dispatchEvent( changeEvent );
+
+		prevPointer.set( event.clientX, event.clientY );
+
+	};
+
+	var onMouseUp = function ( event ) {
+
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+		document.removeEventListener( 'mouseup', onMouseUp, false );
+
+		if ( Math.abs( distance ) < 2 ) {
+
+			dom.focus();
+			dom.select();
+
+		}
+
+	};
+
+	var onChange = function ( event ) {
+
+		var number = parseFloat( dom.value );
+
+		dom.value = isNaN( number ) === false ? number : 0;
+
+	};
+
+	var onFocus = function ( event ) {
+
+		dom.style.backgroundColor = '';
+		dom.style.borderColor = '#ccc';
+		dom.style.cursor = '';
+
+	};
+
+	var onBlur = function ( event ) {
+
+		dom.style.backgroundColor = 'transparent';
+		dom.style.borderColor = 'transparent';
+		dom.style.cursor = 'col-resize';
+
+	};
+
+	dom.addEventListener( 'mousedown', onMouseDown, false );
+	dom.addEventListener( 'change', onChange, false );
+	dom.addEventListener( 'focus', onFocus, false );
+	dom.addEventListener( 'blur', onBlur, false );
+
+	return this;
+
+};
+
+UI.Number.prototype = Object.create( UI.Element.prototype );
+
+UI.Number.prototype.getValue = function () {
+
+	return parseFloat( this.dom.value );
+
+};
+
+UI.Number.prototype.setValue = function ( value ) {
+
+	if ( value !== undefined ) {
+
+		this.dom.value = value.toFixed( this.precision );
+
+	}
+
+	return this;
+
+};
+
+UI.Number.prototype.setRange = function ( min, max ) {
+
+	this.min = min;
+	this.max = max;
+
+	return this;
+
+};
+
+UI.Number.prototype.setPrecision = function ( precision ) {
+
+	this.precision = precision;
+
+	return this;
+
+};
+
+
+// Integer
+
+UI.Integer = function ( number ) {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'input' );
+	dom.className = 'Number';
+	dom.value = '0.00';
+
+	dom.addEventListener( 'keydown', function ( event ) {
+
+		event.stopPropagation();
+
+	}, false );
+
+	this.min = - Infinity;
+	this.max = Infinity;
+
+	this.step = 1;
+
+	this.dom = dom;
+	this.setValue( number );
+
+	var changeEvent = document.createEvent( 'HTMLEvents' );
+	changeEvent.initEvent( 'change', true, true );
+
+	var distance = 0;
+	var onMouseDownValue = 0;
+
+	var pointer = new THREE.Vector2();
+	var prevPointer = new THREE.Vector2();
+
+	var onMouseDown = function ( event ) {
+
+		event.preventDefault();
+
+		distance = 0;
+
+		onMouseDownValue = parseFloat( dom.value );
+
+		prevPointer.set( event.clientX, event.clientY );
+
+		document.addEventListener( 'mousemove', onMouseMove, false );
+		document.addEventListener( 'mouseup', onMouseUp, false );
+
+	};
+
+	var onMouseMove = function ( event ) {
+
+		var currentValue = dom.value;
+
+		pointer.set( event.clientX, event.clientY );
+
+		distance += ( pointer.x - prevPointer.x ) - ( pointer.y - prevPointer.y );
+
+		var number = onMouseDownValue + ( distance / ( event.shiftKey ? 5 : 50 ) ) * scope.step;
+
+		dom.value = Math.min( scope.max, Math.max( scope.min, number ) ) | 0;
+
+		if ( currentValue !== dom.value ) dom.dispatchEvent( changeEvent );
+
+		prevPointer.set( event.clientX, event.clientY );
+
+	};
+
+	var onMouseUp = function ( event ) {
+
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+		document.removeEventListener( 'mouseup', onMouseUp, false );
+
+		if ( Math.abs( distance ) < 2 ) {
+
+			dom.focus();
+			dom.select();
+
+		}
+
+	};
+
+	var onChange = function ( event ) {
+
+		var number = parseInt( dom.value );
+
+		if ( isNaN( number ) === false ) {
+
+			dom.value = number;
+
+		}
+
+	};
+
+	var onFocus = function ( event ) {
+
+		dom.style.backgroundColor = '';
+		dom.style.borderColor = '#ccc';
+		dom.style.cursor = '';
+
+	};
+
+	var onBlur = function ( event ) {
+
+		dom.style.backgroundColor = 'transparent';
+		dom.style.borderColor = 'transparent';
+		dom.style.cursor = 'col-resize';
+
+	};
+
+	dom.addEventListener( 'mousedown', onMouseDown, false );
+	dom.addEventListener( 'change', onChange, false );
+	dom.addEventListener( 'focus', onFocus, false );
+	dom.addEventListener( 'blur', onBlur, false );
+
+	return this;
+
+};
+
+UI.Integer.prototype = Object.create( UI.Element.prototype );
+
+UI.Integer.prototype.getValue = function () {
+
+	return parseInt( this.dom.value );
+
+};
+
+UI.Integer.prototype.setValue = function ( value ) {
+
+	if ( value !== undefined ) {
+
+		this.dom.value = value | 0;
+
+	}
+
+	return this;
+
+};
+
+UI.Integer.prototype.setRange = function ( min, max ) {
+
+	this.min = min;
+	this.max = max;
+
+	return this;
+
+};
+
+
+// Break
+
+UI.Break = function () {
+
+	UI.Element.call( this );
+
+	var dom = document.createElement( 'br' );
+	dom.className = 'Break';
+
+	this.dom = dom;
+
+	return this;
+
+};
+
+UI.Break.prototype = Object.create( UI.Element.prototype );
+
+
+// HorizontalRule
+
+UI.HorizontalRule = function () {
+
+	UI.Element.call( this );
+
+	var dom = document.createElement( 'hr' );
+	dom.className = 'HorizontalRule';
+
+	this.dom = dom;
+
+	return this;
+
+};
+
+UI.HorizontalRule.prototype = Object.create( UI.Element.prototype );
+
+
+// Button
+
+UI.Button = function ( value ) {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'button' );
+	dom.className = 'Button';
+
+	this.dom = dom;
+	this.dom.textContent = value;
+
+	return this;
+
+};
+
+UI.Button.prototype = Object.create( UI.Element.prototype );
+
+UI.Button.prototype.setLabel = function ( value ) {
+
+	this.dom.textContent = value;
+
+	return this;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/ui.three.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/ui.three.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/editor/js/libs/ui.three.js	(revision 28897)
@@ -0,0 +1,266 @@
+// Texture
+
+UI.Texture = function () {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'span' );
+
+	var input = document.createElement( 'input' );
+	input.type = 'file';
+	input.addEventListener( 'change', function ( event ) {
+
+		loadFile( event.target.files[ 0 ] );
+
+	} );
+
+	var canvas = document.createElement( 'canvas' );
+	canvas.width = 32;
+	canvas.height = 16;
+	canvas.style.cursor = 'pointer';
+	canvas.style.marginRight = '5px';
+	canvas.style.border = '1px solid #888';
+	canvas.addEventListener( 'click', function ( event ) {
+
+		input.click();
+
+	}, false );
+	canvas.addEventListener( 'drop', function ( event ) {
+
+		event.preventDefault();
+		event.stopPropagation();
+		loadFile( event.dataTransfer.files[ 0 ] );
+
+	}, false );
+	dom.appendChild( canvas );
+
+	var name = document.createElement( 'input' );
+	name.disabled = true;
+	name.style.width = '64px';
+	name.style.border = '1px solid #ccc';
+	dom.appendChild( name );
+
+	var loadFile = function ( file ) {
+
+		if ( file.type.match( 'image.*' ) ) {
+
+			var reader = new FileReader();
+			reader.addEventListener( 'load', function ( event ) {
+
+				var image = document.createElement( 'img' );
+				image.addEventListener( 'load', function( event ) {
+
+					var texture = new THREE.Texture( this );
+					texture.sourceFile = file.name;
+					texture.needsUpdate = true;
+
+					scope.setValue( texture );
+
+					if ( scope.onChangeCallback ) scope.onChangeCallback();
+
+				}, false );
+
+				image.src = event.target.result;
+
+			}, false );
+
+			reader.readAsDataURL( file );
+
+		}
+
+	}
+
+	this.dom = dom;
+	this.texture = null;
+	this.onChangeCallback = null;
+
+	return this;
+
+};
+
+UI.Texture.prototype = Object.create( UI.Element.prototype );
+
+UI.Texture.prototype.getValue = function () {
+
+	return this.texture;
+
+};
+
+UI.Texture.prototype.setValue = function ( texture ) {
+
+	var canvas = this.dom.children[ 0 ];
+	var name = this.dom.children[ 1 ];
+	var context = canvas.getContext( '2d' );
+
+	if ( texture !== null ) {
+
+		var image = texture.image;
+
+		if ( image.width > 0 ) {
+
+			name.value = texture.sourceFile;
+
+			var scale = canvas.width / image.width;
+			context.drawImage( image, 0, 0, image.width * scale, image.height * scale );
+
+		} else {
+
+			name.value = texture.sourceFile + ' (error)';
+			context.clearRect( 0, 0, canvas.width, canvas.height );
+
+		}
+
+	} else {
+
+		name.value = '';
+		context.clearRect( 0, 0, canvas.width, canvas.height );
+
+	}
+
+	this.texture = texture;
+
+};
+
+UI.Texture.prototype.onChange = function ( callback ) {
+
+	this.onChangeCallback = callback;
+
+	return this;
+
+};
+
+
+// CubeTexture
+
+UI.CubeTexture = function () {
+
+	UI.Element.call( this );
+
+	var scope = this;
+
+	var dom = document.createElement( 'span' );
+
+	var input = document.createElement( 'input' );
+	input.type = 'file';
+	input.addEventListener( 'change', function ( event ) {
+
+		loadFile( event.target.files[ 0 ] );
+
+	}, false );
+
+	var canvas = document.createElement( 'canvas' );
+	canvas.width = 32;
+	canvas.height = 16;
+	canvas.style.cursor = 'pointer';
+	canvas.style.marginRight = '5px';
+	canvas.style.border = '1px solid #888';
+	canvas.addEventListener( 'click', function ( event ) {
+
+		input.click();
+
+	}, false );
+	canvas.addEventListener( 'drop', function ( event ) {
+
+		event.preventDefault();
+		event.stopPropagation();
+		loadFile( event.dataTransfer.files[ 0 ] );
+
+	}, false );
+	dom.appendChild( canvas );
+
+	var name = document.createElement( 'input' );
+	name.disabled = true;
+	name.style.width = '64px';
+	name.style.border = '1px solid #ccc';
+	dom.appendChild( name );
+
+	var loadFile = function ( file ) {
+
+		if ( file.type.match( 'image.*' ) ) {
+
+			var reader = new FileReader();
+			reader.addEventListener( 'load', function ( event ) {
+
+				var image = document.createElement( 'img' );
+				image.addEventListener( 'load', function( event ) {
+
+					var array = [ this, this, this, this, this, this ];
+
+					var texture = new THREE.Texture( array, new THREE.CubeReflectionMapping() );
+					texture.sourceFile = file.name;
+					texture.needsUpdate = true;
+
+					scope.setValue( texture );
+
+					if ( scope.onChangeCallback ) scope.onChangeCallback();
+
+				}, false );
+				image.src = event.target.result;
+
+			}, false );
+			reader.readAsDataURL( file );
+
+		}
+
+	};
+
+	this.dom = dom;
+	this.texture = null;
+	this.onChangeCallback = null;
+
+	return this;
+
+};
+
+UI.CubeTexture.prototype = Object.create( UI.Element.prototype );
+
+UI.CubeTexture.prototype.getValue = function () {
+
+	return this.texture;
+
+};
+
+UI.CubeTexture.prototype.setValue = function ( texture ) {
+
+	var canvas = this.dom.children[ 0 ];
+	var name = this.dom.children[ 1 ];
+	var context = canvas.getContext( '2d' );
+
+	if ( texture !== null ) {
+
+		var image = texture.image[ 0 ];
+
+		if ( image.width > 0 ) {
+
+			name.value = texture.sourceFile;
+
+			var scale = canvas.width / image.width;
+			context.drawImage( image, 0, 0, image.width * scale, image.height * scale );
+
+		} else {
+
+			name.value = texture.sourceFile + ' (error)';
+			context.clearRect( 0, 0, canvas.width, canvas.height );
+
+		}
+
+	} else {
+
+		name.value = '';
+		context.clearRect( 0, 0, canvas.width, canvas.height );
+
+	}
+
+	this.texture = texture;
+
+};
+
+UI.CubeTexture.prototype.onChange = function ( callback ) {
+
+	this.onChangeCallback = callback;
+
+	return this;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/Three.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/Three.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/Three.js	(revision 28897)
@@ -0,0 +1,245 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author Larry Battle / http://bateru.com/news
+ * @author bhouston / http://exocortex.com
+ */
+
+var THREE = { REVISION: '65' };
+
+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;
+*/
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/Camera.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/Camera.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/Camera.js	(revision 28897)
@@ -0,0 +1,44 @@
+/**
+ * @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();
+
+};
+
+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 );
+
+	return camera;
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/OrthographicCamera.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/OrthographicCamera.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/OrthographicCamera.js	(revision 28897)
@@ -0,0 +1,44 @@
+/**
+ * @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;
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/PerspectiveCamera.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/PerspectiveCamera.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/PerspectiveCamera.js	(revision 28897)
@@ -0,0 +1,130 @@
+/**
+ * @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;
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/cameras/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/BufferGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/BufferGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/BufferGeometry.js	(revision 28897)
@@ -0,0 +1,632 @@
+/**
+ * @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,
+
+	addAttribute: function( name, type, numItems, itemSize ) {
+
+		this.attributes[ name ] = {
+
+			itemSize: itemSize,
+			array: new type( numItems * itemSize )
+
+		};
+
+	},
+
+	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 ) {
+
+				box.makeEmpty();
+
+				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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Clock.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Clock.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Clock.js	(revision 28897)
@@ -0,0 +1,72 @@
+/**
+ * @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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/EventDispatcher.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/EventDispatcher.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/EventDispatcher.js	(revision 28897)
@@ -0,0 +1,112 @@
+/**
+ * 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 listenerArray = listeners[ type ];
+
+		if ( listenerArray !== undefined ) {
+
+			var index = listenerArray.indexOf( listener );
+
+			if ( index !== - 1 ) {
+
+				listenerArray.splice( index, 1 );
+
+			}
+
+		}
+
+	},
+
+	dispatchEvent: function () {
+
+		var array = [];
+
+		return function ( event ) {
+
+			if ( this._listeners === undefined ) return;
+
+			var listeners = this._listeners;
+			var listenerArray = listeners[ event.type ];
+
+			if ( listenerArray !== undefined ) {
+
+				event.target = this;
+
+				var length = listenerArray.length;
+
+				for ( var i = 0; i < length; i ++ ) {
+
+					array[ i ] = listenerArray[ i ];
+
+				}
+
+				for ( var i = 0; i < length; i ++ ) {
+
+					array[ i ].call( this, event );
+
+				}
+
+			}
+
+		};
+
+	}()
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Face3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Face3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Face3.js	(revision 28897)
@@ -0,0 +1,49 @@
+/**
+ * @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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Face4.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Face4.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Face4.js	(revision 28897)
@@ -0,0 +1,11 @@
+/**
+ * @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 );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Geometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Geometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Geometry.js	(revision 28897)
@@ -0,0 +1,617 @@
+/**
+ * @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;
+
+		vertices = new Array( this.vertices.length );
+
+		for ( v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+			vertices[ v ] = new THREE.Vector3();
+
+		}
+
+		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 ] = vertices[ face.a ].clone();
+			face.vertexNormals[ 1 ] = vertices[ face.b ].clone();
+			face.vertexNormals[ 2 ] = vertices[ face.c ].clone();
+
+		}
+
+	},
+
+	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;
+
+		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;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Object3D.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Object3D.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Object3D.js	(revision 28897)
@@ -0,0 +1,560 @@
+/**
+ * @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 rotation () { 
+		return this._rotation; 
+	},
+
+	set rotation ( value ) {
+		
+		this._rotation = value;
+		this._rotation._quaternion = this._quaternion;
+		this._quaternion._euler = this._rotation;
+		this._rotation._updateQuaternion();
+		
+	},
+
+	get quaternion () { 
+		return this._quaternion; 
+	},
+	
+	set quaternion ( value ) {
+		
+		this._quaternion = value;
+		this._quaternion._euler = this._rotation;
+		this._rotation._quaternion = this._quaternion;
+		this._quaternion._updateEuler();
+		
+	},
+
+	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 ( matrix ) {
+
+		this.matrix.multiplyMatrices( matrix, this.matrix );
+
+		this.matrix.decompose( this.position, this.quaternion, this.scale );
+
+	},
+
+	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;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Projector.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Projector.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Projector.js	(revision 28897)
@@ -0,0 +1,657 @@
+/**
+ * @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,
+	_sprite, _spriteCount, _spritePool = [], _spritePoolLength = 0,
+
+	_renderData = { objects: [], sprites: [], lights: [], elements: [] },
+
+	_vA = new THREE.Vector3(),
+	_vB = new THREE.Vector3(),
+	_vC = new THREE.Vector3(),
+
+	_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 () {
+
+		var projectionMatrixInverse = new THREE.Matrix4();
+
+		return function ( vector, camera ) {
+
+			projectionMatrixInverse.getInverse( camera.projectionMatrix );
+			_viewProjectionMatrix.multiplyMatrices( camera.matrixWorld, 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.setFromMatrixPosition( object.matrixWorld );
+			_vector3.applyProjection( _viewProjectionMatrix );
+			_object.z = _vector3.z;
+
+		}
+
+		return _object;
+
+	};
+
+	var projectVertex = function ( vertex ) {
+
+		var position = vertex.position;
+		var positionWorld = vertex.positionWorld;
+		var positionScreen = vertex.positionScreen;
+
+		positionWorld.copy( position ).applyMatrix4( _modelMatrix );
+		positionScreen.copy( positionWorld ).applyMatrix4( _viewProjectionMatrix );
+
+		var invW = 1 / positionScreen.w;
+
+		positionScreen.x *= invW;
+		positionScreen.y *= invW;
+		positionScreen.z *= invW;
+
+		vertex.visible = positionScreen.x >= -1 && positionScreen.x <= 1 &&
+				 positionScreen.y >= -1 && positionScreen.y <= 1 &&
+				 positionScreen.z >= -1 && positionScreen.z <= 1;
+
+	};
+
+	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 ) {
+
+			_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,
+		object, geometry, vertices, faces, face, faceVertexNormals, faceVertexUvs, uvs,
+		v1, v2, v3, v4, isFaceMaterial, objectMaterials;
+
+		_face3Count = 0;
+		_lineCount = 0;
+		_spriteCount = 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 ( var 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 ( var v = 0, vl = vertices.length; v < vl; v ++ ) {
+
+					_vertex = getNextVertexInPool();
+					_vertex.position.copy( vertices[ v ] );
+
+					projectVertex( _vertex );
+
+				}
+
+				for ( var 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 ];
+
+					if ( material.morphTargets === true ) {
+
+						var morphTargets = geometry.morphTargets;
+						var morphInfluences = object.morphTargetInfluences;
+
+						var v1p = v1.position;
+						var v2p = v2.position;
+						var v3p = v3.position;
+
+						_vA.set( 0, 0, 0 );
+						_vB.set( 0, 0, 0 );
+						_vC.set( 0, 0, 0 );
+
+						for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) {
+
+							var influence = morphInfluences[ t ];
+
+							if ( influence === 0 ) continue;
+
+							var targets = morphTargets[ t ].vertices;
+
+							_vA.x += ( targets[ face.a ].x - v1p.x ) * influence;
+							_vA.y += ( targets[ face.a ].y - v1p.y ) * influence;
+							_vA.z += ( targets[ face.a ].z - v1p.z ) * influence;
+
+							_vB.x += ( targets[ face.b ].x - v2p.x ) * influence;
+							_vB.y += ( targets[ face.b ].y - v2p.y ) * influence;
+							_vB.z += ( targets[ face.b ].z - v2p.z ) * influence;
+
+							_vC.x += ( targets[ face.c ].x - v3p.x ) * influence;
+							_vC.y += ( targets[ face.c ].y - v3p.y ) * influence;
+							_vC.z += ( targets[ face.c ].z - v3p.z ) * influence;
+
+						}
+
+						v1.position.add( _vA );
+						v2.position.add( _vB );
+						v3.position.add( _vC );
+
+						projectVertex( v1 );
+						projectVertex( v2 );
+						projectVertex( v3 );
+
+					}
+
+					_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 ( var 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 ( var c = 0, cl = Math.min( faceVertexUvs.length, 3 ); c < cl; c ++ ) {
+
+						uvs = faceVertexUvs[ c ][ f ];
+
+						if ( uvs === undefined ) continue;
+
+						for ( var 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;
+
+			_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 >= -1 && _vector4.z <= 1 ) {
+
+				_sprite = getNextSpriteInPool();
+				_sprite.id = object.id;
+				_sprite.x = _vector4.x * invW;
+				_sprite.y = _vector4.y * invW;
+				_sprite.z = _vector4.z;
+				_sprite.object = object;
+
+				_sprite.rotation = object.rotation;
+
+				_sprite.scale.x = object.scale.x * Math.abs( _sprite.x - ( _vector4.x + camera.projectionMatrix.elements[0] ) / ( _vector4.w + camera.projectionMatrix.elements[12] ) );
+				_sprite.scale.y = object.scale.y * Math.abs( _sprite.y - ( _vector4.y + camera.projectionMatrix.elements[5] ) / ( _vector4.w + camera.projectionMatrix.elements[13] ) );
+
+				_sprite.material = object.material;
+
+				_renderData.elements.push( _sprite );
+
+			}
+
+		}
+
+		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 getNextSpriteInPool() {
+
+		if ( _spriteCount === _spritePoolLength ) {
+
+			var sprite = new THREE.RenderableSprite();
+			_spritePool.push( sprite );
+			_spritePoolLength ++;
+			_spriteCount ++
+			return sprite;
+
+		}
+
+		return _spritePool[ _spriteCount ++ ];
+
+	}
+
+	//
+
+	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;
+
+			}
+
+		}
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Raycaster.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Raycaster.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/Raycaster.js	(revision 28897)
@@ -0,0 +1,462 @@
+/**
+ * @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.Sprite ) {
+
+			matrixPosition.setFromMatrixPosition( 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.setFromMatrixPosition( 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 ]
+							);
+
+							
+							if ( material.side === THREE.BackSide ) {
+							
+								var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true ); 
+
+							} else {
+
+								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 ]
+						);
+
+						
+						if ( material.side === THREE.BackSide ) {
+							
+							var intersectionPoint = localRay.intersectTriangle( vC, vB, vA, true ); 
+
+						} else {
+
+							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 ];
+
+					if ( material.morphTargets === true ) {
+
+						var morphTargets = geometry.morphTargets;
+						var morphInfluences = object.morphTargetInfluences;
+
+						vA.set( 0, 0, 0 );
+						vB.set( 0, 0, 0 );
+						vC.set( 0, 0, 0 );
+
+						for ( var t = 0, tl = morphTargets.length; t < tl; t ++ ) {
+
+							var influence = morphInfluences[ t ];
+
+							if ( influence === 0 ) continue;
+
+							var targets = morphTargets[ t ].vertices;
+
+							vA.x += ( targets[ face.a ].x - a.x ) * influence;
+							vA.y += ( targets[ face.a ].y - a.y ) * influence;
+							vA.z += ( targets[ face.a ].z - a.z ) * influence;
+
+							vB.x += ( targets[ face.b ].x - b.x ) * influence;
+							vB.y += ( targets[ face.b ].y - b.y ) * influence;
+							vB.z += ( targets[ face.b ].z - b.z ) * influence;
+
+							vC.x += ( targets[ face.c ].x - c.x ) * influence;
+							vC.y += ( targets[ face.c ].y - c.y ) * influence;
+							vC.z += ( targets[ face.c ].z - c.z ) * influence;
+
+						}
+
+						vA.add( a );
+						vB.add( b );
+						vC.add( c );
+
+						a = vA;
+						b = vB;
+						c = vC;
+
+					}
+
+					if ( material.side === THREE.BackSide ) {
+							
+						var intersectionPoint = localRay.intersectTriangle( c, b, a, true );
+
+					} else {
+								
+						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 ) );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/core/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/FontUtils.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/FontUtils.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/FontUtils.js	(revision 28897)
@@ -0,0 +1,460 @@
+/**
+ * @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,w> */
+
+			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;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/GeometryUtils.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/GeometryUtils.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/GeometryUtils.js	(revision 28897)
@@ -0,0 +1,357 @@
+/**
+ * @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();
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/ImageUtils.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/ImageUtils.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/ImageUtils.js	(revision 28897)
@@ -0,0 +1,657 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.ImageUtils = {
+
+	crossOrigin: undefined,
+
+	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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/SceneUtils.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/SceneUtils.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/SceneUtils.js	(revision 28897)
@@ -0,0 +1,40 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/Animation.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/Animation.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/Animation.js	(revision 28897)
@@ -0,0 +1,393 @@
+/**
+ * @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;
+
+	// Mod operation fails on floats
+	// was this supposed to be in frames?
+	while ( this.currentTime > this.data.length ) {
+
+		this.currentTime -= this.data.length;
+
+	}
+
+	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 );
+
+						// if ( nextKey.index < prevKey.index ) then we have wrapped over the end, and nextKey.time < currentTime will loop forever
+						while ( nextKey !== null && nextKey.time < currentTime && nextKey.index > prevKey.index) {
+
+							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 !== null && nextKey.time < currentTime && nextKey.index > prevKey.index )
+					// if ( nextKey.index < prevKey.index ) then we have wrapped over the end, and nextKey.time < currentTime will loop forever
+
+				}
+
+				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 ];
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/AnimationHandler.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/AnimationHandler.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/AnimationHandler.js	(revision 28897)
@@ -0,0 +1,258 @@
+/**
+ * @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;
+
+}());
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/AnimationMorphTarget.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/AnimationMorphTarget.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/AnimationMorphTarget.js	(revision 28897)
@@ -0,0 +1,218 @@
+/**
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+THREE.AnimationMorphTarget = function( root, data ) {
+
+	this.root = root;
+	this.data = THREE.AnimationHandler.get( data );
+	this.hierarchy = THREE.AnimationHandler.parse( root );
+	this.currentTime = 0;
+	this.timeScale = 1;
+	this.isPlaying = false;
+	this.isPaused = true;
+	this.loop = true;
+	this.influence = 1;
+}
+
+/*
+ * Play
+ */
+
+THREE.AnimationMorphTarget.prototype.play = function( loop, startTimeMS ) {
+
+	if( !this.isPlaying ) {
+
+		this.isPlaying = true;
+		this.loop = loop !== undefined ? loop : true;
+		this.currentTime = startTimeMS !== undefined ? startTimeMS : 0;
+
+
+		// reset key cache
+
+		for ( var h = 0; h < this.hierarchy.length; h++ ) {
+
+			if ( this.hierarchy[ h ].animationCache === undefined ) {
+
+				this.hierarchy[ h ].animationCache = {};
+				this.hierarchy[ h ].animationCache.prevKey = 0;
+				this.hierarchy[ h ].animationCache.nextKey = 0;
+			}
+
+			this.hierarchy[ h ].animationCache.prevKey = this.data.hierarchy[ h ].keys[ 0 ];
+			this.hierarchy[ h ].animationCache.nextKey = this.data.hierarchy[ h ].keys[ 1 ];
+		}
+
+		this.update( 0 );
+	}
+
+	this.isPaused = false;
+	THREE.AnimationHandler.addToUpdate( this );
+}
+
+
+/*
+ * Pause
+ */
+
+THREE.AnimationMorphTarget.prototype.pause = function() {
+
+	if( this.isPaused ) {
+		
+		THREE.AnimationHandler.addToUpdate( this );
+		
+	} else {
+		
+		THREE.AnimationHandler.removeFromUpdate( this );
+		
+	}
+	
+	this.isPaused = !this.isPaused;
+}
+
+
+/*
+ * Stop
+ */
+
+THREE.AnimationMorphTarget.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.hierarchy.length; h++ ) {
+		
+		if ( this.hierarchy[ h ].animationCache !== undefined ) {
+			
+			delete this.hierarchy[ h ].animationCache;	
+		}
+
+	}
+
+}
+
+
+/*
+ * Update
+ */
+
+THREE.AnimationMorphTarget.prototype.update = function( deltaTimeMS ) {
+
+	// early out
+
+	if( !this.isPlaying ) return;
+
+
+	// vars
+
+	var scale;
+	var vector;
+	var prevXYZ, nextXYZ;
+	var prevKey, nextKey;
+	var object;
+	var animationCache;
+	var currentTime, unloopedCurrentTime;
+	
+
+	// update time
+	
+	this.currentTime += deltaTimeMS * this.timeScale;
+
+	unloopedCurrentTime = this.currentTime;
+	currentTime         = this.currentTime = this.currentTime % this.data.length;
+
+
+	// update
+
+	for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+		object = this.hierarchy[ h ];
+		animationCache = object.animationCache;
+
+
+		// get keys
+
+		prevKey = animationCache.prevKey;
+		nextKey = animationCache.nextKey;
+
+
+		// switch keys?
+
+		if ( nextKey.time <= unloopedCurrentTime ) {
+
+			// did we loop?
+
+			if ( currentTime < unloopedCurrentTime ) {
+
+				if ( this.loop ) {
+
+					prevKey = this.data.hierarchy[ h ].keys[ 0 ];
+					nextKey = this.data.hierarchy[ h ].keys[ 1 ];
+
+					while( nextKey.time < currentTime ) {
+
+						prevKey = nextKey;
+						nextKey = this.data.hierarchy[ h ].keys[ nextKey.index + 1 ];
+
+					}
+
+				} else {
+
+					this.stop();
+					return;
+
+				}
+
+			} else {
+
+				do {
+
+					prevKey = nextKey;
+					nextKey = this.data.hierarchy[ h ].keys[ nextKey.index + 1 ];
+
+				} while( nextKey.time < currentTime )
+
+			}
+
+			animationCache.prevKey = prevKey;
+			animationCache.nextKey = nextKey;
+
+		}
+
+
+		// calc scale and check for error
+
+		scale = ( currentTime - prevKey.time ) / ( nextKey.time - prevKey.time );
+
+		if ( scale < 0 || scale > 1 ) {
+
+			console.log( "THREE.AnimationMorphTarget.update: Warning! Scale out of bounds:" + scale ); 
+			scale = scale < 0 ? 0 : 1;
+
+		}
+
+
+		// interpolate
+		
+		var pi, pmti = prevKey.morphTargetsInfluences;
+		var ni, nmti = nextKey.morphTargetsInfluences;
+		var mt, i;
+		
+		for( mt in pmti ) {
+			
+			pi = pmti[ mt ];
+			ni = nmti[ mt ];
+			i = this.root.getMorphTargetIndexByName( mt );
+			
+			this.root.morphTargetInfluences[ i ] = ( pi + ( ni - pi ) * scale ) * this.influence;
+		}
+
+	}
+
+};
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/KeyFrameAnimation.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/KeyFrameAnimation.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/KeyFrameAnimation.js	(revision 28897)
@@ -0,0 +1,422 @@
+/**
+ * @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 ];
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/animation/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/CombinedCamera.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/CombinedCamera.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/CombinedCamera.js	(revision 28897)
@@ -0,0 +1,236 @@
+/**
+ *	@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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/CubeCamera.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/CubeCamera.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/CubeCamera.js	(revision 28897)
@@ -0,0 +1,77 @@
+/**
+ * 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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/cameras/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Curve.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Curve.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Curve.js	(revision 28897)
@@ -0,0 +1,335 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/CurvePath.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/CurvePath.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/CurvePath.js	(revision 28897)
@@ -0,0 +1,326 @@
+/**
+ * @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 <d
+
+};
+
+/*
+THREE.CurvePath.prototype.getTangent = function( t ) {
+};*/
+
+
+// We cannot use the default THREE.Curve getPoint() with getLength() because in
+// THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
+// getPoint() depends on getLength
+
+THREE.CurvePath.prototype.getLength = function() {
+
+	var lens = this.getCurveLengths();
+	return lens[ lens.length - 1 ];
+
+};
+
+// Compute lengths and cache them
+// We cannot overwrite getLengths() because UtoT mapping uses it.
+
+THREE.CurvePath.prototype.getCurveLengths = function() {
+
+	// We use cache values if curves and cache array are same length
+
+	if ( this.cacheLengths && this.cacheLengths.length == this.curves.length ) {
+
+		return this.cacheLengths;
+
+	};
+
+	// Get length of subsurve
+	// Push sums into cached array
+
+	var lengths = [], sums = 0;
+	var i, il = this.curves.length;
+
+	for ( i = 0; i < il; i ++ ) {
+
+		sums += this.curves[ i ].getLength();
+		lengths.push( sums );
+
+	}
+
+	this.cacheLengths = lengths;
+
+	return lengths;
+
+};
+
+
+
+// Returns min and max coordinates, as well as centroid
+
+THREE.CurvePath.prototype.getBoundingBox = function () {
+
+	var points = this.getPoints();
+
+	var maxX, maxY, maxZ;
+	var minX, minY, minZ;
+
+	maxX = maxY = Number.NEGATIVE_INFINITY;
+	minX = minY = Number.POSITIVE_INFINITY;
+
+	var p, i, il, sum;
+
+	var v3 = points[0] instanceof THREE.Vector3;
+
+	sum = v3 ? new THREE.Vector3() : new THREE.Vector2();
+
+	for ( i = 0, il = points.length; i < il; i ++ ) {
+
+		p = points[ i ];
+
+		if ( p.x > 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.getTangent( xNorm );
+		normal.set( -normal.y, normal.x ).multiplyScalar( oldY );
+
+		p.x = pathPt.x + normal.x;
+		p.y = pathPt.y + normal.y;
+
+	}
+
+	return oldPts;
+
+};
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Gyroscope.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Gyroscope.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Gyroscope.js	(revision 28897)
@@ -0,0 +1,60 @@
+/**
+ * @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();
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Path.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Path.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Path.js	(revision 28897)
@@ -0,0 +1,562 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Shape.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Shape.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/Shape.js	(revision 28897)
@@ -0,0 +1,532 @@
+/**
+ * @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<pl; p++) {
+				if (allpts[p].equals(find)) return p;
+			}
+			return -1;
+		};
+
+		// triangulate
+		js.poly2tri.sweep.Triangulate(swctx);
+
+		var triangles =  swctx.GetTriangles();
+		var tr ;
+		var facesPts = [];
+		for (var t in triangles) {
+			tr =  triangles[t];
+			facesPts.push([
+				findIndexForPt(tr.GetPoint(0)),
+				findIndexForPt(tr.GetPoint(1)),
+				findIndexForPt(tr.GetPoint(2))
+					]);
+		}
+
+
+	//	console.log(facesPts);
+	//	console.log("triangles", triangles.length, triangles);
+
+		// Returns array of faces with 3 element each
+	return facesPts;
+	},
+*/
+
+	isClockWise: function ( pts ) {
+
+		return THREE.FontUtils.Triangulate.area( pts ) < 0;
+
+	},
+
+	// Bezier Curves formulas obtained from
+	// http://en.wikipedia.org/wiki/B%C3%A9zier_curve
+
+	// Quad Bezier Functions
+
+	b2p0: function ( t, p ) {
+
+		var k = 1 - t;
+		return k * k * p;
+
+	},
+
+	b2p1: function ( t, p ) {
+
+		return 2 * ( 1 - t ) * t * p;
+
+	},
+
+	b2p2: function ( t, p ) {
+
+		return t * t * p;
+
+	},
+
+	b2: function ( t, p0, p1, p2 ) {
+
+		return this.b2p0( t, p0 ) + this.b2p1( t, p1 ) + this.b2p2( t, p2 );
+
+	},
+
+	// Cubic Bezier Functions
+
+	b3p0: function ( t, p ) {
+
+		var k = 1 - t;
+		return k * k * k * p;
+
+	},
+
+	b3p1: function ( t, p ) {
+
+		var k = 1 - t;
+		return 3 * k * k * t * p;
+
+	},
+
+	b3p2: function ( t, p ) {
+
+		var k = 1 - t;
+		return 3 * k * t * t * p;
+
+	},
+
+	b3p3: function ( t, p ) {
+
+		return t * t * t * p;
+
+	},
+
+	b3: function ( t, p0, p1, p2, p3 ) {
+
+		return this.b3p0( t, p0 ) + this.b3p1( t, p1 ) + this.b3p2( t, p2 ) +  this.b3p3( t, p3 );
+
+	}
+
+};
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/core/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/ArcCurve.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/ArcCurve.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/ArcCurve.js	(revision 28897)
@@ -0,0 +1,10 @@
+/**************************************************************
+ *	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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/ClosedSplineCurve3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/ClosedSplineCurve3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/ClosedSplineCurve3.js	(revision 28897)
@@ -0,0 +1,39 @@
+/**************************************************************
+ *	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;
+
+    }
+
+);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/CubicBezierCurve.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/CubicBezierCurve.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/CubicBezierCurve.js	(revision 28897)
@@ -0,0 +1,39 @@
+/**************************************************************
+ *	Cubic Bezier curve
+ **************************************************************/
+
+THREE.CubicBezierCurve = function ( v0, v1, v2, v3 ) {
+
+	this.v0 = v0;
+	this.v1 = v1;
+	this.v2 = v2;
+	this.v3 = v3;
+
+};
+
+THREE.CubicBezierCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.CubicBezierCurve.prototype.getPoint = function ( t ) {
+
+	var tx, ty;
+
+	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 );
+
+	return new THREE.Vector2( tx, ty );
+
+};
+
+THREE.CubicBezierCurve.prototype.getTangent = function( t ) {
+
+	var tx, ty;
+
+	tx = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.x, this.v1.x, this.v2.x, this.v3.x );
+	ty = THREE.Curve.Utils.tangentCubicBezier( t, this.v0.y, this.v1.y, this.v2.y, this.v3.y );
+
+	var tangent = new THREE.Vector2( tx, ty );
+	tangent.normalize();
+
+	return tangent;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/CubicBezierCurve3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/CubicBezierCurve3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/CubicBezierCurve3.js	(revision 28897)
@@ -0,0 +1,28 @@
+/**************************************************************
+ *	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 );
+
+	}
+
+);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/EllipseCurve.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/EllipseCurve.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/EllipseCurve.js	(revision 28897)
@@ -0,0 +1,45 @@
+/**************************************************************
+ *	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 );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/LineCurve.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/LineCurve.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/LineCurve.js	(revision 28897)
@@ -0,0 +1,37 @@
+/**************************************************************
+ *	Line
+ **************************************************************/
+
+THREE.LineCurve = function ( v1, v2 ) {
+
+	this.v1 = v1;
+	this.v2 = v2;
+
+};
+
+THREE.LineCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.LineCurve.prototype.getPoint = function ( t ) {
+
+	var point = this.v2.clone().sub(this.v1);
+	point.multiplyScalar( t ).add( this.v1 );
+
+	return point;
+
+};
+
+// Line curve is linear, so we can overwrite default getPointAt
+
+THREE.LineCurve.prototype.getPointAt = function ( u ) {
+
+	return this.getPoint( u );
+
+};
+
+THREE.LineCurve.prototype.getTangent = function( t ) {
+
+	var tangent = this.v2.clone().sub(this.v1);
+
+	return tangent.normalize();
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/LineCurve3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/LineCurve3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/LineCurve3.js	(revision 28897)
@@ -0,0 +1,27 @@
+/**************************************************************
+ *	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;
+
+	}
+
+);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/QuadraticBezierCurve.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/QuadraticBezierCurve.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/QuadraticBezierCurve.js	(revision 28897)
@@ -0,0 +1,43 @@
+/**************************************************************
+ *	Quadratic Bezier curve
+ **************************************************************/
+
+
+THREE.QuadraticBezierCurve = function ( v0, v1, v2 ) {
+
+	this.v0 = v0;
+	this.v1 = v1;
+	this.v2 = v2;
+
+};
+
+THREE.QuadraticBezierCurve.prototype = Object.create( THREE.Curve.prototype );
+
+
+THREE.QuadraticBezierCurve.prototype.getPoint = function ( t ) {
+
+	var tx, ty;
+
+	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 );
+
+	return new THREE.Vector2( tx, ty );
+
+};
+
+
+THREE.QuadraticBezierCurve.prototype.getTangent = function( t ) {
+
+	var tx, ty;
+
+	tx = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.x, this.v1.x, this.v2.x );
+	ty = THREE.Curve.Utils.tangentQuadraticBezier( t, this.v0.y, this.v1.y, this.v2.y );
+
+	// returns unit vector
+
+	var tangent = new THREE.Vector2( tx, ty );
+	tangent.normalize();
+
+	return tangent;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/QuadraticBezierCurve3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/QuadraticBezierCurve3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/QuadraticBezierCurve3.js	(revision 28897)
@@ -0,0 +1,27 @@
+/**************************************************************
+ *	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 );
+
+	}
+
+);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/SplineCurve.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/SplineCurve.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/SplineCurve.js	(revision 28897)
@@ -0,0 +1,33 @@
+/**************************************************************
+ *	Spline curve
+ **************************************************************/
+
+THREE.SplineCurve = function ( points /* array of Vector2 */ ) {
+
+	this.points = (points == undefined) ? [] : points;
+
+};
+
+THREE.SplineCurve.prototype = Object.create( THREE.Curve.prototype );
+
+THREE.SplineCurve.prototype.getPoint = function ( t ) {
+
+	var v = new THREE.Vector2();
+	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;
+
+	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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/SplineCurve3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/SplineCurve3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/SplineCurve3.js	(revision 28897)
@@ -0,0 +1,71 @@
+/**************************************************************
+ *	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;
+
+// }
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/curves/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CircleGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CircleGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CircleGeometry.js	(revision 28897)
@@ -0,0 +1,54 @@
+/**
+ * @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.clone(), n.clone(), n.clone() ] ) );
+		this.faceVertexUvs[ 0 ].push( [ uvs[ i ].clone(), uvs[ i + 1 ].clone(), centerUV.clone() ] );
+
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+	this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.CircleGeometry.prototype = Object.create( THREE.Geometry.prototype );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CubeGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CubeGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CubeGeometry.js	(revision 28897)
@@ -0,0 +1,120 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CylinderGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CylinderGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/CylinderGeometry.js	(revision 28897)
@@ -0,0 +1,157 @@
+/**
+ * @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.clone(), n3, n4.clone() ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uv2.clone(), uv3, uv4.clone() ] );
+
+		}
+
+	}
+
+	// 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.x, 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.x, 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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ExtrudeGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ExtrudeGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ExtrudeGeometry.js	(revision 28897)
@@ -0,0 +1,734 @@
+/**
+ * @author zz85 / http://www.lab4games.net/zz85/blog
+ *
+ * Creates extruded geometry from a path shape.
+ *
+ * parameters = {
+ *
+ *  curveSegments: <int>, // number of points on the curves
+ *  steps: <int>, // number of points for z-side extrusions / used for subdividing segements of extrude spline too
+ *  amount: <int>, // Depth to extrude the shape
+ *
+ *  bevelEnabled: <bool>, // turn on bevel
+ *  bevelThickness: <float>, // how deep into the original shape bevel goes
+ *  bevelSize: <float>, // how far from shape outline is bevel
+ *  bevelSegments: <int>, // number of bevel layers
+ *
+ *  extrudePath: <THREE.CurvePath> // 3d spline path to extrude shape along. (creates Frames if .frames aren't defined)
+ *  frames: <THREE.TubeGeometry.FrenetFrames> // containing arrays of tangents, normals, binormals
+ *
+ *  material: <int> // material index for front and back faces
+ *  extrudeMaterial: <int> // material index for extrusion and beveled faces
+ *  uvGenerator: <Object> // 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();
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/IcosahedronGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/IcosahedronGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/IcosahedronGeometry.js	(revision 28897)
@@ -0,0 +1,29 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/LatheGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/LatheGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/LatheGeometry.js	(revision 28897)
@@ -0,0 +1,96 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/OctahedronGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/OctahedronGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/OctahedronGeometry.js	(revision 28897)
@@ -0,0 +1,18 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ParametricGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ParametricGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ParametricGeometry.js	(revision 28897)
@@ -0,0 +1,77 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/PlaneGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/PlaneGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/PlaneGeometry.js	(revision 28897)
@@ -0,0 +1,80 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/PolyhedronGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/PolyhedronGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/PolyhedronGeometry.js	(revision 28897)
@@ -0,0 +1,224 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/RingGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/RingGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/RingGeometry.js	(revision 28897)
@@ -0,0 +1,72 @@
+/**
+ * @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 / outerRadius + 1 ) / 2, ( vertex.y / outerRadius + 1 ) / 2 ) );
+		}
+
+		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.clone(), n.clone(), n.clone() ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]);
+
+			v1 = segment + i;
+			v2 = segment + thetaSegments + 1 + i;
+			v3 = segment + 1 + i;
+
+			this.faces.push( new THREE.Face3( v1, v2, v3, [ n.clone(), n.clone(), n.clone() ] ) );
+			this.faceVertexUvs[ 0 ].push( [ uvs[ v1 ].clone(), uvs[ v2 ].clone(), uvs[ v3 ].clone() ]);
+
+		}
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+	this.boundingSphere = new THREE.Sphere( new THREE.Vector3(), radius );
+
+};
+
+THREE.RingGeometry.prototype = Object.create( THREE.Geometry.prototype );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ShapeGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ShapeGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/ShapeGeometry.js	(revision 28897)
@@ -0,0 +1,136 @@
+/**
+ * @author jonobr1 / http://jonobr1.com
+ *
+ * Creates a one-sided polygonal geometry from a path shape. Similar to
+ * ExtrudeGeometry.
+ *
+ * parameters = {
+ *
+ *	curveSegments: <int>, // number of points on the curves. NOT USED AT THE MOMENT.
+ *
+ *	material: <int> // material index for front and back faces
+ *	uvGenerator: <Object> // 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 ) );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/SphereGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/SphereGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/SphereGeometry.js	(revision 28897)
@@ -0,0 +1,101 @@
+/**
+ * @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 ) {
+
+				uv1.x = ( uv1.x + uv2.x ) / 2;
+				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 ) {
+
+				uv3.x = ( uv3.x + uv4.x ) / 2;
+				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.clone(), n3, n4.clone() ] ) );
+				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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TetrahedronGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TetrahedronGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TetrahedronGeometry.js	(revision 28897)
@@ -0,0 +1,19 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TextGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TextGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TextGeometry.js	(revision 28897)
@@ -0,0 +1,59 @@
+/**
+ * @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: 			<float>, 	// size of the text
+ *  height: 		<float>, 	// thickness to extrude text
+ *  curveSegments: 	<int>,		// number of points on the curves
+ *
+ *  font: 			<string>,		// font name
+ *  weight: 		<string>,		// font weight (normal, bold)
+ *  style: 			<string>,		// font style  (normal, italics)
+ *
+ *  bevelEnabled:	<bool>,			// turn on bevel
+ *  bevelThickness: <float>, 		// how deep into text bevel goes
+ *  bevelSize:		<float>, 		// 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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TorusGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TorusGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TorusGeometry.js	(revision 28897)
@@ -0,0 +1,72 @@
+/**
+ * @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 ].clone(), normals[ b ].clone(), normals[ d ].clone() ] );
+			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 ].clone(), normals[ c ].clone(), normals[ d ].clone() ] );
+			this.faces.push( face );
+			this.faceVertexUvs[ 0 ].push( [ uvs[ b ].clone(), uvs[ c ].clone(), uvs[ d ].clone() ] );
+
+		}
+
+	}
+
+	this.computeCentroids();
+	this.computeFaceNormals();
+
+};
+
+THREE.TorusGeometry.prototype = Object.create( THREE.Geometry.prototype );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TorusKnotGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TorusKnotGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TorusKnotGeometry.js	(revision 28897)
@@ -0,0 +1,103 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TubeGeometry.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TubeGeometry.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/TubeGeometry.js	(revision 28897)
@@ -0,0 +1,273 @@
+/**
+ * @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 ] );
+
+		}
+
+	}
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/geometries/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/ArrowHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/ArrowHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/ArrowHelper.js	(revision 28897)
@@ -0,0 +1,102 @@
+/**
+ * @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
+ *  headLength - Number
+ *  headWidth - Number
+ */
+
+THREE.ArrowHelper = function ( dir, origin, length, hex, headLength, headWidth ) {
+
+	// dir is assumed to be normalized
+
+	THREE.Object3D.call( this );
+
+	if ( hex === undefined ) hex = 0xffff00;
+	if ( length === undefined ) length = 1;
+	if ( headLength === undefined ) headLength = 0.2 * length;
+	if ( headWidth === undefined ) headWidth = 0.2 * headLength;
+
+	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.5, 1, 5, 1 );
+	coneGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, - 0.5, 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, headLength, headWidth );
+
+};
+
+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, headLength, headWidth ) {
+
+	if ( headLength === undefined ) headLength = 0.2 * length;
+	if ( headWidth === undefined ) headWidth = 0.2 * headLength;
+
+	this.line.scale.set( 1, length, 1 );
+	this.line.updateMatrix();
+
+	this.cone.scale.set( headWidth, headLength, headWidth );
+	this.cone.position.y = length;
+	this.cone.updateMatrix();
+
+};
+
+THREE.ArrowHelper.prototype.setColor = function ( hex ) {
+
+	this.line.material.color.setHex( hex );
+	this.cone.material.color.setHex( hex );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/AxisHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/AxisHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/AxisHelper.js	(revision 28897)
@@ -0,0 +1,30 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/BoundingBoxHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/BoundingBoxHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/BoundingBoxHelper.js	(revision 28897)
@@ -0,0 +1,29 @@
+/**
+ * @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 !== undefined ) ? 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 );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/BoxHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/BoxHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/BoxHelper.js	(revision 28897)
@@ -0,0 +1,87 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/CameraHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/CameraHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/CameraHelper.js	(revision 28897)
@@ -0,0 +1,185 @@
+/**
+ * @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;
+
+	};
+
+}();
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/DirectionalLightHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/DirectionalLightHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/DirectionalLightHelper.js	(revision 28897)
@@ -0,0 +1,71 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.DirectionalLightHelper = function ( light, size ) {
+
+	THREE.Object3D.call( this );
+
+	this.light = light;
+	this.light.updateMatrixWorld();
+
+	this.matrixWorld = light.matrixWorld;
+	this.matrixAutoUpdate = false;
+
+	size = size || 1;
+	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() );
+
+	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 v1 = new THREE.Vector3();
+	var v2 = new THREE.Vector3();
+	var v3 = new THREE.Vector3();
+
+	return function () {
+
+		v1.setFromMatrixPosition( this.light.matrixWorld );
+		v2.setFromMatrixPosition( this.light.target.matrixWorld );
+		v3.subVectors( v2, v1 );
+
+		this.lightPlane.lookAt( v3 );
+		this.lightPlane.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+		this.targetLine.geometry.vertices[ 1 ].copy( v3 );
+		this.targetLine.geometry.verticesNeedUpdate = true;
+		this.targetLine.material.color.copy( this.lightPlane.material.color );
+
+	}
+
+}();
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/EdgesHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/EdgesHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/EdgesHelper.js	(revision 28897)
@@ -0,0 +1,84 @@
+/**
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+THREE.EdgesHelper = function ( object, hex ) {
+
+	var color = ( hex !== undefined ) ? hex : 0xffffff;
+
+	var edge = [ 0, 0 ], hash = {};
+	var sortFunction = function ( a, b ) { return a - b };
+
+	var keys = [ 'a', 'b', 'c' ];
+	var geometry = new THREE.BufferGeometry();
+
+	var geometry2 = object.geometry.clone();
+
+	geometry2.mergeVertices();
+	geometry2.computeFaceNormals();
+
+	var vertices = geometry2.vertices;
+	var faces = geometry2.faces;
+	var numEdges = 0;
+
+	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 ) {
+
+				hash[ key ] = { vert1: edge[ 0 ], vert2: edge[ 1 ], face1: i, face2: undefined };
+				numEdges ++;
+
+			} else {
+
+				hash[ key ].face2 = i;
+
+			}
+
+		}
+
+	}
+
+	geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 );
+
+	var coords = geometry.attributes.position.array;
+
+	var index = 0;
+
+	for ( var key in hash ) {
+
+		var h = hash[ key ];
+
+		if ( h.face2 === undefined || faces[ h.face1 ].normal.dot( faces[ h.face2 ].normal ) < 0.9999 ) { // hardwired const OK
+
+			var vertex = vertices[ h.vert1 ];
+			coords[ index ++ ] = vertex.x;
+			coords[ index ++ ] = vertex.y;
+			coords[ index ++ ] = vertex.z;
+
+			vertex = vertices[ h.vert2 ];
+			coords[ index ++ ] = vertex.x;
+			coords[ index ++ ] = vertex.y;
+			coords[ index ++ ] = vertex.z;
+
+		}
+
+	}
+
+	THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color } ), THREE.LinePieces );
+
+	this.matrixAutoUpdate = false;
+	this.matrixWorld = object.matrixWorld;
+
+};
+
+THREE.EdgesHelper.prototype = Object.create( THREE.Line.prototype );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/FaceNormalsHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/FaceNormalsHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/FaceNormalsHelper.js	(revision 28897)
@@ -0,0 +1,76 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.FaceNormalsHelper = function ( object, size, hex, linewidth ) {
+
+	this.object = object;
+
+	this.size = ( size !== undefined ) ? size : 1;
+
+	var color = ( hex !== undefined ) ? hex : 0xffff00;
+
+	var width = ( linewidth !== undefined ) ? 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;
+
+	}
+
+}());
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/GridHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/GridHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/GridHelper.js	(revision 28897)
@@ -0,0 +1,39 @@
+/**
+ * @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;
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/HemisphereLightHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/HemisphereLightHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/HemisphereLightHelper.js	(revision 28897)
@@ -0,0 +1,58 @@
+/**
+ * @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.setFromMatrixPosition( this.light.matrixWorld ).negate() );
+		this.lightSphere.geometry.colorsNeedUpdate = true;
+
+	}
+
+}();
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/PointLightHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/PointLightHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/PointLightHelper.js	(revision 28897)
@@ -0,0 +1,72 @@
+/**
+ * @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 );
+
+	}
+	*/
+
+};
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/SpotLightHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/SpotLightHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/SpotLightHelper.js	(revision 28897)
@@ -0,0 +1,59 @@
+/**
+ * @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.setFromMatrixPosition( this.light.matrixWorld );
+		vector2.setFromMatrixPosition( this.light.target.matrixWorld );
+
+		this.cone.lookAt( vector2.sub( vector ) );
+
+		this.cone.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity );
+
+	};
+
+}();
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/VertexNormalsHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/VertexNormalsHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/VertexNormalsHelper.js	(revision 28897)
@@ -0,0 +1,100 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.VertexNormalsHelper = function ( object, size, hex, linewidth ) {
+
+	this.object = object;
+
+	this.size = ( size !== undefined ) ? size : 1;
+
+	var color = ( hex !== undefined ) ? hex : 0xff0000;
+
+	var width = ( linewidth !== undefined ) ? 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;
+
+	}
+
+}());
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/VertexTangentsHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/VertexTangentsHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/VertexTangentsHelper.js	(revision 28897)
@@ -0,0 +1,96 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author WestLangley / http://github.com/WestLangley
+*/
+
+THREE.VertexTangentsHelper = function ( object, size, hex, linewidth ) {
+
+	this.object = object;
+
+	this.size = ( size !== undefined ) ? size : 1;
+
+	var color = ( hex !== undefined ) ? hex : 0x0000ff;
+
+	var width = ( linewidth !== undefined ) ? 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;
+
+	}
+
+}());
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/WireframeHelper.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/WireframeHelper.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/WireframeHelper.js	(revision 28897)
@@ -0,0 +1,166 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.WireframeHelper = function ( object, hex ) {
+
+	var color = ( hex !== undefined ) ? hex : 0xffffff;
+
+	var edge = [ 0, 0 ], hash = {};
+	var sortFunction = function ( a, b ) { return a - b };
+
+	var keys = [ 'a', 'b', 'c' ];
+	var geometry = new THREE.BufferGeometry();
+
+	if ( object.geometry instanceof THREE.Geometry ) {
+
+		var vertices = object.geometry.vertices;
+		var faces = object.geometry.faces;
+		var numEdges = 0;
+
+		// allocate maximal size
+		var edges = new Uint32Array( 6 * faces.length );
+
+		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 ) {
+
+					edges[ 2 * numEdges ] = edge[ 0 ];
+					edges[ 2 * numEdges + 1 ] = edge[ 1 ];
+					hash[ key ] = true;
+					numEdges ++;
+
+				}
+
+			}
+
+		}
+
+		geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 );
+
+		var coords = geometry.attributes.position.array;
+
+		for ( var i = 0, l = numEdges; i < l; i ++ ) {
+
+			for ( var j = 0; j < 2; j ++ ) {
+
+				var vertex = vertices[ edges [ 2 * i + j] ];
+
+				var index = 6 * i + 3 * j;
+				coords[ index + 0 ] = vertex.x;
+				coords[ index + 1 ] = vertex.y;
+				coords[ index + 2 ] = vertex.z;
+
+			}
+
+		}
+
+	} else if ( object.geometry instanceof THREE.BufferGeometry && object.geometry.attributes.index !== undefined ) { // indexed BufferGeometry
+
+		var vertices = object.geometry.attributes.position.array;
+		var indices = object.geometry.attributes.index.array;
+		var offsets = object.geometry.offsets;
+		var numEdges = 0;
+
+		// allocate maximal size
+		var edges = new Uint32Array( 2 * indices.length );
+
+		for ( var o = 0, ol = offsets.length; o < ol; ++ o ) {
+
+			var start = offsets[ o ].start;
+			var count = offsets[ o ].count;
+			var index = offsets[ o ].index;
+
+			for ( var i = start, il = start + count; i < il; i += 3 ) {
+
+				for ( var j = 0; j < 3; j ++ ) {
+
+					edge[ 0 ] = index + indices[ i + j ];
+					edge[ 1 ] = index + indices[ i + ( j + 1 ) % 3 ];
+					edge.sort( sortFunction );
+
+					var key = edge.toString();
+
+					if ( hash[ key ] === undefined ) {
+
+						edges[ 2 * numEdges ] = edge[ 0 ];
+						edges[ 2 * numEdges + 1 ] = edge[ 1 ];
+						hash[ key ] = true;
+						numEdges ++;
+
+					}
+
+				}
+
+			}
+
+		}
+
+		geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 );
+
+		var coords = geometry.attributes.position.array;
+
+		for ( var i = 0, l = numEdges; i < l; i ++ ) {
+
+			for ( var j = 0; j < 2; j ++ ) {
+
+				var index = 6 * i + 3 * j;
+				var index2 = 3 * edges[ 2 * i + j];
+				coords[ index + 0 ] = vertices[ index2 ];
+				coords[ index + 1 ] = vertices[ index2 + 1 ];
+				coords[ index + 2 ] = vertices[ index2 + 2 ];
+
+			}
+
+		}
+
+	} else if ( object.geometry instanceof THREE.BufferGeometry	) { // non-indexed BufferGeometry
+
+		var vertices = object.geometry.attributes.position.array;
+		var numEdges = vertices.length / 3;
+		var numTris = numEdges / 3;
+
+		geometry.addAttribute( 'position', Float32Array, 2 * numEdges, 3 );
+
+		var coords = geometry.attributes.position.array;
+
+		for ( var i = 0, l = numTris; i < l; i ++ ) {
+
+			for ( var j = 0; j < 3; j ++ ) {
+
+				var index = 18 * i + 6 * j;
+
+				var index1 = 9 * i + 3 * j;
+				coords[ index + 0 ] = vertices[ index1 ];
+				coords[ index + 1 ] = vertices[ index1 + 1 ];
+				coords[ index + 2 ] = vertices[ index1 + 2 ];
+
+				var index2 = 9 * i + 3 * ( ( j + 1 ) % 3 );
+				coords[ index + 3 ] = vertices[ index2 ];
+				coords[ index + 4 ] = vertices[ index2 + 1 ];
+				coords[ index + 5 ] = vertices[ index2 + 2 ];
+
+			}
+
+		}
+
+	}
+
+	THREE.Line.call( this, geometry, new THREE.LineBasicMaterial( { color: color } ), THREE.LinePieces );
+
+	this.matrixAutoUpdate = false;
+	this.matrixWorld = object.matrixWorld;
+
+};
+
+THREE.WireframeHelper.prototype = Object.create( THREE.Line.prototype );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/helpers/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/ImmediateRenderObject.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/ImmediateRenderObject.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/ImmediateRenderObject.js	(revision 28897)
@@ -0,0 +1,13 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.ImmediateRenderObject = function () {
+
+	THREE.Object3D.call( this );
+
+	this.render = function ( renderCallback ) { };
+
+};
+
+THREE.ImmediateRenderObject.prototype = Object.create( THREE.Object3D.prototype );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/LensFlare.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/LensFlare.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/LensFlare.js	(revision 28897)
@@ -0,0 +1,89 @@
+/**
+ * @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;
+
+	}
+
+};
+
+
+
+
+
+
+
+
+
+
+
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/MorphBlendMesh.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/MorphBlendMesh.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/MorphBlendMesh.js	(revision 28897)
@@ -0,0 +1,307 @@
+/**
+ * @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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/objects/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/DepthPassPlugin.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/DepthPassPlugin.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/DepthPassPlugin.js	(revision 28897)
@@ -0,0 +1,200 @@
+/**
+ * @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;
+
+	};
+
+};
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/LensFlarePlugin.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/LensFlarePlugin.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/LensFlarePlugin.js	(revision 28897)
@@ -0,0 +1,303 @@
+/**
+ * @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;
+
+	};
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/ShadowMapPlugin.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/ShadowMapPlugin.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/ShadowMapPlugin.js	(revision 28897)
@@ -0,0 +1,496 @@
+/**
+ * @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.setFromMatrixPosition( light.matrixWorld );
+			_matrixPosition.setFromMatrixPosition( 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();
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/SpritePlugin.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/SpritePlugin.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/SpritePlugin.js	(revision 28897)
@@ -0,0 +1,360 @@
+/**
+ * @author mikael emtinger / http://gomo.se/
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.SpritePlugin = function () {
+
+	var _gl, _renderer, _texture;
+
+	var vertices, faces, vertexBuffer, elementBuffer;
+	var program, attributes, uniforms;
+
+	this.init = function ( renderer ) {
+
+		_gl = renderer.context;
+		_renderer = renderer;
+
+		vertices = new Float32Array( [
+			- 0.5, - 0.5, 0, 0, 
+			  0.5, - 0.5, 1, 0,
+			  0.5,   0.5, 1, 1,
+			- 0.5,   0.5, 0, 1
+		] );
+
+		faces = new Uint16Array( [
+			0, 1, 2,
+			0, 2, 3
+		] );
+
+		vertexBuffer  = _gl.createBuffer();
+		elementBuffer = _gl.createBuffer();
+
+		_gl.bindBuffer( _gl.ARRAY_BUFFER, vertexBuffer );
+		_gl.bufferData( _gl.ARRAY_BUFFER, vertices, _gl.STATIC_DRAW );
+
+		_gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, elementBuffer );
+		_gl.bufferData( _gl.ELEMENT_ARRAY_BUFFER, faces, _gl.STATIC_DRAW );
+
+		program = createProgram();
+
+		attributes = {
+			position:			_gl.getAttribLocation ( program, 'position' ),
+			uv:					_gl.getAttribLocation ( program, 'uv' )
+		};
+
+		uniforms = {
+			uvOffset:			_gl.getUniformLocation( program, 'uvOffset' ),
+			uvScale:			_gl.getUniformLocation( program, 'uvScale' ),
+
+			rotation:			_gl.getUniformLocation( program, 'rotation' ),
+			scale:				_gl.getUniformLocation( program, 'scale' ),
+
+			color:				_gl.getUniformLocation( program, 'color' ),
+			map:				_gl.getUniformLocation( program, 'map' ),
+			opacity:			_gl.getUniformLocation( program, 'opacity' ),
+
+			modelViewMatrix: 	_gl.getUniformLocation( program, 'modelViewMatrix' ),
+			projectionMatrix:	_gl.getUniformLocation( program, 'projectionMatrix' ),
+
+			fogType:			_gl.getUniformLocation( program, 'fogType' ),
+			fogDensity:			_gl.getUniformLocation( program, 'fogDensity' ),
+			fogNear:			_gl.getUniformLocation( program, 'fogNear' ),
+			fogFar:				_gl.getUniformLocation( program, 'fogFar' ),
+			fogColor:			_gl.getUniformLocation( program, 'fogColor' ),
+
+			alphaTest:			_gl.getUniformLocation( program, 'alphaTest' )
+		};
+
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = 8;
+		canvas.height = 8;
+
+		var context = canvas.getContext( '2d' );
+		context.fillStyle = '#ffffff';
+		context.fillRect( 0, 0, canvas.width, canvas.height );
+
+		_texture = new THREE.Texture( canvas );
+		_texture.needsUpdate = true;
+
+	};
+
+	this.render = function ( scene, camera, viewportWidth, viewportHeight ) {
+
+		var sprites = scene.__webglSprites,
+			nSprites = sprites.length;
+
+		if ( ! nSprites ) return;
+
+		// setup gl
+
+		_gl.useProgram( program );
+
+		_gl.enableVertexAttribArray( attributes.position );
+		_gl.enableVertexAttribArray( attributes.uv );
+
+		_gl.disable( _gl.CULL_FACE );
+		_gl.enable( _gl.BLEND );
+
+		_gl.bindBuffer( _gl.ARRAY_BUFFER, 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, 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, fogType, scale = [];
+
+		for( i = 0; i < nSprites; i ++ ) {
+
+			sprite = sprites[ i ];
+			material = sprite.material;
+
+			if ( sprite.visible === false ) continue;
+
+			sprite._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, sprite.matrixWorld );
+			sprite.z = - sprite._modelViewMatrix.elements[ 14 ];
+
+		}
+
+		sprites.sort( painterSortStable );
+
+		// render all sprites
+
+		for( i = 0; i < nSprites; i ++ ) {
+
+			sprite = sprites[ i ];
+
+			if ( sprite.visible === false ) continue;
+
+			material = sprite.material;
+
+			_gl.uniform1f( uniforms.alphaTest, material.alphaTest );
+			_gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, sprite._modelViewMatrix.elements );
+
+			scale[ 0 ] = sprite.scale.x;
+			scale[ 1 ] = sprite.scale.y;
+
+			if ( scene.fog && material.fog ) {
+
+				fogType = sceneFogType;
+
+			} else {
+
+				fogType = 0;
+
+			}
+
+			if ( oldFogType !== fogType ) {
+
+				_gl.uniform1i( uniforms.fogType, fogType );
+				oldFogType = fogType;
+
+			}
+
+			if ( material.map !== null ) {
+
+				_gl.uniform2f( uniforms.uvOffset, material.map.offset.x, material.map.offset.y );
+				_gl.uniform2f( uniforms.uvScale, material.map.repeat.x, material.map.repeat.y );
+
+			} else {
+
+				_gl.uniform2f( uniforms.uvOffset, 0, 0 );
+				_gl.uniform2f( uniforms.uvScale, 1, 1 );
+
+			}
+
+			_gl.uniform1f( uniforms.opacity, material.opacity );
+			_gl.uniform3f( uniforms.color, material.color.r, material.color.g, material.color.b );
+
+			_gl.uniform1f( uniforms.rotation, material.rotation );
+			_gl.uniform2fv( uniforms.scale, scale );
+
+			_renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst );
+			_renderer.setDepthTest( material.depthTest );
+			_renderer.setDepthWrite( material.depthWrite );
+
+			if ( material.map && material.map.image && material.map.image.width ) {
+
+				_renderer.setTexture( material.map, 0 );
+
+			} else {
+
+				_renderer.setTexture( _texture, 0 );
+
+			}
+
+			_gl.drawElements( _gl.TRIANGLES, 6, _gl.UNSIGNED_SHORT, 0 );
+
+		}
+
+		// restore gl
+
+		_gl.enable( _gl.CULL_FACE );
+
+	};
+
+	function createProgram () {
+
+		var program = _gl.createProgram();
+
+		var vertexShader = _gl.createShader( _gl.VERTEX_SHADER );
+		var fragmentShader = _gl.createShader( _gl.FRAGMENT_SHADER );
+
+		_gl.shaderSource( vertexShader, [
+
+			'precision ' + _renderer.getPrecision() + ' float;',
+
+			'uniform mat4 modelViewMatrix;',
+			'uniform mat4 projectionMatrix;',
+			'uniform float rotation;',
+			'uniform vec2 scale;',
+			'uniform vec2 uvOffset;',
+			'uniform vec2 uvScale;',
+
+			'attribute vec2 position;',
+			'attribute vec2 uv;',
+
+			'varying vec2 vUV;',
+
+			'void main() {',
+
+				'vUV = uvOffset + uv * uvScale;',
+
+				'vec2 alignedPosition = position * scale;',
+
+				'vec2 rotatedPosition;',
+				'rotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;',
+				'rotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;',
+
+				'vec4 finalPosition;',
+
+				'finalPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );',
+				'finalPosition.xy += rotatedPosition;',
+				'finalPosition = projectionMatrix * finalPosition;',
+
+				'gl_Position = finalPosition;',
+
+			'}'
+
+		].join( '\n' ) );
+
+		_gl.shaderSource( fragmentShader, [
+
+			'precision ' + _renderer.getPrecision() + ' float;',
+
+			'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' ) );
+
+		_gl.compileShader( vertexShader );
+		_gl.compileShader( fragmentShader );
+
+		_gl.attachShader( program, vertexShader );
+		_gl.attachShader( program, fragmentShader );
+
+		_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;
+
+		}
+
+	};
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/renderers/plugins/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/shaders/ShaderFlares.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/shaders/ShaderFlares.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/shaders/ShaderFlares.js	(revision 28897)
@@ -0,0 +1,185 @@
+/**
+ * @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" )
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/shaders/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/shaders/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/extras/shaders/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/AmbientLight.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/AmbientLight.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/AmbientLight.js	(revision 28897)
@@ -0,0 +1,21 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/AreaLight.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/AreaLight.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/AreaLight.js	(revision 28897)
@@ -0,0 +1,25 @@
+/**
+ * @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 );
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/DirectionalLight.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/DirectionalLight.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/DirectionalLight.js	(revision 28897)
@@ -0,0 +1,78 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/HemisphereLight.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/HemisphereLight.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/HemisphereLight.js	(revision 28897)
@@ -0,0 +1,29 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/Light.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/Light.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/Light.js	(revision 28897)
@@ -0,0 +1,26 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/PointLight.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/PointLight.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/PointLight.js	(revision 28897)
@@ -0,0 +1,27 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/SpotLight.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/SpotLight.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/SpotLight.js	(revision 28897)
@@ -0,0 +1,63 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/lights/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/BufferGeometryLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/BufferGeometryLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/BufferGeometryLoader.js	(revision 28897)
@@ -0,0 +1,73 @@
+/**
+ * @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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/GeometryLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/GeometryLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/GeometryLoader.js	(revision 28897)
@@ -0,0 +1,41 @@
+/**
+ * @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 ) {
+
+		
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/ImageLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/ImageLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/ImageLoader.js	(revision 28897)
@@ -0,0 +1,67 @@
+/**
+ * @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;
+
+	}
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/JSONLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/JSONLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/JSONLoader.js	(revision 28897)
@@ -0,0 +1,527 @@
+/**
+ * @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;
+		// 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 ) {
+
+		return { geometry: geometry };
+
+	} else {
+
+		var materials = this.initMaterials( json.materials, texturePath );
+
+		if ( this.needsTangents( materials ) ) {
+
+			geometry.computeTangents();
+
+		}
+
+		return { geometry: geometry, materials: materials };
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/Loader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/Loader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/Loader.js	(revision 28897)
@@ -0,0 +1,441 @@
+/**
+ * @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: undefined,
+
+	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;
+
+			};
+
+			if ( _this.crossOrigin !== undefined ) 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[ "diffuse" ].value.setHex( mpars.color );
+			uniforms[ "specular" ].value.setHex( mpars.specular );
+			uniforms[ "ambient" ].value.setHex( mpars.ambient );
+
+			uniforms[ "shininess" ].value = mpars.shininess;
+
+			if ( mpars.opacity !== undefined ) {
+
+				uniforms[ "opacity" ].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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/LoadingManager.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/LoadingManager.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/LoadingManager.js	(revision 28897)
@@ -0,0 +1,41 @@
+/**
+ * @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();
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/MaterialLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/MaterialLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/MaterialLoader.js	(revision 28897)
@@ -0,0 +1,65 @@
+/**
+ * @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.side !== undefined ) material.side = json.side;
+		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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/ObjectLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/ObjectLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/ObjectLoader.js	(revision 28897)
@@ -0,0 +1,345 @@
+/**
+ * @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.radialSegments,
+							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;
+
+				case 'Sprite':
+
+					var material = materials[ data.material ];
+
+					if ( material === undefined ) {
+
+						console.error( 'THREE.ObjectLoader: Undefined material ' + data.material );
+
+					}
+
+					object = new THREE.Sprite( 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;
+
+		}
+
+	}()
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/SceneLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/SceneLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/SceneLoader.js	(revision 28897)
@@ -0,0 +1,1238 @@
+/**
+ * @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 === "AmbientLight" || objJSON.type === "PointLight" ||
+						objJSON.type === "DirectionalLight" || objJSON.type === "SpotLight" ||
+						objJSON.type === "HemisphereLight" || objJSON.type === "AreaLight" ) {
+
+						var color = objJSON.color;
+						var intensity = objJSON.intensity;
+						var distance = objJSON.distance;
+						var position = objJSON.position;
+						var rotation = objJSON.rotation;
+
+						switch ( objJSON.type ) {
+
+							case 'AmbientLight':
+								light = new THREE.AmbientLight( color );
+								break;
+
+							case 'PointLight':
+								light = new THREE.PointLight( color, intensity, distance );
+								light.position.fromArray( position );
+								break;
+
+							case 'DirectionalLight':
+								light = new THREE.DirectionalLight( color, intensity );
+								light.position.fromArray( objJSON.direction );
+								break;
+
+							case 'SpotLight':
+								light = new THREE.SpotLight( color, intensity, distance, 1 );
+								light.angle = objJSON.angle;
+								light.position.fromArray( position );
+								light.target.set( position[ 0 ], position[ 1 ] - distance, position[ 2 ] );
+								light.target.applyEuler( new THREE.Euler( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ], 'XYZ' ) );
+								break;
+
+							case 'HemisphereLight':
+								light = new THREE.DirectionalLight( color, intensity, distance );
+								light.target.set( position[ 0 ], position[ 1 ] - distance, position[ 2 ] );
+								light.target.applyEuler( new THREE.Euler( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ], 'XYZ' ) );
+								break;
+
+							case 'AreaLight':
+								light = new THREE.AreaLight(color, intensity);
+								light.position.fromArray( position );
+								light.width = objJSON.size;
+								light.height = objJSON.size_y;
+								break;
+
+						}
+
+						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[ "reflectivity" ].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[ "diffuse" ].value.setHex( diffuse );
+				uniforms[ "specular" ].value.setHex( specular );
+				uniforms[ "ambient" ].value.setHex( ambient );
+
+				uniforms[ "shininess" ].value = shininess;
+
+				if ( matJSON.parameters.opacity ) {
+
+					uniforms[ "opacity" ].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();
+
+	}
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/TextureLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/TextureLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/TextureLoader.js	(revision 28897)
@@ -0,0 +1,42 @@
+/**
+ * @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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/XHRLoader.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/XHRLoader.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/XHRLoader.js	(revision 28897)
@@ -0,0 +1,66 @@
+/**
+ * @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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/loaders/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/LineBasicMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/LineBasicMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/LineBasicMaterial.js	(revision 28897)
@@ -0,0 +1,61 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  linewidth: <float>,
+ *  linecap: "round",
+ *  linejoin: "round",
+ *
+ *  vertexColors: <bool>
+ *
+ *  fog: <bool>
+ * }
+ */
+
+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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/LineDashedMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/LineDashedMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/LineDashedMaterial.js	(revision 28897)
@@ -0,0 +1,66 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  linewidth: <float>,
+ *
+ *  scale: <float>,
+ *  dashSize: <float>,
+ *  gapSize: <float>,
+ *
+ *  vertexColors: <bool>
+ *
+ *  fog: <bool>
+ * }
+ */
+
+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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/Material.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/Material.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/Material.js	(revision 28897)
@@ -0,0 +1,133 @@
+/**
+ * @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;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshBasicMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshBasicMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshBasicMaterial.js	(revision 28897)
@@ -0,0 +1,108 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *
+ *  fog: <bool>
+ * }
+ */
+
+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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshDepthMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshDepthMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshDepthMaterial.js	(revision 28897)
@@ -0,0 +1,41 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  opacity: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>
+ * }
+ */
+
+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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshFaceMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshFaceMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshFaceMaterial.js	(revision 28897)
@@ -0,0 +1,23 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshLambertMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshLambertMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshLambertMaterial.js	(revision 28897)
@@ -0,0 +1,124 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  ambient: <hex>,
+ *  emissive: <hex>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshNormalMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshNormalMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshNormalMaterial.js	(revision 28897)
@@ -0,0 +1,47 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ *  opacity: <float>,
+ *
+ *  shading: THREE.FlatShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>
+ * }
+ */
+
+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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshPhongMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshPhongMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/MeshPhongMaterial.js	(revision 28897)
@@ -0,0 +1,154 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  ambient: <hex>,
+ *  emissive: <hex>,
+ *  specular: <hex>,
+ *  shininess: <float>,
+ *  opacity: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  lightMap: new THREE.Texture( <Image> ),
+ *
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
+ *
+ *  normalMap: new THREE.Texture( <Image> ),
+ *  normalScale: <Vector2>,
+ *
+ *  specularMap: new THREE.Texture( <Image> ),
+ *
+ *  envMap: new THREE.TextureCube( [posx, negx, posy, negy, posz, negz] ),
+ *  combine: THREE.Multiply,
+ *  reflectivity: <float>,
+ *  refractionRatio: <float>,
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/ParticleSystemMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/ParticleSystemMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/ParticleSystemMaterial.js	(revision 28897)
@@ -0,0 +1,66 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  size: <float>,
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  vertexColors: <bool>,
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.ParticleSystemMaterial = 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.ParticleSystemMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.ParticleSystemMaterial.prototype.clone = function () {
+
+	var material = new THREE.ParticleSystemMaterial();
+
+	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;
+
+};
+
+// backwards compatibility
+
+THREE.ParticleBasicMaterial = THREE.ParticleSystemMaterial;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/ShaderMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/ShaderMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/ShaderMaterial.js	(revision 28897)
@@ -0,0 +1,110 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  fragmentShader: <string>,
+ *  vertexShader: <string>,
+ *
+ *  uniforms: { "parameter1": { type: "f", value: 1.0 }, "parameter2": { type: "i" value2: 2 } },
+ *
+ *  defines: { "label" : "value" },
+ *
+ *  shading: THREE.SmoothShading,
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *  wireframe: <boolean>,
+ *  wireframeLinewidth: <float>,
+ *
+ *  lights: <bool>,
+ *
+ *  vertexColors: THREE.NoColors / THREE.VertexColors / THREE.FaceColors,
+ *
+ *  skinning: <bool>,
+ *  morphTargets: <bool>,
+ *  morphNormals: <bool>,
+ *
+ *	fog: <bool>
+ * }
+ */
+
+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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/SpriteCanvasMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/SpriteCanvasMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/SpriteCanvasMaterial.js	(revision 28897)
@@ -0,0 +1,40 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  program: <function>,
+ *  opacity: <float>,
+ *  blending: THREE.NormalBlending
+ * }
+ */
+
+THREE.SpriteCanvasMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	this.color = new THREE.Color( 0xffffff );
+	this.program = function ( context, color ) {};
+
+	this.setValues( parameters );
+
+};
+
+THREE.SpriteCanvasMaterial.prototype = Object.create( THREE.Material.prototype );
+
+THREE.SpriteCanvasMaterial.prototype.clone = function () {
+
+	var material = new THREE.SpriteCanvasMaterial();
+
+	THREE.Material.prototype.clone.call( this, material );
+
+	material.color.copy( this.color );
+	material.program = this.program;
+
+	return material;
+
+};
+
+// backwards compatibility
+
+THREE.ParticleCanvasMaterial = THREE.SpriteCanvasMaterial;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/SpriteMaterial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/SpriteMaterial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/SpriteMaterial.js	(revision 28897)
@@ -0,0 +1,56 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ *
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  blending: THREE.NormalBlending,
+ *  depthTest: <bool>,
+ *  depthWrite: <bool>,
+ *
+ *	uvOffset: new THREE.Vector2(),
+ *	uvScale: new THREE.Vector2(),
+ *
+ *  fog: <bool>
+ * }
+ */
+
+THREE.SpriteMaterial = function ( parameters ) {
+
+	THREE.Material.call( this );
+
+	// defaults
+
+	this.color = new THREE.Color( 0xffffff );
+	this.map = null;
+
+	this.rotation = 0;
+
+	this.fog = false;
+
+	// set parameters
+
+	this.setValues( parameters );
+
+};
+
+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.rotation = this.rotation;
+
+	material.fog = this.fog;
+
+	return material;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/materials/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Box2.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Box2.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Box2.js	(revision 28897)
@@ -0,0 +1,264 @@
+/**
+ * @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, optionalTarget ) {
+
+		// This can potentially have a divide by zero if the box
+		// has a size dimension of 0.
+
+		var result = optionalTarget || new THREE.Vector2();
+
+		return result.set(
+			( 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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Box3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Box3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Box3.js	(revision 28897)
@@ -0,0 +1,379 @@
+/**
+ * @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, optionalTarget ) {
+
+		// This can potentially have a divide by zero if the box
+		// has a size dimension of 0.
+
+		var result = optionalTarget || new THREE.Vector3();
+
+		return result.set(
+			( 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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Color.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Color.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Color.js	(revision 28897)
@@ -0,0 +1,402 @@
+/**
+ * @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 ( optionalTarget ) {
+
+		// h,s,l ranges are in 0.0 - 1.0
+
+		var hsl = optionalTarget || { h: 0, s: 0, l: 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 };
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Euler.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Euler.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Euler.js	(revision 28897)
@@ -0,0 +1,363 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Frustum.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Frustum.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Frustum.js	(revision 28897)
@@ -0,0 +1,178 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Line3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Line3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Line3.js	(revision 28897)
@@ -0,0 +1,126 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Math.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Math.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Math.js	(revision 28897)
@@ -0,0 +1,157 @@
+/**
+ * @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 <a, b>
+
+	clamp: function ( x, a, b ) {
+
+		return ( x < a ) ? a : ( ( x > b ) ? b : x );
+
+	},
+
+	// Clamp value to range <a, inf)
+
+	clampBottom: function ( x, a ) {
+
+		return x < a ? a : x;
+
+	},
+
+	// Linear mapping from range <a1, a2> to range <b1, b2>
+
+	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 <low, high> interval
+
+	randInt: function ( low, high ) {
+
+		return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+	},
+
+	// Random float from <low, high> 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;
+
+		};
+
+	}()
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Matrix3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Matrix3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Matrix3.js	(revision 28897)
@@ -0,0 +1,245 @@
+/**
+ * @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;
+
+	},
+
+	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 ]
+		];
+
+	},
+
+	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]
+
+		);
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Matrix4.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Matrix4.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Matrix4.js	(revision 28897)
@@ -0,0 +1,971 @@
+/**
+ * @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.setFromMatrixPosition( 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();
+
+			// if determine is negative, we need to invert one scale
+			var det = this.determinant();
+			if( det < 0 ) {
+				sx = -sx;
+			}
+
+			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]
+
+		);
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Plane.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Plane.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Plane.js	(revision 28897)
@@ -0,0 +1,223 @@
+/**
+ * @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();
+		var m1 = new THREE.Matrix3();
+
+		return function ( matrix, optionalNormalMatrix ) {
+
+			// compute new normal based on theory here:
+			// http://www.songho.ca/opengl/gl_normaltransform.html
+			var normalMatrix = optionalNormalMatrix || m1.getNormalMatrix( matrix );
+			var newNormal = v1.copy( this.normal ).applyMatrix3( normalMatrix );
+			
+			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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Quaternion.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Quaternion.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Quaternion.js	(revision 28897)
@@ -0,0 +1,453 @@
+/**
+ * @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 );
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Ray.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Ray.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Ray.js	(revision 28897)
@@ -0,0 +1,470 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Sphere.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Sphere.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Sphere.js	(revision 28897)
@@ -0,0 +1,153 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Spline.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Spline.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Spline.js	(revision 28897)
@@ -0,0 +1,176 @@
+/**
+ * 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;
+
+	};
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Triangle.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Triangle.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Triangle.js	(revision 28897)
@@ -0,0 +1,190 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/UV.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/UV.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/UV.js	(revision 28897)
@@ -0,0 +1,10 @@
+/**
+ * @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 );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector2.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector2.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector2.js	(revision 28897)
@@ -0,0 +1,322 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector3.js	(revision 28897)
@@ -0,0 +1,704 @@
+/**
+ * @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 ) {
+
+		console.warn( "DEPRECATED: Vector3\'s .getPositionFromMatrix() has been renamed to .setFromMatrixPosition(). Please update your code." );
+
+		return this.setFromMatrixPosition( m );
+
+	},
+
+	getScaleFromMatrix: function ( m ) {
+
+		console.warn( "DEPRECATED: Vector3\'s .getScaleFromMatrix() has been renamed to .setFromMatrixScale(). Please update your code." );
+
+		return this.setFromMatrixScale( m );
+	},
+
+	getColumnFromMatrix: function ( index, matrix ) {
+
+		console.warn( "DEPRECATED: Vector3\'s .getColumnFromMatrix() has been renamed to .setFromMatrixColumn(). Please update your code." );
+
+		return this.setFromMatrixColumn( index, matrix );
+
+	},
+
+	setFromMatrixPosition: function ( m ) {
+
+		this.x = m.elements[ 12 ];
+		this.y = m.elements[ 13 ];
+		this.z = m.elements[ 14 ];
+
+		return this;
+
+	},
+
+	setFromMatrixScale: 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;
+	},
+
+	setFromMatrixColumn: 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 );
+
+		}
+
+	}()
+
+} );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector4.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector4.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vector4.js	(revision 28897)
@@ -0,0 +1,576 @@
+/**
+ * @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 );
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vertex.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vertex.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/Vertex.js	(revision 28897)
@@ -0,0 +1,10 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.Vertex = function ( v ) {
+
+	console.warn( 'THREE.Vertex has been DEPRECATED. Use THREE.Vector3 instead.')
+	return v;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/math/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Bone.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Bone.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Bone.js	(revision 28897)
@@ -0,0 +1,57 @@
+/**
+ * @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 );
+
+	}
+
+};
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/LOD.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/LOD.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/LOD.js	(revision 28897)
@@ -0,0 +1,102 @@
+/**
+ * @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.setFromMatrixPosition( camera.matrixWorld );
+			v2.setFromMatrixPosition( 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
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Line.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Line.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Line.js	(revision 28897)
@@ -0,0 +1,29 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Mesh.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Mesh.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Mesh.js	(revision 28897)
@@ -0,0 +1,63 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/MorphAnimMesh.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/MorphAnimMesh.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/MorphAnimMesh.js	(revision 28897)
@@ -0,0 +1,196 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/ParticleSystem.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/ParticleSystem.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/ParticleSystem.js	(revision 28897)
@@ -0,0 +1,29 @@
+/**
+ * @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.ParticleSystemMaterial( { 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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/SkinnedMesh.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/SkinnedMesh.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/SkinnedMesh.js	(revision 28897)
@@ -0,0 +1,269 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Sprite.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Sprite.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/Sprite.js	(revision 28897)
@@ -0,0 +1,40 @@
+/**
+ * @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();
+
+};
+
+THREE.Sprite.prototype = Object.create( THREE.Object3D.prototype );
+
+/*
+ * Custom update matrix
+ */
+
+THREE.Sprite.prototype.updateMatrix = function () {
+
+	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;
+
+};
+
+// Backwards compatibility
+
+THREE.Particle = THREE.Sprite;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/objects/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/CanvasRenderer.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/CanvasRenderer.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/CanvasRenderer.js	(revision 28897)
@@ -0,0 +1,1133 @@
+/**
+ * @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 = _canvas.width,
+	_canvasHeight = _canvas.height,
+	_canvasWidthHalf = Math.floor( _canvasWidth / 2 ),
+	_canvasHeightHalf = Math.floor( _canvasHeight / 2 ),
+	
+	_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 = {},
+
+	_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();
+
+		}
+
+	};
+
+	// compatibility
+
+	this.clearColor = function () {};
+	this.clearDepth = function () {};
+	this.clearStencil = function () {};
+
+	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.RenderableSprite ) {
+
+				_v1 = element;
+				_v1.x *= _canvasWidthHalf; _v1.y *= _canvasHeightHalf;
+
+				renderSprite( _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 sprites
+
+				_directionalLights.add( lightColor );
+
+			} else if ( light instanceof THREE.PointLight ) {
+
+				// for sprites
+
+				_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.setFromMatrixPosition( 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.setFromMatrixPosition( 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 renderSprite( v1, element, material ) {
+
+		setOpacity( material.opacity );
+		setBlending( material.blending );
+
+		var scaleX = element.scale.x * _canvasWidthHalf;
+		var scaleY = element.scale.y * _canvasHeightHalf;
+
+		var dist = 0.5 * Math.sqrt( scaleX * scaleX + scaleY * scaleY ); // allow for rotated sprite
+		_elemBox.min.set( v1.x - dist, v1.y - dist );
+		_elemBox.max.set( v1.x + dist, v1.y + dist );
+
+		if ( _clipBox.isIntersectionBox( _elemBox ) === false ) {
+
+			_elemBox.makeEmpty();
+			return;
+
+		}
+
+		if ( material instanceof THREE.SpriteMaterial ||
+			 material instanceof THREE.ParticleSystemMaterial ) { // Backwards compatibility
+
+			var texture = material.map;
+
+			if ( texture !== null ) {
+
+				if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) {
+
+					if ( texture.image !== undefined && texture.image.width > 0 ) {
+
+						textureToPattern( texture );
+
+					}
+
+					texture.addEventListener( 'update', onTextureUpdate );
+
+				}
+
+				var pattern = _patterns[ texture.id ];
+
+				if ( pattern !== undefined ) {
+
+					setFillStyle( pattern );
+
+				} else {
+
+					setFillStyle( 'rgba( 0, 0, 0, 1 )' );
+
+				}
+
+				//
+
+				var bitmap = texture.image;
+
+				var ox = bitmap.width * texture.offset.x;
+				var oy = bitmap.height * texture.offset.y;
+
+				var sx = bitmap.width * texture.repeat.x;
+				var sy = bitmap.height * texture.repeat.y;
+
+				var cx = scaleX / sx;
+				var cy = scaleY / sy;
+
+				_context.save();
+				_context.translate( v1.x, v1.y );
+				if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+				_context.translate( - scaleX / 2, - scaleY / 2 );
+				_context.scale( cx, cy );
+				_context.translate( - ox, - oy );
+				_context.fillRect( ox, oy, sx, sy );
+				_context.restore();
+
+			} else { // no texture
+
+				setFillStyle( material.color.getStyle() );
+
+				_context.save();
+				_context.translate( v1.x, v1.y );
+				if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+				_context.scale( scaleX, - scaleY );
+				_context.fillRect( - 0.5, - 0.5, 1, 1 );
+				_context.restore();
+
+			}
+
+		} else if ( material instanceof THREE.SpriteCanvasMaterial ) {
+
+			setStrokeStyle( material.color.getStyle() );
+			setFillStyle( material.color.getStyle() );
+
+			_context.save();
+			_context.translate( v1.x, v1.y );
+			if ( material.rotation !== 0 ) _context.rotate( material.rotation );
+			_context.scale( scaleX, scaleY );
+
+			material.program( _context );
+
+			_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();
+		*/
+
+	}
+
+	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 onTextureUpdate ( event ) {
+
+		textureToPattern( event.target );
+
+	}
+
+	function textureToPattern( texture ) {
+
+		var repeatX = texture.wrapS === THREE.RepeatWrapping;
+		var repeatY = texture.wrapT === THREE.RepeatWrapping;
+
+		var image = texture.image;
+
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = image.width;
+		canvas.height = image.height;
+
+		var context = canvas.getContext( '2d' );
+		context.setTransform( 1, 0, 0, - 1, 0, image.height );
+		context.drawImage( image, 0, 0 );
+
+		_patterns[ texture.id ] = _context.createPattern(
+			canvas, repeatX === true && repeatY === true
+				? 'repeat'
+				: repeatX === true && repeatY === false
+					? 'repeat-x'
+					: repeatX === false && repeatY === true
+						? 'repeat-y'
+						: 'no-repeat'
+		);
+
+	}
+
+	function patternPath( x0, y0, x1, y1, x2, y2, u0, v0, u1, v1, u2, v2, texture ) {
+
+		if ( texture instanceof THREE.DataTexture ) return;
+
+		if ( texture.hasEventListener( 'update', onTextureUpdate ) === false ) {
+
+			if ( texture.image !== undefined && texture.image.width > 0 ) {
+
+				textureToPattern( texture );
+
+			}
+
+			texture.addEventListener( 'update', onTextureUpdate );
+
+		}
+
+		var pattern = _patterns[ texture.id ];
+
+		if ( pattern !== undefined ) {
+
+			setFillStyle( pattern );
+
+		} else {
+
+			setFillStyle( 'rgba(0,0,0,1)' );
+			_context.fill();
+
+			return;
+
+		}	
+
+		// 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 = ( v0 + offsetY ) * height;
+
+		u1 = ( u1 + offsetX ) * width;
+		v1 = ( v1 + offsetY ) * height;
+
+		u2 = ( u2 + offsetX ) * width;
+		v2 = ( 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 ) 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;
+
+		}
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderTarget.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderTarget.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderTarget.js	(revision 28897)
@@ -0,0 +1,77 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderTargetCube.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderTargetCube.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderTargetCube.js	(revision 28897)
@@ -0,0 +1,13 @@
+/**
+ * @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 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderer.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderer.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/WebGLRenderer.js	(revision 28897)
@@ -0,0 +1,6570 @@
+/**
+ * @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 : false,
+	_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;
+
+	// 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 = _canvas.width,
+	_viewportHeight = _canvas.height,
+	_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.clearColor = function () {
+
+		_gl.clear( _gl.COLOR_BUFFER_BIT );
+
+	};
+
+	this.clearDepth = function () {
+
+		_gl.clear( _gl.DEPTH_BUFFER_BIT );
+
+	};
+
+	this.clearStencil = function () {
+
+		_gl.clear( _gl.STENCIL_BUFFER_BIT );
+
+	};
+
+	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.setFromMatrixPosition( 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.ParticleSystemMaterial ) {
+
+			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.ParticleSystemMaterial ) {
+
+				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.setFromMatrixPosition( 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.setFromMatrixPosition( light.matrixWorld );
+				_vector3.setFromMatrixPosition( 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.setFromMatrixPosition( 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.setFromMatrixPosition( light.matrixWorld );
+
+				spotPositions[ spotOffset ]     = _vector3.x;
+				spotPositions[ spotOffset + 1 ] = _vector3.y;
+				spotPositions[ spotOffset + 2 ] = _vector3.z;
+
+				spotDistances[ spotLength ] = distance;
+
+				_direction.copy( _vector3 );
+				_vector3.setFromMatrixPosition( 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.setFromMatrixPosition( 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" : "",
+
+			"#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" : "",
+
+			( 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.viewport( _viewportX, _viewportY, _viewportWidth, _viewportHeight );
+		
+		_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() );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableFace3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableFace3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableFace3.js	(revision 28897)
@@ -0,0 +1,28 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableLine.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableLine.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableLine.js	(revision 28897)
@@ -0,0 +1,17 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableObject.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableObject.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableObject.js	(revision 28897)
@@ -0,0 +1,12 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableObject = function () {
+
+	this.id = 0;
+
+	this.object = null;
+	this.z = 0;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableSprite.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableSprite.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableSprite.js	(revision 28897)
@@ -0,0 +1,20 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableSprite = function () {
+
+	this.id = 0;
+
+	this.object = null;
+
+	this.x = 0;
+	this.y = 0;
+	this.z = 0;
+
+	this.rotation = 0;
+	this.scale = new THREE.Vector2();
+
+	this.material = null;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableVertex.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableVertex.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/RenderableVertex.js	(revision 28897)
@@ -0,0 +1,20 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+THREE.RenderableVertex = function () {
+
+	this.position = new THREE.Vector3();
+	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 );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/renderables/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/ShaderChunk.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/ShaderChunk.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/ShaderChunk.js	(revision 28897)
@@ -0,0 +1,1745 @@
+/**
+ * Shader chunks for WebLG Shader library
+ * 
+ * @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 );",
+
+				// 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;",
+
+			"}",
+
+		"#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 );",
+
+					// 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;",
+
+				"}",
+
+			"}",
+
+		"#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 );",
+
+					/*
+				// 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;",
+
+
+			"}",
+
+		"#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 );",
+
+				"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 ) );",
+
+			"}",
+
+		"#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, 1.0 );",
+
+		"#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")
+
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/ShaderLib.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/ShaderLib.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/ShaderLib.js	(revision 28897)
@@ -0,0 +1,1244 @@
+/**
+ * Webgl Shader Library for three.js
+ *
+ * @author alteredq / http://alteredqualia.com/
+ * @author mrdoob / http://mrdoob.com/
+ * @author mikael emtinger / http://gomo.se/
+ */
+
+
+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 },
+
+			"diffuse": { type: "c", value: new THREE.Color( 0xffffff ) },
+			"specular": { type: "c", value: new THREE.Color( 0x111111 ) },
+			"ambient": { type: "c", value: new THREE.Color( 0xffffff ) },
+			"shininess": { type: "f", value: 30 },
+			"opacity": { type: "f", value: 1 },
+
+			"useRefract": { type: "i", value: 0 },
+			"refractionRatio": { type: "f", value: 0.98 },
+			"reflectivity": { 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 ambient;",
+			"uniform vec3 diffuse;",
+			"uniform vec3 specular;",
+			"uniform float shininess;",
+			"uniform float opacity;",
+
+			"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 refractionRatio;",
+			"uniform float reflectivity;",
+
+			"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 ), opacity );",
+
+				"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 ] * diffuse * pointDiffuseWeight;",
+
+						// specular
+
+						"vec3 pointHalfVector = normalize( pointVector + viewPosition );",
+						"float pointDotNormalHalf = max( dot( normal, pointHalfVector ), 0.0 );",
+						"float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );",
+
+						// 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( pointVector, pointHalfVector ), 5.0 );",
+						"pointSpecular += schlick * pointLightColor[ i ] * pointSpecularWeight * pointDiffuseWeight * pointDistance * specularNormalization;",
+
+					"}",
+
+				"#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 ] * diffuse * spotDiffuseWeight * spotEffect;",
+
+							// specular
+
+							"vec3 spotHalfVector = normalize( spotVector + viewPosition );",
+							"float spotDotNormalHalf = max( dot( normal, spotHalfVector ), 0.0 );",
+							"float spotSpecularWeight = specularTex.r * max( pow( spotDotNormalHalf, shininess ), 0.0 );",
+
+							// 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( spotVector, spotHalfVector ), 5.0 );",
+							"spotSpecular += schlick * spotLightColor[ i ] * spotSpecularWeight * spotDiffuseWeight * spotDistance * specularNormalization * spotEffect;",
+
+						"}",
+
+					"}",
+
+				"#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 ] * diffuse * dirDiffuseWeight;",
+
+						// specular
+
+						"vec3 dirHalfVector = normalize( dirVector + viewPosition );",
+						"float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
+						"float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );",
+
+						// 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( dirVector, dirHalfVector ), 5.0 );",
+						"dirSpecular += schlick * directionalLightColor[ i ] * dirSpecularWeight * dirDiffuseWeight * specularNormalization;",
+
+					"}",
+
+				"#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 += diffuse * 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, 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 = specularTex.r * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );",
+
+						"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 ) );",
+
+					"}",
+
+				"#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 * ambient + totalSpecular );",
+
+				"#else",
+
+					"gl_FragColor.xyz = gl_FragColor.xyz * ( totalDiffuse + ambientLightColor * ambient ) + totalSpecular;",
+
+				"#endif",
+
+				"if ( enableReflection ) {",
+
+					"vec3 vReflect;",
+					"vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );",
+
+					"if ( useRefract ) {",
+
+						"vReflect = refract( cameraToVertex, normal, refractionRatio );",
+
+					"} 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 * reflectivity );",
+
+				"}",
+
+				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")
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/UniformsLib.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/UniformsLib.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/UniformsLib.js	(revision 28897)
@@ -0,0 +1,102 @@
+/**
+ * Uniforms library for shared webgl shaders
+ */
+
+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: [] }
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/UniformsUtils.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/UniformsUtils.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/UniformsUtils.js	(revision 28897)
@@ -0,0 +1,66 @@
+/**
+ * Uniform Utilities
+ */
+
+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;
+
+	}
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/renderers/shaders/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/Fog.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/Fog.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/Fog.js	(revision 28897)
@@ -0,0 +1,21 @@
+/**
+ * @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 );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/FogExp2.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/FogExp2.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/FogExp2.js	(revision 28897)
@@ -0,0 +1,19 @@
+/**
+ * @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 );
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/Scene.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/Scene.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/Scene.js	(revision 28897)
@@ -0,0 +1,130 @@
+/**
+ * @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 );
+
+		}
+
+	}
+
+	this.dispatchEvent( { type: 'objectAdded', object: object } );
+	object.dispatchEvent( { type: 'addedToScene', scene: this } );
+
+	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 );
+
+		}
+
+	}
+
+	this.dispatchEvent( { type: 'objectRemoved', object: object } );
+	object.dispatchEvent( { type: 'removedFromScene', scene: this } );
+
+	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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/scenes/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/CompressedTexture.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/CompressedTexture.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/CompressedTexture.js	(revision 28897)
@@ -0,0 +1,26 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/DataTexture.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/DataTexture.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/DataTexture.js	(revision 28897)
@@ -0,0 +1,23 @@
+/**
+ * @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;
+
+};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/Texture.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/Texture.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/Texture.js	(revision 28897)
@@ -0,0 +1,109 @@
+/**
+ * @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,
+
+	get needsUpdate () {
+
+		return this._needsUpdate;
+
+	},
+
+	set needsUpdate ( value ) {
+
+		if ( value === true ) this.update();
+
+		this._needsUpdate = value;
+
+	},
+
+	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;
+
+	},
+
+	update: function () {
+
+		this.dispatchEvent( { type: 'update' } );
+
+	},
+
+	dispose: function () {
+
+		this.dispatchEvent( { type: 'dispose' } );
+
+	}
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.Texture.prototype );
+
+THREE.TextureIdCount = 0;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/src/textures/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmark-1.0.0.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmark-1.0.0.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmark-1.0.0.js	(revision 28897)
@@ -0,0 +1,3919 @@
+/*!
+ * Benchmark.js v1.0.0 <http://benchmarkjs.com/>
+ * Copyright 2010-2012 Mathias Bynens <http://mths.be/>
+ * Based on JSLitmus.js, copyright Robert Kieffer <http://broofa.com/>
+ * Modified by John-David Dalton <http://allyoucanleet.com/>
+ * Available under MIT license <http://mths.be/mit>
+ */
+;(function(window, undefined) {
+  'use strict';
+
+  /** Used to assign each benchmark an incrimented id */
+  var counter = 0;
+
+  /** Detect DOM document object */
+  var doc = isHostType(window, 'document') && document;
+
+  /** Detect free variable `define` */
+  var freeDefine = typeof define == 'function' &&
+    typeof define.amd == 'object' && define.amd && define;
+
+  /** Detect free variable `exports` */
+  var freeExports = typeof exports == 'object' && exports &&
+    (typeof global == 'object' && global && global == global.global && (window = global), exports);
+
+  /** Detect free variable `require` */
+  var freeRequire = typeof require == 'function' && require;
+
+  /** Used to crawl all properties regardless of enumerability */
+  var getAllKeys = Object.getOwnPropertyNames;
+
+  /** Used to get property descriptors */
+  var getDescriptor = Object.getOwnPropertyDescriptor;
+
+  /** Used in case an object doesn't have its own method */
+  var hasOwnProperty = {}.hasOwnProperty;
+
+  /** Used to check if an object is extensible */
+  var isExtensible = Object.isExtensible || function() { return true; };
+
+  /** Used to access Wade Simmons' Node microtime module */
+  var microtimeObject = req('microtime');
+
+  /** Used to access the browser's high resolution timer */
+  var perfObject = isHostType(window, 'performance') && performance;
+
+  /** Used to call the browser's high resolution timer */
+  var perfName = perfObject && (
+    perfObject.now && 'now' ||
+    perfObject.webkitNow && 'webkitNow'
+  );
+
+  /** Used to access Node's high resolution timer */
+  var processObject = isHostType(window, 'process') && process;
+
+  /** Used to check if an own property is enumerable */
+  var propertyIsEnumerable = {}.propertyIsEnumerable;
+
+  /** Used to set property descriptors */
+  var setDescriptor = Object.defineProperty;
+
+  /** Used to resolve a value's internal [[Class]] */
+  var toString = {}.toString;
+
+  /** Used to prevent a `removeChild` memory leak in IE < 9 */
+  var trash = doc && doc.createElement('div');
+
+  /** Used to integrity check compiled tests */
+  var uid = 'uid' + (+new Date);
+
+  /** Used to avoid infinite recursion when methods call each other */
+  var calledBy = {};
+
+  /** Used to avoid hz of Infinity */
+  var divisors = {
+    '1': 4096,
+    '2': 512,
+    '3': 64,
+    '4': 8,
+    '5': 0
+  };
+
+  /**
+   * T-Distribution two-tailed critical values for 95% confidence
+   * http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm
+   */
+  var tTable = {
+    '1':  12.706,'2':  4.303, '3':  3.182, '4':  2.776, '5':  2.571, '6':  2.447,
+    '7':  2.365, '8':  2.306, '9':  2.262, '10': 2.228, '11': 2.201, '12': 2.179,
+    '13': 2.16,  '14': 2.145, '15': 2.131, '16': 2.12,  '17': 2.11,  '18': 2.101,
+    '19': 2.093, '20': 2.086, '21': 2.08,  '22': 2.074, '23': 2.069, '24': 2.064,
+    '25': 2.06,  '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
+    'infinity': 1.96
+  };
+
+  /**
+   * Critical Mann-Whitney U-values for 95% confidence
+   * http://www.saburchill.com/IBbiology/stats/003.html
+   */
+  var uTable = {
+    '5':  [0, 1, 2],
+    '6':  [1, 2, 3, 5],
+    '7':  [1, 3, 5, 6, 8],
+    '8':  [2, 4, 6, 8, 10, 13],
+    '9':  [2, 4, 7, 10, 12, 15, 17],
+    '10': [3, 5, 8, 11, 14, 17, 20, 23],
+    '11': [3, 6, 9, 13, 16, 19, 23, 26, 30],
+    '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37],
+    '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45],
+    '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55],
+    '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64],
+    '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75],
+    '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87],
+    '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99],
+    '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113],
+    '20': [8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127],
+    '21': [8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142],
+    '22': [9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158],
+    '23': [9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175],
+    '24': [10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192],
+    '25': [10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211],
+    '26': [11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230],
+    '27': [11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250],
+    '28': [12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272],
+    '29': [13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294],
+    '30': [13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317]
+  };
+
+  /**
+   * An object used to flag environments/features.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @type Object
+   */
+  var support = {};
+
+  (function() {
+
+    /**
+     * Detect Adobe AIR.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.air = isClassOf(window.runtime, 'ScriptBridgingProxyObject');
+
+    /**
+     * Detect if `arguments` objects have the correct internal [[Class]] value.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.argumentsClass = isClassOf(arguments, 'Arguments');
+
+    /**
+     * Detect if in a browser environment.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.browser = doc && isHostType(window, 'navigator');
+
+    /**
+     * Detect if strings support accessing characters by index.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.charByIndex =
+      // IE 8 supports indexes on string literals but not string objects
+      ('x'[0] + Object('x')[0]) == 'xx';
+
+    /**
+     * Detect if strings have indexes as own properties.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.charByOwnIndex =
+      // Narwhal, Rhino, RingoJS, IE 8, and Opera < 10.52 support indexes on
+      // strings but don't detect them as own properties
+      support.charByIndex && hasKey('x', '0');
+
+    /**
+     * Detect if Java is enabled/exposed.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.java = isClassOf(window.java, 'JavaPackage');
+
+    /**
+     * Detect if the Timers API exists.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.timeout = isHostType(window, 'setTimeout') && isHostType(window, 'clearTimeout');
+
+    /**
+     * Detect if functions support decompilation.
+     *
+     * @name decompilation
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    try {
+      // Safari 2.x removes commas in object literals
+      // from Function#toString results
+      // http://webk.it/11609
+      // Firefox 3.6 and Opera 9.25 strip grouping
+      // parentheses from Function#toString results
+      // http://bugzil.la/559438
+      support.decompilation = Function(
+        'return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')'
+      )()(0).x === '1';
+    } catch(e) {
+      support.decompilation = false;
+    }
+
+    /**
+     * Detect ES5+ property descriptor API.
+     *
+     * @name descriptors
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    try {
+      var o = {};
+      support.descriptors = (setDescriptor(o, o, o), 'value' in getDescriptor(o, o));
+    } catch(e) {
+      support.descriptors = false;
+    }
+
+    /**
+     * Detect ES5+ Object.getOwnPropertyNames().
+     *
+     * @name getAllKeys
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    try {
+      support.getAllKeys = /\bvalueOf\b/.test(getAllKeys(Object.prototype));
+    } catch(e) {
+      support.getAllKeys = false;
+    }
+
+    /**
+     * Detect if own properties are iterated before inherited properties (all but IE < 9).
+     *
+     * @name iteratesOwnLast
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.iteratesOwnFirst = (function() {
+      var props = [];
+      function ctor() { this.x = 1; }
+      ctor.prototype = { 'y': 1 };
+      for (var prop in new ctor) { props.push(prop); }
+      return props[0] == 'x';
+    }());
+
+    /**
+     * Detect if a node's [[Class]] is resolvable (all but IE < 9)
+     * and that the JS engine errors when attempting to coerce an object to a
+     * string without a `toString` property value of `typeof` "function".
+     *
+     * @name nodeClass
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    try {
+      support.nodeClass = ({ 'toString': 0 } + '', toString.call(doc || 0) != '[object Object]');
+    } catch(e) {
+      support.nodeClass = true;
+    }
+  }());
+
+  /**
+   * Timer object used by `clock()` and `Deferred#resolve`.
+   *
+   * @private
+   * @type Object
+   */
+  var timer = {
+
+   /**
+    * The timer namespace object or constructor.
+    *
+    * @private
+    * @memberOf timer
+    * @type Function|Object
+    */
+    'ns': Date,
+
+   /**
+    * Starts the deferred timer.
+    *
+    * @private
+    * @memberOf timer
+    * @param {Object} deferred The deferred instance.
+    */
+    'start': null, // lazy defined in `clock()`
+
+   /**
+    * Stops the deferred timer.
+    *
+    * @private
+    * @memberOf timer
+    * @param {Object} deferred The deferred instance.
+    */
+    'stop': null // lazy defined in `clock()`
+  };
+
+  /** Shortcut for inverse results */
+  var noArgumentsClass = !support.argumentsClass,
+      noCharByIndex = !support.charByIndex,
+      noCharByOwnIndex = !support.charByOwnIndex;
+
+  /** Math shortcuts */
+  var abs   = Math.abs,
+      floor = Math.floor,
+      max   = Math.max,
+      min   = Math.min,
+      pow   = Math.pow,
+      sqrt  = Math.sqrt;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The Benchmark constructor.
+   *
+   * @constructor
+   * @param {String} name A name to identify the benchmark.
+   * @param {Function|String} fn The test to benchmark.
+   * @param {Object} [options={}] Options object.
+   * @example
+   *
+   * // basic usage (the `new` operator is optional)
+   * var bench = new Benchmark(fn);
+   *
+   * // or using a name first
+   * var bench = new Benchmark('foo', fn);
+   *
+   * // or with options
+   * var bench = new Benchmark('foo', fn, {
+   *
+   *   // displayed by Benchmark#toString if `name` is not available
+   *   'id': 'xyz',
+   *
+   *   // called when the benchmark starts running
+   *   'onStart': onStart,
+   *
+   *   // called after each run cycle
+   *   'onCycle': onCycle,
+   *
+   *   // called when aborted
+   *   'onAbort': onAbort,
+   *
+   *   // called when a test errors
+   *   'onError': onError,
+   *
+   *   // called when reset
+   *   'onReset': onReset,
+   *
+   *   // called when the benchmark completes running
+   *   'onComplete': onComplete,
+   *
+   *   // compiled/called before the test loop
+   *   'setup': setup,
+   *
+   *   // compiled/called after the test loop
+   *   'teardown': teardown
+   * });
+   *
+   * // or name and options
+   * var bench = new Benchmark('foo', {
+   *
+   *   // a flag to indicate the benchmark is deferred
+   *   'defer': true,
+   *
+   *   // benchmark test function
+   *   'fn': function(deferred) {
+   *     // call resolve() when the deferred test is finished
+   *     deferred.resolve();
+   *   }
+   * });
+   *
+   * // or options only
+   * var bench = new Benchmark({
+   *
+   *   // benchmark name
+   *   'name': 'foo',
+   *
+   *   // benchmark test as a string
+   *   'fn': '[1,2,3,4].sort()'
+   * });
+   *
+   * // a test's `this` binding is set to the benchmark instance
+   * var bench = new Benchmark('foo', function() {
+   *   'My name is '.concat(this.name); // My name is foo
+   * });
+   */
+  function Benchmark(name, fn, options) {
+    var me = this;
+
+    // allow instance creation without the `new` operator
+    if (me == null || me.constructor != Benchmark) {
+      return new Benchmark(name, fn, options);
+    }
+    // juggle arguments
+    if (isClassOf(name, 'Object')) {
+      // 1 argument (options)
+      options = name;
+    }
+    else if (isClassOf(name, 'Function')) {
+      // 2 arguments (fn, options)
+      options = fn;
+      fn = name;
+    }
+    else if (isClassOf(fn, 'Object')) {
+      // 2 arguments (name, options)
+      options = fn;
+      fn = null;
+      me.name = name;
+    }
+    else {
+      // 3 arguments (name, fn [, options])
+      me.name = name;
+    }
+    setOptions(me, options);
+    me.id || (me.id = ++counter);
+    me.fn == null && (me.fn = fn);
+    me.stats = deepClone(me.stats);
+    me.times = deepClone(me.times);
+  }
+
+  /**
+   * The Deferred constructor.
+   *
+   * @constructor
+   * @memberOf Benchmark
+   * @param {Object} clone The cloned benchmark instance.
+   */
+  function Deferred(clone) {
+    var me = this;
+    if (me == null || me.constructor != Deferred) {
+      return new Deferred(clone);
+    }
+    me.benchmark = clone;
+    clock(me);
+  }
+
+  /**
+   * The Event constructor.
+   *
+   * @constructor
+   * @memberOf Benchmark
+   * @param {String|Object} type The event type.
+   */
+  function Event(type) {
+    var me = this;
+    return (me == null || me.constructor != Event)
+      ? new Event(type)
+      : (type instanceof Event)
+          ? type
+          : extend(me, { 'timeStamp': +new Date }, typeof type == 'string' ? { 'type': type } : type);
+  }
+
+  /**
+   * The Suite constructor.
+   *
+   * @constructor
+   * @memberOf Benchmark
+   * @param {String} name A name to identify the suite.
+   * @param {Object} [options={}] Options object.
+   * @example
+   *
+   * // basic usage (the `new` operator is optional)
+   * var suite = new Benchmark.Suite;
+   *
+   * // or using a name first
+   * var suite = new Benchmark.Suite('foo');
+   *
+   * // or with options
+   * var suite = new Benchmark.Suite('foo', {
+   *
+   *   // called when the suite starts running
+   *   'onStart': onStart,
+   *
+   *   // called between running benchmarks
+   *   'onCycle': onCycle,
+   *
+   *   // called when aborted
+   *   'onAbort': onAbort,
+   *
+   *   // called when a test errors
+   *   'onError': onError,
+   *
+   *   // called when reset
+   *   'onReset': onReset,
+   *
+   *   // called when the suite completes running
+   *   'onComplete': onComplete
+   * });
+   */
+  function Suite(name, options) {
+    var me = this;
+
+    // allow instance creation without the `new` operator
+    if (me == null || me.constructor != Suite) {
+      return new Suite(name, options);
+    }
+    // juggle arguments
+    if (isClassOf(name, 'Object')) {
+      // 1 argument (options)
+      options = name;
+    } else {
+      // 2 arguments (name [, options])
+      me.name = name;
+    }
+    setOptions(me, options);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Note: Some array methods have been implemented in plain JavaScript to avoid
+   * bugs in IE, Opera, Rhino, and Mobile Safari.
+   *
+   * IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()`
+   * functions that fail to remove the last element, `object[0]`, of
+   * array-like-objects even though the `length` property is set to `0`.
+   * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+   * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+   *
+   * In Opera < 9.50 and some older/beta Mobile Safari versions using `unshift()`
+   * generically to augment the `arguments` object will pave the value at index 0
+   * without incrimenting the other values's indexes.
+   * https://github.com/documentcloud/underscore/issues/9
+   *
+   * Rhino and environments it powers, like Narwhal and RingoJS, may have
+   * buggy Array `concat()`, `reverse()`, `shift()`, `slice()`, `splice()` and
+   * `unshift()` functions that make sparse arrays non-sparse by assigning the
+   * undefined indexes a value of undefined.
+   * https://github.com/mozilla/rhino/commit/702abfed3f8ca043b2636efd31c14ba7552603dd
+   */
+
+  /**
+   * Creates an array containing the elements of the host array followed by the
+   * elements of each argument in order.
+   *
+   * @memberOf Benchmark.Suite
+   * @returns {Array} The new array.
+   */
+  function concat() {
+    var value,
+        j = -1,
+        length = arguments.length,
+        result = slice.call(this),
+        index = result.length;
+
+    while (++j < length) {
+      value = arguments[j];
+      if (isClassOf(value, 'Array')) {
+        for (var k = 0, l = value.length; k < l; k++, index++) {
+          if (k in value) {
+            result[index] = value[k];
+          }
+        }
+      } else {
+        result[index++] = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Utility function used by `shift()`, `splice()`, and `unshift()`.
+   *
+   * @private
+   * @param {Number} start The index to start inserting elements.
+   * @param {Number} deleteCount The number of elements to delete from the insert point.
+   * @param {Array} elements The elements to insert.
+   * @returns {Array} An array of deleted elements.
+   */
+  function insert(start, deleteCount, elements) {
+    // `result` should have its length set to the `deleteCount`
+    // see https://bugs.ecmascript.org/show_bug.cgi?id=332
+    var deleteEnd = start + deleteCount,
+        elementCount = elements ? elements.length : 0,
+        index = start - 1,
+        length = start + elementCount,
+        object = this,
+        result = Array(deleteCount),
+        tail = slice.call(object, deleteEnd);
+
+    // delete elements from the array
+    while (++index < deleteEnd) {
+      if (index in object) {
+        result[index - start] = object[index];
+        delete object[index];
+      }
+    }
+    // insert elements
+    index = start - 1;
+    while (++index < length) {
+      object[index] = elements[index - start];
+    }
+    // append tail elements
+    start = index--;
+    length = max(0, (object.length >>> 0) - deleteCount + elementCount);
+    while (++index < length) {
+      if ((index - start) in tail) {
+        object[index] = tail[index - start];
+      } else if (index in object) {
+        delete object[index];
+      }
+    }
+    // delete excess elements
+    deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0;
+    while (deleteCount--) {
+      index = length + deleteCount;
+      if (index in object) {
+        delete object[index];
+      }
+    }
+    object.length = length;
+    return result;
+  }
+
+  /**
+   * Rearrange the host array's elements in reverse order.
+   *
+   * @memberOf Benchmark.Suite
+   * @returns {Array} The reversed array.
+   */
+  function reverse() {
+    var upperIndex,
+        value,
+        index = -1,
+        object = Object(this),
+        length = object.length >>> 0,
+        middle = floor(length / 2);
+
+    if (length > 1) {
+      while (++index < middle) {
+        upperIndex = length - index - 1;
+        value = upperIndex in object ? object[upperIndex] : uid;
+        if (index in object) {
+          object[upperIndex] = object[index];
+        } else {
+          delete object[upperIndex];
+        }
+        if (value != uid) {
+          object[index] = value;
+        } else {
+          delete object[index];
+        }
+      }
+    }
+    return object;
+  }
+
+  /**
+   * Removes the first element of the host array and returns it.
+   *
+   * @memberOf Benchmark.Suite
+   * @returns {Mixed} The first element of the array.
+   */
+  function shift() {
+    return insert.call(this, 0, 1)[0];
+  }
+
+  /**
+   * Creates an array of the host array's elements from the start index up to,
+   * but not including, the end index.
+   *
+   * @memberOf Benchmark.Suite
+   * @param {Number} start The starting index.
+   * @param {Number} end The end index.
+   * @returns {Array} The new array.
+   */
+  function slice(start, end) {
+    var index = -1,
+        object = Object(this),
+        length = object.length >>> 0,
+        result = [];
+
+    start = toInteger(start);
+    start = start < 0 ? max(length + start, 0) : min(start, length);
+    start--;
+    end = end == null ? length : toInteger(end);
+    end = end < 0 ? max(length + end, 0) : min(end, length);
+
+    while ((++index, ++start) < end) {
+      if (start in object) {
+        result[index] = object[start];
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Allows removing a range of elements and/or inserting elements into the
+   * host array.
+   *
+   * @memberOf Benchmark.Suite
+   * @param {Number} start The start index.
+   * @param {Number} deleteCount The number of elements to delete.
+   * @param {Mixed} [val1, val2, ...] values to insert at the `start` index.
+   * @returns {Array} An array of removed elements.
+   */
+  function splice(start, deleteCount) {
+    var object = Object(this),
+        length = object.length >>> 0;
+
+    start = toInteger(start);
+    start = start < 0 ? max(length + start, 0) : min(start, length);
+
+    // support the de-facto SpiderMonkey extension
+    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice#Parameters
+    // https://bugs.ecmascript.org/show_bug.cgi?id=429
+    deleteCount = arguments.length == 1
+      ? length - start
+      : min(max(toInteger(deleteCount), 0), length - start);
+
+    return insert.call(object, start, deleteCount, slice.call(arguments, 2));
+  }
+
+  /**
+   * Converts the specified `value` to an integer.
+   *
+   * @private
+   * @param {Mixed} value The value to convert.
+   * @returns {Number} The resulting integer.
+   */
+  function toInteger(value) {
+    value = +value;
+    return value === 0 || !isFinite(value) ? value || 0 : value - (value % 1);
+  }
+
+  /**
+   * Appends arguments to the host array.
+   *
+   * @memberOf Benchmark.Suite
+   * @returns {Number} The new length.
+   */
+  function unshift() {
+    var object = Object(this);
+    insert.call(object, 0, 0, arguments);
+    return object.length;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * A generic `Function#bind` like method.
+   *
+   * @private
+   * @param {Function} fn The function to be bound to `thisArg`.
+   * @param {Mixed} thisArg The `this` binding for the given function.
+   * @returns {Function} The bound function.
+   */
+  function bind(fn, thisArg) {
+    return function() { fn.apply(thisArg, arguments); };
+  }
+
+  /**
+   * Creates a function from the given arguments string and body.
+   *
+   * @private
+   * @param {String} args The comma separated function arguments.
+   * @param {String} body The function body.
+   * @returns {Function} The new function.
+   */
+  function createFunction() {
+    // lazy define
+    createFunction = function(args, body) {
+      var result,
+          anchor = freeDefine ? define.amd : Benchmark,
+          prop = uid + 'createFunction';
+
+      runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}');
+      result = anchor[prop];
+      delete anchor[prop];
+      return result;
+    };
+    // fix JaegerMonkey bug
+    // http://bugzil.la/639720
+    createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || noop)() == uid ? createFunction : Function;
+    return createFunction.apply(null, arguments);
+  }
+
+  /**
+   * Delay the execution of a function based on the benchmark's `delay` property.
+   *
+   * @private
+   * @param {Object} bench The benchmark instance.
+   * @param {Object} fn The function to execute.
+   */
+  function delay(bench, fn) {
+    bench._timerId = setTimeout(fn, bench.delay * 1e3);
+  }
+
+  /**
+   * Destroys the given element.
+   *
+   * @private
+   * @param {Element} element The element to destroy.
+   */
+  function destroyElement(element) {
+    trash.appendChild(element);
+    trash.innerHTML = '';
+  }
+
+  /**
+   * Iterates over an object's properties, executing the `callback` for each.
+   * Callbacks may terminate the loop by explicitly returning `false`.
+   *
+   * @private
+   * @param {Object} object The object to iterate over.
+   * @param {Function} callback The function executed per own property.
+   * @param {Object} options The options object.
+   * @returns {Object} Returns the object iterated over.
+   */
+  function forProps() {
+    var forShadowed,
+        skipSeen,
+        forArgs = true,
+        shadowed = ['constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf'];
+
+    (function(enumFlag, key) {
+      // must use a non-native constructor to catch the Safari 2 issue
+      function Klass() { this.valueOf = 0; };
+      Klass.prototype.valueOf = 0;
+      // check various for-in bugs
+      for (key in new Klass) {
+        enumFlag += key == 'valueOf' ? 1 : 0;
+      }
+      // check if `arguments` objects have non-enumerable indexes
+      for (key in arguments) {
+        key == '0' && (forArgs = false);
+      }
+      // Safari 2 iterates over shadowed properties twice
+      // http://replay.waybackmachine.org/20090428222941/http://tobielangel.com/2007/1/29/for-in-loop-broken-in-safari/
+      skipSeen = enumFlag == 2;
+      // IE < 9 incorrectly makes an object's properties non-enumerable if they have
+      // the same name as other non-enumerable properties in its prototype chain.
+      forShadowed = !enumFlag;
+    }(0));
+
+    // lazy define
+    forProps = function(object, callback, options) {
+      options || (options = {});
+
+      var result = object;
+      object = Object(object);
+
+      var ctor,
+          key,
+          keys,
+          skipCtor,
+          done = !result,
+          which = options.which,
+          allFlag = which == 'all',
+          index = -1,
+          iteratee = object,
+          length = object.length,
+          ownFlag = allFlag || which == 'own',
+          seen = {},
+          skipProto = isClassOf(object, 'Function'),
+          thisArg = options.bind;
+
+      if (thisArg !== undefined) {
+        callback = bind(callback, thisArg);
+      }
+      // iterate all properties
+      if (allFlag && support.getAllKeys) {
+        for (index = 0, keys = getAllKeys(object), length = keys.length; index < length; index++) {
+          key = keys[index];
+          if (callback(object[key], key, object) === false) {
+            break;
+          }
+        }
+      }
+      // else iterate only enumerable properties
+      else {
+        for (key in object) {
+          // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+          // (if the prototype or a property on the prototype has been set)
+          // incorrectly set a function's `prototype` property [[Enumerable]] value
+          // to `true`. Because of this we standardize on skipping the `prototype`
+          // property of functions regardless of their [[Enumerable]] value.
+          if ((done =
+              !(skipProto && key == 'prototype') &&
+              !(skipSeen && (hasKey(seen, key) || !(seen[key] = true))) &&
+              (!ownFlag || ownFlag && hasKey(object, key)) &&
+              callback(object[key], key, object) === false)) {
+            break;
+          }
+        }
+        // in IE < 9 strings don't support accessing characters by index
+        if (!done && (forArgs && isArguments(object) ||
+            ((noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String') &&
+              (iteratee = noCharByIndex ? object.split('') : object)))) {
+          while (++index < length) {
+            if ((done =
+                callback(iteratee[index], String(index), object) === false)) {
+              break;
+            }
+          }
+        }
+        if (!done && forShadowed) {
+          // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing
+          // property and the `constructor` property of a prototype defaults to
+          // non-enumerable, we manually skip the `constructor` property when we
+          // think we are iterating over a `prototype` object.
+          ctor = object.constructor;
+          skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor;
+          for (index = 0; index < 7; index++) {
+            key = shadowed[index];
+            if (!(skipCtor && key == 'constructor') &&
+                hasKey(object, key) &&
+                callback(object[key], key, object) === false) {
+              break;
+            }
+          }
+        }
+      }
+      return result;
+    };
+    return forProps.apply(null, arguments);
+  }
+
+  /**
+   * Gets the name of the first argument from a function's source.
+   *
+   * @private
+   * @param {Function} fn The function.
+   * @returns {String} The argument name.
+   */
+  function getFirstArgument(fn) {
+    return (!hasKey(fn, 'toString') &&
+      (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || '';
+  }
+
+  /**
+   * Computes the arithmetic mean of a sample.
+   *
+   * @private
+   * @param {Array} sample The sample.
+   * @returns {Number} The mean.
+   */
+  function getMean(sample) {
+    return reduce(sample, function(sum, x) {
+      return sum + x;
+    }) / sample.length || 0;
+  }
+
+  /**
+   * Gets the source code of a function.
+   *
+   * @private
+   * @param {Function} fn The function.
+   * @param {String} altSource A string used when a function's source code is unretrievable.
+   * @returns {String} The function's source code.
+   */
+  function getSource(fn, altSource) {
+    var result = altSource;
+    if (isStringable(fn)) {
+      result = String(fn);
+    } else if (support.decompilation) {
+      // escape the `{` for Firefox 1
+      result = (/^[^{]+\{([\s\S]*)}\s*$/.exec(fn) || 0)[1];
+    }
+    // trim string
+    result = (result || '').replace(/^\s+|\s+$/g, '');
+
+    // detect strings containing only the "use strict" directive
+    return /^(?:\/\*+[\w|\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result)
+      ? ''
+      : result;
+  }
+
+  /**
+   * Checks if a value is an `arguments` object.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the value is an `arguments` object, else `false`.
+   */
+  function isArguments() {
+    // lazy define
+    isArguments = function(value) {
+      return toString.call(value) == '[object Arguments]';
+    };
+    if (noArgumentsClass) {
+      isArguments = function(value) {
+        return hasKey(value, 'callee') &&
+          !(propertyIsEnumerable && propertyIsEnumerable.call(value, 'callee'));
+      };
+    }
+    return isArguments(arguments[0]);
+  }
+
+  /**
+   * Checks if an object is of the specified class.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @param {String} name The name of the class.
+   * @returns {Boolean} Returns `true` if the value is of the specified class, else `false`.
+   */
+  function isClassOf(value, name) {
+    return value != null && toString.call(value) == '[object ' + name + ']';
+  }
+
+  /**
+   * Host objects can return type values that are different from their actual
+   * data type. The objects we are concerned with usually return non-primitive
+   * types of object, function, or unknown.
+   *
+   * @private
+   * @param {Mixed} object The owner of the property.
+   * @param {String} property The property to check.
+   * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`.
+   */
+  function isHostType(object, property) {
+    var type = object != null ? typeof object[property] : 'number';
+    return !/^(?:boolean|number|string|undefined)$/.test(type) &&
+      (type == 'object' ? !!object[property] : true);
+  }
+
+  /**
+   * Checks if a given `value` is an object created by the `Object` constructor
+   * assuming objects created by the `Object` constructor have no inherited
+   * enumerable properties and that there are no `Object.prototype` extensions.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, else `false`.
+   */
+  function isPlainObject(value) {
+    // avoid non-objects and false positives for `arguments` objects in IE < 9
+    var result = false;
+    if (!(value && typeof value == 'object') || isArguments(value)) {
+      return result;
+    }
+    // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
+    // methods that are `typeof` "string" and still can coerce nodes to strings.
+    // Also check that the constructor is `Object` (i.e. `Object instanceof Object`)
+    var ctor = value.constructor;
+    if ((support.nodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) &&
+        (!isClassOf(ctor, 'Function') || ctor instanceof ctor)) {
+      // In most environments an object's own properties are iterated before
+      // its inherited properties. If the last iterated property is an object's
+      // own property then there are no inherited enumerable properties.
+      if (support.iteratesOwnFirst) {
+        forProps(value, function(subValue, subKey) {
+          result = subKey;
+        });
+        return result === false || hasKey(value, result);
+      }
+      // IE < 9 iterates inherited properties before own properties. If the first
+      // iterated property is an object's own property then there are no inherited
+      // enumerable properties.
+      forProps(value, function(subValue, subKey) {
+        result = !hasKey(value, subKey);
+        return false;
+      });
+      return result === false;
+    }
+    return result;
+  }
+
+  /**
+   * Checks if a value can be safely coerced to a string.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the value can be coerced, else `false`.
+   */
+  function isStringable(value) {
+    return hasKey(value, 'toString') || isClassOf(value, 'String');
+  }
+
+  /**
+   * Wraps a function and passes `this` to the original function as the
+   * first argument.
+   *
+   * @private
+   * @param {Function} fn The function to be wrapped.
+   * @returns {Function} The new function.
+   */
+  function methodize(fn) {
+    return function() {
+      var args = [this];
+      args.push.apply(args, arguments);
+      return fn.apply(null, args);
+    };
+  }
+
+  /**
+   * A no-operation function.
+   *
+   * @private
+   */
+  function noop() {
+    // no operation performed
+  }
+
+  /**
+   * A wrapper around require() to suppress `module missing` errors.
+   *
+   * @private
+   * @param {String} id The module id.
+   * @returns {Mixed} The exported module or `null`.
+   */
+  function req(id) {
+    try {
+      var result = freeExports && freeRequire(id);
+    } catch(e) { }
+    return result || null;
+  }
+
+  /**
+   * Runs a snippet of JavaScript via script injection.
+   *
+   * @private
+   * @param {String} code The code to run.
+   */
+  function runScript(code) {
+    var anchor = freeDefine ? define.amd : Benchmark,
+        script = doc.createElement('script'),
+        sibling = doc.getElementsByTagName('script')[0],
+        parent = sibling.parentNode,
+        prop = uid + 'runScript',
+        prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();';
+
+    // Firefox 2.0.0.2 cannot use script injection as intended because it executes
+    // asynchronously, but that's OK because script injection is only used to avoid
+    // the previously commented JaegerMonkey bug.
+    try {
+      // remove the inserted script *before* running the code to avoid differences
+      // in the expected script element count/order of the document.
+      script.appendChild(doc.createTextNode(prefix + code));
+      anchor[prop] = function() { destroyElement(script); };
+    } catch(e) {
+      parent = parent.cloneNode(false);
+      sibling = null;
+      script.text = code;
+    }
+    parent.insertBefore(script, sibling);
+    delete anchor[prop];
+  }
+
+  /**
+   * A helper function for setting options/event handlers.
+   *
+   * @private
+   * @param {Object} bench The benchmark instance.
+   * @param {Object} [options={}] Options object.
+   */
+  function setOptions(bench, options) {
+    options = extend({}, bench.constructor.options, options);
+    bench.options = forOwn(options, function(value, key) {
+      if (value != null) {
+        // add event listeners
+        if (/^on[A-Z]/.test(key)) {
+          forEach(key.split(' '), function(key) {
+            bench.on(key.slice(2).toLowerCase(), value);
+          });
+        } else if (!hasKey(bench, key)) {
+          bench[key] = deepClone(value);
+        }
+      }
+    });
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Handles cycling/completing the deferred benchmark.
+   *
+   * @memberOf Benchmark.Deferred
+   */
+  function resolve() {
+    var me = this,
+        clone = me.benchmark,
+        bench = clone._original;
+
+    if (bench.aborted) {
+      // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete
+      me.teardown();
+      clone.running = false;
+      cycle(me);
+    }
+    else if (++me.cycles < clone.count) {
+      // continue the test loop
+      if (support.timeout) {
+        // use setTimeout to avoid a call stack overflow if called recursively
+        setTimeout(function() { clone.compiled.call(me, timer); }, 0);
+      } else {
+        clone.compiled.call(me, timer);
+      }
+    }
+    else {
+      timer.stop(me);
+      me.teardown();
+      delay(clone, function() { cycle(me); });
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * A deep clone utility.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Mixed} value The value to clone.
+   * @returns {Mixed} The cloned value.
+   */
+  function deepClone(value) {
+    var accessor,
+        circular,
+        clone,
+        ctor,
+        descriptor,
+        extensible,
+        key,
+        length,
+        markerKey,
+        parent,
+        result,
+        source,
+        subIndex,
+        data = { 'value': value },
+        index = 0,
+        marked = [],
+        queue = { 'length': 0 },
+        unmarked = [];
+
+    /**
+     * An easily detectable decorator for cloned values.
+     */
+    function Marker(object) {
+      this.raw = object;
+    }
+
+    /**
+     * The callback used by `forProps()`.
+     */
+    function forPropsCallback(subValue, subKey) {
+      // exit early to avoid cloning the marker
+      if (subValue && subValue.constructor == Marker) {
+        return;
+      }
+      // add objects to the queue
+      if (subValue === Object(subValue)) {
+        queue[queue.length++] = { 'key': subKey, 'parent': clone, 'source': value };
+      }
+      // assign non-objects
+      else {
+        try {
+          // will throw an error in strict mode if the property is read-only
+          clone[subKey] = subValue;
+        } catch(e) { }
+      }
+    }
+
+    /**
+     * Gets an available marker key for the given object.
+     */
+    function getMarkerKey(object) {
+      // avoid collisions with existing keys
+      var result = uid;
+      while (object[result] && object[result].constructor != Marker) {
+        result += 1;
+      }
+      return result;
+    }
+
+    do {
+      key = data.key;
+      parent = data.parent;
+      source = data.source;
+      clone = value = source ? source[key] : data.value;
+      accessor = circular = descriptor = false;
+
+      // create a basic clone to filter out functions, DOM elements, and
+      // other non `Object` objects
+      if (value === Object(value)) {
+        // use custom deep clone function if available
+        if (isClassOf(value.deepClone, 'Function')) {
+          clone = value.deepClone();
+        } else {
+          ctor = value.constructor;
+          switch (toString.call(value)) {
+            case '[object Array]':
+              clone = new ctor(value.length);
+              break;
+
+            case '[object Boolean]':
+              clone = new ctor(value == true);
+              break;
+
+            case '[object Date]':
+              clone = new ctor(+value);
+              break;
+
+            case '[object Object]':
+              isPlainObject(value) && (clone = {});
+              break;
+
+            case '[object Number]':
+            case '[object String]':
+              clone = new ctor(value);
+              break;
+
+            case '[object RegExp]':
+              clone = ctor(value.source,
+                (value.global     ? 'g' : '') +
+                (value.ignoreCase ? 'i' : '') +
+                (value.multiline  ? 'm' : ''));
+          }
+        }
+        // continue clone if `value` doesn't have an accessor descriptor
+        // http://es5.github.com/#x8.10.1
+        if (clone && clone != value &&
+            !(descriptor = source && support.descriptors && getDescriptor(source, key),
+              accessor = descriptor && (descriptor.get || descriptor.set))) {
+          // use an existing clone (circular reference)
+          if ((extensible = isExtensible(value))) {
+            markerKey = getMarkerKey(value);
+            if (value[markerKey]) {
+              circular = clone = value[markerKey].raw;
+            }
+          } else {
+            // for frozen/sealed objects
+            for (subIndex = 0, length = unmarked.length; subIndex < length; subIndex++) {
+              data = unmarked[subIndex];
+              if (data.object === value) {
+                circular = clone = data.clone;
+                break;
+              }
+            }
+          }
+          if (!circular) {
+            // mark object to allow quickly detecting circular references and tie it to its clone
+            if (extensible) {
+              value[markerKey] = new Marker(clone);
+              marked.push({ 'key': markerKey, 'object': value });
+            } else {
+              // for frozen/sealed objects
+              unmarked.push({ 'clone': clone, 'object': value });
+            }
+            // iterate over object properties
+            forProps(value, forPropsCallback, { 'which': 'all' });
+          }
+        }
+      }
+      if (parent) {
+        // for custom property descriptors
+        if (accessor || (descriptor && !(descriptor.configurable && descriptor.enumerable && descriptor.writable))) {
+          if ('value' in descriptor) {
+            descriptor.value = clone;
+          }
+          setDescriptor(parent, key, descriptor);
+        }
+        // for default property descriptors
+        else {
+          parent[key] = clone;
+        }
+      } else {
+        result = clone;
+      }
+    } while ((data = queue[index++]));
+
+    // remove markers
+    for (index = 0, length = marked.length; index < length; index++) {
+      data = marked[index];
+      delete data.object[data.key];
+    }
+    return result;
+  }
+
+  /**
+   * An iteration utility for arrays and objects.
+   * Callbacks may terminate the loop by explicitly returning `false`.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array|Object} object The object to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Array|Object} Returns the object iterated over.
+   */
+  function each(object, callback, thisArg) {
+    var result = object;
+    object = Object(object);
+
+    var fn = callback,
+        index = -1,
+        length = object.length,
+        isSnapshot = !!(object.snapshotItem && (length = object.snapshotLength)),
+        isSplittable = (noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String'),
+        isConvertable = isSnapshot || isSplittable || 'item' in object,
+        origObject = object;
+
+    // in Opera < 10.5 `hasKey(object, 'length')` returns `false` for NodeLists
+    if (length === length >>> 0) {
+      if (isConvertable) {
+        // the third argument of the callback is the original non-array object
+        callback = function(value, index) {
+          return fn.call(this, value, index, origObject);
+        };
+        // in IE < 9 strings don't support accessing characters by index
+        if (isSplittable) {
+          object = object.split('');
+        } else {
+          object = [];
+          while (++index < length) {
+            // in Safari 2 `index in object` is always `false` for NodeLists
+            object[index] = isSnapshot ? result.snapshotItem(index) : result[index];
+          }
+        }
+      }
+      forEach(object, callback, thisArg);
+    } else {
+      forOwn(object, callback, thisArg);
+    }
+    return result;
+  }
+
+  /**
+   * Copies enumerable properties from the source(s) object to the destination object.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Object} destination The destination object.
+   * @param {Object} [source={}] The source object.
+   * @returns {Object} The destination object.
+   */
+  function extend(destination, source) {
+    // Chrome < 14 incorrectly sets `destination` to `undefined` when we `delete arguments[0]`
+    // http://code.google.com/p/v8/issues/detail?id=839
+    var result = destination;
+    delete arguments[0];
+
+    forEach(arguments, function(source) {
+      forProps(source, function(value, key) {
+        result[key] = value;
+      });
+    });
+    return result;
+  }
+
+  /**
+   * A generic `Array#filter` like method.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Function|String} callback The function/alias called per iteration.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Array} A new array of values that passed callback filter.
+   * @example
+   *
+   * // get odd numbers
+   * Benchmark.filter([1, 2, 3, 4, 5], function(n) {
+   *   return n % 2;
+   * }); // -> [1, 3, 5];
+   *
+   * // get fastest benchmarks
+   * Benchmark.filter(benches, 'fastest');
+   *
+   * // get slowest benchmarks
+   * Benchmark.filter(benches, 'slowest');
+   *
+   * // get benchmarks that completed without erroring
+   * Benchmark.filter(benches, 'successful');
+   */
+  function filter(array, callback, thisArg) {
+    var result;
+
+    if (callback == 'successful') {
+      // callback to exclude those that are errored, unrun, or have hz of Infinity
+      callback = function(bench) { return bench.cycles && isFinite(bench.hz); };
+    }
+    else if (callback == 'fastest' || callback == 'slowest') {
+      // get successful, sort by period + margin of error, and filter fastest/slowest
+      result = filter(array, 'successful').sort(function(a, b) {
+        a = a.stats; b = b.stats;
+        return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback == 'fastest' ? 1 : -1);
+      });
+      result = filter(result, function(bench) {
+        return result[0].compare(bench) == 0;
+      });
+    }
+    return result || reduce(array, function(result, value, index) {
+      return callback.call(thisArg, value, index, array) ? (result.push(value), result) : result;
+    }, []);
+  }
+
+  /**
+   * A generic `Array#forEach` like method.
+   * Callbacks may terminate the loop by explicitly returning `false`.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Array} Returns the array iterated over.
+   */
+  function forEach(array, callback, thisArg) {
+    var index = -1,
+        length = (array = Object(array)).length >>> 0;
+
+    if (thisArg !== undefined) {
+      callback = bind(callback, thisArg);
+    }
+    while (++index < length) {
+      if (index in array &&
+          callback(array[index], index, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * Iterates over an object's own properties, executing the `callback` for each.
+   * Callbacks may terminate the loop by explicitly returning `false`.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Object} object The object to iterate over.
+   * @param {Function} callback The function executed per own property.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Object} Returns the object iterated over.
+   */
+  function forOwn(object, callback, thisArg) {
+    return forProps(object, callback, { 'bind': thisArg, 'which': 'own' });
+  }
+
+  /**
+   * Converts a number to a more readable comma-separated string representation.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Number} number The number to convert.
+   * @returns {String} The more readable string representation.
+   */
+  function formatNumber(number) {
+    number = String(number).split('.');
+    return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') +
+      (number[1] ? '.' + number[1] : '');
+  }
+
+  /**
+   * Checks if an object has the specified key as a direct property.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Object} object The object to check.
+   * @param {String} key The key to check for.
+   * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
+   */
+  function hasKey() {
+    // lazy define for worst case fallback (not as accurate)
+    hasKey = function(object, key) {
+      var parent = object != null && (object.constructor || Object).prototype;
+      return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]);
+    };
+    // for modern browsers
+    if (isClassOf(hasOwnProperty, 'Function')) {
+      hasKey = function(object, key) {
+        return object != null && hasOwnProperty.call(object, key);
+      };
+    }
+    // for Safari 2
+    else if ({}.__proto__ == Object.prototype) {
+      hasKey = function(object, key) {
+        var result = false;
+        if (object != null) {
+          object = Object(object);
+          object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0];
+        }
+        return result;
+      };
+    }
+    return hasKey.apply(this, arguments);
+  }
+
+  /**
+   * A generic `Array#indexOf` like method.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Mixed} value The value to search for.
+   * @param {Number} [fromIndex=0] The index to start searching from.
+   * @returns {Number} The index of the matched value or `-1`.
+   */
+  function indexOf(array, value, fromIndex) {
+    var index = toInteger(fromIndex),
+        length = (array = Object(array)).length >>> 0;
+
+    index = (index < 0 ? max(0, length + index) : index) - 1;
+    while (++index < length) {
+      if (index in array && value === array[index]) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Modify a string by replacing named tokens with matching object property values.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {String} string The string to modify.
+   * @param {Object} object The template object.
+   * @returns {String} The modified string.
+   */
+  function interpolate(string, object) {
+    forOwn(object, function(value, key) {
+      // escape regexp special characters in `key`
+      string = string.replace(RegExp('#\\{' + key.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1') + '\\}', 'g'), value);
+    });
+    return string;
+  }
+
+  /**
+   * Invokes a method on all items in an array.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} benches Array of benchmarks to iterate over.
+   * @param {String|Object} name The name of the method to invoke OR options object.
+   * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+   * @returns {Array} A new array of values returned from each method invoked.
+   * @example
+   *
+   * // invoke `reset` on all benchmarks
+   * Benchmark.invoke(benches, 'reset');
+   *
+   * // invoke `emit` with arguments
+   * Benchmark.invoke(benches, 'emit', 'complete', listener);
+   *
+   * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks
+   * Benchmark.invoke(benches, {
+   *
+   *   // invoke the `run` method
+   *   'name': 'run',
+   *
+   *   // pass a single argument
+   *   'args': true,
+   *
+   *   // treat as queue, removing benchmarks from front of `benches` until empty
+   *   'queued': true,
+   *
+   *   // called before any benchmarks have been invoked.
+   *   'onStart': onStart,
+   *
+   *   // called between invoking benchmarks
+   *   'onCycle': onCycle,
+   *
+   *   // called after all benchmarks have been invoked.
+   *   'onComplete': onComplete
+   * });
+   */
+  function invoke(benches, name) {
+    var args,
+        bench,
+        queued,
+        index = -1,
+        eventProps = { 'currentTarget': benches },
+        options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop },
+        result = map(benches, function(bench) { return bench; });
+
+    /**
+     * Invokes the method of the current object and if synchronous, fetches the next.
+     */
+    function execute() {
+      var listeners,
+          async = isAsync(bench);
+
+      if (async) {
+        // use `getNext` as the first listener
+        bench.on('complete', getNext);
+        listeners = bench.events.complete;
+        listeners.splice(0, 0, listeners.pop());
+      }
+      // execute method
+      result[index] = isClassOf(bench && bench[name], 'Function') ? bench[name].apply(bench, args) : undefined;
+      // if synchronous return true until finished
+      return !async && getNext();
+    }
+
+    /**
+     * Fetches the next bench or executes `onComplete` callback.
+     */
+    function getNext(event) {
+      var cycleEvent,
+          last = bench,
+          async = isAsync(last);
+
+      if (async) {
+        last.off('complete', getNext);
+        last.emit('complete');
+      }
+      // emit "cycle" event
+      eventProps.type = 'cycle';
+      eventProps.target = last;
+      cycleEvent = Event(eventProps);
+      options.onCycle.call(benches, cycleEvent);
+
+      // choose next benchmark if not exiting early
+      if (!cycleEvent.aborted && raiseIndex() !== false) {
+        bench = queued ? benches[0] : result[index];
+        if (isAsync(bench)) {
+          delay(bench, execute);
+        }
+        else if (async) {
+          // resume execution if previously asynchronous but now synchronous
+          while (execute()) { }
+        }
+        else {
+          // continue synchronous execution
+          return true;
+        }
+      } else {
+        // emit "complete" event
+        eventProps.type = 'complete';
+        options.onComplete.call(benches, Event(eventProps));
+      }
+      // When used as a listener `event.aborted = true` will cancel the rest of
+      // the "complete" listeners because they were already called above and when
+      // used as part of `getNext` the `return false` will exit the execution while-loop.
+      if (event) {
+        event.aborted = true;
+      } else {
+        return false;
+      }
+    }
+
+    /**
+     * Checks if invoking `Benchmark#run` with asynchronous cycles.
+     */
+    function isAsync(object) {
+      // avoid using `instanceof` here because of IE memory leak issues with host objects
+      var async = args[0] && args[0].async;
+      return Object(object).constructor == Benchmark && name == 'run' &&
+        ((async == null ? object.options.async : async) && support.timeout || object.defer);
+    }
+
+    /**
+     * Raises `index` to the next defined index or returns `false`.
+     */
+    function raiseIndex() {
+      var length = result.length;
+      if (queued) {
+        // if queued remove the previous bench and subsequent skipped non-entries
+        do {
+          ++index > 0 && shift.call(benches);
+        } while ((length = benches.length) && !('0' in benches));
+      }
+      else {
+        while (++index < length && !(index in result)) { }
+      }
+      // if we reached the last index then return `false`
+      return (queued ? length : index < length) ? index : (index = false);
+    }
+
+    // juggle arguments
+    if (isClassOf(name, 'String')) {
+      // 2 arguments (array, name)
+      args = slice.call(arguments, 2);
+    } else {
+      // 2 arguments (array, options)
+      options = extend(options, name);
+      name = options.name;
+      args = isClassOf(args = 'args' in options ? options.args : [], 'Array') ? args : [args];
+      queued = options.queued;
+    }
+
+    // start iterating over the array
+    if (raiseIndex() !== false) {
+      // emit "start" event
+      bench = result[index];
+      eventProps.type = 'start';
+      eventProps.target = bench;
+      options.onStart.call(benches, Event(eventProps));
+
+      // end early if the suite was aborted in an "onStart" listener
+      if (benches.aborted && benches.constructor == Suite && name == 'run') {
+        // emit "cycle" event
+        eventProps.type = 'cycle';
+        options.onCycle.call(benches, Event(eventProps));
+        // emit "complete" event
+        eventProps.type = 'complete';
+        options.onComplete.call(benches, Event(eventProps));
+      }
+      // else start
+      else {
+        if (isAsync(bench)) {
+          delay(bench, execute);
+        } else {
+          while (execute()) { }
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates a string of joined array values or object key-value pairs.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array|Object} object The object to operate on.
+   * @param {String} [separator1=','] The separator used between key-value pairs.
+   * @param {String} [separator2=': '] The separator used between keys and values.
+   * @returns {String} The joined result.
+   */
+  function join(object, separator1, separator2) {
+    var result = [],
+        length = (object = Object(object)).length,
+        arrayLike = length === length >>> 0;
+
+    separator2 || (separator2 = ': ');
+    each(object, function(value, key) {
+      result.push(arrayLike ? value : key + separator2 + value);
+    });
+    return result.join(separator1 || ',');
+  }
+
+  /**
+   * A generic `Array#map` like method.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Array} A new array of values returned by the callback.
+   */
+  function map(array, callback, thisArg) {
+    return reduce(array, function(result, value, index) {
+      result[index] = callback.call(thisArg, value, index, array);
+      return result;
+    }, Array(Object(array).length >>> 0));
+  }
+
+  /**
+   * Retrieves the value of a specified property from all items in an array.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {String} property The property to pluck.
+   * @returns {Array} A new array of property values.
+   */
+  function pluck(array, property) {
+    return map(array, function(object) {
+      return object == null ? undefined : object[property];
+    });
+  }
+
+  /**
+   * A generic `Array#reduce` like method.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} accumulator Initial value of the accumulator.
+   * @returns {Mixed} The accumulator.
+   */
+  function reduce(array, callback, accumulator) {
+    var noaccum = arguments.length < 3;
+    forEach(array, function(value, index) {
+      accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, array);
+    });
+    return accumulator;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Aborts all benchmarks in the suite.
+   *
+   * @name abort
+   * @memberOf Benchmark.Suite
+   * @returns {Object} The suite instance.
+   */
+  function abortSuite() {
+    var event,
+        me = this,
+        resetting = calledBy.resetSuite;
+
+    if (me.running) {
+      event = Event('abort');
+      me.emit(event);
+      if (!event.cancelled || resetting) {
+        // avoid infinite recursion
+        calledBy.abortSuite = true;
+        me.reset();
+        delete calledBy.abortSuite;
+
+        if (!resetting) {
+          me.aborted = true;
+          invoke(me, 'abort');
+        }
+      }
+    }
+    return me;
+  }
+
+  /**
+   * Adds a test to the benchmark suite.
+   *
+   * @memberOf Benchmark.Suite
+   * @param {String} name A name to identify the benchmark.
+   * @param {Function|String} fn The test to benchmark.
+   * @param {Object} [options={}] Options object.
+   * @returns {Object} The benchmark instance.
+   * @example
+   *
+   * // basic usage
+   * suite.add(fn);
+   *
+   * // or using a name first
+   * suite.add('foo', fn);
+   *
+   * // or with options
+   * suite.add('foo', fn, {
+   *   'onCycle': onCycle,
+   *   'onComplete': onComplete
+   * });
+   *
+   * // or name and options
+   * suite.add('foo', {
+   *   'fn': fn,
+   *   'onCycle': onCycle,
+   *   'onComplete': onComplete
+   * });
+   *
+   * // or options only
+   * suite.add({
+   *   'name': 'foo',
+   *   'fn': fn,
+   *   'onCycle': onCycle,
+   *   'onComplete': onComplete
+   * });
+   */
+  function add(name, fn, options) {
+    var me = this,
+        bench = Benchmark(name, fn, options),
+        event = Event({ 'type': 'add', 'target': bench });
+
+    if (me.emit(event), !event.cancelled) {
+      me.push(bench);
+    }
+    return me;
+  }
+
+  /**
+   * Creates a new suite with cloned benchmarks.
+   *
+   * @name clone
+   * @memberOf Benchmark.Suite
+   * @param {Object} options Options object to overwrite cloned options.
+   * @returns {Object} The new suite instance.
+   */
+  function cloneSuite(options) {
+    var me = this,
+        result = new me.constructor(extend({}, me.options, options));
+
+    // copy own properties
+    forOwn(me, function(value, key) {
+      if (!hasKey(result, key)) {
+        result[key] = value && isClassOf(value.clone, 'Function')
+          ? value.clone()
+          : deepClone(value);
+      }
+    });
+    return result;
+  }
+
+  /**
+   * An `Array#filter` like method.
+   *
+   * @name filter
+   * @memberOf Benchmark.Suite
+   * @param {Function|String} callback The function/alias called per iteration.
+   * @returns {Object} A new suite of benchmarks that passed callback filter.
+   */
+  function filterSuite(callback) {
+    var me = this,
+        result = new me.constructor;
+
+    result.push.apply(result, filter(me, callback));
+    return result;
+  }
+
+  /**
+   * Resets all benchmarks in the suite.
+   *
+   * @name reset
+   * @memberOf Benchmark.Suite
+   * @returns {Object} The suite instance.
+   */
+  function resetSuite() {
+    var event,
+        me = this,
+        aborting = calledBy.abortSuite;
+
+    if (me.running && !aborting) {
+      // no worries, `resetSuite()` is called within `abortSuite()`
+      calledBy.resetSuite = true;
+      me.abort();
+      delete calledBy.resetSuite;
+    }
+    // reset if the state has changed
+    else if ((me.aborted || me.running) &&
+        (me.emit(event = Event('reset')), !event.cancelled)) {
+      me.running = false;
+      if (!aborting) {
+        invoke(me, 'reset');
+      }
+    }
+    return me;
+  }
+
+  /**
+   * Runs the suite.
+   *
+   * @name run
+   * @memberOf Benchmark.Suite
+   * @param {Object} [options={}] Options object.
+   * @returns {Object} The suite instance.
+   * @example
+   *
+   * // basic usage
+   * suite.run();
+   *
+   * // or with options
+   * suite.run({ 'async': true, 'queued': true });
+   */
+  function runSuite(options) {
+    var me = this;
+
+    me.reset();
+    me.running = true;
+    options || (options = {});
+
+    invoke(me, {
+      'name': 'run',
+      'args': options,
+      'queued': options.queued,
+      'onStart': function(event) {
+        me.emit(event);
+      },
+      'onCycle': function(event) {
+        var bench = event.target;
+        if (bench.error) {
+          me.emit({ 'type': 'error', 'target': bench });
+        }
+        me.emit(event);
+        event.aborted = me.aborted;
+      },
+      'onComplete': function(event) {
+        me.running = false;
+        me.emit(event);
+      }
+    });
+    return me;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Executes all registered listeners of the specified event type.
+   *
+   * @memberOf Benchmark, Benchmark.Suite
+   * @param {String|Object} type The event type or object.
+   * @returns {Mixed} Returns the return value of the last listener executed.
+   */
+  function emit(type) {
+    var listeners,
+        me = this,
+        event = Event(type),
+        events = me.events,
+        args = (arguments[0] = event, arguments);
+
+    event.currentTarget || (event.currentTarget = me);
+    event.target || (event.target = me);
+    delete event.result;
+
+    if (events && (listeners = hasKey(events, event.type) && events[event.type])) {
+      forEach(listeners.slice(), function(listener) {
+        if ((event.result = listener.apply(me, args)) === false) {
+          event.cancelled = true;
+        }
+        return !event.aborted;
+      });
+    }
+    return event.result;
+  }
+
+  /**
+   * Returns an array of event listeners for a given type that can be manipulated
+   * to add or remove listeners.
+   *
+   * @memberOf Benchmark, Benchmark.Suite
+   * @param {String} type The event type.
+   * @returns {Array} The listeners array.
+   */
+  function listeners(type) {
+    var me = this,
+        events = me.events || (me.events = {});
+
+    return hasKey(events, type) ? events[type] : (events[type] = []);
+  }
+
+  /**
+   * Unregisters a listener for the specified event type(s),
+   * or unregisters all listeners for the specified event type(s),
+   * or unregisters all listeners for all event types.
+   *
+   * @memberOf Benchmark, Benchmark.Suite
+   * @param {String} [type] The event type.
+   * @param {Function} [listener] The function to unregister.
+   * @returns {Object} The benchmark instance.
+   * @example
+   *
+   * // unregister a listener for an event type
+   * bench.off('cycle', listener);
+   *
+   * // unregister a listener for multiple event types
+   * bench.off('start cycle', listener);
+   *
+   * // unregister all listeners for an event type
+   * bench.off('cycle');
+   *
+   * // unregister all listeners for multiple event types
+   * bench.off('start cycle complete');
+   *
+   * // unregister all listeners for all event types
+   * bench.off();
+   */
+  function off(type, listener) {
+    var me = this,
+        events = me.events;
+
+    events && each(type ? type.split(' ') : events, function(listeners, type) {
+      var index;
+      if (typeof listeners == 'string') {
+        type = listeners;
+        listeners = hasKey(events, type) && events[type];
+      }
+      if (listeners) {
+        if (listener) {
+          index = indexOf(listeners, listener);
+          if (index > -1) {
+            listeners.splice(index, 1);
+          }
+        } else {
+          listeners.length = 0;
+        }
+      }
+    });
+    return me;
+  }
+
+  /**
+   * Registers a listener for the specified event type(s).
+   *
+   * @memberOf Benchmark, Benchmark.Suite
+   * @param {String} type The event type.
+   * @param {Function} listener The function to register.
+   * @returns {Object} The benchmark instance.
+   * @example
+   *
+   * // register a listener for an event type
+   * bench.on('cycle', listener);
+   *
+   * // register a listener for multiple event types
+   * bench.on('start cycle', listener);
+   */
+  function on(type, listener) {
+    var me = this,
+        events = me.events || (me.events = {});
+
+    forEach(type.split(' '), function(type) {
+      (hasKey(events, type)
+        ? events[type]
+        : (events[type] = [])
+      ).push(listener);
+    });
+    return me;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Aborts the benchmark without recording times.
+   *
+   * @memberOf Benchmark
+   * @returns {Object} The benchmark instance.
+   */
+  function abort() {
+    var event,
+        me = this,
+        resetting = calledBy.reset;
+
+    if (me.running) {
+      event = Event('abort');
+      me.emit(event);
+      if (!event.cancelled || resetting) {
+        // avoid infinite recursion
+        calledBy.abort = true;
+        me.reset();
+        delete calledBy.abort;
+
+        if (support.timeout) {
+          clearTimeout(me._timerId);
+          delete me._timerId;
+        }
+        if (!resetting) {
+          me.aborted = true;
+          me.running = false;
+        }
+      }
+    }
+    return me;
+  }
+
+  /**
+   * Creates a new benchmark using the same test and options.
+   *
+   * @memberOf Benchmark
+   * @param {Object} options Options object to overwrite cloned options.
+   * @returns {Object} The new benchmark instance.
+   * @example
+   *
+   * var bizarro = bench.clone({
+   *   'name': 'doppelganger'
+   * });
+   */
+  function clone(options) {
+    var me = this,
+        result = new me.constructor(extend({}, me, options));
+
+    // correct the `options` object
+    result.options = extend({}, me.options, options);
+
+    // copy own custom properties
+    forOwn(me, function(value, key) {
+      if (!hasKey(result, key)) {
+        result[key] = deepClone(value);
+      }
+    });
+    return result;
+  }
+
+  /**
+   * Determines if a benchmark is faster than another.
+   *
+   * @memberOf Benchmark
+   * @param {Object} other The benchmark to compare.
+   * @returns {Number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate.
+   */
+  function compare(other) {
+    var critical,
+        zStat,
+        me = this,
+        sample1 = me.stats.sample,
+        sample2 = other.stats.sample,
+        size1 = sample1.length,
+        size2 = sample2.length,
+        maxSize = max(size1, size2),
+        minSize = min(size1, size2),
+        u1 = getU(sample1, sample2),
+        u2 = getU(sample2, sample1),
+        u = min(u1, u2);
+
+    function getScore(xA, sampleB) {
+      return reduce(sampleB, function(total, xB) {
+        return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5);
+      }, 0);
+    }
+
+    function getU(sampleA, sampleB) {
+      return reduce(sampleA, function(total, xA) {
+        return total + getScore(xA, sampleB);
+      }, 0);
+    }
+
+    function getZ(u) {
+      return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12);
+    }
+
+    // exit early if comparing the same benchmark
+    if (me == other) {
+      return 0;
+    }
+    // reject the null hyphothesis the two samples come from the
+    // same population (i.e. have the same median) if...
+    if (size1 + size2 > 30) {
+      // ...the z-stat is greater than 1.96 or less than -1.96
+      // http://www.statisticslectures.com/topics/mannwhitneyu/
+      zStat = getZ(u);
+      return abs(zStat) > 1.96 ? (zStat > 0 ? -1 : 1) : 0;
+    }
+    // ...the U value is less than or equal the critical U value
+    // http://www.geoib.com/mann-whitney-u-test.html
+    critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3];
+    return u <= critical ? (u == u1 ? 1 : -1) : 0;
+  }
+
+  /**
+   * Reset properties and abort if running.
+   *
+   * @memberOf Benchmark
+   * @returns {Object} The benchmark instance.
+   */
+  function reset() {
+    var data,
+        event,
+        me = this,
+        index = 0,
+        changes = { 'length': 0 },
+        queue = { 'length': 0 };
+
+    if (me.running && !calledBy.abort) {
+      // no worries, `reset()` is called within `abort()`
+      calledBy.reset = true;
+      me.abort();
+      delete calledBy.reset;
+    }
+    else {
+      // a non-recursive solution to check if properties have changed
+      // http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4
+      data = { 'destination': me, 'source': extend({}, me.constructor.prototype, me.options) };
+      do {
+        forOwn(data.source, function(value, key) {
+          var changed,
+              destination = data.destination,
+              currValue = destination[key];
+
+          if (value && typeof value == 'object') {
+            if (isClassOf(value, 'Array')) {
+              // check if an array value has changed to a non-array value
+              if (!isClassOf(currValue, 'Array')) {
+                changed = currValue = [];
+              }
+              // or has changed its length
+              if (currValue.length != value.length) {
+                changed = currValue = currValue.slice(0, value.length);
+                currValue.length = value.length;
+              }
+            }
+            // check if an object has changed to a non-object value
+            else if (!currValue || typeof currValue != 'object') {
+              changed = currValue = {};
+            }
+            // register a changed object
+            if (changed) {
+              changes[changes.length++] = { 'destination': destination, 'key': key, 'value': currValue };
+            }
+            queue[queue.length++] = { 'destination': currValue, 'source': value };
+          }
+          // register a changed primitive
+          else if (value !== currValue && !(value == null || isClassOf(value, 'Function'))) {
+            changes[changes.length++] = { 'destination': destination, 'key': key, 'value': value };
+          }
+        });
+      }
+      while ((data = queue[index++]));
+
+      // if changed emit the `reset` event and if it isn't cancelled reset the benchmark
+      if (changes.length && (me.emit(event = Event('reset')), !event.cancelled)) {
+        forEach(changes, function(data) {
+          data.destination[data.key] = data.value;
+        });
+      }
+    }
+    return me;
+  }
+
+  /**
+   * Displays relevant benchmark information when coerced to a string.
+   *
+   * @name toString
+   * @memberOf Benchmark
+   * @returns {String} A string representation of the benchmark instance.
+   */
+  function toStringBench() {
+    var me = this,
+        error = me.error,
+        hz = me.hz,
+        id = me.id,
+        stats = me.stats,
+        size = stats.sample.length,
+        pm = support.java ? '+/-' : '\xb1',
+        result = me.name || (isNaN(id) ? id : '<Test #' + id + '>');
+
+    if (error) {
+      result += ': ' + join(error);
+    } else {
+      result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm +
+        stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)';
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Clocks the time taken to execute a test per cycle (secs).
+   *
+   * @private
+   * @param {Object} bench The benchmark instance.
+   * @returns {Number} The time taken.
+   */
+  function clock() {
+    var applet,
+        options = Benchmark.options,
+        template = { 'begin': 's$=new n$', 'end': 'r$=(new n$-s$)/1e3', 'uid': uid },
+        timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }];
+
+    // lazy define for hi-res timers
+    clock = function(clone) {
+      var deferred;
+      if (clone instanceof Deferred) {
+        deferred = clone;
+        clone = deferred.benchmark;
+      }
+
+      var bench = clone._original,
+          fn = bench.fn,
+          fnArg = deferred ? getFirstArgument(fn) || 'deferred' : '',
+          stringable = isStringable(fn);
+
+      var source = {
+        'setup': getSource(bench.setup, preprocess('m$.setup()')),
+        'fn': getSource(fn, preprocess('m$.fn(' + fnArg + ')')),
+        'fnArg': fnArg,
+        'teardown': getSource(bench.teardown, preprocess('m$.teardown()'))
+      };
+
+      var count = bench.count = clone.count,
+          decompilable = support.decompilation || stringable,
+          id = bench.id,
+          isEmpty = !(source.fn || stringable),
+          name = bench.name || (typeof id == 'number' ? '<Test #' + id + '>' : id),
+          ns = timer.ns,
+          result = 0;
+
+      // init `minTime` if needed
+      clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime);
+
+      // repair nanosecond timer
+      // (some Chrome builds erase the `ns` variable after millions of executions)
+      if (applet) {
+        try {
+          ns.nanoTime();
+        } catch(e) {
+          // use non-element to avoid issues with libs that augment them
+          ns = timer.ns = new applet.Packages.nano;
+        }
+      }
+
+      // Compile in setup/teardown functions and the test loop.
+      // Create a new compiled test, instead of using the cached `bench.compiled`,
+      // to avoid potential engine optimizations enabled over the life of the test.
+      var compiled = bench.compiled = createFunction(preprocess('t$'), interpolate(
+        preprocess(deferred
+          ? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' +
+            // when `deferred.cycles` is `0` then...
+            'if(!d$.cycles){' +
+            // set `deferred.fn`
+            'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' +
+            // set `deferred.teardown`
+            'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' +
+            // execute the benchmark's `setup`
+            'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' +
+            // start timer
+            't$.start(d$);' +
+            // execute `deferred.fn` and return a dummy object
+            '}d$.fn();return{}'
+
+          : 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count,n$=t$.ns;#{setup}\n#{begin};' +
+            'while(i$--){#{fn}\n}#{end};#{teardown}\nreturn{elapsed:r$,uid:"#{uid}"}'),
+        source
+      ));
+
+      try {
+        if (isEmpty) {
+          // Firefox may remove dead code from Function#toString results
+          // http://bugzil.la/536085
+          throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.');
+        }
+        else if (!deferred) {
+          // pretest to determine if compiled code is exits early, usually by a
+          // rogue `return` statement, by checking for a return object with the uid
+          bench.count = 1;
+          compiled = (compiled.call(bench, timer) || {}).uid == uid && compiled;
+          bench.count = count;
+        }
+      } catch(e) {
+        compiled = null;
+        clone.error = e || new Error(String(e));
+        bench.count = count;
+      }
+      // fallback when a test exits early or errors during pretest
+      if (decompilable && !compiled && !deferred && !isEmpty) {
+        compiled = createFunction(preprocess('t$'), interpolate(
+          preprocess(
+            (clone.error && !stringable
+              ? 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count'
+              : 'function f$(){#{fn}\n}var r$,s$,m$=this,i$=m$.count'
+            ) +
+            ',n$=t$.ns;#{setup}\n#{begin};m$.f$=f$;while(i$--){m$.f$()}#{end};' +
+            'delete m$.f$;#{teardown}\nreturn{elapsed:r$}'
+          ),
+          source
+        ));
+
+        try {
+          // pretest one more time to check for errors
+          bench.count = 1;
+          compiled.call(bench, timer);
+          bench.compiled = compiled;
+          bench.count = count;
+          delete clone.error;
+        }
+        catch(e) {
+          bench.count = count;
+          if (clone.error) {
+            compiled = null;
+          } else {
+            bench.compiled = compiled;
+            clone.error = e || new Error(String(e));
+          }
+        }
+      }
+      // assign `compiled` to `clone` before calling in case a deferred benchmark
+      // immediately calls `deferred.resolve()`
+      clone.compiled = compiled;
+      // if no errors run the full test loop
+      if (!clone.error) {
+        result = compiled.call(deferred || bench, timer).elapsed;
+      }
+      return result;
+    };
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Gets the current timer's minimum resolution (secs).
+     */
+    function getRes(unit) {
+      var measured,
+          begin,
+          count = 30,
+          divisor = 1e3,
+          ns = timer.ns,
+          sample = [];
+
+      // get average smallest measurable time
+      while (count--) {
+        if (unit == 'us') {
+          divisor = 1e6;
+          if (ns.stop) {
+            ns.start();
+            while (!(measured = ns.microseconds())) { }
+          } else if (ns[perfName]) {
+            divisor = 1e3;
+            measured = Function('n', 'var r,s=n.' + perfName + '();while(!(r=n.' + perfName + '()-s)){};return r')(ns);
+          } else {
+            begin = ns();
+            while (!(measured = ns() - begin)) { }
+          }
+        }
+        else if (unit == 'ns') {
+          divisor = 1e9;
+          if (ns.nanoTime) {
+            begin = ns.nanoTime();
+            while (!(measured = ns.nanoTime() - begin)) { }
+          } else {
+            begin = (begin = ns())[0] + (begin[1] / divisor);
+            while (!(measured = ((measured = ns())[0] + (measured[1] / divisor)) - begin)) { }
+            divisor = 1;
+          }
+        }
+        else {
+          begin = new ns;
+          while (!(measured = new ns - begin)) { }
+        }
+        // check for broken timers (nanoTime may have issues)
+        // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/
+        if (measured > 0) {
+          sample.push(measured);
+        } else {
+          sample.push(Infinity);
+          break;
+        }
+      }
+      // convert to seconds
+      return getMean(sample) / divisor;
+    }
+
+    /**
+     * Replaces all occurrences of `$` with a unique number and
+     * template tokens with content.
+     */
+    function preprocess(code) {
+      return interpolate(code, template).replace(/\$/g, /\d+/.exec(uid));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // detect nanosecond support from a Java applet
+    each(doc && doc.applets || [], function(element) {
+      return !(timer.ns = applet = 'nanoTime' in element && element);
+    });
+
+    // check type in case Safari returns an object instead of a number
+    try {
+      if (typeof timer.ns.nanoTime() == 'number') {
+        timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' });
+      }
+    } catch(e) { }
+
+    // detect Chrome's microsecond timer:
+    // enable benchmarking via the --enable-benchmarking command
+    // line switch in at least Chrome 7 to use chrome.Interval
+    try {
+      if ((timer.ns = new (window.chrome || window.chromium).Interval)) {
+        timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
+      }
+    } catch(e) { }
+
+    // detect `performance.now` microsecond resolution timer
+    if ((timer.ns = perfName && perfObject)) {
+      timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
+    }
+
+    // detect Node's nanosecond resolution timer available in Node >= 0.8
+    if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') {
+      timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' });
+    }
+
+    // detect Wade Simmons' Node microtime module
+    if (microtimeObject && typeof (timer.ns = microtimeObject.now) == 'function') {
+      timers.push({ 'ns': timer.ns,  'res': getRes('us'), 'unit': 'us' });
+    }
+
+    // pick timer with highest resolution
+    timer = reduce(timers, function(timer, other) {
+      return other.res < timer.res ? other : timer;
+    });
+
+    // remove unused applet
+    if (timer.unit != 'ns' && applet) {
+      applet = destroyElement(applet);
+    }
+    // error if there are no working timers
+    if (timer.res == Infinity) {
+      throw new Error('Benchmark.js was unable to find a working timer.');
+    }
+    // use API of chosen timer
+    if (timer.unit == 'ns') {
+      if (timer.ns.nanoTime) {
+        extend(template, {
+          'begin': 's$=n$.nanoTime()',
+          'end': 'r$=(n$.nanoTime()-s$)/1e9'
+        });
+      } else {
+        extend(template, {
+          'begin': 's$=n$()',
+          'end': 'r$=n$(s$);r$=r$[0]+(r$[1]/1e9)'
+        });
+      }
+    }
+    else if (timer.unit == 'us') {
+      if (timer.ns.stop) {
+        extend(template, {
+          'begin': 's$=n$.start()',
+          'end': 'r$=n$.microseconds()/1e6'
+        });
+      } else if (perfName) {
+        extend(template, {
+          'begin': 's$=n$.' + perfName + '()',
+          'end': 'r$=(n$.' + perfName + '()-s$)/1e3'
+        });
+      } else {
+        extend(template, {
+          'begin': 's$=n$()',
+          'end': 'r$=(n$()-s$)/1e6'
+        });
+      }
+    }
+
+    // define `timer` methods
+    timer.start = createFunction(preprocess('o$'),
+      preprocess('var n$=this.ns,#{begin};o$.elapsed=0;o$.timeStamp=s$'));
+
+    timer.stop = createFunction(preprocess('o$'),
+      preprocess('var n$=this.ns,s$=o$.timeStamp,#{end};o$.elapsed=r$'));
+
+    // resolve time span required to achieve a percent uncertainty of at most 1%
+    // http://spiff.rit.edu/classes/phys273/uncert/uncert.html
+    options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05));
+    return clock.apply(null, arguments);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Computes stats on benchmark results.
+   *
+   * @private
+   * @param {Object} bench The benchmark instance.
+   * @param {Object} options The options object.
+   */
+  function compute(bench, options) {
+    options || (options = {});
+
+    var async = options.async,
+        elapsed = 0,
+        initCount = bench.initCount,
+        minSamples = bench.minSamples,
+        queue = [],
+        sample = bench.stats.sample;
+
+    /**
+     * Adds a clone to the queue.
+     */
+    function enqueue() {
+      queue.push(bench.clone({
+        '_original': bench,
+        'events': {
+          'abort': [update],
+          'cycle': [update],
+          'error': [update],
+          'start': [update]
+        }
+      }));
+    }
+
+    /**
+     * Updates the clone/original benchmarks to keep their data in sync.
+     */
+    function update(event) {
+      var clone = this,
+          type = event.type;
+
+      if (bench.running) {
+        if (type == 'start') {
+          // Note: `clone.minTime` prop is inited in `clock()`
+          clone.count = bench.initCount;
+        }
+        else {
+          if (type == 'error') {
+            bench.error = clone.error;
+          }
+          if (type == 'abort') {
+            bench.abort();
+            bench.emit('cycle');
+          } else {
+            event.currentTarget = event.target = bench;
+            bench.emit(event);
+          }
+        }
+      } else if (bench.aborted) {
+        // clear abort listeners to avoid triggering bench's abort/cycle again
+        clone.events.abort.length = 0;
+        clone.abort();
+      }
+    }
+
+    /**
+     * Determines if more clones should be queued or if cycling should stop.
+     */
+    function evaluate(event) {
+      var critical,
+          df,
+          mean,
+          moe,
+          rme,
+          sd,
+          sem,
+          variance,
+          clone = event.target,
+          done = bench.aborted,
+          now = +new Date,
+          size = sample.push(clone.times.period),
+          maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime,
+          times = bench.times,
+          varOf = function(sum, x) { return sum + pow(x - mean, 2); };
+
+      // exit early for aborted or unclockable tests
+      if (done || clone.hz == Infinity) {
+        maxedOut = !(size = sample.length = queue.length = 0);
+      }
+
+      if (!done) {
+        // sample mean (estimate of the population mean)
+        mean = getMean(sample);
+        // sample variance (estimate of the population variance)
+        variance = reduce(sample, varOf, 0) / (size - 1) || 0;
+        // sample standard deviation (estimate of the population standard deviation)
+        sd = sqrt(variance);
+        // standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean)
+        sem = sd / sqrt(size);
+        // degrees of freedom
+        df = size - 1;
+        // critical value
+        critical = tTable[Math.round(df) || 1] || tTable.infinity;
+        // margin of error
+        moe = sem * critical;
+        // relative margin of error
+        rme = (moe / mean) * 100 || 0;
+
+        extend(bench.stats, {
+          'deviation': sd,
+          'mean': mean,
+          'moe': moe,
+          'rme': rme,
+          'sem': sem,
+          'variance': variance
+        });
+
+        // Abort the cycle loop when the minimum sample size has been collected
+        // and the elapsed time exceeds the maximum time allowed per benchmark.
+        // We don't count cycle delays toward the max time because delays may be
+        // increased by browsers that clamp timeouts for inactive tabs.
+        // https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs
+        if (maxedOut) {
+          // reset the `initCount` in case the benchmark is rerun
+          bench.initCount = initCount;
+          bench.running = false;
+          done = true;
+          times.elapsed = (now - times.timeStamp) / 1e3;
+        }
+        if (bench.hz != Infinity) {
+          bench.hz = 1 / mean;
+          times.cycle = mean * bench.count;
+          times.period = mean;
+        }
+      }
+      // if time permits, increase sample size to reduce the margin of error
+      if (queue.length < 2 && !maxedOut) {
+        enqueue();
+      }
+      // abort the invoke cycle when done
+      event.aborted = done;
+    }
+
+    // init queue and begin
+    enqueue();
+    invoke(queue, {
+      'name': 'run',
+      'args': { 'async': async },
+      'queued': true,
+      'onCycle': evaluate,
+      'onComplete': function() { bench.emit('complete'); }
+    });
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Cycles a benchmark until a run `count` can be established.
+   *
+   * @private
+   * @param {Object} clone The cloned benchmark instance.
+   * @param {Object} options The options object.
+   */
+  function cycle(clone, options) {
+    options || (options = {});
+
+    var deferred;
+    if (clone instanceof Deferred) {
+      deferred = clone;
+      clone = clone.benchmark;
+    }
+
+    var clocked,
+        cycles,
+        divisor,
+        event,
+        minTime,
+        period,
+        async = options.async,
+        bench = clone._original,
+        count = clone.count,
+        times = clone.times;
+
+    // continue, if not aborted between cycles
+    if (clone.running) {
+      // `minTime` is set to `Benchmark.options.minTime` in `clock()`
+      cycles = ++clone.cycles;
+      clocked = deferred ? deferred.elapsed : clock(clone);
+      minTime = clone.minTime;
+
+      if (cycles > bench.cycles) {
+        bench.cycles = cycles;
+      }
+      if (clone.error) {
+        event = Event('error');
+        event.message = clone.error;
+        clone.emit(event);
+        if (!event.cancelled) {
+          clone.abort();
+        }
+      }
+    }
+
+    // continue, if not errored
+    if (clone.running) {
+      // time taken to complete last test cycle
+      bench.times.cycle = times.cycle = clocked;
+      // seconds per operation
+      period = bench.times.period = times.period = clocked / count;
+      // ops per second
+      bench.hz = clone.hz = 1 / period;
+      // avoid working our way up to this next time
+      bench.initCount = clone.initCount = count;
+      // do we need to do another cycle?
+      clone.running = clocked < minTime;
+
+      if (clone.running) {
+        // tests may clock at `0` when `initCount` is a small number,
+        // to avoid that we set its count to something a bit higher
+        if (!clocked && (divisor = divisors[clone.cycles]) != null) {
+          count = floor(4e6 / divisor);
+        }
+        // calculate how many more iterations it will take to achive the `minTime`
+        if (count <= clone.count) {
+          count += Math.ceil((minTime - clocked) / period);
+        }
+        clone.running = count != Infinity;
+      }
+    }
+    // should we exit early?
+    event = Event('cycle');
+    clone.emit(event);
+    if (event.aborted) {
+      clone.abort();
+    }
+    // figure out what to do next
+    if (clone.running) {
+      // start a new cycle
+      clone.count = count;
+      if (deferred) {
+        clone.compiled.call(deferred, timer);
+      } else if (async) {
+        delay(clone, function() { cycle(clone, options); });
+      } else {
+        cycle(clone);
+      }
+    }
+    else {
+      // fix TraceMonkey bug associated with clock fallbacks
+      // http://bugzil.la/509069
+      if (support.browser) {
+        runScript(uid + '=1;delete ' + uid);
+      }
+      // done
+      clone.emit('complete');
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Runs the benchmark.
+   *
+   * @memberOf Benchmark
+   * @param {Object} [options={}] Options object.
+   * @returns {Object} The benchmark instance.
+   * @example
+   *
+   * // basic usage
+   * bench.run();
+   *
+   * // or with options
+   * bench.run({ 'async': true });
+   */
+  function run(options) {
+    var me = this,
+        event = Event('start');
+
+    // set `running` to `false` so `reset()` won't call `abort()`
+    me.running = false;
+    me.reset();
+    me.running = true;
+
+    me.count = me.initCount;
+    me.times.timeStamp = +new Date;
+    me.emit(event);
+
+    if (!event.cancelled) {
+      options = { 'async': ((options = options && options.async) == null ? me.async : options) && support.timeout };
+
+      // for clones created within `compute()`
+      if (me._original) {
+        if (me.defer) {
+          Deferred(me);
+        } else {
+          cycle(me, options);
+        }
+      }
+      // for original benchmarks
+      else {
+        compute(me, options);
+      }
+    }
+    return me;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // Firefox 1 erroneously defines variable and argument names of functions on
+  // the function itself as non-configurable properties with `undefined` values.
+  // The bugginess continues as the `Benchmark` constructor has an argument
+  // named `options` and Firefox 1 will not assign a value to `Benchmark.options`,
+  // making it non-writable in the process, unless it is the first property
+  // assigned by for-in loop of `extend()`.
+  extend(Benchmark, {
+
+    /**
+     * The default options copied by benchmark instances.
+     *
+     * @static
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'options': {
+
+      /**
+       * A flag to indicate that benchmark cycles will execute asynchronously
+       * by default.
+       *
+       * @memberOf Benchmark.options
+       * @type Boolean
+       */
+      'async': false,
+
+      /**
+       * A flag to indicate that the benchmark clock is deferred.
+       *
+       * @memberOf Benchmark.options
+       * @type Boolean
+       */
+      'defer': false,
+
+      /**
+       * The delay between test cycles (secs).
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'delay': 0.005,
+
+      /**
+       * Displayed by Benchmark#toString when a `name` is not available
+       * (auto-generated if absent).
+       *
+       * @memberOf Benchmark.options
+       * @type String
+       */
+      'id': undefined,
+
+      /**
+       * The default number of times to execute a test on a benchmark's first cycle.
+       *
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'initCount': 1,
+
+      /**
+       * The maximum time a benchmark is allowed to run before finishing (secs).
+       *
+       * Note: Cycle delays aren't counted toward the maximum time.
+       *
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'maxTime': 5,
+
+      /**
+       * The minimum sample size required to perform statistical analysis.
+       *
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'minSamples': 5,
+
+      /**
+       * The time needed to reduce the percent uncertainty of measurement to 1% (secs).
+       *
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'minTime': 0,
+
+      /**
+       * The name of the benchmark.
+       *
+       * @memberOf Benchmark.options
+       * @type String
+       */
+      'name': undefined,
+
+      /**
+       * An event listener called when the benchmark is aborted.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onAbort': undefined,
+
+      /**
+       * An event listener called when the benchmark completes running.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onComplete': undefined,
+
+      /**
+       * An event listener called after each run cycle.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onCycle': undefined,
+
+      /**
+       * An event listener called when a test errors.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onError': undefined,
+
+      /**
+       * An event listener called when the benchmark is reset.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onReset': undefined,
+
+      /**
+       * An event listener called when the benchmark starts running.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onStart': undefined
+    },
+
+    /**
+     * Platform object with properties describing things like browser name,
+     * version, and operating system.
+     *
+     * @static
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'platform': req('platform') || window.platform || {
+
+      /**
+       * The platform description.
+       *
+       * @memberOf Benchmark.platform
+       * @type String
+       */
+      'description': window.navigator && navigator.userAgent || null,
+
+      /**
+       * The name of the browser layout engine.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'layout': null,
+
+      /**
+       * The name of the product hosting the browser.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'product': null,
+
+      /**
+       * The name of the browser/environment.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'name': null,
+
+      /**
+       * The name of the product's manufacturer.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'manufacturer': null,
+
+      /**
+       * The name of the operating system.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'os': null,
+
+      /**
+       * The alpha/beta release indicator.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'prerelease': null,
+
+      /**
+       * The browser/environment version.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'version': null,
+
+      /**
+       * Return platform description when the platform object is coerced to a string.
+       *
+       * @memberOf Benchmark.platform
+       * @type Function
+       * @returns {String} The platform description.
+       */
+      'toString': function() {
+        return this.description || '';
+      }
+    },
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf Benchmark
+     * @type String
+     */
+    'version': '1.0.0',
+
+    // an object of environment/feature detection flags
+    'support': support,
+
+    // clone objects
+    'deepClone': deepClone,
+
+    // iteration utility
+    'each': each,
+
+    // augment objects
+    'extend': extend,
+
+    // generic Array#filter
+    'filter': filter,
+
+    // generic Array#forEach
+    'forEach': forEach,
+
+    // generic own property iteration utility
+    'forOwn': forOwn,
+
+    // converts a number to a comma-separated string
+    'formatNumber': formatNumber,
+
+    // generic Object#hasOwnProperty
+    // (trigger hasKey's lazy define before assigning it to Benchmark)
+    'hasKey': (hasKey(Benchmark, ''), hasKey),
+
+    // generic Array#indexOf
+    'indexOf': indexOf,
+
+    // template utility
+    'interpolate': interpolate,
+
+    // invokes a method on each item in an array
+    'invoke': invoke,
+
+    // generic Array#join for arrays and objects
+    'join': join,
+
+    // generic Array#map
+    'map': map,
+
+    // retrieves a property value from each item in an array
+    'pluck': pluck,
+
+    // generic Array#reduce
+    'reduce': reduce
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  extend(Benchmark.prototype, {
+
+    /**
+     * The number of times a test was executed.
+     *
+     * @memberOf Benchmark
+     * @type Number
+     */
+    'count': 0,
+
+    /**
+     * The number of cycles performed while benchmarking.
+     *
+     * @memberOf Benchmark
+     * @type Number
+     */
+    'cycles': 0,
+
+    /**
+     * The number of executions per second.
+     *
+     * @memberOf Benchmark
+     * @type Number
+     */
+    'hz': 0,
+
+    /**
+     * The compiled test function.
+     *
+     * @memberOf Benchmark
+     * @type Function|String
+     */
+    'compiled': undefined,
+
+    /**
+     * The error object if the test failed.
+     *
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'error': undefined,
+
+    /**
+     * The test to benchmark.
+     *
+     * @memberOf Benchmark
+     * @type Function|String
+     */
+    'fn': undefined,
+
+    /**
+     * A flag to indicate if the benchmark is aborted.
+     *
+     * @memberOf Benchmark
+     * @type Boolean
+     */
+    'aborted': false,
+
+    /**
+     * A flag to indicate if the benchmark is running.
+     *
+     * @memberOf Benchmark
+     * @type Boolean
+     */
+    'running': false,
+
+    /**
+     * Compiled into the test and executed immediately **before** the test loop.
+     *
+     * @memberOf Benchmark
+     * @type Function|String
+     * @example
+     *
+     * // basic usage
+     * var bench = Benchmark({
+     *   'setup': function() {
+     *     var c = this.count,
+     *         element = document.getElementById('container');
+     *     while (c--) {
+     *       element.appendChild(document.createElement('div'));
+     *     }
+     *   },
+     *   'fn': function() {
+     *     element.removeChild(element.lastChild);
+     *   }
+     * });
+     *
+     * // compiles to something like:
+     * var c = this.count,
+     *     element = document.getElementById('container');
+     * while (c--) {
+     *   element.appendChild(document.createElement('div'));
+     * }
+     * var start = new Date;
+     * while (count--) {
+     *   element.removeChild(element.lastChild);
+     * }
+     * var end = new Date - start;
+     *
+     * // or using strings
+     * var bench = Benchmark({
+     *   'setup': '\
+     *     var a = 0;\n\
+     *     (function() {\n\
+     *       (function() {\n\
+     *         (function() {',
+     *   'fn': 'a += 1;',
+     *   'teardown': '\
+     *          }())\n\
+     *        }())\n\
+     *      }())'
+     * });
+     *
+     * // compiles to something like:
+     * var a = 0;
+     * (function() {
+     *   (function() {
+     *     (function() {
+     *       var start = new Date;
+     *       while (count--) {
+     *         a += 1;
+     *       }
+     *       var end = new Date - start;
+     *     }())
+     *   }())
+     * }())
+     */
+    'setup': noop,
+
+    /**
+     * Compiled into the test and executed immediately **after** the test loop.
+     *
+     * @memberOf Benchmark
+     * @type Function|String
+     */
+    'teardown': noop,
+
+    /**
+     * An object of stats including mean, margin or error, and standard deviation.
+     *
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'stats': {
+
+      /**
+       * The margin of error.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'moe': 0,
+
+      /**
+       * The relative margin of error (expressed as a percentage of the mean).
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'rme': 0,
+
+      /**
+       * The standard error of the mean.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'sem': 0,
+
+      /**
+       * The sample standard deviation.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'deviation': 0,
+
+      /**
+       * The sample arithmetic mean.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'mean': 0,
+
+      /**
+       * The array of sampled periods.
+       *
+       * @memberOf Benchmark#stats
+       * @type Array
+       */
+      'sample': [],
+
+      /**
+       * The sample variance.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'variance': 0
+    },
+
+    /**
+     * An object of timing data including cycle, elapsed, period, start, and stop.
+     *
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'times': {
+
+      /**
+       * The time taken to complete the last cycle (secs).
+       *
+       * @memberOf Benchmark#times
+       * @type Number
+       */
+      'cycle': 0,
+
+      /**
+       * The time taken to complete the benchmark (secs).
+       *
+       * @memberOf Benchmark#times
+       * @type Number
+       */
+      'elapsed': 0,
+
+      /**
+       * The time taken to execute the test once (secs).
+       *
+       * @memberOf Benchmark#times
+       * @type Number
+       */
+      'period': 0,
+
+      /**
+       * A timestamp of when the benchmark started (ms).
+       *
+       * @memberOf Benchmark#times
+       * @type Number
+       */
+      'timeStamp': 0
+    },
+
+    // aborts benchmark (does not record times)
+    'abort': abort,
+
+    // creates a new benchmark using the same test and options
+    'clone': clone,
+
+    // compares benchmark's hertz with another
+    'compare': compare,
+
+    // executes listeners
+    'emit': emit,
+
+    // get listeners
+    'listeners': listeners,
+
+    // unregister listeners
+    'off': off,
+
+    // register listeners
+    'on': on,
+
+    // reset benchmark properties
+    'reset': reset,
+
+    // runs the benchmark
+    'run': run,
+
+    // pretty print benchmark info
+    'toString': toStringBench
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  extend(Deferred.prototype, {
+
+    /**
+     * The deferred benchmark instance.
+     *
+     * @memberOf Benchmark.Deferred
+     * @type Object
+     */
+    'benchmark': null,
+
+    /**
+     * The number of deferred cycles performed while benchmarking.
+     *
+     * @memberOf Benchmark.Deferred
+     * @type Number
+     */
+    'cycles': 0,
+
+    /**
+     * The time taken to complete the deferred benchmark (secs).
+     *
+     * @memberOf Benchmark.Deferred
+     * @type Number
+     */
+    'elapsed': 0,
+
+    /**
+     * A timestamp of when the deferred benchmark started (ms).
+     *
+     * @memberOf Benchmark.Deferred
+     * @type Number
+     */
+    'timeStamp': 0,
+
+    // cycles/completes the deferred benchmark
+    'resolve': resolve
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  extend(Event.prototype, {
+
+    /**
+     * A flag to indicate if the emitters listener iteration is aborted.
+     *
+     * @memberOf Benchmark.Event
+     * @type Boolean
+     */
+    'aborted': false,
+
+    /**
+     * A flag to indicate if the default action is cancelled.
+     *
+     * @memberOf Benchmark.Event
+     * @type Boolean
+     */
+    'cancelled': false,
+
+    /**
+     * The object whose listeners are currently being processed.
+     *
+     * @memberOf Benchmark.Event
+     * @type Object
+     */
+    'currentTarget': undefined,
+
+    /**
+     * The return value of the last executed listener.
+     *
+     * @memberOf Benchmark.Event
+     * @type Mixed
+     */
+    'result': undefined,
+
+    /**
+     * The object to which the event was originally emitted.
+     *
+     * @memberOf Benchmark.Event
+     * @type Object
+     */
+    'target': undefined,
+
+    /**
+     * A timestamp of when the event was created (ms).
+     *
+     * @memberOf Benchmark.Event
+     * @type Number
+     */
+    'timeStamp': 0,
+
+    /**
+     * The event type.
+     *
+     * @memberOf Benchmark.Event
+     * @type String
+     */
+    'type': ''
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The default options copied by suite instances.
+   *
+   * @static
+   * @memberOf Benchmark.Suite
+   * @type Object
+   */
+  Suite.options = {
+
+    /**
+     * The name of the suite.
+     *
+     * @memberOf Benchmark.Suite.options
+     * @type String
+     */
+    'name': undefined
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  extend(Suite.prototype, {
+
+    /**
+     * The number of benchmarks in the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @type Number
+     */
+    'length': 0,
+
+    /**
+     * A flag to indicate if the suite is aborted.
+     *
+     * @memberOf Benchmark.Suite
+     * @type Boolean
+     */
+    'aborted': false,
+
+    /**
+     * A flag to indicate if the suite is running.
+     *
+     * @memberOf Benchmark.Suite
+     * @type Boolean
+     */
+    'running': false,
+
+    /**
+     * An `Array#forEach` like method.
+     * Callbacks may terminate the loop by explicitly returning `false`.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Function} callback The function called per iteration.
+     * @returns {Object} The suite iterated over.
+     */
+    'forEach': methodize(forEach),
+
+    /**
+     * An `Array#indexOf` like method.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Mixed} value The value to search for.
+     * @returns {Number} The index of the matched value or `-1`.
+     */
+    'indexOf': methodize(indexOf),
+
+    /**
+     * Invokes a method on all benchmarks in the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {String|Object} name The name of the method to invoke OR options object.
+     * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+     * @returns {Array} A new array of values returned from each method invoked.
+     */
+    'invoke': methodize(invoke),
+
+    /**
+     * Converts the suite of benchmarks to a string.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {String} [separator=','] A string to separate each element of the array.
+     * @returns {String} The string.
+     */
+    'join': [].join,
+
+    /**
+     * An `Array#map` like method.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Function} callback The function called per iteration.
+     * @returns {Array} A new array of values returned by the callback.
+     */
+    'map': methodize(map),
+
+    /**
+     * Retrieves the value of a specified property from all benchmarks in the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {String} property The property to pluck.
+     * @returns {Array} A new array of property values.
+     */
+    'pluck': methodize(pluck),
+
+    /**
+     * Removes the last benchmark from the suite and returns it.
+     *
+     * @memberOf Benchmark.Suite
+     * @returns {Mixed} The removed benchmark.
+     */
+    'pop': [].pop,
+
+    /**
+     * Appends benchmarks to the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @returns {Number} The suite's new length.
+     */
+    'push': [].push,
+
+    /**
+     * Sorts the benchmarks of the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Function} [compareFn=null] A function that defines the sort order.
+     * @returns {Object} The sorted suite.
+     */
+    'sort': [].sort,
+
+    /**
+     * An `Array#reduce` like method.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Function} callback The function called per iteration.
+     * @param {Mixed} accumulator Initial value of the accumulator.
+     * @returns {Mixed} The accumulator.
+     */
+    'reduce': methodize(reduce),
+
+    // aborts all benchmarks in the suite
+    'abort': abortSuite,
+
+    // adds a benchmark to the suite
+    'add': add,
+
+    // creates a new suite with cloned benchmarks
+    'clone': cloneSuite,
+
+    // executes listeners of a specified type
+    'emit': emit,
+
+    // creates a new suite of filtered benchmarks
+    'filter': filterSuite,
+
+    // get listeners
+    'listeners': listeners,
+
+    // unregister listeners
+    'off': off,
+
+   // register listeners
+    'on': on,
+
+    // resets all benchmarks in the suite
+    'reset': resetSuite,
+
+    // runs all benchmarks in the suite
+    'run': runSuite,
+
+    // array methods
+    'concat': concat,
+
+    'reverse': reverse,
+
+    'shift': shift,
+
+    'slice': slice,
+
+    'splice': splice,
+
+    'unshift': unshift
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose Deferred, Event and Suite
+  extend(Benchmark, {
+    'Deferred': Deferred,
+    'Event': Event,
+    'Suite': Suite
+  });
+
+  // expose Benchmark
+  // some AMD build optimizers, like r.js, check for specific condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // define as an anonymous module so, through path mapping, it can be aliased
+    define(function() {
+      return Benchmark;
+    });
+  }
+  // check for `exports` after `define` in case a build optimizer adds an `exports` object
+  else if (freeExports) {
+    // in Node.js or RingoJS v0.8.0+
+    if (typeof module == 'object' && module && module.exports == freeExports) {
+      (module.exports = Benchmark).Benchmark = Benchmark;
+    }
+    // in Narwhal or RingoJS v0.7.0-
+    else {
+      freeExports.Benchmark = Benchmark;
+    }
+  }
+  // in a browser or Rhino
+  else {
+    // use square bracket notation so Closure Compiler won't munge `Benchmark`
+    // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
+    window['Benchmark'] = Benchmark;
+  }
+
+  // trigger clock's lazy define early to avoid a security error
+  if (support.air) {
+    clock({ '_original': { 'fn': noop, 'count': 1, 'options': {} } });
+  }
+}(this));
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_float32array.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_float32array.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_float32array.html	(revision 28897)
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ThreeJS Benchmark Tests - Using Files in /src</title>
+</head>
+<body>
+
+  During this Benchmarking test the browser will be unresponsive.<br/><br/>
+
+  Benchmark output is written to the JavaScript console.  To access the JavaScript console presss Ctrl-Shift-J.
+
+  <script src="benchmark-1.0.0.js"></script>
+
+  <!-- add ThreeJS sources to test below -->
+
+  <script src="../../src/Three.js"></script>
+  <script src="../../src/math/Vector2.js"></script>
+  <script src="../../src/math/Vector3.js"></script>
+  <script src="../../src/math/Vector4.js"></script>
+  <script src="../../src/math/Box2.js"></script>
+  <script src="../../src/math/Box3.js"></script>
+  <script src="../../src/math/Plane.js"></script>
+  <script src="../../src/math/Ray.js"></script>
+  <script src="../../src/math/Sphere.js"></script>
+  <script src="../../src/math/Triangle.js"></script>
+  <script src="../../src/math/Matrix3.js"></script>
+  <script src="../../src/math/Matrix4.js"></script>
+  <script src="../../src/math/Color.js"></script>
+
+  <!-- add class-based unit tests below -->
+
+  <script src="core/Float32Array.js"></script>
+  
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3components.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3components.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3components.html	(revision 28897)
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ThreeJS Benchmark Tests - Using Files in /src</title>
+</head>
+<body>
+
+  During this Benchmarking test the browser will be unresponsive.<br/><br/>
+
+  Benchmark output is written to the JavaScript console.  To access the JavaScript console presss Ctrl-Shift-J.
+
+  <script src="benchmark-1.0.0.js"></script>
+
+  <!-- add class-based unit tests below -->
+
+  <script src="core/Vector3Components.js"></script>
+  
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3length.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3length.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3length.html	(revision 28897)
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ThreeJS Benchmark Tests - Using Files in /src</title>
+</head>
+<body>
+
+  During this Benchmarking test the browser will be unresponsive.<br/><br/>
+
+  Benchmark output is written to the JavaScript console.  To access the JavaScript console presss Ctrl-Shift-J.
+
+  <script src="benchmark-1.0.0.js"></script>
+
+  <!-- add class-based unit tests below -->
+
+  <script src="core/Vector3Length.js"></script>
+  
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3storage.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3storage.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/benchmarking_vector3storage.html	(revision 28897)
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ThreeJS Benchmark Tests - Using Files in /src</title>
+</head>
+<body>
+
+  During this Benchmarking test the browser will be unresponsive.<br/><br/>
+
+  Benchmark output is written to the JavaScript console.  To access the JavaScript console presss Ctrl-Shift-J.
+
+  <script src="benchmark-1.0.0.js"></script>
+
+  <!-- add class-based unit tests below -->
+
+  <script src="core/Vector3Storage.js"></script>
+  
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Float32Array.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Float32Array.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Float32Array.js	(revision 28897)
@@ -0,0 +1,108 @@
+
+var input = new Float32Array( 10000 * 3 );
+var output = new Float32Array( 10000 * 3 );
+
+for( var j = 0, jl = input.length; j < jl; j ++ ) {
+	input[j] = j;
+}
+
+var inputVectors = [];
+var outputVectors = [];
+
+for( var j = 0, jl = input.length/3; j < jl; j ++ ) {
+	inputVectors.push( new THREE.Vector3( j*3, j*3+1, j*3+2 ) );
+	outputVectors.push( new THREE.Vector3() );
+}
+
+var suite = new Benchmark.Suite;
+
+suite.add('Float32Array-Float32Array', function() {
+	var value3 = new Float32Array( 3 );
+	for (var i = 0, il = input.length / 3; i < il; i += 3) {
+	  value3[0] = input[i + 0];
+	  value3[1] = input[i + 1];
+	  value3[2] = input[i + 2];
+	  value3[0] *= 1.01;
+	  value3[1] *= 1.03;
+	  value3[2] *= 0.98;
+	  output[i + 0] = value3[0];
+	  output[i + 1] = value3[1];
+	  output[i + 2] = value3[2];
+	}
+});
+
+suite.add('Float32Array-Array', function() {
+	var value2 = [0,0,0];
+	for (var i = 0, il = input.length / 3; i < il; i += 3) {
+	  value2[0] = input[i + 0];
+	  value2[1] = input[i + 1];
+	  value2[2] = input[i + 2];
+	  value2[0] *= 1.01;
+	  value2[1] *= 1.03;
+	  value2[2] *= 0.98;
+	  output[i + 0] = value2[0];
+	  output[i + 1] = value2[1];
+	  output[i + 2] = value2[2];
+	}
+});
+
+suite.add('Float32Array-Literal', function() {
+	var x, y, z;
+	for (var i = 0, il = input.length / 3; i < il; i += 3) {
+	  x = input[i + 0];
+	  y = input[i + 1];
+	  z = input[i + 2];
+	  x *= 1.01;
+	  y *= 1.03;
+	  z *= 0.98;
+	  output[i + 0] = x;
+	  output[i + 1] = y;
+	  output[i + 2] = z;
+	}
+});
+
+suite.add('Float32Array-Vector3', function() {
+	var value = new THREE.Vector3();
+	for (var i = 0, il = input.length / 3; i < il; i += 3) {
+	  value.x = input[i + 0];
+	  value.y = input[i + 1];
+	  value.z = input[i + 2];
+	  value.x *= 1.01;
+	  value.y *= 1.03;
+	  value.z *= 0.98;
+	  output[i + 0] = value.x;
+	  output[i + 1] = value.y;
+	  output[i + 2] = value.z;
+	}
+});
+
+suite.add('Vector3Array-Direct', function() {
+	for (var i = 0, il = inputVectors.length; i < il; i ++ ) {
+		outputVectors[i].copy( inputVectors[i] );
+		outputVectors[i].x *= 1.01;
+		outputVectors[i].y *= 1.03;
+		outputVectors[i].z *= 0.98;
+	}
+});
+
+suite.add('Vector3Array-Vector3', function() {
+	var value = new THREE.Vector3();
+	for (var i = 0, il = inputVectors.length; i < il; i ++ ) {
+	  value.copy( inputVectors[i] );
+	  value.x *= 1.01;
+	  value.y *= 1.03;
+	  value.z *= 0.98;
+	  outputVectors[i].copy( value );
+	}
+});
+
+suite.on('cycle', function(event, bench) {
+  console.log(String(event.target));
+});
+
+suite.on('complete', function() {
+  console.log('Fastest is ' + this.filter('fastest').pluck('name'));
+  console.log( "Done" );
+});
+
+suite.run(true);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Components.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Components.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Components.js	(revision 28897)
@@ -0,0 +1,149 @@
+THREE = {};
+    
+THREE.Vector3 = function ( x, y, z ) {
+
+    this.x = x || 0;
+    this.y = y || 0;
+    this.z = z || 0;
+
+};
+
+THREE.Vector3.prototype = {
+
+    constructor: THREE.Vector3,
+ 
+    setComponent: function ( index, value ) {
+
+        this[ THREE.Vector3.__indexToName[ index ] ] = value;
+        
+    },
+
+    getComponent: function ( index ) {
+
+        return this[ THREE.Vector3.__indexToName[ index ] ];
+
+    }, 
+
+
+
+    setComponent2: 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 );
+
+        }
+
+    },
+
+    getComponent2: 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 );
+
+        }
+
+    },
+
+
+    getComponent3: function ( index ) {
+
+        if ( index === 0 ) return this.x;
+        if ( index === 1 ) return this.y;
+        if ( index === 2 ) return this.z;
+
+        throw new Error( "index is out of range: " + index );
+
+    },
+
+    getComponent4: function ( index ) {
+
+        if ( index === 0 ) return this.x;
+        else if ( index === 1 ) return this.y;
+        else if ( index === 2 ) return this.z;
+        else throw new Error( "index is out of range: " + index );
+
+    }
+};
+
+
+THREE.Vector3.__indexToName = {
+  0: 'x',
+  1: 'y',
+  2: 'z'
+};
+
+var a = [];
+
+for ( var i = 0; i < 100000; i ++ ) {
+    
+    a[ i ] = new THREE.Vector3( i * 0.01, i * 2, i * -1.3 );
+    
+}
+
+
+var suite = new Benchmark.Suite;
+
+suite.add('IndexToName', function() {
+
+    var result = 0;
+
+    for ( var i = 0; i < 100000; i ++ ) {
+        result += a[i].getComponent( i % 3 );
+    }
+
+});
+
+suite.add('SwitchStatement', function() {
+
+    var result = 0;
+
+    for ( var i = 0; i < 100000; i ++ ) {
+
+        result += a[i].getComponent2( i % 3 );
+
+    }
+
+});
+
+suite.add('IfAndReturnSeries', function() {
+
+    var result = 0;
+
+    for ( var i = 0; i < 100000; i ++ ) {
+
+        result += a[i].getComponent3( i % 3 );
+
+    }
+
+});
+
+suite.add('IfReturnElseSeries', function() {
+
+    var result = 0;
+
+    for ( var i = 0; i < 100000; i ++ ) {
+
+        result += a[i].getComponent4( i % 3 );
+
+    }
+
+});
+
+suite.on('cycle', function(event, bench) {
+  console.log(String(event.target));
+});
+
+suite.on('complete', function() {
+  console.log('Fastest is ' + this.filter('fastest').pluck('name'));
+  console.log( "Done" );
+});
+
+suite.run(true);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Length.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Length.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Length.js	(revision 28897)
@@ -0,0 +1,88 @@
+THREE = {};
+    
+THREE.Vector3 = function ( x, y, z ) {
+
+    this.x = x || 0;
+    this.y = y || 0;
+    this.z = z || 0;
+
+};
+
+THREE.Vector3.prototype = {
+
+    constructor: THREE.Vector3,
+    
+    lengthSq: function () {
+
+            return this.x * this.x + this.y * this.y + this.z * this.z;
+
+    },
+
+    length: function () {
+
+            return Math.sqrt( this.lengthSq() );
+
+    },
+
+    length2: function () {
+
+            return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+    }
+    
+};
+
+var a = [];
+
+for ( var i = 0; i < 100000; i ++ ) {
+    
+    a[ i ] = new THREE.Vector3( i * 0.01, i * 2, i * -1.3 );
+    
+}
+
+
+var suite = new Benchmark.Suite;
+
+suite.add('NoCallTest', function() {
+
+    var result = 0;
+
+    for ( var i = 0; i < 100000; i ++ ) {
+        var v = a[i];
+        result += Math.sqrt( v.x * v.x + v.y * v.y + v.z * v.z );
+    }
+
+});
+
+suite.add('InlineCallTest', function() {
+
+    var result = 0;
+
+    for ( var i = 0; i < 100000; i ++ ) {
+
+        result += a[ i ].length2();
+
+    }
+
+});
+
+suite.add('FunctionCallTest', function() {
+    var result = 0;
+
+    for ( var i = 0; i < 100000; i ++ ) {
+
+        result += a[ i ].length();
+
+    }
+});
+
+suite.on('cycle', function(event, bench) {
+  console.log(String(event.target));
+});
+
+suite.on('complete', function() {
+  console.log('Fastest is ' + this.filter('fastest').pluck('name'));
+  console.log( "Done" );
+});
+
+suite.run(true);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Storage.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Storage.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/Vector3Storage.js	(revision 28897)
@@ -0,0 +1,120 @@
+THREE = {};
+    
+THREE.Vector3 = function ( x, y, z ) {
+
+    this.x = x || 0;
+    this.y = y || 0;
+    this.z = z || 0;
+
+};
+
+THREE.Vector3.prototype = {
+
+    constructor: THREE.Vector3,
+    
+    length: function () {
+
+            return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+    }
+    
+};
+
+THREE.Vector3X = function ( x, y, z ) {
+
+    var elements = this.elements = new Float32Array( 3 );
+    elements[0] = x || 0;
+    elements[1] = y || 1;
+    elements[2] = z || 2;
+
+};
+
+THREE.Vector3X.prototype = {
+
+    constructor: THREE.Vector3X,
+    
+    length: function () {
+
+            return Math.sqrt( this.elements[0] * this.elements[0] + this.elements[1] * this.elements[1] + this.elements[2] * this.elements[2] );
+
+    }
+    
+};
+
+
+THREE.Vector3Y = function ( x, y, z ) {
+
+    this.elements = [ x || 0, y || 1, z || 2 ];
+
+};
+
+THREE.Vector3Y.prototype = {
+
+    constructor: THREE.Vector3Y,
+    
+    length: function () {
+
+            return Math.sqrt( this.elements[0] * this.elements[0] + this.elements[1] * this.elements[1] + this.elements[2] * this.elements[2] );
+
+    }
+    
+};
+
+
+var suite = new Benchmark.Suite;
+
+suite.add('Vector3-Set', function() {
+
+    var array = [];
+    for ( var i = 0; i < 100000; i ++ ) {
+        var v = new THREE.Vector3( i, i, i );
+        array.push( v );
+    }
+
+    var result = 0;
+    for ( var i = 0; i < 100000; i ++ ) {
+        var v = array[i];
+        result += v.length();
+    }
+});
+
+suite.add('Vector3-Float32Array', function() {
+
+    var array = [];
+    for ( var i = 0; i < 100000; i ++ ) {
+        var v = new THREE.Vector3X( i, i, i );
+        array.push( v );
+    }
+
+    var result = 0;
+    for ( var i = 0; i < 100000; i ++ ) {
+        var v = array[i];
+        result += v.length();
+    }
+});
+
+suite.add('Vector3-Array', function() {
+
+    var array = [];
+    for ( var i = 0; i < 100000; i ++ ) {
+        var v = new THREE.Vector3Y( i, i, i );
+        array.push( v );
+    }
+
+    var result = 0;
+    for ( var i = 0; i < 100000; i ++ ) {
+        var v = array[i];
+        result += v.length();
+    }
+});
+
+suite.on('cycle', function(event, bench) {
+  console.log(String(event.target));
+});
+
+suite.on('complete', function() {
+  console.log('Fastest is ' + this.filter('fastest').pluck('name'));
+  console.log( "Done" );
+});
+
+suite.run(true);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/core/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/benchmark/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/perf.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/perf.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/perf.html	(revision 28897)
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html lang="en">
+	<body>
+
+		<script src="../build/three.min.js"></script>
+		<script src="../examples/js/math/TypeArrayVector3.js"></script>
+
+		<script>
+
+			var Benchmark = function ( title, test ) {
+
+				var titleDom = document.createElement( 'div' );
+				titleDom.style.fontWeight = 'bold';
+				titleDom.textContent = title;
+				document.body.appendChild( titleDom );
+
+				var msDom = document.createElement( 'div' );
+				document.body.appendChild( msDom );
+
+				this.run = function () {
+
+					var start = performance.now();
+
+					test();
+
+					msDom.textContent = performance.now() - start;
+
+				}
+
+			};
+
+
+			var vector11 = new THREE.Vector3( 1.2, 2.3, 3.4 );
+			var vector12 = new THREE.Vector3( 1.2, 2.3, 3.4 );
+			var vector13 = new THREE.Vector3( 1.2, 2.3, 3.4 );
+
+			var vector21 = new THREE.TypeArrayVector3( 1.2, 2.3, 3.4 );
+			var vector22 = new THREE.TypeArrayVector3( 1.2, 2.3, 3.4 );
+			var vector23 = new THREE.TypeArrayVector3( 1.2, 2.3, 3.4 );
+
+			var benchmarks = [
+
+				new Benchmark( 'new Vector3()', function () {
+
+					for ( var i = 0; i < 10000; i ++ ) {
+
+						var vector = new THREE.Vector3( 1, 1, 1 );
+
+					}
+
+				} ),
+
+				new Benchmark( 'new TypeArrayVector3()', function () {
+
+					for ( var i = 0; i < 10000; i ++ ) {
+
+						var vector = new THREE.TypeArrayVector3( 1, 1, 1 );
+
+					}
+
+				} ),
+
+				new Benchmark( 'Vector3 add()', function () {
+
+					for ( var i = 0; i < 10000000; i ++ ) {
+
+						vector11.add( vector12 );
+
+					}
+
+				} ),
+
+				new Benchmark( 'TypeArrayVector3 add()', function () {
+
+					for ( var i = 0; i < 10000000; i ++ ) {
+
+						vector21.add( vector22 );
+
+					}
+
+				} ),
+
+				new Benchmark( 'Vector3 addVectors()', function () {
+
+					for ( var i = 0; i < 10000000; i ++ ) {
+
+						vector11.addVectors( vector12, vector13 );
+
+					}
+
+				} ),
+
+				new Benchmark( 'TypeArrayVector3 addVectors()', function () {
+
+					for ( var i = 0; i < 10000000; i ++ ) {
+
+						vector21.addVectors( vector22, vector23 );
+
+					}
+
+				} )
+
+			];
+
+			function update () {
+
+				vector11 = new THREE.Vector3( Math.random(), Math.random(), Math.random() );
+				vector12 = new THREE.Vector3( Math.random(), Math.random(), Math.random() );
+
+				vector21 = new THREE.TypeArrayVector3().set( vector11.x, vector11.y, vector11.z );
+				vector22 = new THREE.TypeArrayVector3().set( vector12.x, vector12.y, vector12.z );
+
+				for ( var i = 0, l = benchmarks.length; i < l; i ++ ) {
+
+					benchmarks[ i ].run();
+
+				}
+
+				setTimeout( update, 2000 );
+
+			}
+
+			update();
+
+		</script>
+
+	</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Box2.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Box2.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Box2.js	(revision 28897)
@@ -0,0 +1,245 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Box2" );
+
+test( "constructor", function() {
+	var a = new THREE.Box2();
+	ok( a.min.equals( posInf2 ), "Passed!" );
+	ok( a.max.equals( negInf2 ), "Passed!" );
+
+	a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	ok( a.min.equals( zero2 ), "Passed!" );
+	ok( a.max.equals( zero2 ), "Passed!" );
+
+	a = new THREE.Box2( zero2.clone(), one2.clone() );
+	ok( a.min.equals( zero2 ), "Passed!" );
+	ok( a.max.equals( one2 ), "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Box2( zero2.clone(), one2.clone() );
+	var b = new THREE.Box2().copy( a );
+	ok( b.min.equals( zero2 ), "Passed!" );
+	ok( b.max.equals( one2 ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.min = zero2;
+	a.max = one2;
+	ok( b.min.equals( zero2 ), "Passed!" );
+	ok( b.max.equals( one2 ), "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Box2();
+
+	a.set( zero2, one2 );
+	ok( a.min.equals( zero2 ), "Passed!" );
+	ok( a.max.equals( one2 ), "Passed!" );
+});
+
+test( "empty/makeEmpty", function() {
+	var a = new THREE.Box2();
+
+	ok( a.empty(), "Passed!" );
+
+	var a = new THREE.Box2( zero2.clone(), one2.clone() );
+	ok( ! a.empty(), "Passed!" );
+
+	a.makeEmpty();
+	ok( a.empty(), "Passed!" );
+});
+
+test( "center", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+
+	ok( a.center().equals( zero2 ), "Passed!" );
+
+	a = new THREE.Box2( zero2, one2 );
+	var midpoint = one2.clone().multiplyScalar( 0.5 );
+	ok( a.center().equals( midpoint ), "Passed!" );
+});
+
+test( "size", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+
+	ok( a.size().equals( zero2 ), "Passed!" );
+
+	a = new THREE.Box2( zero2.clone(), one2.clone() );
+	ok( a.size().equals( one2 ), "Passed!" );
+});
+
+test( "expandByPoint", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+
+	a.expandByPoint( zero2 );
+	ok( a.size().equals( zero2 ), "Passed!" );
+
+	a.expandByPoint( one2 );
+	ok( a.size().equals( one2 ), "Passed!" );
+
+	a.expandByPoint( one2.clone().negate() );
+	ok( a.size().equals( one2.clone().multiplyScalar( 2 ) ), "Passed!" );
+	ok( a.center().equals( zero2 ), "Passed!" );
+});
+
+test( "expandByVector", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+
+	a.expandByVector( zero2 );
+	ok( a.size().equals( zero2 ), "Passed!" );
+
+	a.expandByVector( one2 );
+	ok( a.size().equals( one2.clone().multiplyScalar( 2 ) ), "Passed!" );
+	ok( a.center().equals( zero2 ), "Passed!" );
+});
+
+test( "expandByScalar", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+
+	a.expandByScalar( 0 );
+	ok( a.size().equals( zero2 ), "Passed!" );
+
+	a.expandByScalar( 1 );
+	ok( a.size().equals( one2.clone().multiplyScalar( 2 ) ), "Passed!" );
+	ok( a.center().equals( zero2 ), "Passed!" );
+});
+
+test( "containsPoint", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+
+	ok( a.containsPoint( zero2 ), "Passed!" );
+	ok( ! a.containsPoint( one2 ), "Passed!" );
+
+	a.expandByScalar( 1 );
+	ok( a.containsPoint( zero2 ), "Passed!" );
+	ok( a.containsPoint( one2 ), "Passed!" );
+	ok( a.containsPoint( one2.clone().negate() ), "Passed!" );
+});
+
+test( "containsBox", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	var b = new THREE.Box2( zero2.clone(), one2.clone() );
+	var c = new THREE.Box2( one2.clone().negate(), one2.clone() );
+
+	ok( a.containsBox( a ), "Passed!" );
+	ok( ! a.containsBox( b ), "Passed!" );
+	ok( ! a.containsBox( c ), "Passed!" );
+
+	ok( b.containsBox( a ), "Passed!" );
+	ok( c.containsBox( a ), "Passed!" );
+	ok( ! b.containsBox( c ), "Passed!" );
+});
+
+test( "getParameter", function() {
+	var a = new THREE.Box2( zero2.clone(), one2.clone() );
+	var b = new THREE.Box2( one2.clone().negate(), one2.clone() );
+
+	ok( a.getParameter( new THREE.Vector2( 0, 0 ) ).equals( new THREE.Vector2( 0, 0 ) ), "Passed!" );
+	ok( a.getParameter( new THREE.Vector2( 1, 1 ) ).equals( new THREE.Vector2( 1, 1 ) ), "Passed!" );
+
+	ok( b.getParameter( new THREE.Vector2( -1, -1 ) ).equals( new THREE.Vector2( 0, 0 ) ), "Passed!" );
+	ok( b.getParameter( new THREE.Vector2( 0, 0 ) ).equals( new THREE.Vector2( 0.5, 0.5 ) ), "Passed!" );
+	ok( b.getParameter( new THREE.Vector2( 1, 1 ) ).equals( new THREE.Vector2( 1, 1 ) ), "Passed!" );
+});
+
+test( "clampPoint", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	var b = new THREE.Box2( one2.clone().negate(), one2.clone() );
+
+	ok( a.clampPoint( new THREE.Vector2( 0, 0 ) ).equals( new THREE.Vector2( 0, 0 ) ), "Passed!" );
+	ok( a.clampPoint( new THREE.Vector2( 1, 1 ) ).equals( new THREE.Vector2( 0, 0 ) ), "Passed!" );
+	ok( a.clampPoint( new THREE.Vector2( -1, -1 ) ).equals( new THREE.Vector2( 0, 0 ) ), "Passed!" );
+
+	ok( b.clampPoint( new THREE.Vector2( 2, 2 ) ).equals( new THREE.Vector2( 1, 1 ) ), "Passed!" );
+	ok( b.clampPoint( new THREE.Vector2( 1, 1 ) ).equals( new THREE.Vector2( 1, 1 ) ), "Passed!" );
+	ok( b.clampPoint( new THREE.Vector2( 0, 0 ) ).equals( new THREE.Vector2( 0, 0 ) ), "Passed!" );
+	ok( b.clampPoint( new THREE.Vector2( -1, -1 ) ).equals( new THREE.Vector2( -1, -1 ) ), "Passed!" );
+	ok( b.clampPoint( new THREE.Vector2( -2, -2 ) ).equals( new THREE.Vector2( -1, -1 ) ), "Passed!" );
+});
+
+test( "distanceToPoint", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	var b = new THREE.Box2( one2.clone().negate(), one2.clone() );
+
+	ok( a.distanceToPoint( new THREE.Vector2( 0, 0 ) ) == 0, "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector2( 1, 1 ) ) == Math.sqrt( 2 ), "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector2( -1, -1 ) ) == Math.sqrt( 2 ), "Passed!" );
+
+	ok( b.distanceToPoint( new THREE.Vector2( 2, 2 ) ) == Math.sqrt( 2 ), "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector2( 1, 1 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector2( 0, 0 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector2( -1, -1 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector2( -2, -2 ) ) == Math.sqrt( 2 ), "Passed!" );
+});
+
+test( "distanceToPoint", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	var b = new THREE.Box2( one2.clone().negate(), one2.clone() );
+
+	ok( a.distanceToPoint( new THREE.Vector2( 0, 0 ) ) == 0, "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector2( 1, 1 ) ) == Math.sqrt( 2 ), "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector2( -1, -1 ) ) == Math.sqrt( 2 ), "Passed!" );
+
+	ok( b.distanceToPoint( new THREE.Vector2( 2, 2 ) ) == Math.sqrt( 2 ), "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector2( 1, 1 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector2( 0, 0 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector2( -1, -1 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector2( -2, -2 ) ) == Math.sqrt( 2 ), "Passed!" );
+});
+
+test( "isIntersectionBox", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	var b = new THREE.Box2( zero2.clone(), one2.clone() );
+	var c = new THREE.Box2( one2.clone().negate(), one2.clone() );
+
+	ok( a.isIntersectionBox( a ), "Passed!" );
+	ok( a.isIntersectionBox( b ), "Passed!" );
+	ok( a.isIntersectionBox( c ), "Passed!" );
+
+	ok( b.isIntersectionBox( a ), "Passed!" );
+	ok( c.isIntersectionBox( a ), "Passed!" );
+	ok( b.isIntersectionBox( c ), "Passed!" );
+
+	b.translate( new THREE.Vector2( 2, 2 ) );
+	ok( ! a.isIntersectionBox( b ), "Passed!" );
+	ok( ! b.isIntersectionBox( a ), "Passed!" );
+	ok( ! b.isIntersectionBox( c ), "Passed!" );
+});
+
+test( "intersect", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	var b = new THREE.Box2( zero2.clone(), one2.clone() );
+	var c = new THREE.Box2( one2.clone().negate(), one2.clone() );
+
+	ok( a.clone().intersect( a ).equals( a ), "Passed!" );
+	ok( a.clone().intersect( b ).equals( a ), "Passed!" );
+	ok( b.clone().intersect( b ).equals( b ), "Passed!" );
+	ok( a.clone().intersect( c ).equals( a ), "Passed!" );
+	ok( b.clone().intersect( c ).equals( b ), "Passed!" );
+	ok( c.clone().intersect( c ).equals( c ), "Passed!" );
+});
+
+test( "union", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	var b = new THREE.Box2( zero2.clone(), one2.clone() );
+	var c = new THREE.Box2( one2.clone().negate(), one2.clone() );
+
+	ok( a.clone().union( a ).equals( a ), "Passed!" );
+	ok( a.clone().union( b ).equals( b ), "Passed!" );
+	ok( a.clone().union( c ).equals( c ), "Passed!" );
+	ok( b.clone().union( c ).equals( c ), "Passed!" );
+});
+
+test( "translate", function() {
+	var a = new THREE.Box2( zero2.clone(), zero2.clone() );
+	var b = new THREE.Box2( zero2.clone(), one2.clone() );
+	var c = new THREE.Box2( one2.clone().negate(), one2.clone() );
+	var d = new THREE.Box2( one2.clone().negate(), zero2.clone() );
+
+	ok( a.clone().translate( one2 ).equals( new THREE.Box2( one2, one2 ) ), "Passed!" );
+	ok( a.clone().translate( one2 ).translate( one2.clone().negate() ).equals( a ), "Passed!" );
+	ok( d.clone().translate( one2 ).equals( b ), "Passed!" );
+	ok( b.clone().translate( one2.clone().negate() ).equals( d ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Box3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Box3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Box3.js	(revision 28897)
@@ -0,0 +1,276 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Box3" );
+
+test( "constructor", function() {
+	var a = new THREE.Box3();
+	ok( a.min.equals( posInf3 ), "Passed!" );
+	ok( a.max.equals( negInf3 ), "Passed!" );
+
+	a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	ok( a.min.equals( zero3 ), "Passed!" );
+	ok( a.max.equals( zero3 ), "Passed!" );
+
+	a = new THREE.Box3( zero3.clone(), one3.clone() );
+	ok( a.min.equals( zero3 ), "Passed!" );
+	ok( a.max.equals( one3 ), "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Box3( zero3.clone(), one3.clone() );
+	var b = new THREE.Box3().copy( a );
+	ok( b.min.equals( zero3 ), "Passed!" );
+	ok( b.max.equals( one3 ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.min = zero3;
+	a.max = one3;
+	ok( b.min.equals( zero3 ), "Passed!" );
+	ok( b.max.equals( one3 ), "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Box3();
+
+	a.set( zero3, one3 );
+	ok( a.min.equals( zero3 ), "Passed!" );
+	ok( a.max.equals( one3 ), "Passed!" );
+});
+
+test( "empty/makeEmpty", function() {
+	var a = new THREE.Box3();
+
+	ok( a.empty(), "Passed!" );
+
+	var a = new THREE.Box3( zero3.clone(), one3.clone() );
+	ok( ! a.empty(), "Passed!" );
+
+	a.makeEmpty();
+	ok( a.empty(), "Passed!" );
+});
+
+test( "center", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+
+	ok( a.center().equals( zero3 ), "Passed!" );
+
+	a = new THREE.Box3( zero3.clone(), one3.clone() );
+	var midpoint = one3.clone().multiplyScalar( 0.5 );
+	ok( a.center().equals( midpoint ), "Passed!" );
+});
+
+test( "size", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+
+	ok( a.size().equals( zero3 ), "Passed!" );
+
+	a = new THREE.Box3( zero3.clone(), one3.clone() );
+	ok( a.size().equals( one3 ), "Passed!" );
+});
+
+test( "expandByPoint", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+
+	a.expandByPoint( zero3 );
+	ok( a.size().equals( zero3 ), "Passed!" );
+
+	a.expandByPoint( one3 );
+	ok( a.size().equals( one3 ), "Passed!" );
+
+	a.expandByPoint( one3.clone().negate() );
+	ok( a.size().equals( one3.clone().multiplyScalar( 2 ) ), "Passed!" );
+	ok( a.center().equals( zero3 ), "Passed!" );
+});
+
+test( "expandByVector", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+
+	a.expandByVector( zero3 );
+	ok( a.size().equals( zero3 ), "Passed!" );
+
+	a.expandByVector( one3 );
+	ok( a.size().equals( one3.clone().multiplyScalar( 2 ) ), "Passed!" );
+	ok( a.center().equals( zero3 ), "Passed!" );
+});
+
+test( "expandByScalar", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+
+	a.expandByScalar( 0 );
+	ok( a.size().equals( zero3 ), "Passed!" );
+
+	a.expandByScalar( 1 );
+	ok( a.size().equals( one3.clone().multiplyScalar( 2 ) ), "Passed!" );
+	ok( a.center().equals( zero3 ), "Passed!" );
+});
+
+test( "containsPoint", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+
+	ok( a.containsPoint( zero3 ), "Passed!" );
+	ok( ! a.containsPoint( one3 ), "Passed!" );
+
+	a.expandByScalar( 1 );
+	ok( a.containsPoint( zero3 ), "Passed!" );
+	ok( a.containsPoint( one3 ), "Passed!" );
+	ok( a.containsPoint( one3.clone().negate() ), "Passed!" );
+});
+
+test( "containsBox", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( zero3.clone(), one3.clone() );
+	var c = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.containsBox( a ), "Passed!" );
+	ok( ! a.containsBox( b ), "Passed!" );
+	ok( ! a.containsBox( c ), "Passed!" );
+
+	ok( b.containsBox( a ), "Passed!" );
+	ok( c.containsBox( a ), "Passed!" );
+	ok( ! b.containsBox( c ), "Passed!" );
+});
+
+test( "getParameter", function() {
+	var a = new THREE.Box3( zero3.clone(), one3.clone() );
+	var b = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.getParameter( new THREE.Vector3( 0, 0, 0 ) ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+	ok( a.getParameter( new THREE.Vector3( 1, 1, 1 ) ).equals( new THREE.Vector3( 1, 1, 1 ) ), "Passed!" );
+
+	ok( b.getParameter( new THREE.Vector3( -1, -1, -1 ) ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+	ok( b.getParameter( new THREE.Vector3( 0, 0, 0 ) ).equals( new THREE.Vector3( 0.5, 0.5, 0.5 ) ), "Passed!" );
+	ok( b.getParameter( new THREE.Vector3( 1, 1, 1 ) ).equals( new THREE.Vector3( 1, 1, 1 ) ), "Passed!" );
+});
+
+test( "clampPoint", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.clampPoint( new THREE.Vector3( 0, 0, 0 ) ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+	ok( a.clampPoint( new THREE.Vector3( 1, 1, 1 ) ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+	ok( a.clampPoint( new THREE.Vector3( -1, -1, -1 ) ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+
+	ok( b.clampPoint( new THREE.Vector3( 2, 2, 2 ) ).equals( new THREE.Vector3( 1, 1, 1 ) ), "Passed!" );
+	ok( b.clampPoint( new THREE.Vector3( 1, 1, 1 ) ).equals( new THREE.Vector3( 1, 1, 1 ) ), "Passed!" );
+	ok( b.clampPoint( new THREE.Vector3( 0, 0, 0 ) ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+	ok( b.clampPoint( new THREE.Vector3( -1, -1, -1 ) ).equals( new THREE.Vector3( -1, -1, -1 ) ), "Passed!" );
+	ok( b.clampPoint( new THREE.Vector3( -2, -2, -2 ) ).equals( new THREE.Vector3( -1, -1, -1 ) ), "Passed!" );
+});
+
+test( "distanceToPoint", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.distanceToPoint( new THREE.Vector3( 0, 0, 0 ) ) == 0, "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector3( 1, 1, 1 ) ) == Math.sqrt( 3 ), "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector3( -1, -1, -1 ) ) == Math.sqrt( 3 ), "Passed!" );
+
+	ok( b.distanceToPoint( new THREE.Vector3( 2, 2, 2 ) ) == Math.sqrt( 3 ), "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector3( 1, 1, 1 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector3( 0, 0, 0 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector3( -1, -1, -1 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector3( -2, -2, -2 ) ) == Math.sqrt( 3 ), "Passed!" );
+});
+
+test( "distanceToPoint", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.distanceToPoint( new THREE.Vector3( 0, 0, 0 ) ) == 0, "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector3( 1, 1, 1 ) ) == Math.sqrt( 3 ), "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector3( -1, -1, -1 ) ) == Math.sqrt( 3 ), "Passed!" );
+
+	ok( b.distanceToPoint( new THREE.Vector3( 2, 2, 2 ) ) == Math.sqrt( 3 ), "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector3( 1, 1, 1 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector3( 0, 0, 0 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector3( -1, -1, -1 ) ) == 0, "Passed!" );
+	ok( b.distanceToPoint( new THREE.Vector3( -2, -2, -2 ) ) == Math.sqrt( 3 ), "Passed!" );
+});
+
+test( "isIntersectionBox", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( zero3.clone(), one3.clone() );
+	var c = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.isIntersectionBox( a ), "Passed!" );
+	ok( a.isIntersectionBox( b ), "Passed!" );
+	ok( a.isIntersectionBox( c ), "Passed!" );
+
+	ok( b.isIntersectionBox( a ), "Passed!" );
+	ok( c.isIntersectionBox( a ), "Passed!" );
+	ok( b.isIntersectionBox( c ), "Passed!" );
+
+	b.translate( new THREE.Vector3( 2, 2, 2 ) );
+	ok( ! a.isIntersectionBox( b ), "Passed!" );
+	ok( ! b.isIntersectionBox( a ), "Passed!" );
+	ok( ! b.isIntersectionBox( c ), "Passed!" );
+});
+
+test( "getBoundingSphere", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( zero3.clone(), one3.clone() );
+	var c = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.getBoundingSphere().equals( new THREE.Sphere( zero3, 0 ) ), "Passed!" );
+	ok( b.getBoundingSphere().equals( new THREE.Sphere( one3.clone().multiplyScalar( 0.5 ), Math.sqrt( 3 ) * 0.5 ) ), "Passed!" );
+	ok( c.getBoundingSphere().equals( new THREE.Sphere( zero3, Math.sqrt( 12 ) * 0.5 ) ), "Passed!" );
+});
+
+test( "intersect", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( zero3.clone(), one3.clone() );
+	var c = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.clone().intersect( a ).equals( a ), "Passed!" );
+	ok( a.clone().intersect( b ).equals( a ), "Passed!" );
+	ok( b.clone().intersect( b ).equals( b ), "Passed!" );
+	ok( a.clone().intersect( c ).equals( a ), "Passed!" );
+	ok( b.clone().intersect( c ).equals( b ), "Passed!" );
+	ok( c.clone().intersect( c ).equals( c ), "Passed!" );
+});
+
+test( "union", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( zero3.clone(), one3.clone() );
+	var c = new THREE.Box3( one3.clone().negate(), one3.clone() );
+
+	ok( a.clone().union( a ).equals( a ), "Passed!" );
+	ok( a.clone().union( b ).equals( b ), "Passed!" );
+	ok( a.clone().union( c ).equals( c ), "Passed!" );
+	ok( b.clone().union( c ).equals( c ), "Passed!" );
+});
+
+var compareBox = function ( a, b, threshold ) {
+	threshold = threshold || 0.0001;
+	return ( a.min.distanceTo( b.min ) < threshold &&
+	a.max.distanceTo( b.max ) < threshold );
+};
+
+test( "applyMatrix4", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( zero3.clone(), one3.clone() );
+	var c = new THREE.Box3( one3.clone().negate(), one3.clone() );
+	var d = new THREE.Box3( one3.clone().negate(), zero3.clone() );
+
+	var m = new THREE.Matrix4().makeTranslation( 1, -2, 1 );
+	var t1 = new THREE.Vector3( 1, -2, 1 );
+
+	ok( compareBox( a.clone().applyMatrix4( m ), a.clone().translate( t1 ) ), "Passed!" );
+	ok( compareBox( b.clone().applyMatrix4( m ), b.clone().translate( t1 ) ), "Passed!" );
+	ok( compareBox( c.clone().applyMatrix4( m ), c.clone().translate( t1 ) ), "Passed!" );
+	ok( compareBox( d.clone().applyMatrix4( m ), d.clone().translate( t1 ) ), "Passed!" );
+});
+
+test( "translate", function() {
+	var a = new THREE.Box3( zero3.clone(), zero3.clone() );
+	var b = new THREE.Box3( zero3.clone(), one3.clone() );
+	var c = new THREE.Box3( one3.clone().negate(), one3.clone() );
+	var d = new THREE.Box3( one3.clone().negate(), zero3.clone() );
+
+	ok( a.clone().translate( one3 ).equals( new THREE.Box3( one3, one3 ) ), "Passed!" );
+	ok( a.clone().translate( one3 ).translate( one3.clone().negate() ).equals( a ), "Passed!" );
+	ok( d.clone().translate( one3 ).equals( b ), "Passed!" );
+	ok( b.clone().translate( one3.clone().negate() ).equals( d ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Color.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Color.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Color.js	(revision 28897)
@@ -0,0 +1,198 @@
+module( "Color" );
+
+test( "constructor", function(){
+    var c = new THREE.Color();
+    ok( c.r, "Red: " + c.r );
+    ok( c.g, "Green: " + c.g );
+    ok( c.b, "Blue: " + c.b );
+});
+
+test( "copyHex", function(){
+    var c = new THREE.Color();
+    var c2 = new THREE.Color(0xF5FFFA);
+    c.copy(c2);
+    ok(c.getHex() == c2.getHex(), "Hex c: " + c.getHex() + " Hex c2: " + c2.getHex());
+});
+
+test( "copyColorString", function(){
+    var c = new THREE.Color();
+    var c2 = new THREE.Color('ivory');
+    c.copy(c2);
+    ok(c.getHex() == c2.getHex(), "Hex c: " + c.getHex() + " Hex c2: " + c2.getHex());
+});
+
+test( "setRGB", function(){
+    var c = new THREE.Color()
+    c.setRGB(255, 2, 1);
+    ok( c.r == 255, "Red: " + c.r );
+    ok( c.g == 2, "Green: " + c.g );
+    ok( c.b == 1, "Blue: " + c.b );
+});
+
+test( "copyGammaToLinear", function(){
+    var c = new THREE.Color();
+    var c2 = new THREE.Color();
+    c2.setRGB(2, 4, 8)
+    c.copyGammaToLinear(c2)
+    ok( c.r == 4, "Red c: " + c.r + " Red c2: " + c2.r);
+    ok( c.g == 16, "Green c: " + c.g + " Green c2: " + c2.g);
+    ok( c.b == 64, "Blue c: " + c.b + " Blue c2: " + c2.b);
+});
+
+test( "copyLinearToGamma", function(){
+    var c = new THREE.Color();
+    var c2 = new THREE.Color();
+    c2.setRGB(4, 9, 16)
+    c.copyLinearToGamma(c2)
+    ok( c.r == 2, "Red c: " + c.r + " Red c2: " + c2.r);
+    ok( c.g == 3, "Green c: " + c.g + " Green c2: " + c2.g);
+    ok( c.b == 4, "Blue c: " + c.b + " Blue c2: " + c2.b);
+});
+
+
+test( "convertGammaToLinear", function(){
+    var c = new THREE.Color();
+    c.setRGB(2, 4, 8)
+    c.convertGammaToLinear()
+    ok( c.r == 4, "Red: " + c.r );
+    ok( c.g == 16, "Green: " + c.g );
+    ok( c.b == 64, "Blue: " + c.b );
+});
+
+
+test( "convertLinearToGamma", function(){
+    var c = new THREE.Color();
+    c.setRGB(4, 9, 16)
+    c.convertLinearToGamma()
+    ok( c.r == 2, "Red: " + c.r );
+    ok( c.g == 3, "Green: " + c.g );
+    ok( c.b == 4, "Blue: " + c.b );
+});
+
+test("setWithNum", function(){
+    var c = new THREE.Color();
+    c.set(0xFF0000);
+    ok( c.r == 1, "Red: " + c.r );
+    ok( c.g == 0, "Green: " + c.g );
+    ok( c.b == 0, "Blue: " + c.b );
+});
+
+
+test( "setWithString", function(){
+    var c = new THREE.Color();
+    c.set('silver');
+    ok(c.getHex() == 0xC0C0C0, "Hex c: " + c.getHex());
+});
+
+
+test( "clone", function(){
+    var c = new THREE.Color('teal');
+    var c2 = c.clone();
+    ok(c2.getHex() == 0x008080, "Hex c2: " + c2.getHex());
+});
+
+test( "lerp", function(){
+    var c = new THREE.Color();
+    var c2 = new THREE.Color();
+    c.setRGB(0, 0, 0);
+    c.lerp(c2, 2);
+    ok( c.r == 2, "Red: " + c.r );
+    ok( c.g == 2, "Green: " + c.g );
+    ok( c.b == 2, "Blue: " + c.b );
+    
+});
+
+
+test( "setStyleRGBRed", function(){
+    var c = new THREE.Color();
+    c.setStyle('rgb(255,0,0)');
+    ok( c.r == 1, "Red: " + c.r );
+    ok( c.g == 0, "Green: " + c.g );
+    ok( c.b == 0, "Blue: " + c.b );
+});
+
+test( "setStyleRGBRedWithSpaces", function(){
+    var c = new THREE.Color();
+    c.setStyle('rgb(255, 0, 0)');
+    ok( c.r == 1, "Red: " + c.r );
+    ok( c.g == 0, "Green: " + c.g );
+    ok( c.b == 0, "Blue: " + c.b );
+});
+
+test( "setStyleRGBPercent", function(){
+    var c = new THREE.Color();
+    c.setStyle('rgb(100%,50%,10%)');
+    ok( c.r == 1, "Red: " + c.r );
+    ok( c.g == 0.5, "Green: " + c.g );
+    ok( c.b == 0.1, "Blue: " + c.b );
+});
+
+test( "setStyleRGBPercentWithSpaces", function(){
+    var c = new THREE.Color();
+    c.setStyle('rgb(100%,50%,10%)');
+    ok( c.r == 1, "Red: " + c.r );
+    ok( c.g == 0.5, "Green: " + c.g );
+    ok( c.b == 0.1, "Blue: " + c.b );
+});
+
+test( "setStyleHexSkyBlue", function(){
+    var c = new THREE.Color();
+    c.setStyle('#87CEEB');
+    ok(c.getHex() == 0x87CEEB, "Hex c: " + c.getHex());
+});
+
+test( "setStyleHex2Olive", function(){
+    var c = new THREE.Color();
+    c.setStyle('#F00');
+    ok(c.getHex() == 0xFF0000, "Hex c: " + c.getHex());
+});
+
+test( "setStyleColorName", function(){
+    var c = new THREE.Color();
+    c.setStyle('powderblue');
+    ok(c.getHex() == 0xB0E0E6, "Hex c: " + c.getHex());
+});
+
+
+test( "getHex", function(){
+    var c = new THREE.Color('red');
+    var res = c.getHex();
+    ok( res == 0xFF0000, "Hex: " + res );
+});
+
+test( "setHex", function(){
+    var c = new THREE.Color();
+    c.setHex(0xFA8072);
+    ok( c.getHex() == 0xFA8072, "Hex: " + c.getHex());
+});
+
+test( "getHexString", function(){
+    var c = new THREE.Color('tomato');
+    var res = c.getHexString();
+    ok( res == 'ff6347', "Hex: " + res );
+});
+
+test( "getStyle", function(){
+    var c = new THREE.Color('plum');
+    var res = c.getStyle();
+    ok( res == 'rgb(221,160,221)', "style: " + res );
+});
+
+test( "getHSL", function () {
+    var c = new THREE.Color( 0x80ffff );
+    var hsl = c.getHSL();
+
+    ok( hsl.h == 0.5, "hue: " + hsl.h );
+    ok( hsl.s == 1.0, "saturation: " + hsl.s );
+    ok( (Math.round(parseFloat(hsl.l)*100)/100) == 0.75, "lightness: " + hsl.l );
+});
+
+test( "setHSL", function () {
+    var c = new THREE.Color();
+    c.setHSL(0.75, 1.0, 0.25);
+    var hsl = c.getHSL();
+
+    ok( hsl.h == 0.75, "hue: " + hsl.h );
+    ok( hsl.s == 1.00, "saturation: " + hsl.s );
+    ok( hsl.l == 0.25, "lightness: " + hsl.l );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Constants.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Constants.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Constants.js	(revision 28897)
@@ -0,0 +1,22 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+var x = 2;
+var y = 3;
+var z = 4;
+var w = 5;
+
+var negInf2 = new THREE.Vector2( -Infinity, -Infinity );
+var posInf2 = new THREE.Vector2( Infinity, Infinity );
+
+var zero2 = new THREE.Vector2();
+var one2 = new THREE.Vector2( 1, 1 );
+var two2 = new THREE.Vector2( 2, 2 );
+
+var negInf3 = new THREE.Vector3( -Infinity, -Infinity, -Infinity );
+var posInf3 = new THREE.Vector3( Infinity, Infinity, Infinity );
+
+var zero3 = new THREE.Vector3();
+var one3 = new THREE.Vector3( 1, 1, 1 );
+var two3 = new THREE.Vector3( 2, 2, 2 );
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Euler.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Euler.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Euler.js	(revision 28897)
@@ -0,0 +1,93 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Euler" );
+
+var eulerZero = new THREE.Euler( 0, 0, 0, "XYZ" );
+var eulerAxyz = new THREE.Euler( 1, 0, 0, "XYZ" );
+var eulerAzyx = new THREE.Euler( 0, 1, 0, "ZYX" );
+	
+var matrixEquals4 = function( a, b, tolerance ) {
+	tolerance = tolerance || 0.0001;
+	if( a.elements.length != b.elements.length ) {
+		return false;
+	}
+	for( var i = 0, il = a.elements.length; i < il; i ++ ) {
+		var delta = a.elements[i] - b.elements[i];
+		if( delta > tolerance ) {
+			return false;
+		}
+	}
+	return true;
+};
+
+test( "constructor/equals", function() {
+	var a = new THREE.Euler();
+	ok( a.equals( eulerZero ), "Passed!" );
+	ok( ! a.equals( eulerAxyz ), "Passed!" );
+	ok( ! a.equals( eulerAzyx ), "Passed!" );
+});
+
+test( "clone/copy/equals", function() {
+	var a = eulerAxyz.clone();
+	ok( a.equals( eulerAxyz ), "Passed!" );
+	ok( ! a.equals( eulerZero ), "Passed!" );
+	ok( ! a.equals( eulerAzyx ), "Passed!" );
+
+	a.copy( eulerAzyx );
+	ok( a.equals( eulerAzyx ), "Passed!" );
+	ok( ! a.equals( eulerAxyz ), "Passed!" );
+	ok( ! a.equals( eulerZero ), "Passed!" );
+
+});
+
+test( "set", function() {
+	var a = new THREE.Euler();
+
+	a.set( 0, 1, 0, "ZYX" );
+	ok( a.equals( eulerAzyx ), "Passed!" );
+	ok( ! a.equals( eulerAxyz ), "Passed!" );
+	ok( ! a.equals( eulerZero ), "Passed!" );
+});
+
+test( "Quaternion.setFromEuler/Euler.fromQuaternion", function() {
+	var testValues = [ eulerZero, eulerAxyz, eulerAzyx ];
+	for( var i = 0; i < testValues.length; i ++ ) {
+		var v = testValues[i];
+		var q = new THREE.Quaternion().setFromEuler( v );
+
+		var v2 = new THREE.Euler().setFromQuaternion( q, v.order );
+		var q2 = new THREE.Quaternion().setFromEuler( v2 );
+		ok( q.equals( q2 ), "Passed!" );	
+	}
+});
+
+
+test( "Matrix4.setFromEuler/Euler.fromRotationMatrix", function() {
+	var testValues = [ eulerZero, eulerAxyz, eulerAzyx ];
+	for( var i = 0; i < testValues.length; i ++ ) {
+		var v = testValues[i];
+		var m = new THREE.Matrix4().makeRotationFromEuler( v );
+
+		var v2 = new THREE.Euler().setFromRotationMatrix( m, v.order );
+		var m2 = new THREE.Matrix4().makeRotationFromEuler( v2 );
+		ok( matrixEquals4( m, m2, 0.0001 ), "Passed!" );	
+	}
+});
+
+test( "reorder", function() {
+	var testValues = [ eulerZero, eulerAxyz, eulerAzyx ];
+	for( var i = 0; i < testValues.length; i ++ ) {
+		var v = testValues[i];
+		var q = new THREE.Quaternion().setFromEuler( v );
+
+		v.reorder( 'YZX' );		
+		var q2 = new THREE.Quaternion().setFromEuler( v );
+		ok( q.equals( q2 ), "Passed!" );	
+
+		v.reorder( 'ZXY' );
+		var q3 = new THREE.Quaternion().setFromEuler( v );
+		ok( q.equals( q3 ), "Passed!" );	
+	}
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Frustum.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Frustum.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Frustum.js	(revision 28897)
@@ -0,0 +1,156 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Frustum" );
+
+var unit3 = new THREE.Vector3( 1, 0, 0 );
+
+var planeEquals = function ( a, b, tolerance ) {
+	tolerance = tolerance || 0.0001;
+	if( a.normal.distanceTo( b.normal ) > tolerance ) {
+		return false;
+	}
+	if( Math.abs( a.constant - b.constant ) > tolerance ) {
+		return false;
+	}
+	return true;
+};
+
+test( "constructor", function() {
+	var a = new THREE.Frustum();
+
+	ok( a.planes !== undefined, "Passed!" );
+	ok( a.planes.length === 6, "Passed!" );
+
+	var pDefault = new THREE.Plane();
+	for( var i = 0; i < 6; i ++ ) {
+		ok( a.planes[i].equals( pDefault ), "Passed!" );
+	}
+
+	var p0 = new THREE.Plane( unit3, -1 );
+	var p1 = new THREE.Plane( unit3, 1 );
+	var p2 = new THREE.Plane( unit3, 2 );
+	var p3 = new THREE.Plane( unit3, 3 );
+	var p4 = new THREE.Plane( unit3, 4 );
+	var p5 = new THREE.Plane( unit3, 5 );
+
+	a = new THREE.Frustum( p0, p1, p2, p3, p4, p5 );
+	ok( a.planes[0].equals( p0 ), "Passed!" );
+	ok( a.planes[1].equals( p1 ), "Passed!" );
+	ok( a.planes[2].equals( p2 ), "Passed!" );
+	ok( a.planes[3].equals( p3 ), "Passed!" );
+	ok( a.planes[4].equals( p4 ), "Passed!" );
+	ok( a.planes[5].equals( p5 ), "Passed!" );
+});
+
+test( "copy", function() {
+
+	var p0 = new THREE.Plane( unit3, -1 );
+	var p1 = new THREE.Plane( unit3, 1 );
+	var p2 = new THREE.Plane( unit3, 2 );
+	var p3 = new THREE.Plane( unit3, 3 );
+	var p4 = new THREE.Plane( unit3, 4 );
+	var p5 = new THREE.Plane( unit3, 5 );
+
+	var b = new THREE.Frustum( p0, p1, p2, p3, p4, p5 );
+	var a = new THREE.Frustum().copy( b );
+	ok( a.planes[0].equals( p0 ), "Passed!" );
+	ok( a.planes[1].equals( p1 ), "Passed!" );
+	ok( a.planes[2].equals( p2 ), "Passed!" );
+	ok( a.planes[3].equals( p3 ), "Passed!" );
+	ok( a.planes[4].equals( p4 ), "Passed!" );
+	ok( a.planes[5].equals( p5 ), "Passed!" );
+
+	// ensure it is a true copy by modifying source
+	b.planes[0] = p1;
+	ok( a.planes[0].equals( p0 ), "Passed!" );
+});
+
+test( "setFromMatrix/makeOrthographic/containsPoint", function() {
+	var m = new THREE.Matrix4().makeOrthographic( -1, 1, -1, 1, 1, 100 )
+	var a = new THREE.Frustum().setFromMatrix( m );
+
+	ok( ! a.containsPoint( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 0, 0, -50 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 0, 0, -1.001 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( -1, -1, -1.001 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( -1.1, -1.1, -1.001 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 1, 1, -1.001 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( 1.1, 1.1, -1.001 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 0, 0, -100 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( -1, -1, -100 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( -1.1, -1.1, -100.1 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 1, 1, -100 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( 1.1, 1.1, -100.1 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( 0, 0, -101 ) ), "Passed!" );
+
+});
+
+test( "setFromMatrix/makeFrustum/containsPoint", function() {
+	var m = new THREE.Matrix4().makeFrustum( -1, 1, -1, 1, 1, 100 )
+	var a = new THREE.Frustum().setFromMatrix( m );
+
+	ok( ! a.containsPoint( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 0, 0, -50 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 0, 0, -1.001 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( -1, -1, -1.001 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( -1.1, -1.1, -1.001 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 1, 1, -1.001 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( 1.1, 1.1, -1.001 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 0, 0, -99.999 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( -99.999, -99.999, -99.999 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( -100.1, -100.1, -100.1 ) ), "Passed!" );
+	ok( a.containsPoint( new THREE.Vector3( 99.999, 99.999, -99.999 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( 100.1, 100.1, -100.1 ) ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( 0, 0, -101 ) ), "Passed!" );
+});
+
+test( "setFromMatrix/makeFrustum/intersectsSphere", function() {
+	var m = new THREE.Matrix4().makeFrustum( -1, 1, -1, 1, 1, 100 )
+	var a = new THREE.Frustum().setFromMatrix( m );
+
+	ok( ! a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 0, 0, 0 ), 0 ) ), "Passed!" );
+	ok( ! a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 0, 0, 0 ), 0.9 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 0, 0, 0 ), 1.1 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 0, 0, -50 ), 0 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 0, 0, -1.001 ), 0 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( -1, -1, -1.001 ), 0 ) ), "Passed!" );
+	ok( ! a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( -1.1, -1.1, -1.001 ), 0 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( -1.1, -1.1, -1.001 ), 0.5 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 1, 1, -1.001 ), 0 ) ), "Passed!" );
+	ok( ! a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 1.1, 1.1, -1.001 ), 0 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 1.1, 1.1, -1.001 ), 0.5 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 0, 0, -99.999 ), 0 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( -99.999, -99.999, -99.999 ), 0 ) ), "Passed!" );
+	ok( ! a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( -100.1, -100.1, -100.1 ), 0 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( -100.1, -100.1, -100.1 ), 0.5 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 99.999, 99.999, -99.999 ), 0 ) ), "Passed!" );
+	ok( ! a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 100.1, 100.1, -100.1 ), 0 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 100.1, 100.1, -100.1 ), 0.2 ) ), "Passed!" );
+	ok( ! a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 0, 0, -101 ), 0 ) ), "Passed!" );
+	ok( a.intersectsSphere( new THREE.Sphere( new THREE.Vector3( 0, 0, -101 ), 1.1 ) ), "Passed!" );
+});
+
+test( "clone", function() {
+
+	var p0 = new THREE.Plane( unit3, -1 );
+	var p1 = new THREE.Plane( unit3, 1 );
+	var p2 = new THREE.Plane( unit3, 2 );
+	var p3 = new THREE.Plane( unit3, 3 );
+	var p4 = new THREE.Plane( unit3, 4 );
+	var p5 = new THREE.Plane( unit3, 5 );
+
+	var b = new THREE.Frustum( p0, p1, p2, p3, p4, p5 );
+	var a = b.clone();
+	ok( a.planes[0].equals( p0 ), "Passed!" );
+	ok( a.planes[1].equals( p1 ), "Passed!" );
+	ok( a.planes[2].equals( p2 ), "Passed!" );
+	ok( a.planes[3].equals( p3 ), "Passed!" );
+	ok( a.planes[4].equals( p4 ), "Passed!" );
+	ok( a.planes[5].equals( p5 ), "Passed!" );
+
+	// ensure it is a true copy by modifying source
+	a.planes[0].copy( p1 );
+	ok( b.planes[0].equals( p0 ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Line3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Line3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Line3.js	(revision 28897)
@@ -0,0 +1,69 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Line3" );
+
+test( "constructor/equals", function() {
+	var a = new THREE.Line3();
+	ok( a.start.equals( zero3 ), "Passed!" );
+	ok( a.end.equals( zero3 ), "Passed!" );
+
+	a = new THREE.Line3( two3.clone(), one3.clone() );
+	ok( a.start.equals( two3 ), "Passed!" );
+	ok( a.end.equals( one3 ), "Passed!" );
+});
+
+test( "copy/equals", function() {
+	var a = new THREE.Line3( zero3.clone(), one3.clone() );
+	var b = new THREE.Line3().copy( a );
+	ok( b.start.equals( zero3 ), "Passed!" );
+	ok( b.end.equals( one3 ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.start = zero3;
+	a.end = one3;
+	ok( b.start.equals( zero3 ), "Passed!" );
+	ok( b.end.equals( one3 ), "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Line3();
+
+	a.set( one3, one3 );
+	ok( a.start.equals( one3 ), "Passed!" );
+	ok( a.end.equals( one3 ), "Passed!" );
+});
+
+test( "at", function() {
+	var a = new THREE.Line3( one3.clone(), new THREE.Vector3( 1, 1, 2 ) );
+
+	ok( a.at( -1 ).distanceTo( new THREE.Vector3( 1, 1, 0 ) ) < 0.0001, "Passed!" );
+	ok( a.at( 0 ).distanceTo( one3.clone() ) < 0.0001, "Passed!" );
+	ok( a.at( 1 ).distanceTo( new THREE.Vector3( 1, 1, 2 ) ) < 0.0001, "Passed!" );
+	ok( a.at( 2 ).distanceTo( new THREE.Vector3( 1, 1, 3 ) ) < 0.0001, "Passed!" );
+});
+
+test( "closestPointToPoint/closestPointToPointParameter", function() {
+	var a = new THREE.Line3( one3.clone(), new THREE.Vector3( 1, 1, 2 ) );
+
+	// nearby the ray
+	ok( a.closestPointToPointParameter( zero3.clone(), true ) == 0, "Passed!" );
+	var b1 = a.closestPointToPoint( zero3.clone(), true );
+	ok( b1.distanceTo( new THREE.Vector3( 1, 1, 1 ) ) < 0.0001, "Passed!" );
+
+	// nearby the ray
+	ok( a.closestPointToPointParameter( zero3.clone(), false ) == -1, "Passed!" );
+	var b2 = a.closestPointToPoint( zero3.clone(), false );
+	ok( b2.distanceTo( new THREE.Vector3( 1, 1, 0 ) ) < 0.0001, "Passed!" );
+
+	// nearby the ray
+	ok( a.closestPointToPointParameter( new THREE.Vector3( 1, 1, 5 ), true ) == 1, "Passed!" );
+	var b = a.closestPointToPoint( new THREE.Vector3( 1, 1, 5 ), true );
+	ok( b.distanceTo( new THREE.Vector3( 1, 1, 2 ) ) < 0.0001, "Passed!" );
+
+	// exactly on the ray
+	ok( a.closestPointToPointParameter( one3.clone(), true ) == 0, "Passed!" );
+	var c = a.closestPointToPoint( one3.clone(), true );
+	ok( c.distanceTo( one3.clone() ) < 0.0001, "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Matrix3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Matrix3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Matrix3.js	(revision 28897)
@@ -0,0 +1,209 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Matrix3" );
+
+var matrixEquals3 = function( a, b, tolerance ) {
+	tolerance = tolerance || 0.0001;
+	if( a.elements.length != b.elements.length ) {
+		return false;
+	}
+	for( var i = 0, il = a.elements.length; i < il; i ++ ) {
+		var delta = a.elements[i] - b.elements[i];
+		if( delta > tolerance ) {
+			return false;
+		}
+	}
+	return true;
+};
+
+
+var toMatrix4 = function( m3 ) {
+	var result = new THREE.Matrix4();
+	var re = result.elements;
+	var me = m3.elements;
+	re[0] = me[0];
+	re[1] = me[1];
+	re[2] = me[2];
+	re[4] = me[3];
+	re[5] = me[4];
+	re[6] = me[5];
+	re[8] = me[6];
+	re[9] = me[7];
+	re[10] = me[8];
+
+	return result;
+};
+
+test( "constructor", function() {
+	var a = new THREE.Matrix3();
+	ok( a.determinant() == 1, "Passed!" );
+
+	var b = new THREE.Matrix3( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+	ok( b.elements[0] == 0 );
+	ok( b.elements[1] == 3 );
+	ok( b.elements[2] == 6 );
+	ok( b.elements[3] == 1 );
+	ok( b.elements[4] == 4 );
+	ok( b.elements[5] == 7 );
+	ok( b.elements[6] == 2 );
+	ok( b.elements[7] == 5 );
+	ok( b.elements[8] == 8 );
+
+	ok( ! matrixEquals3( a, b ), "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Matrix3( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+	var b = new THREE.Matrix3().copy( a );
+
+	ok( matrixEquals3( a, b ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.elements[0] = 2;
+	ok( ! matrixEquals3( a, b ), "Passed!" );
+});
+
+test( "set", function() {
+	var b = new THREE.Matrix3();
+	ok( b.determinant() == 1, "Passed!" );
+
+	b.set( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+	ok( b.elements[0] == 0 );
+	ok( b.elements[1] == 3 );
+	ok( b.elements[2] == 6 );
+	ok( b.elements[3] == 1 );
+	ok( b.elements[4] == 4 );
+	ok( b.elements[5] == 7 );
+	ok( b.elements[6] == 2 );
+	ok( b.elements[7] == 5 );
+	ok( b.elements[8] == 8 );
+});
+
+test( "identity", function() {
+	var b = new THREE.Matrix3( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+	ok( b.elements[0] == 0 );
+	ok( b.elements[1] == 3 );
+	ok( b.elements[2] == 6 );
+	ok( b.elements[3] == 1 );
+	ok( b.elements[4] == 4 );
+	ok( b.elements[5] == 7 );
+	ok( b.elements[6] == 2 );
+	ok( b.elements[7] == 5 );
+	ok( b.elements[8] == 8 );
+
+	var a = new THREE.Matrix3();
+	ok( ! matrixEquals3( a, b ), "Passed!" );
+
+	b.identity();
+	ok( matrixEquals3( a, b ), "Passed!" );
+});
+
+test( "multiplyScalar", function() {
+	var b = new THREE.Matrix3( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+	ok( b.elements[0] == 0 );
+	ok( b.elements[1] == 3 );
+	ok( b.elements[2] == 6 );
+	ok( b.elements[3] == 1 );
+	ok( b.elements[4] == 4 );
+	ok( b.elements[5] == 7 );
+	ok( b.elements[6] == 2 );
+	ok( b.elements[7] == 5 );
+	ok( b.elements[8] == 8 );
+
+	b.multiplyScalar( 2 );
+	ok( b.elements[0] == 0*2 );
+	ok( b.elements[1] == 3*2 );
+	ok( b.elements[2] == 6*2 );
+	ok( b.elements[3] == 1*2 );
+	ok( b.elements[4] == 4*2 );
+	ok( b.elements[5] == 7*2 );
+	ok( b.elements[6] == 2*2 );
+	ok( b.elements[7] == 5*2 );
+	ok( b.elements[8] == 8*2 );
+});
+
+test( "determinant", function() {
+	var a = new THREE.Matrix3();
+	ok( a.determinant() == 1, "Passed!" );
+
+	a.elements[0] = 2;
+	ok( a.determinant() == 2, "Passed!" );
+
+	a.elements[0] = 0;
+	ok( a.determinant() == 0, "Passed!" );
+
+	// calculated via http://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/threeD/index.htm
+	a.set( 2, 3, 4, 5, 13, 7, 8, 9, 11 );
+	ok( a.determinant() == -73, "Passed!" );
+});
+
+
+test( "getInverse", function() {
+	var identity = new THREE.Matrix4();
+	var a = new THREE.Matrix4();
+	var b = new THREE.Matrix3( 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+	var c = new THREE.Matrix4( 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+	ok( ! matrixEquals3( a, b ), "Passed!" );
+	b.getInverse( a, false );
+	ok( matrixEquals3( b, new THREE.Matrix3() ), "Passed!" );
+
+	try { 
+		b.getInverse( c, true );
+		ok( false, "Passed!" ); // should never get here.
+	}
+	catch( err ) {
+		ok( true, "Passed!" );
+	}
+
+	var testMatrices = [
+		new THREE.Matrix4().makeRotationX( 0.3 ),
+		new THREE.Matrix4().makeRotationX( -0.3 ),
+		new THREE.Matrix4().makeRotationY( 0.3 ),
+		new THREE.Matrix4().makeRotationY( -0.3 ),
+		new THREE.Matrix4().makeRotationZ( 0.3 ),
+		new THREE.Matrix4().makeRotationZ( -0.3 ),
+		new THREE.Matrix4().makeScale( 1, 2, 3 ),
+		new THREE.Matrix4().makeScale( 1/8, 1/2, 1/3 )
+		];
+
+	for( var i = 0, il = testMatrices.length; i < il; i ++ ) {
+		var m = testMatrices[i];
+		var mInverse3 = new THREE.Matrix3().getInverse( m );
+
+		var mInverse = toMatrix4( mInverse3 );
+
+		// the determinant of the inverse should be the reciprocal
+		ok( Math.abs( m.determinant() * mInverse3.determinant() - 1 ) < 0.0001, "Passed!" );
+		ok( Math.abs( m.determinant() * mInverse.determinant() - 1 ) < 0.0001, "Passed!" );
+
+		var mProduct = new THREE.Matrix4().multiplyMatrices( m, mInverse );
+		ok( Math.abs( mProduct.determinant() - 1 ) < 0.0001, "Passed!" );
+		ok( matrixEquals3( mProduct, identity ), "Passed!" );
+	}
+});
+
+test( "transpose", function() {
+	var a = new THREE.Matrix3();
+	var b = a.clone().transpose();
+	ok( matrixEquals3( a, b ), "Passed!" );
+
+	b = new THREE.Matrix3( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+	var c = b.clone().transpose();
+	ok( ! matrixEquals3( b, c ), "Passed!" ); 
+	c.transpose();
+	ok( matrixEquals3( b, c ), "Passed!" ); 
+});
+
+test( "clone", function() {
+	var a = new THREE.Matrix3( 0, 1, 2, 3, 4, 5, 6, 7, 8 );
+	var b = a.clone();
+
+	ok( matrixEquals3( a, b ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.elements[0] = 2;
+	ok( ! matrixEquals3( a, b ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Matrix4.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Matrix4.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Matrix4.js	(revision 28897)
@@ -0,0 +1,299 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Matrix4" );
+
+var matrixEquals4 = function( a, b, tolerance ) {
+	tolerance = tolerance || 0.0001;
+	if( a.elements.length != b.elements.length ) {
+		return false;
+	}
+	for( var i = 0, il = a.elements.length; i < il; i ++ ) {
+		var delta = a.elements[i] - b.elements[i];
+		if( delta > tolerance ) {
+			return false;
+		}
+	}
+	return true;
+};
+
+test( "constructor", function() {
+	var a = new THREE.Matrix4();
+	ok( a.determinant() == 1, "Passed!" );
+
+	var b = new THREE.Matrix4( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+	ok( b.elements[0] == 0 );
+	ok( b.elements[1] == 4 );
+	ok( b.elements[2] == 8 );
+	ok( b.elements[3] == 12 );
+	ok( b.elements[4] == 1 );
+	ok( b.elements[5] == 5 );
+	ok( b.elements[6] == 9 );
+	ok( b.elements[7] == 13 );
+	ok( b.elements[8] == 2 );
+	ok( b.elements[9] == 6 );
+	ok( b.elements[10] == 10 );
+	ok( b.elements[11] == 14 );
+	ok( b.elements[12] == 3 );
+	ok( b.elements[13] == 7 );
+	ok( b.elements[14] == 11 );
+	ok( b.elements[15] == 15 );
+
+	ok( ! matrixEquals4( a, b ), "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Matrix4( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+	var b = new THREE.Matrix4().copy( a );
+
+	ok( matrixEquals4( a, b ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.elements[0] = 2;
+	ok( ! matrixEquals4( a, b ), "Passed!" );
+});
+
+test( "set", function() {
+	var b = new THREE.Matrix4();
+	ok( b.determinant() == 1, "Passed!" );
+
+	b.set( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+	ok( b.elements[0] == 0 );
+	ok( b.elements[1] == 4 );
+	ok( b.elements[2] == 8 );
+	ok( b.elements[3] == 12 );
+	ok( b.elements[4] == 1 );
+	ok( b.elements[5] == 5 );
+	ok( b.elements[6] == 9 );
+	ok( b.elements[7] == 13 );
+	ok( b.elements[8] == 2 );
+	ok( b.elements[9] == 6 );
+	ok( b.elements[10] == 10 );
+	ok( b.elements[11] == 14 );
+	ok( b.elements[12] == 3 );
+	ok( b.elements[13] == 7 );
+	ok( b.elements[14] == 11 );
+	ok( b.elements[15] == 15 );
+});
+
+test( "identity", function() {
+	var b = new THREE.Matrix4( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+	ok( b.elements[0] == 0 );
+	ok( b.elements[1] == 4 );
+	ok( b.elements[2] == 8 );
+	ok( b.elements[3] == 12 );
+	ok( b.elements[4] == 1 );
+	ok( b.elements[5] == 5 );
+	ok( b.elements[6] == 9 );
+	ok( b.elements[7] == 13 );
+	ok( b.elements[8] == 2 );
+	ok( b.elements[9] == 6 );
+	ok( b.elements[10] == 10 );
+	ok( b.elements[11] == 14 );
+	ok( b.elements[12] == 3 );
+	ok( b.elements[13] == 7 );
+	ok( b.elements[14] == 11 );
+	ok( b.elements[15] == 15 );
+
+	var a = new THREE.Matrix4();
+	ok( ! matrixEquals4( a, b ), "Passed!" );
+
+	b.identity();
+	ok( matrixEquals4( a, b ), "Passed!" );
+});
+
+test( "multiplyScalar", function() {
+	var b = new THREE.Matrix4( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+	ok( b.elements[0] == 0 );
+	ok( b.elements[1] == 4 );
+	ok( b.elements[2] == 8 );
+	ok( b.elements[3] == 12 );
+	ok( b.elements[4] == 1 );
+	ok( b.elements[5] == 5 );
+	ok( b.elements[6] == 9 );
+	ok( b.elements[7] == 13 );
+	ok( b.elements[8] == 2 );
+	ok( b.elements[9] == 6 );
+	ok( b.elements[10] == 10 );
+	ok( b.elements[11] == 14 );
+	ok( b.elements[12] == 3 );
+	ok( b.elements[13] == 7 );
+	ok( b.elements[14] == 11 );
+	ok( b.elements[15] == 15 );
+
+	b.multiplyScalar( 2 );
+	ok( b.elements[0] == 0*2 );
+	ok( b.elements[1] == 4*2 );
+	ok( b.elements[2] == 8*2 );
+	ok( b.elements[3] == 12*2 );
+	ok( b.elements[4] == 1*2 );
+	ok( b.elements[5] == 5*2 );
+	ok( b.elements[6] == 9*2 );
+	ok( b.elements[7] == 13*2 );
+	ok( b.elements[8] == 2*2 );
+	ok( b.elements[9] == 6*2 );
+	ok( b.elements[10] == 10*2 );
+	ok( b.elements[11] == 14*2 );
+	ok( b.elements[12] == 3*2 );
+	ok( b.elements[13] == 7*2 );
+	ok( b.elements[14] == 11*2 );
+	ok( b.elements[15] == 15*2 );
+});
+
+test( "determinant", function() {
+	var a = new THREE.Matrix4();
+	ok( a.determinant() == 1, "Passed!" );
+
+	a.elements[0] = 2;
+	ok( a.determinant() == 2, "Passed!" );
+
+	a.elements[0] = 0;
+	ok( a.determinant() == 0, "Passed!" );
+
+	// calculated via http://www.euclideanspace.com/maths/algebra/matrix/functions/determinant/fourD/index.htm
+	a.set( 2, 3, 4, 5, -1, -21, -3, -4, 6, 7, 8, 10, -8, -9, -10, -12 );
+	ok( a.determinant() == 76, "Passed!" );
+});
+
+test( "getInverse", function() {
+	var identity = new THREE.Matrix4();
+
+	var a = new THREE.Matrix4();
+	var b = new THREE.Matrix4( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+	var c = new THREE.Matrix4( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+	ok( ! matrixEquals4( a, b ), "Passed!" );
+	b.getInverse( a, false );
+	ok( matrixEquals4( b, new THREE.Matrix4() ), "Passed!" );
+
+	try { 
+		b.getInverse( c, true );
+		ok( false, "Passed!" ); // should never get here.
+	}
+	catch( err ) {
+		ok( true, "Passed!" );
+	}
+
+	var testMatrices = [
+		new THREE.Matrix4().makeRotationX( 0.3 ),
+		new THREE.Matrix4().makeRotationX( -0.3 ),
+		new THREE.Matrix4().makeRotationY( 0.3 ),
+		new THREE.Matrix4().makeRotationY( -0.3 ),
+		new THREE.Matrix4().makeRotationZ( 0.3 ),
+		new THREE.Matrix4().makeRotationZ( -0.3 ),
+		new THREE.Matrix4().makeScale( 1, 2, 3 ),
+		new THREE.Matrix4().makeScale( 1/8, 1/2, 1/3 ),
+		new THREE.Matrix4().makeFrustum( -1, 1, -1, 1, 1, 1000 ),
+		new THREE.Matrix4().makeFrustum( -16, 16, -9, 9, 0.1, 10000 ),
+		new THREE.Matrix4().makeTranslation( 1, 2, 3 )
+		];
+
+	for( var i = 0, il = testMatrices.length; i < il; i ++ ) {
+		var m = testMatrices[i];
+
+		var mInverse = new THREE.Matrix4().getInverse( m );
+		var mSelfInverse = m.clone();
+		mSelfInverse.getInverse( mSelfInverse );
+
+
+		// self-inverse should the same as inverse
+		ok( matrixEquals4( mSelfInverse, mInverse ), "Passed!" );
+
+		// the determinant of the inverse should be the reciprocal
+		ok( Math.abs( m.determinant() * mInverse.determinant() - 1 ) < 0.0001, "Passed!" );
+
+		var mProduct = new THREE.Matrix4().multiplyMatrices( m, mInverse );
+
+		// the determinant of the identity matrix is 1
+		ok( Math.abs( mProduct.determinant() - 1 ) < 0.0001, "Passed!" );
+		ok( matrixEquals4( mProduct, identity ), "Passed!" );
+	}
+});
+
+test( "transpose", function() {
+	var a = new THREE.Matrix4();
+	var b = a.clone().transpose();
+	ok( matrixEquals4( a, b ), "Passed!" );
+
+	b = new THREE.Matrix4( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+	var c = b.clone().transpose();
+	ok( ! matrixEquals4( b, c ), "Passed!" ); 
+	c.transpose();
+	ok( matrixEquals4( b, c ), "Passed!" ); 
+});
+
+test( "clone", function() {
+	var a = new THREE.Matrix4( 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 );
+	var b = a.clone();
+
+	ok( matrixEquals4( a, b ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.elements[0] = 2;
+	ok( ! matrixEquals4( a, b ), "Passed!" );
+});
+
+
+test( "compose/decompose", function() {
+	var tValues = [
+		new THREE.Vector3(),
+		new THREE.Vector3( 3, 0, 0 ),
+		new THREE.Vector3( 0, 4, 0 ),
+		new THREE.Vector3( 0, 0, 5 ),
+		new THREE.Vector3( -6, 0, 0 ),
+		new THREE.Vector3( 0, -7, 0 ),
+		new THREE.Vector3( 0, 0, -8 ),
+		new THREE.Vector3( -2, 5, -9 ),
+		new THREE.Vector3( -2, -5, -9 )
+	];
+
+	var sValues = [
+		new THREE.Vector3( 1, 1, 1 ),
+		new THREE.Vector3( 2, 2, 2 ),
+		new THREE.Vector3( 1, -1, 1 ),
+		new THREE.Vector3( -1, 1, 1 ),
+		new THREE.Vector3( 1, 1, -1 ),
+		new THREE.Vector3( 2, -2, 1 ),
+		new THREE.Vector3( -1, 2, -2 ),
+		new THREE.Vector3( -1, -1, -1 ),
+		new THREE.Vector3( -2, -2, -2 )
+	];
+
+	var rValues = [
+		new THREE.Quaternion(),
+		new THREE.Quaternion().setFromEuler( new THREE.Euler( 1, 1, 0 ) ),
+		new THREE.Quaternion().setFromEuler( new THREE.Euler( 1, -1, 1 ) ),
+		new THREE.Quaternion( 0, 0.9238795292366128, 0, 0.38268342717215614 )
+	];
+
+
+	for( var ti = 0; ti < tValues.length; ti ++ ) {
+		for( var si = 0; si < sValues.length; si ++ ) {
+			for( var ri = 0; ri < rValues.length; ri ++ ) {
+				var t = tValues[ti];
+				var s = sValues[si];
+				var r = rValues[ri];
+
+				var m = new THREE.Matrix4().compose( t, r, s );
+				var t2 = new THREE.Vector3();
+				var r2 = new THREE.Quaternion();
+				var s2 = new THREE.Vector3();
+
+				m.decompose( t2, r2, s2 );
+
+				var m2 = new THREE.Matrix4().compose( t2, r2, s2 );
+			
+				var matrixIsSame = matrixEquals4( m, m2 );
+				/* debug code
+				if( ! matrixIsSame ) {
+					console.log( t, s, r );
+					console.log( t2, s2, r2 );
+					console.log( m, m2 );
+				}*/
+				ok( matrixEquals4( m, m2 ), "Passed!" );
+
+			}
+		}
+	}
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Plane.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Plane.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Plane.js	(revision 28897)
@@ -0,0 +1,197 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Plane" );
+
+var comparePlane = function ( a, b, threshold ) {
+	threshold = threshold || 0.0001;
+	return ( a.normal.distanceTo( b.normal ) < threshold &&
+	Math.abs( a.constant - b.constant ) < threshold );
+};
+
+
+test( "constructor", function() {
+	var a = new THREE.Plane();
+	ok( a.normal.x == 1, "Passed!" );
+	ok( a.normal.y == 0, "Passed!" );
+	ok( a.normal.z == 0, "Passed!" );
+	ok( a.constant == 0, "Passed!" );
+
+	a = new THREE.Plane( one3.clone(), 0 );
+	ok( a.normal.x == 1, "Passed!" );
+	ok( a.normal.y == 1, "Passed!" );
+	ok( a.normal.z == 1, "Passed!" );
+	ok( a.constant == 0, "Passed!" );
+
+	a = new THREE.Plane( one3.clone(), 1 );
+	ok( a.normal.x == 1, "Passed!" );
+	ok( a.normal.y == 1, "Passed!" );
+	ok( a.normal.z == 1, "Passed!" );
+	ok( a.constant == 1, "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Plane( new THREE.Vector3( x, y, z ), w );
+	var b = new THREE.Plane().copy( a );
+	ok( b.normal.x == x, "Passed!" );
+	ok( b.normal.y == y, "Passed!" );
+	ok( b.normal.z == z, "Passed!" );
+	ok( b.constant == w, "Passed!" );
+
+	// ensure that it is a true copy
+	a.normal.x = 0;
+	a.normal.y = -1;
+	a.normal.z = -2;
+	a.constant = -3;
+	ok( b.normal.x == x, "Passed!" );
+	ok( b.normal.y == y, "Passed!" );
+	ok( b.normal.z == z, "Passed!" );
+	ok( b.constant == w, "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Plane();
+	ok( a.normal.x == 1, "Passed!" );
+	ok( a.normal.y == 0, "Passed!" );
+	ok( a.normal.z == 0, "Passed!" );
+	ok( a.constant == 0, "Passed!" );
+
+	var b = a.clone().set( new THREE.Vector3( x, y, z ), w );
+	ok( b.normal.x == x, "Passed!" );
+	ok( b.normal.y == y, "Passed!" );
+	ok( b.normal.z == z, "Passed!" );
+	ok( b.constant == w, "Passed!" );
+});
+
+test( "setComponents", function() {
+	var a = new THREE.Plane();
+	ok( a.normal.x == 1, "Passed!" );
+	ok( a.normal.y == 0, "Passed!" );
+	ok( a.normal.z == 0, "Passed!" );
+	ok( a.constant == 0, "Passed!" );
+
+	var b = a.clone().setComponents( x, y, z , w );
+	ok( b.normal.x == x, "Passed!" );
+	ok( b.normal.y == y, "Passed!" );
+	ok( b.normal.z == z, "Passed!" );
+	ok( b.constant == w, "Passed!" );
+});
+
+test( "setFromNormalAndCoplanarPoint", function() {
+	var normal = one3.clone().normalize();
+	var a = new THREE.Plane().setFromNormalAndCoplanarPoint( normal, zero3 );
+	
+	ok( a.normal.equals( normal ), "Passed!" );
+	ok( a.constant == 0, "Passed!" );
+});
+
+test( "normalize", function() {
+	var a = new THREE.Plane( new THREE.Vector3( 2, 0, 0 ), 2 );
+	
+	a.normalize();
+	ok( a.normal.length() == 1, "Passed!" );
+	ok( a.normal.equals( new THREE.Vector3( 1, 0, 0 ) ), "Passed!" );
+	ok( a.constant == 1, "Passed!" );
+});
+
+test( "negate/distanceToPoint", function() {
+	var a = new THREE.Plane( new THREE.Vector3( 2, 0, 0 ), -2 );
+	
+	a.normalize();
+	ok( a.distanceToPoint( new THREE.Vector3( 4, 0, 0 ) ) === 3, "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector3( 1, 0, 0 ) ) === 0, "Passed!" );
+
+	a.negate();
+	ok( a.distanceToPoint( new THREE.Vector3( 4, 0, 0 ) ) === -3, "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector3( 1, 0, 0 ) ) === 0, "Passed!" );
+});
+
+test( "distanceToPoint", function() {
+	var a = new THREE.Plane( new THREE.Vector3( 2, 0, 0 ), -2 );
+	
+	a.normalize();
+	ok( a.distanceToPoint( a.projectPoint( zero3.clone() ) ) === 0, "Passed!" );
+	ok( a.distanceToPoint( new THREE.Vector3( 4, 0, 0 ) ) === 3, "Passed!" );
+});
+
+test( "distanceToSphere", function() {
+	var a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 );
+
+	var b = new THREE.Sphere( new THREE.Vector3( 2, 0, 0 ), 1 );
+	
+	ok( a.distanceToSphere( b ) === 1, "Passed!" );
+
+	a.set( new THREE.Vector3( 1, 0, 0 ), 2 );
+	ok( a.distanceToSphere( b ) === 3, "Passed!" );
+	a.set( new THREE.Vector3( 1, 0, 0 ), -2 );
+	ok( a.distanceToSphere( b ) === -1, "Passed!" );
+});
+
+test( "isInterestionLine/intersectLine", function() {
+	var a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 );
+
+	var l1 = new THREE.Line3( new THREE.Vector3( -10, 0, 0 ), new THREE.Vector3( 10, 0, 0 ) );
+	ok( a.isIntersectionLine( l1 ), "Passed!" );
+	ok( a.intersectLine( l1 ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+
+	a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), -3 );
+
+	ok( a.isIntersectionLine( l1 ), "Passed!" );
+	ok( a.intersectLine( l1 ).equals( new THREE.Vector3( 3, 0, 0 ) ), "Passed!" );
+
+
+	a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), -11 );
+
+	ok( ! a.isIntersectionLine( l1 ), "Passed!" );
+	ok( a.intersectLine( l1 ) === undefined, "Passed!" );
+	
+	a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 11 );
+
+	ok( ! a.isIntersectionLine( l1 ), "Passed!" );
+	ok( a.intersectLine( l1 ) === undefined, "Passed!" );
+
+});
+
+test( "projectPoint", function() {
+	var a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 );
+
+	ok( a.projectPoint( new THREE.Vector3( 10, 0, 0 ) ).equals( zero3 ), "Passed!" );
+	ok( a.projectPoint( new THREE.Vector3( -10, 0, 0 ) ).equals( zero3 ), "Passed!" );
+
+	a = new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), -1 );
+	ok( a.projectPoint( new THREE.Vector3( 0, 0, 0 ) ).equals( new THREE.Vector3( 0, 1, 0 ) ), "Passed!" );
+	ok( a.projectPoint( new THREE.Vector3( 0, 1, 0 ) ).equals( new THREE.Vector3( 0, 1, 0 ) ), "Passed!" );
+	
+});
+
+test( "orthoPoint", function() {
+	var a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 );
+
+	ok( a.orthoPoint( new THREE.Vector3( 10, 0, 0 ) ).equals( new THREE.Vector3( 10, 0, 0 ) ), "Passed!" );
+	ok( a.orthoPoint( new THREE.Vector3( -10, 0, 0 ) ).equals( new THREE.Vector3( -10, 0, 0 ) ), "Passed!" );
+});
+
+test( "coplanarPoint", function() {
+	var a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 );
+	ok( a.distanceToPoint( a.coplanarPoint() ) === 0, "Passed!" );
+
+	a = new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), -1 );
+	ok( a.distanceToPoint( a.coplanarPoint() ) === 0, "Passed!" );
+});
+
+test( "applyMatrix4/translate", function() {
+
+	var a = new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 );
+
+	var m = new THREE.Matrix4();
+	m.makeRotationZ( Math.PI * 0.5 );
+
+	ok( comparePlane( a.clone().applyMatrix4( m ), new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), 0 ) ), "Passed!" );
+
+	a = new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), -1 );
+	ok( comparePlane( a.clone().applyMatrix4( m ), new THREE.Plane( new THREE.Vector3( -1, 0, 0 ), -1 ) ), "Passed!" );
+
+	m.makeTranslation( 1, 1, 1 );
+	ok( comparePlane( a.clone().applyMatrix4( m ), a.clone().translate( new THREE.Vector3( 1, 1, 1 ) ) ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Quaternion.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Quaternion.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Quaternion.js	(revision 28897)
@@ -0,0 +1,211 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Quaternion" );
+
+var orders = [ 'XYZ', 'YXZ', 'ZXY', 'ZYX', 'YZX', 'XZY' ];
+var eulerAngles = new THREE.Euler( 0.1, -0.3, 0.25 );
+
+
+
+var qSub = function ( a, b ) {
+	var result = new THREE.Quaternion();
+	result.copy( a );
+
+	result.x -= b.x;
+	result.y -= b.y;
+	result.z -= b.z;
+	result.w -= b.w;
+
+	return result;
+
+};
+
+test( "constructor", function() {
+	var a = new THREE.Quaternion();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+	ok( a.w == 1, "Passed!" );
+
+	a = new THREE.Quaternion( x, y, z, w );
+	ok( a.x === x, "Passed!" );
+	ok( a.y === y, "Passed!" );
+	ok( a.z === z, "Passed!" );
+	ok( a.w === w, "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Quaternion( x, y, z, w );
+	var b = new THREE.Quaternion().copy( a );
+	ok( b.x == x, "Passed!" );
+	ok( b.y == y, "Passed!" );
+	ok( b.z == z, "Passed!" );
+	ok( b.w == w, "Passed!" );
+
+	// ensure that it is a true copy
+	a.x = 0;
+	a.y = -1;
+	a.z = 0;
+	a.w = -1;
+	ok( b.x == x, "Passed!" );
+	ok( b.y == y, "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Quaternion();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+	ok( a.w == 1, "Passed!" );
+
+	a.set( x, y, z, w );
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+	ok( a.z === z, "Passed!" );
+	ok( a.w === w, "Passed!" );
+});
+
+test( "setFromAxisAngle", function() {
+
+	// TODO: find cases to validate.
+	ok( true, "Passed!" );
+
+	var zero = new THREE.Quaternion();
+
+	var a = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), 0 );
+	ok( a.equals( zero ), "Passed!" );
+	a = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 0, 1, 0 ), 0 );
+	ok( a.equals( zero ), "Passed!" );
+	a = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), 0 );
+	ok( a.equals( zero ), "Passed!" );
+
+	var b1 = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), Math.PI );
+	ok( ! a.equals( b1 ), "Passed!" );
+	var b2 = new THREE.Quaternion().setFromAxisAngle( new THREE.Vector3( 1, 0, 0 ), -Math.PI );
+	ok( ! a.equals( b2 ), "Passed!" );
+
+	b1.multiply( b2 );
+	ok( a.equals( b1 ), "Passed!" );
+});
+
+
+test( "setFromEuler/setFromQuaternion", function() {
+
+	var angles = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ];
+
+	// ensure euler conversion to/from Quaternion matches.
+	for( var i = 0; i < orders.length; i ++ ) {
+		for( var j = 0; j < angles.length; j ++ ) {
+			var eulers2 = new THREE.Euler().setFromQuaternion( new THREE.Quaternion().setFromEuler( new THREE.Euler( angles[j].x, angles[j].y, angles[j].z, orders[i] ) ), orders[i] );
+			var newAngle = new THREE.Vector3( eulers2.x, eulers2.y, eulers2.z );
+			ok( newAngle.distanceTo( angles[j] ) < 0.001, "Passed!" );
+		}
+	}
+
+});
+
+test( "setFromEuler/setFromRotationMatrix", function() {
+
+	// ensure euler conversion for Quaternion matches that of Matrix4
+	for( var i = 0; i < orders.length; i ++ ) {
+		var q = new THREE.Quaternion().setFromEuler( eulerAngles, orders[i] );
+		var m = new THREE.Matrix4().makeRotationFromEuler( eulerAngles, orders[i] );
+		var q2 = new THREE.Quaternion().setFromRotationMatrix( m );
+
+		ok( qSub( q, q2 ).length() < 0.001, "Passed!" );
+	}
+
+});
+
+test( "normalize/length/lengthSq", function() {
+	var a = new THREE.Quaternion( x, y, z, w );
+	var b = new THREE.Quaternion( -x, -y, -z, -w );
+
+	ok( a.length() != 1, "Passed!");
+	ok( a.lengthSq() != 1, "Passed!");
+	a.normalize();
+	ok( a.length() == 1, "Passed!");
+	ok( a.lengthSq() == 1, "Passed!");
+
+	a.set( 0, 0, 0, 0 );
+	ok( a.lengthSq() == 0, "Passed!");
+	ok( a.length() == 0, "Passed!");
+	a.normalize();
+	ok( a.lengthSq() == 1, "Passed!");
+	ok( a.length() == 1, "Passed!");
+});
+
+test( "inverse/conjugate", function() {
+	var a = new THREE.Quaternion( x, y, z, w );
+
+	// TODO: add better validation here.
+
+	var b = a.clone().conjugate();
+
+	ok( a.x == -b.x, "Passed!" );
+	ok( a.y == -b.y, "Passed!" );
+	ok( a.z == -b.z, "Passed!" );
+	ok( a.w == b.w, "Passed!" );
+});
+
+
+test( "multiplyQuaternions/multiply", function() {
+
+	var angles = [ new THREE.Euler( 1, 0, 0 ), new THREE.Euler( 0, 1, 0 ), new THREE.Euler( 0, 0, 1 ) ];
+
+	var q1 = new THREE.Quaternion().setFromEuler( angles[0], "XYZ" );
+	var q2 = new THREE.Quaternion().setFromEuler( angles[1], "XYZ" );
+	var q3 = new THREE.Quaternion().setFromEuler( angles[2], "XYZ" );
+
+	var q = new THREE.Quaternion().multiplyQuaternions( q1, q2 ).multiply( q3 );
+
+	var m1 = new THREE.Matrix4().makeRotationFromEuler( angles[0], "XYZ" );
+	var m2 = new THREE.Matrix4().makeRotationFromEuler( angles[1], "XYZ" );
+	var m3 = new THREE.Matrix4().makeRotationFromEuler( angles[2], "XYZ" );
+
+	var m = new THREE.Matrix4().multiplyMatrices( m1, m2 ).multiply( m3 );
+
+	var qFromM = new THREE.Quaternion().setFromRotationMatrix( m );
+
+	ok( qSub( q, qFromM ).length() < 0.001, "Passed!" );
+});
+
+test( "multiplyVector3", function() {
+	
+	var angles = [ new THREE.Euler( 1, 0, 0 ), new THREE.Euler( 0, 1, 0 ), new THREE.Euler( 0, 0, 1 ) ];
+
+	// ensure euler conversion for Quaternion matches that of Matrix4
+	for( var i = 0; i < orders.length; i ++ ) {
+		for( var j = 0; j < angles.length; j ++ ) {
+			var q = new THREE.Quaternion().setFromEuler( angles[j], orders[i] );
+			var m = new THREE.Matrix4().makeRotationFromEuler( angles[j], orders[i] );
+
+			var v0 = new THREE.Vector3(1, 0, 0);
+			var qv = v0.clone().applyQuaternion( q );
+			var mv = v0.clone().applyMatrix4( m );
+		
+			ok( qv.distanceTo( mv ) < 0.001, "Passed!" );
+		}
+	}
+
+});
+
+test( "equals", function() {
+	var a = new THREE.Quaternion( x, y, z, w );
+	var b = new THREE.Quaternion( -x, -y, -z, -w );
+	
+	ok( a.x != b.x, "Passed!" );
+	ok( a.y != b.y, "Passed!" );
+
+	ok( ! a.equals( b ), "Passed!" );
+	ok( ! b.equals( a ), "Passed!" );
+
+	a.copy( b );
+	ok( a.x == b.x, "Passed!" );
+	ok( a.y == b.y, "Passed!" );
+
+	ok( a.equals( b ), "Passed!" );
+	ok( b.equals( a ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Ray.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Ray.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Ray.js	(revision 28897)
@@ -0,0 +1,258 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Ray" );
+
+test( "constructor/equals", function() {
+	var a = new THREE.Ray();
+	ok( a.origin.equals( zero3 ), "Passed!" );
+	ok( a.direction.equals( zero3 ), "Passed!" );
+
+	a = new THREE.Ray( two3.clone(), one3.clone() );
+	ok( a.origin.equals( two3 ), "Passed!" );
+	ok( a.direction.equals( one3 ), "Passed!" );
+});
+
+test( "copy/equals", function() {
+	var a = new THREE.Ray( zero3.clone(), one3.clone() );
+	var b = new THREE.Ray().copy( a );
+	ok( b.origin.equals( zero3 ), "Passed!" );
+	ok( b.direction.equals( one3 ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.origin = zero3;
+	a.direction = one3;
+	ok( b.origin.equals( zero3 ), "Passed!" );
+	ok( b.direction.equals( one3 ), "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Ray();
+
+	a.set( one3, one3 );
+	ok( a.origin.equals( one3 ), "Passed!" );
+	ok( a.direction.equals( one3 ), "Passed!" );
+});
+
+test( "at", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+
+	ok( a.at( 0 ).equals( one3 ), "Passed!" );
+	ok( a.at( -1 ).equals( new THREE.Vector3( 1, 1, 0 ) ), "Passed!" );
+	ok( a.at( 1 ).equals( new THREE.Vector3( 1, 1, 2 ) ), "Passed!" );
+});
+
+test( "recast/clone", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+
+	ok( a.recast( 0 ).equals( a ), "Passed!" );
+
+	var b = a.clone();
+	ok( b.recast( -1 ).equals( new THREE.Ray( new THREE.Vector3( 1, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ) ), "Passed!" );
+
+	var c = a.clone();
+	ok( c.recast( 1 ).equals( new THREE.Ray( new THREE.Vector3( 1, 1, 2 ), new THREE.Vector3( 0, 0, 1 ) ) ), "Passed!" );
+
+	var d = a.clone();
+	var e = d.clone().recast( 1 );
+	ok( d.equals( a ), "Passed!" );
+	ok( ! e.equals( d ), "Passed!" );
+	ok( e.equals( c ), "Passed!" );
+});
+
+test( "closestPointToPoint", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+
+	// behind the ray
+	var b = a.closestPointToPoint( zero3 );
+	ok( b.equals( one3 ), "Passed!" );
+
+	// front of the ray
+	var c = a.closestPointToPoint( new THREE.Vector3( 0, 0, 50 ) );
+	ok( c.equals( new THREE.Vector3( 1, 1, 50 ) ), "Passed!" );
+
+	// exactly on the ray
+	var d = a.closestPointToPoint( one3 );
+	ok( d.equals( one3 ), "Passed!" );
+});
+
+test( "distanceToPoint", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+
+	// behind the ray
+	var b = a.distanceToPoint( zero3 );
+	ok( b === Math.sqrt( 3 ), "Passed!" );
+
+	// front of the ray
+	var c = a.distanceToPoint( new THREE.Vector3( 0, 0, 50 ) );
+	ok( c === Math.sqrt( 2 ), "Passed!" );
+
+	// exactly on the ray
+	var d = a.distanceToPoint( one3 );
+	ok( d === 0, "Passed!" );
+});
+
+test( "isIntersectionSphere", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+	var b = new THREE.Sphere( zero3, 0.5 );
+	var c = new THREE.Sphere( zero3, 1.5 );
+	var d = new THREE.Sphere( one3, 0.1 );
+	var e = new THREE.Sphere( two3, 0.1 );
+	var f = new THREE.Sphere( two3, 1 );
+
+	ok( ! a.isIntersectionSphere( b ), "Passed!" );
+	ok( ! a.isIntersectionSphere( c ), "Passed!" );
+	ok( a.isIntersectionSphere( d ), "Passed!" );
+	ok( ! a.isIntersectionSphere( e ), "Passed!" );
+	ok( ! a.isIntersectionSphere( f ), "Passed!" );
+});
+
+test( "isIntersectionPlane", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+
+	// parallel plane in front of the ray
+	var b = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), one3.clone().sub( new THREE.Vector3( 0, 0, -1 ) ) );
+	ok( a.isIntersectionPlane( b ), "Passed!" );
+
+	// parallel plane coincident with origin
+	var c = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), one3.clone().sub( new THREE.Vector3( 0, 0, 0 ) ) );
+	ok( a.isIntersectionPlane( c ), "Passed!" );
+
+	// parallel plane behind the ray
+	var d = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), one3.clone().sub( new THREE.Vector3( 0, 0, 1 ) ) );
+	ok( ! a.isIntersectionPlane( d ), "Passed!" );
+
+	// perpendical ray that overlaps exactly
+	var e = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 1, 0, 0 ), one3 );
+	ok( a.isIntersectionPlane( e ), "Passed!" );
+
+	// perpendical ray that doesn't overlap
+	var f = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 1, 0, 0 ), zero3 );
+	ok( ! a.isIntersectionPlane( f ), "Passed!" );
+});
+
+test( "intersectPlane", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+
+	// parallel plane behind
+	var b = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 1, 1, -1 ) );
+	ok( a.intersectPlane( b ) === null, "Passed!" );
+
+	// parallel plane coincident with origin
+	var c = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 1, 1, 0 ) );
+	ok( a.intersectPlane( c ) === null, "Passed!" );
+
+	// parallel plane infront
+	var d = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 1, 1, 1 ) );
+	ok( a.intersectPlane( d ).equals( a.origin ), "Passed!" );
+
+	// perpendical ray that overlaps exactly
+	var e = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 1, 0, 0 ), one3 );
+	ok( a.intersectPlane( e ).equals( a.origin ), "Passed!" );
+
+	// perpendical ray that doesn't overlap
+	var f = new THREE.Plane().setFromNormalAndCoplanarPoint( new THREE.Vector3( 1, 0, 0 ), zero3 );
+	ok( a.intersectPlane( f ) === null, "Passed!" );
+});
+
+
+test( "applyMatrix4", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+	var m = new THREE.Matrix4();
+
+	ok( a.clone().applyMatrix4( m ).equals( a ), "Passed!" );
+
+	a = new THREE.Ray( zero3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+	m.makeRotationZ( Math.PI );
+	ok( a.clone().applyMatrix4( m ).equals( a ), "Passed!" );
+
+	m.makeRotationX( Math.PI );
+	var b = a.clone();
+	b.direction.negate();
+	var a2 = a.clone().applyMatrix4( m );
+	ok( a2.origin.distanceTo( b.origin ) < 0.0001, "Passed!" );
+	ok( a2.direction.distanceTo( b.direction ) < 0.0001, "Passed!" );
+
+	a.origin = new THREE.Vector3( 0, 0, 1 );
+	b.origin = new THREE.Vector3( 0, 0, -1 );
+	var a2 = a.clone().applyMatrix4( m );
+	ok( a2.origin.distanceTo( b.origin ) < 0.0001, "Passed!" );
+	ok( a2.direction.distanceTo( b.direction ) < 0.0001, "Passed!" );
+});
+
+
+test( "distanceSqToSegment", function() {
+	var a = new THREE.Ray( one3.clone(), new THREE.Vector3( 0, 0, 1 ) );
+	var ptOnLine = new THREE.Vector3();
+	var ptOnSegment = new THREE.Vector3();
+
+	//segment in front of the ray
+	var v0 = new THREE.Vector3( 3, 5, 50 );
+	var v1 = new THREE.Vector3( 50, 50, 50 ); // just a far away point
+	var distSqr = a.distanceSqToSegment( v0, v1, ptOnLine, ptOnSegment );
+
+	ok( ptOnSegment.distanceTo( v0 ) < 0.0001, "Passed!" );
+	ok( ptOnLine.distanceTo( new THREE.Vector3(1, 1, 50) ) < 0.0001, "Passed!" );
+	// ((3-1) * (3-1) + (5-1) * (5-1) = 4 + 16 = 20
+	ok( Math.abs( distSqr - 20 ) < 0.0001, "Passed!" );
+
+	//segment behind the ray
+	v0 = new THREE.Vector3( -50, -50, -50 ); // just a far away point
+	v1 = new THREE.Vector3( -3, -5, -4 );
+	distSqr = a.distanceSqToSegment( v0, v1, ptOnLine, ptOnSegment );
+
+	ok( ptOnSegment.distanceTo( v1 ) < 0.0001, "Passed!" );
+	ok( ptOnLine.distanceTo( one3 ) < 0.0001, "Passed!" );
+	// ((-3-1) * (-3-1) + (-5-1) * (-5-1) + (-4-1) + (-4-1) = 16 + 36 + 25 = 77
+	ok( Math.abs( distSqr - 77 ) < 0.0001, "Passed!" );
+
+	//exact intersection between the ray and the segment
+	v0 = new THREE.Vector3( -50, -50, -50 );
+	v1 = new THREE.Vector3( 50, 50, 50 );
+	distSqr = a.distanceSqToSegment( v0, v1, ptOnLine, ptOnSegment );
+
+	ok( ptOnSegment.distanceTo( one3 ) < 0.0001, "Passed!" );
+	ok( ptOnLine.distanceTo( one3 ) < 0.0001, "Passed!" );
+	ok( distSqr < 0.0001, "Passed!" );
+});
+
+test( "intersectBox", function() {
+
+	var TOL = 0.0001;
+	
+	var box = new THREE.Box3( new THREE.Vector3(  -1, -1, -1 ), new THREE.Vector3( 1, 1, 1 ) );
+
+	var a = new THREE.Ray( new THREE.Vector3( -2, 0, 0 ), new THREE.Vector3( 1, 0, 0) );
+	//ray should intersect box at -1,0,0
+	ok( a.isIntersectionBox(box) === true, "Passed!" );
+	ok( a.intersectBox(box).distanceTo( new THREE.Vector3( -1, 0, 0 ) ) < TOL, "Passed!" );
+
+	var b = new THREE.Ray( new THREE.Vector3( -2, 0, 0 ), new THREE.Vector3( -1, 0, 0) );
+	//ray is point away from box, it should not intersect
+	ok( b.isIntersectionBox(box) === false, "Passed!" );
+	ok( b.intersectBox(box) === null, "Passed!" );
+
+	var c = new THREE.Ray( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0) );
+	// ray is inside box, should return exit point
+	ok( c.isIntersectionBox(box) === true, "Passed!" );
+	ok( c.intersectBox(box).distanceTo( new THREE.Vector3( 1, 0, 0 ) ) < TOL, "Passed!" );
+
+	var d = new THREE.Ray( new THREE.Vector3( 0, 2, 1 ), new THREE.Vector3( 0, -1, -1).normalize() );
+	//tilted ray should intersect box at 0,1,0
+	ok( d.isIntersectionBox(box) === true, "Passed!" );
+	ok( d.intersectBox(box).distanceTo( new THREE.Vector3( 0, 1, 0 ) ) < TOL, "Passed!" );	
+
+	var e = new THREE.Ray( new THREE.Vector3( 1, -2, 1 ), new THREE.Vector3( 0, 1, 0).normalize() );
+	//handle case where ray is coplanar with one of the boxes side - box in front of ray
+	ok( e.isIntersectionBox(box) === true, "Passed!" );
+	ok( e.intersectBox(box).distanceTo( new THREE.Vector3( 1, -1, 1 ) ) < TOL, "Passed!" );	
+	
+	var f = new THREE.Ray( new THREE.Vector3( 1, -2, 0 ), new THREE.Vector3( 0, -1, 0).normalize() );
+	//handle case where ray is coplanar with one of the boxes side - box behind ray
+	ok( f.isIntersectionBox(box) === false, "Passed!" );
+	ok( f.intersectBox(box) == null, "Passed!" );		
+	
+});
+
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Sphere.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Sphere.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Sphere.js	(revision 28897)
@@ -0,0 +1,101 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Sphere" );
+
+test( "constructor", function() {
+	var a = new THREE.Sphere();
+	ok( a.center.equals( zero3 ), "Passed!" );
+	ok( a.radius == 0, "Passed!" );
+
+	a = new THREE.Sphere( one3.clone(), 1 );
+	ok( a.center.equals( one3 ), "Passed!" );
+	ok( a.radius == 1, "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Sphere( one3.clone(), 1 );
+	var b = new THREE.Sphere().copy( a );
+
+	ok( b.center.equals( one3 ), "Passed!" );
+	ok( b.radius == 1, "Passed!" );
+
+	// ensure that it is a true copy
+	a.center = zero3;
+	a.radius = 0;
+	ok( b.center.equals( one3 ), "Passed!" );
+	ok( b.radius == 1, "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Sphere();
+	ok( a.center.equals( zero3 ), "Passed!" );
+	ok( a.radius == 0, "Passed!" );
+
+	a.set( one3, 1 );
+	ok( a.center.equals( one3 ), "Passed!" );
+	ok( a.radius == 1, "Passed!" );
+});
+
+test( "empty", function() {
+	var a = new THREE.Sphere();
+	ok( a.empty(), "Passed!" );
+
+	a.set( one3, 1 );
+	ok( ! a.empty(), "Passed!" );
+});
+
+test( "containsPoint", function() {
+	var a = new THREE.Sphere( one3.clone(), 1 );
+
+	ok( ! a.containsPoint( zero3 ), "Passed!" );
+	ok( a.containsPoint( one3 ), "Passed!" );
+});
+
+test( "distanceToPoint", function() {
+	var a = new THREE.Sphere( one3.clone(), 1 );
+
+	ok( ( a.distanceToPoint( zero3 ) - 0.7320 ) < 0.001, "Passed!" );
+	ok( a.distanceToPoint( one3 ) === -1, "Passed!" );
+});
+
+test( "intersectsSphere", function() {
+	var a = new THREE.Sphere( one3.clone(), 1 );
+	var b = new THREE.Sphere( zero3.clone(), 1 );
+	var c = new THREE.Sphere( zero3.clone(), 0.25 );
+
+	ok( a.intersectsSphere( b ) , "Passed!" );
+	ok( ! a.intersectsSphere( c ) , "Passed!" );
+});
+
+test( "clampPoint", function() {
+	var a = new THREE.Sphere( one3.clone(), 1 );
+
+	ok( a.clampPoint( new THREE.Vector3( 1, 1, 3 ) ).equals( new THREE.Vector3( 1, 1, 2 ) ), "Passed!" );
+	ok( a.clampPoint( new THREE.Vector3( 1, 1, -3 ) ).equals( new THREE.Vector3( 1, 1, 0 ) ), "Passed!" );
+});
+
+test( "getBoundingBox", function() {
+	var a = new THREE.Sphere( one3.clone(), 1 );
+
+	ok( a.getBoundingBox().equals( new THREE.Box3( zero3, two3 ) ), "Passed!" );
+
+	a.set( zero3, 0 )
+	ok( a.getBoundingBox().equals( new THREE.Box3( zero3, zero3 ) ), "Passed!" );
+});
+
+test( "applyMatrix4", function() {
+	var a = new THREE.Sphere( one3.clone(), 1 );
+
+	var m = new THREE.Matrix4().makeTranslation( 1, -2, 1 );
+
+	ok( a.clone().applyMatrix4( m ).getBoundingBox().equals( a.getBoundingBox().applyMatrix4( m ) ), "Passed!" );
+});
+
+test( "translate", function() {
+	var a = new THREE.Sphere( one3.clone(), 1 );
+
+	a.translate( one3.clone().negate() );
+	ok( a.center.equals( zero3 ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Triangle.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Triangle.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Triangle.js	(revision 28897)
@@ -0,0 +1,160 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Triangle" );
+
+test( "constructor", function() {
+	var a = new THREE.Triangle();
+	ok( a.a.equals( zero3 ), "Passed!" );
+	ok( a.b.equals( zero3 ), "Passed!" );
+	ok( a.c.equals( zero3 ), "Passed!" );
+
+	a = new THREE.Triangle( one3.clone().negate(), one3.clone(), two3.clone() );
+	ok( a.a.equals( one3.clone().negate() ), "Passed!" );
+	ok( a.b.equals( one3 ), "Passed!" );
+	ok( a.c.equals( two3 ), "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Triangle( one3.clone().negate(), one3.clone(), two3.clone() );
+	var b = new THREE.Triangle().copy( a );
+	ok( b.a.equals( one3.clone().negate() ), "Passed!" );
+	ok( b.b.equals( one3 ), "Passed!" );
+	ok( b.c.equals( two3 ), "Passed!" );
+
+	// ensure that it is a true copy
+	a.a = one3;
+	a.b = zero3;
+	a.c = zero3;
+	ok( b.a.equals( one3.clone().negate() ), "Passed!" );
+	ok( b.b.equals( one3 ), "Passed!" );
+	ok( b.c.equals( two3 ), "Passed!" );
+});
+
+test( "setFromPointsAndIndices", function() {
+	var a = new THREE.Triangle();
+
+	var points = [ one3, one3.clone().negate(), two3 ];
+	a.setFromPointsAndIndices( points, 1, 0, 2 );
+	ok( a.a.equals( one3.clone().negate() ), "Passed!" );
+	ok( a.b.equals( one3 ), "Passed!" );
+	ok( a.c.equals( two3 ), "Passed!" );
+
+});
+
+test( "set", function() {
+	var a = new THREE.Triangle();
+
+	a.set( one3.clone().negate(), one3, two3 );
+	ok( a.a.equals( one3.clone().negate() ), "Passed!" );
+	ok( a.b.equals( one3 ), "Passed!" );
+	ok( a.c.equals( two3 ), "Passed!" );
+
+});
+
+test( "area", function() {
+	var a = new THREE.Triangle();
+
+	ok( a.area() == 0, "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) );
+	ok( a.area() == 0.5, "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 2, 0, 0 ), new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 2 ) );
+	ok( a.area() == 2, "Passed!" );
+
+	// colinear triangle.
+	a = new THREE.Triangle( new THREE.Vector3( 2, 0, 0 ), new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 3, 0, 0 ) );
+	ok( a.area() == 0, "Passed!" );
+});
+
+test( "midpoint", function() {
+	var a = new THREE.Triangle();
+
+	ok( a.midpoint().equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) );
+	ok( a.midpoint().equals( new THREE.Vector3( 1/3, 1/3, 0 ) ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 2, 0, 0 ), new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 2 ) );
+	ok( a.midpoint().equals( new THREE.Vector3( 2/3, 0, 2/3 ) ), "Passed!" );
+});
+
+test( "normal", function() {
+	var a = new THREE.Triangle();
+
+	ok( a.normal().equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) );
+	ok( a.normal().equals( new THREE.Vector3( 0, 0, 1 ) ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 2, 0, 0 ), new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 2 ) );
+	ok( a.normal().equals( new THREE.Vector3( 0, 1, 0 ) ), "Passed!" );
+});
+
+test( "plane", function() {
+	var a = new THREE.Triangle();
+
+	// artificial normal is created in this case.
+	ok( a.plane().distanceToPoint( a.a ) == 0, "Passed!" );
+	ok( a.plane().distanceToPoint( a.b ) == 0, "Passed!" );
+	ok( a.plane().distanceToPoint( a.c ) == 0, "Passed!" );
+	ok( a.plane().normal.equals( a.normal() ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) );
+	ok( a.plane().distanceToPoint( a.a ) == 0, "Passed!" );
+	ok( a.plane().distanceToPoint( a.b ) == 0, "Passed!" );
+	ok( a.plane().distanceToPoint( a.c ) == 0, "Passed!" );
+	ok( a.plane().normal.equals( a.normal() ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 2, 0, 0 ), new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 2 ) );
+	ok( a.plane().distanceToPoint( a.a ) == 0, "Passed!" );
+	ok( a.plane().distanceToPoint( a.b ) == 0, "Passed!" );
+	ok( a.plane().distanceToPoint( a.c ) == 0, "Passed!" );
+	ok( a.plane().normal.clone().normalize().equals( a.normal() ), "Passed!" );
+});
+
+test( "barycoordFromPoint", function() {
+	var a = new THREE.Triangle();
+
+	var bad = new THREE.Vector3( -2, -1, -1 );
+
+	ok( a.barycoordFromPoint( a.a ).equals( bad ), "Passed!" );
+	ok( a.barycoordFromPoint( a.b ).equals( bad ), "Passed!" );
+	ok( a.barycoordFromPoint( a.c ).equals( bad ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) );
+	ok( a.barycoordFromPoint( a.a ).equals( new THREE.Vector3( 1, 0, 0 ) ), "Passed!" );
+	ok( a.barycoordFromPoint( a.b ).equals( new THREE.Vector3( 0, 1, 0 ) ), "Passed!" );
+	ok( a.barycoordFromPoint( a.c ).equals( new THREE.Vector3( 0, 0, 1 ) ), "Passed!" );
+	ok( a.barycoordFromPoint( a.midpoint() ).distanceTo( new THREE.Vector3( 1/3, 1/3, 1/3 ) ) < 0.0001, "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 2, 0, 0 ), new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 2 ) );
+	ok( a.barycoordFromPoint( a.a ).equals( new THREE.Vector3( 1, 0, 0 ) ), "Passed!" );
+	ok( a.barycoordFromPoint( a.b ).equals( new THREE.Vector3( 0, 1, 0 ) ), "Passed!" );
+	ok( a.barycoordFromPoint( a.c ).equals( new THREE.Vector3( 0, 0, 1 ) ), "Passed!" );
+	ok( a.barycoordFromPoint( a.midpoint() ).distanceTo( new THREE.Vector3( 1/3, 1/3, 1/3 ) ) < 0.0001, "Passed!" );
+});
+
+test( "containsPoint", function() {
+	var a = new THREE.Triangle();
+
+	ok( ! a.containsPoint( a.a ), "Passed!" );
+	ok( ! a.containsPoint( a.b ), "Passed!" );
+	ok( ! a.containsPoint( a.c ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ) );
+	ok( a.containsPoint( a.a ), "Passed!" );
+	ok( a.containsPoint( a.b ), "Passed!" );
+	ok( a.containsPoint( a.c ), "Passed!" );
+	ok( a.containsPoint( a.midpoint() ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( -1, -1, -1 ) ), "Passed!" );
+
+	a = new THREE.Triangle( new THREE.Vector3( 2, 0, 0 ), new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, 2 ) );
+	ok( a.containsPoint( a.a ), "Passed!" );
+	ok( a.containsPoint( a.b ), "Passed!" );
+	ok( a.containsPoint( a.c ), "Passed!" );
+	ok( a.containsPoint( a.midpoint() ), "Passed!" );
+	ok( ! a.containsPoint( new THREE.Vector3( -1, -1, -1 ) ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector2.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector2.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector2.js	(revision 28897)
@@ -0,0 +1,236 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Vector2" );
+
+test( "constructor", function() {
+	var a = new THREE.Vector2();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+
+	a = new THREE.Vector2( x, y );
+	ok( a.x === x, "Passed!" );
+	ok( a.y === y, "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Vector2( x, y );
+	var b = new THREE.Vector2().copy( a );
+	ok( b.x == x, "Passed!" );
+	ok( b.y == y, "Passed!" );
+
+	// ensure that it is a true copy
+	a.x = 0;
+	a.y = -1;
+	ok( b.x == x, "Passed!" );
+	ok( b.y == y, "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Vector2();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+
+	a.set( x, y );
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+});
+
+test( "setX,setY", function() {
+	var a = new THREE.Vector2();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+
+	a.setX( x );
+	a.setY( y );
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+});
+
+test( "setComponent,getComponent", function() {
+	var a = new THREE.Vector2();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+
+	a.setComponent( 0, 1 );
+	a.setComponent( 1, 2 );
+	ok( a.getComponent( 0 ) == 1, "Passed!" );
+	ok( a.getComponent( 1 ) == 2, "Passed!" );
+});
+
+test( "add", function() {
+	var a = new THREE.Vector2( x, y );
+	var b = new THREE.Vector2( -x, -y );
+
+	a.add( b );
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+
+	var c = new THREE.Vector2().addVectors( b, b );
+	ok( c.x == -2*x, "Passed!" );
+	ok( c.y == -2*y, "Passed!" );
+});
+
+test( "sub", function() {
+	var a = new THREE.Vector2( x, y );
+	var b = new THREE.Vector2( -x, -y );
+
+	a.sub( b );
+	ok( a.x == 2*x, "Passed!" );
+	ok( a.y == 2*y, "Passed!" );
+
+	var c = new THREE.Vector2().subVectors( a, a );
+	ok( c.x == 0, "Passed!" );
+	ok( c.y == 0, "Passed!" );
+});
+
+test( "multiply/divide", function() {
+	var a = new THREE.Vector2( x, y );
+	var b = new THREE.Vector2( -x, -y );
+
+	a.multiplyScalar( -2 );
+	ok( a.x == x*-2, "Passed!" );
+	ok( a.y == y*-2, "Passed!" );
+
+	b.multiplyScalar( -2 );
+	ok( b.x == 2*x, "Passed!" );
+	ok( b.y == 2*y, "Passed!" );
+
+	a.divideScalar( -2 );
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+
+	b.divideScalar( -2 );
+	ok( b.x == -x, "Passed!" );
+	ok( b.y == -y, "Passed!" );
+});
+
+
+test( "min/max/clamp", function() {
+	var a = new THREE.Vector2( x, y );
+	var b = new THREE.Vector2( -x, -y );
+	var c = new THREE.Vector2();
+
+	c.copy( a ).min( b );
+	ok( c.x == -x, "Passed!" );
+	ok( c.y == -y, "Passed!" );
+
+	c.copy( a ).max( b );
+	ok( c.x == x, "Passed!" );
+	ok( c.y == y, "Passed!" );
+
+	c.set( -2*x, 2*y );
+	c.clamp( b, a );
+	ok( c.x == -x, "Passed!" );
+	ok( c.y == y, "Passed!" );
+});
+
+test( "negate", function() {
+	var a = new THREE.Vector2( x, y );
+
+	a.negate();
+	ok( a.x == -x, "Passed!" );
+	ok( a.y == -y, "Passed!" );
+});
+
+test( "dot", function() {
+	var a = new THREE.Vector2( x, y );
+	var b = new THREE.Vector2( -x, -y );
+	var c = new THREE.Vector2();
+
+	var result = a.dot( b );
+	ok( result == (-x*x-y*y), "Passed!" );
+
+	result = a.dot( c );
+	ok( result == 0, "Passed!" );
+});
+
+test( "length/lengthSq", function() {
+	var a = new THREE.Vector2( x, 0 );
+	var b = new THREE.Vector2( 0, -y );
+	var c = new THREE.Vector2();
+
+	ok( a.length() == x, "Passed!" );
+	ok( a.lengthSq() == x*x, "Passed!" );
+	ok( b.length() == y, "Passed!" );
+	ok( b.lengthSq() == y*y, "Passed!" );
+	ok( c.length() == 0, "Passed!" );
+	ok( c.lengthSq() == 0, "Passed!" );
+
+	a.set( x, y );
+	ok( a.length() == Math.sqrt( x*x + y*y ), "Passed!" );
+	ok( a.lengthSq() == ( x*x + y*y ), "Passed!" );
+});
+
+test( "normalize", function() {
+	var a = new THREE.Vector2( x, 0 );
+	var b = new THREE.Vector2( 0, -y );
+	var c = new THREE.Vector2();
+
+	a.normalize();
+	ok( a.length() == 1, "Passed!" );
+	ok( a.x == 1, "Passed!" );
+
+	b.normalize();
+	ok( b.length() == 1, "Passed!" );
+	ok( b.y == -1, "Passed!" );
+});
+
+test( "distanceTo/distanceToSquared", function() {
+	var a = new THREE.Vector2( x, 0 );
+	var b = new THREE.Vector2( 0, -y );
+	var c = new THREE.Vector2();
+
+	ok( a.distanceTo( c ) == x, "Passed!" );
+	ok( a.distanceToSquared( c ) == x*x, "Passed!" );
+
+	ok( b.distanceTo( c ) == y, "Passed!" );
+	ok( b.distanceToSquared( c ) == y*y, "Passed!" );
+});
+
+test( "setLength", function() {
+	var a = new THREE.Vector2( x, 0 );
+
+	ok( a.length() == x, "Passed!" );
+	a.setLength( y );
+	ok( a.length() == y, "Passed!" );
+
+	a = new THREE.Vector2( 0, 0 );
+	ok( a.length() == 0, "Passed!" );
+	a.setLength( y );
+	ok( a.length() == 0, "Passed!" );
+});
+
+test( "lerp/clone", function() {
+	var a = new THREE.Vector2( x, 0 );
+	var b = new THREE.Vector2( 0, -y );
+
+	ok( a.lerp( a, 0 ).equals( a.lerp( a, 0.5 ) ), "Passed!" );
+	ok( a.lerp( a, 0 ).equals( a.lerp( a, 1 ) ), "Passed!" );
+
+	ok( a.clone().lerp( b, 0 ).equals( a ), "Passed!" );
+
+	ok( a.clone().lerp( b, 0.5 ).x == x*0.5, "Passed!" );
+	ok( a.clone().lerp( b, 0.5 ).y == -y*0.5, "Passed!" );
+
+	ok( a.clone().lerp( b, 1 ).equals( b ), "Passed!" );
+});
+
+test( "equals", function() {
+	var a = new THREE.Vector2( x, 0 );
+	var b = new THREE.Vector2( 0, -y );
+
+	ok( a.x != b.x, "Passed!" );
+	ok( a.y != b.y, "Passed!" );
+
+	ok( ! a.equals( b ), "Passed!" );
+	ok( ! b.equals( a ), "Passed!" );
+
+	a.copy( b );
+	ok( a.x == b.x, "Passed!" );
+	ok( a.y == b.y, "Passed!" );
+
+	ok( a.equals( b ), "Passed!" );
+	ok( b.equals( a ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector3.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector3.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector3.js	(revision 28897)
@@ -0,0 +1,345 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Vector3" );
+
+test( "constructor", function() {
+	var a = new THREE.Vector3();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+
+	a = new THREE.Vector3( x, y, z );
+	ok( a.x === x, "Passed!" );
+	ok( a.y === y, "Passed!" );
+	ok( a.z === z, "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3().copy( a );
+	ok( b.x == x, "Passed!" );
+	ok( b.y == y, "Passed!" );
+	ok( b.z == z, "Passed!" );
+
+	// ensure that it is a true copy
+	a.x = 0;
+	a.y = -1;
+	a.z = -2;
+	ok( b.x == x, "Passed!" );
+	ok( b.y == y, "Passed!" );
+	ok( b.z == z, "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Vector3();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+
+	a.set( x, y, z );
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+	ok( a.z == z, "Passed!" );
+});
+
+test( "setX,setY,setZ", function() {
+	var a = new THREE.Vector3();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+
+	a.setX( x );
+	a.setY( y );
+	a.setZ( z );
+
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+	ok( a.z == z, "Passed!" );
+});
+
+test( "setComponent,getComponent", function() {
+	var a = new THREE.Vector3();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+
+	a.setComponent( 0, 1 );
+	a.setComponent( 1, 2 );
+	a.setComponent( 2, 3 );
+	ok( a.getComponent( 0 ) == 1, "Passed!" );
+	ok( a.getComponent( 1 ) == 2, "Passed!" );
+	ok( a.getComponent( 2 ) == 3, "Passed!" );
+});
+
+test( "add", function() {
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( -x, -y, -z );
+
+	a.add( b );
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+
+	var c = new THREE.Vector3().addVectors( b, b );
+	ok( c.x == -2*x, "Passed!" );
+	ok( c.y == -2*y, "Passed!" );
+	ok( c.z == -2*z, "Passed!" );
+});
+
+test( "sub", function() {
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( -x, -y, -z );
+
+	a.sub( b );
+	ok( a.x == 2*x, "Passed!" );
+	ok( a.y == 2*y, "Passed!" );
+	ok( a.z == 2*z, "Passed!" );
+
+	var c = new THREE.Vector3().subVectors( a, a );
+	ok( c.x == 0, "Passed!" );
+	ok( c.y == 0, "Passed!" );
+	ok( c.z == 0, "Passed!" );
+});
+
+test( "multiply/divide", function() {
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( -x, -y, -z );
+
+	a.multiplyScalar( -2 );
+	ok( a.x == x*-2, "Passed!" );
+	ok( a.y == y*-2, "Passed!" );
+	ok( a.z == z*-2, "Passed!" );
+
+	b.multiplyScalar( -2 );
+	ok( b.x == 2*x, "Passed!" );
+	ok( b.y == 2*y, "Passed!" );
+	ok( b.z == 2*z, "Passed!" );
+
+	a.divideScalar( -2 );
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+	ok( a.z == z, "Passed!" );
+
+	b.divideScalar( -2 );
+	ok( b.x == -x, "Passed!" );
+	ok( b.y == -y, "Passed!" );
+	ok( b.z == -z, "Passed!" );
+});
+
+test( "min/max/clamp", function() {
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( -x, -y, -z );
+	var c = new THREE.Vector3();
+
+	c.copy( a ).min( b );
+	ok( c.x == -x, "Passed!" );
+	ok( c.y == -y, "Passed!" );
+	ok( c.z == -z, "Passed!" );
+
+	c.copy( a ).max( b );
+	ok( c.x == x, "Passed!" );
+	ok( c.y == y, "Passed!" );
+	ok( c.z == z, "Passed!" );
+
+	c.set( -2*x, 2*y, -2*z );
+	c.clamp( b, a );
+	ok( c.x == -x, "Passed!" );
+	ok( c.y == y, "Passed!" );
+	ok( c.z == -z, "Passed!" );
+});
+
+test( "negate", function() {
+	var a = new THREE.Vector3( x, y, z );
+
+	a.negate();
+	ok( a.x == -x, "Passed!" );
+	ok( a.y == -y, "Passed!" );
+	ok( a.z == -z, "Passed!" );
+});
+
+test( "dot", function() {
+	var a = new THREE.Vector3( x, y, z );
+	var b = new THREE.Vector3( -x, -y, -z );
+	var c = new THREE.Vector3();
+
+	var result = a.dot( b );
+	ok( result == (-x*x-y*y-z*z), "Passed!" );
+
+	result = a.dot( c );
+	ok( result == 0, "Passed!" );
+});
+
+test( "length/lengthSq", function() {
+	var a = new THREE.Vector3( x, 0, 0 );
+	var b = new THREE.Vector3( 0, -y, 0 );
+	var c = new THREE.Vector3( 0, 0, z );
+	var d = new THREE.Vector3();
+
+	ok( a.length() == x, "Passed!" );
+	ok( a.lengthSq() == x*x, "Passed!" );
+	ok( b.length() == y, "Passed!" );
+	ok( b.lengthSq() == y*y, "Passed!" );
+	ok( c.length() == z, "Passed!" );
+	ok( c.lengthSq() == z*z, "Passed!" );
+	ok( d.length() == 0, "Passed!" );
+	ok( d.lengthSq() == 0, "Passed!" );
+
+	a.set( x, y, z );
+	ok( a.length() == Math.sqrt( x*x + y*y + z*z ), "Passed!" );
+	ok( a.lengthSq() == ( x*x + y*y + z*z ), "Passed!" );
+});
+
+test( "normalize", function() {
+	var a = new THREE.Vector3( x, 0, 0 );
+	var b = new THREE.Vector3( 0, -y, 0 );
+	var c = new THREE.Vector3( 0, 0, z );
+
+	a.normalize();
+	ok( a.length() == 1, "Passed!" );
+	ok( a.x == 1, "Passed!" );
+
+	b.normalize();
+	ok( b.length() == 1, "Passed!" );
+	ok( b.y == -1, "Passed!" );
+
+	c.normalize();
+	ok( c.length() == 1, "Passed!" );
+	ok( c.z == 1, "Passed!" );
+});
+
+test( "distanceTo/distanceToSquared", function() {
+	var a = new THREE.Vector3( x, 0, 0 );
+	var b = new THREE.Vector3( 0, -y, 0 );
+	var c = new THREE.Vector3( 0, 0, z );
+	var d = new THREE.Vector3();
+
+	ok( a.distanceTo( d ) == x, "Passed!" );
+	ok( a.distanceToSquared( d ) == x*x, "Passed!" );
+
+	ok( b.distanceTo( d ) == y, "Passed!" );
+	ok( b.distanceToSquared( d ) == y*y, "Passed!" );
+
+	ok( c.distanceTo( d ) == z, "Passed!" );
+	ok( c.distanceToSquared( d ) == z*z, "Passed!" );
+});
+
+test( "setLength", function() {
+	var a = new THREE.Vector3( x, 0, 0 );
+
+	ok( a.length() == x, "Passed!" );
+	a.setLength( y );
+	ok( a.length() == y, "Passed!" );
+
+	a = new THREE.Vector3( 0, 0, 0 );
+	ok( a.length() == 0, "Passed!" );
+	a.setLength( y );
+	ok( a.length() == 0, "Passed!" );
+
+});
+
+test( "projectOnVector", function() {
+	var a = new THREE.Vector3( 1, 0, 0 );
+	var b = new THREE.Vector3();
+	var normal = new THREE.Vector3( 10, 0, 0 );
+
+	ok( b.copy( a ).projectOnVector( normal ).equals( new THREE.Vector3( 1, 0, 0 ) ), "Passed!" );
+
+	a.set( 0, 1, 0 );
+	ok( b.copy( a ).projectOnVector( normal ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+
+	a.set( 0, 0, -1 );
+	ok( b.copy( a ).projectOnVector( normal ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+
+	a.set( -1, 0, 0 );
+	ok( b.copy( a ).projectOnVector( normal ).equals( new THREE.Vector3( -1, 0, 0 ) ), "Passed!" );
+
+});
+
+test( "projectOnPlane", function() {
+	var a = new THREE.Vector3( 1, 0, 0 );
+	var b = new THREE.Vector3();
+	var normal = new THREE.Vector3( 1, 0, 0 );
+
+	ok( b.copy( a ).projectOnPlane( normal ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+
+	a.set( 0, 1, 0 );
+	ok( b.copy( a ).projectOnPlane( normal ).equals( new THREE.Vector3( 0, 1, 0 ) ), "Passed!" );
+
+	a.set( 0, 0, -1 );
+	ok( b.copy( a ).projectOnPlane( normal ).equals( new THREE.Vector3( 0, 0, -1 ) ), "Passed!" );
+
+	a.set( -1, 0, 0 );
+	ok( b.copy( a ).projectOnPlane( normal ).equals( new THREE.Vector3( 0, 0, 0 ) ), "Passed!" );
+
+});
+
+test( "reflect", function() {
+	var a = new THREE.Vector3( 1, 0, 0 );
+	var normal = new THREE.Vector3( 1, 0, 0 );
+	var b = new THREE.Vector3( 0, 0, 0 );
+
+	ok( b.copy( a ).reflect( normal ).equals( new THREE.Vector3( 1, 0, 0 ) ), "Passed!" );
+
+	a.set( 1, -1, 0 );
+	ok( b.copy( a ).reflect( normal ).equals( new THREE.Vector3( 1, 1, 0 ) ), "Passed!" );
+
+	a.set( 1, -1, 0 );
+	normal.set( 0, -1, 0 );
+	ok( b.copy( a ).reflect(  normal ).equals( new THREE.Vector3( -1, -1, 0 ) ), "Passed!" );
+});
+
+test( "angleTo", function() {
+	var a = new THREE.Vector3( 0, -0.18851655680720186, 0.9820700116639124 );
+	var b = new THREE.Vector3( 0, 0.18851655680720186, -0.9820700116639124 );
+
+	equal( a.angleTo( a ), 0 );
+	equal( a.angleTo( b ), Math.PI );
+
+	var x = new THREE.Vector3( 1, 0, 0 );
+	var y = new THREE.Vector3( 0, 1, 0 );
+	var z = new THREE.Vector3( 0, 0, 1 );
+
+	equal( x.angleTo( y ), Math.PI / 2 );
+	equal( x.angleTo( z ), Math.PI / 2 );
+	equal( z.angleTo( x ), Math.PI / 2 );
+
+	ok( Math.abs( x.angleTo( new THREE.Vector3( 1, 1, 0 ) ) - ( Math.PI / 4 ) ) < 0.0000001 );
+});
+
+test( "lerp/clone", function() {
+	var a = new THREE.Vector3( x, 0, z );
+	var b = new THREE.Vector3( 0, -y, 0 );
+
+	ok( a.lerp( a, 0 ).equals( a.lerp( a, 0.5 ) ), "Passed!" );
+	ok( a.lerp( a, 0 ).equals( a.lerp( a, 1 ) ), "Passed!" );
+
+	ok( a.clone().lerp( b, 0 ).equals( a ), "Passed!" );
+
+	ok( a.clone().lerp( b, 0.5 ).x == x*0.5, "Passed!" );
+	ok( a.clone().lerp( b, 0.5 ).y == -y*0.5, "Passed!" );
+	ok( a.clone().lerp( b, 0.5 ).z == z*0.5, "Passed!" );
+
+	ok( a.clone().lerp( b, 1 ).equals( b ), "Passed!" );
+});
+
+test( "equals", function() {
+	var a = new THREE.Vector3( x, 0, z );
+	var b = new THREE.Vector3( 0, -y, 0 );
+
+	ok( a.x != b.x, "Passed!" );
+	ok( a.y != b.y, "Passed!" );
+	ok( a.z != b.z, "Passed!" );
+
+	ok( ! a.equals( b ), "Passed!" );
+	ok( ! b.equals( a ), "Passed!" );
+
+	a.copy( b );
+	ok( a.x == b.x, "Passed!" );
+	ok( a.y == b.y, "Passed!" );
+	ok( a.z == b.z, "Passed!" );
+
+	ok( a.equals( b ), "Passed!" );
+	ok( b.equals( a ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector4.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector4.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/Vector4.js	(revision 28897)
@@ -0,0 +1,318 @@
+/**
+ * @author bhouston / http://exocortex.com
+ */
+
+module( "Vector4" );
+
+test( "constructor", function() {
+	var a = new THREE.Vector4();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+	ok( a.w == 1, "Passed!" );
+
+	a = new THREE.Vector4( x, y, z, w );
+	ok( a.x === x, "Passed!" );
+	ok( a.y === y, "Passed!" );
+	ok( a.z === z, "Passed!" );
+	ok( a.w === w, "Passed!" );
+});
+
+test( "copy", function() {
+	var a = new THREE.Vector4( x, y, z, w );
+	var b = new THREE.Vector4().copy( a );
+	ok( b.x == x, "Passed!" );
+	ok( b.y == y, "Passed!" );
+	ok( b.z == z, "Passed!" );
+	ok( b.w == w, "Passed!" );
+
+	// ensure that it is a true copy
+	a.x = 0;
+	a.y = -1;
+	a.z = -2;
+	a.w = -3;
+	ok( b.x == x, "Passed!" );
+	ok( b.y == y, "Passed!" );
+	ok( b.z == z, "Passed!" );
+	ok( b.w == w, "Passed!" );
+});
+
+test( "set", function() {
+	var a = new THREE.Vector4();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+	ok( a.w == 1, "Passed!" );
+
+	a.set( x, y, z, w );
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+	ok( a.z == z, "Passed!" );
+	ok( a.w == w, "Passed!" );
+});
+
+test( "setX,setY,setZ,setW", function() {
+	var a = new THREE.Vector4();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+	ok( a.w == 1, "Passed!" );
+
+	a.setX( x );
+	a.setY( y );
+	a.setZ( z );
+	a.setW( w );
+
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+	ok( a.z == z, "Passed!" );
+	ok( a.w == w, "Passed!" );
+});
+
+test( "setComponent,getComponent", function() {
+	var a = new THREE.Vector4();
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+	ok( a.w == 1, "Passed!" );
+
+	a.setComponent( 0, 1 );
+	a.setComponent( 1, 2 );
+	a.setComponent( 2, 3 );
+	a.setComponent( 3, 4 );
+	ok( a.getComponent( 0 ) == 1, "Passed!" );
+	ok( a.getComponent( 1 ) == 2, "Passed!" );
+	ok( a.getComponent( 2 ) == 3, "Passed!" );
+	ok( a.getComponent( 3 ) == 4, "Passed!" );
+});
+
+test( "add", function() {
+	var a = new THREE.Vector4( x, y, z, w );
+	var b = new THREE.Vector4( -x, -y, -z, -w );
+
+	a.add( b );
+	ok( a.x == 0, "Passed!" );
+	ok( a.y == 0, "Passed!" );
+	ok( a.z == 0, "Passed!" );
+	ok( a.w == 0, "Passed!" );
+
+	var c = new THREE.Vector4().addVectors( b, b );
+	ok( c.x == -2*x, "Passed!" );
+	ok( c.y == -2*y, "Passed!" );
+	ok( c.z == -2*z, "Passed!" );
+	ok( c.w == -2*w, "Passed!" );
+});
+
+test( "sub", function() {
+	var a = new THREE.Vector4( x, y, z, w );
+	var b = new THREE.Vector4( -x, -y, -z, -w );
+
+	a.sub( b );
+	ok( a.x == 2*x, "Passed!" );
+	ok( a.y == 2*y, "Passed!" );
+	ok( a.z == 2*z, "Passed!" );
+	ok( a.w == 2*w, "Passed!" );
+
+	var c = new THREE.Vector4().subVectors( a, a );
+	ok( c.x == 0, "Passed!" );
+	ok( c.y == 0, "Passed!" );
+	ok( c.z == 0, "Passed!" );
+	ok( c.w == 0, "Passed!" );
+});
+
+test( "multiply/divide", function() {
+	var a = new THREE.Vector4( x, y, z, w );
+	var b = new THREE.Vector4( -x, -y, -z, -w );
+
+	a.multiplyScalar( -2 );
+	ok( a.x == x*-2, "Passed!" );
+	ok( a.y == y*-2, "Passed!" );
+	ok( a.z == z*-2, "Passed!" );
+	ok( a.w == w*-2, "Passed!" );
+
+	b.multiplyScalar( -2 );
+	ok( b.x == 2*x, "Passed!" );
+	ok( b.y == 2*y, "Passed!" );	
+	ok( b.z == 2*z, "Passed!" );	
+	ok( b.w == 2*w, "Passed!" );	
+
+	a.divideScalar( -2 );
+	ok( a.x == x, "Passed!" );
+	ok( a.y == y, "Passed!" );
+	ok( a.z == z, "Passed!" );
+	ok( a.w == w, "Passed!" );
+
+	b.divideScalar( -2 );
+	ok( b.x == -x, "Passed!" );
+	ok( b.y == -y, "Passed!" );
+	ok( b.z == -z, "Passed!" );
+	ok( b.w == -w, "Passed!" );
+});
+
+test( "min/max/clamp", function() {
+	var a = new THREE.Vector4( x, y, z, w );
+	var b = new THREE.Vector4( -x, -y, -z, -w );
+	var c = new THREE.Vector4();
+
+	c.copy( a ).min( b );
+	ok( c.x == -x, "Passed!" );
+	ok( c.y == -y, "Passed!" );
+	ok( c.z == -z, "Passed!" );
+	ok( c.w == -w, "Passed!" );
+
+	c.copy( a ).max( b );
+	ok( c.x == x, "Passed!" );
+	ok( c.y == y, "Passed!" );
+	ok( c.z == z, "Passed!" );
+	ok( c.w == w, "Passed!" );
+
+	c.set( -2*x, 2*y, -2*z, 2*w );
+	c.clamp( b, a );
+	ok( c.x == -x, "Passed!" );
+	ok( c.y == y, "Passed!" );
+	ok( c.z == -z, "Passed!" );
+	ok( c.w == w, "Passed!" );
+});
+
+test( "negate", function() {
+	var a = new THREE.Vector4( x, y, z, w );
+
+	a.negate();
+	ok( a.x == -x, "Passed!" );
+	ok( a.y == -y, "Passed!" );
+	ok( a.z == -z, "Passed!" );
+	ok( a.w == -w, "Passed!" );
+});
+
+test( "dot", function() {
+	var a = new THREE.Vector4( x, y, z, w );
+	var b = new THREE.Vector4( -x, -y, -z, -w );
+	var c = new THREE.Vector4( 0, 0, 0, 0 );
+
+	var result = a.dot( b );
+	ok( result == (-x*x-y*y-z*z-w*w), "Passed!" );
+
+	result = a.dot( c );
+	ok( result == 0, "Passed!" );
+});
+
+test( "length/lengthSq", function() {
+	var a = new THREE.Vector4( x, 0, 0, 0 );
+	var b = new THREE.Vector4( 0, -y, 0, 0 );
+	var c = new THREE.Vector4( 0, 0, z, 0 );
+	var d = new THREE.Vector4( 0, 0, 0, w );
+	var e = new THREE.Vector4( 0, 0, 0, 0 );
+	
+	ok( a.length() == x, "Passed!" );
+	ok( a.lengthSq() == x*x, "Passed!" );
+	ok( b.length() == y, "Passed!" );
+	ok( b.lengthSq() == y*y, "Passed!" );
+	ok( c.length() == z, "Passed!" );
+	ok( c.lengthSq() == z*z, "Passed!" );
+	ok( d.length() == w, "Passed!" );
+	ok( d.lengthSq() == w*w, "Passed!" );
+	ok( e.length() == 0, "Passed!" );
+	ok( e.lengthSq() == 0, "Passed!" );
+
+	a.set( x, y, z, w );
+	ok( a.length() == Math.sqrt( x*x + y*y + z*z + w*w ), "Passed!" );
+	ok( a.lengthSq() == ( x*x + y*y + z*z + w*w ), "Passed!" );
+});
+
+test( "normalize", function() {
+	var a = new THREE.Vector4( x, 0, 0, 0 );
+	var b = new THREE.Vector4( 0, -y, 0, 0 );
+	var c = new THREE.Vector4( 0, 0, z, 0 );
+	var d = new THREE.Vector4( 0, 0, 0, -w );
+	
+	a.normalize();
+	ok( a.length() == 1, "Passed!" );
+	ok( a.x == 1, "Passed!" );
+
+	b.normalize();
+	ok( b.length() == 1, "Passed!" );
+	ok( b.y == -1, "Passed!" );
+
+	c.normalize();
+	ok( c.length() == 1, "Passed!" );
+	ok( c.z == 1, "Passed!" );
+
+	d.normalize();
+	ok( d.length() == 1, "Passed!" );
+	ok( d.w == -1, "Passed!" );
+});
+
+/*
+test( "distanceTo/distanceToSquared", function() {
+	var a = new THREE.Vector4( x, 0, 0, 0 );
+	var b = new THREE.Vector4( 0, -y, 0, 0 );
+	var c = new THREE.Vector4( 0, 0, z, 0 );
+	var d = new THREE.Vector4( 0, 0, 0, -w );
+	var e = new THREE.Vector4();
+	
+	ok( a.distanceTo( e ) == x, "Passed!" );
+	ok( a.distanceToSquared( e ) == x*x, "Passed!" );
+
+	ok( b.distanceTo( e ) == y, "Passed!" );
+	ok( b.distanceToSquared( e ) == y*y, "Passed!" );
+
+	ok( c.distanceTo( e ) == z, "Passed!" );
+	ok( c.distanceToSquared( e ) == z*z, "Passed!" );
+
+	ok( d.distanceTo( e ) == w, "Passed!" );
+	ok( d.distanceToSquared( e ) == w*w, "Passed!" );
+});
+*/
+
+
+test( "setLength", function() {
+	var a = new THREE.Vector4( x, 0, 0, 0 );
+
+	ok( a.length() == x, "Passed!" );
+	a.setLength( y );
+	ok( a.length() == y, "Passed!" );
+
+	a = new THREE.Vector4( 0, 0, 0, 0 );
+	ok( a.length() == 0, "Passed!" );
+	a.setLength( y );
+	ok( a.length() == 0, "Passed!" );
+});
+
+test( "lerp/clone", function() {
+	var a = new THREE.Vector4( x, 0, z, 0 );
+	var b = new THREE.Vector4( 0, -y, 0, -w );
+
+	ok( a.lerp( a, 0 ).equals( a.lerp( a, 0.5 ) ), "Passed!" );
+	ok( a.lerp( a, 0 ).equals( a.lerp( a, 1 ) ), "Passed!" );
+
+	ok( a.clone().lerp( b, 0 ).equals( a ), "Passed!" );
+
+	ok( a.clone().lerp( b, 0.5 ).x == x*0.5, "Passed!" );
+	ok( a.clone().lerp( b, 0.5 ).y == -y*0.5, "Passed!" );
+	ok( a.clone().lerp( b, 0.5 ).z == z*0.5, "Passed!" );
+	ok( a.clone().lerp( b, 0.5 ).w == -w*0.5, "Passed!" );
+
+	ok( a.clone().lerp( b, 1 ).equals( b ), "Passed!" );
+});
+
+test( "equals", function() {
+	var a = new THREE.Vector4( x, 0, z, 0 );
+	var b = new THREE.Vector4( 0, -y, 0, -w );
+
+	ok( a.x != b.x, "Passed!" );
+	ok( a.y != b.y, "Passed!" );
+	ok( a.z != b.z, "Passed!" );
+	ok( a.w != b.w, "Passed!" );
+
+	ok( ! a.equals( b ), "Passed!" );
+	ok( ! b.equals( a ), "Passed!" );
+
+	a.copy( b );
+	ok( a.x == b.x, "Passed!" );
+	ok( a.y == b.y, "Passed!" );
+	ok( a.z == b.z, "Passed!" );
+	ok( a.w == b.w, "Passed!" );
+
+	ok( a.equals( b ), "Passed!" );
+	ok( b.equals( a ), "Passed!" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/math/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/qunit-1.10.0.css
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/qunit-1.10.0.css	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/qunit-1.10.0.css	(revision 28897)
@@ -0,0 +1,235 @@
+/**
+ * QUnit v1.10.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+	font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
+	margin: 0;
+	padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+	padding: 0.5em 0 0.5em 1em;
+
+	color: #8699a4;
+	background-color: #0d3349;
+
+	font-size: 1.5em;
+	line-height: 1em;
+	font-weight: normal;
+
+	border-radius: 5px 5px 0 0;
+	-moz-border-radius: 5px 5px 0 0;
+	-webkit-border-top-right-radius: 5px;
+	-webkit-border-top-left-radius: 5px;
+}
+
+#qunit-header a {
+	text-decoration: none;
+	color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+	color: #fff;
+}
+
+#qunit-testrunner-toolbar label {
+	display: inline-block;
+	padding: 0 .5em 0 .1em;
+}
+
+#qunit-banner {
+	height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+	padding: 0.5em 0 0.5em 2em;
+	color: #5E740B;
+	background-color: #eee;
+	overflow: hidden;
+}
+
+#qunit-userAgent {
+	padding: 0.5em 0 0.5em 2.5em;
+	background-color: #2b81af;
+	color: #fff;
+	text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+#qunit-modulefilter-container {
+	float: right;
+}
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+	list-style-position: inside;
+}
+
+#qunit-tests li {
+	padding: 0.4em 0.5em 0.4em 2.5em;
+	border-bottom: 1px solid #fff;
+	list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
+	display: none;
+}
+
+#qunit-tests li strong {
+	cursor: pointer;
+}
+
+#qunit-tests li a {
+	padding: 0.5em;
+	color: #c2ccd1;
+	text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+	color: #000;
+}
+
+#qunit-tests ol {
+	margin-top: 0.5em;
+	padding: 0.5em;
+
+	background-color: #fff;
+
+	border-radius: 5px;
+	-moz-border-radius: 5px;
+	-webkit-border-radius: 5px;
+}
+
+#qunit-tests table {
+	border-collapse: collapse;
+	margin-top: .2em;
+}
+
+#qunit-tests th {
+	text-align: right;
+	vertical-align: top;
+	padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+	vertical-align: top;
+}
+
+#qunit-tests pre {
+	margin: 0;
+	white-space: pre-wrap;
+	word-wrap: break-word;
+}
+
+#qunit-tests del {
+	background-color: #e0f2be;
+	color: #374e0c;
+	text-decoration: none;
+}
+
+#qunit-tests ins {
+	background-color: #ffcaca;
+	color: #500;
+	text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts                       { color: black; }
+#qunit-tests b.passed                       { color: #5E740B; }
+#qunit-tests b.failed                       { color: #710909; }
+
+#qunit-tests li li {
+	padding: 5px;
+	background-color: #fff;
+	border-bottom: none;
+	list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+	color: #3c510c;
+	background-color: #fff;
+	border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name               { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected           { color: #999999; }
+
+#qunit-banner.qunit-pass                    { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+	color: #710909;
+	background-color: #fff;
+	border-left: 10px solid #EE5757;
+	white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+	border-radius: 0 0 5px 5px;
+	-moz-border-radius: 0 0 5px 5px;
+	-webkit-border-bottom-right-radius: 5px;
+	-webkit-border-bottom-left-radius: 5px;
+}
+
+#qunit-tests .fail                          { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name             { color: #000000; }
+
+#qunit-tests .fail .test-actual             { color: #EE5757; }
+#qunit-tests .fail .test-expected           { color: green;   }
+
+#qunit-banner.qunit-fail                    { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+	padding: 0.5em 0.5em 0.5em 2.5em;
+
+	color: #2b81af;
+	background-color: #D2E0E6;
+
+	border-bottom: 1px solid white;
+}
+#qunit-testresult .module-name {
+	font-weight: bold;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+	position: absolute;
+	top: -10000px;
+	left: -10000px;
+	width: 1000px;
+	height: 1000px;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/qunit-1.10.0.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/qunit-1.10.0.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/qunit-1.10.0.js	(revision 28897)
@@ -0,0 +1,1977 @@
+/**
+ * QUnit v1.10.0 - A JavaScript Unit Testing Framework
+ *
+ * http://qunitjs.com
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+(function( window ) {
+
+var QUnit,
+	config,
+	onErrorFnPrev,
+	testId = 0,
+	fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+	toString = Object.prototype.toString,
+	hasOwn = Object.prototype.hasOwnProperty,
+	// Keep a local reference to Date (GH-283)
+	Date = window.Date,
+	defined = {
+	setTimeout: typeof window.setTimeout !== "undefined",
+	sessionStorage: (function() {
+		var x = "qunit-test-string";
+		try {
+			sessionStorage.setItem( x, x );
+			sessionStorage.removeItem( x );
+			return true;
+		} catch( e ) {
+			return false;
+		}
+	}())
+};
+
+function Test( settings ) {
+	extend( this, settings );
+	this.assertions = [];
+	this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
+Test.prototype = {
+	init: function() {
+		var a, b, li,
+        tests = id( "qunit-tests" );
+
+		if ( tests ) {
+			b = document.createElement( "strong" );
+			b.innerHTML = this.name;
+
+			// `a` initialized at top of scope
+			a = document.createElement( "a" );
+			a.innerHTML = "Rerun";
+			a.href = QUnit.url({ testNumber: this.testNumber });
+
+			li = document.createElement( "li" );
+			li.appendChild( b );
+			li.appendChild( a );
+			li.className = "running";
+			li.id = this.id = "qunit-test-output" + testId++;
+
+			tests.appendChild( li );
+		}
+	},
+	setup: function() {
+		if ( this.module !== config.previousModule ) {
+			if ( config.previousModule ) {
+				runLoggingCallbacks( "moduleDone", QUnit, {
+					name: config.previousModule,
+					failed: config.moduleStats.bad,
+					passed: config.moduleStats.all - config.moduleStats.bad,
+					total: config.moduleStats.all
+				});
+			}
+			config.previousModule = this.module;
+			config.moduleStats = { all: 0, bad: 0 };
+			runLoggingCallbacks( "moduleStart", QUnit, {
+				name: this.module
+			});
+		} else if ( config.autorun ) {
+			runLoggingCallbacks( "moduleStart", QUnit, {
+				name: this.module
+			});
+		}
+
+		config.current = this;
+
+		this.testEnvironment = extend({
+			setup: function() {},
+			teardown: function() {}
+		}, this.moduleTestEnvironment );
+
+		runLoggingCallbacks( "testStart", QUnit, {
+			name: this.testName,
+			module: this.module
+		});
+
+		// allow utility functions to access the current test environment
+		// TODO why??
+		QUnit.current_testEnvironment = this.testEnvironment;
+
+		if ( !config.pollution ) {
+			saveGlobal();
+		}
+		if ( config.notrycatch ) {
+			this.testEnvironment.setup.call( this.testEnvironment );
+			return;
+		}
+		try {
+			this.testEnvironment.setup.call( this.testEnvironment );
+		} catch( e ) {
+			QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+		}
+	},
+	run: function() {
+		config.current = this;
+
+		var running = id( "qunit-testresult" );
+
+		if ( running ) {
+			running.innerHTML = "Running: <br/>" + this.name;
+		}
+
+		if ( this.async ) {
+			QUnit.stop();
+		}
+
+		if ( config.notrycatch ) {
+			this.callback.call( this.testEnvironment, QUnit.assert );
+			return;
+		}
+
+		try {
+			this.callback.call( this.testEnvironment, QUnit.assert );
+		} catch( e ) {
+			QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
+			// else next test will carry the responsibility
+			saveGlobal();
+
+			// Restart the tests if they're blocking
+			if ( config.blocking ) {
+				QUnit.start();
+			}
+		}
+	},
+	teardown: function() {
+		config.current = this;
+		if ( config.notrycatch ) {
+			this.testEnvironment.teardown.call( this.testEnvironment );
+			return;
+		} else {
+			try {
+				this.testEnvironment.teardown.call( this.testEnvironment );
+			} catch( e ) {
+				QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+			}
+		}
+		checkPollution();
+	},
+	finish: function() {
+		config.current = this;
+		if ( config.requireExpects && this.expected == null ) {
+			QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
+		} else if ( this.expected != null && this.expected != this.assertions.length ) {
+			QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+		} else if ( this.expected == null && !this.assertions.length ) {
+			QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+		}
+
+		var assertion, a, b, i, li, ol,
+			test = this,
+			good = 0,
+			bad = 0,
+			tests = id( "qunit-tests" );
+
+		config.stats.all += this.assertions.length;
+		config.moduleStats.all += this.assertions.length;
+
+		if ( tests ) {
+			ol = document.createElement( "ol" );
+
+			for ( i = 0; i < this.assertions.length; i++ ) {
+				assertion = this.assertions[i];
+
+				li = document.createElement( "li" );
+				li.className = assertion.result ? "pass" : "fail";
+				li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
+				ol.appendChild( li );
+
+				if ( assertion.result ) {
+					good++;
+				} else {
+					bad++;
+					config.stats.bad++;
+					config.moduleStats.bad++;
+				}
+			}
+
+			// store result when possible
+			if ( QUnit.config.reorder && defined.sessionStorage ) {
+				if ( bad ) {
+					sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
+				} else {
+					sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
+				}
+			}
+
+			if ( bad === 0 ) {
+				ol.style.display = "none";
+			}
+
+			// `b` initialized at top of scope
+			b = document.createElement( "strong" );
+			b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+
+			addEvent(b, "click", function() {
+				var next = b.nextSibling.nextSibling,
+					display = next.style.display;
+				next.style.display = display === "none" ? "block" : "none";
+			});
+
+			addEvent(b, "dblclick", function( e ) {
+				var target = e && e.target ? e.target : window.event.srcElement;
+				if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+					target = target.parentNode;
+				}
+				if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+					window.location = QUnit.url({ testNumber: test.testNumber });
+				}
+			});
+
+			// `li` initialized at top of scope
+			li = id( this.id );
+			li.className = bad ? "fail" : "pass";
+			li.removeChild( li.firstChild );
+			a = li.firstChild;
+			li.appendChild( b );
+			li.appendChild ( a );
+			li.appendChild( ol );
+
+		} else {
+			for ( i = 0; i < this.assertions.length; i++ ) {
+				if ( !this.assertions[i].result ) {
+					bad++;
+					config.stats.bad++;
+					config.moduleStats.bad++;
+				}
+			}
+		}
+
+		runLoggingCallbacks( "testDone", QUnit, {
+			name: this.testName,
+			module: this.module,
+			failed: bad,
+			passed: this.assertions.length - bad,
+			total: this.assertions.length
+		});
+
+		QUnit.reset();
+
+		config.current = undefined;
+	},
+
+	queue: function() {
+		var bad,
+			test = this;
+
+		synchronize(function() {
+			test.init();
+		});
+		function run() {
+			// each of these can by async
+			synchronize(function() {
+				test.setup();
+			});
+			synchronize(function() {
+				test.run();
+			});
+			synchronize(function() {
+				test.teardown();
+			});
+			synchronize(function() {
+				test.finish();
+			});
+		}
+
+		// `bad` initialized at top of scope
+		// defer when previous test run passed, if storage is available
+		bad = QUnit.config.reorder && defined.sessionStorage &&
+						+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+
+		if ( bad ) {
+			run();
+		} else {
+			synchronize( run, true );
+		}
+	}
+};
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+QUnit = {
+
+	// call on start of module test to prepend name to all tests
+	module: function( name, testEnvironment ) {
+		config.currentModule = name;
+		config.currentModuleTestEnvironment = testEnvironment;
+		config.modules[name] = true;
+	},
+
+	asyncTest: function( testName, expected, callback ) {
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		QUnit.test( testName, expected, callback, true );
+	},
+
+	test: function( testName, expected, callback, async ) {
+		var test,
+			name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
+
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		if ( config.currentModule ) {
+			name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
+		}
+
+		test = new Test({
+			name: name,
+			testName: testName,
+			expected: expected,
+			async: async,
+			callback: callback,
+			module: config.currentModule,
+			moduleTestEnvironment: config.currentModuleTestEnvironment,
+			stack: sourceFromStacktrace( 2 )
+		});
+
+		if ( !validTest( test ) ) {
+			return;
+		}
+
+		test.queue();
+	},
+
+	// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+	expect: function( asserts ) {
+		if (arguments.length === 1) {
+			config.current.expected = asserts;
+		} else {
+			return config.current.expected;
+		}
+	},
+
+	start: function( count ) {
+		config.semaphore -= count || 1;
+		// don't start until equal number of stop-calls
+		if ( config.semaphore > 0 ) {
+			return;
+		}
+		// ignore if start is called more often then stop
+		if ( config.semaphore < 0 ) {
+			config.semaphore = 0;
+		}
+		// A slight delay, to avoid any current callbacks
+		if ( defined.setTimeout ) {
+			window.setTimeout(function() {
+				if ( config.semaphore > 0 ) {
+					return;
+				}
+				if ( config.timeout ) {
+					clearTimeout( config.timeout );
+				}
+
+				config.blocking = false;
+				process( true );
+			}, 13);
+		} else {
+			config.blocking = false;
+			process( true );
+		}
+	},
+
+	stop: function( count ) {
+		config.semaphore += count || 1;
+		config.blocking = true;
+
+		if ( config.testTimeout && defined.setTimeout ) {
+			clearTimeout( config.timeout );
+			config.timeout = window.setTimeout(function() {
+				QUnit.ok( false, "Test timed out" );
+				config.semaphore = 1;
+				QUnit.start();
+			}, config.testTimeout );
+		}
+	}
+};
+
+// Asssert helpers
+// All of these must call either QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+QUnit.assert = {
+	/**
+	 * Asserts rough true-ish result.
+	 * @name ok
+	 * @function
+	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+	 */
+	ok: function( result, msg ) {
+		if ( !config.current ) {
+			throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+		}
+		result = !!result;
+
+		var source,
+			details = {
+				module: config.current.module,
+				name: config.current.testName,
+				result: result,
+				message: msg
+			};
+
+		msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
+		msg = "<span class='test-message'>" + msg + "</span>";
+
+		if ( !result ) {
+			source = sourceFromStacktrace( 2 );
+			if ( source ) {
+				details.source = source;
+				msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
+			}
+		}
+		runLoggingCallbacks( "log", QUnit, details );
+		config.current.assertions.push({
+			result: result,
+			message: msg
+		});
+	},
+
+	/**
+	 * Assert that the first two arguments are equal, with an optional message.
+	 * Prints out both actual and expected values.
+	 * @name equal
+	 * @function
+	 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+	 */
+	equal: function( actual, expected, message ) {
+		QUnit.push( expected == actual, actual, expected, message );
+	},
+
+	/**
+	 * @name notEqual
+	 * @function
+	 */
+	notEqual: function( actual, expected, message ) {
+		QUnit.push( expected != actual, actual, expected, message );
+	},
+
+	/**
+	 * @name deepEqual
+	 * @function
+	 */
+	deepEqual: function( actual, expected, message ) {
+		QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+	},
+
+	/**
+	 * @name notDeepEqual
+	 * @function
+	 */
+	notDeepEqual: function( actual, expected, message ) {
+		QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+	},
+
+	/**
+	 * @name strictEqual
+	 * @function
+	 */
+	strictEqual: function( actual, expected, message ) {
+		QUnit.push( expected === actual, actual, expected, message );
+	},
+
+	/**
+	 * @name notStrictEqual
+	 * @function
+	 */
+	notStrictEqual: function( actual, expected, message ) {
+		QUnit.push( expected !== actual, actual, expected, message );
+	},
+
+	throws: function( block, expected, message ) {
+		var actual,
+			ok = false;
+
+		// 'expected' is optional
+		if ( typeof expected === "string" ) {
+			message = expected;
+			expected = null;
+		}
+
+		config.current.ignoreGlobalErrors = true;
+		try {
+			block.call( config.current.testEnvironment );
+		} catch (e) {
+			actual = e;
+		}
+		config.current.ignoreGlobalErrors = false;
+
+		if ( actual ) {
+			// we don't want to validate thrown error
+			if ( !expected ) {
+				ok = true;
+			// expected is a regexp
+			} else if ( QUnit.objectType( expected ) === "regexp" ) {
+				ok = expected.test( actual );
+			// expected is a constructor
+			} else if ( actual instanceof expected ) {
+				ok = true;
+			// expected is a validation function which returns true is validation passed
+			} else if ( expected.call( {}, actual ) === true ) {
+				ok = true;
+			}
+
+			QUnit.push( ok, actual, null, message );
+		} else {
+			QUnit.pushFailure( message, null, 'No exception was thrown.' );
+		}
+	}
+};
+
+/**
+ * @deprecate since 1.8.0
+ * Kept assertion helpers in root for backwards compatibility
+ */
+extend( QUnit, QUnit.assert );
+
+/**
+ * @deprecated since 1.9.0
+ * Kept global "raises()" for backwards compatibility
+ */
+QUnit.raises = QUnit.assert.throws;
+
+/**
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
+ * Kept to avoid TypeErrors for undefined methods.
+ */
+QUnit.equals = function() {
+	QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.same = function() {
+	QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
+};
+
+// We want access to the constructor's prototype
+(function() {
+	function F() {}
+	F.prototype = QUnit;
+	QUnit = new F();
+	// Make F QUnit's constructor so that we can add to the prototype later
+	QUnit.constructor = F;
+}());
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+	// The queue of tests to run
+	queue: [],
+
+	// block until document ready
+	blocking: true,
+
+	// when enabled, show only failing tests
+	// gets persisted through sessionStorage and can be changed in UI via checkbox
+	hidepassed: false,
+
+	// by default, run previously failed tests first
+	// very useful in combination with "Hide passed tests" checked
+	reorder: true,
+
+	// by default, modify document.title when suite is done
+	altertitle: true,
+
+	// when enabled, all tests must call expect()
+	requireExpects: false,
+
+	// add checkboxes that are persisted in the query-string
+	// when enabled, the id is set to `true` as a `QUnit.config` property
+	urlConfig: [
+		{
+			id: "noglobals",
+			label: "Check for Globals",
+			tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
+		},
+		{
+			id: "notrycatch",
+			label: "No try-catch",
+			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
+		}
+	],
+
+	// Set of all modules.
+	modules: {},
+
+	// logging callback queues
+	begin: [],
+	done: [],
+	log: [],
+	testStart: [],
+	testDone: [],
+	moduleStart: [],
+	moduleDone: []
+};
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+	var i,
+		location = window.location || { search: "", protocol: "file:" },
+		params = location.search.slice( 1 ).split( "&" ),
+		length = params.length,
+		urlParams = {},
+		current;
+
+	if ( params[ 0 ] ) {
+		for ( i = 0; i < length; i++ ) {
+			current = params[ i ].split( "=" );
+			current[ 0 ] = decodeURIComponent( current[ 0 ] );
+			// allow just a key to turn on a flag, e.g., test.html?noglobals
+			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+			urlParams[ current[ 0 ] ] = current[ 1 ];
+		}
+	}
+
+	QUnit.urlParams = urlParams;
+
+	// String search anywhere in moduleName+testName
+	config.filter = urlParams.filter;
+
+	// Exact match of the module name
+	config.module = urlParams.module;
+
+	config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
+
+	// Figure out if we're running the tests from a server or not
+	QUnit.isLocal = location.protocol === "file:";
+}());
+
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+	extend( window, QUnit );
+
+	// Expose QUnit object
+	window.QUnit = QUnit;
+}
+
+// Extend QUnit object,
+// these after set here because they should not be exposed as global functions
+extend( QUnit, {
+	config: config,
+
+	// Initialize the configuration options
+	init: function() {
+		extend( config, {
+			stats: { all: 0, bad: 0 },
+			moduleStats: { all: 0, bad: 0 },
+			started: +new Date(),
+			updateRate: 1000,
+			blocking: false,
+			autostart: true,
+			autorun: false,
+			filter: "",
+			queue: [],
+			semaphore: 0
+		});
+
+		var tests, banner, result,
+			qunit = id( "qunit" );
+
+		if ( qunit ) {
+			qunit.innerHTML =
+				"<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
+				"<h2 id='qunit-banner'></h2>" +
+				"<div id='qunit-testrunner-toolbar'></div>" +
+				"<h2 id='qunit-userAgent'></h2>" +
+				"<ol id='qunit-tests'></ol>";
+		}
+
+		tests = id( "qunit-tests" );
+		banner = id( "qunit-banner" );
+		result = id( "qunit-testresult" );
+
+		if ( tests ) {
+			tests.innerHTML = "";
+		}
+
+		if ( banner ) {
+			banner.className = "";
+		}
+
+		if ( result ) {
+			result.parentNode.removeChild( result );
+		}
+
+		if ( tests ) {
+			result = document.createElement( "p" );
+			result.id = "qunit-testresult";
+			result.className = "result";
+			tests.parentNode.insertBefore( result, tests );
+			result.innerHTML = "Running...<br/>&nbsp;";
+		}
+	},
+
+	// Resets the test setup. Useful for tests that modify the DOM.
+	reset: function() {
+		var fixture = id( "qunit-fixture" );
+		if ( fixture ) {
+			fixture.innerHTML = config.fixture;
+		}
+	},
+
+	// Trigger an event on an element.
+	// @example triggerEvent( document.body, "click" );
+	triggerEvent: function( elem, type, event ) {
+		if ( document.createEvent ) {
+			event = document.createEvent( "MouseEvents" );
+			event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+				0, 0, 0, 0, 0, false, false, false, false, 0, null);
+
+			elem.dispatchEvent( event );
+		} else if ( elem.fireEvent ) {
+			elem.fireEvent( "on" + type );
+		}
+	},
+
+	// Safe object type checking
+	is: function( type, obj ) {
+		return QUnit.objectType( obj ) == type;
+	},
+
+	objectType: function( obj ) {
+		if ( typeof obj === "undefined" ) {
+				return "undefined";
+		// consider: typeof null === object
+		}
+		if ( obj === null ) {
+				return "null";
+		}
+
+		var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || "";
+
+		switch ( type ) {
+			case "Number":
+				if ( isNaN(obj) ) {
+					return "nan";
+				}
+				return "number";
+			case "String":
+			case "Boolean":
+			case "Array":
+			case "Date":
+			case "RegExp":
+			case "Function":
+				return type.toLowerCase();
+		}
+		if ( typeof obj === "object" ) {
+			return "object";
+		}
+		return undefined;
+	},
+
+	push: function( result, actual, expected, message ) {
+		if ( !config.current ) {
+			throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
+		}
+
+		var output, source,
+			details = {
+				module: config.current.module,
+				name: config.current.testName,
+				result: result,
+				message: message,
+				actual: actual,
+				expected: expected
+			};
+
+		message = escapeInnerText( message ) || ( result ? "okay" : "failed" );
+		message = "<span class='test-message'>" + message + "</span>";
+		output = message;
+
+		if ( !result ) {
+			expected = escapeInnerText( QUnit.jsDump.parse(expected) );
+			actual = escapeInnerText( QUnit.jsDump.parse(actual) );
+			output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
+
+			if ( actual != expected ) {
+				output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
+				output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
+			}
+
+			source = sourceFromStacktrace();
+
+			if ( source ) {
+				details.source = source;
+				output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+			}
+
+			output += "</table>";
+		}
+
+		runLoggingCallbacks( "log", QUnit, details );
+
+		config.current.assertions.push({
+			result: !!result,
+			message: output
+		});
+	},
+
+	pushFailure: function( message, source, actual ) {
+		if ( !config.current ) {
+			throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
+		}
+
+		var output,
+			details = {
+				module: config.current.module,
+				name: config.current.testName,
+				result: false,
+				message: message
+			};
+
+		message = escapeInnerText( message ) || "error";
+		message = "<span class='test-message'>" + message + "</span>";
+		output = message;
+
+		output += "<table>";
+
+		if ( actual ) {
+			output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
+		}
+
+		if ( source ) {
+			details.source = source;
+			output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+		}
+
+		output += "</table>";
+
+		runLoggingCallbacks( "log", QUnit, details );
+
+		config.current.assertions.push({
+			result: false,
+			message: output
+		});
+	},
+
+	url: function( params ) {
+		params = extend( extend( {}, QUnit.urlParams ), params );
+		var key,
+			querystring = "?";
+
+		for ( key in params ) {
+			if ( !hasOwn.call( params, key ) ) {
+				continue;
+			}
+			querystring += encodeURIComponent( key ) + "=" +
+				encodeURIComponent( params[ key ] ) + "&";
+		}
+		return window.location.pathname + querystring.slice( 0, -1 );
+	},
+
+	extend: extend,
+	id: id,
+	addEvent: addEvent
+	// load, equiv, jsDump, diff: Attached later
+});
+
+/**
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
+ * QUnit object.
+ */
+extend( QUnit.constructor.prototype, {
+
+	// Logging callbacks; all receive a single argument with the listed properties
+	// run test/logs.html for any related changes
+	begin: registerLoggingCallback( "begin" ),
+
+	// done: { failed, passed, total, runtime }
+	done: registerLoggingCallback( "done" ),
+
+	// log: { result, actual, expected, message }
+	log: registerLoggingCallback( "log" ),
+
+	// testStart: { name }
+	testStart: registerLoggingCallback( "testStart" ),
+
+	// testDone: { name, failed, passed, total }
+	testDone: registerLoggingCallback( "testDone" ),
+
+	// moduleStart: { name }
+	moduleStart: registerLoggingCallback( "moduleStart" ),
+
+	// moduleDone: { name, failed, passed, total }
+	moduleDone: registerLoggingCallback( "moduleDone" )
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+	config.autorun = true;
+}
+
+QUnit.load = function() {
+	runLoggingCallbacks( "begin", QUnit, {} );
+
+	// Initialize the config, saving the execution queue
+	var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes, moduleFilter,
+	    numModules = 0,
+	    moduleFilterHtml = "",
+		urlConfigHtml = "",
+		oldconfig = extend( {}, config );
+
+	QUnit.init();
+	extend(config, oldconfig);
+
+	config.blocking = false;
+
+	len = config.urlConfig.length;
+
+	for ( i = 0; i < len; i++ ) {
+		val = config.urlConfig[i];
+		if ( typeof val === "string" ) {
+			val = {
+				id: val,
+				label: val,
+				tooltip: "[no tooltip available]"
+			};
+		}
+		config[ val.id ] = QUnit.urlParams[ val.id ];
+		urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
+	}
+
+	moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label><select id='qunit-modulefilter' name='modulefilter'><option value='' " + ( config.module === undefined  ? "selected" : "" ) + ">< All Modules ></option>";
+	for ( i in config.modules ) {
+		if ( config.modules.hasOwnProperty( i ) ) {
+			numModules += 1;
+			moduleFilterHtml += "<option value='" + encodeURIComponent(i) + "' " + ( config.module === i ? "selected" : "" ) + ">" + i + "</option>";
+		}
+	}
+	moduleFilterHtml += "</select>";
+
+	// `userAgent` initialized at top of scope
+	userAgent = id( "qunit-userAgent" );
+	if ( userAgent ) {
+		userAgent.innerHTML = navigator.userAgent;
+	}
+
+	// `banner` initialized at top of scope
+	banner = id( "qunit-header" );
+	if ( banner ) {
+		banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
+	}
+
+	// `toolbar` initialized at top of scope
+	toolbar = id( "qunit-testrunner-toolbar" );
+	if ( toolbar ) {
+		// `filter` initialized at top of scope
+		filter = document.createElement( "input" );
+		filter.type = "checkbox";
+		filter.id = "qunit-filter-pass";
+
+		addEvent( filter, "click", function() {
+			var tmp,
+				ol = document.getElementById( "qunit-tests" );
+
+			if ( filter.checked ) {
+				ol.className = ol.className + " hidepass";
+			} else {
+				tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+				ol.className = tmp.replace( / hidepass /, " " );
+			}
+			if ( defined.sessionStorage ) {
+				if (filter.checked) {
+					sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
+				} else {
+					sessionStorage.removeItem( "qunit-filter-passed-tests" );
+				}
+			}
+		});
+
+		if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
+			filter.checked = true;
+			// `ol` initialized at top of scope
+			ol = document.getElementById( "qunit-tests" );
+			ol.className = ol.className + " hidepass";
+		}
+		toolbar.appendChild( filter );
+
+		// `label` initialized at top of scope
+		label = document.createElement( "label" );
+		label.setAttribute( "for", "qunit-filter-pass" );
+		label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
+		label.innerHTML = "Hide passed tests";
+		toolbar.appendChild( label );
+
+		urlConfigCheckboxes = document.createElement( 'span' );
+		urlConfigCheckboxes.innerHTML = urlConfigHtml;
+		addEvent( urlConfigCheckboxes, "change", function( event ) {
+			var params = {};
+			params[ event.target.name ] = event.target.checked ? true : undefined;
+			window.location = QUnit.url( params );
+		});
+		toolbar.appendChild( urlConfigCheckboxes );
+
+		if (numModules > 1) {
+			moduleFilter = document.createElement( 'span' );
+			moduleFilter.setAttribute( 'id', 'qunit-modulefilter-container' );
+			moduleFilter.innerHTML = moduleFilterHtml;
+			addEvent( moduleFilter, "change", function() {
+				var selectBox = moduleFilter.getElementsByTagName("select")[0],
+				    selectedModule = decodeURIComponent(selectBox.options[selectBox.selectedIndex].value);
+
+				window.location = QUnit.url( { module: ( selectedModule === "" ) ? undefined : selectedModule } );
+			});
+			toolbar.appendChild(moduleFilter);
+		}
+	}
+
+	// `main` initialized at top of scope
+	main = id( "qunit-fixture" );
+	if ( main ) {
+		config.fixture = main.innerHTML;
+	}
+
+	if ( config.autostart ) {
+		QUnit.start();
+	}
+};
+
+addEvent( window, "load", QUnit.load );
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will surpress the default browser handler,
+// returning false will let it run.
+window.onerror = function ( error, filePath, linerNr ) {
+	var ret = false;
+	if ( onErrorFnPrev ) {
+		ret = onErrorFnPrev( error, filePath, linerNr );
+	}
+
+	// Treat return value as window.onerror itself does,
+	// Only do our handling if not surpressed.
+	if ( ret !== true ) {
+		if ( QUnit.config.current ) {
+			if ( QUnit.config.current.ignoreGlobalErrors ) {
+				return true;
+			}
+			QUnit.pushFailure( error, filePath + ":" + linerNr );
+		} else {
+			QUnit.test( "global failure", extend( function() {
+				QUnit.pushFailure( error, filePath + ":" + linerNr );
+			}, { validTest: validTest } ) );
+		}
+		return false;
+	}
+
+	return ret;
+};
+
+function done() {
+	config.autorun = true;
+
+	// Log the last module results
+	if ( config.currentModule ) {
+		runLoggingCallbacks( "moduleDone", QUnit, {
+			name: config.currentModule,
+			failed: config.moduleStats.bad,
+			passed: config.moduleStats.all - config.moduleStats.bad,
+			total: config.moduleStats.all
+		});
+	}
+
+	var i, key,
+		banner = id( "qunit-banner" ),
+		tests = id( "qunit-tests" ),
+		runtime = +new Date() - config.started,
+		passed = config.stats.all - config.stats.bad,
+		html = [
+			"Tests completed in ",
+			runtime,
+			" milliseconds.<br/>",
+			"<span class='passed'>",
+			passed,
+			"</span> tests of <span class='total'>",
+			config.stats.all,
+			"</span> passed, <span class='failed'>",
+			config.stats.bad,
+			"</span> failed."
+		].join( "" );
+
+	if ( banner ) {
+		banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
+	}
+
+	if ( tests ) {
+		id( "qunit-testresult" ).innerHTML = html;
+	}
+
+	if ( config.altertitle && typeof document !== "undefined" && document.title ) {
+		// show â for good, â for bad suite result in title
+		// use escape sequences in case file gets loaded with non-utf-8-charset
+		document.title = [
+			( config.stats.bad ? "\u2716" : "\u2714" ),
+			document.title.replace( /^[\u2714\u2716] /i, "" )
+		].join( " " );
+	}
+
+	// clear own sessionStorage items if all tests passed
+	if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+		// `key` & `i` initialized at top of scope
+		for ( i = 0; i < sessionStorage.length; i++ ) {
+			key = sessionStorage.key( i++ );
+			if ( key.indexOf( "qunit-test-" ) === 0 ) {
+				sessionStorage.removeItem( key );
+			}
+		}
+	}
+
+	// scroll back to top to show results
+	if ( window.scrollTo ) {
+		window.scrollTo(0, 0);
+	}
+
+	runLoggingCallbacks( "done", QUnit, {
+		failed: config.stats.bad,
+		passed: passed,
+		total: config.stats.all,
+		runtime: runtime
+	});
+}
+
+/** @return Boolean: true if this test should be ran */
+function validTest( test ) {
+	var include,
+		filter = config.filter && config.filter.toLowerCase(),
+		module = config.module && config.module.toLowerCase(),
+		fullName = (test.module + ": " + test.testName).toLowerCase();
+
+	// Internally-generated tests are always valid
+	if ( test.callback && test.callback.validTest === validTest ) {
+		delete test.callback.validTest;
+		return true;
+	}
+
+	if ( config.testNumber ) {
+		return test.testNumber === config.testNumber;
+	}
+
+	if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
+		return false;
+	}
+
+	if ( !filter ) {
+		return true;
+	}
+
+	include = filter.charAt( 0 ) !== "!";
+	if ( !include ) {
+		filter = filter.slice( 1 );
+	}
+
+	// If the filter matches, we need to honour include
+	if ( fullName.indexOf( filter ) !== -1 ) {
+		return include;
+	}
+
+	// Otherwise, do the opposite
+	return !include;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
+// Later Safari and IE10 are supposed to support error.stack as well
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+	offset = offset === undefined ? 3 : offset;
+
+	var stack, include, i, regex;
+
+	if ( e.stacktrace ) {
+		// Opera
+		return e.stacktrace.split( "\n" )[ offset + 3 ];
+	} else if ( e.stack ) {
+		// Firefox, Chrome
+		stack = e.stack.split( "\n" );
+		if (/^error$/i.test( stack[0] ) ) {
+			stack.shift();
+		}
+		if ( fileName ) {
+			include = [];
+			for ( i = offset; i < stack.length; i++ ) {
+				if ( stack[ i ].indexOf( fileName ) != -1 ) {
+					break;
+				}
+				include.push( stack[ i ] );
+			}
+			if ( include.length ) {
+				return include.join( "\n" );
+			}
+		}
+		return stack[ offset ];
+	} else if ( e.sourceURL ) {
+		// Safari, PhantomJS
+		// hopefully one day Safari provides actual stacktraces
+		// exclude useless self-reference for generated Error objects
+		if ( /qunit.js$/.test( e.sourceURL ) ) {
+			return;
+		}
+		// for actual exceptions, this is useful
+		return e.sourceURL + ":" + e.line;
+	}
+}
+function sourceFromStacktrace( offset ) {
+	try {
+		throw new Error();
+	} catch ( e ) {
+		return extractStacktrace( e, offset );
+	}
+}
+
+function escapeInnerText( s ) {
+	if ( !s ) {
+		return "";
+	}
+	s = s + "";
+	return s.replace( /[\&<>]/g, function( s ) {
+		switch( s ) {
+			case "&": return "&amp;";
+			case "<": return "&lt;";
+			case ">": return "&gt;";
+			default: return s;
+		}
+	});
+}
+
+function synchronize( callback, last ) {
+	config.queue.push( callback );
+
+	if ( config.autorun && !config.blocking ) {
+		process( last );
+	}
+}
+
+function process( last ) {
+	function next() {
+		process( last );
+	}
+	var start = new Date().getTime();
+	config.depth = config.depth ? config.depth + 1 : 1;
+
+	while ( config.queue.length && !config.blocking ) {
+		if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+			config.queue.shift()();
+		} else {
+			window.setTimeout( next, 13 );
+			break;
+		}
+	}
+	config.depth--;
+	if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+		done();
+	}
+}
+
+function saveGlobal() {
+	config.pollution = [];
+
+	if ( config.noglobals ) {
+		for ( var key in window ) {
+			// in Opera sometimes DOM element ids show up here, ignore them
+			if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
+				continue;
+			}
+			config.pollution.push( key );
+		}
+	}
+}
+
+function checkPollution( name ) {
+	var newGlobals,
+		deletedGlobals,
+		old = config.pollution;
+
+	saveGlobal();
+
+	newGlobals = diff( config.pollution, old );
+	if ( newGlobals.length > 0 ) {
+		QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
+	}
+
+	deletedGlobals = diff( old, config.pollution );
+	if ( deletedGlobals.length > 0 ) {
+		QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
+	}
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+	var i, j,
+		result = a.slice();
+
+	for ( i = 0; i < result.length; i++ ) {
+		for ( j = 0; j < b.length; j++ ) {
+			if ( result[i] === b[j] ) {
+				result.splice( i, 1 );
+				i--;
+				break;
+			}
+		}
+	}
+	return result;
+}
+
+function extend( a, b ) {
+	for ( var prop in b ) {
+		if ( b[ prop ] === undefined ) {
+			delete a[ prop ];
+
+		// Avoid "Member not found" error in IE8 caused by setting window.constructor
+		} else if ( prop !== "constructor" || a !== window ) {
+			a[ prop ] = b[ prop ];
+		}
+	}
+
+	return a;
+}
+
+function addEvent( elem, type, fn ) {
+	if ( elem.addEventListener ) {
+		elem.addEventListener( type, fn, false );
+	} else if ( elem.attachEvent ) {
+		elem.attachEvent( "on" + type, fn );
+	} else {
+		fn();
+	}
+}
+
+function id( name ) {
+	return !!( typeof document !== "undefined" && document && document.getElementById ) &&
+		document.getElementById( name );
+}
+
+function registerLoggingCallback( key ) {
+	return function( callback ) {
+		config[key].push( callback );
+	};
+}
+
+// Supports deprecated method of completely overwriting logging callbacks
+function runLoggingCallbacks( key, scope, args ) {
+	//debugger;
+	var i, callbacks;
+	if ( QUnit.hasOwnProperty( key ) ) {
+		QUnit[ key ].call(scope, args );
+	} else {
+		callbacks = config[ key ];
+		for ( i = 0; i < callbacks.length; i++ ) {
+			callbacks[ i ].call( scope, args );
+		}
+	}
+}
+
+// Test for equality any JavaScript type.
+// Author: Philippe RathÃ© <prathe@gmail.com>
+QUnit.equiv = (function() {
+
+	// Call the o related callback with the given arguments.
+	function bindCallbacks( o, callbacks, args ) {
+		var prop = QUnit.objectType( o );
+		if ( prop ) {
+			if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+				return callbacks[ prop ].apply( callbacks, args );
+			} else {
+				return callbacks[ prop ]; // or undefined
+			}
+		}
+	}
+
+	// the real equiv function
+	var innerEquiv,
+		// stack to decide between skip/abort functions
+		callers = [],
+		// stack to avoiding loops from circular referencing
+		parents = [],
+
+		getProto = Object.getPrototypeOf || function ( obj ) {
+			return obj.__proto__;
+		},
+		callbacks = (function () {
+
+			// for string, boolean, number and null
+			function useStrictEquality( b, a ) {
+				if ( b instanceof a.constructor || a instanceof b.constructor ) {
+					// to catch short annotaion VS 'new' annotation of a
+					// declaration
+					// e.g. var i = 1;
+					// var j = new Number(1);
+					return a == b;
+				} else {
+					return a === b;
+				}
+			}
+
+			return {
+				"string": useStrictEquality,
+				"boolean": useStrictEquality,
+				"number": useStrictEquality,
+				"null": useStrictEquality,
+				"undefined": useStrictEquality,
+
+				"nan": function( b ) {
+					return isNaN( b );
+				},
+
+				"date": function( b, a ) {
+					return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+				},
+
+				"regexp": function( b, a ) {
+					return QUnit.objectType( b ) === "regexp" &&
+						// the regex itself
+						a.source === b.source &&
+						// and its modifers
+						a.global === b.global &&
+						// (gmi) ...
+						a.ignoreCase === b.ignoreCase &&
+						a.multiline === b.multiline &&
+						a.sticky === b.sticky;
+				},
+
+				// - skip when the property is a method of an instance (OOP)
+				// - abort otherwise,
+				// initial === would have catch identical references anyway
+				"function": function() {
+					var caller = callers[callers.length - 1];
+					return caller !== Object && typeof caller !== "undefined";
+				},
+
+				"array": function( b, a ) {
+					var i, j, len, loop;
+
+					// b could be an object literal here
+					if ( QUnit.objectType( b ) !== "array" ) {
+						return false;
+					}
+
+					len = a.length;
+					if ( len !== b.length ) {
+						// safe and faster
+						return false;
+					}
+
+					// track reference to avoid circular references
+					parents.push( a );
+					for ( i = 0; i < len; i++ ) {
+						loop = false;
+						for ( j = 0; j < parents.length; j++ ) {
+							if ( parents[j] === a[i] ) {
+								loop = true;// dont rewalk array
+							}
+						}
+						if ( !loop && !innerEquiv(a[i], b[i]) ) {
+							parents.pop();
+							return false;
+						}
+					}
+					parents.pop();
+					return true;
+				},
+
+				"object": function( b, a ) {
+					var i, j, loop,
+						// Default to true
+						eq = true,
+						aProperties = [],
+						bProperties = [];
+
+					// comparing constructors is more strict than using
+					// instanceof
+					if ( a.constructor !== b.constructor ) {
+						// Allow objects with no prototype to be equivalent to
+						// objects with Object as their constructor.
+						if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
+							( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
+								return false;
+						}
+					}
+
+					// stack constructor before traversing properties
+					callers.push( a.constructor );
+					// track reference to avoid circular references
+					parents.push( a );
+
+					for ( i in a ) { // be strict: don't ensures hasOwnProperty
+									// and go deep
+						loop = false;
+						for ( j = 0; j < parents.length; j++ ) {
+							if ( parents[j] === a[i] ) {
+								// don't go down the same path twice
+								loop = true;
+							}
+						}
+						aProperties.push(i); // collect a's properties
+
+						if (!loop && !innerEquiv( a[i], b[i] ) ) {
+							eq = false;
+							break;
+						}
+					}
+
+					callers.pop(); // unstack, we are done
+					parents.pop();
+
+					for ( i in b ) {
+						bProperties.push( i ); // collect b's properties
+					}
+
+					// Ensures identical properties name
+					return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+				}
+			};
+		}());
+
+	innerEquiv = function() { // can take multiple arguments
+		var args = [].slice.apply( arguments );
+		if ( args.length < 2 ) {
+			return true; // end transition
+		}
+
+		return (function( a, b ) {
+			if ( a === b ) {
+				return true; // catch the most you can
+			} else if ( a === null || b === null || typeof a === "undefined" ||
+					typeof b === "undefined" ||
+					QUnit.objectType(a) !== QUnit.objectType(b) ) {
+				return false; // don't lose time with error prone cases
+			} else {
+				return bindCallbacks(a, callbacks, [ b, a ]);
+			}
+
+			// apply transition with (1..n) arguments
+		}( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) );
+	};
+
+	return innerEquiv;
+}());
+
+/**
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+	function quote( str ) {
+		return '"' + str.toString().replace( /"/g, '\\"' ) + '"';
+	}
+	function literal( o ) {
+		return o + "";
+	}
+	function join( pre, arr, post ) {
+		var s = jsDump.separator(),
+			base = jsDump.indent(),
+			inner = jsDump.indent(1);
+		if ( arr.join ) {
+			arr = arr.join( "," + s + inner );
+		}
+		if ( !arr ) {
+			return pre + post;
+		}
+		return [ pre, inner + arr, base + post ].join(s);
+	}
+	function array( arr, stack ) {
+		var i = arr.length, ret = new Array(i);
+		this.up();
+		while ( i-- ) {
+			ret[i] = this.parse( arr[i] , undefined , stack);
+		}
+		this.down();
+		return join( "[", ret, "]" );
+	}
+
+	var reName = /^function (\w+)/,
+		jsDump = {
+			parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
+				stack = stack || [ ];
+				var inStack, res,
+					parser = this.parsers[ type || this.typeOf(obj) ];
+
+				type = typeof parser;
+				inStack = inArray( obj, stack );
+
+				if ( inStack != -1 ) {
+					return "recursion(" + (inStack - stack.length) + ")";
+				}
+				//else
+				if ( type == "function" )  {
+					stack.push( obj );
+					res = parser.call( this, obj, stack );
+					stack.pop();
+					return res;
+				}
+				// else
+				return ( type == "string" ) ? parser : this.parsers.error;
+			},
+			typeOf: function( obj ) {
+				var type;
+				if ( obj === null ) {
+					type = "null";
+				} else if ( typeof obj === "undefined" ) {
+					type = "undefined";
+				} else if ( QUnit.is( "regexp", obj) ) {
+					type = "regexp";
+				} else if ( QUnit.is( "date", obj) ) {
+					type = "date";
+				} else if ( QUnit.is( "function", obj) ) {
+					type = "function";
+				} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
+					type = "window";
+				} else if ( obj.nodeType === 9 ) {
+					type = "document";
+				} else if ( obj.nodeType ) {
+					type = "node";
+				} else if (
+					// native arrays
+					toString.call( obj ) === "[object Array]" ||
+					// NodeList objects
+					( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
+				) {
+					type = "array";
+				} else {
+					type = typeof obj;
+				}
+				return type;
+			},
+			separator: function() {
+				return this.multiline ?	this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
+			},
+			indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
+				if ( !this.multiline ) {
+					return "";
+				}
+				var chr = this.indentChar;
+				if ( this.HTML ) {
+					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&nbsp;" );
+				}
+				return new Array( this._depth_ + (extra||0) ).join(chr);
+			},
+			up: function( a ) {
+				this._depth_ += a || 1;
+			},
+			down: function( a ) {
+				this._depth_ -= a || 1;
+			},
+			setParser: function( name, parser ) {
+				this.parsers[name] = parser;
+			},
+			// The next 3 are exposed so you can use them
+			quote: quote,
+			literal: literal,
+			join: join,
+			//
+			_depth_: 1,
+			// This is the list of parsers, to modify them, use jsDump.setParser
+			parsers: {
+				window: "[Window]",
+				document: "[Document]",
+				error: "[ERROR]", //when no parser is found, shouldn"t happen
+				unknown: "[Unknown]",
+				"null": "null",
+				"undefined": "undefined",
+				"function": function( fn ) {
+					var ret = "function",
+						name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE
+
+					if ( name ) {
+						ret += " " + name;
+					}
+					ret += "( ";
+
+					ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
+					return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
+				},
+				array: array,
+				nodelist: array,
+				"arguments": array,
+				object: function( map, stack ) {
+					var ret = [ ], keys, key, val, i;
+					QUnit.jsDump.up();
+					if ( Object.keys ) {
+						keys = Object.keys( map );
+					} else {
+						keys = [];
+						for ( key in map ) {
+							keys.push( key );
+						}
+					}
+					keys.sort();
+					for ( i = 0; i < keys.length; i++ ) {
+						key = keys[ i ];
+						val = map[ key ];
+						ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
+					}
+					QUnit.jsDump.down();
+					return join( "{", ret, "}" );
+				},
+				node: function( node ) {
+					var a, val,
+						open = QUnit.jsDump.HTML ? "&lt;" : "<",
+						close = QUnit.jsDump.HTML ? "&gt;" : ">",
+						tag = node.nodeName.toLowerCase(),
+						ret = open + tag;
+
+					for ( a in QUnit.jsDump.DOMAttrs ) {
+						val = node[ QUnit.jsDump.DOMAttrs[a] ];
+						if ( val ) {
+							ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" );
+						}
+					}
+					return ret + close + open + "/" + tag + close;
+				},
+				functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
+					var args,
+						l = fn.length;
+
+					if ( !l ) {
+						return "";
+					}
+
+					args = new Array(l);
+					while ( l-- ) {
+						args[l] = String.fromCharCode(97+l);//97 is 'a'
+					}
+					return " " + args.join( ", " ) + " ";
+				},
+				key: quote, //object calls it internally, the key part of an item in a map
+				functionCode: "[code]", //function calls it internally, it's the content of the function
+				attribute: quote, //node calls it internally, it's an html attribute value
+				string: quote,
+				date: quote,
+				regexp: literal, //regex
+				number: literal,
+				"boolean": literal
+			},
+			DOMAttrs: {
+				//attributes to dump from nodes, name=>realName
+				id: "id",
+				name: "name",
+				"class": "className"
+			},
+			HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
+			indentChar: "  ",//indentation unit
+			multiline: true //if true, items in a collection, are separated by a \n, else just a space.
+		};
+
+	return jsDump;
+}());
+
+// from Sizzle.js
+function getText( elems ) {
+	var i, elem,
+		ret = "";
+
+	for ( i = 0; elems[i]; i++ ) {
+		elem = elems[i];
+
+		// Get the text from text nodes and CDATA nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+			ret += elem.nodeValue;
+
+		// Traverse everything else, except comment nodes
+		} else if ( elem.nodeType !== 8 ) {
+			ret += getText( elem.childNodes );
+		}
+	}
+
+	return ret;
+}
+
+// from jquery.js
+function inArray( elem, array ) {
+	if ( array.indexOf ) {
+		return array.indexOf( elem );
+	}
+
+	for ( var i = 0, length = array.length; i < length; i++ ) {
+		if ( array[ i ] === elem ) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+/*
+ * Javascript Diff Algorithm
+ *  By John Resig (http://ejohn.org/)
+ *  Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ *  http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
+ */
+QUnit.diff = (function() {
+	function diff( o, n ) {
+		var i,
+			ns = {},
+			os = {};
+
+		for ( i = 0; i < n.length; i++ ) {
+			if ( ns[ n[i] ] == null ) {
+				ns[ n[i] ] = {
+					rows: [],
+					o: null
+				};
+			}
+			ns[ n[i] ].rows.push( i );
+		}
+
+		for ( i = 0; i < o.length; i++ ) {
+			if ( os[ o[i] ] == null ) {
+				os[ o[i] ] = {
+					rows: [],
+					n: null
+				};
+			}
+			os[ o[i] ].rows.push( i );
+		}
+
+		for ( i in ns ) {
+			if ( !hasOwn.call( ns, i ) ) {
+				continue;
+			}
+			if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) {
+				n[ ns[i].rows[0] ] = {
+					text: n[ ns[i].rows[0] ],
+					row: os[i].rows[0]
+				};
+				o[ os[i].rows[0] ] = {
+					text: o[ os[i].rows[0] ],
+					row: ns[i].rows[0]
+				};
+			}
+		}
+
+		for ( i = 0; i < n.length - 1; i++ ) {
+			if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
+						n[ i + 1 ] == o[ n[i].row + 1 ] ) {
+
+				n[ i + 1 ] = {
+					text: n[ i + 1 ],
+					row: n[i].row + 1
+				};
+				o[ n[i].row + 1 ] = {
+					text: o[ n[i].row + 1 ],
+					row: i + 1
+				};
+			}
+		}
+
+		for ( i = n.length - 1; i > 0; i-- ) {
+			if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
+						n[ i - 1 ] == o[ n[i].row - 1 ]) {
+
+				n[ i - 1 ] = {
+					text: n[ i - 1 ],
+					row: n[i].row - 1
+				};
+				o[ n[i].row - 1 ] = {
+					text: o[ n[i].row - 1 ],
+					row: i - 1
+				};
+			}
+		}
+
+		return {
+			o: o,
+			n: n
+		};
+	}
+
+	return function( o, n ) {
+		o = o.replace( /\s+$/, "" );
+		n = n.replace( /\s+$/, "" );
+
+		var i, pre,
+			str = "",
+			out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
+			oSpace = o.match(/\s+/g),
+			nSpace = n.match(/\s+/g);
+
+		if ( oSpace == null ) {
+			oSpace = [ " " ];
+		}
+		else {
+			oSpace.push( " " );
+		}
+
+		if ( nSpace == null ) {
+			nSpace = [ " " ];
+		}
+		else {
+			nSpace.push( " " );
+		}
+
+		if ( out.n.length === 0 ) {
+			for ( i = 0; i < out.o.length; i++ ) {
+				str += "<del>" + out.o[i] + oSpace[i] + "</del>";
+			}
+		}
+		else {
+			if ( out.n[0].text == null ) {
+				for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
+					str += "<del>" + out.o[n] + oSpace[n] + "</del>";
+				}
+			}
+
+			for ( i = 0; i < out.n.length; i++ ) {
+				if (out.n[i].text == null) {
+					str += "<ins>" + out.n[i] + nSpace[i] + "</ins>";
+				}
+				else {
+					// `pre` initialized at top of scope
+					pre = "";
+
+					for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
+						pre += "<del>" + out.o[n] + oSpace[n] + "</del>";
+					}
+					str += " " + out.n[i].text + nSpace[i] + pre;
+				}
+			}
+		}
+
+		return str;
+	};
+}());
+
+// for CommonJS enviroments, export everything
+if ( typeof exports !== "undefined" ) {
+	extend(exports, QUnit);
+}
+
+// get at whatever the global object is, like window in browsers
+}( (function() {return this;}.call()) ));
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_sources.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_sources.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_sources.html	(revision 28897)
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ThreeJS Unit Tests - Using Files in /src</title>
+  <link rel="stylesheet" href="qunit-1.10.0.css">
+</head>
+<body>
+  <div id="qunit"></div>
+  <script src="qunit-1.10.0.js"></script>
+
+  <!-- add sources to test below -->
+
+  <script src="../../src/Three.js"></script>
+  <script src="../../src/math/Color.js"></script>
+  <script src="../../src/math/Quaternion.js"></script>
+  <script src="../../src/math/Vector2.js"></script>
+  <script src="../../src/math/Vector3.js"></script>
+  <script src="../../src/math/Vector4.js"></script>
+  <script src="../../src/math/Euler.js"></script>
+  <script src="../../src/math/Line3.js"></script>
+  <script src="../../src/math/Box2.js"></script>
+  <script src="../../src/math/Box3.js"></script>
+  <script src="../../src/math/Matrix3.js"></script>
+  <script src="../../src/math/Matrix4.js"></script>
+  <script src="../../src/math/Ray.js"></script>
+  <script src="../../src/math/Sphere.js"></script>
+  <script src="../../src/math/Frustum.js"></script>
+  <script src="../../src/math/Plane.js"></script>
+  <script src="../../src/math/Math.js"></script>
+  <script src="../../src/math/Triangle.js"></script>
+
+  <!-- add class-based unit tests below -->
+
+  <script src="math/Constants.js"></script>
+  <script src="math/Box2.js"></script>
+  <script src="math/Box3.js"></script>
+  <script src="math/Plane.js"></script>
+  <script src="math/Ray.js"></script>
+  <script src="math/Sphere.js"></script>
+  <script src="math/Triangle.js"></script>
+  <script src="math/Vector2.js"></script>
+  <script src="math/Vector3.js"></script>
+  <script src="math/Vector4.js"></script>
+  <script src="math/Euler.js"></script>
+  <script src="math/Line3.js"></script>
+  <script src="math/Quaternion.js"></script>
+  <script src="math/Matrix3.js"></script>
+  <script src="math/Matrix4.js"></script>
+  <script src="math/Frustum.js"></script>
+  <script src="math/Color.js"></script>
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three-math.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three-math.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three-math.html	(revision 28897)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ThreeJS Unit Tests - Using build/Three-math.js</title>
+  <link rel="stylesheet" href="qunit-1.10.0.css">
+</head>
+<body>
+  <div id="qunit"></div>
+  <script src="qunit-1.10.0.js"></script>
+
+  <!-- add sources to test below -->
+
+  <script src="../../build/three-math.js"></script>
+
+  <!-- add class-based unit tests below -->
+
+  <script src="math/Constants.js"></script>
+  <script src="math/Box2.js"></script>
+  <script src="math/Box3.js"></script>
+  <script src="math/Plane.js"></script>
+  <script src="math/Ray.js"></script>
+  <script src="math/Sphere.js"></script>
+  <script src="math/Triangle.js"></script>
+  <script src="math/Vector2.js"></script>
+  <script src="math/Vector3.js"></script>
+  <script src="math/Vector4.js"></script>
+  <script src="math/Euler.js"></script>
+  <script src="math/Line3.js"></script>
+  <script src="math/Quaternion.js"></script>
+  <script src="math/Matrix3.js"></script>
+  <script src="math/Matrix4.js"></script>
+  <script src="math/Frustum.js"></script>
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three.html	(revision 28897)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ThreeJS Unit Tests - Using build/Three.js</title>
+  <link rel="stylesheet" href="qunit-1.10.0.css">
+</head>
+<body>
+  <div id="qunit"></div>
+  <script src="qunit-1.10.0.js"></script>
+
+  <!-- add sources to test below -->
+
+  <script src="../../build/three.js"></script>
+
+  <!-- add class-based unit tests below -->
+
+  <script src="math/Constants.js"></script>
+  <script src="math/Box2.js"></script>
+  <script src="math/Box3.js"></script>
+  <script src="math/Plane.js"></script>
+  <script src="math/Ray.js"></script>
+  <script src="math/Sphere.js"></script>
+  <script src="math/Triangle.js"></script>
+  <script src="math/Vector2.js"></script>
+  <script src="math/Vector3.js"></script>
+  <script src="math/Vector4.js"></script>
+  <script src="math/Euler.js"></script>
+  <script src="math/Line3.js"></script>
+  <script src="math/Quaternion.js"></script>
+  <script src="math/Matrix3.js"></script>
+  <script src="math/Matrix4.js"></script>
+  <script src="math/Frustum.js"></script>
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three.min.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three.min.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/test/unit/unittests_three.min.html	(revision 28897)
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>ThreeJS Unit Tests - Using build/Three.min.js</title>
+  <link rel="stylesheet" href="qunit-1.10.0.css">
+</head>
+<body>
+  <div id="qunit"></div>
+  <script src="qunit-1.10.0.js"></script>
+
+  <!-- add sources to test below -->
+
+  <script src="../../build/three.min.js"></script>
+
+  <!-- add class-based unit tests below -->
+
+  <script src="math/Constants.js"></script>
+  <script src="math/Box2.js"></script>
+  <script src="math/Box3.js"></script>
+  <script src="math/Plane.js"></script>
+  <script src="math/Ray.js"></script>
+  <script src="math/Sphere.js"></script>
+  <script src="math/Triangle.js"></script>
+  <script src="math/Vector2.js"></script>
+  <script src="math/Vector3.js"></script>
+  <script src="math/Vector4.js"></script>
+  <script src="math/Euler.js"></script>
+  <script src="math/Line3.js"></script>
+  <script src="math/Quaternion.js"></script>
+  <script src="math/Matrix3.js"></script>
+  <script src="math/Matrix4.js"></script>
+  <script src="math/Frustum.js"></script>
+</body>
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.bat
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.bat	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.bat	(revision 28897)
@@ -0,0 +1,2 @@
+python build.py --include common --include extras --output ../../build/three.js
+python build.py --include common --include extras --minify --output ../../build/three.min.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.js	(revision 28897)
@@ -0,0 +1,75 @@
+var fs = require("fs");
+var path = require("path");
+var argparse =  require( "argparse" );
+var uglify = require("uglify-js2");
+var spawn = require('child_process').spawn;
+
+function main() {
+
+	"use strict";
+
+	var parser = new argparse.ArgumentParser();
+	parser.addArgument( ['--include'], { action: 'append', required: true } );
+	parser.addArgument( ['--externs'], { action: 'append', defaultValue: ['./externs/common.js'] } );
+	parser.addArgument( ['--minify'], { action: 'storeTrue', defaultValue: false } );
+	parser.addArgument( ['--output'], { defaultValue: '../../build/three.js' } );
+	parser.addArgument( ['--sourcemaps'], { action: 'storeTrue', defaultValue: false } );
+
+	
+	var args = parser.parseArgs();
+	
+	var output = args.output;
+	console.log(' * Building ' + output);
+	
+	var sourcemap = '';
+	var sourcemapping = '';
+
+	if ( args.sourcemaps ){
+
+		sourcemap = output + '.map';
+		sourcemapping = '\n//@ sourceMappingURL=' + sourcemap;
+
+	}
+
+	var buffer = [];
+	var sources = [];
+
+	for ( var i = 0; i < args.include.length; i ++ ){
+		
+		var contents = fs.readFileSync( './includes/' + args.include[i] + '.json', 'utf8' );
+		var files = JSON.parse( contents );
+
+		for ( var j = 0; j < files.length; j ++ ){
+
+			var file = '../../' + files[ j ];
+			sources.push( file );
+			buffer.push( fs.readFileSync( file, 'utf8' ) );
+
+		}
+
+	}
+
+	console.log( buffer.length );
+	var temp = buffer.join( '' );
+	
+	if ( !args.minify ){
+
+		fs.writeFileSync( output,temp, 'utf8' );
+
+	} else {
+
+		var result = uglify.minify( sources, { outSourceMap: sourcemap } );
+		
+		fs.writeFileSync( output, result.code + sourcemapping, 'utf8' );
+
+		if ( args.sourcemaps ) {
+
+			fs.writeFileSync( sourcemap, result.map, 'utf8' );
+
+		}
+
+	}
+
+}
+
+main();
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.py	(revision 28897)
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+import sys
+
+if sys.version_info < (2, 7):
+	print("This script requires at least Python 2.7.")
+	print("Please, update to a newer version: http://www.python.org/download/releases/")
+	exit()
+
+import argparse
+import json
+import os
+import shutil
+import tempfile
+
+
+def main(argv=None):
+
+	parser = argparse.ArgumentParser()
+	parser.add_argument('--include', action='append', required=True)
+	parser.add_argument('--externs', action='append', default=['externs/common.js'])
+	parser.add_argument('--minify', action='store_true', default=False)
+	parser.add_argument('--output', default='../../build/three.js')
+	parser.add_argument('--sourcemaps', action='store_true', default=False)
+
+	args = parser.parse_args()
+
+	output = args.output
+
+	# merge
+
+	print(' * Building ' + output)
+
+	# enable sourcemaps support
+
+	if args.sourcemaps:
+		sourcemap = output + '.map'
+		sourcemapping = '\n//@ sourceMappingURL=' + sourcemap
+		sourcemapargs = ' --create_source_map ' + sourcemap + ' --source_map_format=V3'
+	else:
+		sourcemap = sourcemapping = sourcemapargs = ''
+
+	fd, path = tempfile.mkstemp()
+	tmp = open(path, 'w')
+	sources = []
+
+	for include in args.include:
+		with open('includes/' + include + '.json','r') as f:
+			files = json.load(f)
+		for filename in files:
+			filename = '../../' + filename;
+			sources.append(filename)
+			with open(filename, 'r') as f:
+				tmp.write(f.read())
+				tmp.write('\n')
+
+	tmp.close()
+
+	# save
+
+	if args.minify is False:
+		shutil.copy(path, output)
+		os.chmod(output, 0o664); # temp files would usually get 0600
+
+	else:
+
+		externs = ' --externs '.join(args.externs)
+		source = ' '.join(sources)
+		cmd = 'java -jar compiler/compiler.jar --warning_level=VERBOSE --jscomp_off=globalThis --externs %s --jscomp_off=checkTypes --language_in=ECMASCRIPT5_STRICT --js %s --js_output_file %s %s' % (externs, source, output, sourcemapargs)
+		os.system(cmd)
+
+		# header
+
+		with open(output,'r') as f: text = f.read()
+		with open(output,'w') as f: f.write('// three.js - http://github.com/mrdoob/three.js\n' + text + sourcemapping)
+
+	os.close(fd)
+	os.remove(path)
+
+
+if __name__ == "__main__":
+	main()
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.sh	(revision 28897)
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+python build.py --include common --include extras --output ../../build/three.js
+python build.py --include common --include extras --minify --output ../../build/three.min.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.xml
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.xml	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build.xml	(revision 28897)
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="THREE.JS" basedir="." default="build">
+	<property name="build_dir" value="./" description="THREE.js build directory" />
+	<property name="build_py" value="${build_dir}/build.py" description="THREE.js 'build.py' file" />
+	<property name="python_dir" value="" description="Python directory, leave empty if in classpath or don't forget ending slash" />
+
+    <target name="build" description="Build all THREE.js">
+    	<exec executable="${python_dir}python">
+    	    <arg value="${build_py}"/>
+    	    <arg value="--all"/>
+    	  </exec>
+    </target>
+	
+    <target name="common" description="Build THREE.js commons">
+    	<exec executable="${python_dir}python">
+    	    <arg value="${build_py}"/>
+    	    <arg value="--common"/>
+    	    <arg value="--includes"/>
+    	  </exec>
+    </target>
+
+	<target name="debug" description="Build debug THREE.js">
+    	<exec executable="${python_dir}python">
+    	    <arg value="${build_py}"/>
+    	    <arg value="--all"/>
+    	    <arg value="--debug"/>
+    	  </exec>
+    </target>
+	
+    <target name="minified" description="Build minified THREE.js">
+    	<exec executable="${python_dir}python">
+    	    <arg value="${build_py}"/>
+    	    <arg value="--all"/>
+    	    <arg value="--minified"/>
+    	  </exec>
+    </target>
+</project>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build2.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build2.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build2.sh	(revision 28897)
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+python build.py --include common2 --include extras --output ../../build/three.js
+python build.py --include common2 --include extras --minify --output ../../build/three.min.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_all.bat
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_all.bat	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_all.bat	(revision 28897)
@@ -0,0 +1,8 @@
+python build.py --include common --include extras --output ../../build/three.js
+python build.py --include common --include extras --minify --output ../../build/three.min.js
+python build.py --include canvas --minify --output ../../build/three-canvas.min.js
+python build.py --include css3d --minify --output ../../build/three-css3d.min.js
+python build.py --include webgl --minify --output ../../build/three-webgl.min.js
+python build.py --include extras --externs externs/extras.js --minify --output ../../build/three-extras.min.js
+python build.py --include math --output ../../build/three-math.js
+python build.py --include math --minify --output ../../build/three-math.min.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_all.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_all.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_all.sh	(revision 28897)
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+python build.py --include common --include extras --output ../../build/three.js
+python build.py --include common --include extras --minify --output ../../build/three.min.js
+python build.py --include canvas --minify --output ../../build/three-canvas.min.js
+python build.py --include css3d --minify --output ../../build/three-css3d.min.js
+python build.py --include webgl --minify --output ../../build/three-webgl.min.js
+python build.py --include extras --externs externs/extras.js --minify --output ../../build/three-extras.min.js
+python build.py --include math --output ../../build/three-math.js
+python build.py --include math --minify --output ../../build/three-math.min.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_debug.bat
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_debug.bat	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_debug.bat	(revision 28897)
@@ -0,0 +1,1 @@
+python build.py --include common --include extras --output ../../build/three.min.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_debug.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_debug.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_debug.sh	(revision 28897)
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+python build.py --include common --include extras --output ../../build/three.min.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_max.bat
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_max.bat	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/build_max.bat	(revision 28897)
@@ -0,0 +1,1 @@
+python build.py --include common --include extras --include examples --externs externs/examples.js --minify --output ../../build/three.max.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/COPYING
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/COPYING	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/COPYING	(revision 28897)
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/README
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/README	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/README	(revision 28897)
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2009 The Closure Compiler Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Contents
+//
+
+The Closure Compiler performs checking, instrumentation, and
+optimizations on JavaScript code. The purpose of this README is to
+explain how to build and run the Closure Compiler.
+
+The Closure Compiler requires Java 6 or higher.
+http://www.java.com/
+
+
+//
+// Building The Closure Compiler
+//
+
+There are three ways to get a Closure Compiler executable.
+
+1) Use one we built for you.
+
+Pre-built Closure binaries can be found at
+http://code.google.com/p/closure-compiler/downloads/list
+
+
+2) Check out the source and build it with Apache Ant.
+
+First, check out the full source tree of the Closure Compiler. There
+are instructions on how to do this at the project site.
+http://code.google.com/p/closure-compiler/source/checkout
+
+Apache Ant is a cross-platform build tool.
+http://ant.apache.org/
+
+At the root of the source tree, there is an Ant file named
+build.xml. To use it, navigate to the same directory and type the
+command
+
+ant jar
+
+This will produce a jar file called "build/compiler.jar".
+
+
+3) Check out the source and build it with Eclipse.
+
+Eclipse is a cross-platform IDE.
+http://www.eclipse.org/
+
+Under Eclipse's File menu, click "New > Project ..." and create a
+"Java Project."  You will see an options screen. Give the project a
+name, select "Create project from existing source," and choose the
+root of the checked-out source tree as the existing directory. Verify
+that you are using JRE version 6 or higher.
+
+Eclipse can use the build.xml file to discover rules. When you
+navigate to the build.xml file, you will see all the build rules in
+the "Outline" pane. Run the "jar" rule to build the compiler in
+build/compiler.jar.
+
+
+//
+// Running The Closure Compiler
+//
+
+Once you have the jar binary, running the Closure Compiler is straightforward.
+
+On the command line, type
+
+java -jar compiler.jar
+
+This starts the compiler in interactive mode. Type
+
+var x = 17 + 25;
+
+then hit "Enter", then hit "Ctrl-Z" (on Windows) or "Ctrl-D" (on Mac or Linux)
+and "Enter" again. The Compiler will respond:
+
+var x=42;
+
+The Closure Compiler has many options for reading input from a file,
+writing output to a file, checking your code, and running
+optimizations. To learn more, type
+
+java -jar compiler.jar --help
+
+You can read more detailed documentation about the many flags at
+http://code.google.com/closure/compiler/docs/gettingstarted_app.html
+
+
+//
+// Compiling Multiple Scripts
+//
+
+If you have multiple scripts, you should compile them all together with
+one compile command.
+
+java -jar compiler.jar --js=in1.js --js=in2.js ... --js_output_file=out.js
+
+The Closure Compiler will concatenate the files in the order they're
+passed at the command line.
+
+If you need to compile many, many scripts together, you may start to
+run into problems with managing dependencies between scripts. You
+should check out the Closure Library. It contains functions for
+enforcing dependencies between scripts, and a tool called calcdeps.py
+that knows how to give scripts to the Closure Compiler in the right
+order.
+
+http://code.google.com/p/closure-library/
+
+//
+// Licensing
+//
+
+Unless otherwise stated, all source files are licensed under
+the Apache License, Version 2.0.
+
+
+-----
+Code under:
+src/com/google/javascript/rhino
+test/com/google/javascript/rhino
+
+URL: http://www.mozilla.org/rhino
+Version:  1.5R3, with heavy modifications
+License:  Netscape Public License and MPL / GPL dual license
+
+Description: A partial copy of Mozilla Rhino. Mozilla Rhino is an
+implementation of JavaScript for the JVM.  The JavaScript parser and
+the parse tree data structures were extracted and modified
+significantly for use by Google's JavaScript compiler.
+
+Local Modifications: The packages have been renamespaced. All code not
+relevant to parsing has been removed. A JsDoc parser and static typing
+system have been added.
+
+
+-----
+Code in:
+lib/rhino
+
+Rhino
+URL: http://www.mozilla.org/rhino
+Version:  Trunk
+License:  Netscape Public License and MPL / GPL dual license
+
+Description: Mozilla Rhino is an implementation of JavaScript for the JVM.
+
+Local Modifications: Minor changes to parsing JSDoc that usually get pushed
+up-stream to Rhino trunk.
+
+
+-----
+Code in:
+lib/args4j.jar
+
+Args4j
+URL: https://args4j.dev.java.net/
+Version: 2.0.16
+License: MIT
+
+Description:
+args4j is a small Java class library that makes it easy to parse command line
+options/arguments in your CUI application.
+
+Local Modifications: None.
+
+
+-----
+Code in:
+lib/guava.jar
+
+Guava Libraries
+URL: http://code.google.com/p/guava-libraries/
+Version:  13.0.1
+License: Apache License 2.0
+
+Description: Google's core Java libraries.
+
+Local Modifications: None.
+
+
+-----
+Code in:
+lib/jsr305.jar
+
+Annotations for software defect detection
+URL: http://code.google.com/p/jsr-305/
+Version: svn revision 47
+License: BSD License
+
+Description: Annotations for software defect detection.
+
+Local Modifications: None.
+
+
+-----
+Code in:
+lib/jarjar.jar
+
+Jar Jar Links
+URL: http://jarjar.googlecode.com/
+Version: 1.1
+License: Apache License 2.0
+
+Description:
+A utility for repackaging Java libraries.
+
+Local Modifications: None.
+
+
+----
+Code in:
+lib/junit.jar
+
+JUnit
+URL:  http://sourceforge.net/projects/junit/
+Version:  4.10
+License:  Common Public License 1.0
+
+Description: A framework for writing and running automated tests in Java.
+
+Local Modifications: None.
+
+
+---
+Code in:
+lib/protobuf-java.jar
+
+Protocol Buffers
+URL: http://code.google.com/p/protobuf/
+Version: 2.4.1
+License: New BSD License
+
+Description: Supporting libraries for protocol buffers,
+an encoding of structured data.
+
+Local Modifications: None
+
+
+---
+Code in:
+lib/ant.jar
+lib/ant-launcher.jar
+
+URL: http://ant.apache.org/bindownload.cgi
+Version: 1.8.1
+License: Apache License 2.0
+Description:
+  Ant is a Java based build tool. In theory it is kind of like "make"
+  without make's wrinkles and with the full portability of pure java code.
+
+Local Modifications: None
+
+
+---
+Code in:
+lib/json.jar
+URL: http://json.org/java/index.html
+Version: JSON version 20090211
+License: MIT license
+Description:
+JSON is a set of java files for use in transmitting data in JSON format.
+
+Local Modifications: None
+
+---
+Code in:
+tools/maven-ant-tasks-2.1.3.jar
+URL: http://maven.apache.org
+Version 2.1.3
+License: Apache License 2.0
+Description:
+  Maven Ant tasks are used to manage dependencies and to install/deploy to
+  maven repositories.
+
+Local Modifications: None
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/compiler/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/common.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/common.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/common.js	(revision 28897)
@@ -0,0 +1,2 @@
+var console;
+var JSON;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/examples.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/examples.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/examples.js	(revision 28897)
@@ -0,0 +1,2 @@
+var webkitAudioContext;
+var TWEEN;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/extras.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/extras.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/externs/extras.js	(revision 28897)
@@ -0,0 +1,1 @@
+var THREE;
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/canvas.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/canvas.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/canvas.json	(revision 28897)
@@ -0,0 +1,66 @@
+[
+	"src/Three.js",
+	"src/math/Color.js",
+	"src/math/Quaternion.js",
+	"src/math/Vector2.js",
+	"src/math/Vector3.js",
+	"src/math/Vector4.js",
+	"src/math/Euler.js",
+	"src/math/Matrix3.js",
+	"src/math/Matrix4.js",
+	"src/math/Ray.js",
+	"src/math/Box2.js",
+	"src/math/Box3.js",
+	"src/math/Sphere.js",
+	"src/math/Plane.js",
+	"src/math/Frustum.js",
+	"src/math/Math.js",
+	"src/math/Triangle.js",
+	"src/math/Vertex.js",
+	"src/math/UV.js",
+	"src/core/EventDispatcher.js",
+	"src/core/Raycaster.js",
+	"src/core/Object3D.js",
+	"src/core/Projector.js",
+	"src/core/Face3.js",
+	"src/core/Face4.js",
+	"src/core/Geometry.js",
+	"src/cameras/Camera.js",
+	"src/cameras/OrthographicCamera.js",
+	"src/cameras/PerspectiveCamera.js",
+	"src/lights/Light.js",
+	"src/lights/AmbientLight.js",
+	"src/lights/DirectionalLight.js",
+	"src/lights/PointLight.js",
+	"src/loaders/Loader.js",
+	"src/loaders/ImageLoader.js",
+	"src/loaders/JSONLoader.js",
+	"src/loaders/GeometryLoader.js",
+	"src/loaders/MaterialLoader.js",
+	"src/loaders/SceneLoader.js",
+	"src/loaders/TextureLoader.js",
+	"src/materials/Material.js",
+	"src/materials/LineBasicMaterial.js",
+	"src/materials/MeshBasicMaterial.js",
+	"src/materials/MeshLambertMaterial.js",
+	"src/materials/MeshPhongMaterial.js",
+	"src/materials/MeshDepthMaterial.js",
+	"src/materials/MeshNormalMaterial.js",
+	"src/materials/MeshFaceMaterial.js",
+	"src/materials/ParticleSystemMaterial.js",
+	"src/materials/SpriteMaterial.js",
+	"src/materials/SpriteCanvasMaterial.js",
+	"src/textures/Texture.js",
+	"src/textures/DataTexture.js",
+	"src/objects/Line.js",
+	"src/objects/Mesh.js",
+	"src/objects/Bone.js",
+	"src/objects/Sprite.js",
+	"src/scenes/Scene.js",
+	"src/renderers/CanvasRenderer.js",
+	"src/renderers/renderables/RenderableVertex.js",
+	"src/renderers/renderables/RenderableFace3.js",
+	"src/renderers/renderables/RenderableObject.js",
+	"src/renderers/renderables/RenderableSprite.js",
+	"src/renderers/renderables/RenderableLine.js"
+]
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/common.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/common.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/common.json	(revision 28897)
@@ -0,0 +1,93 @@
+[
+	"src/Three.js",
+	"src/math/Color.js",
+	"src/math/Quaternion.js",
+	"src/math/Vector2.js",
+	"src/math/Vector3.js",
+	"src/math/Vector4.js",
+	"src/math/Euler.js",
+	"src/math/Line3.js",
+	"src/math/Box2.js",
+	"src/math/Box3.js",
+	"src/math/Matrix3.js",
+	"src/math/Matrix4.js",
+	"src/math/Ray.js",
+	"src/math/Sphere.js",
+	"src/math/Frustum.js",
+	"src/math/Plane.js",
+	"src/math/Math.js",
+	"src/math/Spline.js",
+	"src/math/Triangle.js",
+	"src/math/Vertex.js",
+	"src/math/UV.js",
+	"src/core/Clock.js",
+	"src/core/EventDispatcher.js",
+	"src/core/Raycaster.js",
+	"src/core/Object3D.js",
+	"src/core/Projector.js",
+	"src/core/Face3.js",
+	"src/core/Face4.js",
+	"src/core/Geometry.js",
+	"src/core/BufferGeometry.js",
+	"src/cameras/Camera.js",
+	"src/cameras/OrthographicCamera.js",
+	"src/cameras/PerspectiveCamera.js",
+	"src/lights/Light.js",
+	"src/lights/AmbientLight.js",
+	"src/lights/AreaLight.js",
+	"src/lights/DirectionalLight.js",
+	"src/lights/HemisphereLight.js",
+	"src/lights/PointLight.js",
+	"src/lights/SpotLight.js",
+	"src/loaders/Loader.js",
+	"src/loaders/XHRLoader.js",
+	"src/loaders/ImageLoader.js",
+	"src/loaders/JSONLoader.js",
+	"src/loaders/LoadingManager.js",
+	"src/loaders/BufferGeometryLoader.js",
+	"src/loaders/GeometryLoader.js",
+	"src/loaders/MaterialLoader.js",
+	"src/loaders/ObjectLoader.js",
+	"src/loaders/SceneLoader.js",
+	"src/loaders/TextureLoader.js",
+	"src/materials/Material.js",
+	"src/materials/LineBasicMaterial.js",
+	"src/materials/LineDashedMaterial.js",
+	"src/materials/MeshBasicMaterial.js",
+	"src/materials/MeshLambertMaterial.js",
+	"src/materials/MeshPhongMaterial.js",
+	"src/materials/MeshDepthMaterial.js",
+	"src/materials/MeshNormalMaterial.js",
+	"src/materials/MeshFaceMaterial.js",
+	"src/materials/ParticleSystemMaterial.js",
+	"src/materials/ShaderMaterial.js",
+	"src/materials/SpriteMaterial.js",
+	"src/materials/SpriteCanvasMaterial.js",
+	"src/textures/Texture.js",
+	"src/textures/CompressedTexture.js",
+	"src/textures/DataTexture.js",
+	"src/objects/ParticleSystem.js",
+	"src/objects/Line.js",
+	"src/objects/Mesh.js",
+	"src/objects/Bone.js",
+	"src/objects/SkinnedMesh.js",
+	"src/objects/MorphAnimMesh.js",
+	"src/objects/LOD.js",
+	"src/objects/Sprite.js",
+	"src/scenes/Scene.js",
+	"src/scenes/Fog.js",
+	"src/scenes/FogExp2.js",
+	"src/renderers/CanvasRenderer.js",
+	"src/renderers/shaders/ShaderChunk.js",
+	"src/renderers/shaders/UniformsUtils.js",
+	"src/renderers/shaders/UniformsLib.js",
+	"src/renderers/shaders/ShaderLib.js",
+	"src/renderers/WebGLRenderer.js",
+	"src/renderers/WebGLRenderTarget.js",
+	"src/renderers/WebGLRenderTargetCube.js",
+	"src/renderers/renderables/RenderableVertex.js",
+	"src/renderers/renderables/RenderableFace3.js",
+	"src/renderers/renderables/RenderableObject.js",
+	"src/renderers/renderables/RenderableSprite.js",
+	"src/renderers/renderables/RenderableLine.js"
+]
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/css3d.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/css3d.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/css3d.json	(revision 28897)
@@ -0,0 +1,24 @@
+[
+	"src/Three.js",
+	"src/math/Color.js",
+	"src/math/Quaternion.js",
+	"src/math/Vector2.js",
+	"src/math/Vector3.js",
+	"src/math/Vector4.js",
+	"src/math/Euler.js",
+	"src/math/Math.js",
+	"src/math/Matrix3.js",
+	"src/math/Matrix4.js",
+	"src/math/Box3.js",
+	"src/math/Frustum.js",
+	"src/math/Sphere.js",
+	"src/math/Plane.js",
+	"src/core/EventDispatcher.js",
+	"src/core/Object3D.js",
+	"src/cameras/Camera.js",
+	"src/cameras/PerspectiveCamera.js",
+	"src/lights/Light.js",
+	"src/objects/Bone.js",
+	"src/scenes/Scene.js",
+	"examples/js/renderers/CSS3DRenderer.js"
+]
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/extras.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/extras.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/extras.json	(revision 28897)
@@ -0,0 +1,68 @@
+[
+	"src/extras/GeometryUtils.js",
+	"src/extras/ImageUtils.js",
+	"src/extras/SceneUtils.js",
+	"src/extras/FontUtils.js",
+	"src/extras/core/Curve.js",
+	"src/extras/core/CurvePath.js",
+	"src/extras/core/Gyroscope.js",
+	"src/extras/core/Path.js",
+	"src/extras/core/Shape.js",
+	"src/extras/curves/LineCurve.js",
+	"src/extras/curves/QuadraticBezierCurve.js",
+	"src/extras/curves/CubicBezierCurve.js",
+	"src/extras/curves/SplineCurve.js",
+	"src/extras/curves/EllipseCurve.js",
+	"src/extras/curves/ArcCurve.js",
+	"src/extras/curves/LineCurve3.js",
+	"src/extras/curves/QuadraticBezierCurve3.js",
+	"src/extras/curves/CubicBezierCurve3.js",
+	"src/extras/curves/SplineCurve3.js",
+	"src/extras/curves/ClosedSplineCurve3.js",
+	"src/extras/animation/AnimationHandler.js",
+	"src/extras/animation/Animation.js",
+	"src/extras/animation/KeyFrameAnimation.js",
+	"src/extras/cameras/CubeCamera.js",
+	"src/extras/cameras/CombinedCamera.js",
+	"src/extras/geometries/CircleGeometry.js",
+	"src/extras/geometries/CubeGeometry.js",
+	"src/extras/geometries/CylinderGeometry.js",
+	"src/extras/geometries/ExtrudeGeometry.js",
+	"src/extras/geometries/ShapeGeometry.js",
+	"src/extras/geometries/LatheGeometry.js",
+	"src/extras/geometries/PlaneGeometry.js",
+	"src/extras/geometries/RingGeometry.js",
+	"src/extras/geometries/SphereGeometry.js",
+	"src/extras/geometries/TextGeometry.js",
+	"src/extras/geometries/TorusGeometry.js",
+	"src/extras/geometries/TorusKnotGeometry.js",
+	"src/extras/geometries/TubeGeometry.js",
+	"src/extras/geometries/PolyhedronGeometry.js",
+	"src/extras/geometries/IcosahedronGeometry.js",
+	"src/extras/geometries/OctahedronGeometry.js",
+	"src/extras/geometries/TetrahedronGeometry.js",
+	"src/extras/geometries/ParametricGeometry.js",
+	"src/extras/helpers/AxisHelper.js",
+	"src/extras/helpers/ArrowHelper.js",
+	"src/extras/helpers/BoxHelper.js",
+	"src/extras/helpers/BoundingBoxHelper.js",
+	"src/extras/helpers/CameraHelper.js",
+	"src/extras/helpers/DirectionalLightHelper.js",
+	"src/extras/helpers/EdgesHelper.js",
+	"src/extras/helpers/FaceNormalsHelper.js",
+	"src/extras/helpers/GridHelper.js",
+	"src/extras/helpers/HemisphereLightHelper.js",
+	"src/extras/helpers/PointLightHelper.js",
+	"src/extras/helpers/SpotLightHelper.js",
+	"src/extras/helpers/VertexNormalsHelper.js",
+	"src/extras/helpers/VertexTangentsHelper.js",
+	"src/extras/helpers/WireframeHelper.js",
+	"src/extras/objects/ImmediateRenderObject.js",
+	"src/extras/objects/LensFlare.js",
+	"src/extras/objects/MorphBlendMesh.js",
+	"src/extras/renderers/plugins/LensFlarePlugin.js",
+	"src/extras/renderers/plugins/ShadowMapPlugin.js",
+	"src/extras/renderers/plugins/SpritePlugin.js",
+	"src/extras/renderers/plugins/DepthPassPlugin.js",
+	"src/extras/shaders/ShaderFlares.js"
+]
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/math.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/math.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/math.json	(revision 28897)
@@ -0,0 +1,21 @@
+[
+	"src/Three.js",
+	"src/math/Color.js",
+	"src/math/Quaternion.js",
+	"src/math/Vector2.js",
+	"src/math/Vector3.js",
+	"src/math/Vector4.js",
+	"src/math/Euler.js",
+	"src/math/Line3.js",
+	"src/math/Box2.js",
+	"src/math/Box3.js",
+	"src/math/Matrix3.js",
+	"src/math/Matrix4.js",
+	"src/math/Ray.js",
+	"src/math/Frustum.js",
+	"src/math/Plane.js",
+	"src/math/Sphere.js",
+	"src/math/Math.js",
+	"src/math/Spline.js",
+	"src/math/Triangle.js"
+]
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/webgl.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/webgl.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/includes/webgl.json	(revision 28897)
@@ -0,0 +1,96 @@
+[
+	"src/Three.js",
+	"src/math/Color.js",
+	"src/math/Quaternion.js",
+	"src/math/Vector2.js",
+	"src/math/Vector3.js",
+	"src/math/Vector4.js",
+	"src/math/Euler.js",
+	"src/math/Matrix3.js",
+	"src/math/Matrix4.js",
+	"src/math/Frustum.js",
+	"src/math/Math.js",
+	"src/math/Vertex.js",
+	"src/math/UV.js",
+	"src/math/Spline.js",
+	"src/math/Box2.js",
+	"src/math/Box3.js",
+	"src/math/Triangle.js",
+	"src/math/Sphere.js",
+	"src/math/Plane.js",
+	"src/core/Clock.js",
+	"src/core/EventDispatcher.js",
+	"src/core/Raycaster.js",
+	"src/core/Object3D.js",
+	"src/core/Projector.js",
+	"src/core/Face3.js",
+	"src/core/Face4.js",
+	"src/core/Geometry.js",
+	"src/core/BufferGeometry.js",
+	"src/cameras/Camera.js",
+	"src/cameras/OrthographicCamera.js",
+	"src/cameras/PerspectiveCamera.js",
+	"src/lights/Light.js",
+	"src/lights/AmbientLight.js",
+	"src/lights/DirectionalLight.js",
+	"src/lights/HemisphereLight.js",
+	"src/lights/PointLight.js",
+	"src/lights/SpotLight.js",
+	"src/loaders/Loader.js",
+	"src/loaders/ImageLoader.js",
+	"src/loaders/JSONLoader.js",
+	"src/loaders/GeometryLoader.js",
+	"src/loaders/MaterialLoader.js",
+	"src/loaders/SceneLoader.js",
+	"src/loaders/TextureLoader.js",
+	"src/materials/Material.js",
+	"src/materials/LineBasicMaterial.js",
+	"src/materials/LineDashedMaterial.js",
+	"src/materials/MeshBasicMaterial.js",
+	"src/materials/MeshLambertMaterial.js",
+	"src/materials/MeshPhongMaterial.js",
+	"src/materials/MeshDepthMaterial.js",
+	"src/materials/MeshNormalMaterial.js",
+	"src/materials/MeshFaceMaterial.js",
+	"src/materials/ParticleSystemMaterial.js",
+	"src/materials/ShaderMaterial.js",
+	"src/materials/SpriteMaterial.js",
+	"src/textures/Texture.js",
+	"src/textures/CompressedTexture.js",
+	"src/textures/DataTexture.js",
+	"src/objects/ParticleSystem.js",
+	"src/objects/Line.js",
+	"src/objects/Mesh.js",
+	"src/objects/Bone.js",
+	"src/objects/SkinnedMesh.js",
+	"src/objects/MorphAnimMesh.js",
+	"src/objects/LOD.js",
+	"src/objects/Sprite.js",
+	"src/scenes/Scene.js",
+	"src/scenes/Fog.js",
+	"src/scenes/FogExp2.js",
+	"src/renderers/shaders/ShaderChunk.js",
+	"src/renderers/shaders/UniformsUtils.js",
+	"src/renderers/shaders/UniformsLib.js",
+	"src/renderers/shaders/ShaderLib.js",
+	"src/renderers/WebGLRenderer.js",
+	"src/renderers/WebGLRenderTarget.js",
+	"src/renderers/WebGLRenderTargetCube.js",
+	"src/renderers/renderables/RenderableVertex.js",
+	"src/renderers/renderables/RenderableFace3.js",
+	"src/renderers/renderables/RenderableObject.js",
+	"src/renderers/renderables/RenderableSprite.js",
+	"src/renderers/renderables/RenderableLine.js",
+	"src/extras/core/Gyroscope.js",
+	"src/extras/helpers/CameraHelper.js",
+	"src/extras/helpers/DirectionalLightHelper.js",
+	"src/extras/helpers/HemisphereLightHelper.js",
+	"src/extras/helpers/PointLightHelper.js",
+	"src/extras/helpers/SpotLightHelper.js",
+	"src/extras/objects/LensFlare.js",
+	"src/extras/objects/ImmediateRenderObject.js",
+	"src/extras/renderers/plugins/LensFlarePlugin.js",
+	"src/extras/renderers/plugins/ShadowMapPlugin.js",
+	"src/extras/renderers/plugins/SpritePlugin.js",
+	"src/extras/shaders/ShaderFlares.js"
+]
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/package.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/package.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/build/package.json	(revision 28897)
@@ -0,0 +1,24 @@
+{
+    "name": "three.js",
+    "description": "JavaScript 3D library",
+    "version": "50dev",
+    "homepage" : "http://threejs.org/",
+    "author": "three.js contributors",
+    "help": {
+        "web": "http://stackoverflow.com/questions/tagged/three.js"
+    },
+
+    "devDependencies": {
+    	"uglify-js2": "*",
+    	"argparse" : "*"
+    },
+    
+    "repository" : {
+        "type" : "git",
+         "url" : "git://github.com/mrdoob/three.js.git"
+    },
+    "licenses": [{
+        "type": "The MIT License",
+        "url": "https://raw.github.com/mrdoob/three.js/master/LICENSE"
+    }]
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/LICENSE.txt
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/LICENSE.txt	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/LICENSE.txt	(revision 28897)
@@ -0,0 +1,20 @@
+Copyright (c) 2009-2010 Marcus Geelnard
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any damages
+arising from the use of this software.
+
+Permission is granted to anyone to use this software for any purpose,
+including commercial applications, and to alter it and redistribute it
+freely, subject to the following restrictions:
+
+    1. The origin of this software must not be misrepresented; you must not
+    claim that you wrote the original software. If you use this software
+    in a product, an acknowledgment in the product documentation would be
+    appreciated but is not required.
+
+    2. Altered source versions must be plainly marked as such, and must not
+    be misrepresented as being the original software.
+
+    3. This notice may not be removed or altered from any source
+    distribution.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/join_ctm.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/join_ctm.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/ctm/join_ctm.py	(revision 28897)
@@ -0,0 +1,114 @@
+"""Join multiple binary files into single file and generate JSON snippet with offsets
+
+-------------------------------------
+How to use
+-------------------------------------
+
+python join_ctm.py -i "part_*.ctm" -o joined.ctm [-j offsets.js]
+
+Will read multiple files following wildcard pattern (ordered lexicographically):
+
+part_000.ctm
+part_001.ctm
+part_002.ctm
+
+...
+
+part_XXX.ctm
+
+And generate single concatenated files:
+
+joined.ctm
+offsets.js (optional, offsets are also dumped to standard output)
+
+"""
+
+import getopt
+import glob
+import sys
+import os
+
+# #####################################################
+# Templates
+# #####################################################
+TEMPLATE_JSON = u"""\
+"offsets": [ %(offsets)s ],
+"""
+
+# #############################################################################
+# Helpers
+# #############################################################################
+def usage():
+    print 'Usage: %s -i "filename_*.ctm" -o filename.ctm [-j offsets.js]' % os.path.basename(sys.argv[0])
+
+# #####################################################
+# Main
+# #####################################################
+if __name__ == "__main__":
+
+    # get parameters from the command line
+
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "hi:o:j:", ["help", "input=", "output=", "json="])
+
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+
+
+    inpattern = ""
+    outname = ""
+    jsonname = ""
+
+    for o, a in opts:
+        if o in ("-h", "--help"):
+            usage()
+            sys.exit()
+
+        elif o in ("-i", "--input"):
+            inpattern = a
+
+        elif o in ("-o", "--output"):
+            outname = a
+
+        elif o in ("-j", "--json"):
+            jsonname = a
+
+    # quit if required parameters are missing
+
+    if inpattern == "" or outname == "":
+        usage()
+        sys.exit(2)
+
+    outfile = open(outname, "wb")
+
+    matches = glob.glob(inpattern)
+    matches.sort()
+
+    total = 0
+    offsets = []
+
+    for filename in matches:
+        filesize = os.path.getsize(filename)
+        offsets.append(total)
+        total += filesize
+
+        print filename, filesize
+
+        infile = open(filename, "rb")
+        buffer = infile.read()
+        outfile.write(buffer)
+        infile.close()
+
+    outfile.close()
+
+    json_str = TEMPLATE_JSON % {
+    "offsets" : ", ".join(["%d" % o for o in offsets])
+    }
+
+    print json_str
+
+    if jsonname:
+        jsonfile = open(jsonname, "w")
+        jsonfile.write(json_str)
+        jsonfile.close()
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/LICENSE
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/LICENSE	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/LICENSE	(revision 28897)
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2012 convert-to-three.py authors.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/README.md
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/README.md	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/README.md	(revision 28897)
@@ -0,0 +1,69 @@
+## convert-to-threejs
+
+Utility for converting model files to the Three.js JSON format
+
+## Supported Formats
+
+* Fbx (.fbx) (versions 7.3, 7.2, 7.1, 7.0, 6.1, and 6.0) (non-binary)
+* Collada (.dae) (1.5 and earlier) 
+* Wavefront/Alias (.obj)
+* 3D Studio Max (.3ds)
+
+## Usage 
+
+```
+convert_to_threejs.py [source_file] [output_file] [options]
+
+Options:
+  -t, --triangulate     force quad geometry into triangles
+  -p, --prefix          prefix object names in the output file
+  -g, --geometry-only   output geometry only
+  -c, --default-camera  include a default camera in the output scene
+  -l, --defualt-light   include a default light in the output scene
+  -x, --no-textures     don't include texture references in the output file
+```
+
+## Supported Features
+
+* Object Hierarchies
+* Lights (Ambient, Point, Directional)
+* Cameras (Perspective, Ortho)
+* Geometries (Triangles, Quads, Nurbs)
+* Materials (Phong, Lambert)
+* Textures (Diffuse, Emissive, Ambient, Specular, Normal, Bump)
+* Multiple UV layers
+* Multiple materials per mesh
+
+## Current Limitations
+
+* No animation support
+* Only Lambert and Phong materials are supported
+* Some camera properties are not converted correctly
+* Some light properties are not converted correctly
+* Some material properties are not converted correctly
+
+## Dependencies
+
+### FBX SDK
+* Requires Autodesk FBX SDK Python 2013.3 bindings. 
+
+```
+You can download the python bindings from the Autodesk website: 
+  http://usa.autodesk.com/fbx/
+```
+
+```
+Don't forget the visit the FBX SDK documentation website:
+  http://docs.autodesk.com/FBX/2013/ENU/FBX-SDK-Documentation/cpp_ref/index.html
+```
+
+### Python
+* Requires Python 2.6 or 3.1 (The FBX SDK requires one of these versions)
+
+``` bash
+sudo apt-get install build-essential
+wget http://www.python.org/ftp/python/2.6.8/Python-2.6.8.tar.bz2
+tar jxf ./Python-2.6.8.tar.bz2
+cd ./Python-2.6.8
+./configure --prefix=/opt/python2.6.8 && make && make install
+```
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/convert_to_threejs.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/convert_to_threejs.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/convert_to_threejs.py	(revision 28897)
@@ -0,0 +1,1883 @@
+# @author zfedoran / http://github.com/zfedoran
+
+import os
+import sys
+import math
+import operator
+
+# #####################################################
+# Globals
+# #####################################################
+option_triangulate = True
+option_textures = True
+option_prefix = True
+option_geometry = False
+option_default_camera = False
+option_default_light = False
+
+converter = None
+global_up_vector = None
+
+# #####################################################
+# Templates
+# #####################################################
+def Vector2String(v, no_brackets = False):
+    if no_brackets:
+        return '%g,%g' % (v[0], v[1])
+    else:
+        return '[ %g, %g ]' % (v[0], v[1])
+
+def Vector3String(v, no_brackets = False):
+    if no_brackets:
+        return '%g,%g,%g' % (v[0], v[1], v[2])
+    else:
+        return '[ %g, %g, %g ]' % (v[0], v[1], v[2])
+
+def ColorString(c, no_brackets = False):
+    if no_brackets:
+        return '%g, %g, %g' % (c[0], c[1], c[2])
+    else:
+        return '[ %g, %g, %g ]' % (c[0], c[1], c[2])
+
+def LabelString(s):
+    return '"%s"' % s
+
+def ArrayString(s):
+    return '[ %s ]' % s
+
+def PaddingString(n):
+    output = ""
+    for i in range(n):
+        output += "\t"
+    return output
+        
+def BoolString(value):
+    if value:
+        return "true"
+    return "false"
+
+# #####################################################
+# Helpers
+# #####################################################
+def getObjectName(o, force_prefix = False): 
+    if not o:
+        return ""  
+    prefix = ""
+    if option_prefix or force_prefix:
+        prefix = "Object_%s_" % o.GetUniqueID()
+    return prefix + o.GetName()
+      
+def getGeometryName(g, force_prefix = False):
+    prefix = ""
+    if option_prefix or force_prefix:
+        prefix = "Geometry_%s_" % g.GetUniqueID()
+    return prefix + g.GetName()
+
+def getEmbedName(e, force_prefix = False):
+    prefix = ""
+    if option_prefix or force_prefix:
+        prefix = "Embed_%s_" % e.GetUniqueID()
+    return prefix + e.GetName()
+
+def getMaterialName(m, force_prefix = False):
+    prefix = ""
+    if option_prefix or force_prefix:
+        prefix = "Material_%s_" % m.GetUniqueID()
+    return prefix + m.GetName()
+
+def getTextureName(t, force_prefix = False):
+    texture_file = t.GetFileName()
+    texture_id = os.path.splitext(os.path.basename(texture_file))[0]
+    prefix = ""
+    if option_prefix or force_prefix:
+        prefix = "Texture_%s_" % t.GetUniqueID()
+    return prefix + texture_id
+
+def getFogName(f, force_prefix = False):
+    prefix = ""
+    if option_prefix or force_prefix:
+        prefix = "Fog_%s_" % f.GetUniqueID()
+    return prefix + f.GetName()
+
+def getObjectVisible(n):
+    return BoolString(True)
+    
+def getRadians(v):
+    return ((v[0]*math.pi)/180, (v[1]*math.pi)/180, (v[2]*math.pi)/180)
+
+def getHex(c):
+    color = (int(c[0]*255) << 16) + (int(c[1]*255) << 8) + int(c[2]*255)
+    return color
+
+def setBit(value, position, on):
+    if on:
+        mask = 1 << position
+        return (value | mask)
+    else:
+        mask = ~(1 << position)
+        return (value & mask)
+
+def convert_fbx_color(color):
+    return [color.mRed, color.mGreen, color.mBlue, color.mAlpha]
+    
+def convert_fbx_vec2(v):
+    return [v[0], v[1]]
+
+def convert_fbx_vec3(v):
+    return [v[0], v[1], v[2]]
+    
+def generate_uvs(uv_layers):
+    layers = []
+    for uvs in uv_layers:
+        layer = ",".join(Vector2String(n, True) for n in uvs)
+        layers.append(layer)
+
+    return ",".join("[%s]" % n for n in layers)
+
+def generateMultiLineString(lines, separator, padding):
+    cleanLines = []
+    for i in range(len(lines)):
+        line = lines[i]
+        line = PaddingString(padding) + line
+        cleanLines.append(line)
+    return separator.join(cleanLines)
+
+def get_up_vector(scene):
+    global_settings = scene.GetGlobalSettings()
+    axis_system = global_settings.GetAxisSystem()
+    up_vector = axis_system.GetUpVector()
+    tmp = [0,0,0]
+    tmp[up_vector[0] - 1] = up_vector[1] * 1
+    return FbxVector4(tmp[0], tmp[1], tmp[2], 1)
+
+def generate_bounding_box(vertices):
+    minx = 0
+    miny = 0
+    minz = 0
+    maxx = 0
+    maxy = 0
+    maxz = 0
+
+    for vertex in vertices:
+        if vertex[0] < minx:
+            minx = vertex[0]
+        if vertex[1] < miny:
+            miny = vertex[1]
+        if vertex[2] < minz:
+            minz = vertex[2]
+
+        if vertex[0] > maxx:
+            maxx = vertex[0]
+        if vertex[1] > maxy:
+            maxy = vertex[1]
+        if vertex[2] > maxz:
+            maxz = vertex[2]
+
+    return [minx, miny, minz], [maxx, maxy, maxz]
+
+    
+# #####################################################
+# Generate - Triangles 
+# #####################################################
+def triangulate_node_hierarchy(node):
+    node_attribute = node.GetNodeAttribute();
+
+    if node_attribute:
+        if node_attribute.GetAttributeType() == FbxNodeAttribute.eMesh or \
+           node_attribute.GetAttributeType() == FbxNodeAttribute.eNurbs or \
+           node_attribute.GetAttributeType() == FbxNodeAttribute.eNurbsSurface or \
+           node_attribute.GetAttributeType() == FbxNodeAttribute.ePatch:
+            converter.TriangulateInPlace(node);
+        
+        child_count = node.GetChildCount()
+        for i in range(child_count):
+            triangulate_node_hierarchy(node.GetChild(i))
+
+def triangulate_scene(scene):
+    node = scene.GetRootNode()
+    if node:
+        for i in range(node.GetChildCount()):
+            triangulate_node_hierarchy(node.GetChild(i))
+
+# #####################################################
+# Generate - Material String 
+# #####################################################
+def generate_texture_bindings(material_property, texture_list):
+    binding_types = {
+    "DiffuseColor": "map", "DiffuseFactor": "diffuseFactor", "EmissiveColor": "emissiveMap", 
+    "EmissiveFactor": "emissiveFactor", "AmbientColor": "ambientMap", "AmbientFactor": "ambientFactor", 
+    "SpecularColor": "specularMap", "SpecularFactor": "specularFactor", "ShininessExponent": "shininessExponent",
+    "NormalMap": "normalMap", "Bump": "bumpMap", "TransparentColor": "transparentMap", 
+    "TransparencyFactor": "transparentFactor", "ReflectionColor": "reflectionMap", 
+    "ReflectionFactor": "reflectionFactor", "DisplacementColor": "displacementMap", 
+    "VectorDisplacementColor": "vectorDisplacementMap"
+    }
+
+    if material_property.IsValid():
+        #Here we have to check if it's layeredtextures, or just textures:
+        layered_texture_count = material_property.GetSrcObjectCount(FbxLayeredTexture.ClassId)
+        if layered_texture_count > 0:
+            for j in range(layered_texture_count):
+                layered_texture = material_property.GetSrcObject(FbxLayeredTexture.ClassId, j)
+                texture_count = layered_texture.GetSrcObjectCount(FbxTexture.ClassId)
+                for k in range(texture_count):
+                    texture = layered_texture.GetSrcObject(FbxTexture.ClassId,k)
+                    if texture:
+                        texture_id = getTextureName(texture, True) 
+                        texture_binding = '		"%s": "%s",' % (binding_types[str(material_property.GetName())], texture_id)
+                        texture_list.append(texture_binding)
+        else:
+            # no layered texture simply get on the property
+            texture_count = material_property.GetSrcObjectCount(FbxTexture.ClassId)
+            for j in range(texture_count):
+                texture = material_property.GetSrcObject(FbxTexture.ClassId,j)
+                if texture:
+                    texture_id = getTextureName(texture, True) 
+                    texture_binding = '		"%s": "%s",' % (binding_types[str(material_property.GetName())], texture_id)
+                    texture_list.append(texture_binding)
+
+def generate_material_string(material):
+    #Get the implementation to see if it's a hardware shader.
+    implementation = GetImplementation(material, "ImplementationHLSL")
+    implementation_type = "HLSL"
+    if not implementation:
+        implementation = GetImplementation(material, "ImplementationCGFX")
+        implementation_type = "CGFX"
+
+    output = []
+
+    if implementation:
+        # This material is a hardware shader, skip it
+        print("Shader materials are not supported")
+        return ''
+        
+    elif material.GetClassId().Is(FbxSurfaceLambert.ClassId):
+
+        ambient   = str(getHex(material.Ambient.Get()))
+        diffuse   = str(getHex(material.Diffuse.Get()))
+        emissive  = str(getHex(material.Emissive.Get()))
+        opacity   = 1.0 - material.TransparencyFactor.Get()
+        opacity   = 1.0 if opacity == 0 else opacity
+        opacity   = str(opacity)
+        transparent = BoolString(False)
+        reflectivity = "1"
+
+        output = [
+
+        '\t' + LabelString( getMaterialName( material ) ) + ': {',
+        '	"type"    : "MeshLambertMaterial",',
+        '	"parameters"  : {',
+        '		"color"  : ' 	  + diffuse + ',',
+        '		"ambient"  : ' 	+ ambient + ',',
+        '		"emissive"  : ' + emissive + ',',
+        '		"reflectivity"  : ' + reflectivity + ',',
+        '		"transparent" : '   + transparent + ',',
+        '		"opacity" : ' 	    + opacity + ',',
+
+        ]
+
+    elif material.GetClassId().Is(FbxSurfacePhong.ClassId):
+
+        ambient   = str(getHex(material.Ambient.Get()))
+        diffuse   = str(getHex(material.Diffuse.Get()))
+        emissive  = str(getHex(material.Emissive.Get()))
+        specular  = str(getHex(material.Specular.Get()))
+        opacity   = 1.0 - material.TransparencyFactor.Get()
+        opacity   = 1.0 if opacity == 0 else opacity
+        opacity   = str(opacity)
+        shininess = str(material.Shininess.Get())
+        transparent = BoolString(False)
+        reflectivity = "1"
+        bumpScale = "1"
+
+        output = [
+
+        '\t' + LabelString( getMaterialName( material ) ) + ': {',
+        '	"type"    : "MeshPhongMaterial",',
+        '	"parameters"  : {',
+        '		"color"  : ' 	  + diffuse + ',',
+        '		"ambient"  : ' 	+ ambient + ',',
+        '		"emissive"  : ' + emissive + ',',
+        '		"specular"  : ' + specular + ',',
+        '		"shininess" : ' + shininess + ',',
+        '		"bumpScale"  : '    + bumpScale + ',',
+        '		"reflectivity"  : ' + reflectivity + ',',
+        '		"transparent" : '   + transparent + ',',
+        '		"opacity" : ' 	+ opacity + ',',
+
+        ]
+
+    else:
+      print("Unknown type of Material")
+      return ''
+
+    if option_textures:
+        texture_list = []
+        texture_count = FbxLayerElement.sTypeTextureCount()
+        for texture_index in range(texture_count):
+            material_property = material.FindProperty(FbxLayerElement.sTextureChannelNames(texture_index))
+            generate_texture_bindings(material_property, texture_list)
+
+        output += texture_list
+
+    wireframe = BoolString(False)
+    wireframeLinewidth = "1"
+
+    output.append('		"wireframe" : ' + wireframe + ',')
+    output.append('		"wireframeLinewidth" : ' + wireframeLinewidth)
+    output.append('	}')
+    output.append('}')
+
+    return generateMultiLineString( output, '\n\t\t', 0 )
+
+def generate_proxy_material_string(node, material_names):
+    
+    output = [
+
+    '\t' + LabelString( getMaterialName( node, True ) ) + ': {',
+    '	"type"    : "MeshFaceMaterial",',
+    '	"parameters"  : {',
+    '		"materials"  : ' + ArrayString( ",".join(LabelString(m) for m in material_names) ),
+    '	}',
+    '}'
+
+    ]
+
+    return generateMultiLineString( output, '\n\t\t', 0 )
+
+# #####################################################
+# Parse - Materials 
+# #####################################################
+def extract_materials_from_node(node, material_list):
+    name = node.GetName()
+    mesh = node.GetNodeAttribute()
+
+    node = None
+    if mesh:
+        node = mesh.GetNode()
+        if node:
+            material_count = node.GetMaterialCount()
+    
+    material_names = []
+    for l in range(mesh.GetLayerCount()):
+        materials = mesh.GetLayer(l).GetMaterials()
+        if materials:
+            if materials.GetReferenceMode() == FbxLayerElement.eIndex:
+                #Materials are in an undefined external table
+                continue
+            for i in range(material_count):
+                material = node.GetMaterial(i)
+                material_names.append(getMaterialName(material))
+                material_string = generate_material_string(material)
+                material_list.append(material_string)
+
+    if material_count > 1:
+      proxy_material = generate_proxy_material_string(node, material_names)
+      material_list.append(proxy_material)
+
+
+def generate_materials_from_hierarchy(node, material_list):
+    if node.GetNodeAttribute() == None:
+        pass
+    else:
+        attribute_type = (node.GetNodeAttribute().GetAttributeType())
+        if attribute_type == FbxNodeAttribute.eMesh:
+            extract_materials_from_node(node, material_list)
+    for i in range(node.GetChildCount()):
+        generate_materials_from_hierarchy(node.GetChild(i), material_list)
+
+def generate_material_list(scene):
+    material_list = []
+    node = scene.GetRootNode()
+    if node:
+        for i in range(node.GetChildCount()):
+            generate_materials_from_hierarchy(node.GetChild(i), material_list)
+    return material_list
+
+# #####################################################
+# Generate - Texture String 
+# #####################################################
+def generate_texture_string(texture):
+
+    #TODO: extract more texture properties
+    wrap_u = texture.GetWrapModeU()
+    wrap_v = texture.GetWrapModeV()
+    offset = texture.GetUVTranslation()
+
+    output = [
+
+    '\t' + LabelString( getTextureName( texture, True ) ) + ': {',
+    '	"url"    : "' + texture.GetFileName() + '",',
+    '	"repeat" : ' + Vector2String( (1,1) ) + ',',
+    '	"offset" : ' + Vector2String( texture.GetUVTranslation() ) + ',',
+    '	"magFilter" : ' + LabelString( "LinearFilter" ) + ',',
+    '	"minFilter" : ' + LabelString( "LinearMipMapLinearFilter" ) + ',',
+    '	"anisotropy" : ' + BoolString( True ),
+    '}'
+
+    ]
+
+    return generateMultiLineString( output, '\n\t\t', 0 )
+
+# #####################################################
+# Parse - Textures 
+# #####################################################
+def extract_material_textures(material_property, texture_list):
+    if material_property.IsValid():
+        #Here we have to check if it's layeredtextures, or just textures:
+        layered_texture_count = material_property.GetSrcObjectCount(FbxLayeredTexture.ClassId)
+        if layered_texture_count > 0:
+            for j in range(layered_texture_count):
+                layered_texture = material_property.GetSrcObject(FbxLayeredTexture.ClassId, j)
+                texture_count = layered_texture.GetSrcObjectCount(FbxTexture.ClassId)
+                for k in range(texture_count):
+                    texture = layered_texture.GetSrcObject(FbxTexture.ClassId,k)
+                    if texture:
+                        texture_string = generate_texture_string(texture)
+                        texture_list.append(texture_string)
+        else:
+            # no layered texture simply get on the property
+            texture_count = material_property.GetSrcObjectCount(FbxTexture.ClassId)
+            for j in range(texture_count):
+                texture = material_property.GetSrcObject(FbxTexture.ClassId,j)
+                if texture:
+                    texture_string = generate_texture_string(texture)
+                    texture_list.append(texture_string)
+
+def extract_textures_from_node(node, texture_list):
+    name = node.GetName()
+    mesh = node.GetNodeAttribute()
+    
+    #for all materials attached to this mesh
+    material_count = mesh.GetNode().GetSrcObjectCount(FbxSurfaceMaterial.ClassId)
+    for material_index in range(material_count):
+        material = mesh.GetNode().GetSrcObject(FbxSurfaceMaterial.ClassId, material_index)
+
+        #go through all the possible textures types
+        if material:            
+            texture_count = FbxLayerElement.sTypeTextureCount()
+            for texture_index in range(texture_count):
+                material_property = material.FindProperty(FbxLayerElement.sTextureChannelNames(texture_index))
+                extract_material_textures(material_property, texture_list)
+
+def generate_textures_from_hierarchy(node, texture_list):
+    if node.GetNodeAttribute() == None:
+        pass
+    else:
+        attribute_type = (node.GetNodeAttribute().GetAttributeType())
+        if attribute_type == FbxNodeAttribute.eMesh:
+            extract_textures_from_node(node, texture_list)
+    for i in range(node.GetChildCount()):
+        generate_textures_from_hierarchy(node.GetChild(i), texture_list)
+
+def generate_texture_list(scene):
+    if not option_textures:
+        return []
+
+    texture_list = []
+    node = scene.GetRootNode()
+    if node:
+        for i in range(node.GetChildCount()):
+            generate_textures_from_hierarchy(node.GetChild(i), texture_list)
+    return texture_list
+
+# #####################################################
+# Extract - Fbx Mesh data
+# #####################################################
+def extract_fbx_vertex_positions(mesh):
+    control_points_count = mesh.GetControlPointsCount()
+    control_points = mesh.GetControlPoints()
+
+    positions = []
+    for i in range(control_points_count):
+        positions.append(convert_fbx_vec3(control_points[i]))
+
+    node = mesh.GetNode()
+    if node and option_geometry:
+        # FbxMeshes are local to their node, we need the vertices in global space
+        # when scene nodes are not exported
+        transform = node.EvaluateGlobalTransform()
+        transform = FbxMatrix(transform)
+
+        for i in range(len(positions)):
+            v = positions[i]
+            position = FbxVector4(v[0], v[1], v[2])
+            position = transform.MultNormalize(position)
+            positions[i] = convert_fbx_vec3(position)
+
+    return positions
+
+def extract_fbx_vertex_normals(mesh):
+#   eNone             The mapping is undetermined.
+#   eByControlPoint   There will be one mapping coordinate for each surface control point/vertex.
+#   eByPolygonVertex  There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part.
+#   eByPolygon        There can be only one mapping coordinate for the whole polygon.
+#   eByEdge           There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
+#   eAllSame          There can be only one mapping coordinate for the whole surface.
+
+    layered_normal_indices = []
+    layered_normal_values = []
+
+    poly_count = mesh.GetPolygonCount()
+    control_points = mesh.GetControlPoints() 
+
+    for l in range(mesh.GetLayerCount()):
+        mesh_normals = mesh.GetLayer(l).GetNormals()
+        if not mesh_normals:
+            continue
+          
+        normals_array = mesh_normals.GetDirectArray()
+        normals_count = normals_array.GetCount()
+  
+        if normals_count == 0:
+            continue
+
+        normal_indices = []
+        normal_values = []
+
+        # values
+        for i in range(normals_count):
+            normal = convert_fbx_vec3(normals_array.GetAt(i))
+            normal_values.append(normal)
+
+        node = mesh.GetNode()
+        if node and option_geometry:
+            # FbxMeshes are local to their node, we need the normals in global space
+            # when scene nodes are not exported
+            transform = node.EvaluateGlobalTransform()
+            transform.SetT(FbxVector4(0,0,0,0))
+            transform = FbxMatrix(transform)
+
+            for i in range(len(normal_values)):
+                n = normal_values[i]
+                normal = FbxVector4(n[0], n[1], n[2])
+                normal = transform.MultNormalize(normal)
+                normal_values[i] = convert_fbx_vec3(normal)
+
+        # indices
+        vertexId = 0
+        for p in range(poly_count):
+            poly_size = mesh.GetPolygonSize(p)
+            poly_normals = []
+
+            for v in range(poly_size):
+                control_point_index = mesh.GetPolygonVertex(p, v)
+
+                if mesh_normals.GetMappingMode() == FbxLayerElement.eByControlPoint:
+                    if mesh_normals.GetReferenceMode() == FbxLayerElement.eDirect:
+                        poly_normals.append(control_point_index)
+                    elif mesh_normals.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
+                        index = mesh_normals.GetIndexArray().GetAt(control_point_index)
+                        poly_normals.append(index)
+                elif mesh_normals.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
+                    if mesh_normals.GetReferenceMode() == FbxLayerElement.eDirect:
+                        poly_normals.append(vertexId)
+                    elif mesh_normals.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
+                        index = mesh_normals.GetIndexArray().GetAt(vertexId)
+                        poly_normals.append(index)
+                elif mesh_normals.GetMappingMode() == FbxLayerElement.eByPolygon or \
+                     mesh_normals.GetMappingMode() ==  FbxLayerElement.eAllSame or \
+                     mesh_normals.GetMappingMode() ==  FbxLayerElement.eNone:       
+                    print("unsupported normal mapping mode for polygon vertex")
+
+                vertexId += 1
+            normal_indices.append(poly_normals)
+
+        layered_normal_values.append(normal_values)
+        layered_normal_indices.append(normal_indices)
+
+    normal_values = []
+    normal_indices = []
+
+    # Three.js only supports one layer of normals
+    if len(layered_normal_values) > 0:
+        normal_values = layered_normal_values[0]
+        normal_indices = layered_normal_indices[0]
+
+    return normal_values, normal_indices
+
+def extract_fbx_vertex_colors(mesh):
+#   eNone             The mapping is undetermined.
+#   eByControlPoint   There will be one mapping coordinate for each surface control point/vertex.
+#   eByPolygonVertex  There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part.
+#   eByPolygon        There can be only one mapping coordinate for the whole polygon.
+#   eByEdge           There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
+#   eAllSame          There can be only one mapping coordinate for the whole surface.
+
+    layered_color_indices = []
+    layered_color_values = []
+
+    poly_count = mesh.GetPolygonCount()
+    control_points = mesh.GetControlPoints() 
+
+    for l in range(mesh.GetLayerCount()):
+        mesh_colors = mesh.GetLayer(l).GetVertexColors()
+        if not mesh_colors:
+            continue
+          
+        colors_array = mesh_colors.GetDirectArray()
+        colors_count = colors_array.GetCount()
+  
+        if colors_count == 0:
+            continue
+
+        color_indices = []
+        color_values = []
+
+        # values
+        for i in range(colors_count):
+            color = convert_fbx_color(colors_array.GetAt(i))
+            color_values.append(color)
+
+        # indices
+        vertexId = 0
+        for p in range(poly_count):
+            poly_size = mesh.GetPolygonSize(p)
+            poly_colors = []
+
+            for v in range(poly_size):
+                control_point_index = mesh.GetPolygonVertex(p, v)
+
+                if mesh_colors.GetMappingMode() == FbxLayerElement.eByControlPoint:
+                    if mesh_colors.GetReferenceMode() == FbxLayerElement.eDirect:
+                        poly_colors.append(control_point_index)
+                    elif mesh_colors.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
+                        index = mesh_colors.GetIndexArray().GetAt(control_point_index)
+                        poly_colors.append(index)
+                elif mesh_colors.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
+                    if mesh_colors.GetReferenceMode() == FbxLayerElement.eDirect:
+                        poly_colors.append(vertexId)
+                    elif mesh_colors.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
+                        index = mesh_colors.GetIndexArray().GetAt(vertexId)
+                        poly_colors.append(index)
+                elif mesh_colors.GetMappingMode() == FbxLayerElement.eByPolygon or \
+                     mesh_colors.GetMappingMode() ==  FbxLayerElement.eAllSame or \
+                     mesh_colors.GetMappingMode() ==  FbxLayerElement.eNone:       
+                    print("unsupported color mapping mode for polygon vertex")
+
+                vertexId += 1
+            color_indices.append(poly_colors)
+
+    color_values = []
+    color_indices = []
+
+    # Three.js only supports one layer of colors
+    if len(layered_color_values) > 0:
+        color_values = layered_color_values[0]
+        color_indices = layered_color_indices[0]
+
+    return color_values, color_indices
+
+def extract_fbx_vertex_uvs(mesh):
+#   eNone             The mapping is undetermined.
+#   eByControlPoint   There will be one mapping coordinate for each surface control point/vertex.
+#   eByPolygonVertex  There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part.
+#   eByPolygon        There can be only one mapping coordinate for the whole polygon.
+#   eByEdge           There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements.
+#   eAllSame          There can be only one mapping coordinate for the whole surface.
+
+    layered_uv_indices = []
+    layered_uv_values = []
+
+    poly_count = mesh.GetPolygonCount()
+    control_points = mesh.GetControlPoints() 
+
+    for l in range(mesh.GetLayerCount()):
+        mesh_uvs = mesh.GetLayer(l).GetUVs()
+        if not mesh_uvs:
+            continue
+          
+        uvs_array = mesh_uvs.GetDirectArray()
+        uvs_count = uvs_array.GetCount()
+  
+        if uvs_count == 0:
+            continue
+
+        uv_indices = []
+        uv_values = []
+
+        # values
+        for i in range(uvs_count):
+            uv = convert_fbx_vec2(uvs_array.GetAt(i))
+            uv_values.append(uv)
+
+        # indices
+        vertexId = 0
+        for p in range(poly_count):
+            poly_size = mesh.GetPolygonSize(p)
+            poly_uvs = []
+
+            for v in range(poly_size):
+                control_point_index = mesh.GetPolygonVertex(p, v)
+
+                if mesh_uvs.GetMappingMode() == FbxLayerElement.eByControlPoint:
+                    if mesh_uvs.GetReferenceMode() == FbxLayerElement.eDirect:
+                        poly_uvs.append(control_point_index)
+                    elif mesh_uvs.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
+                        index = mesh_uvs.GetIndexArray().GetAt(control_point_index)
+                        poly_uvs.append(index)
+                elif mesh_uvs.GetMappingMode() == FbxLayerElement.eByPolygonVertex:
+                    uv_texture_index = mesh.GetTextureUVIndex(p, v)
+                    if mesh_uvs.GetReferenceMode() == FbxLayerElement.eDirect or \
+                       mesh_uvs.GetReferenceMode() == FbxLayerElement.eIndexToDirect:
+                        poly_uvs.append(uv_texture_index)
+                elif mesh_uvs.GetMappingMode() == FbxLayerElement.eByPolygon or \
+                     mesh_uvs.GetMappingMode() ==  FbxLayerElement.eAllSame or \
+                     mesh_uvs.GetMappingMode() ==  FbxLayerElement.eNone:       
+                    print("unsupported uv mapping mode for polygon vertex")
+
+                vertexId += 1
+            uv_indices.append(poly_uvs)
+
+        layered_uv_values.append(uv_values)
+        layered_uv_indices.append(uv_indices)
+
+    return layered_uv_values, layered_uv_indices
+
+# #####################################################
+# Generate - Mesh String (for scene output) 
+# #####################################################
+def generate_mesh_string_for_scene_output(node):
+    mesh = node.GetNodeAttribute()
+    mesh_list = [ mesh ]
+
+    vertices, vertex_offsets = process_mesh_vertices(mesh_list)
+    materials, material_offsets = process_mesh_materials(mesh_list)
+
+    normals_to_indices = generate_unique_normals_dictionary(mesh_list)
+    colors_to_indices = generate_unique_colors_dictionary(mesh_list)
+    uvs_to_indices_list = generate_unique_uvs_dictionary_layers(mesh_list)
+    
+    normal_values = generate_normals_from_dictionary(normals_to_indices)
+    color_values = generate_colors_from_dictionary(colors_to_indices)
+    uv_values = generate_uvs_from_dictionary_layers(uvs_to_indices_list)
+
+    faces = process_mesh_polygons(mesh_list, 
+                normals_to_indices, 
+                colors_to_indices, 
+                uvs_to_indices_list, 
+                vertex_offsets, 
+                material_offsets)
+
+    nuvs = []
+    for layer_index, uvs in enumerate(uv_values):
+        nuvs.append(str(len(uvs)))
+
+    nvertices = len(vertices)
+    nnormals = len(normal_values)
+    ncolors = len(color_values)
+    nfaces = len(faces)
+    nuvs = ",".join(nuvs)
+    
+    aabb_min, aabb_max = generate_bounding_box(vertices)
+    aabb_min = ",".join(str(f) for f in aabb_min)
+    aabb_max = ",".join(str(f) for f in aabb_max)
+
+    vertices = ",".join(Vector3String(v, True) for v in vertices)
+    normals  = ",".join(Vector3String(v, True) for v in normal_values)
+    colors   = ",".join(Vector3String(v, True) for v in color_values)
+    faces    = ",".join(faces)
+    uvs      = generate_uvs(uv_values)
+
+    output = [
+
+    '\t' + LabelString( getEmbedName( node, True ) ) + ' : {',
+    '	"metadata"  : {',
+    '		"vertices" : ' + str(nvertices) + ',',
+    '		"normals" : ' + str(nnormals) + ',',
+    '		"colors" : ' + str(ncolors) + ',',
+    '		"faces" : ' + str(nfaces) + ',',
+    '		"uvs" : ' + ArrayString(nuvs),
+    '	},',
+    '	"boundingBox"  : {',
+    '		"min" : ' + ArrayString(aabb_min) + ',',   
+    '		"max" : ' + ArrayString(aabb_max),   
+    '	},',
+    '	"scale" : ' + str( 1 ) + ',',   
+    '	"materials" : ' + ArrayString("") + ',',   
+    '	"vertices" : ' + ArrayString(vertices) + ',',   
+    '	"normals" : ' + ArrayString(normals) + ',',   
+    '	"colors" : ' + ArrayString(colors) + ',',   
+    '	"uvs" : ' + ArrayString(uvs) + ',',   
+    '	"faces" : ' + ArrayString(faces),
+    '}'
+
+    ]
+    
+    return generateMultiLineString( output, '\n\t\t', 0 )
+
+# #####################################################
+# Generate - Mesh String (for non-scene output) 
+# #####################################################
+def generate_mesh_string_for_non_scene_output(scene):
+    mesh_list = generate_mesh_list(scene)
+
+    vertices, vertex_offsets = process_mesh_vertices(mesh_list)
+    materials, material_offsets = process_mesh_materials(mesh_list)
+
+    normals_to_indices = generate_unique_normals_dictionary(mesh_list)
+    colors_to_indices = generate_unique_colors_dictionary(mesh_list)
+    uvs_to_indices_list = generate_unique_uvs_dictionary_layers(mesh_list)
+    
+    normal_values = generate_normals_from_dictionary(normals_to_indices)
+    color_values = generate_colors_from_dictionary(colors_to_indices)
+    uv_values = generate_uvs_from_dictionary_layers(uvs_to_indices_list)
+
+    faces = process_mesh_polygons(mesh_list, 
+                normals_to_indices, 
+                colors_to_indices, 
+                uvs_to_indices_list, 
+                vertex_offsets, 
+                material_offsets)
+
+    nuvs = []
+    for layer_index, uvs in enumerate(uv_values):
+        nuvs.append(str(len(uvs)))
+
+    nvertices = len(vertices)
+    nnormals = len(normal_values)
+    ncolors = len(color_values)
+    nfaces = len(faces)
+    nuvs = ",".join(nuvs)
+
+    aabb_min, aabb_max = generate_bounding_box(vertices)
+    aabb_min = ",".join(str(f) for f in aabb_min)
+    aabb_max = ",".join(str(f) for f in aabb_max)
+
+    vertices = ",".join(Vector3String(v, True) for v in vertices)
+    normals  = ",".join(Vector3String(v, True) for v in normal_values)
+    colors   = ",".join(Vector3String(v, True) for v in color_values)
+    faces    = ",".join(faces)
+    uvs      = generate_uvs(uv_values)
+
+    output = [
+
+    '{',
+    '	"metadata"  : {',
+    '		"formatVersion" : 3.2,',
+    '		"type"		: "geometry",',
+    '		"generatedBy"	: "convert-to-threejs.py"' + ',',
+    '		"vertices" : ' + str(nvertices) + ',',
+    '		"normals" : ' + str(nnormals) + ',',
+    '		"colors" : ' + str(ncolors) + ',',
+    '		"faces" : ' + str(nfaces) + ',',
+    '		"uvs" : ' + ArrayString(nuvs),
+    '	},',
+    '	"boundingBox"  : {',
+    '		"min" : ' + ArrayString(aabb_min) + ',',   
+    '		"max" : ' + ArrayString(aabb_max),   
+    '	},',
+    '	"scale" : ' + str( 1 ) + ',',   
+    '	"materials" : ' + ArrayString("") + ',',   
+    '	"vertices" : ' + ArrayString(vertices) + ',',   
+    '	"normals" : ' + ArrayString(normals) + ',',   
+    '	"colors" : ' + ArrayString(colors) + ',',   
+    '	"uvs" : ' + ArrayString(uvs) + ',',   
+    '	"faces" : ' + ArrayString(faces),
+    '}'
+
+    ]
+
+    return generateMultiLineString( output, '\n', 0 )
+
+# #####################################################
+# Process - Mesh Geometry
+# #####################################################
+def generate_normal_key(normal):
+    return (round(normal[0], 6), round(normal[1], 6), round(normal[2], 6))
+
+def generate_color_key(color):
+    return getHex(color)
+
+def generate_uv_key(uv):
+    return (round(uv[0], 6), round(uv[1], 6))
+                
+def append_non_duplicate_uvs(source_uvs, dest_uvs, counts):
+    source_layer_count = len(source_uvs)
+    for layer_index in range(source_layer_count):
+
+        dest_layer_count = len(dest_uvs)
+
+        if dest_layer_count <= layer_index:
+            dest_uv_layer = {}
+            count = 0
+            dest_uvs.append(dest_uv_layer)
+            counts.append(count)
+        else:
+            dest_uv_layer = dest_uvs[layer_index]
+            count = counts[layer_index]
+
+        source_uv_layer = source_uvs[layer_index]
+
+        for uv in source_uv_layer:
+            key = generate_uv_key(uv) 
+            if key not in dest_uv_layer:
+                dest_uv_layer[key] = count
+                count += 1
+
+        counts[layer_index] = count
+
+    return counts
+
+def generate_unique_normals_dictionary(mesh_list):
+    normals_dictionary = {}
+    nnormals = 0
+      
+    # Merge meshes, remove duplicate data
+    for mesh in mesh_list:
+        node = mesh.GetNode()
+        normal_values, normal_indices = extract_fbx_vertex_normals(mesh)
+
+        if len(normal_values) > 0:
+            for normal in normal_values:
+                key = generate_normal_key(normal) 
+                if key not in normals_dictionary:
+                    normals_dictionary[key] = nnormals
+                    nnormals += 1
+
+    return normals_dictionary
+
+def generate_unique_colors_dictionary(mesh_list):
+    colors_dictionary = {}
+    ncolors = 0
+      
+    # Merge meshes, remove duplicate data
+    for mesh in mesh_list:
+        color_values, color_indices = extract_fbx_vertex_colors(mesh)
+
+        if len(color_values) > 0:
+            for color in color_values:
+                key = generate_color_key(color) 
+                if key not in colors_dictionary:
+                    colors_dictionary[key] = count
+                    count += 1
+
+    return colors_dictionary
+
+def generate_unique_uvs_dictionary_layers(mesh_list):
+    uvs_dictionary_layers = []
+    nuvs_list = []
+
+    # Merge meshes, remove duplicate data
+    for mesh in mesh_list:
+        uv_values, uv_indices = extract_fbx_vertex_uvs(mesh)
+
+        if len(uv_values) > 0:
+            nuvs_list = append_non_duplicate_uvs(uv_values, uvs_dictionary_layers, nuvs_list)
+
+    return uvs_dictionary_layers
+
+def generate_normals_from_dictionary(normals_dictionary):
+    normal_values = []
+    for key, index in sorted(normals_dictionary.items(), key = operator.itemgetter(1)):
+        normal_values.append(key)
+
+    return normal_values
+
+def generate_colors_from_dictionary(colors_dictionary):
+    color_values = []
+    for key, index in sorted(colors_dictionary.items(), key = operator.itemgetter(1)):
+        color_values.append(key)
+
+    return color_values
+
+def generate_uvs_from_dictionary_layers(uvs_dictionary_layers):
+    uv_values = []
+    for uvs_dictionary in uvs_dictionary_layers:
+        uv_values_layer = []    
+        for key, index in sorted(uvs_dictionary.items(), key = operator.itemgetter(1)):
+            uv_values_layer.append(key)
+        uv_values.append(uv_values_layer)
+
+    return uv_values
+
+def generate_normal_indices_for_poly(poly_index, mesh_normal_values, mesh_normal_indices, normals_to_indices):
+    if len(mesh_normal_indices) <= 0:
+        return []
+
+    poly_normal_indices = mesh_normal_indices[poly_index]
+    poly_size = len(poly_normal_indices)
+
+    output_poly_normal_indices = []
+    for v in range(poly_size):
+        normal_index = poly_normal_indices[v]
+        normal_value = mesh_normal_values[normal_index]
+
+        key = generate_normal_key(normal_value)
+
+        output_index = normals_to_indices[key]
+        output_poly_normal_indices.append(output_index)
+
+    return output_poly_normal_indices
+
+def generate_color_indices_for_poly(poly_index, mesh_color_values, mesh_color_indices, colors_to_indices):
+    if len(mesh_color_indices) <= 0:
+        return []
+
+    poly_color_indices = mesh_color_indices[poly_index]
+    poly_size = len(poly_color_indices)
+
+    output_poly_color_indices = []
+    for v in range(poly_size):
+        color_index = poly_color_indices[v]
+        color_value = mesh_color_values[color_index]
+
+        key = generate_color_key(color_value)
+
+        output_index = colors_to_indices[key]
+        output_poly_color_indices.append(output_index)
+
+    return output_poly_color_indices
+
+def generate_uv_indices_for_poly(poly_index, mesh_uv_values, mesh_uv_indices, uvs_to_indices):
+    if len(mesh_uv_indices) <= 0:
+        return []
+
+    poly_uv_indices = mesh_uv_indices[poly_index]
+    poly_size = len(poly_uv_indices)
+
+    output_poly_uv_indices = []
+    for v in range(poly_size):
+        uv_index = poly_uv_indices[v]
+        uv_value = mesh_uv_values[uv_index]
+
+        key = generate_uv_key(uv_value)
+
+        output_index = uvs_to_indices[key]
+        output_poly_uv_indices.append(output_index)
+
+    return output_poly_uv_indices
+
+def process_mesh_vertices(mesh_list):
+    vertex_offset = 0
+    vertex_offset_list = [0]
+    vertices = []
+    for mesh in mesh_list:
+        node = mesh.GetNode()
+        mesh_vertices = extract_fbx_vertex_positions(mesh)
+                
+        vertices.extend(mesh_vertices[:])
+        vertex_offset += len(mesh_vertices)
+        vertex_offset_list.append(vertex_offset)
+
+    return vertices, vertex_offset_list
+
+def process_mesh_materials(mesh_list):
+    material_offset = 0
+    material_offset_list = [0]
+    materials_list = []
+
+    #TODO: remove duplicate mesh references
+    for mesh in mesh_list:
+        node = mesh.GetNode()
+                
+        material_count = node.GetMaterialCount()
+        if material_count > 0:
+            for l in range(mesh.GetLayerCount()):
+                materials = mesh.GetLayer(l).GetMaterials()
+                if materials:
+                    if materials.GetReferenceMode() == FbxLayerElement.eIndex:
+                        #Materials are in an undefined external table
+                        continue
+
+                    for i in range(material_count):
+                        material = node.GetMaterial(i)
+                        materials_list.append( material )
+
+                    material_offset += material_count
+                    material_offset_list.append(material_offset)
+
+    return materials_list, material_offset_list
+
+def process_mesh_polygons(mesh_list, normals_to_indices, colors_to_indices, uvs_to_indices_list, vertex_offset_list, material_offset_list):
+    faces = []
+    for mesh_index in range(len(mesh_list)):
+        mesh = mesh_list[mesh_index]
+        poly_count = mesh.GetPolygonCount()
+        control_points = mesh.GetControlPoints() 
+
+        normal_values, normal_indices = extract_fbx_vertex_normals(mesh)
+        color_values, color_indices = extract_fbx_vertex_colors(mesh)
+        uv_values_layers, uv_indices_layers = extract_fbx_vertex_uvs(mesh)
+
+        for poly_index in range(poly_count):
+            poly_size = mesh.GetPolygonSize(poly_index)
+
+            face_normals = generate_normal_indices_for_poly(poly_index, normal_values, normal_indices, normals_to_indices)
+            face_colors = generate_color_indices_for_poly(poly_index, color_values, color_indices, colors_to_indices)
+
+            face_uv_layers = []
+            for l in range(len(uv_indices_layers)):
+                uv_values = uv_values_layers[l]
+                uv_indices = uv_indices_layers[l]
+                face_uv_indices = generate_uv_indices_for_poly(poly_index, uv_values, uv_indices, uvs_to_indices_list[l])
+                face_uv_layers.append(face_uv_indices)
+                
+            face_vertices = []
+            for vertex_index in range(poly_size):
+                control_point_index = mesh.GetPolygonVertex(poly_index, vertex_index)
+                face_vertices.append(control_point_index)
+
+            #TODO: assign a default material to any mesh without one
+            if len(material_offset_list) <= mesh_index:
+                material_offset = 0
+            else:
+                material_offset = material_offset_list[mesh_index]
+
+            vertex_offset = vertex_offset_list[mesh_index]
+
+            face = generate_mesh_face(mesh, 
+                      poly_index, 
+                      face_vertices,
+                      face_normals,
+                      face_colors,
+                      face_uv_layers,
+                      vertex_offset,
+                      material_offset)
+
+            faces.append(face)
+
+
+    return faces
+
+def generate_mesh_face(mesh, polygon_index, vertex_indices, normals, colors, uv_layers, vertex_offset, material_offset):
+    isTriangle = ( len(vertex_indices) == 3 )
+    nVertices = 3 if isTriangle else 4
+
+    hasMaterial = False
+    for l in range(mesh.GetLayerCount()):
+        materials = mesh.GetLayer(l).GetMaterials()
+        if materials:
+            hasMaterial = True
+            break
+                
+    hasFaceUvs = False
+    hasFaceVertexUvs = len(uv_layers) > 0
+    hasFaceNormals = False 
+    hasFaceVertexNormals = len(normals) > 0
+    hasFaceColors = False 
+    hasFaceVertexColors = len(colors) > 0
+
+    faceType = 0
+    faceType = setBit(faceType, 0, not isTriangle)
+    faceType = setBit(faceType, 1, hasMaterial)
+    faceType = setBit(faceType, 2, hasFaceUvs)
+    faceType = setBit(faceType, 3, hasFaceVertexUvs)
+    faceType = setBit(faceType, 4, hasFaceNormals)
+    faceType = setBit(faceType, 5, hasFaceVertexNormals)
+    faceType = setBit(faceType, 6, hasFaceColors)
+    faceType = setBit(faceType, 7, hasFaceVertexColors)
+
+    faceData = []
+
+    # order is important, must match order in JSONLoader
+
+    # face type
+    # vertex indices
+    # material index
+    # face uvs index
+    # face vertex uvs indices
+    # face color index
+    # face vertex colors indices
+
+    faceData.append(faceType)
+
+    tmp = []
+    for i in range(nVertices):
+        tmp.append(vertex_indices[i])
+        index = vertex_indices[i] + vertex_offset
+        faceData.append(index)
+
+    if hasMaterial:
+        material_id = 0
+        for l in range(mesh.GetLayerCount()):
+            materials = mesh.GetLayer(l).GetMaterials()
+            if materials:
+                material_id = materials.GetIndexArray().GetAt(polygon_index)
+                break
+        material_id += material_offset
+        faceData.append( material_id )
+
+    if hasFaceVertexUvs:
+        for polygon_uvs in uv_layers:
+            for i in range(nVertices):
+                index = polygon_uvs[i]
+                faceData.append(index)
+
+    if hasFaceVertexNormals:
+        for i in range(nVertices):
+            index = normals[i]
+            faceData.append(index)
+
+    if hasFaceVertexColors:
+        for i in range(nVertices):
+            index = colors[i]
+            faceData.append(index)
+
+    return ",".join( map(str, faceData) ) 
+
+
+# #####################################################
+# Generate - Mesh List 
+# #####################################################
+def generate_mesh_list_from_hierarchy(node, mesh_list):
+    if node.GetNodeAttribute() == None:
+        pass
+    else:
+        attribute_type = (node.GetNodeAttribute().GetAttributeType())
+        if attribute_type == FbxNodeAttribute.eMesh or \
+           attribute_type == FbxNodeAttribute.eNurbs or \
+           attribute_type == FbxNodeAttribute.eNurbsSurface or \
+           attribute_type == FbxNodeAttribute.ePatch:
+
+            if attribute_type != FbxNodeAttribute.eMesh:
+                converter.TriangulateInPlace(node);
+
+            mesh_list.append(node.GetNodeAttribute())
+
+    for i in range(node.GetChildCount()):
+        generate_mesh_list_from_hierarchy(node.GetChild(i), mesh_list)
+
+def generate_mesh_list(scene):
+    mesh_list = []
+    node = scene.GetRootNode()
+    if node:
+        for i in range(node.GetChildCount()):
+            generate_mesh_list_from_hierarchy(node.GetChild(i), mesh_list)
+    return mesh_list
+
+# #####################################################
+# Generate - Embeds 
+# #####################################################
+def generate_embed_list_from_hierarchy(node, embed_list):
+    if node.GetNodeAttribute() == None:
+        pass
+    else:
+        attribute_type = (node.GetNodeAttribute().GetAttributeType())
+        if attribute_type == FbxNodeAttribute.eMesh or \
+           attribute_type == FbxNodeAttribute.eNurbs or \
+           attribute_type == FbxNodeAttribute.eNurbsSurface or \
+           attribute_type == FbxNodeAttribute.ePatch:
+
+            if attribute_type != FbxNodeAttribute.eMesh:
+                converter.TriangulateInPlace(node);
+
+            embed_string = generate_mesh_string_for_scene_output(node)
+            embed_list.append(embed_string)
+
+    for i in range(node.GetChildCount()):
+        generate_embed_list_from_hierarchy(node.GetChild(i), embed_list)
+
+def generate_embed_list(scene):
+    embed_list = []
+    node = scene.GetRootNode()
+    if node:
+        for i in range(node.GetChildCount()):
+            generate_embed_list_from_hierarchy(node.GetChild(i), embed_list)
+    return embed_list
+
+# #####################################################
+# Generate - Geometries 
+# #####################################################
+def generate_geometry_string(node):
+
+    output = [
+    '\t' + LabelString( getGeometryName( node, True ) ) + ' : {',
+    '	"type"  : "embedded",',
+    '	"id" : ' + LabelString( getEmbedName( node, True ) ),
+    '}'
+    ]
+
+    return generateMultiLineString( output, '\n\t\t', 0 )
+
+def generate_geometry_list_from_hierarchy(node, geometry_list):
+    if node.GetNodeAttribute() == None:
+        pass
+    else:
+        attribute_type = (node.GetNodeAttribute().GetAttributeType())
+        if attribute_type == FbxNodeAttribute.eMesh:
+            geometry_string = generate_geometry_string(node)
+            geometry_list.append(geometry_string)
+    for i in range(node.GetChildCount()):
+        generate_geometry_list_from_hierarchy(node.GetChild(i), geometry_list)
+
+def generate_geometry_list(scene):
+    geometry_list = []
+    node = scene.GetRootNode()
+    if node:
+        for i in range(node.GetChildCount()):
+            generate_geometry_list_from_hierarchy(node.GetChild(i), geometry_list)
+    return geometry_list
+
+# #####################################################
+# Generate - Camera Names
+# #####################################################
+def generate_camera_name_list_from_hierarchy(node, camera_list):
+    if node.GetNodeAttribute() == None:
+        pass
+    else:
+        attribute_type = (node.GetNodeAttribute().GetAttributeType())
+        if attribute_type == FbxNodeAttribute.eCamera:
+            camera_string = getObjectName(node) 
+            camera_list.append(camera_string)
+    for i in range(node.GetChildCount()):
+        generate_camera_name_list_from_hierarchy(node.GetChild(i), camera_list)
+
+def generate_camera_name_list(scene):
+    camera_list = []
+    node = scene.GetRootNode()
+    if node:
+        for i in range(node.GetChildCount()):
+            generate_camera_name_list_from_hierarchy(node.GetChild(i), camera_list)
+    return camera_list
+
+# #####################################################
+# Generate - Light Object 
+# #####################################################
+def generate_default_light_string(padding):
+    direction = (1,1,1)
+    color = (1,1,1)
+    intensity = 80.0
+
+    output = [
+
+    '\t\t' + LabelString( 'default_light' ) + ' : {',
+    '	"type"      : "DirectionalLight",',
+    '	"color"     : ' + str(getHex(color)) + ',',
+    '	"intensity" : ' + str(intensity/100.0) + ',',
+    '	"direction" : ' + Vector3String( direction ) + ',',
+    '	"target"    : ' + LabelString( getObjectName( None ) ),
+    ' }'
+
+    ]
+
+    return generateMultiLineString( output, '\n\t\t', padding )
+
+def generate_light_string(node, padding):
+    light = node.GetNodeAttribute()
+    light_types = ["point", "directional", "spot", "area", "volume"]
+    light_type = light_types[light.LightType.Get()]
+
+    transform = node.EvaluateLocalTransform()
+    position = transform.GetT()
+
+    output = []
+
+    if light_type == "directional":
+
+        # Three.js directional lights emit light from a point in 3d space to a target node or the origin.
+        # When there is no target, we need to take a point, one unit away from the origin, and move it 
+        # into the right location so that the origin acts like the target
+        
+        if node.GetTarget():
+            direction = position
+        else:
+            translation = FbxVector4(0,0,0,0)
+            scale = FbxVector4(1,1,1,1)
+            rotation = transform.GetR()
+            matrix = FbxMatrix(translation, rotation, scale)
+            direction = matrix.MultNormalize(global_up_vector) 
+
+        output = [
+
+        '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
+        '	"type"      : "DirectionalLight",',
+        '	"color"     : ' + str(getHex(light.Color.Get())) + ',',
+        '	"intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
+        '	"direction" : ' + Vector3String( direction ) + ',',
+        '	"target"    : ' + LabelString( getObjectName( node.GetTarget() ) ) + ( ',' if node.GetChildCount() > 0 else '' )
+        ]
+
+    elif light_type == "point":
+
+        output = [
+
+        '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
+        '	"type"      : "PointLight",',
+        '	"color"     : ' + str(getHex(light.Color.Get())) + ',',
+        '	"intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
+        '	"position"  : ' + Vector3String( position ) + ',',
+        '	"distance"  : ' + str(light.FarAttenuationEnd.Get()) + ( ',' if node.GetChildCount() > 0 else '' )
+
+        ]
+
+    elif light_type == "spot":
+
+        output = [
+
+        '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
+        '	"type"      : "SpotLight",',
+        '	"color"     : ' + str(getHex(light.Color.Get())) + ',',
+        '	"intensity" : ' + str(light.Intensity.Get()/100.0) + ',',
+        '	"position"  : ' + Vector3String( position ) + ',',
+        '	"distance"  : ' + str(light.FarAttenuationEnd.Get()) + ',',
+        '	"angle"     : ' + str((light.OuterAngle.Get()*math.pi)/180) + ',',
+        '	"exponent"  : ' + str(light.DecayType.Get()) + ',',
+        '	"target"    : ' + LabelString( getObjectName( node.GetTarget() ) ) + ( ',' if node.GetChildCount() > 0 else '' )
+
+        ]
+
+    return generateMultiLineString( output, '\n\t\t', padding )
+
+def generate_ambient_light_string(scene):
+
+    scene_settings = scene.GetGlobalSettings()
+    ambient_color = scene_settings.GetAmbientColor()
+    ambient_color = (ambient_color.mRed, ambient_color.mGreen, ambient_color.mBlue)
+
+    if ambient_color[0] == 0 and ambient_color[1] == 0 and ambient_color[2] == 0:
+        return None
+
+    class AmbientLight:
+        def GetName(self):
+            return "AmbientLight"
+
+    node = AmbientLight()
+
+    output = [
+
+    '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
+    '	"type"  : "AmbientLight",',
+    '	"color" : ' + str(getHex(ambient_color)),
+    '}'
+
+    ]
+
+    return generateMultiLineString( output, '\n\t\t', 0 )
+    
+# #####################################################
+# Generate - Camera Object 
+# #####################################################
+def generate_default_camera_string(padding):
+    position = (100, 100, 100)
+    near = 0.1
+    far = 1000
+    fov = 75
+
+    output = [
+
+    '\t\t' + LabelString( 'default_camera' ) + ' : {',
+    '	"type"     : "PerspectiveCamera",',
+    '	"fov"      : ' + str(fov) + ',',
+    '	"near"     : ' + str(near) + ',',
+    '	"far"      : ' + str(far) + ',',
+    '	"position" : ' + Vector3String( position ), 
+    ' }'
+
+    ]
+
+    return generateMultiLineString( output, '\n\t\t', padding )
+
+def generate_camera_string(node, padding):
+    camera = node.GetNodeAttribute()
+
+    target_node = node.GetTarget()
+    target = ""
+    if target_node:
+        transform = target.EvaluateLocalTransform()
+        target = transform.GetT()
+    else:
+        target = camera.InterestPosition.Get()
+
+    position = camera.Position.Get()
+  
+    projection_types = [ "perspective", "orthogonal" ]
+    projection = projection_types[camera.ProjectionType.Get()]
+
+    near = camera.NearPlane.Get()
+    far = camera.FarPlane.Get()
+
+    output = []
+
+    if projection == "perspective":
+
+        aspect = camera.PixelAspectRatio.Get()
+        fov = camera.FieldOfView.Get()
+
+        output = [
+
+        '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
+        '	"type"     : "PerspectiveCamera",',
+        '	"fov"      : ' + str(fov) + ',',
+        '	"aspect"   : ' + str(aspect) + ',',
+        '	"near"     : ' + str(near) + ',',
+        '	"far"      : ' + str(far) + ',',
+        '	"position" : ' + Vector3String( position ) + ( ',' if node.GetChildCount() > 0 else '' )
+
+        ]
+
+    elif projection == "orthogonal":
+
+        left = ""
+        right = ""
+        top = ""
+        bottom = ""
+
+        output = [
+
+        '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
+        '	"type"     : "OrthographicCamera",',
+        '	"left"     : ' + left + ',',
+        '	"right"    : ' + right + ',',
+        '	"top"      : ' + top + ',',
+        '	"bottom"   : ' + bottom + ',',
+        '	"near"     : ' + str(near) + ',',
+        '	"far"      : ' + str(far) + ',',
+        '	"position" : ' + Vector3String( position ) + ( ',' if node.GetChildCount() > 0 else '' )
+
+        ]
+
+    return generateMultiLineString( output, '\n\t\t', padding )
+
+# #####################################################
+# Generate - Mesh Object 
+# #####################################################
+def generate_mesh_object_string(node, padding):
+    mesh = node.GetNodeAttribute()
+    transform = node.EvaluateLocalTransform()
+    position = transform.GetT()
+    scale = transform.GetS()
+    rotation = getRadians(transform.GetR())
+
+    material_count = node.GetMaterialCount()
+    material_name = ""
+
+    if material_count > 0:
+        material_names = []
+        for l in range(mesh.GetLayerCount()):
+            materials = mesh.GetLayer(l).GetMaterials()
+            if materials:
+                if materials.GetReferenceMode() == FbxLayerElement.eIndex:
+                    #Materials are in an undefined external table
+                    continue
+                for i in range(material_count):
+                    material = node.GetMaterial(i)
+                    material_names.append( getMaterialName(material) )
+        #If this mesh has more than one material, use a proxy material
+        material_name = getMaterialName( node, True) if material_count > 1 else material_names[0] 
+
+    output = [
+
+    '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
+    '	"geometry" : ' + LabelString( getGeometryName( node, True ) ) + ',',
+    '	"material" : ' + LabelString( material_name ) + ',',
+    '	"position" : ' + Vector3String( position ) + ',',
+    '	"rotation" : ' + Vector3String( rotation ) + ',',
+    '	"scale"	   : ' + Vector3String( scale ) + ',',
+    '	"visible"  : ' + getObjectVisible( node ) + ( ',' if node.GetChildCount() > 0 else '' )
+
+    ]
+
+    return generateMultiLineString( output, '\n\t\t', padding )
+
+# #####################################################
+# Generate - Object 
+# #####################################################
+def generate_object_string(node, padding):
+    node_types = ["Unknown", "Null", "Marker", "Skeleton", "Mesh", "Nurbs", "Patch", "Camera", 
+    "CameraStereo", "CameraSwitcher", "Light", "OpticalReference", "OpticalMarker", "NurbsCurve", 
+    "TrimNurbsSurface", "Boundary", "NurbsSurface", "Shape", "LODGroup", "SubDiv", "CachedEffect", "Line"]
+
+    transform = node.EvaluateLocalTransform()
+    position = transform.GetT()
+    scale = transform.GetS()
+    rotation = getRadians(transform.GetR())
+
+    node_type = ""
+    if node.GetNodeAttribute() == None:
+        node_type = "Null"
+    else:
+        node_type = node_types[node.GetNodeAttribute().GetAttributeType()]
+
+    output = [
+
+    '\t\t' + LabelString( getObjectName( node ) ) + ' : {',
+    '	"fbx_type" : ' + LabelString( node_type ) + ',',
+    '	"position" : ' + Vector3String( position ) + ',',
+    '	"rotation" : ' + Vector3String( rotation ) + ',',
+    '	"scale"	   : ' + Vector3String( scale ) + ',',
+    '	"visible"  : ' + getObjectVisible( node ) + ( ',' if node.GetChildCount() > 0 else '' )
+
+    ]
+
+    return generateMultiLineString( output, '\n\t\t', padding )
+
+# #####################################################
+# Parse - Objects 
+# #####################################################
+def generate_object_hierarchy(node, object_list, pad, siblings_left):
+    object_count = 0
+    if node.GetNodeAttribute() == None:
+        object_string = generate_object_string(node, pad)
+        object_list.append(object_string)
+        object_count += 1
+    else:
+        attribute_type = (node.GetNodeAttribute().GetAttributeType())
+        if attribute_type == FbxNodeAttribute.eMesh:
+            object_string = generate_mesh_object_string(node, pad)
+            object_list.append(object_string)
+            object_count += 1
+        elif attribute_type == FbxNodeAttribute.eLight:
+            object_string = generate_light_string(node, pad)
+            object_list.append(object_string)
+            object_count += 1
+        elif attribute_type == FbxNodeAttribute.eCamera:
+            object_string = generate_camera_string(node, pad)
+            object_list.append(object_string)
+            object_count += 1
+        else:
+            object_string = generate_object_string(node, pad)
+            object_list.append(object_string)
+            object_count += 1
+
+    if node.GetChildCount() > 0:
+      object_list.append( PaddingString( pad + 1 ) + '\t\t"children" : {\n' )
+
+      for i in range(node.GetChildCount()):
+          object_count += generate_object_hierarchy(node.GetChild(i), object_list, pad + 2, node.GetChildCount() - i - 1)
+
+      object_list.append( PaddingString( pad + 1 ) + '\t\t}' )
+    object_list.append( PaddingString( pad ) + '\t\t}' + (',\n' if siblings_left > 0 else ''))
+
+    return object_count
+
+def generate_scene_objects_string(scene):
+    object_count = 0
+    object_list = []
+
+    ambient_light = generate_ambient_light_string(scene)
+    if ambient_light:
+        if scene.GetNodeCount() > 0 or option_default_light or option_default_camera:
+            ambient_light += (',\n')
+        object_list.append(ambient_light)
+        object_count += 1
+
+    if option_default_light:
+        default_light = generate_default_light_string(0)
+        if scene.GetNodeCount() > 0 or option_default_camera:
+            default_light += (',\n')
+        object_list.append(default_light)
+        object_count += 1
+
+    if option_default_camera:
+        default_camera = generate_default_camera_string(0)
+        if scene.GetNodeCount() > 0:
+            default_camera += (',\n')
+        object_list.append(default_camera)
+        object_count += 1
+
+    node = scene.GetRootNode()
+    if node:
+        for i in range(node.GetChildCount()):
+            object_count += generate_object_hierarchy(node.GetChild(i), object_list, 0, node.GetChildCount() - i - 1)
+
+    return "\n".join(object_list), object_count
+
+# #####################################################
+# Parse - Geometry (non-scene output) 
+# #####################################################
+def extract_geometry(scene, filename):
+    mesh_string = generate_mesh_string_for_non_scene_output(scene)
+    return mesh_string
+
+# #####################################################
+# Parse - Scene (scene output)
+# #####################################################
+def extract_scene(scene, filename):
+    global_settings = scene.GetGlobalSettings()
+    objects, nobjects = generate_scene_objects_string(scene)
+
+    textures = generate_texture_list(scene)
+    materials = generate_material_list(scene)
+    geometries = generate_geometry_list(scene)
+    embeds = generate_embed_list(scene)
+    fogs = []
+
+    ntextures = len(textures)
+    nmaterials = len(materials)
+    ngeometries = len(geometries)
+
+    #TODO: extract actual root/scene data here
+    position = Vector3String( (0,0,0) )
+    rotation = Vector3String( (0,0,0) )
+    scale    = Vector3String( (1,1,1) )
+
+    camera_names = generate_camera_name_list(scene)
+    scene_settings = scene.GetGlobalSettings()
+
+    #TODO: this might exist as part of the FBX spec
+    bgcolor = Vector3String( (0.667,0.667,0.667) )
+    bgalpha = 1
+
+    # This does not seem to be any help here
+    # global_settings.GetDefaultCamera() 
+
+    defcamera = LabelString(camera_names[0] if len(camera_names) > 0 else "")
+    if option_default_camera:
+      defcamera = LabelString('default_camera')
+
+    #TODO: extract fog info from scene
+    deffog = LabelString("")
+
+    geometries = generateMultiLineString( geometries, ",\n\n\t", 0 )
+    materials = generateMultiLineString( materials, ",\n\n\t", 0 )
+    textures = generateMultiLineString( textures, ",\n\n\t", 0 )
+    embeds = generateMultiLineString( embeds, ",\n\n\t", 0 )
+    fogs = generateMultiLineString( fogs, ",\n\n\t", 0 )
+
+    output = [
+
+    '{',
+    '	"metadata": {',
+    '		"formatVersion" : 3.2,',
+    '		"type"		: "scene",',
+    '		"generatedBy"	: "convert-to-threejs.py",',
+    '		"objects"       : ' + str(nobjects) + ',',
+    '		"geometries"    : ' + str(ngeometries) + ',',
+    '		"materials"     : ' + str(nmaterials) + ',',
+    '		"textures"      : ' + str(ntextures),
+    '	},',
+
+    '',
+    '	"urlBaseType": "relativeToScene",',
+    '',
+
+    '	"objects" :',
+    '	{',
+    objects,
+    '	},',
+    '',
+
+    '	"geometries" :',
+    '	{',
+    '\t' + 	geometries,
+    '	},',
+    '',
+
+    '	"materials" :',
+    '	{',
+    '\t' + 	materials,
+    '	},',
+    '',
+
+    '	"textures" :',
+    '	{',
+    '\t' + 	textures,
+    '	},',
+    '',
+
+    '	"embeds" :',
+    '	{',
+    '\t' + 	embeds,
+    '	},',
+    '',
+
+    '	"fogs" :',
+    '	{',
+    '\t' + 	fogs,
+    '	},',
+    '',
+
+    '	"transform" :',
+    '	{',
+    '		"position"  : ' + position + ',',
+    '		"rotation"  : ' + rotation + ',',
+    '		"scale"     : ' + scale,
+    '	},',
+    '',
+
+    '	"defaults" :',
+    '	{',
+    '		"bgcolor" : ' + str(bgcolor) + ',',
+    '		"bgalpha" : ' + str(bgalpha) + ',',
+    '		"camera"  : ' + defcamera + ',',
+    '		"fog"  	  : ' + deffog,
+    '	}',
+    '}'
+
+    ]
+
+    return "\n".join(output)
+
+# #####################################################
+# file helpers
+# #####################################################
+def write_file(fname, content):
+    out = open(fname, "w")
+    out.write(content)
+    out.close()
+
+# #####################################################
+# main
+# #####################################################
+if __name__ == "__main__":
+    from optparse import OptionParser
+
+    try:
+        from FbxCommon import *
+    except ImportError:
+        import platform
+        msg = 'Could not locate the python FBX SDK!\n'
+        msg += 'You need to copy the FBX SDK into your python install folder such as '
+        if platform.system() == 'Windows' or platform.system() == 'Microsoft':
+            msg += '"Python26/Lib/site-packages"'
+        elif platform.system() == 'Linux':
+            msg += '"/usr/local/lib/python2.6/site-packages"'
+        elif platform.system() == 'Darwin':
+            msg += '"/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/site-packages"'        
+        msg += ' folder.'
+        print(msg) 
+        sys.exit(1)
+    
+    usage = "Usage: %prog [source_file.fbx] [output_file.js] [options]"
+    parser = OptionParser(usage=usage)
+
+    parser.add_option('-t', '--triangulate', action='store_true', dest='triangulate', help="force quad geometry into triangles", default=False)
+    parser.add_option('-x', '--no-textures', action='store_true', dest='notextures', help="don't include texture references in output file", default=False)
+    parser.add_option('-p', '--prefix', action='store_true', dest='prefix', help="prefix object names in output file", default=False)
+    parser.add_option('-g', '--geometry-only', action='store_true', dest='geometry', help="output geometry only", default=False)
+    parser.add_option('-c', '--default-camera', action='store_true', dest='defcamera', help="include default camera in output scene", default=False)
+    parser.add_option('-l', '--defualt-light', action='store_true', dest='deflight', help="include default light in output scene", default=False)
+
+    (options, args) = parser.parse_args()
+
+    option_triangulate = options.triangulate 
+    option_textures = True if not options.notextures else False
+    option_prefix = options.prefix
+    option_geometry = options.geometry 
+    option_default_camera = options.defcamera 
+    option_default_light = options.deflight 
+
+    # Prepare the FBX SDK.
+    sdk_manager, scene = InitializeSdkObjects()
+    converter = FbxGeometryConverter(sdk_manager)
+    global_up_vector = get_up_vector(scene)
+
+    # The converter takes an FBX file as an argument.
+    if len(args) > 1:
+        print("\nLoading file: %s" % args[0])
+        result = LoadScene(sdk_manager, scene, args[0])
+    else:
+        result = False
+        print("\nUsage: convert_fbx_to_threejs [source_file.fbx] [output_file.js]\n")
+
+    if not result:
+        print("\nAn error occurred while loading the file...")
+    else:
+        if option_triangulate:
+            print("\nForcing geometry to triangles")
+            triangulate_scene(scene)
+
+        if option_geometry:
+            output_content = extract_geometry(scene, os.path.basename(args[0]))
+        else:
+            output_content = extract_scene(scene, os.path.basename(args[0]))
+
+        output_path = os.path.join(os.getcwd(), args[1])
+        write_file(output_path, output_content)
+
+        print("\nExported Three.js file to:\n%s\n" % output_path)
+
+    # Destroy all objects created by the FBX SDK.
+    sdk_manager.Destroy()
+    sys.exit(0)
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/fbx/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/convert_obj_three.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/convert_obj_three.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/convert_obj_three.py	(revision 28897)
@@ -0,0 +1,1583 @@
+"""Convert Wavefront OBJ / MTL files into Three.js (JSON model version, to be used with ascii / binary loader)
+
+-------------------------
+How to use this converter
+-------------------------
+
+python convert_obj_three.py -i infile.obj -o outfile.js [-m "morphfiles*.obj"] [-c "morphcolors*.obj"] [-a center|centerxz|top|bottom|none] [-s smooth|flat] [-t ascii|binary] [-d invert|normal] [-b] [-e]
+
+Notes:
+    - flags
+        -i infile.obj			input OBJ file
+        -o outfile.js			output JS file
+        -m "morphfiles*.obj"	morph OBJ files (can use wildcards, enclosed in quotes multiple patterns separate by space)
+        -c "morphcolors*.obj"	morph colors OBJ files (can use wildcards, enclosed in quotes multiple patterns separate by space)
+        -a center|centerxz|top|bottom|none model alignment
+        -s smooth|flat			smooth = export vertex normals, flat = no normals (face normals computed in loader)
+        -t ascii|binary			export ascii or binary format (ascii has more features, binary just supports vertices, faces, normals, uvs and materials)
+        -d invert|normal		invert transparency
+        -b						bake material colors into face colors
+        -x 10.0                 scale and truncate
+        -f 2                    morph frame sampling step
+
+    - by default:
+        use smooth shading (if there were vertex normals in the original model)
+        will be in ASCII format
+        original model is assumed to use non-inverted transparency / dissolve (0.0 fully transparent, 1.0 fully opaque)
+        no face colors baking
+        no scale and truncate
+        morph frame step = 1 (all files will be processed)
+
+    - binary conversion will create two files:
+        outfile.js  (materials)
+        outfile.bin (binary buffers)
+
+--------------------------------------------------
+How to use generated JS file in your HTML document
+--------------------------------------------------
+
+    <script type="text/javascript" src="Three.js"></script>
+
+    ...
+
+    <script type="text/javascript">
+        ...
+
+        // load ascii model
+
+        var jsonLoader = new THREE.JSONLoader();
+        jsonLoader.load( "Model_ascii.js", createScene );
+
+        // load binary model
+
+        var binLoader = new THREE.BinaryLoader();
+        binLoader.load( "Model_bin.js", createScene );
+
+        function createScene( geometry, materials ) {
+
+            var mesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial( materials ) );
+
+        }
+
+        ...
+    </script>
+
+-------------------------------------
+Parsers based on formats descriptions
+-------------------------------------
+
+    http://en.wikipedia.org/wiki/Obj
+    http://en.wikipedia.org/wiki/Material_Template_Library
+
+-------------------
+Current limitations
+-------------------
+
+    - for the moment, only diffuse color and texture are used
+      (will need to extend shaders / renderers / materials in Three)
+
+    - texture coordinates can be wrong in canvas renderer
+      (there is crude normalization, but it doesn't
+       work for all cases)
+
+    - smoothing can be turned on/off only for the whole mesh
+
+----------------------------------------------
+How to get proper OBJ + MTL files with Blender
+----------------------------------------------
+
+    0. Remove default cube (press DEL and ENTER)
+
+    1. Import / create model
+
+    2. Select all meshes (Select -> Select All by Type -> Mesh)
+
+    3. Export to OBJ (File -> Export -> Wavefront .obj)
+        - enable following options in exporter
+            Material Groups
+            Rotate X90
+            Apply Modifiers
+            High Quality Normals
+            Copy Images
+            Selection Only
+            Objects as OBJ Objects
+            UVs
+            Normals
+            Materials
+
+        - select empty folder
+        - give your exported file name with "obj" extension
+        - click on "Export OBJ" button
+
+    4. Your model is now all files in this folder (OBJ, MTL, number of images)
+        - this converter assumes all files staying in the same folder,
+          (OBJ / MTL files use relative paths)
+
+        - for WebGL, textures must be power of 2 sized
+
+------
+Author
+------
+AlteredQualia http://alteredqualia.com
+
+"""
+
+import fileinput
+import operator
+import random
+import os.path
+import getopt
+import sys
+import struct
+import math
+import glob
+
+# #####################################################
+# Configuration
+# #####################################################
+ALIGN = "none"        	# center centerxz bottom top none
+SHADING = "smooth"      # smooth flat
+TYPE = "ascii"          # ascii binary
+TRANSPARENCY = "normal" # normal invert
+
+TRUNCATE = False
+SCALE = 1.0
+
+FRAMESTEP = 1
+
+BAKE_COLORS = False
+
+# default colors for debugging (each material gets one distinct color):
+# white, red, green, blue, yellow, cyan, magenta
+COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]
+
+# #####################################################
+# Templates
+# #####################################################
+TEMPLATE_FILE_ASCII = u"""\
+{
+
+    "metadata" :
+    {
+        "formatVersion" : 3.1,
+        "sourceFile"    : "%(fname)s",
+        "generatedBy"   : "OBJConverter",
+        "vertices"      : %(nvertex)d,
+        "faces"         : %(nface)d,
+        "normals"       : %(nnormal)d,
+        "colors"        : %(ncolor)d,
+        "uvs"           : %(nuv)d,
+        "materials"     : %(nmaterial)d
+    },
+
+    "scale" : %(scale)f,
+
+    "materials": [%(materials)s],
+
+    "vertices": [%(vertices)s],
+
+    "morphTargets": [%(morphTargets)s],
+
+    "morphColors": [%(morphColors)s],
+
+    "normals": [%(normals)s],
+
+    "colors": [%(colors)s],
+
+    "uvs": [[%(uvs)s]],
+
+    "faces": [%(faces)s]
+
+}
+"""
+
+TEMPLATE_FILE_BIN = u"""\
+{
+
+    "metadata" :
+    {
+        "formatVersion" : 3.1,
+        "sourceFile"    : "%(fname)s",
+        "generatedBy"   : "OBJConverter",
+        "vertices"      : %(nvertex)d,
+        "faces"         : %(nface)d,
+        "normals"       : %(nnormal)d,
+        "uvs"           : %(nuv)d,
+        "materials"     : %(nmaterial)d
+    },
+
+    "materials": [%(materials)s],
+
+    "buffers": "%(buffers)s"
+
+}
+"""
+
+TEMPLATE_VERTEX = "%f,%f,%f"
+TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"
+
+TEMPLATE_N = "%.5g,%.5g,%.5g"
+TEMPLATE_UV = "%.5g,%.5g"
+TEMPLATE_COLOR = "%.3g,%.3g,%.3g"
+TEMPLATE_COLOR_DEC = "%d"
+
+TEMPLATE_MORPH_VERTICES = '\t{ "name": "%s", "vertices": [%s] }'
+TEMPLATE_MORPH_COLORS   = '\t{ "name": "%s", "colors": [%s] }'
+
+# #####################################################
+# Utils
+# #####################################################
+def file_exists(filename):
+    """Return true if file exists and is accessible for reading.
+
+    Should be safer than just testing for existence due to links and
+    permissions magic on Unix filesystems.
+
+    @rtype: boolean
+    """
+
+    try:
+        f = open(filename, 'r')
+        f.close()
+        return True
+    except IOError:
+        return False
+
+
+def get_name(fname):
+    """Create model name based of filename ("path/fname.js" -> "fname").
+    """
+
+    return os.path.splitext(os.path.basename(fname))[0]
+
+def bbox(vertices):
+    """Compute bounding box of vertex array.
+    """
+
+    if len(vertices)>0:
+        minx = maxx = vertices[0][0]
+        miny = maxy = vertices[0][1]
+        minz = maxz = vertices[0][2]
+
+        for v in vertices[1:]:
+            if v[0]<minx:
+                minx = v[0]
+            elif v[0]>maxx:
+                maxx = v[0]
+
+            if v[1]<miny:
+                miny = v[1]
+            elif v[1]>maxy:
+                maxy = v[1]
+
+            if v[2]<minz:
+                minz = v[2]
+            elif v[2]>maxz:
+                maxz = v[2]
+
+        return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
+
+    else:
+        return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
+
+def translate(vertices, t):
+    """Translate array of vertices by vector t.
+    """
+
+    for i in xrange(len(vertices)):
+        vertices[i][0] += t[0]
+        vertices[i][1] += t[1]
+        vertices[i][2] += t[2]
+
+def center(vertices):
+    """Center model (middle of bounding box).
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def top(vertices):
+    """Align top of the model with the floor (Y-axis) and center it around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][1]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def bottom(vertices):
+    """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def centerxz(vertices):
+    """Center model around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = 0
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+def normalize(v):
+    """Normalize 3d vector"""
+
+    l = math.sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2])
+    if l:
+        v[0] /= l
+        v[1] /= l
+        v[2] /= l
+
+def veckey3(v):
+    return round(v[0], 6), round(v[1], 6), round(v[2], 6)
+
+# #####################################################
+# MTL parser
+# #####################################################
+def texture_relative_path(fullpath):
+    texture_file = os.path.basename(fullpath.replace("\\", "/"))
+    return texture_file
+
+def parse_mtl(fname):
+    """Parse MTL file.
+    """
+
+    materials = {}
+
+    for line in fileinput.input(fname):
+        chunks = line.split()
+        if len(chunks) > 0:
+
+            # Material start
+            # newmtl identifier
+            if chunks[0] == "newmtl":
+                if len(chunks) > 1:
+                    identifier = chunks[1]
+                else:
+                    identifier = ""
+                if not identifier in materials:
+                    materials[identifier] = {}
+
+            # Diffuse color
+            # Kd 1.000 1.000 1.000
+            if chunks[0] == "Kd" and len(chunks) == 4:
+                materials[identifier]["colorDiffuse"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
+
+            # Ambient color
+            # Ka 1.000 1.000 1.000
+            if chunks[0] == "Ka" and len(chunks) == 4:
+                materials[identifier]["colorAmbient"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
+
+            # Specular color
+            # Ks 1.000 1.000 1.000
+            if chunks[0] == "Ks" and len(chunks) == 4:
+                materials[identifier]["colorSpecular"] = [float(chunks[1]), float(chunks[2]), float(chunks[3])]
+
+            # Specular coefficient
+            # Ns 154.000
+            if chunks[0] == "Ns" and len(chunks) == 2:
+                materials[identifier]["specularCoef"] = float(chunks[1])
+
+            # Transparency
+            # Tr 0.9 or d 0.9
+            if (chunks[0] == "Tr" or chunks[0] == "d") and len(chunks) == 2:
+                if TRANSPARENCY == "invert":
+                    materials[identifier]["transparency"] = 1.0 - float(chunks[1])
+                else:
+                    materials[identifier]["transparency"] = float(chunks[1])
+
+            # Optical density
+            # Ni 1.0
+            if chunks[0] == "Ni" and len(chunks) == 2:
+                materials[identifier]["opticalDensity"] = float(chunks[1])
+
+            # Diffuse texture
+            # map_Kd texture_diffuse.jpg
+            if chunks[0] == "map_Kd" and len(chunks) == 2:
+                materials[identifier]["mapDiffuse"] = texture_relative_path(chunks[1])
+
+            # Ambient texture
+            # map_Ka texture_ambient.jpg
+            if chunks[0] == "map_Ka" and len(chunks) == 2:
+                materials[identifier]["mapAmbient"] = texture_relative_path(chunks[1])
+
+            # Specular texture
+            # map_Ks texture_specular.jpg
+            if chunks[0] == "map_Ks" and len(chunks) == 2:
+                materials[identifier]["mapSpecular"] = texture_relative_path(chunks[1])
+
+            # Alpha texture
+            # map_d texture_alpha.png
+            if chunks[0] == "map_d" and len(chunks) == 2:
+                materials[identifier]["mapAlpha"] = texture_relative_path(chunks[1])
+
+            # Bump texture
+            # map_bump texture_bump.jpg or bump texture_bump.jpg
+            if (chunks[0] == "map_bump" or chunks[0] == "bump") and len(chunks) == 2:
+                materials[identifier]["mapBump"] = texture_relative_path(chunks[1])
+
+            # Illumination
+            # illum 2
+            #
+            # 0. Color on and Ambient off
+            # 1. Color on and Ambient on
+            # 2. Highlight on
+            # 3. Reflection on and Ray trace on
+            # 4. Transparency: Glass on, Reflection: Ray trace on
+            # 5. Reflection: Fresnel on and Ray trace on
+            # 6. Transparency: Refraction on, Reflection: Fresnel off and Ray trace on
+            # 7. Transparency: Refraction on, Reflection: Fresnel on and Ray trace on
+            # 8. Reflection on and Ray trace off
+            # 9. Transparency: Glass on, Reflection: Ray trace off
+            # 10. Casts shadows onto invisible surfaces
+            if chunks[0] == "illum" and len(chunks) == 2:
+                materials[identifier]["illumination"] = int(chunks[1])
+
+    return materials
+
+# #####################################################
+# OBJ parser
+# #####################################################
+def parse_vertex(text):
+    """Parse text chunk specifying single vertex.
+
+    Possible formats:
+        vertex index
+        vertex index / texture index
+        vertex index / texture index / normal index
+        vertex index / / normal index
+    """
+
+    v = 0
+    t = 0
+    n = 0
+
+    chunks = text.split("/")
+
+    v = int(chunks[0])
+    if len(chunks) > 1:
+        if chunks[1]:
+            t = int(chunks[1])
+    if len(chunks) > 2:
+        if chunks[2]:
+            n = int(chunks[2])
+
+    return { 'v':v, 't':t, 'n':n }
+
+def parse_obj(fname):
+    """Parse OBJ file.
+    """
+
+    vertices = []
+    normals = []
+    uvs = []
+
+    faces = []
+
+    materials = {}
+    material = ""
+    mcounter = 0
+    mcurrent = 0
+
+    mtllib = ""
+
+    # current face state
+    group = 0
+    object = 0
+    smooth = 0
+
+    for line in fileinput.input(fname):
+        chunks = line.split()
+        if len(chunks) > 0:
+
+            # Vertices as (x,y,z) coordinates
+            # v 0.123 0.234 0.345
+            if chunks[0] == "v" and len(chunks) == 4:
+                x = float(chunks[1])
+                y = float(chunks[2])
+                z = float(chunks[3])
+                vertices.append([x,y,z])
+
+            # Normals in (x,y,z) form; normals might not be unit
+            # vn 0.707 0.000 0.707
+            if chunks[0] == "vn" and len(chunks) == 4:
+                x = float(chunks[1])
+                y = float(chunks[2])
+                z = float(chunks[3])
+                normals.append([x,y,z])
+
+            # Texture coordinates in (u,v[,w]) coordinates, w is optional
+            # vt 0.500 -1.352 [0.234]
+            if chunks[0] == "vt" and len(chunks) >= 3:
+                u = float(chunks[1])
+                v = float(chunks[2])
+                w = 0
+                if len(chunks)>3:
+                    w = float(chunks[3])
+                uvs.append([u,v,w])
+
+            # Face
+            if chunks[0] == "f" and len(chunks) >= 4:
+                vertex_index = []
+                uv_index = []
+                normal_index = []
+
+
+                # Precompute vert / normal / uv lists
+                # for negative index lookup
+                vertlen = len(vertices) + 1
+                normlen = len(normals) + 1
+                uvlen = len(uvs) + 1
+
+                for v in chunks[1:]:
+                    vertex = parse_vertex(v)
+                    if vertex['v']:
+                        if vertex['v'] < 0:
+                            vertex['v'] += vertlen
+                        vertex_index.append(vertex['v'])
+                    if vertex['t']:
+                        if vertex['t'] < 0:
+                            vertex['t'] += uvlen
+                        uv_index.append(vertex['t'])
+                    if vertex['n']:
+                        if vertex['n'] < 0:
+                            vertex['n'] += normlen
+                        normal_index.append(vertex['n'])
+                faces.append({
+                    'vertex':vertex_index,
+                    'uv':uv_index,
+                    'normal':normal_index,
+
+                    'material':mcurrent,
+                    'group':group,
+                    'object':object,
+                    'smooth':smooth,
+                    })
+
+            # Group
+            if chunks[0] == "g" and len(chunks) == 2:
+                group = chunks[1]
+
+            # Object
+            if chunks[0] == "o" and len(chunks) == 2:
+                object = chunks[1]
+
+            # Materials definition
+            if chunks[0] == "mtllib" and len(chunks) == 2:
+                mtllib = chunks[1]
+
+            # Material
+            if chunks[0] == "usemtl":
+                if len(chunks) > 1:
+                    material = chunks[1]
+                else:
+                    material = ""
+                if not material in materials:
+                    mcurrent = mcounter
+                    materials[material] = mcounter
+                    mcounter += 1
+                else:
+                    mcurrent = materials[material]
+
+            # Smooth shading
+            if chunks[0] == "s" and len(chunks) == 2:
+                smooth = chunks[1]
+
+    return faces, vertices, uvs, normals, materials, mtllib
+
+# #####################################################
+# Generator - faces
+# #####################################################
+def setBit(value, position, on):
+    if on:
+        mask = 1 << position
+        return (value | mask)
+    else:
+        mask = ~(1 << position)
+        return (value & mask)
+
+def generate_face(f, fc):
+    isTriangle = ( len(f['vertex']) == 3 )
+
+    if isTriangle:
+        nVertices = 3
+    else:
+        nVertices = 4
+
+    hasMaterial = True # for the moment OBJs without materials get default material
+
+    hasFaceUvs = False # not supported in OBJ
+    hasFaceVertexUvs = ( len(f['uv']) >= nVertices )
+
+    hasFaceNormals = False # don't export any face normals (as they are computed in engine)
+    hasFaceVertexNormals = ( len(f["normal"]) >= nVertices and SHADING == "smooth" )
+
+    hasFaceColors = BAKE_COLORS
+    hasFaceVertexColors = False # not supported in OBJ
+
+    faceType = 0
+    faceType = setBit(faceType, 0, not isTriangle)
+    faceType = setBit(faceType, 1, hasMaterial)
+    faceType = setBit(faceType, 2, hasFaceUvs)
+    faceType = setBit(faceType, 3, hasFaceVertexUvs)
+    faceType = setBit(faceType, 4, hasFaceNormals)
+    faceType = setBit(faceType, 5, hasFaceVertexNormals)
+    faceType = setBit(faceType, 6, hasFaceColors)
+    faceType = setBit(faceType, 7, hasFaceVertexColors)
+
+    faceData = []
+
+    # order is important, must match order in JSONLoader
+
+    # face type
+    # vertex indices
+    # material index
+    # face uvs index
+    # face vertex uvs indices
+    # face normal index
+    # face vertex normals indices
+    # face color index
+    # face vertex colors indices
+
+    faceData.append(faceType)
+
+    # must clamp in case on polygons bigger than quads
+
+    for i in xrange(nVertices):
+        index = f['vertex'][i] - 1
+        faceData.append(index)
+
+    faceData.append( f['material'] )
+
+    if hasFaceVertexUvs:
+        for i in xrange(nVertices):
+            index = f['uv'][i] - 1
+            faceData.append(index)
+
+    if hasFaceVertexNormals:
+        for i in xrange(nVertices):
+            index = f['normal'][i] - 1
+            faceData.append(index)
+
+    if hasFaceColors:
+        index = fc['material']
+        faceData.append(index)
+
+    return ",".join( map(str, faceData) )
+
+# #####################################################
+# Generator - chunks
+# #####################################################
+def hexcolor(c):
+    return ( int(c[0] * 255) << 16  ) + ( int(c[1] * 255) << 8 ) + int(c[2] * 255)
+
+def generate_vertex(v, option_vertices_truncate, scale):
+    if not option_vertices_truncate:
+        return TEMPLATE_VERTEX % (v[0], v[1], v[2])
+    else:
+        return TEMPLATE_VERTEX_TRUNCATE % (scale * v[0], scale * v[1], scale * v[2])
+
+def generate_normal(n):
+    return TEMPLATE_N % (n[0], n[1], n[2])
+
+def generate_uv(uv):
+    return TEMPLATE_UV % (uv[0], uv[1])
+
+def generate_color_rgb(c):
+    return TEMPLATE_COLOR % (c[0], c[1], c[2])
+
+def generate_color_decimal(c):
+    return TEMPLATE_COLOR_DEC % hexcolor(c)
+
+# #####################################################
+# Morphs
+# #####################################################
+def generate_morph_vertex(name, vertices):
+    vertex_string = ",".join(generate_vertex(v, TRUNCATE, SCALE) for v in vertices)
+    return TEMPLATE_MORPH_VERTICES % (name, vertex_string)
+
+def generate_morph_color(name, colors):
+    color_string = ",".join(generate_color_rgb(c) for c in colors)
+    return TEMPLATE_MORPH_COLORS % (name, color_string)
+
+def extract_material_colors(materials, mtlfilename, basename):
+    """Extract diffuse colors from MTL materials
+    """
+
+    if not materials:
+        materials = { 'default': 0 }
+
+    mtl = create_materials(materials, mtlfilename, basename)
+
+    mtlColorArraySrt = []
+    for m in mtl:
+        if m in materials:
+            index = materials[m]
+            color = mtl[m].get("colorDiffuse", [1,0,0])
+            mtlColorArraySrt.append([index, color])
+
+    mtlColorArraySrt.sort()
+    mtlColorArray = [x[1] for x in mtlColorArraySrt]
+
+    return mtlColorArray
+
+def extract_face_colors(faces, material_colors):
+    """Extract colors from materials and assign them to faces
+    """
+
+    faceColors = []
+
+    for face in faces:
+        material_index = face['material']
+        faceColors.append(material_colors[material_index])
+
+    return faceColors
+
+def generate_morph_targets(morphfiles, n_vertices, infile):
+    skipOriginalMorph = False
+    norminfile = os.path.normpath(infile)
+
+    morphVertexData = []
+
+    for mfilepattern in morphfiles.split():
+
+        matches = glob.glob(mfilepattern)
+        matches.sort()
+
+        indices = range(0, len(matches), FRAMESTEP)
+        for i in indices:
+            path = matches[i]
+
+            normpath = os.path.normpath(path)
+
+            if normpath != norminfile or not skipOriginalMorph:
+
+                name = os.path.basename(normpath)
+
+                morphFaces, morphVertices, morphUvs, morphNormals, morphMaterials, morphMtllib = parse_obj(normpath)
+
+                n_morph_vertices = len(morphVertices)
+
+                if n_vertices != n_morph_vertices:
+
+                    print "WARNING: skipping morph [%s] with different number of vertices [%d] than the original model [%d]" % (name, n_morph_vertices, n_vertices)
+
+                else:
+
+                    if ALIGN == "center":
+                        center(morphVertices)
+                    elif ALIGN == "centerxz":
+                        centerxz(morphVertices)
+                    elif ALIGN == "bottom":
+                        bottom(morphVertices)
+                    elif ALIGN == "top":
+                        top(morphVertices)
+
+                    morphVertexData.append((get_name(name), morphVertices))
+                    print "adding [%s] with %d vertices" % (name, n_morph_vertices)
+
+    morphTargets = ""
+    if len(morphVertexData):
+        morphTargets = "\n%s\n\t" % ",\n".join(generate_morph_vertex(name, vertices) for name, vertices in morphVertexData)
+
+    return morphTargets
+
+def generate_morph_colors(colorfiles, n_vertices, n_faces):
+    morphColorData = []
+    colorFaces = []
+    materialColors = []
+
+    for mfilepattern in colorfiles.split():
+
+        matches = glob.glob(mfilepattern)
+        matches.sort()
+        for path in matches:
+            normpath = os.path.normpath(path)
+            name = os.path.basename(normpath)
+
+            morphFaces, morphVertices, morphUvs, morphNormals, morphMaterials, morphMtllib = parse_obj(normpath)
+
+            n_morph_vertices = len(morphVertices)
+            n_morph_faces = len(morphFaces)
+
+            if n_vertices != n_morph_vertices:
+
+                print "WARNING: skipping morph color map [%s] with different number of vertices [%d] than the original model [%d]" % (name, n_morph_vertices, n_vertices)
+
+            elif n_faces != n_morph_faces:
+
+                print "WARNING: skipping morph color map [%s] with different number of faces [%d] than the original model [%d]" % (name, n_morph_faces, n_faces)
+
+            else:
+
+                morphMaterialColors = extract_material_colors(morphMaterials, morphMtllib, normpath)
+                morphFaceColors = extract_face_colors(morphFaces, morphMaterialColors)
+                morphColorData.append((get_name(name), morphFaceColors))
+
+                # take first color map for baking into face colors
+
+                if len(colorFaces) == 0:
+                    colorFaces = morphFaces
+                    materialColors = morphMaterialColors
+
+                print "adding [%s] with %d face colors" % (name, len(morphFaceColors))
+
+    morphColors = ""
+    if len(morphColorData):
+        morphColors = "\n%s\n\t" % ",\n".join(generate_morph_color(name, colors) for name, colors in morphColorData)
+
+    return morphColors, colorFaces, materialColors
+
+# #####################################################
+# Materials
+# #####################################################
+def generate_color(i):
+    """Generate hex color corresponding to integer.
+
+    Colors should have well defined ordering.
+    First N colors are hardcoded, then colors are random
+    (must seed random number  generator with deterministic value
+    before getting colors).
+    """
+
+    if i < len(COLORS):
+        #return "0x%06x" % COLORS[i]
+        return COLORS[i]
+    else:
+        #return "0x%06x" % int(0xffffff * random.random())
+        return int(0xffffff * random.random())
+
+def value2string(v):
+    if type(v)==str and v[0:2] != "0x":
+        return '"%s"' % v
+    elif type(v) == bool:
+        return str(v).lower()
+    return str(v)
+
+def generate_materials(mtl, materials):
+    """Generate JS array of materials objects
+
+    JS material objects are basically prettified one-to-one
+    mappings of MTL properties in JSON format.
+    """
+
+    mtl_array = []
+    for m in mtl:
+        if m in materials:
+            index = materials[m]
+
+            # add debug information
+            #  materials should be sorted according to how
+            #  they appeared in OBJ file (for the first time)
+            #  this index is identifier used in face definitions
+            mtl[m]['DbgName'] = m
+            mtl[m]['DbgIndex'] = index
+            mtl[m]['DbgColor'] = generate_color(index)
+
+            if BAKE_COLORS:
+                mtl[m]['vertexColors'] = "face"
+
+            mtl_raw = ",\n".join(['\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
+            mtl_string = "\t{\n%s\n\t}" % mtl_raw
+            mtl_array.append([index, mtl_string])
+
+    return ",\n\n".join([m for i,m in sorted(mtl_array)])
+
+def generate_mtl(materials):
+    """Generate dummy materials (if there is no MTL file).
+    """
+
+    mtl = {}
+    for m in materials:
+        index = materials[m]
+        mtl[m] = {
+            'DbgName': m,
+            'DbgIndex': index,
+            'DbgColor': generate_color(index)
+        }
+    return mtl
+
+def generate_materials_string(materials, mtlfilename, basename):
+    """Generate final materials string.
+    """
+
+    if not materials:
+        materials = { 'default': 0 }
+
+    mtl = create_materials(materials, mtlfilename, basename)
+    return generate_materials(mtl, materials)
+
+def create_materials(materials, mtlfilename, basename):
+    """Parse MTL file and create mapping between its materials and OBJ materials.
+       Eventual edge cases are handled here (missing materials, missing MTL file).
+    """
+
+    random.seed(42) # to get well defined color order for debug colors
+
+    # default materials with debug colors for when
+    # there is no specified MTL / MTL loading failed,
+    # or if there were no materials / null materials
+
+    mtl = generate_mtl(materials)
+
+    if mtlfilename:
+
+        # create full pathname for MTL (included from OBJ)
+
+        path = os.path.dirname(basename)
+        fname = os.path.join(path, mtlfilename)
+
+        if file_exists(fname):
+
+            # override default materials with real ones from MTL
+            # (where they exist, otherwise keep defaults)
+
+            mtl.update(parse_mtl(fname))
+
+        else:
+
+            print "Couldn't find [%s]" % fname
+
+    return mtl
+
+# #####################################################
+# Faces
+# #####################################################
+def is_triangle_flat(f):
+    return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and not f['uv']
+
+def is_triangle_flat_uv(f):
+    return len(f['vertex'])==3 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==3
+
+def is_triangle_smooth(f):
+    return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and not f['uv']
+
+def is_triangle_smooth_uv(f):
+    return len(f['vertex'])==3 and f["normal"] and SHADING == "smooth" and len(f['uv'])==3
+
+def is_quad_flat(f):
+    return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and not f['uv']
+
+def is_quad_flat_uv(f):
+    return len(f['vertex'])==4 and not (f["normal"] and SHADING == "smooth") and len(f['uv'])==4
+
+def is_quad_smooth(f):
+    return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and not f['uv']
+
+def is_quad_smooth_uv(f):
+    return len(f['vertex'])==4 and f["normal"] and SHADING == "smooth" and len(f['uv'])==4
+
+def sort_faces(faces):
+    data = {
+    'triangles_flat': [],
+    'triangles_flat_uv': [],
+    'triangles_smooth': [],
+    'triangles_smooth_uv': [],
+
+    'quads_flat': [],
+    'quads_flat_uv': [],
+    'quads_smooth': [],
+    'quads_smooth_uv': []
+    }
+
+    for f in faces:
+        if is_triangle_flat(f):
+            data['triangles_flat'].append(f)
+        elif is_triangle_flat_uv(f):
+            data['triangles_flat_uv'].append(f)
+        elif is_triangle_smooth(f):
+            data['triangles_smooth'].append(f)
+        elif is_triangle_smooth_uv(f):
+            data['triangles_smooth_uv'].append(f)
+
+        elif is_quad_flat(f):
+            data['quads_flat'].append(f)
+        elif is_quad_flat_uv(f):
+            data['quads_flat_uv'].append(f)
+        elif is_quad_smooth(f):
+            data['quads_smooth'].append(f)
+        elif is_quad_smooth_uv(f):
+            data['quads_smooth_uv'].append(f)
+
+    return data
+
+# #####################################################
+# API - ASCII converter
+# #####################################################
+def convert_ascii(infile, morphfiles, colorfiles, outfile):
+    """Convert infile.obj to outfile.js
+
+    Here is where everything happens. If you need to automate conversions,
+    just import this file as Python module and call this method.
+    """
+
+    if not file_exists(infile):
+        print "Couldn't find [%s]" % infile
+        return
+
+    # parse OBJ / MTL files
+
+    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
+
+    n_vertices = len(vertices)
+    n_faces = len(faces)
+
+    # align model
+
+    if ALIGN == "center":
+        center(vertices)
+    elif ALIGN == "centerxz":
+        centerxz(vertices)
+    elif ALIGN == "bottom":
+        bottom(vertices)
+    elif ALIGN == "top":
+        top(vertices)
+
+    # generate normals string
+
+    nnormal = 0
+    normals_string = ""
+    if SHADING == "smooth":
+        normals_string = ",".join(generate_normal(n) for n in normals)
+        nnormal = len(normals)
+
+    # extract morph vertices
+
+    morphTargets = generate_morph_targets(morphfiles, n_vertices, infile)
+
+    # extract morph colors
+
+    morphColors, colorFaces, materialColors = generate_morph_colors(colorfiles, n_vertices, n_faces)
+
+    # generate colors string
+
+    ncolor = 0
+    colors_string = ""
+
+    if len(colorFaces) < len(faces):
+        colorFaces = faces
+        materialColors = extract_material_colors(materials, mtllib, infile)
+
+    if BAKE_COLORS:
+        colors_string = ",".join(generate_color_decimal(c) for c in materialColors)
+        ncolor = len(materialColors)
+
+    # generate ascii model string
+
+    text = TEMPLATE_FILE_ASCII % {
+    "name"      : get_name(outfile),
+    "fname"     : os.path.basename(infile),
+    "nvertex"   : len(vertices),
+    "nface"     : len(faces),
+    "nuv"       : len(uvs),
+    "nnormal"   : nnormal,
+    "ncolor"    : ncolor,
+    "nmaterial" : len(materials),
+
+    "materials" : generate_materials_string(materials, mtllib, infile),
+
+    "normals"       : normals_string,
+    "colors"        : colors_string,
+    "uvs"           : ",".join(generate_uv(uv) for uv in uvs),
+    "vertices"      : ",".join(generate_vertex(v, TRUNCATE, SCALE) for v in vertices),
+
+    "morphTargets"  : morphTargets,
+    "morphColors"   : morphColors,
+
+    "faces"     : ",".join(generate_face(f, fc) for f, fc in zip(faces, colorFaces)),
+
+    "scale"    : SCALE
+    }
+
+    out = open(outfile, "w")
+    out.write(text)
+    out.close()
+
+    print "%d vertices, %d faces, %d materials" % (len(vertices), len(faces), len(materials))
+
+
+# #############################################################################
+# API - Binary converter
+# #############################################################################
+def dump_materials_to_buffer(faces, buffer):
+    for f in faces:
+        data = struct.pack('<H',
+                            f['material'])
+        buffer.append(data)
+
+def dump_vertices3_to_buffer(faces, buffer):
+    for f in faces:
+        vi = f['vertex']
+        data = struct.pack('<III',
+                            vi[0]-1, vi[1]-1, vi[2]-1)
+        buffer.append(data)
+
+def dump_vertices4_to_buffer(faces, buffer):
+    for f in faces:
+        vi = f['vertex']
+        data = struct.pack('<IIII',
+                            vi[0]-1, vi[1]-1, vi[2]-1, vi[3]-1)
+        buffer.append(data)
+
+def dump_normals3_to_buffer(faces, buffer):
+    for f in faces:
+        ni = f['normal']
+        data = struct.pack('<III',
+                            ni[0]-1, ni[1]-1, ni[2]-1)
+        buffer.append(data)
+
+def dump_normals4_to_buffer(faces, buffer):
+    for f in faces:
+        ni = f['normal']
+        data = struct.pack('<IIII',
+                            ni[0]-1, ni[1]-1, ni[2]-1, ni[3]-1)
+        buffer.append(data)
+
+def dump_uvs3_to_buffer(faces, buffer):
+    for f in faces:
+        ui = f['uv']
+        data = struct.pack('<III',
+                            ui[0]-1, ui[1]-1, ui[2]-1)
+        buffer.append(data)
+
+def dump_uvs4_to_buffer(faces, buffer):
+    for f in faces:
+        ui = f['uv']
+        data = struct.pack('<IIII',
+                            ui[0]-1, ui[1]-1, ui[2]-1, ui[3]-1)
+        buffer.append(data)
+
+def add_padding(buffer, n):
+    if n % 4:
+        for i in range(4 - n % 4):
+            data = struct.pack('<B', 0)
+            buffer.append(data)
+
+def convert_binary(infile, outfile):
+    """Convert infile.obj to outfile.js + outfile.bin
+    """
+
+    if not file_exists(infile):
+        print "Couldn't find [%s]" % infile
+        return
+
+    binfile = get_name(outfile) + ".bin"
+
+    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
+
+    if ALIGN == "center":
+        center(vertices)
+    elif ALIGN == "centerxz":
+        centerxz(vertices)
+    elif ALIGN == "bottom":
+        bottom(vertices)
+    elif ALIGN == "top":
+        top(vertices)
+
+    sfaces = sort_faces(faces)
+
+    if SHADING == "smooth":
+        nnormals = len(normals)
+    else:
+        nnormals = 0
+
+    # ###################
+    # generate JS file
+    # ###################
+
+    text = TEMPLATE_FILE_BIN % {
+    "name"       : get_name(outfile),
+
+    "materials" : generate_materials_string(materials, mtllib, infile),
+    "buffers"   : binfile,
+
+    "fname"     : os.path.basename(infile),
+    "nvertex"   : len(vertices),
+    "nface"     : len(faces),
+    "nmaterial" : len(materials),
+    "nnormal"   : nnormals,
+    "nuv"       : len(uvs)
+    }
+
+    out = open(outfile, "w")
+    out.write(text)
+    out.close()
+
+    # ###################
+    # generate BIN file
+    # ###################
+
+    buffer = []
+
+    # header
+    # ------
+    header_bytes  = struct.calcsize('<12s')
+    header_bytes += struct.calcsize('<BBBBBBBB')
+    header_bytes += struct.calcsize('<IIIIIIIIIII')
+
+    # signature
+    signature = struct.pack('<12s', 'Three.js 003')
+
+    # metadata (all data is little-endian)
+    vertex_coordinate_bytes = 4
+    normal_coordinate_bytes = 1
+    uv_coordinate_bytes = 4
+
+    vertex_index_bytes = 4
+    normal_index_bytes = 4
+    uv_index_bytes = 4
+    material_index_bytes = 2
+
+    # header_bytes            unsigned char   1
+
+    # vertex_coordinate_bytes unsigned char   1
+    # normal_coordinate_bytes unsigned char   1
+    # uv_coordinate_bytes     unsigned char   1
+
+    # vertex_index_bytes      unsigned char   1
+    # normal_index_bytes      unsigned char   1
+    # uv_index_bytes          unsigned char   1
+    # material_index_bytes    unsigned char   1
+    bdata = struct.pack('<BBBBBBBB', header_bytes,
+                               vertex_coordinate_bytes,
+                               normal_coordinate_bytes,
+                               uv_coordinate_bytes,
+                               vertex_index_bytes,
+                               normal_index_bytes,
+                               uv_index_bytes,
+                               material_index_bytes)
+
+    ntri_flat = len(sfaces['triangles_flat'])
+    ntri_smooth = len(sfaces['triangles_smooth'])
+    ntri_flat_uv = len(sfaces['triangles_flat_uv'])
+    ntri_smooth_uv = len(sfaces['triangles_smooth_uv'])
+
+    nquad_flat = len(sfaces['quads_flat'])
+    nquad_smooth = len(sfaces['quads_smooth'])
+    nquad_flat_uv = len(sfaces['quads_flat_uv'])
+    nquad_smooth_uv = len(sfaces['quads_smooth_uv'])
+
+    # nvertices       unsigned int    4
+    # nnormals        unsigned int    4
+    # nuvs            unsigned int    4
+
+    # ntri_flat       unsigned int    4
+    # ntri_smooth     unsigned int    4
+    # ntri_flat_uv    unsigned int    4
+    # ntri_smooth_uv  unsigned int    4
+
+    # nquad_flat      unsigned int    4
+    # nquad_smooth    unsigned int    4
+    # nquad_flat_uv   unsigned int    4
+    # nquad_smooth_uv unsigned int    4
+    ndata = struct.pack('<IIIIIIIIIII', len(vertices),
+                               nnormals,
+                               len(uvs),
+                               ntri_flat,
+                               ntri_smooth,
+                               ntri_flat_uv,
+                               ntri_smooth_uv,
+                               nquad_flat,
+                               nquad_smooth,
+                               nquad_flat_uv,
+                               nquad_smooth_uv)
+    buffer.append(signature)
+    buffer.append(bdata)
+    buffer.append(ndata)
+
+    # 1. vertices
+    # ------------
+    # x float   4
+    # y float   4
+    # z float   4
+    for v in vertices:
+        data = struct.pack('<fff', v[0], v[1], v[2])
+        buffer.append(data)
+
+    # 2. normals
+    # ---------------
+    # x signed char 1
+    # y signed char 1
+    # z signed char 1
+    if SHADING == "smooth":
+        for n in normals:
+            normalize(n)
+            data = struct.pack('<bbb', math.floor(n[0]*127+0.5),
+                                       math.floor(n[1]*127+0.5),
+                                       math.floor(n[2]*127+0.5))
+            buffer.append(data)
+
+        add_padding(buffer, nnormals * 3)
+
+    # 3. uvs
+    # -----------
+    # u float   4
+    # v float   4
+    for uv in uvs:
+        data = struct.pack('<ff', uv[0], uv[1])
+        buffer.append(data)
+
+    # padding
+    #data = struct.pack('<BB', 0, 0)
+    #buffer.append(data)
+
+    # 4. flat triangles (vertices + materials)
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # ------------------
+    # m unsigned short 2
+
+    dump_vertices3_to_buffer(sfaces['triangles_flat'], buffer)
+
+    dump_materials_to_buffer(sfaces['triangles_flat'], buffer)
+    add_padding(buffer, ntri_flat * 2)
+
+    # 5. smooth triangles (vertices + materials + normals)
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # -------------------
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    # -------------------
+    # m  unsigned short 2
+
+    dump_vertices3_to_buffer(sfaces['triangles_smooth'], buffer)
+    dump_normals3_to_buffer(sfaces['triangles_smooth'], buffer)
+
+    dump_materials_to_buffer(sfaces['triangles_smooth'], buffer)
+    add_padding(buffer, ntri_smooth * 2)
+
+    # 6. flat triangles uv (vertices + materials + uvs)
+    # --------------------
+    # a  unsigned int    4
+    # b  unsigned int    4
+    # c  unsigned int    4
+    # --------------------
+    # ua unsigned int    4
+    # ub unsigned int    4
+    # uc unsigned int    4
+    # --------------------
+    # m  unsigned short  2
+
+    dump_vertices3_to_buffer(sfaces['triangles_flat_uv'], buffer)
+    dump_uvs3_to_buffer(sfaces['triangles_flat_uv'], buffer)
+
+    dump_materials_to_buffer(sfaces['triangles_flat_uv'], buffer)
+    add_padding(buffer, ntri_flat_uv * 2)
+
+    # 7. smooth triangles uv (vertices + materials + normals + uvs)
+    # ----------------------
+    # a  unsigned int    4
+    # b  unsigned int    4
+    # c  unsigned int    4
+    # --------------------
+    # na unsigned int    4
+    # nb unsigned int    4
+    # nc unsigned int    4
+    # --------------------
+    # ua unsigned int    4
+    # ub unsigned int    4
+    # uc unsigned int    4
+    # --------------------
+    # m  unsigned short  2
+
+    dump_vertices3_to_buffer(sfaces['triangles_smooth_uv'], buffer)
+    dump_normals3_to_buffer(sfaces['triangles_smooth_uv'], buffer)
+    dump_uvs3_to_buffer(sfaces['triangles_smooth_uv'], buffer)
+
+    dump_materials_to_buffer(sfaces['triangles_smooth_uv'], buffer)
+    add_padding(buffer, ntri_smooth_uv * 2)
+
+    # 8. flat quads (vertices + materials)
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # d unsigned int   4
+    # --------------------
+    # m unsigned short 2
+
+    dump_vertices4_to_buffer(sfaces['quads_flat'], buffer)
+
+    dump_materials_to_buffer(sfaces['quads_flat'], buffer)
+    add_padding(buffer, nquad_flat * 2)
+
+    # 9. smooth quads (vertices + materials + normals)
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # d  unsigned int   4
+    # --------------------
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    # nd unsigned int   4
+    # --------------------
+    # m  unsigned short 2
+
+    dump_vertices4_to_buffer(sfaces['quads_smooth'], buffer)
+    dump_normals4_to_buffer(sfaces['quads_smooth'], buffer)
+
+    dump_materials_to_buffer(sfaces['quads_smooth'], buffer)
+    add_padding(buffer, nquad_smooth * 2)
+
+    # 10. flat quads uv (vertices + materials + uvs)
+    # ------------------
+    # a unsigned int   4
+    # b unsigned int   4
+    # c unsigned int   4
+    # d unsigned int   4
+    # --------------------
+    # ua unsigned int  4
+    # ub unsigned int  4
+    # uc unsigned int  4
+    # ud unsigned int  4
+    # --------------------
+    # m unsigned short 2
+
+    dump_vertices4_to_buffer(sfaces['quads_flat_uv'], buffer)
+    dump_uvs4_to_buffer(sfaces['quads_flat_uv'], buffer)
+
+    dump_materials_to_buffer(sfaces['quads_flat_uv'], buffer)
+    add_padding(buffer, nquad_flat_uv * 2)
+
+    # 11. smooth quads uv
+    # -------------------
+    # a  unsigned int   4
+    # b  unsigned int   4
+    # c  unsigned int   4
+    # d  unsigned int   4
+    # --------------------
+    # na unsigned int   4
+    # nb unsigned int   4
+    # nc unsigned int   4
+    # nd unsigned int   4
+    # --------------------
+    # ua unsigned int   4
+    # ub unsigned int   4
+    # uc unsigned int   4
+    # ud unsigned int   4
+    # --------------------
+    # m  unsigned short 2
+
+    dump_vertices4_to_buffer(sfaces['quads_smooth_uv'], buffer)
+    dump_normals4_to_buffer(sfaces['quads_smooth_uv'], buffer)
+    dump_uvs4_to_buffer(sfaces['quads_smooth_uv'], buffer)
+
+    dump_materials_to_buffer(sfaces['quads_smooth_uv'], buffer)
+    add_padding(buffer, nquad_smooth_uv * 2)
+
+    path = os.path.dirname(outfile)
+    fname = os.path.join(path, binfile)
+
+    out = open(fname, "wb")
+    out.write("".join(buffer))
+    out.close()
+
+# #############################################################################
+# Helpers
+# #############################################################################
+def usage():
+    print "Usage: %s -i filename.obj -o filename.js [-m morphfiles*.obj] [-c morphcolors*.obj] [-a center|top|bottom] [-s flat|smooth] [-t binary|ascii] [-d invert|normal]" % os.path.basename(sys.argv[0])
+
+# #####################################################
+# Main
+# #####################################################
+if __name__ == "__main__":
+
+    # get parameters from the command line
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "hbi:m:c:b:o:a:s:t:d:x:f:", ["help", "bakecolors", "input=", "morphs=", "colors=", "output=", "align=", "shading=", "type=", "dissolve=", "truncatescale=", "framestep="])
+
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+
+    infile = outfile = ""
+    morphfiles = ""
+    colorfiles = ""
+
+    for o, a in opts:
+        if o in ("-h", "--help"):
+            usage()
+            sys.exit()
+
+        elif o in ("-i", "--input"):
+            infile = a
+
+        elif o in ("-m", "--morphs"):
+            morphfiles = a
+
+        elif o in ("-c", "--colors"):
+            colorfiles = a
+
+        elif o in ("-o", "--output"):
+            outfile = a
+
+        elif o in ("-a", "--align"):
+            if a in ("top", "bottom", "center", "centerxz", "none"):
+                ALIGN = a
+
+        elif o in ("-s", "--shading"):
+            if a in ("flat", "smooth"):
+                SHADING = a
+
+        elif o in ("-t", "--type"):
+            if a in ("binary", "ascii"):
+                TYPE = a
+
+        elif o in ("-d", "--dissolve"):
+            if a in ("normal", "invert"):
+                TRANSPARENCY = a
+
+        elif o in ("-b", "--bakecolors"):
+            BAKE_COLORS = True
+
+        elif o in ("-x", "--truncatescale"):
+            TRUNCATE = True
+            SCALE = float(a)
+
+        elif o in ("-f", "--framestep"):
+            FRAMESTEP = int(a)
+
+    if infile == "" or outfile == "":
+        usage()
+        sys.exit(2)
+
+    print "Converting [%s] into [%s] ..." % (infile, outfile)
+
+    if morphfiles:
+        print "Morphs [%s]" % morphfiles
+
+    if colorfiles:
+        print "Colors [%s]" % colorfiles
+
+    if TYPE == "ascii":
+        convert_ascii(infile, morphfiles, colorfiles, outfile)
+    elif TYPE == "binary":
+        convert_binary(infile, outfile)
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/split_obj.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/split_obj.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/obj/split_obj.py	(revision 28897)
@@ -0,0 +1,504 @@
+"""Split single OBJ model into mutliple OBJ files by materials
+
+-------------------------------------
+How to use
+-------------------------------------
+
+python split_obj.py -i infile.obj -o outfile
+
+Will generate:
+
+outfile_000.obj
+outfile_001.obj
+
+...
+
+outfile_XXX.obj
+
+-------------------------------------
+Parser based on format description
+-------------------------------------
+
+    http://en.wikipedia.org/wiki/Obj
+
+------
+Author
+------
+AlteredQualia http://alteredqualia.com
+
+"""
+
+import fileinput
+import operator
+import random
+import os.path
+import getopt
+import sys
+import struct
+import math
+import glob
+
+# #####################################################
+# Configuration
+# #####################################################
+TRUNCATE = False
+SCALE = 1.0
+
+
+# #####################################################
+# Templates
+# #####################################################
+TEMPLATE_OBJ = u"""\
+################################
+# OBJ generated by split_obj.py
+################################
+# Faces:    %(nfaces)d
+# Vertices: %(nvertices)d
+# Normals:  %(nnormals)d
+# UVs:      %(nuvs)d
+################################
+
+# vertices
+
+%(vertices)s
+
+# normals
+
+%(normals)s
+
+# uvs
+
+%(uvs)s
+
+# faces
+
+%(faces)s
+"""
+
+TEMPLATE_VERTEX = "v %f %f %f"
+TEMPLATE_VERTEX_TRUNCATE = "v %d %d %d"
+
+TEMPLATE_NORMAL = "vn %.5g %.5g %.5g"
+TEMPLATE_UV = "vt %.5g %.5g"
+
+TEMPLATE_FACE3_V = "f %d %d %d"
+TEMPLATE_FACE4_V = "f %d %d %d %d"
+
+TEMPLATE_FACE3_VT = "f %d/%d %d/%d %d/%d"
+TEMPLATE_FACE4_VT = "f %d/%d %d/%d %d/%d %d/%d"
+
+TEMPLATE_FACE3_VN = "f %d//%d %d//%d %d//%d"
+TEMPLATE_FACE4_VN = "f %d//%d %d//%d %d//%d %d//%d"
+
+TEMPLATE_FACE3_VTN = "f %d/%d/%d %d/%d/%d %d/%d/%d"
+TEMPLATE_FACE4_VTN = "f %d/%d/%d %d/%d/%d %d/%d/%d %d/%d/%d"
+
+
+# #####################################################
+# Utils
+# #####################################################
+def file_exists(filename):
+    """Return true if file exists and is accessible for reading.
+
+    Should be safer than just testing for existence due to links and
+    permissions magic on Unix filesystems.
+
+    @rtype: boolean
+    """
+
+    try:
+        f = open(filename, 'r')
+        f.close()
+        return True
+    except IOError:
+        return False
+
+# #####################################################
+# OBJ parser
+# #####################################################
+def parse_vertex(text):
+    """Parse text chunk specifying single vertex.
+
+    Possible formats:
+        vertex index
+        vertex index / texture index
+        vertex index / texture index / normal index
+        vertex index / / normal index
+    """
+
+    v = 0
+    t = 0
+    n = 0
+
+    chunks = text.split("/")
+
+    v = int(chunks[0])
+    if len(chunks) > 1:
+        if chunks[1]:
+            t = int(chunks[1])
+    if len(chunks) > 2:
+        if chunks[2]:
+            n = int(chunks[2])
+
+    return { 'v': v, 't': t, 'n': n }
+
+def parse_obj(fname):
+    """Parse OBJ file.
+    """
+
+    vertices = []
+    normals = []
+    uvs = []
+
+    faces = []
+
+    materials = {}
+    mcounter = 0
+    mcurrent = 0
+
+    mtllib = ""
+
+    # current face state
+    group = 0
+    object = 0
+    smooth = 0
+
+    for line in fileinput.input(fname):
+        chunks = line.split()
+        if len(chunks) > 0:
+
+            # Vertices as (x,y,z) coordinates
+            # v 0.123 0.234 0.345
+            if chunks[0] == "v" and len(chunks) == 4:
+                x = float(chunks[1])
+                y = float(chunks[2])
+                z = float(chunks[3])
+                vertices.append([x,y,z])
+
+            # Normals in (x,y,z) form; normals might not be unit
+            # vn 0.707 0.000 0.707
+            if chunks[0] == "vn" and len(chunks) == 4:
+                x = float(chunks[1])
+                y = float(chunks[2])
+                z = float(chunks[3])
+                normals.append([x,y,z])
+
+            # Texture coordinates in (u,v[,w]) coordinates, w is optional
+            # vt 0.500 -1.352 [0.234]
+            if chunks[0] == "vt" and len(chunks) >= 3:
+                u = float(chunks[1])
+                v = float(chunks[2])
+                w = 0
+                if len(chunks)>3:
+                    w = float(chunks[3])
+                uvs.append([u,v,w])
+
+            # Face
+            if chunks[0] == "f" and len(chunks) >= 4:
+                vertex_index = []
+                uv_index = []
+                normal_index = []
+
+                for v in chunks[1:]:
+                    vertex = parse_vertex(v)
+                    if vertex['v']:
+                        vertex_index.append(vertex['v'])
+                    if vertex['t']:
+                        uv_index.append(vertex['t'])
+                    if vertex['n']:
+                        normal_index.append(vertex['n'])
+
+                faces.append({
+                    'vertex':vertex_index,
+                    'uv':uv_index,
+                    'normal':normal_index,
+
+                    'material':mcurrent,
+                    'group':group,
+                    'object':object,
+                    'smooth':smooth,
+                    })
+
+            # Group
+            if chunks[0] == "g" and len(chunks) == 2:
+                group = chunks[1]
+
+            # Object
+            if chunks[0] == "o" and len(chunks) == 2:
+                object = chunks[1]
+
+            # Materials definition
+            if chunks[0] == "mtllib" and len(chunks) == 2:
+                mtllib = chunks[1]
+
+            # Material
+            if chunks[0] == "usemtl" and len(chunks) == 2:
+                material = chunks[1]
+                if not material in materials:
+                    mcurrent = mcounter
+                    materials[material] = mcounter
+                    mcounter += 1
+                else:
+                    mcurrent = materials[material]
+
+            # Smooth shading
+            if chunks[0] == "s" and len(chunks) == 2:
+                smooth = chunks[1]
+
+    return faces, vertices, uvs, normals, materials, mtllib
+
+# #############################################################################
+# API - Breaker
+# #############################################################################
+def break_obj(infile, outfile):
+    """Break infile.obj to outfile.obj
+    """
+
+    if not file_exists(infile):
+        print "Couldn't find [%s]" % infile
+        return
+
+    faces, vertices, uvs, normals, materials, mtllib = parse_obj(infile)
+
+    # sort faces by materials
+
+    chunks = {}
+
+    for face in faces:
+        material = face["material"]
+        if not material in chunks:
+            chunks[material] = {"faces": [], "vertices": set(), "normals": set(), "uvs": set()}
+
+        chunks[material]["faces"].append(face)
+
+    # extract unique vertex / normal / uv indices used per chunk
+
+    for material in chunks:
+        chunk = chunks[material]
+        for face in chunk["faces"]:
+            for i in face["vertex"]:
+                chunk["vertices"].add(i)
+
+            for i in face["normal"]:
+                chunk["normals"].add(i)
+
+            for i in face["uv"]:
+                chunk["uvs"].add(i)
+
+    # generate new OBJs
+
+    for mi, material in enumerate(chunks):
+        chunk = chunks[material]
+
+        # generate separate vertex / normal / uv index lists for each chunk
+        # (including mapping from original to new indices)
+
+        # get well defined order
+
+        new_vertices = list(chunk["vertices"])
+        new_normals = list(chunk["normals"])
+        new_uvs = list(chunk["uvs"])
+
+        # map original => new indices
+
+        vmap = {}
+        for i, v in enumerate(new_vertices):
+            vmap[v] = i + 1
+
+        nmap = {}
+        for i, n in enumerate(new_normals):
+            nmap[n] = i + 1
+
+        tmap = {}
+        for i, t in enumerate(new_uvs):
+            tmap[t] = i + 1
+
+
+        # vertices
+
+        pieces = []
+        for i in new_vertices:
+            vertex = vertices[i-1]
+            txt = TEMPLATE_VERTEX % (vertex[0], vertex[1], vertex[2])
+            pieces.append(txt)
+
+        str_vertices = "\n".join(pieces)
+
+        # normals
+
+        pieces = []
+        for i in new_normals:
+            normal = normals[i-1]
+            txt = TEMPLATE_NORMAL % (normal[0], normal[1], normal[2])
+            pieces.append(txt)
+
+        str_normals = "\n".join(pieces)
+
+        # uvs
+
+        pieces = []
+        for i in new_uvs:
+            uv = uvs[i-1]
+            txt = TEMPLATE_UV % (uv[0], uv[1])
+            pieces.append(txt)
+
+        str_uvs = "\n".join(pieces)
+
+        # faces
+
+        pieces = []
+
+        for face in chunk["faces"]:
+
+            txt = ""
+
+            fv = face["vertex"]
+            fn = face["normal"]
+            ft = face["uv"]
+
+            if len(fv) == 3:
+
+                va = vmap[fv[0]]
+                vb = vmap[fv[1]]
+                vc = vmap[fv[2]]
+
+                if len(fn) == 3 and len(ft) == 3:
+                    na = nmap[fn[0]]
+                    nb = nmap[fn[1]]
+                    nc = nmap[fn[2]]
+
+                    ta = tmap[ft[0]]
+                    tb = tmap[ft[1]]
+                    tc = tmap[ft[2]]
+
+                    txt = TEMPLATE_FACE3_VTN % (va, ta, na, vb, tb, nb, vc, tc, nc)
+
+                elif len(fn) == 3:
+                    na = nmap[fn[0]]
+                    nb = nmap[fn[1]]
+                    nc = nmap[fn[2]]
+
+                    txt = TEMPLATE_FACE3_VN % (va, na, vb, nb, vc, nc)
+
+                elif len(ft) == 3:
+                    ta = tmap[ft[0]]
+                    tb = tmap[ft[1]]
+                    tc = tmap[ft[2]]
+
+                    txt = TEMPLATE_FACE3_VT % (va, ta, vb, tb, vc, tc)
+
+                else:
+                    txt = TEMPLATE_FACE3_V % (va, vb, vc)
+
+            elif len(fv) == 4:
+
+                va = vmap[fv[0]]
+                vb = vmap[fv[1]]
+                vc = vmap[fv[2]]
+                vd = vmap[fv[3]]
+
+                if len(fn) == 4 and len(ft) == 4:
+                    na = nmap[fn[0]]
+                    nb = nmap[fn[1]]
+                    nc = nmap[fn[2]]
+                    nd = nmap[fn[3]]
+
+                    ta = tmap[ft[0]]
+                    tb = tmap[ft[1]]
+                    tc = tmap[ft[2]]
+                    td = tmap[ft[3]]
+
+                    txt = TEMPLATE_FACE4_VTN % (va, ta, na, vb, tb, nb, vc, tc, nc, vd, td, nd)
+
+                elif len(fn) == 4:
+                    na = nmap[fn[0]]
+                    nb = nmap[fn[1]]
+                    nc = nmap[fn[2]]
+                    nd = nmap[fn[3]]
+
+                    txt = TEMPLATE_FACE4_VN % (va, na, vb, nb, vc, nc, vd, nd)
+
+                elif len(ft) == 4:
+                    ta = tmap[ft[0]]
+                    tb = tmap[ft[1]]
+                    tc = tmap[ft[2]]
+                    td = tmap[ft[3]]
+
+                    txt = TEMPLATE_FACE4_VT % (va, ta, vb, tb, vc, tc, vd, td)
+
+                else:
+                    txt = TEMPLATE_FACE4_V % (va, vb, vc, vd)
+
+            pieces.append(txt)
+
+
+        str_faces = "\n".join(pieces)
+
+        # generate OBJ string
+
+        content = TEMPLATE_OBJ % {
+        "nfaces"        : len(chunk["faces"]),
+        "nvertices"     : len(new_vertices),
+        "nnormals"      : len(new_normals),
+        "nuvs"          : len(new_uvs),
+
+        "vertices"      : str_vertices,
+        "normals"       : str_normals,
+        "uvs"           : str_uvs,
+        "faces"         : str_faces
+        }
+
+        # write OBJ file
+
+        outname = "%s_%03d.obj" % (outfile, mi)
+
+        f = open(outname, "w")
+        f.write(content)
+        f.close()
+
+
+# #############################################################################
+# Helpers
+# #############################################################################
+def usage():
+    print "Usage: %s -i filename.obj -o prefix" % os.path.basename(sys.argv[0])
+
+# #####################################################
+# Main
+# #####################################################
+if __name__ == "__main__":
+
+    # get parameters from the command line
+
+    try:
+        opts, args = getopt.getopt(sys.argv[1:], "hi:o:x:", ["help", "input=", "output=", "truncatescale="])
+
+    except getopt.GetoptError:
+        usage()
+        sys.exit(2)
+
+    infile = outfile = ""
+
+    for o, a in opts:
+        if o in ("-h", "--help"):
+            usage()
+            sys.exit()
+
+        elif o in ("-i", "--input"):
+            infile = a
+
+        elif o in ("-o", "--output"):
+            outfile = a
+
+        elif o in ("-x", "--truncatescale"):
+            TRUNCATE = True
+            SCALE = float(a)
+
+    if infile == "" or outfile == "":
+        usage()
+        sys.exit(2)
+
+    print "Splitting [%s] into [%s_XXX.obj] ..." % (infile, outfile)
+
+    break_obj(infile, outfile)
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/build.bat
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/build.bat	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/build.bat	(revision 28897)
@@ -0,0 +1,3 @@
+g++ ./src/objcompress.cc -O2 -Wall -o objcompress
+g++ ./src/obj2utf8.cc -O2 -Wall -o obj2utf8
+g++ ./src/obj2utf8x.cc -O2 -Wall -o obj2utf8x
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/README
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/README	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/README	(revision 28897)
@@ -0,0 +1,20 @@
+Usage: ./objcompress in.obj [out.utf8]
+
+        If 'out' is specified, then attempt to write out a compressed,
+        UTF-8 version to 'out.'
+
+        If not, write a JSON version to STDOUT.
+
+Usage: ./objanalyze in.obj [list of cache sizes]
+
+        Perform vertex cache analysis on in.obj using specified sizes.
+        For example: ./objanalyze in.obj 6 16 24 32
+        Maximum cache size is 32.
+
+Building:
+
+Since there are no external dependences outside of the C/C++ standard
+libraries, you can pretty much build this however you please. I've
+included a cheeky way to do this on POSIX-like systems by including a
+build shell script at the top of the file itself. You can build by
+making the .cc file executable, and running it on the command line.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/base.h
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/base.h	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/base.h	(revision 28897)
@@ -0,0 +1,188 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+#ifndef WEBGL_LOADER_BASE_H_
+#define WEBGL_LOADER_BASE_H_
+
+#include <ctype.h>
+#include <float.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <string>
+#include <vector>
+
+// TODO: consider using C99 spellings.
+typedef unsigned char uint8;
+typedef unsigned short uint16;
+typedef short int16;
+typedef unsigned int uint32;
+
+// printf format strings for size_t.
+#ifdef _WIN32
+# define PRIuS "%Iu"
+#else  // Most compilers use the C99 format string.
+# define PRIuS "%zu"
+#endif
+
+#ifndef isfinite
+# define isfinite _finite
+#endif
+
+typedef std::vector<float> AttribList;
+typedef std::vector<int> IndexList;
+typedef std::vector<uint16> QuantizedAttribList;
+typedef std::vector<uint16> OptimizedIndexList;
+
+// TODO: these data structures ought to go elsewhere.
+struct DrawMesh {
+  // Interleaved vertex format:
+  //  3-D Position
+  //  3-D Normal
+  //  2-D TexCoord
+  // Note that these
+  AttribList attribs;
+  // Indices are 0-indexed.
+  IndexList indices;
+};
+
+struct WebGLMesh {
+  QuantizedAttribList attribs;
+  OptimizedIndexList indices;
+};
+
+typedef std::vector<WebGLMesh> WebGLMeshList;
+
+static inline int strtoint(const char* str, const char** endptr) {
+  return static_cast<int>(strtol(str, const_cast<char**>(endptr), 10));
+}
+
+static inline const char* StripLeadingWhitespace(const char* str) {
+  while (isspace(*str)) {
+    ++str;
+  }
+  return str;
+}
+
+static inline char* StripLeadingWhitespace(char* str) {
+  while (isspace(*str)) {
+    ++str;
+  }
+  return str;
+}
+
+// Like basename.
+static inline const char* StripLeadingDir(const char* const str) {
+  const char* last_slash = NULL;
+  const char* pos = str;
+  while (const char ch = *pos) {
+    if (ch == '/' || ch == '\\') {
+      last_slash = pos;
+    }
+    ++pos;
+  }
+  return last_slash ? (last_slash + 1) : str;
+}
+
+static inline void TerminateAtNewlineOrComment(char* str) {
+  char* newline = strpbrk(str, "#\r\n");
+  if (newline) {
+    *newline = '\0';
+  }
+}
+
+static inline const char* ConsumeFirstToken(const char* const line,
+                                            std::string* token) {
+  const char* curr = line;
+  while (char ch = *curr) {
+    if (isspace(ch)) {
+      token->assign(line, curr);
+      return curr + 1;
+    }
+    ++curr;
+  }
+  if (curr == line) {
+    return NULL;
+  }
+  token->assign(line, curr);
+  return curr;
+}
+
+static inline void ToLower(const char* in, std::string* out) {
+  while (char ch = *in) {
+    out->push_back(tolower(ch));
+    ++in;
+  }
+}
+
+static inline void ToLowerInplace(std::string* in) {
+  std::string& s = *in;
+  for (size_t i = 0; i < s.size(); ++i) {
+    s[i] = tolower(s[i]);
+  }
+}
+
+// Jenkin's One-at-a-time Hash. Not the best, but simple and
+// portable.
+uint32 SimpleHash(char *key, size_t len, uint32 seed = 0) {
+  uint32 hash = seed;
+  for(size_t i = 0; i < len; ++i) {
+    hash += static_cast<unsigned char>(key[i]);
+    hash += (hash << 10);
+    hash ^= (hash >> 6);
+  }
+  hash += (hash << 3);
+  hash ^= (hash >> 11);
+  hash += (hash << 15);
+  return hash;
+}
+
+void ToHex(uint32 w, char out[9]) {
+  const char kOffset0 = '0';
+  const char kOffset10 = 'a' - 10;
+  out[8] = '\0';
+  for (size_t i = 8; i > 0;) {
+    uint32 bits = w & 0xF;
+    out[--i] = bits + ((bits < 10) ? kOffset0 : kOffset10);
+    w >>= 4;
+  }
+}
+
+uint16 Quantize(float f, float in_min, float in_scale, uint16 out_max) {
+  return static_cast<uint16>(out_max * ((f-in_min) / in_scale));
+}
+
+// TODO: Visual Studio calls this someting different.
+#ifdef putc_unlocked
+# define PutChar putc_unlocked
+#else
+# define PutChar putc
+#endif  // putc_unlocked
+
+#ifndef CHECK
+# define CHECK(PRED) if (!(PRED)) {                                     \
+    fprintf(stderr, "%s:%d CHECK failed: ", __FILE__, __LINE__);        \
+    fputs(#PRED "\n", stderr);                                          \
+    exit(-1); } else
+#endif  // CHECK
+
+#ifndef DCHECK
+# ifdef DEBUG
+#  define DCHECK(PRED) CHECK(PRED)
+# else
+#  define DCHECK(PRED)
+# endif  // DEBUG
+#endif  // DCHECK
+
+#endif  // WEBGL_LOADER_BASE_H_
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/bounds.h
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/bounds.h	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/bounds.h	(revision 28897)
@@ -0,0 +1,123 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_BOUNDS_H_
+#define WEBGL_LOADER_BOUNDS_H_
+
+#include <stdio.h>
+
+#include "base.h"
+
+namespace webgl_loader {
+
+// TODO: arbitrary vertex formats.
+
+struct Bounds {
+  float mins[8];
+  float maxes[8];
+
+  void Clear() {
+    for (size_t i = 0; i < 8; ++i) {
+      mins[i] = FLT_MAX;
+      maxes[i] = -FLT_MAX;
+    }
+  }
+
+  void EncloseAttrib(const float* attribs) {
+    for (size_t i = 0; i < 8; ++i) {
+      const float attrib = attribs[i];
+      if (mins[i] > attrib) {
+        mins[i] = attrib;
+      }
+      if (maxes[i] < attrib) {
+        maxes[i] = attrib;
+      }
+    }
+  }
+
+  void Enclose(const AttribList& attribs) {
+    for (size_t i = 0; i < attribs.size(); i += 8) {
+      EncloseAttrib(&attribs[i]);
+    }
+  }
+
+  float UniformScale() const {
+    const float x = maxes[0] - mins[0];
+    const float y = maxes[1] - mins[1];
+    const float z = maxes[2] - mins[2];
+    return (x > y)  // TODO: max3
+        ? ((x > z) ? x : z)
+        : ((y > z) ? y : z);
+  }
+};
+
+// TODO: make maxPosition et. al. configurable.
+struct BoundsParams {
+  static BoundsParams FromBounds(const Bounds& bounds) {
+    BoundsParams ret;
+    const float scale = bounds.UniformScale();
+    // Position. Use a uniform scale.
+    for (size_t i = 0; i < 3; ++i) {
+      const int maxPosition = (1 << 14) - 1;  // 16383;
+      ret.mins[i] = bounds.mins[i];
+      ret.scales[i] = scale;
+      ret.outputMaxes[i] = maxPosition;
+      ret.decodeOffsets[i] = maxPosition * bounds.mins[i] / scale;
+      ret.decodeScales[i] = scale / maxPosition;
+    }
+    // TexCoord.
+    // TODO: get bounds-dependent texcoords working!
+    for (size_t i = 3; i < 5; ++i) {
+      // const float texScale = bounds.maxes[i] - bounds.mins[i];
+      const int maxTexcoord = (1 << 10) - 1;  // 1023
+      ret.mins[i] = 0;  //bounds.mins[i];
+      ret.scales[i] = 1;  //texScale;
+      ret.outputMaxes[i] = maxTexcoord;
+      ret.decodeOffsets[i] = 0;  //maxTexcoord * bounds.mins[i] / texScale;
+      ret.decodeScales[i] = 1.0f / maxTexcoord;  // texScale / maxTexcoord;
+    }
+    // Normal. Always uniform range.
+    for (size_t i = 5; i < 8; ++i) {
+      ret.mins[i] = -1;
+      ret.scales[i] = 2.f;
+      ret.outputMaxes[i] = (1 << 10) - 1;  // 1023
+      ret.decodeOffsets[i] = 1 - (1 << 9);  // -511
+      ret.decodeScales[i] = 1.0 / 511;
+    }
+    return ret;
+  }
+
+  void DumpJson(FILE* out = stdout) {
+    // TODO: use JsonSink.
+    fputs("{\n", out);
+    fprintf(out, "    \"decodeOffsets\": [%d,%d,%d,%d,%d,%d,%d,%d],\n",
+            decodeOffsets[0], decodeOffsets[1], decodeOffsets[2],
+            decodeOffsets[3], decodeOffsets[4], decodeOffsets[5],
+            decodeOffsets[6], decodeOffsets[7]);
+    fprintf(out, "    \"decodeScales\": [%f,%f,%f,%f,%f,%f,%f,%f]\n",
+            decodeScales[0], decodeScales[1], decodeScales[2], decodeScales[3],
+            decodeScales[4], decodeScales[5], decodeScales[6], decodeScales[7]);
+    fputs("  }", out);
+  }
+
+  float mins[8];
+  float scales[8];
+  int outputMaxes[8];
+  int decodeOffsets[8];
+  float decodeScales[8];
+};
+
+}  // namespace webgl_loader
+
+#endif  // WEBGL_LOADER_BOUNDS_H_
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/compress.h
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/compress.h	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/compress.h	(revision 28897)
@@ -0,0 +1,539 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_COMPRESS_H_
+#define WEBGL_LOADER_COMPRESS_H_
+
+#include <math.h>
+
+#include "base.h"
+#include "bounds.h"
+#include "stream.h"
+#include "utf8.h"
+
+namespace webgl_loader {
+
+void AttribsToQuantizedAttribs(const AttribList& interleaved_attribs,
+                               const BoundsParams& bounds_params,
+                               QuantizedAttribList* quantized_attribs) {
+  quantized_attribs->resize(interleaved_attribs.size());
+  for (size_t i = 0; i < interleaved_attribs.size(); i += 8) {
+    for (size_t j = 0; j < 8; ++j) {
+      quantized_attribs->at(i + j) = Quantize(interleaved_attribs[i + j],
+                                              bounds_params.mins[j],
+                                              bounds_params.scales[j],
+                                              bounds_params.outputMaxes[j]);
+    }
+  }
+}
+
+uint16 ZigZag(int16 word) {
+  return (word >> 15) ^ (word << 1);
+}
+
+void CompressAABBToUtf8(const Bounds& bounds,
+                        const BoundsParams& total_bounds,
+                        ByteSinkInterface* utf8) {
+  const int maxPosition = (1 << 14) - 1;  // 16383;
+  uint16 mins[3] = { 0 };
+  uint16 maxes[3] = { 0 };
+  for (int i = 0; i < 3; ++i) {
+    float total_min = total_bounds.mins[i];
+    float total_scale = total_bounds.scales[i];
+    mins[i] = Quantize(bounds.mins[i], total_min, total_scale, maxPosition);
+    maxes[i] = Quantize(bounds.maxes[i], total_min, total_scale, maxPosition);
+  }
+  for (int i = 0; i < 3; ++i) {
+    Uint16ToUtf8(mins[i], utf8);
+  }
+  for (int i = 0; i < 3; ++i) {
+    Uint16ToUtf8(maxes[i] - mins[i], utf8);
+  }
+}
+
+void CompressIndicesToUtf8(const OptimizedIndexList& list,
+                           ByteSinkInterface* utf8) {
+  // For indices, we don't do delta from the most recent index, but
+  // from the high water mark. The assumption is that the high water
+  // mark only ever moves by one at a time. Foruntately, the vertex
+  // optimizer does that for us, to optimize for per-transform vertex
+  // fetch order.
+  uint16 index_high_water_mark = 0;
+  for (size_t i = 0; i < list.size(); ++i) {
+    const int index = list[i];
+    CHECK(index >= 0);
+    CHECK(index <= index_high_water_mark);
+    CHECK(Uint16ToUtf8(index_high_water_mark - index, utf8));
+    if (index == index_high_water_mark) {
+      ++index_high_water_mark;
+    }
+  }
+}
+
+void CompressQuantizedAttribsToUtf8(const QuantizedAttribList& attribs,
+                                    ByteSinkInterface* utf8) {
+  for (size_t i = 0; i < 8; ++i) {
+    // Use a transposed representation, and delta compression.
+    uint16 prev = 0;
+    for (size_t j = i; j < attribs.size(); j += 8) {
+      const uint16 word = attribs[j];
+      const uint16 za = ZigZag(static_cast<int16>(word - prev));
+      prev = word;
+      CHECK(Uint16ToUtf8(za, utf8));
+    }
+  }
+}
+
+class EdgeCachingCompressor {
+ public:
+  // Assuming that the vertex cache optimizer LRU is 32 vertices, we
+  // expect ~64 triangles, and ~96 edges.
+  static const size_t kMaxLruSize = 96;
+  static const int kLruSentinel = -1;
+
+  EdgeCachingCompressor(const QuantizedAttribList& attribs,
+                        OptimizedIndexList& indices)
+      : attribs_(attribs),
+        indices_(indices),
+        deltas_(attribs.size()),
+        index_high_water_mark_(0),
+        lru_size_(0) {
+    memset(last_attrib_, 0, sizeof(last_attrib_));
+  }
+
+  // Work in progress. Does not remotely work.
+  void CompressWithLRU(ByteSinkInterface* utf8) {
+    size_t match_indices[3];
+    size_t match_winding[3];
+    for (size_t i = 0; i < indices_.size(); i += 3) {
+      const uint16* triangle = &indices_[i];
+      // Try to find edge matches to cheaply encode indices and employ
+      // parallelogram prediction.
+      const size_t num_matches = LruEdge(triangle,
+                                         match_indices, match_winding);
+      switch (num_matches) {
+        case 0: 
+          LruEdgeZero(triangle);
+          // No edges match, so use simple predictor.
+          continue;
+        case 1: 
+          LruEdgeOne(triangle[match_winding[1]], 
+                     triangle[match_winding[2]], match_indices[0]);
+          break;
+        case 2: 
+          LruEdgeTwo(triangle[match_winding[2]], 
+                     match_indices[0], match_indices[1]); 
+          break;
+        case 3: 
+          LruEdgeThree(match_indices[0], match_indices[1], match_indices[2]);
+          break;
+        default:
+          DumpDebug();
+          CHECK(false);
+      }
+    }
+  }
+
+  // Instead of using an LRU cache of edges, simply scan the history
+  // for matching edges.
+  void Compress(ByteSinkInterface* utf8) {
+    // TODO: do this pre-quantization.
+    // Normal prediction.
+    const size_t num_attribs = attribs_.size() / 8;
+    std::vector<int> crosses(3 * num_attribs);
+    for (size_t i = 0; i < indices_.size(); i += 3) {
+      // Compute face cross products.
+      const uint16 i0 = indices_[i + 0];
+      const uint16 i1 = indices_[i + 1];
+      const uint16 i2 = indices_[i + 2];
+      int e1[3], e2[3], cross[3];
+      e1[0] = attribs_[8*i1 + 0] - attribs_[8*i0 + 0];
+      e1[1] = attribs_[8*i1 + 1] - attribs_[8*i0 + 1];
+      e1[2] = attribs_[8*i1 + 2] - attribs_[8*i0 + 2];
+      e2[0] = attribs_[8*i2 + 0] - attribs_[8*i0 + 0];
+      e2[1] = attribs_[8*i2 + 1] - attribs_[8*i0 + 1];
+      e2[2] = attribs_[8*i2 + 2] - attribs_[8*i0 + 2];
+      cross[0] = e1[1] * e2[2] - e1[2] * e2[1];
+      cross[1] = e1[2] * e2[0] - e1[0] * e2[2];
+      cross[2] = e1[0] * e2[1] - e1[1] * e2[0];
+      // Accumulate face cross product into each vertex.
+      for (size_t j = 0; j < 3; ++j) {
+        crosses[3*i0 + j] += cross[j];
+        crosses[3*i1 + j] += cross[j];
+        crosses[3*i2 + j] += cross[j];
+      }
+    }
+    // Compute normal residues.
+    for (size_t idx = 0; idx < num_attribs; ++idx) {
+      float pnx = crosses[3*idx + 0];
+      float pny = crosses[3*idx + 1];
+      float pnz = crosses[3*idx + 2];
+      const float pnorm = 511.0 / sqrt(pnx*pnx + pny*pny + pnz*pnz);
+      pnx *= pnorm;
+      pny *= pnorm;
+      pnz *= pnorm;
+
+      float nx = attribs_[8*idx + 5] - 511;
+      float ny = attribs_[8*idx + 6] - 511;
+      float nz = attribs_[8*idx + 7] - 511;
+      const float norm = 511.0 / sqrt(nx*nx + ny*ny + nz*nz);
+      nx *= norm;
+      ny *= norm;
+      nz *= norm;
+
+      const uint16 dx = ZigZag(nx - pnx);
+      const uint16 dy = ZigZag(ny - pny);
+      const uint16 dz = ZigZag(nz - pnz);
+
+      deltas_[5*num_attribs + idx] = dx;
+      deltas_[6*num_attribs + idx] = dy;
+      deltas_[7*num_attribs + idx] = dz;
+    }
+    for (size_t triangle_start_index = 0; 
+         triangle_start_index < indices_.size(); triangle_start_index += 3) {
+      const uint16 i0 = indices_[triangle_start_index + 0];
+      const uint16 i1 = indices_[triangle_start_index + 1];
+      const uint16 i2 = indices_[triangle_start_index + 2];
+      // To force simple compression, set |max_backref| to 0 here
+      // and in loader.js.
+      // |max_backref| should be configurable and communicated.
+      const uint16 max_backref = triangle_start_index < kMaxLruSize ?
+          triangle_start_index : kMaxLruSize;
+      // Scan the index list for matching edges.
+      uint16 backref = 0;
+      for (; backref < max_backref; backref += 3) {
+        const size_t candidate_start_index = triangle_start_index - backref;
+        const uint16 j0 = indices_[candidate_start_index + 0];
+        const uint16 j1 = indices_[candidate_start_index + 1];
+        const uint16 j2 = indices_[candidate_start_index + 2];
+        // Compare input and candidate triangle edges in a
+        // winding-sensitive order. Matching edges must reference
+        // vertices in opposite order, and the first check sees if the
+        // triangles are in strip order. If necessary, re-order the
+        // triangle in |indices_| so that the matching edge appears
+        // first.
+        if (j1 == i1 && j2 == i0) {
+          ParallelogramPredictor(backref, j0, triangle_start_index);
+          break;
+        } else if (j1 == i0 && j2 == i2) {
+          indices_[triangle_start_index + 0] = i2;
+          indices_[triangle_start_index + 1] = i0;
+          indices_[triangle_start_index + 2] = i1;
+          ParallelogramPredictor(backref, j0, triangle_start_index);
+          break;
+        } else if (j1 == i2 && j2 == i1) {
+          indices_[triangle_start_index + 0] = i1;
+          indices_[triangle_start_index + 1] = i2;
+          indices_[triangle_start_index + 2] = i0;
+          ParallelogramPredictor(backref, j0, triangle_start_index);
+          break;
+        } else if (j2 == i1 && j0 == i0) {
+          ParallelogramPredictor(backref + 1, j1, triangle_start_index);
+          break;
+        } else if (j2 == i0 && j0 == i2) {
+          indices_[triangle_start_index + 0] = i2;
+          indices_[triangle_start_index + 1] = i0;
+          indices_[triangle_start_index + 2] = i1;
+          ParallelogramPredictor(backref + 1, j1, triangle_start_index);
+          break;
+        } else if (j2 == i2 && j0 == i1) {
+          indices_[triangle_start_index + 0] = i1;
+          indices_[triangle_start_index + 1] = i2;
+          indices_[triangle_start_index + 2] = i0;
+          ParallelogramPredictor(backref + 1, j1, triangle_start_index);
+          break;
+        } else if (j0 == i1 && j1 == i0) {
+          ParallelogramPredictor(backref + 2, j2, triangle_start_index);
+          break;
+        } else if (j0 == i0 && j1 == i2) {
+          indices_[triangle_start_index + 0] = i2;
+          indices_[triangle_start_index + 1] = i0;
+          indices_[triangle_start_index + 2] = i1;
+          ParallelogramPredictor(backref + 2, j2, triangle_start_index);
+          break;
+        } else if (j0 == i2 && j1 == i1) {
+          indices_[triangle_start_index + 0] = i1;
+          indices_[triangle_start_index + 1] = i2;
+          indices_[triangle_start_index + 2] = i0;
+          ParallelogramPredictor(backref + 2, j2, triangle_start_index);
+          break;
+        }
+      }
+      if (backref == max_backref) {
+        SimplePredictor(max_backref, triangle_start_index);
+      }
+    }
+    // Emit as UTF-8.
+    for (size_t i = 0; i < deltas_.size(); ++i) {
+      if (!Uint16ToUtf8(deltas_[i], utf8)) {
+        // TODO: bounds-dependent texcoords are still busted :(
+        Uint16ToUtf8(0, utf8);
+      }
+    }
+    for (size_t i = 0; i < codes_.size(); ++i) {
+      CHECK(Uint16ToUtf8(codes_[i], utf8));
+    }
+  }
+
+  const QuantizedAttribList& deltas() const { return deltas_; }
+
+  const OptimizedIndexList& codes() const { return codes_; }
+
+  void DumpDebug(FILE* fp = stdout) {
+    for (size_t i = 0; i < lru_size_; ++i) {
+      fprintf(fp, PRIuS ": %d\n", i, edge_lru_[i]);
+    }
+  }
+
+ private:
+  // The simple predictor is slightly (maybe 5%) more effective than
+  // |CompressQuantizedAttribsToUtf8|. Instead of delta encoding in
+  // attribute order, we use the last referenced attribute as the
+  // predictor.
+  void SimplePredictor(size_t max_backref, size_t triangle_start_index) {
+    const uint16 i0 = indices_[triangle_start_index + 0];
+    const uint16 i1 = indices_[triangle_start_index + 1];
+    const uint16 i2 = indices_[triangle_start_index + 2];
+    if (HighWaterMark(i0, max_backref)) {
+      // Would it be faster to do the dumb delta, in this case?
+      EncodeDeltaAttrib(i0, last_attrib_);
+    }
+    if (HighWaterMark(i1)) {
+      EncodeDeltaAttrib(i1, &attribs_[8*i0]);
+    }
+    if (HighWaterMark(i2)) {
+      // We get a little frisky with the third vertex in the triangle.
+      // Instead of simply using the previous vertex, use the average
+      // of the first two.
+      for (size_t j = 0; j < 8; ++j) {
+        int average = attribs_[8*i0 + j];
+        average += attribs_[8*i1 + j];
+        average /= 2;
+        last_attrib_[j] = average;
+      }
+      EncodeDeltaAttrib(i2, last_attrib_);
+      // The above doesn't add much. Consider the simpler:
+      // EncodeDeltaAttrib(i2, &attribs_[8*i1]);
+    }
+  }
+
+  void EncodeDeltaAttrib(size_t index, const uint16* predicted) {
+    const size_t num_attribs = attribs_.size() / 8;
+    for (size_t i = 0; i < 5; ++i) {
+      const int delta = attribs_[8*index + i] - predicted[i];
+      const uint16 code = ZigZag(delta);
+      deltas_[num_attribs*i + index] = code;
+    }
+    UpdateLastAttrib(index);
+  }
+
+  void ParallelogramPredictor(uint16 backref_edge,
+                              size_t backref_vert,
+                              size_t triangle_start_index) {
+    codes_.push_back(backref_edge);  // Encoding matching edge.
+    const uint16 i2 = indices_[triangle_start_index + 2];
+    if (HighWaterMark(i2)) {  // Encode third vertex.
+      // Parallelogram prediction for the new vertex.
+      const uint16 i0 = indices_[triangle_start_index + 0];
+      const uint16 i1 = indices_[triangle_start_index + 1];
+      const size_t num_attribs = attribs_.size() / 8;
+      for (size_t j = 0; j < 5; ++j) {
+        const uint16 orig = attribs_[8*i2 + j]; 
+        int delta = attribs_[8*i0 + j];
+        delta += attribs_[8*i1 + j];
+        delta -= attribs_[8*backref_vert + j];
+        last_attrib_[j] = orig;
+        const uint16 code = ZigZag(orig - delta);
+        deltas_[num_attribs*j + i2] = code;
+      }
+    }
+  }
+
+  // Returns |true| if |index_high_water_mark_| is incremented, otherwise
+  // returns |false| and automatically updates |last_attrib_|. 
+  bool HighWaterMark(uint16 index, uint16 start_code = 0) {
+    codes_.push_back(index_high_water_mark_ - index + start_code);
+    if (index == index_high_water_mark_) {
+      ++index_high_water_mark_;
+      return true;
+    } else {
+      UpdateLastAttrib(index);
+    }
+    return false;
+  }
+
+  void UpdateLastAttrib(uint16 index) {
+    for (size_t i = 0; i < 8; ++i) {
+      last_attrib_[i] = attribs_[8*index + i];
+    }
+  }
+
+  // Find edge matches of |triangle| referenced in |edge_lru_|
+  // |match_indices| stores where the matches occur in |edge_lru_|
+  // |match_winding| stores where the matches occur in |triangle|
+  size_t LruEdge(const uint16* triangle,
+                 size_t* match_indices,
+                 size_t* match_winding) {
+    const uint16 i0 = triangle[0];
+    const uint16 i1 = triangle[1];
+    const uint16 i2 = triangle[2];
+    // The primary thing is to find the first matching edge, if
+    // any. If we assume that our mesh is mostly manifold, then each
+    // edge is shared by at most two triangles (with the indices in
+    // opposite order), so we actually want to eventually remove all
+    // matching edges. However, this means we have to continue
+    // scanning the array to find all matching edges, not just the
+    // first. The edges that don't match will then pushed to the
+    // front.
+    size_t num_matches = 0;
+    for (size_t i = 0; i < lru_size_ && num_matches < 3; ++i) {
+      const int edge_index = edge_lru_[i];
+      // |winding| is a tricky detail used to dereference the edge to
+      // yield |e0| and |e1|, since we must handle the edge that wraps
+      // the last and first vertex. For now, just implement this in a
+      // straightforward way using a switch, but since this code would
+      // actually also need to run in the decompressor, we must
+      // optimize it.
+      const int winding = edge_index % 3;
+      uint16 e0, e1;
+      switch (winding) {
+        case 0:
+          e0 = indices_[edge_index + 1];
+          e1 = indices_[edge_index + 2];
+          break;
+        case 1:
+          e0 = indices_[edge_index + 2];
+          e1 = indices_[edge_index];
+          break;
+        case 2:
+          e0 = indices_[edge_index];
+          e1 = indices_[edge_index + 1];
+          break;
+        default:
+          DumpDebug();
+          CHECK(false);
+      }
+
+      // Check each edge of the input triangle against |e0| and
+      // |e1|. Note that we reverse the winding of the input triangle.
+      // TODO: does this properly handle degenerate input?
+      if (e0 == i1 && e1 == i0) {
+        match_winding[num_matches] = 2;
+        match_indices[++num_matches] = i;
+      } else if (e0 == i2 && e1 == i1) {
+        match_winding[num_matches] = 0;
+        match_indices[++num_matches] = i;
+      } else if (e0 == i0 && e1 == i2) {
+        match_winding[num_matches] = 1;
+        match_indices[++num_matches] = i;
+      }
+    }
+    switch (num_matches) {
+      case 1:
+        match_winding[1] = (match_winding[0] + 1) % 3;  // Fall through.
+      case 2:
+        match_winding[2] = 3 - match_winding[1] - match_winding[0];
+      default: ;  // Do nothing.
+    }
+    return num_matches;
+  }
+
+  // If no edges were found in |triangle|, then simply push the edges
+  // onto |edge_lru_|.
+  void LruEdgeZero(const uint16* triangle) {
+    // Shift |edge_lru_| by three elements. Note that the |edge_lru_|
+    // array has at least three extra elements to make this simple.
+    lru_size_ += 3;
+    if (lru_size_ > kMaxLruSize) lru_size_ = kMaxLruSize;
+    memmove(edge_lru_ + 3, edge_lru_, lru_size_ * sizeof(int));
+    // Push |triangle| to front of |edge_lru_|
+    edge_lru_[0] = triangle[0];
+    edge_lru_[1] = triangle[1];
+    edge_lru_[2] = triangle[2];
+  }
+
+  // Remove one edge and add two.
+  void LruEdgeOne(size_t i0, size_t i1, size_t match_index) {
+    CHECK(match_index < lru_size_);
+    // Shift |edge_lru_| by one element, starting with |match_index| + 1.
+    memmove(edge_lru_ + match_index + 2, edge_lru_ + match_index + 1,
+            (lru_size_ - match_index) * sizeof(int));
+    // Shift |edge_lru_| by two elements until reaching |match_index|.
+    memmove(edge_lru_ + 2, edge_lru_, match_index * sizeof(int));
+    edge_lru_[0] = i0;
+    edge_lru_[1] = i1;
+  }
+
+  // Remove two edges and add one.
+  void LruEdgeTwo(int i0, size_t match_index0, size_t match_index1) {
+    CHECK(match_index0 < lru_size_);
+    CHECK(match_index1 < lru_size_);
+
+    // memmove 1
+    // memmove 2
+    edge_lru_[0] = i0;
+  }
+
+  // All edges were found, so just remove them from |edge_lru_|.
+  void LruEdgeThree(size_t match_index0, 
+                    size_t match_index1, 
+                    size_t match_index2) {
+    const size_t shift_two = match_index1 - 1;
+    for (size_t i = match_index0; i < shift_two; ++i) {
+      edge_lru_[i] = edge_lru_[i + 1];
+    }
+    const size_t shift_three = match_index2 - 2;
+    for (size_t i = shift_two; i < shift_three; ++i) {
+      edge_lru_[i] = edge_lru_[i + 2];
+    }
+    lru_size_ -= 3;
+    for (size_t i = shift_three; i < lru_size_; ++i) {
+      edge_lru_[i] = edge_lru_[i + 3];
+    }
+  }
+
+  // |attribs_| and |indices_| is the input mesh.
+  const QuantizedAttribList& attribs_;
+  // |indices_| are non-const because |Compress| may update triangle
+  // winding order.
+  OptimizedIndexList& indices_;
+  // |deltas_| contains the compressed attributes. They can be
+  // compressed in one of two ways:
+  // (1) with parallelogram prediction, compared with the predicted vertex,
+  // (2) otherwise, compared with the last referenced vertex.
+  // Note that even (2) is different and probably better than what
+  // |CompressQuantizedAttribsToutf8| does, which is comparing with
+  // the last encoded vertex.
+  QuantizedAttribList deltas_;
+  // |codes_| contains the compressed indices. Small values encode an
+  // edge match; that is, the first edge of the next triangle matches
+  // a recently-seen edge.
+  OptimizedIndexList codes_; 
+  // |index_high_water_mark_| is used as it is in |CompressIndicesToUtf8|.
+  uint16 index_high_water_mark_;
+  // |last_attrib_referenced_| is the index of the last referenced
+  // attribute. This is used to delta encode attributes when no edge match
+  // is found.
+  uint16 last_attrib_[8];
+  size_t lru_size_;
+  // |edge_lru_| contains the LRU lits of edge references. It stores
+  // indices to the input |indices_|. By convention, an edge points to
+  // the vertex opposite the edge in question. We pad the array by a
+  // triangle to simplify edge cases.
+  int edge_lru_[kMaxLruSize + 3];
+};
+
+}  // namespace webgl_loader
+
+#endif  // WEBGL_LOADER_COMPRESS_H_
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/mesh.h
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/mesh.h	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/mesh.h	(revision 28897)
@@ -0,0 +1,701 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_MESH_H_
+#define WEBGL_LOADER_MESH_H_
+
+#include <float.h>
+#include <limits.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <map>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base.h"
+#include "bounds.h"
+#include "stream.h"
+#include "utf8.h"
+
+// A short list of floats, useful for parsing a single vector
+// attribute.
+class ShortFloatList {
+ public:
+  // MeshLab can create position attributes with
+  // color coordinates like: v x y z r g b
+  static const size_t kMaxNumFloats = 6;
+  ShortFloatList()
+      : size_(0)
+  {
+    clear();
+  }
+
+  void clear() {
+    for (size_t i = 0; i < kMaxNumFloats; ++i) {
+      a_[i] = 0.f;
+    }
+  }
+
+  // Parse up to kMaxNumFloats from C string.
+  // TODO: this should instead return endptr, since size
+  // is recoverable.
+  size_t ParseLine(const char* line) {
+    for (size_ = 0; size_ != kMaxNumFloats; ++size_) {
+      char* endptr = NULL;
+      a_[size_] = strtof(line, &endptr);
+      if (endptr == NULL || line == endptr) break;
+      line = endptr;
+    }
+    return size_;
+  }
+
+  float operator[](size_t idx) const {
+    return a_[idx];
+  }
+
+  void AppendTo(AttribList* attribs) const {
+    AppendNTo(attribs, size_);
+  }
+
+  void AppendNTo(AttribList* attribs, const size_t sz) const {
+    attribs->insert(attribs->end(), a_, a_ + sz);
+  }
+
+  bool empty() const { return size_ == 0; }
+
+  size_t size() const { return size_; }
+ private:
+  float a_[kMaxNumFloats];
+  size_t size_;
+};
+
+class IndexFlattener {
+ public:
+  explicit IndexFlattener(size_t num_positions)
+      : count_(0),
+        table_(num_positions) {
+  }
+
+  int count() const { return count_; }
+
+  void reserve(size_t size) {
+    table_.reserve(size);
+  }
+
+  // Returns a pair of: < flattened index, newly inserted >.
+  std::pair<int, bool> GetFlattenedIndex(int position_index,
+                                         int texcoord_index,
+                                         int normal_index) {
+    if (position_index >= static_cast<int>(table_.size())) {
+      table_.resize(position_index + 1);
+    }
+    // First, optimistically look up position_index in the table.
+    IndexType& index = table_[position_index];
+    if (index.position_or_flat == kIndexUnknown) {
+      // This is the first time we've seen this position in the table,
+      // so fill it. Since the table is indexed by position, we can
+      // use the position_or_flat_index field to store the flat index.
+      const int flat_index = count_++;
+      index.position_or_flat = flat_index;
+      index.texcoord = texcoord_index;
+      index.normal = normal_index;
+      return std::make_pair(flat_index, true);
+    } else if (index.position_or_flat == kIndexNotInTable) {
+      // There are multiple flattened indices at this position index,
+      // so resort to the map.
+      return GetFlattenedIndexFromMap(position_index,
+                                      texcoord_index,
+                                      normal_index);
+    } else if (index.texcoord == texcoord_index &&
+               index.normal == normal_index) {
+      // The other indices match, so we can use the value cached in
+      // the table.
+      return std::make_pair(index.position_or_flat, false);
+    }
+    // The other indices don't match, so we mark this table entry,
+    // and insert both the old and new indices into the map.
+    const IndexType old_index(position_index, index.texcoord, index.normal);
+    map_.insert(std::make_pair(old_index, index.position_or_flat));
+    index.position_or_flat = kIndexNotInTable;
+    const IndexType new_index(position_index, texcoord_index, normal_index);
+    const int flat_index = count_++;
+    map_.insert(std::make_pair(new_index, flat_index));
+    return std::make_pair(flat_index, true);
+  }
+ private:
+  std::pair<int, bool> GetFlattenedIndexFromMap(int position_index,
+                                                int texcoord_index,
+                                                int normal_index) {
+    IndexType index(position_index, texcoord_index, normal_index);
+    MapType::iterator iter = map_.lower_bound(index);
+    if (iter == map_.end() || iter->first != index) {
+      const int flat_index = count_++;
+      map_.insert(iter, std::make_pair(index, flat_index));
+      return std::make_pair(flat_index, true);
+    } else {
+      return std::make_pair(iter->second, false);
+    }
+  }
+
+  static const int kIndexUnknown = -1;
+  static const int kIndexNotInTable = -2;
+
+  struct IndexType {
+    IndexType()
+        : position_or_flat(kIndexUnknown),
+          texcoord(kIndexUnknown),
+          normal(kIndexUnknown)
+    { }
+
+    IndexType(int position_index, int texcoord_index, int normal_index)
+        : position_or_flat(position_index),
+          texcoord(texcoord_index),
+          normal(normal_index)
+    { }
+
+    // I'm being tricky/lazy here. The table_ stores the flattened
+    // index in the first field, since it is indexed by position. The
+    // map_ stores position and uses this struct as a key to lookup the
+    // flattened index.
+    int position_or_flat;
+    int texcoord;
+    int normal;
+
+    // An ordering for std::map.
+    bool operator<(const IndexType& that) const {
+      if (position_or_flat == that.position_or_flat) {
+        if (texcoord == that.texcoord) {
+          return normal < that.normal;
+        } else {
+          return texcoord < that.texcoord;
+        }
+      } else {
+        return position_or_flat < that.position_or_flat;
+      }
+    }
+
+    bool operator==(const IndexType& that) const {
+      return position_or_flat == that.position_or_flat &&
+          texcoord == that.texcoord && normal == that.normal;
+    }
+
+    bool operator!=(const IndexType& that) const {
+      return !operator==(that);
+    }
+  };
+  typedef std::map<IndexType, int> MapType;
+
+  int count_;
+  std::vector<IndexType> table_;
+  MapType map_;
+};
+
+static inline size_t positionDim() { return 3; }
+static inline size_t texcoordDim() { return 2; }
+static inline size_t normalDim() { return 3; }
+
+// TODO(wonchun): Make a c'tor to properly initialize.
+struct GroupStart {
+  size_t offset;  // offset into draw_mesh_.indices.
+  unsigned int group_line;
+  int min_index, max_index;  // range into attribs.
+  webgl_loader::Bounds bounds;
+};
+
+class DrawBatch {
+ public:
+  DrawBatch()
+      : flattener_(0),
+        current_group_line_(0xFFFFFFFF) {
+  }
+
+  const std::vector<GroupStart>& group_starts() const {
+    return group_starts_;
+  }
+
+  void Init(AttribList* positions, AttribList* texcoords, AttribList* normals) {
+    positions_ = positions;
+    texcoords_ = texcoords;
+    normals_ = normals;
+    flattener_.reserve(1024);
+  }
+
+  void AddTriangle(unsigned int group_line, int* indices) {
+    if (group_line != current_group_line_) {
+      current_group_line_ = group_line;
+      GroupStart group_start;
+      group_start.offset = draw_mesh_.indices.size();
+      group_start.group_line = group_line;
+      group_start.min_index = INT_MAX;
+      group_start.max_index = INT_MIN;
+      group_start.bounds.Clear();
+      group_starts_.push_back(group_start);
+    }
+    GroupStart& group = group_starts_.back();
+    for (size_t i = 0; i < 9; i += 3) {
+      // .OBJ files use 1-based indexing.
+      const int position_index = indices[i + 0] - 1;
+      const int texcoord_index = indices[i + 1] - 1;
+      const int normal_index = indices[i + 2] - 1;
+      const std::pair<int, bool> flattened = flattener_.GetFlattenedIndex(
+          position_index, texcoord_index, normal_index);
+      const int flat_index = flattened.first;
+      CHECK(flat_index >= 0);
+      draw_mesh_.indices.push_back(flat_index);
+      if (flattened.second) {
+        // This is a new index. Keep track of index ranges and vertex
+        // bounds.
+        if (flat_index > group.max_index) {
+          group.max_index = flat_index;
+        }
+        if (flat_index < group.min_index) {
+          group.min_index = flat_index;
+        }
+        const size_t new_loc = draw_mesh_.attribs.size();
+        CHECK(8*size_t(flat_index) == new_loc);
+        for (size_t i = 0; i < positionDim(); ++i) {
+          draw_mesh_.attribs.push_back(
+              positions_->at(positionDim() * position_index + i));
+        }
+        if (texcoord_index == -1) {
+          for (size_t i = 0; i < texcoordDim(); ++i) {
+            draw_mesh_.attribs.push_back(0);
+          }
+        } else {
+          for (size_t i = 0; i < texcoordDim(); ++i) {
+            draw_mesh_.attribs.push_back(
+                texcoords_->at(texcoordDim() * texcoord_index + i));
+          }
+        }
+        if (normal_index == -1) {
+          for (size_t i = 0; i < normalDim(); ++i) {
+            draw_mesh_.attribs.push_back(0);
+          }
+        } else {
+          for (size_t i = 0; i < normalDim(); ++i) {
+            draw_mesh_.attribs.push_back(
+                normals_->at(normalDim() * normal_index + i));
+          }
+        }
+        // TODO: is the covariance body useful for anything?
+        group.bounds.EncloseAttrib(&draw_mesh_.attribs[new_loc]);
+      }
+    }
+  }
+
+  const DrawMesh& draw_mesh() const {
+    return draw_mesh_;
+  }
+ private:
+  AttribList* positions_, *texcoords_, *normals_;
+  DrawMesh draw_mesh_;
+  IndexFlattener flattener_;
+  unsigned int current_group_line_;
+  std::vector<GroupStart> group_starts_;
+};
+
+struct Material {
+  std::string name;
+  float Kd[3];
+  std::string map_Kd;
+
+  void DumpJson(FILE* out = stdout) const {
+    fprintf(out, "    \"%s\": { ", name.c_str());
+    if (map_Kd.empty()) {
+      fprintf(out, "\"Kd\": [%hu, %hu, %hu] }",
+              Quantize(Kd[0], 0, 1, 255),
+              Quantize(Kd[1], 0, 1, 255),
+              Quantize(Kd[2], 0, 1, 255));
+    } else {
+      fprintf(out, "\"map_Kd\": \"%s\" }", map_Kd.c_str());
+    }
+  }
+};
+
+typedef std::vector<Material> MaterialList;
+
+class WavefrontMtlFile {
+ public:
+  explicit WavefrontMtlFile(FILE* fp) {
+    ParseFile(fp);
+  }
+
+  const MaterialList& materials() const {
+    return materials_;
+  }
+
+ private:
+  // TODO: factor this parsing stuff out.
+  void ParseFile(FILE* fp) {
+    // TODO: don't use a fixed-size buffer.
+    const size_t kLineBufferSize = 256;
+    char buffer[kLineBufferSize];
+    unsigned int line_num = 1;
+    while (fgets(buffer, kLineBufferSize, fp) != NULL) {
+      char* stripped = StripLeadingWhitespace(buffer);
+      TerminateAtNewlineOrComment(stripped);
+      ParseLine(stripped, line_num++);
+    }
+  }
+
+  void ParseLine(const char* line, unsigned int line_num) {
+    switch (*line) {
+      case 'K':
+        ParseColor(line + 1, line_num);
+        break;
+      case 'm':
+        if (0 == strncmp(line + 1, "ap_Kd", 5)) {
+          ParseMapKd(line + 6, line_num);
+        }
+        break;
+      case 'n':
+        if (0 == strncmp(line + 1, "ewmtl", 5)) {
+          ParseNewmtl(line + 6, line_num);
+        }
+      default:
+        break;
+    }
+  }
+
+  void ParseColor(const char* line, unsigned int line_num) {
+    switch (*line) {
+      case 'd': {
+        ShortFloatList floats;
+        floats.ParseLine(line + 1);
+        float* Kd = current_->Kd;
+        Kd[0] = floats[0];
+        Kd[1] = floats[1];
+        Kd[2] = floats[2];
+        break;
+      }
+      default:
+        break;
+    }
+  }
+
+  void ParseMapKd(const char* line, unsigned int line_num) {
+    current_->map_Kd = StripLeadingWhitespace(line);
+  }
+
+  void ParseNewmtl(const char* line, unsigned int line_num) {
+    materials_.push_back(Material());
+    current_ = &materials_.back();
+    ToLower(StripLeadingWhitespace(line), &current_->name);
+  }
+
+  Material* current_;
+  MaterialList materials_;
+};
+
+typedef std::map<std::string, DrawBatch> MaterialBatches;
+
+// TODO: consider splitting this into a low-level parser and a high-level
+// object.
+class WavefrontObjFile {
+ public:
+  explicit WavefrontObjFile(FILE* fp) {
+    current_batch_ = &material_batches_[""];
+    current_batch_->Init(&positions_, &texcoords_, &normals_);
+    current_group_line_ = 0;
+    line_to_groups_.insert(std::make_pair(0, "default"));
+    ParseFile(fp);
+  }
+
+  const MaterialList& materials() const {
+    return materials_;
+  }
+
+  const MaterialBatches& material_batches() const {
+    return material_batches_;
+  }
+
+  const std::string& LineToGroup(unsigned int line) const {
+    typedef LineToGroups::const_iterator Iterator;
+    typedef std::pair<Iterator, Iterator> EqualRange;
+    EqualRange equal_range = line_to_groups_.equal_range(line);
+    const std::string* best_group = NULL;
+    int best_count = 0;
+    for (Iterator iter = equal_range.first; iter != equal_range.second;
+         ++iter) {
+      const std::string& group = iter->second;
+      const int count = group_counts_.find(group)->second;
+      if (!best_group || (count < best_count)) {
+        best_group = &group;
+        best_count = count;
+      }
+    }
+    if (!best_group) {
+      ErrorLine("no suitable group found", line);
+    }
+    return *best_group;
+  }
+
+  void DumpDebug() const {
+    printf("positions size: " PRIuS "\n"
+	   "texcoords size: " PRIuS "\n"
+	   "normals size: " PRIuS "\n",
+           positions_.size(), texcoords_.size(), normals_.size());
+  }
+ private:
+  WavefrontObjFile() { }  // For testing.
+
+  void ParseFile(FILE* fp) {
+    // TODO: don't use a fixed-size buffer.
+    const size_t kLineBufferSize = 256;
+    char buffer[kLineBufferSize] = { 0 };
+    unsigned int line_num = 1;
+    while (fgets(buffer, kLineBufferSize, fp) != NULL) {
+      char* stripped = StripLeadingWhitespace(buffer);
+      TerminateAtNewlineOrComment(stripped);
+      ParseLine(stripped, line_num++);
+    }
+  }
+
+  void ParseLine(const char* line, unsigned int line_num) {
+    switch (*line) {
+      case 'v':
+        ParseAttrib(line + 1, line_num);
+        break;
+      case 'f':
+        ParseFace(line + 1, line_num);
+        break;
+      case 'g':
+        if (isspace(line[1])) {
+          ParseGroup(line + 2, line_num);
+        } else {
+          goto unknown;
+        }
+        break;
+      case '\0':
+      case '#':
+        break;  // Do nothing for comments or blank lines.
+      case 'p':
+        WarnLine("point unsupported", line_num);
+        break;
+      case 'l':
+        WarnLine("line unsupported", line_num);
+        break;
+      case 'u':
+        if (0 == strncmp(line + 1, "semtl", 5)) {
+          ParseUsemtl(line + 6, line_num);
+        } else {
+          goto unknown;
+        }
+        break;
+      case 'm':
+        if (0 == strncmp(line + 1, "tllib", 5)) {
+          ParseMtllib(line + 6, line_num);
+        } else {
+          goto unknown;
+        }
+        break;
+      case 's':
+        ParseSmoothingGroup(line + 1, line_num);
+        break;
+      unknown:
+      default:
+        WarnLine("unknown keyword", line_num);
+        break;
+    }
+  }
+
+  void ParseAttrib(const char* line, unsigned int line_num) {
+    ShortFloatList floats;
+    floats.ParseLine(line + 1);
+    if (isspace(*line)) {
+      ParsePosition(floats, line_num);
+    } else if (*line == 't') {
+      ParseTexCoord(floats, line_num);
+    } else if (*line == 'n') {
+      ParseNormal(floats, line_num);
+    } else {
+      WarnLine("unknown attribute format", line_num);
+    }
+  }
+
+  void ParsePosition(const ShortFloatList& floats, unsigned int line_num) {
+    if (floats.size() != positionDim() &&
+        floats.size() != 6) {  // ignore r g b for now.
+      ErrorLine("bad position", line_num);
+    }
+    floats.AppendNTo(&positions_, positionDim());
+  }
+
+  void ParseTexCoord(const ShortFloatList& floats, unsigned int line_num) {
+    if ((floats.size() < 1) || (floats.size() > 3)) {
+      // TODO: correctly handle 3-D texcoords intead of just
+      // truncating.
+      ErrorLine("bad texcoord", line_num);
+    }
+    floats.AppendNTo(&texcoords_, texcoordDim());
+  }
+
+  void ParseNormal(const ShortFloatList& floats, unsigned int line_num) {
+    if (floats.size() != normalDim()) {
+      ErrorLine("bad normal", line_num);
+    }
+    // Normalize to avoid out-of-bounds quantization. This should be
+    // optional, in case someone wants to be using the normal magnitude as
+    // something meaningful.
+    const float x = floats[0];
+    const float y = floats[1];
+    const float z = floats[2];
+    const float scale = 1.0/sqrt(x*x + y*y + z*z);
+    if (isfinite(scale)) {
+      normals_.push_back(scale * x);
+      normals_.push_back(scale * y);
+      normals_.push_back(scale * z);
+    } else {
+      normals_.push_back(0);
+      normals_.push_back(0);
+      normals_.push_back(0);
+    }
+  }
+
+  // Parses faces and converts to triangle fans. This is not a
+  // particularly good tesselation in general case, but it is really
+  // simple, and is perfectly fine for triangles and quads.
+  void ParseFace(const char* line, unsigned int line_num) {
+    // Also handle face outlines as faces.
+    if (*line == 'o') ++line;
+
+    // TODO: instead of storing these indices as-is, it might make
+    // sense to flatten them right away. This can reduce memory
+    // consumption and improve access locality, especially since .OBJ
+    // face indices are so needlessly large.
+    int indices[9] = { 0 };
+    // The first index acts as the pivot for the triangle fan.
+    line = ParseIndices(line, line_num, indices + 0, indices + 1, indices + 2);
+    if (line == NULL) {
+      ErrorLine("bad first index", line_num);
+    }
+    line = ParseIndices(line, line_num, indices + 3, indices + 4, indices + 5);
+    if (line == NULL) {
+      ErrorLine("bad second index", line_num);
+    }
+    // After the first two indices, each index introduces a new
+    // triangle to the fan.
+    while ((line = ParseIndices(line, line_num,
+                                indices + 6, indices + 7, indices + 8))) {
+      current_batch_->AddTriangle(current_group_line_, indices);
+      // The most recent vertex is reused for the next triangle.
+      indices[3] = indices[6];
+      indices[4] = indices[7];
+      indices[5] = indices[8];
+      indices[6] = indices[7] = indices[8] = 0;
+    }
+  }
+
+  // Parse a single group of indices, separated by slashes ('/').
+  // TODO: convert negative indices (that is, relative to the end of
+  // the current vertex positions) to more conventional positive
+  // indices.
+  const char* ParseIndices(const char* line, unsigned int line_num,
+                           int* position_index, int* texcoord_index,
+                           int* normal_index) {
+    const char* endptr = NULL;
+    *position_index = strtoint(line, &endptr);
+    if (*position_index == 0) {
+      return NULL;
+    }
+    if (endptr != NULL && *endptr == '/') {
+      *texcoord_index = strtoint(endptr + 1, &endptr);
+    } else {
+      *texcoord_index = *normal_index = 0;
+    }
+    if (endptr != NULL && *endptr == '/') {
+      *normal_index = strtoint(endptr + 1, &endptr);
+    } else {
+      *normal_index = 0;
+    }
+    return endptr;
+  }
+
+  // .OBJ files can specify multiple groups for a set of faces. This
+  // implementation finds the "most unique" group for a set of faces
+  // and uses that for the batch. In the first pass, we use the line
+  // number of the "g" command to tag the faces. Afterwards, after we
+  // collect group populations, we can go back and give them real
+  // names.
+  void ParseGroup(const char* line, unsigned int line_num) {
+    std::string token;
+    while ((line = ConsumeFirstToken(line, &token))) {
+      ToLowerInplace(&token);
+      group_counts_[token]++;
+      line_to_groups_.insert(std::make_pair(line_num, token));
+    }
+    current_group_line_ = line_num;
+  }
+
+  void ParseSmoothingGroup(const char* line, unsigned int line_num) {
+    static bool once = true;
+    if (once) {
+      WarnLine("s ignored", line_num);
+      once = false;
+    }
+  }
+
+  void ParseMtllib(const char* line, unsigned int line_num) {
+    FILE* fp = fopen(StripLeadingWhitespace(line), "r");
+    if (!fp) {
+      WarnLine("mtllib not found", line_num);
+      return;
+    }
+    WavefrontMtlFile mtlfile(fp);
+    fclose(fp);
+    materials_ = mtlfile.materials();
+    for (size_t i = 0; i < materials_.size(); ++i) {
+      DrawBatch& draw_batch = material_batches_[materials_[i].name];
+      draw_batch.Init(&positions_, &texcoords_, &normals_);
+    }
+  }
+
+  void ParseUsemtl(const char* line, unsigned int line_num) {
+    std::string usemtl;
+    ToLower(StripLeadingWhitespace(line), &usemtl);
+    MaterialBatches::iterator iter = material_batches_.find(usemtl);
+    if (iter == material_batches_.end()) {
+      ErrorLine("material not found", line_num);
+    }
+    current_batch_ = &iter->second;
+  }
+
+  void WarnLine(const char* why, unsigned int line_num) const {
+    fprintf(stderr, "WARNING: %s at line %u\n", why, line_num);
+  }
+
+  void ErrorLine(const char* why, unsigned int line_num) const {
+    fprintf(stderr, "ERROR: %s at line %u\n", why, line_num);
+    exit(-1);
+  }
+
+  AttribList positions_;
+  AttribList texcoords_;
+  AttribList normals_;
+  MaterialList materials_;
+
+  // Currently, batch by texture (i.e. map_Kd).
+  MaterialBatches material_batches_;
+  DrawBatch* current_batch_;
+
+  typedef std::multimap<unsigned int, std::string> LineToGroups;
+  LineToGroups line_to_groups_;
+  std::map<std::string, int> group_counts_;
+  unsigned int current_group_line_;
+};
+
+#endif  // WEBGL_LOADER_MESH_H_
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/obj2utf8.cc
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/obj2utf8.cc	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/obj2utf8.cc	(revision 28897)
@@ -0,0 +1,135 @@
+#if 0  // A cute trick to making this .cc self-building from shell.
+g++ $0 -O2 -Wall -Werror -o `basename $0 .cc`;
+exit;
+#endif
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#include "bounds.h"
+#include "compress.h"
+#include "mesh.h"
+#include "optimize.h"
+#include "stream.h"
+
+int main(int argc, const char* argv[]) {
+  FILE* json_out = stdout;
+  if (argc != 3 && argc != 4) {
+    fprintf(stderr, "Usage: %s in.obj out.utf8\n\n"
+            "\tCompress in.obj to out.utf8 and writes JS to STDOUT.\n\n",
+            argv[0]);
+    return -1;
+  } else if (argc == 4) {
+    json_out = fopen(argv[3], "w");
+    CHECK(json_out != NULL);
+  }
+
+  FILE* fp = fopen(argv[1], "r");
+  WavefrontObjFile obj(fp);
+  fclose(fp);
+
+  fputs("{\n  \"materials\": {\n", json_out);
+  const MaterialList& materials = obj.materials();
+  for (size_t i = 0; i < materials.size(); ++i) {
+    materials[i].DumpJson(json_out);
+    const bool last = i == materials.size() - 1;
+    fputs(",\n" + last, json_out);
+  }
+  fputs("  },\n", json_out);
+  
+  const MaterialBatches& batches = obj.material_batches();
+
+  // Pass 1: compute bounds.
+  webgl_loader::Bounds bounds;
+  bounds.Clear();
+  for (MaterialBatches::const_iterator iter = batches.begin();
+       iter != batches.end(); ++iter) {
+    const DrawBatch& draw_batch = iter->second;
+    bounds.Enclose(draw_batch.draw_mesh().attribs);
+  }
+  webgl_loader::BoundsParams bounds_params = 
+      webgl_loader::BoundsParams::FromBounds(bounds);
+  fputs("  \"decodeParams\": ", json_out);
+  bounds_params.DumpJson(json_out);
+  fputs(", \"urls\": {\n", json_out);
+  // Pass 2: quantize, optimize, compress, report.
+  FILE* utf8_out_fp = fopen(argv[2], "wb");
+  CHECK(utf8_out_fp != NULL);
+  fprintf(json_out, "    \"%s\": [\n", argv[2]);
+  webgl_loader::FileSink utf8_sink(utf8_out_fp);
+  size_t offset = 0;
+  MaterialBatches::const_iterator iter = batches.begin();
+  while (iter != batches.end()) {
+    const DrawMesh& draw_mesh = iter->second.draw_mesh();
+    if (draw_mesh.indices.empty()) {
+      ++iter;
+      continue;
+    }
+    QuantizedAttribList quantized_attribs;
+    webgl_loader::AttribsToQuantizedAttribs(draw_mesh.attribs, bounds_params,
+					    &quantized_attribs);
+    VertexOptimizer vertex_optimizer(quantized_attribs);
+    const std::vector<GroupStart>& group_starts = iter->second.group_starts();
+    WebGLMeshList webgl_meshes;
+    std::vector<size_t> group_lengths;
+    for (size_t i = 1; i < group_starts.size(); ++i) {
+      const size_t here = group_starts[i-1].offset;
+      const size_t length = group_starts[i].offset - here;
+      group_lengths.push_back(length);
+      vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                    &webgl_meshes);
+    }
+    const size_t here = group_starts.back().offset;
+    const size_t length = draw_mesh.indices.size() - here;
+    CHECK(length % 3 == 0);
+    group_lengths.push_back(length);
+    vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                  &webgl_meshes);
+
+    std::vector<std::string> material;
+    std::vector<size_t> attrib_start, attrib_length, index_start, index_length;
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      const size_t num_attribs = webgl_meshes[i].attribs.size();
+      const size_t num_indices = webgl_meshes[i].indices.size();
+      CHECK(num_attribs % 8 == 0);
+      CHECK(num_indices % 3 == 0);
+      webgl_loader::CompressQuantizedAttribsToUtf8(webgl_meshes[i].attribs, 
+						   &utf8_sink);
+      webgl_loader::CompressIndicesToUtf8(webgl_meshes[i].indices, &utf8_sink);
+      material.push_back(iter->first);
+      attrib_start.push_back(offset);
+      attrib_length.push_back(num_attribs / 8);
+      index_start.push_back(offset + num_attribs);
+      index_length.push_back(num_indices / 3);
+      offset += num_attribs + num_indices;
+    }
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      fprintf(json_out,
+              "      { \"material\": \"%s\",\n"
+              "        \"attribRange\": [" PRIuS ", " PRIuS "],\n"
+              "        \"indexRange\": [" PRIuS ", " PRIuS "]\n"
+              "      }",
+              material[i].c_str(),
+              attrib_start[i], attrib_length[i],
+              index_start[i], index_length[i]);
+      if (i != webgl_meshes.size() - 1) {
+        fputs(",\n", json_out);
+      }
+    }
+    const bool last = (++iter == batches.end());
+    fputs(",\n" + last, json_out);
+  }
+  fputs("    ]\n", json_out);
+  fputs("  }\n}", json_out);
+  return 0;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/obj2utf8x.cc
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/obj2utf8x.cc	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/obj2utf8x.cc	(revision 28897)
@@ -0,0 +1,138 @@
+#if 0  // A cute trick to making this .cc self-building from shell.
+g++ $0 -O2 -Wall -Werror -o `basename $0 .cc`;
+exit;
+#endif
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#include "bounds.h"
+#include "compress.h"
+#include "mesh.h"
+#include "optimize.h"
+#include "stream.h"
+
+int main(int argc, const char* argv[]) {
+  FILE* json_out = stdout;
+  if (argc != 3 && argc != 4) {
+    fprintf(stderr, "Usage: %s in.obj out.utf8\n\n"
+            "\tCompress in.obj to out.utf8 and writes JS to STDOUT.\n\n",
+            argv[0]);
+    return -1;
+  } else if (argc == 4) {
+    json_out = fopen(argv[3], "w");
+    CHECK(json_out != NULL);
+  }
+
+  FILE* fp = fopen(argv[1], "r");
+  WavefrontObjFile obj(fp);
+  fclose(fp);
+
+  fputs("{\n  \"materials\": {\n", json_out);
+  const MaterialList& materials = obj.materials();
+  for (size_t i = 0; i < materials.size(); ++i) {
+    materials[i].DumpJson(json_out);
+    const bool last = i == materials.size() - 1;
+    fputs(",\n" + last, json_out);
+  }
+  fputs("  },\n", json_out);
+  
+  const MaterialBatches& batches = obj.material_batches();
+
+  // Pass 1: compute bounds.
+  webgl_loader::Bounds bounds;
+  bounds.Clear();
+  for (MaterialBatches::const_iterator iter = batches.begin();
+       iter != batches.end(); ++iter) {
+    const DrawBatch& draw_batch = iter->second;
+    bounds.Enclose(draw_batch.draw_mesh().attribs);
+  }
+  webgl_loader::BoundsParams bounds_params = 
+      webgl_loader::BoundsParams::FromBounds(bounds);
+  fputs("  \"decodeParams\": ", json_out);
+  bounds_params.DumpJson(json_out);
+  fputs(",\n  \"urls\": {\n", json_out);
+  // Pass 2: quantize, optimize, compress, report.
+  FILE* utf8_out_fp = fopen(argv[2], "wb");
+  CHECK(utf8_out_fp != NULL);
+  fprintf(json_out, "    \"%s\": [\n", argv[2]);
+  webgl_loader::FileSink utf8_sink(utf8_out_fp);
+  size_t offset = 0;
+  MaterialBatches::const_iterator iter = batches.begin();
+  while (iter != batches.end()) {
+    const DrawMesh& draw_mesh = iter->second.draw_mesh();
+    if (draw_mesh.indices.empty()) {
+      ++iter;
+      continue;
+    }
+    QuantizedAttribList quantized_attribs;
+    webgl_loader::AttribsToQuantizedAttribs(draw_mesh.attribs, bounds_params,
+					    &quantized_attribs);
+    VertexOptimizer vertex_optimizer(quantized_attribs);
+    const std::vector<GroupStart>& group_starts = iter->second.group_starts();
+    WebGLMeshList webgl_meshes;
+    std::vector<size_t> group_lengths;
+    for (size_t i = 1; i < group_starts.size(); ++i) {
+      const size_t here = group_starts[i-1].offset;
+      const size_t length = group_starts[i].offset - here;
+      group_lengths.push_back(length);
+      vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                    &webgl_meshes);
+    }
+    const size_t here = group_starts.back().offset;
+    const size_t length = draw_mesh.indices.size() - here;
+    CHECK(length % 3 == 0);
+    group_lengths.push_back(length);
+    vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                  &webgl_meshes);
+
+    std::vector<std::string> material;
+    // TODO: is this buffering still necessary?
+    std::vector<size_t> attrib_start, attrib_length, 
+        code_start, code_length, num_tris;
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      const size_t num_attribs = webgl_meshes[i].attribs.size();
+      const size_t num_indices = webgl_meshes[i].indices.size();
+      CHECK(num_attribs % 8 == 0);
+      CHECK(num_indices % 3 == 0);
+      webgl_loader::EdgeCachingCompressor compressor(webgl_meshes[i].attribs,
+                                                     webgl_meshes[i].indices);
+      compressor.Compress(&utf8_sink);
+      material.push_back(iter->first);
+      attrib_start.push_back(offset);
+      attrib_length.push_back(num_attribs / 8);
+      code_start.push_back(offset + num_attribs);
+      code_length.push_back(compressor.codes().size());
+      num_tris.push_back(num_indices / 3);
+      offset += num_attribs + compressor.codes().size();
+    }
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      fprintf(json_out,
+              "      { \"material\": \"%s\",\n"
+              "        \"attribRange\": [" PRIuS ", " PRIuS "],\n"
+              "        \"codeRange\": [" PRIuS ", " PRIuS ", " PRIuS "]\n"
+              "      }",
+              material[i].c_str(),
+              attrib_start[i], attrib_length[i],
+              code_start[i], code_length[i], num_tris[i]);
+      if (i != webgl_meshes.size() - 1) {
+        fputs(",\n", json_out);
+      }
+    }
+    const bool last = (++iter == batches.end());
+    fputs(",\n" + last, json_out);
+  }
+  fputs("    ]\n", json_out);
+  fputs("  }\n}", json_out);
+  return 0;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/objcompress.cc
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/objcompress.cc	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/objcompress.cc	(revision 28897)
@@ -0,0 +1,165 @@
+#if 0  // A cute trick to making this .cc self-building from shell.
+g++ $0 -O2 -Wall -Werror -o `basename $0 .cc`;
+exit;
+#endif
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#include "bounds.h"
+#include "compress.h"
+#include "mesh.h"
+#include "optimize.h"
+#include "stream.h"
+
+int main(int argc, const char* argv[]) {
+  if (argc != 3) {
+    fprintf(stderr, "Usage: %s in.obj out.utf8\n\n"
+            "\tCompress in.obj to out.utf8 and writes JS to STDOUT.\n\n",
+            argv[0]);
+    return -1;
+  }
+  FILE* fp = fopen(argv[1], "r");
+  WavefrontObjFile obj(fp);
+  fclose(fp);
+
+  printf("MODELS[\'%s\'] = {\n", StripLeadingDir(argv[1]));
+  puts("  materials: {");
+  const MaterialList& materials = obj.materials();
+  for (size_t i = 0; i < materials.size(); ++i) {
+    materials[i].DumpJson();
+  }
+  puts("  },");
+  
+  const MaterialBatches& batches = obj.material_batches();
+
+  // Pass 1: compute bounds.
+  webgl_loader::Bounds bounds;
+  bounds.Clear();
+  for (MaterialBatches::const_iterator iter = batches.begin();
+       iter != batches.end(); ++iter) {
+    const DrawBatch& draw_batch = iter->second;
+    bounds.Enclose(draw_batch.draw_mesh().attribs);
+  }
+  webgl_loader::BoundsParams bounds_params = 
+    webgl_loader::BoundsParams::FromBounds(bounds);
+  printf("  decodeParams: ");
+  bounds_params.DumpJson();
+
+  puts("  urls: {");
+  std::vector<char> utf8;
+  webgl_loader::VectorSink sink(&utf8);
+  // Pass 2: quantize, optimize, compress, report.
+  for (MaterialBatches::const_iterator iter = batches.begin();
+       iter != batches.end(); ++iter) {
+    size_t offset = 0;
+    utf8.clear();
+    const DrawMesh& draw_mesh = iter->second.draw_mesh();
+    if (draw_mesh.indices.empty()) continue;
+    
+    QuantizedAttribList quantized_attribs;
+    webgl_loader::AttribsToQuantizedAttribs(draw_mesh.attribs, bounds_params,
+					    &quantized_attribs);
+    VertexOptimizer vertex_optimizer(quantized_attribs);
+    const std::vector<GroupStart>& group_starts = iter->second.group_starts();
+    WebGLMeshList webgl_meshes;
+    std::vector<size_t> group_lengths;
+    for (size_t i = 1; i < group_starts.size(); ++i) {
+      const size_t here = group_starts[i-1].offset;
+      const size_t length = group_starts[i].offset - here;
+      group_lengths.push_back(length);
+      vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                    &webgl_meshes);
+    }
+    const size_t here = group_starts.back().offset;
+    const size_t length = draw_mesh.indices.size() - here;
+    const bool divisible_by_3 = length % 3 == 0;
+    CHECK(divisible_by_3);
+    group_lengths.push_back(length);
+    vertex_optimizer.AddTriangles(&draw_mesh.indices[here], length,
+                                  &webgl_meshes);
+
+    std::vector<std::string> material;
+    std::vector<size_t> attrib_start, attrib_length, index_start, index_length;
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      const size_t num_attribs = webgl_meshes[i].attribs.size();
+      const size_t num_indices = webgl_meshes[i].indices.size();
+      const bool kBadSizes = num_attribs % 8 || num_indices % 3;
+      CHECK(!kBadSizes);
+      webgl_loader::CompressQuantizedAttribsToUtf8(webgl_meshes[i].attribs, 
+						   &sink);
+      webgl_loader::CompressIndicesToUtf8(webgl_meshes[i].indices, &sink);
+      material.push_back(iter->first);
+      attrib_start.push_back(offset);
+      attrib_length.push_back(num_attribs / 8);
+      index_start.push_back(offset + num_attribs);
+      index_length.push_back(num_indices / 3);
+      offset += num_attribs + num_indices;
+    }
+    const uint32 hash = SimpleHash(&utf8[0], utf8.size());
+    char buf[9] = { '\0' };
+    ToHex(hash, buf);
+    // TODO: this needs to handle paths.
+    std::string out_fn = std::string(buf) + "." + argv[2];
+    FILE* out_fp = fopen(out_fn.c_str(), "wb");
+    printf("    \'%s\': [\n", out_fn.c_str());
+    size_t group_index = 0;
+    for (size_t i = 0; i < webgl_meshes.size(); ++i) {
+      printf("      { material: \'%s\',\n"
+             "        attribRange: [" PRIuS ", " PRIuS "],\n"
+             "        indexRange: [" PRIuS ", " PRIuS "],\n"
+             "        bboxes: " PRIuS ",\n"
+             "        names: [",
+             material[i].c_str(),
+             attrib_start[i], attrib_length[i],
+             index_start[i], index_length[i],
+             offset);
+      std::vector<size_t> buffered_lengths;
+      size_t group_start = 0;
+      while (group_index < group_lengths.size()) {
+        printf("\'%s\', ",
+               obj.LineToGroup(group_starts[group_index].group_line).c_str());
+        const size_t group_length = group_lengths[group_index];
+        const size_t next_start = group_start + group_length;
+        const size_t webgl_index_length = webgl_meshes[i].indices.size();
+        // TODO: bbox info is better placed at the head of the file,
+        // perhaps transposed. Also, when a group gets split between
+        // batches, the bbox gets stored twice.
+	webgl_loader::CompressAABBToUtf8(group_starts[group_index].bounds,
+					 bounds_params, &sink);
+        offset += 6;
+        if (next_start < webgl_index_length) {
+          buffered_lengths.push_back(group_length);
+          group_start = next_start;
+          ++group_index;
+        } else {
+          const size_t fits = webgl_index_length - group_start;
+          buffered_lengths.push_back(fits);
+          group_start = 0;
+          group_lengths[group_index] -= fits;
+          break;
+        }
+      }
+      printf("],\n        lengths: [");
+      for (size_t k = 0; k < buffered_lengths.size(); ++k) {
+        printf(PRIuS ", ", buffered_lengths[k]);
+      }
+      puts("],\n      },");
+    }
+    fwrite(&utf8[0], 1, utf8.size(), out_fp);
+    fclose(out_fp);
+    puts("    ],");
+  }
+  puts("  }\n};");
+  return 0;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/optimize.h
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/optimize.h	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/optimize.h	(revision 28897)
@@ -0,0 +1,273 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_OPTIMIZE_H_
+#define WEBGL_LOADER_OPTIMIZE_H_
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "base.h"
+
+// TODO: since most vertices are part of 6 faces, you can optimize
+// this by using a small inline buffer.
+typedef std::vector<int> FaceList;
+
+// Linear-Speed Vertex Cache Optimisation, via:
+// http://home.comcast.net/~tom_forsyth/papers/fast_vert_cache_opt.html
+class VertexOptimizer {
+ public:
+  struct TriangleData {
+    bool active;  // true iff triangle has not been optimized and emitted.
+    // TODO: eliminate some wasted computation by using this cache.
+    // float score;
+  };
+
+  VertexOptimizer(const QuantizedAttribList& attribs)
+      : attribs_(attribs),
+        per_vertex_(attribs_.size() / 8),
+        next_unused_index_(0)
+  {
+    // The cache has an extra slot allocated to simplify the logic in
+    // InsertIndexToCache.
+    for (unsigned int i = 0; i < kCacheSize + 1; ++i) {
+      cache_[i] = kUnknownIndex;
+    }
+
+    // Initialize per-vertex state.
+    for (size_t i = 0; i < per_vertex_.size(); ++i) {
+      VertexData& vertex_data = per_vertex_[i];
+      vertex_data.cache_tag = kCacheSize;
+      vertex_data.output_index = kMaxOutputIndex;
+    }
+  }
+
+  void AddTriangles(const int* indices, size_t length,
+                    WebGLMeshList* meshes) {
+    std::vector<TriangleData> per_tri(length / 3);
+
+    // Loop through the triangles, updating vertex->face lists.
+    for (size_t i = 0; i < per_tri.size(); ++i) {
+      per_tri[i].active = true;
+      per_vertex_[indices[3*i + 0]].faces.push_back(i);
+      per_vertex_[indices[3*i + 1]].faces.push_back(i);
+      per_vertex_[indices[3*i + 2]].faces.push_back(i);
+    }
+
+    // TODO: with index bounds, no need to recompute everything.
+    // Compute initial vertex scores.
+    for (size_t i = 0; i < per_vertex_.size(); ++i) {
+      VertexData& vertex_data = per_vertex_[i];
+      vertex_data.cache_tag = kCacheSize;
+      vertex_data.output_index = kMaxOutputIndex;
+      vertex_data.UpdateScore();
+    }
+
+    // Prepare output.
+    if (meshes->empty()) {
+      meshes->push_back(WebGLMesh());
+    }
+    WebGLMesh* mesh = &meshes->back();
+
+    // Consume indices, one triangle at a time.
+    for (size_t c = 0; c < per_tri.size(); ++c) {
+      const int best_triangle = FindBestTriangle(indices, per_tri);
+      per_tri[best_triangle].active = false;
+
+      // Iterate through triangle indices.
+      for (size_t i = 0; i < 3; ++i) {
+        const int index = indices[3*best_triangle + i];
+        VertexData& vertex_data = per_vertex_[index];
+        vertex_data.RemoveFace(best_triangle);
+      
+        InsertIndexToCache(index);
+        const int cached_output_index = per_vertex_[index].output_index;
+        // Have we seen this index before?
+        if (cached_output_index != kMaxOutputIndex) {
+          mesh->indices.push_back(cached_output_index);
+          continue;
+        }
+        // The first time we see an index, not only do we increment
+        // next_unused_index_ counter, but we must also copy the
+        // corresponding attributes.  TODO: do quantization here?
+        per_vertex_[index].output_index = next_unused_index_;
+        for (size_t j = 0; j < 8; ++j) {
+          mesh->attribs.push_back(attribs_[8*index + j]);
+        }
+        mesh->indices.push_back(next_unused_index_++);
+      }
+      // Check if there is room for another triangle.
+      if (next_unused_index_ > kMaxOutputIndex - 3) {
+        // Is it worth figuring out which other triangles can be added
+        // given the verties already added? Then, perhaps
+        // re-optimizing?
+        next_unused_index_ = 0;
+        meshes->push_back(WebGLMesh());
+        mesh = &meshes->back();
+        for (size_t i = 0; i <= kCacheSize; ++i) {
+          cache_[i] = kUnknownIndex;
+        }
+        for (size_t i = 0; i < per_vertex_.size(); ++i) {
+          per_vertex_[i].output_index = kMaxOutputIndex;
+        }
+      }
+    }
+  }
+ private:
+  static const int kUnknownIndex = -1;
+  static const uint16 kMaxOutputIndex = 0xD800;
+  static const size_t kCacheSize = 32;  // Does larger improve compression?
+
+  struct VertexData {
+    // Should this also update scores for incident triangles?
+    void UpdateScore() {
+      const size_t active_tris = faces.size();
+      if (active_tris <= 0) {
+        score = -1.f;
+        return;
+      }
+      // TODO: build initial score table.
+      if (cache_tag < 3) {
+        // The most recent triangle should has a fixed score to
+        // discourage generating nothing but really long strips. If we
+        // want strips, we should use a different optimizer.
+        const float kLastTriScore = 0.75f;
+        score = kLastTriScore;
+      } else if (cache_tag < kCacheSize) {
+        // Points for being recently used.
+        const float kScale = 1.f / (kCacheSize - 3);
+        const float kCacheDecayPower = 1.5f;
+        score = powf(1.f - kScale * (cache_tag - 3), kCacheDecayPower);
+      } else {
+        // Not in cache.
+        score = 0.f;
+      }
+
+      // Bonus points for having a low number of tris still to use the
+      // vert, so we get rid of lone verts quickly.
+      const float kValenceBoostScale = 2.0f;
+      const float kValenceBoostPower = 0.5f;
+      // rsqrt?
+      const float valence_boost = powf(active_tris, -kValenceBoostPower);
+      score += valence_boost * kValenceBoostScale;
+    }
+
+    // TODO: this assumes that "tri" is in the list!
+    void RemoveFace(int tri) {
+      FaceList::iterator face = faces.begin();
+      while (*face != tri) ++face;
+      *face = faces.back();
+      faces.pop_back();
+    }
+
+    FaceList faces;
+    unsigned int cache_tag;  // kCacheSize means not in cache.
+    float score;
+    uint16 output_index;
+  };
+
+  int FindBestTriangle(const int* indices,
+                       const std::vector<TriangleData>& per_tri) {
+    float best_score = -HUGE_VALF;
+    int best_triangle = -1;
+
+    // The trick to making this algorithm run in linear time (with
+    // respect to the vertices) is to only scan the triangles incident
+    // on the simulated cache for the next triangle. It is an
+    // approximation, but the score is heuristic. Anyway, most of the
+    // time the best triangle will be found this way.
+    for (size_t i = 0; i < kCacheSize; ++i) {
+      if (cache_[i] == kUnknownIndex) {
+        break;
+      }
+      const VertexData& vertex_data = per_vertex_[cache_[i]];
+      for (size_t j = 0; j < vertex_data.faces.size(); ++j) {
+        const int tri_index = vertex_data.faces[j];
+        if (per_tri[tri_index].active) {
+          const float score =
+              per_vertex_[indices[3*tri_index + 0]].score +
+              per_vertex_[indices[3*tri_index + 1]].score +
+              per_vertex_[indices[3*tri_index + 2]].score;
+          if (score > best_score) {
+            best_score = score;
+            best_triangle = tri_index;
+          }
+        }
+      }
+    }
+    // TODO: keep a range of active triangles to make the slow scan a
+    // little faster. Does this ever happen?
+    if (best_triangle == -1) {
+      // If no triangles can be found through the cache (e.g. for the
+      // first triangle) go through all the active triangles and find
+      // the best one.
+      for (size_t i = 0; i < per_tri.size(); ++i) {
+        if (per_tri[i].active) {
+          const float score =
+              per_vertex_[indices[3*i + 0]].score +
+              per_vertex_[indices[3*i + 1]].score +
+              per_vertex_[indices[3*i + 2]].score;
+          if (score > best_score) {
+            best_score = score;
+            best_triangle = i;
+          }
+        }
+      }
+      CHECK(-1 != best_triangle);
+    }
+    return best_triangle;
+  }
+
+  // TODO: faster to update an entire triangle.
+  // This also updates the vertex scores!
+  void InsertIndexToCache(int index) {
+    // Find how recently the vertex was used.
+    const unsigned int cache_tag = per_vertex_[index].cache_tag;
+
+    // Don't do anything if the vertex is already at the head of the
+    // LRU list.
+    if (cache_tag == 0) return;
+
+    // Loop through the cache, inserting the index at the front, and
+    // bubbling down to where the index was originally found. If the
+    // index was not originally in the cache, then it claims to be at
+    // the (kCacheSize + 1)th entry, and we use an extra slot to make
+    // that case simpler.
+    int to_insert = index;
+    for (unsigned int i = 0; i <= cache_tag; ++i) {
+      const int current_index = cache_[i];
+
+      // Update cross references between the entry of the cache and
+      // the per-vertex data.
+      cache_[i] = to_insert;
+      per_vertex_[to_insert].cache_tag = i;
+      per_vertex_[to_insert].UpdateScore();
+      
+      // No need to continue if we find an empty entry.
+      if (current_index == kUnknownIndex) {
+        break;
+      }
+      
+      to_insert = current_index;
+    }
+  }
+
+  const QuantizedAttribList& attribs_;
+  std::vector<VertexData> per_vertex_;
+  int cache_[kCacheSize + 1];
+  uint16 next_unused_index_;
+};
+
+#endif  // WEBGL_LOADER_OPTIMIZE_H_
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/stream.h
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/stream.h	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/stream.h	(revision 28897)
@@ -0,0 +1,272 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_STREAM_H_
+#define WEBGL_LOADER_STREAM_H_
+
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+#include "base.h"
+
+namespace webgl_loader {
+
+// An abstract interface to allow appending bytes to various streams.
+class ByteSinkInterface {
+ public:
+  virtual void Put(char c) = 0;
+  virtual size_t PutN(const char* data, size_t len) = 0;
+  virtual ~ByteSinkInterface() { }
+
+ protected:
+  ByteSinkInterface() { }
+
+ private:
+  // Disallow copy and assignment.
+  ByteSinkInterface(const ByteSinkInterface&);
+  void operator=(const ByteSinkInterface&);
+};
+
+// None of the concrete implementations actually own the backing data.
+// They should be safe to copy.
+
+class NullSink : public ByteSinkInterface {
+ public:
+  NullSink() { }
+
+  virtual void Put(char) { }
+  
+  virtual size_t PutN(const char*, size_t len) { return len; }
+};
+
+class FileSink : public ByteSinkInterface {
+ public:
+  // |fp| is unowned and must not be NULL.
+  explicit FileSink(FILE* fp)
+    : fp_(fp) {
+  }
+
+  virtual void Put(char c) {
+    PutChar(c, fp_);
+  }
+
+  virtual size_t PutN(const char* data, size_t len) {
+    return fwrite(data, 1, len, fp_);
+  }
+
+ private:
+  FILE *fp_;  // unowned.
+};
+
+class VectorSink : public ByteSinkInterface {
+ public:
+  // |vec| is unowned and must not be NULL.
+  explicit VectorSink(std::vector<char>* vec)
+    : vec_(vec) {
+  }
+  
+  virtual void Put(char c) {
+    vec_->push_back(c);
+  }
+
+  virtual size_t PutN(const char* data, size_t len) {
+    vec_->insert(vec_->end(), data, data + len);
+    return len;
+  }
+
+ private:
+  std::vector<char>* vec_;  // unowned.
+};
+
+class StringSink : public ByteSinkInterface {
+ public:
+  // |str| is unowned and must not be NULL.
+  explicit StringSink(std::string* str)
+    : str_(str) {
+    DCHECK(str != NULL);
+  }
+
+  virtual void Put(char c) {
+    str_->push_back(c);
+  }
+
+  virtual size_t PutN(const char* data, size_t len) {
+    str_->append(data, len);
+    return len;
+  }
+
+ private:
+  std::string* str_;  // unowned.
+};
+
+class ByteHistogramSink : public ByteSinkInterface {
+ public:
+  // |sink| in unowned and must not be NULL.
+  explicit ByteHistogramSink(ByteSinkInterface* sink)
+      : sink_(sink) {
+    memset(histo_, 0, sizeof(histo_));
+  }
+
+  virtual void Put(char c) {
+    histo_[static_cast<uint8>(c)]++;
+    sink_->Put(c);
+  }
+
+  virtual size_t PutN(const char* data, size_t len) {
+    const char* const end = data + len;
+    for (const char* iter = data; iter != end; ++iter) {
+      histo_[static_cast<uint8>(*iter)]++;
+    }
+    return sink_->PutN(data, len);
+  }
+
+  const size_t* histo() const {
+    return histo_;
+  }
+
+ private:
+  size_t histo_[256];
+  ByteSinkInterface* sink_;  // unowned.
+};
+
+// TODO: does it make sense to have a global enum? How should 
+// new BufferedInput implementations define new error codes? 
+enum ErrorCode {
+  kNoError = 0, 
+  kEndOfFile = 1,
+  kFileError = 2,  // TODO: translate errno.
+};
+
+// Adapted from ryg's BufferedStream abstraction:
+// http://fgiesen.wordpress.com/2011/11/21/buffer-centric-io/
+class BufferedInput {
+ public:
+  typedef ErrorCode (*Refiller)(BufferedInput*);
+
+  BufferedInput(Refiller refiller = RefillZeroes)
+      : cursor(NULL),
+        begin_(NULL),
+        end_(NULL),
+        refiller_(refiller),
+        error_(kNoError) {
+  }
+
+  // InitFromMemory.
+  BufferedInput(const char* data, size_t length)
+      : cursor(data),
+        begin_(data),
+        end_(data + length),
+        refiller_(RefillEndOfFile),
+        error_(kNoError) {
+  }
+
+  const char* begin() const {
+    return begin_;
+  }
+
+  const char* end() const {
+    return end_;
+  }
+
+  const char* cursor;
+
+  ErrorCode error() const {
+    DCHECK(begin() <= cursor);
+    DCHECK(cursor <= end());
+    return error_;
+  }
+
+  ErrorCode Refill() {
+    DCHECK(begin() <= cursor);
+    DCHECK(cursor <= end());
+    if (cursor == end()) {
+      error_ = refiller_(this);
+    }
+    return error_;
+  }
+
+ protected:
+  static ErrorCode RefillZeroes(BufferedInput* bi) {
+    static const char kZeroes[64] = { 0 };
+    bi->cursor = kZeroes;
+    bi->begin_ = kZeroes;
+    bi->end_ = kZeroes + sizeof(kZeroes);
+    return bi->error_;
+  }
+
+  static ErrorCode RefillEndOfFile(BufferedInput* bi) {
+    return bi->fail(kEndOfFile);
+  }
+
+  ErrorCode fail(ErrorCode why) {
+    error_ = why;
+    refiller_ = RefillZeroes;
+    return Refill();
+  }
+
+  const char* begin_;
+  const char* end_;
+  Refiller refiller_;
+  ErrorCode error_;
+
+ private:
+  // Disallow copy and assign.
+  BufferedInput(const BufferedInput&);
+  void operator=(const BufferedInput&);
+};
+
+class BufferedInputStream : public BufferedInput {
+ public:
+  BufferedInputStream(FILE* fp, char* buf, size_t size)
+      : BufferedInput(RefillFread),
+        fp_(fp),
+        buf_(buf),
+        size_(size) {
+    DCHECK(buf != NULL);
+    // Disable buffering since we're doing it ourselves.
+    // TODO check error.
+    setvbuf(fp_, NULL, _IONBF, 0);
+    cursor = buf;
+    begin_ = buf;
+    end_ = buf;
+  }
+ protected:
+  // TODO: figure out how to automate this casting pattern.
+  static ErrorCode RefillFread(BufferedInput* bi) {
+    return static_cast<BufferedInputStream*>(bi)->DoRefillFread();
+  }
+ private:
+  ErrorCode DoRefillFread() {
+    const size_t bytes_read = fread(buf_, 1, size_, fp_);
+    cursor = begin_;
+    end_ = begin_ + bytes_read;
+    if (bytes_read < size_) {
+      if (feof(fp_)) {
+        refiller_ = RefillEndOfFile;
+      } else if (ferror(fp_)) {
+        return fail(kFileError);
+      }
+    }
+    return kNoError;
+  }
+
+  FILE* fp_;
+  char* buf_;
+  size_t size_;
+};
+
+}  // namespace webgl_loader
+
+#endif  // WEBGL_LOADER_STREAM_H_
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/utf8.h
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/utf8.h	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/converters/utf8/src/utf8.h	(revision 28897)
@@ -0,0 +1,61 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you
+// may not use this file except in compliance with the License. You
+// may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+#ifndef WEBGL_LOADER_UTF8_H_
+#define WEBGL_LOADER_UTF8_H_
+
+#include "base.h"
+#include "stream.h"
+
+namespace webgl_loader {
+
+const uint8 kUtf8MoreBytesPrefix = 0x80;
+const uint8 kUtf8TwoBytePrefix = 0xC0;
+const uint8 kUtf8ThreeBytePrefix = 0xE0;
+
+const uint16 kUtf8TwoByteLimit = 0x0800;
+const uint16 kUtf8SurrogatePairStart = 0xD800;
+const uint16 kUtf8SurrogatePairNum = 0x0800;
+const uint16 kUtf8EncodableEnd = 0x10000 - kUtf8SurrogatePairNum;
+
+const uint16 kUtf8MoreBytesMask = 0x3F;
+
+bool Uint16ToUtf8(uint16 word, ByteSinkInterface* sink) {
+  if (word < 0x80) {
+    sink->Put(static_cast<char>(word));
+  } else if (word < kUtf8TwoByteLimit) {
+    sink->Put(static_cast<char>(kUtf8TwoBytePrefix + (word >> 6)));
+    sink->Put(static_cast<char>(kUtf8MoreBytesPrefix +
+				(word & kUtf8MoreBytesMask)));
+  } else if (word < kUtf8EncodableEnd) {
+    // We can only encode 65535 - 2048 values because of illegal UTF-8
+    // characters, such as surrogate pairs in [0xD800, 0xDFFF].
+    if (word >= kUtf8SurrogatePairStart) {
+      // Shift the result to avoid the surrogate pair range.
+      word += kUtf8SurrogatePairNum;
+    }
+    sink->Put(static_cast<char>(kUtf8ThreeBytePrefix + (word >> 12)));
+    sink->Put(static_cast<char>(kUtf8MoreBytesPrefix +
+				((word >> 6) & kUtf8MoreBytesMask)));
+    sink->Put(static_cast<char>(kUtf8MoreBytesPrefix +
+				(word & kUtf8MoreBytesMask)));
+  } else {
+    return false;
+  }
+  return true;
+}
+
+}  // namespace webgl_loader
+
+#endif  // WEBGL_LOADER_UTF8_H_
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublime.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublime.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublime.py	(revision 28897)
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+import sys
+
+if sys.version_info < (2, 7):
+	print("This script requires at least Python 2.7.")
+	print("Please, update to a newer version: http://www.python.org/download/releases/")
+	exit()
+
+import argparse
+import json
+import os
+import re
+import shutil
+import tempfile
+
+
+def main(argv=None):
+
+	parser = argparse.ArgumentParser()
+	parser.add_argument('--include', action='append', required=True)
+	parser.add_argument('--output', default='sublimetext2/threejs.sublime-completions')
+
+	args = parser.parse_args()
+
+	output = args.output
+
+	# parsing
+
+	print(' * Generating ' + output)
+
+	fd, path = tempfile.mkstemp()
+	tmp = open(path, 'w')
+	tmp.write('{\n\t"scope": "source.js,source.js.embedded.html,source.coffee",\n\t"version": "r55",\n\t"completions":\n\t[\n')
+
+	for include in args.include:
+		with open('../build/includes/' + include + '.json','r') as f: files = json.load(f)
+		for filename in files:
+			filename = '../../' + filename;
+			with open(filename, 'r') as f:
+				string = f.read()
+				match = re.search('THREE.(\w+)[\ ]+?=[\ ]+?function[\ ]+\(([\w\,\ ]+)?\)', string)
+				if match:
+					name = match.group(1)
+					parameters = match.group(2)
+					if parameters is None:
+						parameters = ''
+					else:
+						array = parameters.split( ',' )
+						for i in range(len(array)):
+							array[i] = '${'+str(i+1)+':'+array[i].strip()+'}' # ${1:param}
+						parameters = ' '+', '.join(array)+' '
+					tmp.write('\t\t{ "trigger": "THREE.'+name+'", "contents": "THREE.'+name+'('+parameters+')$0" },\n' )
+
+	tmp.write("\t\t\"THREE\"\n\t]\n}")
+	tmp.close()
+
+	# save
+
+	shutil.copy(path, output)
+	os.chmod(output, 0o664); # temp files would usually get 0600
+
+
+if __name__ == "__main__":
+	main()
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublime.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublime.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublime.sh	(revision 28897)
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+python sublime.py --include common --include extras --output sublimetext2/threejs.sublime-completions
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/README.md
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/README.md	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/README.md	(revision 28897)
@@ -0,0 +1,9 @@
+### How to install
+
+1. Compress `threejs.sublime-completions` into `threejs.sublime-package`.
+
+```shell
+zip threejs.sublime-package threejs.sublime-completions
+```
+
+2. Copy the compressed file into `Sublime Text 2/Pristine Packages`.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/threejs.sublime-completions
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/threejs.sublime-completions	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/editors/sublimetext2/threejs.sublime-completions	(revision 28897)
@@ -0,0 +1,143 @@
+{
+	"scope": "source.js,source.js.embedded.html,source.coffee,source.ts",
+	"version": "r55",
+	"completions":
+	[
+		{ "trigger": "THREE.extend", "contents": "THREE.extend( ${1:obj}, ${2:source} )$0" },
+		{ "trigger": "THREE.Color", "contents": "THREE.Color( ${1:value} )$0" },
+		{ "trigger": "THREE.Quaternion", "contents": "THREE.Quaternion( ${1:x}, ${2:y}, ${3:z}, ${4:w} )$0" },
+		{ "trigger": "THREE.Vector2", "contents": "THREE.Vector2( ${1:x}, ${2:y} )$0" },
+		{ "trigger": "THREE.Vector3", "contents": "THREE.Vector3( ${1:x}, ${2:y}, ${3:z} )$0" },
+		{ "trigger": "THREE.Vector4", "contents": "THREE.Vector4( ${1:x}, ${2:y}, ${3:z}, ${4:w} )$0" },
+		{ "trigger": "THREE.Euler", "contents": "THREE.Euler( ${1:x}, ${2:y}, ${3:z}, ${4:order} )$0" },
+		{ "trigger": "THREE.Line3", "contents": "THREE.Line3( ${1:start}, ${2:end} )$0" },
+		{ "trigger": "THREE.Box2", "contents": "THREE.Box2( ${1:min}, ${2:max} )$0" },
+		{ "trigger": "THREE.Box3", "contents": "THREE.Box3( ${1:min}, ${2:max} )$0" },
+		{ "trigger": "THREE.Matrix3", "contents": "THREE.Matrix3( ${1:n11}, ${2:n12}, ${3:n13}, ${4:n21}, ${5:n22}, ${6:n23}, ${7:n31}, ${8:n32}, ${9:n33} )$0" },
+		{ "trigger": "THREE.Matrix4", "contents": "THREE.Matrix4( ${1:n11}, ${2:n12}, ${3:n13}, ${4:n14}, ${5:n21}, ${6:n22}, ${7:n23}, ${8:n24}, ${9:n31}, ${10:n32}, ${11:n33}, ${12:n34}, ${13:n41}, ${14:n42}, ${15:n43}, ${16:n44} )$0" },
+		{ "trigger": "THREE.Ray", "contents": "THREE.Ray( ${1:origin}, ${2:direction} )$0" },
+		{ "trigger": "THREE.Sphere", "contents": "THREE.Sphere( ${1:center}, ${2:radius} )$0" },
+		{ "trigger": "THREE.Frustum", "contents": "THREE.Frustum( ${1:p0}, ${2:p1}, ${3:p2}, ${4:p3}, ${5:p4}, ${6:p5} )$0" },
+		{ "trigger": "THREE.Plane", "contents": "THREE.Plane( ${1:normal}, ${2:constant} )$0" },
+		{ "trigger": "THREE.Spline", "contents": "THREE.Spline( ${1:points} )$0" },
+		{ "trigger": "THREE.Triangle", "contents": "THREE.Triangle( ${1:a}, ${2:b}, ${3:c} )$0" },
+		{ "trigger": "THREE.Vertex", "contents": "THREE.Vertex( ${1:v} )$0" },
+		{ "trigger": "THREE.UV", "contents": "THREE.UV( ${1:u}, ${2:v} )$0" },
+		{ "trigger": "THREE.Clock", "contents": "THREE.Clock( ${1:autoStart} )$0" },
+		{ "trigger": "THREE.EventDispatcher", "contents": "THREE.EventDispatcher()$0" },
+		{ "trigger": "THREE.Raycaster", "contents": "THREE.Raycaster( ${1:origin}, ${2:direction}, ${3:near}, ${4:far} )$0" },
+		{ "trigger": "THREE.Object3D", "contents": "THREE.Object3D()$0" },
+		{ "trigger": "THREE.Projector", "contents": "THREE.Projector()$0" },
+		{ "trigger": "THREE.Face3", "contents": "THREE.Face3( ${1:a}, ${2:b}, ${3:c}, ${4:normal}, ${5:color}, ${6:materialIndex} )$0" },
+		{ "trigger": "THREE.Face4", "contents": "THREE.Face4( ${1:a}, ${2:b}, ${3:c}, ${4:d}, ${5:normal}, ${6:color}, ${7:materialIndex} )$0" },
+		{ "trigger": "THREE.Geometry", "contents": "THREE.Geometry()$0" },
+		{ "trigger": "THREE.BufferGeometry", "contents": "THREE.BufferGeometry()$0" },
+		{ "trigger": "THREE.Camera", "contents": "THREE.Camera()$0" },
+		{ "trigger": "THREE.OrthographicCamera", "contents": "THREE.OrthographicCamera( ${1:left}, ${2:right}, ${3:top}, ${4:bottom}, ${5:near}, ${6:far} )$0" },
+		{ "trigger": "THREE.PerspectiveCamera", "contents": "THREE.PerspectiveCamera( ${1:fov}, ${2:aspect}, ${3:near}, ${4:far} )$0" },
+		{ "trigger": "THREE.Light", "contents": "THREE.Light( ${1:hex} )$0" },
+		{ "trigger": "THREE.AmbientLight", "contents": "THREE.AmbientLight( ${1:hex} )$0" },
+		{ "trigger": "THREE.AreaLight", "contents": "THREE.AreaLight( ${1:hex}, ${2:intensity} )$0" },
+		{ "trigger": "THREE.DirectionalLight", "contents": "THREE.DirectionalLight( ${1:hex}, ${2:intensity} )$0" },
+		{ "trigger": "THREE.HemisphereLight", "contents": "THREE.HemisphereLight( ${1:skyColorHex}, ${2:groundColorHex}, ${3:intensity} )$0" },
+		{ "trigger": "THREE.PointLight", "contents": "THREE.PointLight( ${1:hex}, ${2:intensity}, ${3:distance} )$0" },
+		{ "trigger": "THREE.SpotLight", "contents": "THREE.SpotLight( ${1:hex}, ${2:intensity}, ${3:distance}, ${4:angle}, ${5:exponent} )$0" },
+		{ "trigger": "THREE.Loader", "contents": "THREE.Loader( ${1:showStatus} )$0" },
+		{ "trigger": "THREE.XHRLoader", "contents": "THREE.XHRLoader( ${1:manager} )$0" },
+		{ "trigger": "THREE.ImageLoader", "contents": "THREE.ImageLoader( ${1:manager} )$0" },
+		{ "trigger": "THREE.JSONLoader", "contents": "THREE.JSONLoader( ${1:showStatus} )$0" },
+		{ "trigger": "THREE.LoadingManager", "contents": "THREE.LoadingManager( ${1:onLoad}, ${2:onProgress}, ${3:onError} )$0" },
+		{ "trigger": "THREE.BufferGeometryLoader", "contents": "THREE.BufferGeometryLoader( ${1:manager} )$0" },
+		{ "trigger": "THREE.GeometryLoader", "contents": "THREE.GeometryLoader( ${1:manager} )$0" },
+		{ "trigger": "THREE.MaterialLoader", "contents": "THREE.MaterialLoader( ${1:manager} )$0" },
+		{ "trigger": "THREE.ObjectLoader", "contents": "THREE.ObjectLoader( ${1:manager} )$0" },
+		{ "trigger": "THREE.SceneLoader", "contents": "THREE.SceneLoader()$0" },
+		{ "trigger": "THREE.TextureLoader", "contents": "THREE.TextureLoader( ${1:manager} )$0" },
+		{ "trigger": "THREE.Material", "contents": "THREE.Material()$0" },
+		{ "trigger": "THREE.LineBasicMaterial", "contents": "THREE.LineBasicMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.LineDashedMaterial", "contents": "THREE.LineDashedMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.MeshBasicMaterial", "contents": "THREE.MeshBasicMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.MeshLambertMaterial", "contents": "THREE.MeshLambertMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.MeshPhongMaterial", "contents": "THREE.MeshPhongMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.MeshDepthMaterial", "contents": "THREE.MeshDepthMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.MeshNormalMaterial", "contents": "THREE.MeshNormalMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.MeshFaceMaterial", "contents": "THREE.MeshFaceMaterial( ${1:materials} )$0" },
+		{ "trigger": "THREE.ParticleBasicMaterial", "contents": "THREE.ParticleBasicMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.ParticleCanvasMaterial", "contents": "THREE.ParticleCanvasMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.ShaderMaterial", "contents": "THREE.ShaderMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.SpriteMaterial", "contents": "THREE.SpriteMaterial( ${1:parameters} )$0" },
+		{ "trigger": "THREE.Texture", "contents": "THREE.Texture( ${1:image}, ${2:mapping}, ${3:wrapS}, ${4:wrapT}, ${5:magFilter}, ${6:minFilter}, ${7:format}, ${8:type}, ${9:anisotropy} )$0" },
+		{ "trigger": "THREE.CompressedTexture", "contents": "THREE.CompressedTexture( ${1:mipmaps}, ${2:width}, ${3:height}, ${4:format}, ${5:type}, ${6:mapping}, ${7:wrapS}, ${8:wrapT}, ${9:magFilter}, ${10:minFilter}, ${11:anisotropy} )$0" },
+		{ "trigger": "THREE.DataTexture", "contents": "THREE.DataTexture( ${1:data}, ${2:width}, ${3:height}, ${4:format}, ${5:type}, ${6:mapping}, ${7:wrapS}, ${8:wrapT}, ${9:magFilter}, ${10:minFilter}, ${11:anisotropy} )$0" },
+		{ "trigger": "THREE.Particle", "contents": "THREE.Particle( ${1:material} )$0" },
+		{ "trigger": "THREE.ParticleSystem", "contents": "THREE.ParticleSystem( ${1:geometry}, ${2:material} )$0" },
+		{ "trigger": "THREE.Line", "contents": "THREE.Line( ${1:geometry}, ${2:material}, ${3:type} )$0" },
+		{ "trigger": "THREE.Mesh", "contents": "THREE.Mesh( ${1:geometry}, ${2:material} )$0" },
+		{ "trigger": "THREE.SkinnedMesh", "contents": "THREE.SkinnedMesh( ${1:geometry}, ${2:material}, ${3:useVertexTexture} )$0" },
+		{ "trigger": "THREE.MorphAnimMesh", "contents": "THREE.MorphAnimMesh( ${1:geometry}, ${2:material} )$0" },
+		{ "trigger": "THREE.LOD", "contents": "THREE.LOD()$0" },
+		{ "trigger": "THREE.Sprite", "contents": "THREE.Sprite( ${1:material} )$0" },
+		{ "trigger": "THREE.Scene", "contents": "THREE.Scene()$0" },
+		{ "trigger": "THREE.Fog", "contents": "THREE.Fog( ${1:hex}, ${2:near}, ${3:far} )$0" },
+		{ "trigger": "THREE.FogExp2", "contents": "THREE.FogExp2( ${1:hex}, ${2:density} )$0" },
+		{ "trigger": "THREE.CanvasRenderer", "contents": "THREE.CanvasRenderer( ${1:parameters} )$0" },
+		{ "trigger": "THREE.WebGLRenderer", "contents": "THREE.WebGLRenderer( ${1:parameters} )$0" },
+		{ "trigger": "THREE.WebGLRenderTarget", "contents": "THREE.WebGLRenderTarget( ${1:width}, ${2:height}, ${3:options} )$0" },
+		{ "trigger": "THREE.WebGLRenderTargetCube", "contents": "THREE.WebGLRenderTargetCube( ${1:width}, ${2:height}, ${3:options} )$0" },
+		{ "trigger": "THREE.RenderableVertex", "contents": "THREE.RenderableVertex()$0" },
+		{ "trigger": "THREE.RenderableFace3", "contents": "THREE.RenderableFace3()$0" },
+		{ "trigger": "THREE.RenderableObject", "contents": "THREE.RenderableObject()$0" },
+		{ "trigger": "THREE.RenderableParticle", "contents": "THREE.RenderableParticle()$0" },
+		{ "trigger": "THREE.RenderableLine", "contents": "THREE.RenderableLine()$0" },
+		{ "trigger": "THREE.Curve", "contents": "THREE.Curve()$0" },
+		{ "trigger": "THREE.CurvePath", "contents": "THREE.CurvePath()$0" },
+		{ "trigger": "THREE.Gyroscope", "contents": "THREE.Gyroscope()$0" },
+		{ "trigger": "THREE.Path", "contents": "THREE.Path( ${1:points} )$0" },
+		{ "trigger": "THREE.Shape", "contents": "THREE.Shape()$0" },
+		{ "trigger": "THREE.LineCurve", "contents": "THREE.LineCurve( ${1:v1}, ${2:v2} )$0" },
+		{ "trigger": "THREE.QuadraticBezierCurve", "contents": "THREE.QuadraticBezierCurve( ${1:v0}, ${2:v1}, ${3:v2} )$0" },
+		{ "trigger": "THREE.CubicBezierCurve", "contents": "THREE.CubicBezierCurve( ${1:v0}, ${2:v1}, ${3:v2}, ${4:v3} )$0" },
+		{ "trigger": "THREE.ArcCurve", "contents": "THREE.ArcCurve( ${1:aX}, ${2:aY}, ${3:aRadius}, ${4:aStartAngle}, ${5:aEndAngle}, ${6:aClockwise} )$0" },
+		{ "trigger": "THREE.Animation", "contents": "THREE.Animation( ${1:root}, ${2:name}, ${3:interpolationType} )$0" },
+		{ "trigger": "THREE.CubeCamera", "contents": "THREE.CubeCamera( ${1:near}, ${2:far}, ${3:cubeResolution} )$0" },
+		{ "trigger": "THREE.CombinedCamera", "contents": "THREE.CombinedCamera( ${1:width}, ${2:height}, ${3:fov}, ${4:near}, ${5:far}, ${6:orthoNear}, ${7:orthoFar} )$0" },
+		{ "trigger": "THREE.CircleGeometry", "contents": "THREE.CircleGeometry( ${1:radius}, ${2:segments}, ${3:thetaStart}, ${4:thetaLength} )$0" },
+		{ "trigger": "THREE.CubeGeometry", "contents": "THREE.CubeGeometry( ${1:width}, ${2:height}, ${3:depth}, ${4:widthSegments}, ${5:heightSegments}, ${6:depthSegments} )$0" },
+		{ "trigger": "THREE.CylinderGeometry", "contents": "THREE.CylinderGeometry( ${1:radiusTop}, ${2:radiusBottom}, ${3:height}, ${4:radialSegments}, ${5:heightSegments}, ${6:openEnded} )$0" },
+		{ "trigger": "THREE.ExtrudeGeometry", "contents": "THREE.ExtrudeGeometry( ${1:shapes}, ${2:options} )$0" },
+		{ "trigger": "THREE.ShapeGeometry", "contents": "THREE.ShapeGeometry( ${1:shapes}, ${2:options} )$0" },
+		{ "trigger": "THREE.LatheGeometry", "contents": "THREE.LatheGeometry( ${1:points}, ${2:segments}, ${3:phiStart}, ${4:phiLength} )$0" },
+		{ "trigger": "THREE.PlaneGeometry", "contents": "THREE.PlaneGeometry( ${1:width}, ${2:height}, ${3:widthSegments}, ${4:heightSegments} )$0" },
+		{ "trigger": "THREE.RingGeometry", "contents": "THREE.RingGeometry( ${1:innerRadius}, ${2:outerRadius}, ${3:thetaSegments}, ${4:phiSegments}, ${5:thetaStart}, ${6:thetaLength} )$0" },
+		{ "trigger": "THREE.SphereGeometry", "contents": "THREE.SphereGeometry( ${1:radius}, ${2:widthSegments}, ${3:heightSegments}, ${4:phiStart}, ${5:phiLength}, ${6:thetaStart}, ${7:thetaLength} )$0" },
+		{ "trigger": "THREE.TextGeometry", "contents": "THREE.TextGeometry( ${1:text}, ${2:parameters} )$0" },
+		{ "trigger": "THREE.TorusGeometry", "contents": "THREE.TorusGeometry( ${1:radius}, ${2:tube}, ${3:radialSegments}, ${4:tubularSegments}, ${5:arc} )$0" },
+		{ "trigger": "THREE.TorusKnotGeometry", "contents": "THREE.TorusKnotGeometry( ${1:radius}, ${2:tube}, ${3:radialSegments}, ${4:tubularSegments}, ${5:p}, ${6:q}, ${7:heightScale} )$0" },
+		{ "trigger": "THREE.PolyhedronGeometry", "contents": "THREE.PolyhedronGeometry( ${1:vertices}, ${2:faces}, ${3:radius}, ${4:detail} )$0" },
+		{ "trigger": "THREE.IcosahedronGeometry", "contents": "THREE.IcosahedronGeometry( ${1:radius}, ${2:detail} )$0" },
+		{ "trigger": "THREE.OctahedronGeometry", "contents": "THREE.OctahedronGeometry( ${1:radius}, ${2:detail} )$0" },
+		{ "trigger": "THREE.TetrahedronGeometry", "contents": "THREE.TetrahedronGeometry( ${1:radius}, ${2:detail} )$0" },
+		{ "trigger": "THREE.ParametricGeometry", "contents": "THREE.ParametricGeometry( ${1:func}, ${2:slices}, ${3:stacks} )$0" },
+		{ "trigger": "THREE.AxisHelper", "contents": "THREE.AxisHelper( ${1:size} )$0" },
+		{ "trigger": "THREE.ArrowHelper", "contents": "THREE.ArrowHelper( ${1:dir}, ${2:origin}, ${3:length}, ${4:hex} )$0" },
+		{ "trigger": "THREE.BoxHelper", "contents": "THREE.BoxHelper( ${1:object} )$0" },
+		{ "trigger": "THREE.BoundingBoxHelper", "contents": "THREE.BoundingBoxHelper( ${1:object}, ${2:hex} )$0" },
+		{ "trigger": "THREE.CameraHelper", "contents": "THREE.CameraHelper( ${1:camera} )$0" },
+		{ "trigger": "THREE.DirectionalLightHelper", "contents": "THREE.DirectionalLightHelper( ${1:light}, ${2:size} )$0" },
+		{ "trigger": "THREE.FaceNormalsHelper", "contents": "THREE.FaceNormalsHelper( ${1:object}, ${2:size}, ${3:hex}, ${4:linewidth} )$0" },
+		{ "trigger": "THREE.GridHelper", "contents": "THREE.GridHelper( ${1:size}, ${2:step} )$0" },
+		{ "trigger": "THREE.HemisphereLightHelper", "contents": "THREE.HemisphereLightHelper( ${1:light}, ${2:sphereSize}, ${3:arrowLength}, ${4:domeSize} )$0" },
+		{ "trigger": "THREE.PointLightHelper", "contents": "THREE.PointLightHelper( ${1:light}, ${2:sphereSize} )$0" },
+		{ "trigger": "THREE.SpotLightHelper", "contents": "THREE.SpotLightHelper( ${1:light} )$0" },
+		{ "trigger": "THREE.VertexNormalsHelper", "contents": "THREE.VertexNormalsHelper( ${1:object}, ${2:size}, ${3:hex}, ${4:linewidth} )$0" },
+		{ "trigger": "THREE.VertexTangentsHelper", "contents": "THREE.VertexTangentsHelper( ${1:object}, ${2:size}, ${3:hex}, ${4:linewidth} )$0" },
+		{ "trigger": "THREE.WireframeHelper", "contents": "THREE.WireframeHelper( ${1:object} )$0" },
+		{ "trigger": "THREE.ImmediateRenderObject", "contents": "THREE.ImmediateRenderObject()$0" },
+		{ "trigger": "THREE.LensFlare", "contents": "THREE.LensFlare( ${1:texture}, ${2:size}, ${3:distance}, ${4:blending}, ${5:color} )$0" },
+		{ "trigger": "THREE.LensFlarePlugin", "contents": "THREE.LensFlarePlugin()$0" },
+		{ "trigger": "THREE.ShadowMapPlugin", "contents": "THREE.ShadowMapPlugin()$0" },
+		{ "trigger": "THREE.SpritePlugin", "contents": "THREE.SpritePlugin()$0" },
+		{ "trigger": "THREE.DepthPassPlugin", "contents": "THREE.DepthPassPlugin()$0" },
+		"THREE"
+	]
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/__init__.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/__init__.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/__init__.py	(revision 28897)
@@ -0,0 +1,453 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+# ################################################################
+# Init
+# ################################################################
+
+
+bl_info = {
+    "name": "three.js format",
+    "author": "mrdoob, kikko, alteredq, remoe, pxf, n3tfr34k",
+    "version": (1, 4, 0),
+    "blender": (2, 65, 0),
+    "location": "File > Import-Export",
+    "description": "Import-Export three.js meshes",
+    "warning": "",
+    "wiki_url": "https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender",
+    "tracker_url": "https://github.com/mrdoob/three.js/issues",
+    "category": "Import-Export"}
+
+# To support reload properly, try to access a package var,
+# if it's there, reload everything
+
+import bpy
+
+if "bpy" in locals():
+    import imp
+    if "export_threejs" in locals():
+        imp.reload(export_threejs)
+    if "import_threejs" in locals():
+        imp.reload(import_threejs)
+
+from bpy.props import *
+from bpy_extras.io_utils import ExportHelper, ImportHelper
+
+# ################################################################
+# Custom properties
+# ################################################################
+
+bpy.types.Object.THREE_castShadow = bpy.props.BoolProperty()
+bpy.types.Object.THREE_receiveShadow = bpy.props.BoolProperty()
+bpy.types.Object.THREE_doubleSided = bpy.props.BoolProperty()
+bpy.types.Object.THREE_exportGeometry = bpy.props.BoolProperty(default = True)
+
+bpy.types.Material.THREE_useVertexColors = bpy.props.BoolProperty()
+bpy.types.Material.THREE_depthWrite = bpy.props.BoolProperty(default = True)
+bpy.types.Material.THREE_depthTest = bpy.props.BoolProperty(default = True)
+
+THREE_material_types = [("Basic", "Basic", "Basic"), ("Phong", "Phong", "Phong"), ("Lambert", "Lambert", "Lambert")]
+bpy.types.Material.THREE_materialType = EnumProperty(name = "Material type", description = "Material type", items = THREE_material_types, default = "Lambert")
+
+THREE_blending_types = [("NoBlending", "NoBlending", "NoBlending"), ("NormalBlending", "NormalBlending", "NormalBlending"),
+                        ("AdditiveBlending", "AdditiveBlending", "AdditiveBlending"), ("SubtractiveBlending", "SubtractiveBlending", "SubtractiveBlending"),
+                        ("MultiplyBlending", "MultiplyBlending", "MultiplyBlending"), ("AdditiveAlphaBlending", "AdditiveAlphaBlending", "AdditiveAlphaBlending")]
+bpy.types.Material.THREE_blendingType = EnumProperty(name = "Blending type", description = "Blending type", items = THREE_blending_types, default = "NormalBlending")
+
+class OBJECT_PT_hello( bpy.types.Panel ):
+
+    bl_label = "THREE"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "object"
+
+    def draw(self, context):
+        layout = self.layout
+        obj = context.object
+
+        row = layout.row()
+        row.label(text="Selected object: " + obj.name )
+
+        row = layout.row()
+        row.prop( obj, "THREE_exportGeometry", text="Export geometry" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_castShadow", text="Casts shadow" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_receiveShadow", text="Receives shadow" )
+
+        row = layout.row()
+        row.prop( obj, "THREE_doubleSided", text="Double sided" )
+
+class MATERIAL_PT_hello( bpy.types.Panel ):
+
+    bl_label = "THREE"
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "material"
+
+    def draw(self, context):
+        layout = self.layout
+        mat = context.material
+
+        row = layout.row()
+        row.label(text="Selected material: " + mat.name )
+
+        row = layout.row()
+        row.prop( mat, "THREE_materialType", text="Material type" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_blendingType", text="Blending type" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_useVertexColors", text="Use vertex colors" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_depthWrite", text="Enable depth writing" )
+
+        row = layout.row()
+        row.prop( mat, "THREE_depthTest", text="Enable depth testing" )
+
+
+# ################################################################
+# Importer
+# ################################################################
+
+class ImportTHREEJS(bpy.types.Operator, ImportHelper):
+    '''Load a Three.js ASCII JSON model'''
+
+    bl_idname = "import.threejs"
+    bl_label = "Import Three.js"
+
+    filename_ext = ".js"
+    filter_glob = StringProperty(default="*.js", options={'HIDDEN'})
+
+    option_flip_yz = BoolProperty(name="Flip YZ", description="Flip YZ", default=True)
+    recalculate_normals = BoolProperty(name="Recalculate normals", description="Recalculate vertex normals", default=True)
+    option_worker = BoolProperty(name="Worker", description="Old format using workers", default=False)
+
+    def execute(self, context):
+        import io_mesh_threejs.import_threejs
+        return io_mesh_threejs.import_threejs.load(self, context, **self.properties)
+
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        row.prop(self.properties, "option_flip_yz")
+
+        row = layout.row()
+        row.prop(self.properties, "recalculate_normals")
+
+        row = layout.row()
+        row.prop(self.properties, "option_worker")
+
+
+# ################################################################
+# Exporter - settings
+# ################################################################
+
+SETTINGS_FILE_EXPORT = "threejs_settings_export.js"
+
+import os
+import json
+
+def file_exists(filename):
+    """Return true if file exists and accessible for reading.
+
+    Should be safer than just testing for existence due to links and
+    permissions magic on Unix filesystems.
+
+    @rtype: boolean
+    """
+
+    try:
+        f = open(filename, 'r')
+        f.close()
+        return True
+    except IOError:
+        return False
+
+def get_settings_fullpath():
+    return os.path.join(bpy.app.tempdir, SETTINGS_FILE_EXPORT)
+
+def save_settings_export(properties):
+
+    settings = {
+    "option_export_scene" : properties.option_export_scene,
+    "option_embed_meshes" : properties.option_embed_meshes,
+    "option_url_base_html" : properties.option_url_base_html,
+    "option_copy_textures" : properties.option_copy_textures,
+
+    "option_lights" : properties.option_lights,
+    "option_cameras" : properties.option_cameras,
+
+    "option_animation_morph" : properties.option_animation_morph,
+    "option_animation_skeletal" : properties.option_animation_skeletal,
+
+    "option_frame_step" : properties.option_frame_step,
+    "option_all_meshes" : properties.option_all_meshes,
+
+    "option_flip_yz"      : properties.option_flip_yz,
+
+    "option_materials"       : properties.option_materials,
+    "option_normals"         : properties.option_normals,
+    "option_colors"          : properties.option_colors,
+    "option_uv_coords"       : properties.option_uv_coords,
+    "option_faces"           : properties.option_faces,
+    "option_vertices"        : properties.option_vertices,
+
+    "option_skinning"        : properties.option_skinning,
+    "option_bones"           : properties.option_bones,
+
+    "option_vertices_truncate" : properties.option_vertices_truncate,
+    "option_scale"        : properties.option_scale,
+
+    "align_model"         : properties.align_model
+    }
+
+    fname = get_settings_fullpath()
+    f = open(fname, "w")
+    json.dump(settings, f)
+
+def restore_settings_export(properties):
+
+    settings = {}
+
+    fname = get_settings_fullpath()
+    if file_exists(fname):
+        f = open(fname, "r")
+        settings = json.load(f)
+
+    properties.option_vertices = settings.get("option_vertices", True)
+    properties.option_vertices_truncate = settings.get("option_vertices_truncate", False)
+    properties.option_faces = settings.get("option_faces", True)
+    properties.option_normals = settings.get("option_normals", True)
+
+    properties.option_colors = settings.get("option_colors", True)
+    properties.option_uv_coords = settings.get("option_uv_coords", True)
+    properties.option_materials = settings.get("option_materials", True)
+
+    properties.option_skinning = settings.get("option_skinning", True)
+    properties.option_bones = settings.get("option_bones", True)
+
+    properties.align_model = settings.get("align_model", "None")
+
+    properties.option_scale = settings.get("option_scale", 1.0)
+    properties.option_flip_yz = settings.get("option_flip_yz", True)
+
+    properties.option_export_scene = settings.get("option_export_scene", False)
+    properties.option_embed_meshes = settings.get("option_embed_meshes", True)
+    properties.option_url_base_html = settings.get("option_url_base_html", False)
+    properties.option_copy_textures = settings.get("option_copy_textures", False)
+
+    properties.option_lights = settings.get("option_lights", False)
+    properties.option_cameras = settings.get("option_cameras", False)
+
+    properties.option_animation_morph = settings.get("option_animation_morph", False)
+    properties.option_animation_skeletal = settings.get("option_animation_skeletal", False)
+
+    properties.option_frame_step = settings.get("option_frame_step", 1)
+    properties.option_all_meshes = settings.get("option_all_meshes", True)
+
+# ################################################################
+# Exporter
+# ################################################################
+
+class ExportTHREEJS(bpy.types.Operator, ExportHelper):
+    '''Export selected object / scene for Three.js (ASCII JSON format).'''
+
+    bl_idname = "export.threejs"
+    bl_label = "Export Three.js"
+
+    filename_ext = ".js"
+
+    option_vertices = BoolProperty(name = "Vertices", description = "Export vertices", default = True)
+    option_vertices_deltas = BoolProperty(name = "Deltas", description = "Delta vertices", default = False)
+    option_vertices_truncate = BoolProperty(name = "Truncate", description = "Truncate vertices", default = False)
+
+    option_faces = BoolProperty(name = "Faces", description = "Export faces", default = True)
+    option_faces_deltas = BoolProperty(name = "Deltas", description = "Delta faces", default = False)
+
+    option_normals = BoolProperty(name = "Normals", description = "Export normals", default = True)
+
+    option_colors = BoolProperty(name = "Colors", description = "Export vertex colors", default = True)
+    option_uv_coords = BoolProperty(name = "UVs", description = "Export texture coordinates", default = True)
+    option_materials = BoolProperty(name = "Materials", description = "Export materials", default = True)
+
+    option_skinning = BoolProperty(name = "Skinning", description = "Export skin data", default = True)
+    option_bones = BoolProperty(name = "Bones", description = "Export bones", default = True)
+
+    align_types = [("None","None","None"), ("Center","Center","Center"), ("Bottom","Bottom","Bottom"), ("Top","Top","Top")]
+    align_model = EnumProperty(name = "Align model", description = "Align model", items = align_types, default = "None")
+
+    option_scale = FloatProperty(name = "Scale", description = "Scale vertices", min = 0.01, max = 1000.0, soft_min = 0.01, soft_max = 1000.0, default = 1.0)
+    option_flip_yz = BoolProperty(name = "Flip YZ", description = "Flip YZ", default = True)
+
+    option_export_scene = BoolProperty(name = "Scene", description = "Export scene", default = False)
+    option_embed_meshes = BoolProperty(name = "Embed meshes", description = "Embed meshes", default = True)
+    option_copy_textures = BoolProperty(name = "Copy textures", description = "Copy textures", default = False)
+    option_url_base_html = BoolProperty(name = "HTML as url base", description = "Use HTML as url base ", default = False)
+
+    option_lights = BoolProperty(name = "Lights", description = "Export default scene lights", default = False)
+    option_cameras = BoolProperty(name = "Cameras", description = "Export default scene cameras", default = False)
+
+    option_animation_morph = BoolProperty(name = "Morph animation", description = "Export animation (morphs)", default = False)
+    option_animation_skeletal = BoolProperty(name = "Skeletal animation", description = "Export animation (skeletal)", default = False)
+
+    option_frame_step = IntProperty(name = "Frame step", description = "Animation frame step", min = 1, max = 1000, soft_min = 1, soft_max = 1000, default = 1)
+    option_all_meshes = BoolProperty(name = "All meshes", description = "All meshes (merged)", default = True)
+
+    def invoke(self, context, event):
+        restore_settings_export(self.properties)
+        return ExportHelper.invoke(self, context, event)
+
+    @classmethod
+    def poll(cls, context):
+        return context.active_object is not None
+
+    def execute(self, context):
+        print("Selected: " + context.active_object.name)
+
+        if not self.properties.filepath:
+            raise Exception("filename not set")
+
+        save_settings_export(self.properties)
+
+        filepath = self.filepath
+
+        import io_mesh_threejs.export_threejs
+        return io_mesh_threejs.export_threejs.save(self, context, **self.properties)
+
+    def draw(self, context):
+        layout = self.layout
+
+        row = layout.row()
+        row.label(text="Geometry:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_vertices")
+        # row = layout.row()
+        # row.enabled = self.properties.option_vertices
+        # row.prop(self.properties, "option_vertices_deltas")
+        row.prop(self.properties, "option_vertices_truncate")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_faces")
+        row = layout.row()
+        row.enabled = self.properties.option_faces
+        # row.prop(self.properties, "option_faces_deltas")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_normals")
+        layout.separator()
+
+        row = layout.row()
+        row.prop(self.properties, "option_bones")
+        row.prop(self.properties, "option_skinning")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Materials:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_uv_coords")
+        row.prop(self.properties, "option_colors")
+        row = layout.row()
+        row.prop(self.properties, "option_materials")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Settings:")
+
+        row = layout.row()
+        row.prop(self.properties, "align_model")
+        row = layout.row()
+        row.prop(self.properties, "option_flip_yz")
+        row.prop(self.properties, "option_scale")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="--------- Experimental ---------")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Scene:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_export_scene")
+        row.prop(self.properties, "option_embed_meshes")
+
+        row = layout.row()
+        row.prop(self.properties, "option_lights")
+        row.prop(self.properties, "option_cameras")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Animation:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_animation_morph")
+        row = layout.row()
+        row.prop(self.properties, "option_animation_skeletal")
+        row = layout.row()
+        row.prop(self.properties, "option_frame_step")
+        layout.separator()
+
+        row = layout.row()
+        row.label(text="Settings:")
+
+        row = layout.row()
+        row.prop(self.properties, "option_all_meshes")
+
+        row = layout.row()
+        row.prop(self.properties, "option_copy_textures")
+
+        row = layout.row()
+        row.prop(self.properties, "option_url_base_html")
+
+        layout.separator()
+
+
+# ################################################################
+# Common
+# ################################################################
+
+def menu_func_export(self, context):
+    default_path = bpy.data.filepath.replace(".blend", ".js")
+    self.layout.operator(ExportTHREEJS.bl_idname, text="Three.js (.js)").filepath = default_path
+
+def menu_func_import(self, context):
+    self.layout.operator(ImportTHREEJS.bl_idname, text="Three.js (.js)")
+
+def register():
+    bpy.utils.register_module(__name__)
+    bpy.types.INFO_MT_file_export.append(menu_func_export)
+    bpy.types.INFO_MT_file_import.append(menu_func_import)
+
+def unregister():
+    bpy.utils.unregister_module(__name__)
+    bpy.types.INFO_MT_file_export.remove(menu_func_export)
+    bpy.types.INFO_MT_file_import.remove(menu_func_import)
+
+if __name__ == "__main__":
+    register()
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/export_threejs.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/export_threejs.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/export_threejs.py	(revision 28897)
@@ -0,0 +1,2544 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Blender exporter for Three.js (ASCII JSON format).
+
+TODO
+    - binary format
+"""
+
+import bpy
+import mathutils
+
+import shutil
+import os
+import os.path
+import math
+import operator
+import random
+
+# #####################################################
+# Configuration
+# #####################################################
+
+DEFAULTS = {
+"bgcolor" : [0, 0, 0],
+"bgalpha" : 1.0,
+
+"position" : [0, 0, 0],
+"rotation" : [0, 0, 0],
+"scale"    : [1, 1, 1],
+
+"camera"  :
+    {
+        "name" : "default_camera",
+        "type" : "PerspectiveCamera",
+        "near" : 1,
+        "far"  : 10000,
+        "fov"  : 60,
+        "aspect": 1.333,
+        "position" : [0, 0, 10],
+        "target"   : [0, 0, 0]
+    },
+
+"light" :
+ {
+    "name"       : "default_light",
+    "type"       : "DirectionalLight",
+    "direction"  : [0, 1, 1],
+    "color"      : [1, 1, 1],
+    "intensity"  : 0.8
+ }
+}
+
+ROTATE_X_PI2 = mathutils.Quaternion((1.0, 0.0, 0.0), math.radians(-90.0)).to_matrix().to_4x4()
+
+# default colors for debugging (each material gets one distinct color):
+# white, red, green, blue, yellow, cyan, magenta
+COLORS = [0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee, 0xeeee00, 0x00eeee, 0xee00ee]
+
+
+# skinning
+MAX_INFLUENCES = 2
+
+
+# #####################################################
+# Templates - scene
+# #####################################################
+
+TEMPLATE_SCENE_ASCII = """\
+{
+
+"metadata" :
+{
+	"formatVersion" : 3.2,
+	"type"          : "scene",
+	"sourceFile"    : "%(fname)s",
+	"generatedBy"   : "Blender 2.65 Exporter",
+	"objects"       : %(nobjects)s,
+	"geometries"    : %(ngeometries)s,
+	"materials"     : %(nmaterials)s,
+	"textures"      : %(ntextures)s
+},
+
+"urlBaseType" : %(basetype)s,
+
+%(sections)s
+
+"transform" :
+{
+	"position"  : %(position)s,
+	"rotation"  : %(rotation)s,
+	"scale"     : %(scale)s
+},
+
+"defaults" :
+{
+	"bgcolor" : %(bgcolor)s,
+	"bgalpha" : %(bgalpha)f,
+	"camera"  : %(defcamera)s
+}
+
+}
+"""
+
+TEMPLATE_SECTION = """
+"%s" :
+{
+%s
+},
+"""
+
+TEMPLATE_OBJECT = """\
+	%(object_id)s : {
+		"geometry"  : %(geometry_id)s,
+		"groups"    : [ %(group_id)s ],
+		"material"  : %(material_id)s,
+		"position"  : %(position)s,
+		"rotation"  : %(rotation)s,
+		"quaternion": %(quaternion)s,
+		"scale"     : %(scale)s,
+		"visible"       : %(visible)s,
+		"castShadow"    : %(castShadow)s,
+		"receiveShadow" : %(receiveShadow)s,
+		"doubleSided"   : %(doubleSided)s
+	}"""
+
+TEMPLATE_EMPTY = """\
+	%(object_id)s : {
+		"groups"    : [ %(group_id)s ],
+		"position"  : %(position)s,
+		"rotation"  : %(rotation)s,
+		"quaternion": %(quaternion)s,
+		"scale"     : %(scale)s
+	}"""
+
+TEMPLATE_GEOMETRY_LINK = """\
+	%(geometry_id)s : {
+		"type" : "ascii",
+		"url"  : %(model_file)s
+	}"""
+
+TEMPLATE_GEOMETRY_EMBED = """\
+	%(geometry_id)s : {
+		"type" : "embedded",
+		"id"  : %(embed_id)s
+	}"""
+
+TEMPLATE_TEXTURE = """\
+	%(texture_id)s : {
+		"url": %(texture_file)s%(extras)s
+	}"""
+
+TEMPLATE_MATERIAL_SCENE = """\
+	%(material_id)s : {
+		"type": %(type)s,
+		"parameters": { %(parameters)s }
+	}"""
+
+TEMPLATE_CAMERA_PERSPECTIVE = """\
+	%(camera_id)s : {
+		"type"  : "PerspectiveCamera",
+		"fov"   : %(fov)f,
+		"aspect": %(aspect)f,
+		"near"  : %(near)f,
+		"far"   : %(far)f,
+		"position": %(position)s,
+		"target"  : %(target)s
+	}"""
+
+TEMPLATE_CAMERA_ORTHO = """\
+	%(camera_id)s : {
+		"type"  : "OrthographicCamera",
+		"left"  : %(left)f,
+		"right" : %(right)f,
+		"top"   : %(top)f,
+		"bottom": %(bottom)f,
+		"near"  : %(near)f,
+		"far"   : %(far)f,
+		"position": %(position)s,
+		"target"  : %(target)s
+	}"""
+
+TEMPLATE_LIGHT_POINT = """\
+	%(light_id)s : {
+		"type"       : "PointLight",
+		"position"   : %(position)s,
+		"rotation"   : %(rotation)s,
+		"color"      : %(color)d,
+		"distance"   : %(distance).3f,
+		"intensity"  : %(intensity).3f
+	}"""
+
+TEMPLATE_LIGHT_SUN = """\
+	%(light_id)s : {
+		"type"       : "AmbientLight",
+		"position"   : %(position)s,
+		"rotation"   : %(rotation)s,
+		"color"      : %(color)d,
+		"distance"   : %(distance).3f,
+		"intensity"  : %(intensity).3f
+	}"""
+
+TEMPLATE_LIGHT_SPOT = """\
+	%(light_id)s : {
+		"type"       : "SpotLight",
+		"position"   : %(position)s,
+		"rotation"   : %(rotation)s,
+		"color"      : %(color)d,
+		"distance"   : %(distance).3f,
+		"intensity"  : %(intensity).3f,
+		"use_shadow" : %(use_shadow)d,
+		"angle"      : %(angle).3f
+	}"""
+
+TEMPLATE_LIGHT_HEMI = """\
+	%(light_id)s : {
+		"type"       : "HemisphereLight",
+		"position"   : %(position)s,
+		"rotation"   : %(rotation)s,
+		"color"      : %(color)d,
+		"distance"   : %(distance).3f,
+		"intensity"  : %(intensity).3f
+	}"""
+
+TEMPLATE_LIGHT_AREA = """\
+	%(light_id)s : {
+		"type"       : "AreaLight",
+		"position"   : %(position)s,
+		"rotation"   : %(rotation)s,
+		"color"      : %(color)d,
+		"distance"   : %(distance).3f,
+		"intensity"  : %(intensity).3f,
+		"gamma"      : %(gamma).3f,
+		"shape"      : "%(shape)s",
+		"size"       : %(size).3f,
+		"size_y"     : %(size_y).3f
+	}"""
+
+
+TEMPLATE_VEC4 = '[ %g, %g, %g, %g ]'
+TEMPLATE_VEC3 = '[ %g, %g, %g ]'
+TEMPLATE_VEC2 = '[ %g, %g ]'
+TEMPLATE_STRING = '"%s"'
+TEMPLATE_HEX = "0x%06x"
+
+# #####################################################
+# Templates - model
+# #####################################################
+
+TEMPLATE_FILE_ASCII = """\
+{
+
+	"metadata" :
+	{
+		"formatVersion" : 3.1,
+		"generatedBy"   : "Blender 2.65 Exporter",
+		"vertices"      : %(nvertex)d,
+		"faces"         : %(nface)d,
+		"normals"       : %(nnormal)d,
+		"colors"        : %(ncolor)d,
+		"uvs"           : [%(nuvs)s],
+		"materials"     : %(nmaterial)d,
+		"morphTargets"  : %(nmorphTarget)d,
+		"bones"         : %(nbone)d
+	},
+
+%(model)s
+
+}
+"""
+
+TEMPLATE_MODEL_ASCII = """\
+	"scale" : %(scale)f,
+
+	"materials" : [%(materials)s],
+
+	"vertices" : [%(vertices)s],
+
+	"morphTargets" : [%(morphTargets)s],
+
+	"normals" : [%(normals)s],
+
+	"colors" : [%(colors)s],
+
+	"uvs" : [%(uvs)s],
+
+	"faces" : [%(faces)s],
+
+	"bones" : [%(bones)s],
+
+	"skinIndices" : [%(indices)s],
+
+	"skinWeights" : [%(weights)s],
+
+	"animations" : [%(animations)s]
+"""
+
+TEMPLATE_VERTEX = "%g,%g,%g"
+TEMPLATE_VERTEX_TRUNCATE = "%d,%d,%d"
+
+TEMPLATE_N = "%g,%g,%g"
+TEMPLATE_UV = "%g,%g"
+TEMPLATE_C = "%d"
+
+# #####################################################
+# Utils
+# #####################################################
+
+def veckey3(x,y,z):
+    return round(x, 6), round(y, 6), round(z, 6)
+
+def veckey3d(v):
+    return veckey3(v.x, v.y, v.z)
+
+def veckey2d(v):
+    return round(v[0], 6), round(v[1], 6)
+
+def get_faces(obj):
+    if hasattr(obj, "tessfaces"):
+        return obj.tessfaces
+    else:
+        return obj.faces
+
+def get_normal_indices(v, normals, mesh):
+    n = []
+    mv = mesh.vertices
+
+    for i in v:
+        normal = mv[i].normal
+        key = veckey3d(normal)
+
+        n.append( normals[key] )
+
+    return n
+
+def get_uv_indices(face_index, uvs, mesh, layer_index):
+    uv = []
+    uv_layer = mesh.tessface_uv_textures[layer_index].data
+    for i in uv_layer[face_index].uv:
+        uv.append( uvs[veckey2d(i)] )
+    return uv
+
+def get_color_indices(face_index, colors, mesh):
+    c = []
+    color_layer = mesh.tessface_vertex_colors.active.data
+    face_colors = color_layer[face_index]
+    face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+    for i in face_colors:
+        c.append( colors[hexcolor(i)] )
+    return c
+
+def rgb2int(rgb):
+    color = (int(rgb[0]*255) << 16) + (int(rgb[1]*255) << 8) + int(rgb[2]*255);
+    return color
+
+# #####################################################
+# Utils - files
+# #####################################################
+
+def write_file(fname, content):
+    out = open(fname, "w")
+    out.write(content)
+    out.close()
+
+def ensure_folder_exist(foldername):
+    """Create folder (with whole path) if it doesn't exist yet."""
+
+    if not os.access(foldername, os.R_OK|os.W_OK|os.X_OK):
+        os.makedirs(foldername)
+
+def ensure_extension(filepath, extension):
+    if not filepath.lower().endswith(extension):
+        filepath += extension
+    return filepath
+
+def generate_mesh_filename(meshname, filepath):
+    normpath = os.path.normpath(filepath)
+    path, ext = os.path.splitext(normpath)
+    return "%s.%s%s" % (path, meshname, ext)
+
+
+# #####################################################
+# Utils - alignment
+# #####################################################
+
+def bbox(vertices):
+    """Compute bounding box of vertex array.
+    """
+
+    if len(vertices)>0:
+        minx = maxx = vertices[0].co.x
+        miny = maxy = vertices[0].co.y
+        minz = maxz = vertices[0].co.z
+
+        for v in vertices[1:]:
+            if v.co.x < minx:
+                minx = v.co.x
+            elif v.co.x > maxx:
+                maxx = v.co.x
+
+            if v.co.y < miny:
+                miny = v.co.y
+            elif v.co.y > maxy:
+                maxy = v.co.y
+
+            if v.co.z < minz:
+                minz = v.co.z
+            elif v.co.z > maxz:
+                maxz = v.co.z
+
+        return { 'x':[minx,maxx], 'y':[miny,maxy], 'z':[minz,maxz] }
+
+    else:
+        return { 'x':[0,0], 'y':[0,0], 'z':[0,0] }
+
+def translate(vertices, t):
+    """Translate array of vertices by vector t.
+    """
+
+    for i in range(len(vertices)):
+        vertices[i].co.x += t[0]
+        vertices[i].co.y += t[1]
+        vertices[i].co.z += t[2]
+
+def center(vertices):
+    """Center model (middle of bounding box).
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0] + (bb['y'][1] - bb['y'][0])/2.0
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+    return [-cx,-cy,-cz]
+
+def top(vertices):
+    """Align top of the model with the floor (Y-axis) and center it around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][1]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+    return [-cx,-cy,-cz]
+
+def bottom(vertices):
+    """Align bottom of the model with the floor (Y-axis) and center it around X and Z.
+    """
+
+    bb = bbox(vertices)
+
+    cx = bb['x'][0] + (bb['x'][1] - bb['x'][0])/2.0
+    cy = bb['y'][0]
+    cz = bb['z'][0] + (bb['z'][1] - bb['z'][0])/2.0
+
+    translate(vertices, [-cx,-cy,-cz])
+
+    return [-cx,-cy,-cz]
+
+# #####################################################
+# Elements rendering
+# #####################################################
+
+def hexcolor(c):
+    return ( int(c[0] * 255) << 16  ) + ( int(c[1] * 255) << 8 ) + int(c[2] * 255)
+
+def generate_vertices(vertices, option_vertices_truncate, option_vertices):
+    if not option_vertices:
+        return ""
+
+    return ",".join(generate_vertex(v, option_vertices_truncate) for v in vertices)
+
+def generate_vertex(v, option_vertices_truncate):
+    if not option_vertices_truncate:
+        return TEMPLATE_VERTEX % (v.co.x, v.co.y, v.co.z)
+    else:
+        return TEMPLATE_VERTEX_TRUNCATE % (v.co.x, v.co.y, v.co.z)
+
+def generate_normal(n):
+    return TEMPLATE_N % (n[0], n[1], n[2])
+
+def generate_vertex_color(c):
+    return TEMPLATE_C % c
+
+def generate_uv(uv):
+    return TEMPLATE_UV % (uv[0], uv[1])
+
+# #####################################################
+# Model exporter - faces
+# #####################################################
+
+def setBit(value, position, on):
+    if on:
+        mask = 1 << position
+        return (value | mask)
+    else:
+        mask = ~(1 << position)
+        return (value & mask)
+
+def generate_faces(normals, uv_layers, colors, meshes, option_normals, option_colors, option_uv_coords, option_materials, option_faces):
+
+    if not option_faces:
+        return "", 0
+
+    vertex_offset = 0
+    material_offset = 0
+
+    chunks = []
+    for mesh, object in meshes:
+
+        vertexUV = len(mesh.uv_textures) > 0
+        vertexColors = len(mesh.vertex_colors) > 0
+
+        mesh_colors = option_colors and vertexColors
+        mesh_uvs = option_uv_coords and vertexUV
+
+        if vertexUV:
+            active_uv_layer = mesh.uv_textures.active
+            if not active_uv_layer:
+                mesh_extract_uvs = False
+
+        if vertexColors:
+            active_col_layer = mesh.vertex_colors.active
+            if not active_col_layer:
+                mesh_extract_colors = False
+
+        for i, f in enumerate(get_faces(mesh)):
+            face = generate_face(f, i, normals, uv_layers, colors, mesh, option_normals, mesh_colors, mesh_uvs, option_materials, vertex_offset, material_offset)
+            chunks.append(face)
+
+        vertex_offset += len(mesh.vertices)
+
+        material_count = len(mesh.materials)
+        if material_count == 0:
+            material_count = 1
+
+        material_offset += material_count
+
+    return ",".join(chunks), len(chunks)
+
+def generate_face(f, faceIndex, normals, uv_layers, colors, mesh, option_normals, option_colors, option_uv_coords, option_materials, vertex_offset, material_offset):
+    isTriangle = ( len(f.vertices) == 3 )
+
+    if isTriangle:
+        nVertices = 3
+    else:
+        nVertices = 4
+
+    hasMaterial = option_materials
+
+    hasFaceUvs = False # not supported in Blender
+    hasFaceVertexUvs = option_uv_coords
+
+    hasFaceNormals = False # don't export any face normals (as they are computed in engine)
+    hasFaceVertexNormals = option_normals
+
+    hasFaceColors = False       # not supported in Blender
+    hasFaceVertexColors = option_colors
+
+    faceType = 0
+    faceType = setBit(faceType, 0, not isTriangle)
+    faceType = setBit(faceType, 1, hasMaterial)
+    faceType = setBit(faceType, 2, hasFaceUvs)
+    faceType = setBit(faceType, 3, hasFaceVertexUvs)
+    faceType = setBit(faceType, 4, hasFaceNormals)
+    faceType = setBit(faceType, 5, hasFaceVertexNormals)
+    faceType = setBit(faceType, 6, hasFaceColors)
+    faceType = setBit(faceType, 7, hasFaceVertexColors)
+
+    faceData = []
+
+    # order is important, must match order in JSONLoader
+
+    # face type
+    # vertex indices
+    # material index
+    # face uvs index
+    # face vertex uvs indices
+    # face color index
+    # face vertex colors indices
+
+    faceData.append(faceType)
+
+    # must clamp in case on polygons bigger than quads
+
+    for i in range(nVertices):
+        index = f.vertices[i] + vertex_offset
+        faceData.append(index)
+
+    if hasMaterial:
+        index = f.material_index + material_offset
+        faceData.append( index )
+
+    if hasFaceVertexUvs:
+        for layer_index, uvs in enumerate(uv_layers):
+            uv = get_uv_indices(faceIndex, uvs, mesh, layer_index)
+            for i in range(nVertices):
+                index = uv[i]
+                faceData.append(index)
+
+    if hasFaceVertexNormals:
+        n = get_normal_indices(f.vertices, normals, mesh)
+        for i in range(nVertices):
+            index = n[i]
+            faceData.append(index)
+
+    if hasFaceVertexColors:
+        c = get_color_indices(faceIndex, colors, mesh)
+        for i in range(nVertices):
+            index = c[i]
+            faceData.append(index)
+
+    return ",".join( map(str, faceData) )
+
+
+# #####################################################
+# Model exporter - normals
+# #####################################################
+
+def extract_vertex_normals(mesh, normals, count):
+    for f in get_faces(mesh):
+        for v in f.vertices:
+
+            normal = mesh.vertices[v].normal
+            key = veckey3d(normal)
+
+            if key not in normals:
+                normals[key] = count
+                count += 1
+
+    return count
+
+def generate_normals(normals, option_normals):
+    if not option_normals:
+        return ""
+
+    chunks = []
+    for key, index in sorted(normals.items(), key = operator.itemgetter(1)):
+        chunks.append(key)
+
+    return ",".join(generate_normal(n) for n in chunks)
+
+# #####################################################
+# Model exporter - vertex colors
+# #####################################################
+
+def extract_vertex_colors(mesh, colors, count):
+    color_layer = mesh.tessface_vertex_colors.active.data
+
+    for face_index, face in enumerate(get_faces(mesh)):
+
+        face_colors = color_layer[face_index]
+        face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+
+        for c in face_colors:
+            key = hexcolor(c)
+            if key not in colors:
+                colors[key] = count
+                count += 1
+
+    return count
+
+def generate_vertex_colors(colors, option_colors):
+    if not option_colors:
+        return ""
+
+    chunks = []
+    for key, index in sorted(colors.items(), key=operator.itemgetter(1)):
+        chunks.append(key)
+
+    return ",".join(generate_vertex_color(c) for c in chunks)
+
+# #####################################################
+# Model exporter - UVs
+# #####################################################
+
+def extract_uvs(mesh, uv_layers, counts):
+    for index, layer in enumerate(mesh.tessface_uv_textures):
+
+        if len(uv_layers) <= index:
+            uvs = {}
+            count = 0
+            uv_layers.append(uvs)
+            counts.append(count)
+        else:
+            uvs = uv_layers[index]
+            count = counts[index]
+
+        uv_layer = layer.data
+
+        for face_index, face in enumerate(get_faces(mesh)):
+
+            for uv_index, uv in enumerate(uv_layer[face_index].uv):
+
+                key = veckey2d(uv)
+                if key not in uvs:
+                    uvs[key] = count
+                    count += 1
+
+        counts[index] = count
+
+    return counts
+
+def generate_uvs(uv_layers, option_uv_coords):
+    if not option_uv_coords:
+        return "[]"
+
+    layers = []
+    for uvs in uv_layers:
+        chunks = []
+        for key, index in sorted(uvs.items(), key=operator.itemgetter(1)):
+            chunks.append(key)
+        layer = ",".join(generate_uv(n) for n in chunks)
+        layers.append(layer)
+
+    return ",".join("[%s]" % n for n in layers)
+
+# ##############################################################################
+# Model exporter - armature
+# (only the first armature will exported)
+# ##############################################################################
+def get_armature():
+    if len(bpy.data.armatures) == 0:
+        print("Warning: no armatures in the scene")
+        return None, None
+
+    armature = bpy.data.armatures[0]
+
+    # Someone please figure out a proper way to get the armature node
+    for object in bpy.data.objects:
+        if object.type == 'ARMATURE':
+            return armature, object
+
+    print("Warning: no node of type 'ARMATURE' in the scene")
+    return None, None
+
+# ##############################################################################
+# Model exporter - bones
+# (only the first armature will exported)
+# ##############################################################################
+
+def generate_bones(option_bones, flipyz):
+
+    if not option_bones:
+        return "", 0
+
+    armature, armatureObject = get_armature()
+    if armature is None or armatureObject is None:
+        return "", 0
+
+    hierarchy = []
+
+    TEMPLATE_BONE = '{"parent":%d,"name":"%s","pos":[%g,%g,%g],"rotq":[0,0,0,1]}'
+
+    for bone in armature.bones:
+        bonePos = None
+        boneIndex = None
+        if bone.parent is None:
+            bonePos = bone.head_local
+            boneIndex = -1
+        else:
+            bonePos = bone.head_local - bone.parent.head_local
+            boneIndex = i = 0
+            for parent in armature.bones:
+                if parent.name == bone.parent.name:
+                    boneIndex = i
+                i += 1
+
+        bonePosWorld = armatureObject.matrix_world * bonePos
+        if flipyz:
+            joint = TEMPLATE_BONE % (boneIndex, bone.name, bonePosWorld.x, bonePosWorld.z, -bonePosWorld.y)
+            hierarchy.append(joint)
+        else:
+            joint = TEMPLATE_BONE % (boneIndex, bone.name, bonePosWorld.x, bonePosWorld.y, bonePosWorld.z)
+            hierarchy.append(joint)
+                
+    bones_string = ",".join(hierarchy)
+
+    return bones_string, len(armature.bones)
+
+
+# ##############################################################################
+# Model exporter - skin indices and weights
+# ##############################################################################
+
+def generate_indices_and_weights(meshes, option_skinning):
+
+    if not option_skinning or len(bpy.data.armatures) == 0:
+        return "", ""
+
+    indices = []
+    weights = []
+
+    armature, armatureObject = get_armature()
+
+    for mesh, object in meshes:
+
+        i = 0
+        mesh_index = -1
+
+        # find the original object
+
+        for obj in bpy.data.objects:
+            if obj.name == mesh.name or obj == object:
+                mesh_index = i
+            i += 1
+
+        if mesh_index == -1:
+            print("generate_indices: couldn't find object for mesh", mesh.name)
+            continue
+
+        object = bpy.data.objects[mesh_index]
+
+        for vertex in mesh.vertices:
+
+            # sort bones by influence
+
+            bone_array = []
+
+            for group in vertex.groups:
+                index = group.group
+                weight = group.weight
+
+                bone_array.append( (index, weight) )
+
+            bone_array.sort(key = operator.itemgetter(1), reverse=True)
+
+            # select first N bones
+
+            for i in range(MAX_INFLUENCES):
+
+                if i < len(bone_array):
+                    bone_proxy = bone_array[i]
+                    
+                    found = 0
+                    index = bone_proxy[0]
+                    weight = bone_proxy[1]
+
+                    for j, bone in enumerate(armature.bones):
+                        if object.vertex_groups[index].name == bone.name:
+                            indices.append('%d' % j)
+                            weights.append('%g' % weight)
+                            found = 1
+                            break
+
+                    if found != 1:
+                        indices.append('0')
+                        weights.append('0')
+
+                else:
+                    indices.append('0')
+                    weights.append('0')
+
+
+    indices_string = ",".join(indices)
+    weights_string = ",".join(weights)
+
+    return indices_string, weights_string
+
+
+# ##############################################################################
+# Model exporter - skeletal animation
+# (only the first action will exported)
+# ##############################################################################
+
+def generate_animation(option_animation_skeletal, option_frame_step, flipyz, action_index):
+
+    if not option_animation_skeletal or len(bpy.data.actions) == 0 or len(bpy.data.actions) == 0:
+        return ""
+
+    # TODO: Add scaling influences
+
+    action = bpy.data.actions[action_index]
+    armature, armatureObject = get_armature()
+    if armature is None or armatureObject is None:
+        return "", 0
+    armatureMat = armatureObject.matrix_world
+    l,r,s = armatureMat.decompose()
+    armatureRotMat = r.to_matrix()
+
+    parents = []
+    parent_index = -1
+
+    fps = bpy.data.scenes[0].render.fps
+
+    end_frame = action.frame_range[1]
+    start_frame = action.frame_range[0]
+
+    frame_length = end_frame - start_frame
+
+    TEMPLATE_KEYFRAME_FULL  = '{"time":%g,"pos":[%g,%g,%g],"rot":[%g,%g,%g,%g],"scl":[1,1,1]}'
+    TEMPLATE_KEYFRAME       = '{"time":%g,"pos":[%g,%g,%g],"rot":[%g,%g,%g,%g]}'
+    TEMPLATE_KEYFRAME_POS   = '{"time":%g,"pos":[%g,%g,%g]}'
+    TEMPLATE_KEYFRAME_ROT   = '{"time":%g,"rot":[%g,%g,%g,%g]}'
+
+    for hierarchy in armature.bones:
+
+        keys = []
+
+        for frame in range(int(start_frame), int(end_frame / option_frame_step) + 1):
+
+            pos, pchange = position(hierarchy, frame * option_frame_step, action, armatureMat)
+            rot, rchange = rotation(hierarchy, frame * option_frame_step, action, armatureRotMat)
+
+            if flipyz:
+                px, py, pz = pos.x, pos.z, -pos.y
+                rx, ry, rz, rw = rot.x, rot.z, -rot.y, rot.w
+            else:
+                px, py, pz = pos.x, pos.y, pos.z
+                rx, ry, rz, rw = rot.x, rot.y, rot.z, rot.w
+
+            # START-FRAME: needs pos, rot and scl attributes (required frame)
+
+            if frame == int(start_frame):
+
+                time = (frame * option_frame_step - start_frame) / fps
+                keyframe = TEMPLATE_KEYFRAME_FULL % (time, px, py, pz, rx, ry, rz, rw)
+                keys.append(keyframe)
+
+            # END-FRAME: needs pos, rot and scl attributes with animation length (required frame)
+
+            elif frame == int(end_frame / option_frame_step):
+
+                time = frame_length / fps
+                keyframe = TEMPLATE_KEYFRAME_FULL % (time, px, py, pz, rx, ry, rz, rw)
+                keys.append(keyframe)
+
+            # MIDDLE-FRAME: needs only one of the attributes, can be an empty frame (optional frame)
+
+            elif pchange == True or rchange == True:
+
+                time = (frame * option_frame_step - start_frame) / fps
+
+                if pchange == True and rchange == True:
+                    keyframe = TEMPLATE_KEYFRAME % (time, px, py, pz, rx, ry, rz, rw)
+                elif pchange == True:
+                    keyframe = TEMPLATE_KEYFRAME_POS % (time, px, py, pz)
+                elif rchange == True:
+                    keyframe = TEMPLATE_KEYFRAME_ROT % (time, rx, ry, rz, rw)
+
+                keys.append(keyframe)
+
+        keys_string = ",".join(keys)
+        parent = '{"parent":%d,"keys":[%s]}' % (parent_index, keys_string)
+        parent_index += 1
+        parents.append(parent)
+
+    hierarchy_string = ",".join(parents)
+    animation_string = '"name":"%s","fps":%d,"length":%g,"hierarchy":[%s]' % (action.name, fps, (frame_length / fps), hierarchy_string)
+
+    return animation_string
+
+def generate_all_animations(option_animation_skeletal, option_frame_step, flipyz):
+    all_animations_string = ""
+    if option_animation_skeletal:
+        for index in range(0, len(bpy.data.actions)):
+            if index != 0 :
+                all_animations_string += ", \n"
+            all_animations_string += "{" + generate_animation(option_animation_skeletal, option_frame_step, flipyz, index) + "}"
+    return all_animations_string
+
+def handle_position_channel(channel, frame, position):
+
+    change = False
+
+    if channel.array_index in [0, 1, 2]:
+        for keyframe in channel.keyframe_points:
+            if keyframe.co[0] == frame:
+                change = True
+
+        value = channel.evaluate(frame)
+
+        if channel.array_index == 0:
+            position.x = value
+
+        if channel.array_index == 1:
+            position.y = value
+
+        if channel.array_index == 2:
+            position.z = value
+
+    return change
+
+def position(bone, frame, action, armatureMatrix):
+
+    position = mathutils.Vector((0,0,0))
+    change = False
+
+    ngroups = len(action.groups)
+
+    if ngroups > 0:
+
+        index = 0
+
+        for i in range(ngroups):
+            if action.groups[i].name == bone.name:
+                index = i
+
+        for channel in action.groups[index].channels:
+            if "location" in channel.data_path:
+                hasChanged = handle_position_channel(channel, frame, position)
+                change = change or hasChanged
+
+    else:
+
+        bone_label = '"%s"' % bone.name
+
+        for channel in action.fcurves:
+            data_path = channel.data_path
+            if bone_label in data_path and "location" in data_path:
+                hasChanged = handle_position_channel(channel, frame, position)
+                change = change or hasChanged
+
+    position = position * bone.matrix_local.inverted()
+
+    if bone.parent is None:
+
+        position.x += bone.head.x
+        position.y += bone.head.y
+        position.z += bone.head.z
+
+    else:
+
+        parent = bone.parent
+
+        parentInvertedLocalMatrix = parent.matrix_local.inverted()
+        parentHeadTailDiff = parent.tail_local - parent.head_local
+
+        position.x += (bone.head * parentInvertedLocalMatrix).x + parentHeadTailDiff.x
+        position.y += (bone.head * parentInvertedLocalMatrix).y + parentHeadTailDiff.y
+        position.z += (bone.head * parentInvertedLocalMatrix).z + parentHeadTailDiff.z
+
+    return armatureMatrix*position, change
+
+def handle_rotation_channel(channel, frame, rotation):
+
+    change = False
+
+    if channel.array_index in [0, 1, 2, 3]:
+
+        for keyframe in channel.keyframe_points:
+            if keyframe.co[0] == frame:
+                change = True
+
+        value = channel.evaluate(frame)
+
+        if channel.array_index == 1:
+            rotation.x = value
+
+        elif channel.array_index == 2:
+            rotation.y = value
+
+        elif channel.array_index == 3:
+            rotation.z = value
+
+        elif channel.array_index == 0:
+            rotation.w = value
+
+    return change
+
+def rotation(bone, frame, action, armatureMatrix):
+
+    # TODO: calculate rotation also from rotation_euler channels
+
+    rotation = mathutils.Vector((0,0,0,1))
+
+    change = False
+
+    ngroups = len(action.groups)
+
+    # animation grouped by bones
+
+    if ngroups > 0:
+
+        index = -1
+
+        for i in range(ngroups):
+            if action.groups[i].name == bone.name:
+                index = i
+
+        if index > -1:
+            for channel in action.groups[index].channels:
+                if "quaternion" in channel.data_path:
+                    hasChanged = handle_rotation_channel(channel, frame, rotation)
+                    change = change or hasChanged
+
+    # animation in raw fcurves
+
+    else:
+
+        bone_label = '"%s"' % bone.name
+
+        for channel in action.fcurves:
+            data_path = channel.data_path
+            if bone_label in data_path and "quaternion" in data_path:
+                hasChanged = handle_rotation_channel(channel, frame, rotation)
+                change = change or hasChanged
+
+    rot3 = rotation.to_3d()
+    rotation.xyz = rot3 * bone.matrix_local.inverted()
+    rotation.xyz = armatureMatrix * rotation.xyz
+
+    return rotation, change
+
+# #####################################################
+# Model exporter - materials
+# #####################################################
+
+def generate_color(i):
+    """Generate hex color corresponding to integer.
+
+    Colors should have well defined ordering.
+    First N colors are hardcoded, then colors are random
+    (must seed random number  generator with deterministic value
+    before getting colors).
+    """
+
+    if i < len(COLORS):
+        #return "0x%06x" % COLORS[i]
+        return COLORS[i]
+    else:
+        #return "0x%06x" % int(0xffffff * random.random())
+        return int(0xffffff * random.random())
+
+def generate_mtl(materials):
+    """Generate dummy materials.
+    """
+
+    mtl = {}
+    for m in materials:
+        index = materials[m]
+        mtl[m] = {
+            "DbgName": m,
+            "DbgIndex": index,
+            "DbgColor": generate_color(index),
+            "vertexColors" : False
+        }
+    return mtl
+
+def value2string(v):
+    if type(v) == str and v[0:2] != "0x":
+        return '"%s"' % v
+    elif type(v) == bool:
+        return str(v).lower()
+    elif type(v) == list:
+        return "[%s]" % (", ".join(value2string(x) for x in v))
+    return str(v)
+
+def generate_materials(mtl, materials, draw_type):
+    """Generate JS array of materials objects
+    """
+
+    mtl_array = []
+    for m in mtl:
+        index = materials[m]
+
+        # add debug information
+        #  materials should be sorted according to how
+        #  they appeared in OBJ file (for the first time)
+        #  this index is identifier used in face definitions
+        mtl[m]['DbgName'] = m
+        mtl[m]['DbgIndex'] = index
+        mtl[m]['DbgColor'] = generate_color(index)
+
+        if draw_type in [ "BOUNDS", "WIRE" ]:
+            mtl[m]['wireframe'] = True
+            mtl[m]['DbgColor'] = 0xff0000
+
+        mtl_raw = ",\n".join(['\t\t"%s" : %s' % (n, value2string(v)) for n,v in sorted(mtl[m].items())])
+        mtl_string = "\t{\n%s\n\t}" % mtl_raw
+        mtl_array.append([index, mtl_string])
+
+    return ",\n\n".join([m for i,m in sorted(mtl_array)]), len(mtl_array)
+
+def extract_materials(mesh, scene, option_colors, option_copy_textures, filepath):
+    world = scene.world
+
+    materials = {}
+    for m in mesh.materials:
+        if m:
+            materials[m.name] = {}
+            material = materials[m.name]
+
+            material['colorDiffuse'] = [m.diffuse_intensity * m.diffuse_color[0],
+                                        m.diffuse_intensity * m.diffuse_color[1],
+                                        m.diffuse_intensity * m.diffuse_color[2]]
+
+            material['colorSpecular'] = [m.specular_intensity * m.specular_color[0],
+                                         m.specular_intensity * m.specular_color[1],
+                                         m.specular_intensity * m.specular_color[2]]
+
+            material['colorAmbient'] = [m.ambient * material['colorDiffuse'][0],
+                                        m.ambient * material['colorDiffuse'][1],
+                                        m.ambient * material['colorDiffuse'][2]]
+
+            material['transparency'] = m.alpha
+
+            # not sure about mapping values to Blinn-Phong shader
+            # Blender uses INT from [1, 511] with default 0
+            # http://www.blender.org/documentation/blender_python_api_2_54_0/bpy.types.Material.html#bpy.types.Material.specular_hardness
+
+            material["specularCoef"] = m.specular_hardness
+
+            textures = guess_material_textures(m)
+
+            handle_texture('diffuse', textures, material, filepath, option_copy_textures)
+            handle_texture('light', textures, material, filepath, option_copy_textures)
+            handle_texture('normal', textures, material, filepath, option_copy_textures)
+            handle_texture('specular', textures, material, filepath, option_copy_textures)
+            handle_texture('bump', textures, material, filepath, option_copy_textures)
+
+            material["vertexColors"] = m.THREE_useVertexColors and option_colors
+
+            # can't really use this reliably to tell apart Phong from Lambert
+            # as Blender defaults to non-zero specular color
+            #if m.specular_intensity > 0.0 and (m.specular_color[0] > 0 or m.specular_color[1] > 0 or m.specular_color[2] > 0):
+            #    material['shading'] = "Phong"
+            #else:
+            #    material['shading'] = "Lambert"
+
+            if textures['normal']:
+                material['shading'] = "Phong"
+            else:
+                material['shading'] = m.THREE_materialType
+
+            material['blending'] = m.THREE_blendingType
+            material['depthWrite'] = m.THREE_depthWrite
+            material['depthTest'] = m.THREE_depthTest
+            material['transparent'] = m.use_transparency
+
+    return materials
+
+def generate_materials_string(mesh, scene, option_colors, draw_type, option_copy_textures, filepath, offset):
+
+    random.seed(42) # to get well defined color order for debug materials
+
+    materials = {}
+    if mesh.materials:
+        for i, m in enumerate(mesh.materials):
+            mat_id = i + offset
+            if m:
+                materials[m.name] = mat_id
+            else:
+                materials["undefined_dummy_%0d" % mat_id] = mat_id
+
+
+    if not materials:
+        materials = { 'default': 0 }
+
+    # default dummy materials
+
+    mtl = generate_mtl(materials)
+
+    # extract real materials from the mesh
+
+    mtl.update(extract_materials(mesh, scene, option_colors, option_copy_textures, filepath))
+
+    return generate_materials(mtl, materials, draw_type)
+
+def handle_texture(id, textures, material, filepath, option_copy_textures):
+
+    if textures[id] and textures[id]['texture'].users > 0 and len(textures[id]['texture'].users_material) > 0:
+        texName     = 'map%s'       % id.capitalize()
+        repeatName  = 'map%sRepeat' % id.capitalize()
+        wrapName    = 'map%sWrap'   % id.capitalize()
+
+        slot = textures[id]['slot']
+        texture = textures[id]['texture']
+        image = texture.image
+        fname = extract_texture_filename(image)
+        material[texName] = fname
+
+        if option_copy_textures:
+            save_image(image, fname, filepath)
+
+        if texture.repeat_x != 1 or texture.repeat_y != 1:
+            material[repeatName] = [texture.repeat_x, texture.repeat_y]
+
+        if texture.extension == "REPEAT":
+            wrap_x = "repeat"
+            wrap_y = "repeat"
+
+            if texture.use_mirror_x:
+                wrap_x = "mirror"
+            if texture.use_mirror_y:
+                wrap_y = "mirror"
+
+            material[wrapName] = [wrap_x, wrap_y]
+
+        if slot.use_map_normal:
+            if slot.normal_factor != 1.0:
+                if id == "bump":
+                    material['mapBumpScale'] = slot.normal_factor
+                else:
+                    material['mapNormalFactor'] = slot.normal_factor
+
+
+# #####################################################
+# ASCII model generator
+# #####################################################
+
+def generate_ascii_model(meshes, morphs,
+                         scene,
+                         option_vertices,
+                         option_vertices_truncate,
+                         option_faces,
+                         option_normals,
+                         option_uv_coords,
+                         option_materials,
+                         option_colors,
+                         option_bones,
+                         option_skinning,
+                         align_model,
+                         flipyz,
+                         option_scale,
+                         option_copy_textures,
+                         filepath,
+                         option_animation_morph,
+                         option_animation_skeletal,
+                         option_frame_step):
+
+    vertices = []
+
+    vertex_offset = 0
+    vertex_offsets = []
+
+    nnormal = 0
+    normals = {}
+
+    ncolor = 0
+    colors = {}
+
+    nuvs = []
+    uv_layers = []
+
+    nmaterial = 0
+    materials = []
+
+    for mesh, object in meshes:
+
+        vertexUV = len(mesh.uv_textures) > 0
+        vertexColors = len(mesh.vertex_colors) > 0
+
+        mesh_extract_colors = option_colors and vertexColors
+        mesh_extract_uvs = option_uv_coords and vertexUV
+
+        if vertexUV:
+            active_uv_layer = mesh.uv_textures.active
+            if not active_uv_layer:
+                mesh_extract_uvs = False
+
+        if vertexColors:
+            active_col_layer = mesh.vertex_colors.active
+            if not active_col_layer:
+                mesh_extract_colors = False
+
+        vertex_offsets.append(vertex_offset)
+        vertex_offset += len(vertices)
+
+        vertices.extend(mesh.vertices[:])
+
+        if option_normals:
+            nnormal = extract_vertex_normals(mesh, normals, nnormal)
+
+        if mesh_extract_colors:
+            ncolor = extract_vertex_colors(mesh, colors, ncolor)
+
+        if mesh_extract_uvs:
+            nuvs = extract_uvs(mesh, uv_layers, nuvs)
+
+        if option_materials:
+            mesh_materials, nmaterial = generate_materials_string(mesh, scene, mesh_extract_colors, object.draw_type, option_copy_textures, filepath, nmaterial)
+            materials.append(mesh_materials)
+
+
+    morphTargets_string = ""
+    nmorphTarget = 0
+
+    if option_animation_morph:
+        chunks = []
+        for i, morphVertices in enumerate(morphs):
+            morphTarget = '{ "name": "%s_%06d", "vertices": [%s] }' % ("animation", i, morphVertices)
+            chunks.append(morphTarget)
+
+        morphTargets_string = ",\n\t".join(chunks)
+        nmorphTarget = len(morphs)
+
+    if align_model == 1:
+        center(vertices)
+    elif align_model == 2:
+        bottom(vertices)
+    elif align_model == 3:
+        top(vertices)
+
+    faces_string, nfaces = generate_faces(normals, uv_layers, colors, meshes, option_normals, option_colors, option_uv_coords, option_materials, option_faces)
+
+    bones_string, nbone = generate_bones(option_bones, flipyz)
+    indices_string, weights_string = generate_indices_and_weights(meshes, option_skinning)
+
+    materials_string = ",\n\n".join(materials)
+
+    model_string = TEMPLATE_MODEL_ASCII % {
+    "scale" : option_scale,
+
+    "uvs"       : generate_uvs(uv_layers, option_uv_coords),
+    "normals"   : generate_normals(normals, option_normals),
+    "colors"    : generate_vertex_colors(colors, option_colors),
+
+    "materials" : materials_string,
+
+    "vertices" : generate_vertices(vertices, option_vertices_truncate, option_vertices),
+
+    "faces"    : faces_string,
+
+    "morphTargets" : morphTargets_string,
+
+    "bones"     : bones_string,
+    "indices"   : indices_string,
+    "weights"   : weights_string,
+    "animations" : generate_all_animations(option_animation_skeletal, option_frame_step, flipyz)
+    }
+
+    text = TEMPLATE_FILE_ASCII % {
+    "nvertex"   : len(vertices),
+    "nface"     : nfaces,
+    "nuvs"      : ",".join("%d" % n for n in nuvs),
+    "nnormal"   : nnormal,
+    "ncolor"    : ncolor,
+    "nmaterial" : nmaterial,
+    "nmorphTarget": nmorphTarget,
+    "nbone"     : nbone,
+
+    "model"     : model_string
+    }
+
+
+    return text, model_string
+
+
+# #####################################################
+# Model exporter - export single mesh
+# #####################################################
+
+def extract_meshes(objects, scene, export_single_model, option_scale, flipyz):
+
+    meshes = []
+
+    for object in objects:
+
+        if object.type == "MESH" and object.THREE_exportGeometry:
+
+            # collapse modifiers into mesh
+
+            mesh = object.to_mesh(scene, True, 'RENDER')
+
+            if not mesh:
+                raise Exception("Error, could not get mesh data from object [%s]" % object.name)
+
+            # preserve original name
+
+            mesh.name = object.name
+
+            if export_single_model:
+
+                if flipyz:
+
+                    # that's what Blender's native export_obj.py does to flip YZ
+
+                    X_ROT = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
+                    mesh.transform(X_ROT * object.matrix_world)
+
+                else:
+                    mesh.transform(object.matrix_world)
+                    
+                    
+            mesh.update(calc_tessface=True)
+
+            mesh.calc_normals()
+            mesh.calc_tessface()
+            mesh.transform(mathutils.Matrix.Scale(option_scale, 4))
+            meshes.append([mesh, object])
+
+    return meshes
+
+def generate_mesh_string(objects, scene,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                option_bones,
+                option_skinning,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model,
+                option_copy_textures,
+                filepath,
+                option_animation_morph,
+                option_animation_skeletal,
+                option_frame_step):
+
+    meshes = extract_meshes(objects, scene, export_single_model, option_scale, flipyz)
+
+    morphs = []
+
+    if option_animation_morph:
+
+        original_frame = scene.frame_current # save animation state
+
+        scene_frames = range(scene.frame_start, scene.frame_end + 1, option_frame_step)
+
+        for index, frame in enumerate(scene_frames):
+            scene.frame_set(frame, 0.0)
+
+            anim_meshes = extract_meshes(objects, scene, export_single_model, option_scale, flipyz)
+
+            frame_vertices = []
+
+            for mesh, object in anim_meshes:
+                frame_vertices.extend(mesh.vertices[:])
+
+            if index == 0:
+                if align_model == 1:
+                    offset = center(frame_vertices)
+                elif align_model == 2:
+                    offset = bottom(frame_vertices)
+                elif align_model == 3:
+                    offset = top(frame_vertices)
+                else:
+                    offset = False
+            else:
+                if offset:
+                    translate(frame_vertices, offset)
+
+            morphVertices = generate_vertices(frame_vertices, option_vertices_truncate, option_vertices)
+            morphs.append(morphVertices)
+
+            # remove temp meshes
+
+            for mesh, object in anim_meshes:
+                bpy.data.meshes.remove(mesh)
+
+        scene.frame_set(original_frame, 0.0) # restore animation state
+
+
+    text, model_string = generate_ascii_model(meshes, morphs,
+                                scene,
+                                option_vertices,
+                                option_vertices_truncate,
+                                option_faces,
+                                option_normals,
+                                option_uv_coords,
+                                option_materials,
+                                option_colors,
+                                option_bones,
+                                option_skinning,
+                                align_model,
+                                flipyz,
+                                option_scale,
+                                option_copy_textures,
+                                filepath,
+                                option_animation_morph,
+                                option_animation_skeletal,
+                                option_frame_step)
+
+    # remove temp meshes
+
+    for mesh, object in meshes:
+        bpy.data.meshes.remove(mesh)
+
+    return text, model_string
+
+def export_mesh(objects,
+                scene, filepath,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                option_bones,
+                option_skinning,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model,
+                option_copy_textures,
+                option_animation_morph,
+                option_animation_skeletal,
+                option_frame_step):
+
+    """Export single mesh"""
+
+    text, model_string = generate_mesh_string(objects,
+                scene,
+                option_vertices,
+                option_vertices_truncate,
+                option_faces,
+                option_normals,
+                option_uv_coords,
+                option_materials,
+                option_colors,
+                option_bones,
+                option_skinning,
+                align_model,
+                flipyz,
+                option_scale,
+                export_single_model,
+                option_copy_textures,
+                filepath,
+                option_animation_morph,
+                option_animation_skeletal,
+                option_frame_step)
+
+    write_file(filepath, text)
+
+    print("writing", filepath, "done")
+
+
+# #####################################################
+# Scene exporter - render elements
+# #####################################################
+
+def generate_quat(quat):
+    return TEMPLATE_VEC4 % (quat.x, quat.y, quat.z, quat.w)
+
+def generate_vec4(vec):
+    return TEMPLATE_VEC4 % (vec[0], vec[1], vec[2], vec[3])
+
+def generate_vec3(vec, flipyz = False):
+    if flipyz:
+        return TEMPLATE_VEC3 % (vec[0], vec[2], vec[1])
+    return TEMPLATE_VEC3 % (vec[0], vec[1], vec[2])
+
+def generate_vec2(vec):
+    return TEMPLATE_VEC2 % (vec[0], vec[1])
+
+def generate_hex(number):
+    return TEMPLATE_HEX % number
+
+def generate_string(s):
+    return TEMPLATE_STRING % s
+
+def generate_string_list(src_list):
+    return ", ".join(generate_string(item) for item in src_list)
+
+def generate_section(label, content):
+    return TEMPLATE_SECTION % (label, content)
+
+def get_mesh_filename(mesh):
+    object_id = mesh["data"]["name"]
+    filename = "%s.js" % sanitize(object_id)
+    return filename
+
+def generate_material_id_list(materials):
+    chunks = []
+    for material in materials:
+        chunks.append(material.name)
+
+    return chunks
+
+def generate_group_id_list(obj):
+    chunks = []
+
+    for group in bpy.data.groups:
+        if obj.name in group.objects:
+            chunks.append(group.name)
+
+    return chunks
+
+def generate_bool_property(property):
+    if property:
+        return "true"
+    return "false"
+
+# #####################################################
+# Scene exporter - objects
+# #####################################################
+
+def generate_objects(data):
+    chunks = []
+
+    for obj in data["objects"]:
+
+        if obj.type == "MESH" and obj.THREE_exportGeometry:
+            object_id = obj.name
+
+            #if len(obj.modifiers) > 0:
+            #    geo_name = obj.name
+            #else:
+            geo_name = obj.data.name
+
+            geometry_id = "geo_%s" % geo_name
+
+            material_ids = generate_material_id_list(obj.material_slots)
+            group_ids = generate_group_id_list(obj)
+
+            if data["flipyz"]:
+                matrix_world = ROTATE_X_PI2 * obj.matrix_world
+            else:
+                matrix_world = obj.matrix_world
+
+            position, quaternion, scale = matrix_world.decompose()
+            rotation = quaternion.to_euler("ZYX")
+
+            # use empty material string for multi-material objects
+            # this will trigger use of MeshFaceMaterial in SceneLoader
+
+            material_string = '""'
+            if len(material_ids) == 1:
+                material_string = generate_string_list(material_ids)
+
+            group_string = ""
+            if len(group_ids) > 0:
+                group_string = generate_string_list(group_ids)
+
+            castShadow = obj.THREE_castShadow
+            receiveShadow = obj.THREE_receiveShadow
+            doubleSided = obj.THREE_doubleSided
+
+            visible = True
+
+            geometry_string = generate_string(geometry_id)
+
+            object_string = TEMPLATE_OBJECT % {
+            "object_id"   : generate_string(object_id),
+            "geometry_id" : geometry_string,
+            "group_id"    : group_string,
+            "material_id" : material_string,
+
+            "position"    : generate_vec3(position),
+            "rotation"    : generate_vec3(rotation),
+            "quaternion"  : generate_quat(quaternion),
+            "scale"       : generate_vec3(scale),
+
+            "castShadow"  : generate_bool_property(castShadow),
+            "receiveShadow"  : generate_bool_property(receiveShadow),
+            "doubleSided"  : generate_bool_property(doubleSided),
+            "visible"      : generate_bool_property(visible)
+            }
+            chunks.append(object_string)
+
+        elif obj.type == "EMPTY" or (obj.type == "MESH" and not obj.THREE_exportGeometry):
+
+            object_id = obj.name
+            group_ids = generate_group_id_list(obj)
+
+            if data["flipyz"]:
+                matrix_world = ROTATE_X_PI2 * obj.matrix_world
+            else:
+                matrix_world = obj.matrix_world
+
+            position, quaternion, scale = matrix_world.decompose()
+            rotation = quaternion.to_euler("ZYX")
+
+            group_string = ""
+            if len(group_ids) > 0:
+                group_string = generate_string_list(group_ids)
+
+            object_string = TEMPLATE_EMPTY % {
+            "object_id"   : generate_string(object_id),
+            "group_id"    : group_string,
+
+            "position"    : generate_vec3(position),
+            "rotation"    : generate_vec3(rotation),
+            "quaternion"  : generate_quat(quaternion),
+            "scale"       : generate_vec3(scale)
+            }
+            chunks.append(object_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - geometries
+# #####################################################
+
+def generate_geometries(data):
+    chunks = []
+
+    geo_set = set()
+
+    for obj in data["objects"]:
+        if obj.type == "MESH" and obj.THREE_exportGeometry:
+
+            #if len(obj.modifiers) > 0:
+            #    name = obj.name
+            #else:
+            name = obj.data.name
+
+            if name not in geo_set:
+
+                geometry_id = "geo_%s" % name
+
+                if data["embed_meshes"]:
+
+                    embed_id = "emb_%s" % name
+
+                    geometry_string = TEMPLATE_GEOMETRY_EMBED % {
+                    "geometry_id" : generate_string(geometry_id),
+                    "embed_id"  : generate_string(embed_id)
+                    }
+
+                else:
+
+                    model_filename = os.path.basename(generate_mesh_filename(name, data["filepath"]))
+
+                    geometry_string = TEMPLATE_GEOMETRY_LINK % {
+                    "geometry_id" : generate_string(geometry_id),
+                    "model_file"  : generate_string(model_filename)
+                    }
+
+                chunks.append(geometry_string)
+
+                geo_set.add(name)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - textures
+# #####################################################
+
+def generate_textures_scene(data):
+    chunks = []
+
+    # TODO: extract just textures actually used by some objects in the scene
+
+    for texture in bpy.data.textures:
+
+        if texture.type == 'IMAGE' and texture.image and texture.users > 0 and len(texture.users_material) > 0:
+
+            img = texture.image
+
+            texture_id = img.name
+            texture_file = extract_texture_filename(img)
+
+            if data["copy_textures"]:
+                save_image(img, texture_file, data["filepath"])
+
+            extras = ""
+
+            if texture.repeat_x != 1 or texture.repeat_y != 1:
+                extras += ',\n        "repeat": [%g, %g]' % (texture.repeat_x, texture.repeat_y)
+
+            if texture.extension == "REPEAT":
+                wrap_x = "repeat"
+                wrap_y = "repeat"
+
+                if texture.use_mirror_x:
+                    wrap_x = "mirror"
+                if texture.use_mirror_y:
+                    wrap_y = "mirror"
+
+                extras += ',\n        "wrap": ["%s", "%s"]' % (wrap_x, wrap_y)
+
+            texture_string = TEMPLATE_TEXTURE % {
+            "texture_id"   : generate_string(texture_id),
+            "texture_file" : generate_string(texture_file),
+            "extras"       : extras
+            }
+            chunks.append(texture_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+def extract_texture_filename(image):
+    fn = bpy.path.abspath(image.filepath)
+    fn = os.path.normpath(fn)
+    fn_strip = os.path.basename(fn)
+    return fn_strip
+
+def save_image(img, name, fpath):
+    dst_dir = os.path.dirname(fpath)
+    dst_path = os.path.join(dst_dir, name)
+
+    ensure_folder_exist(dst_dir)
+
+    if img.packed_file:
+        img.save_render(dst_path)
+
+    else:
+        src_path = bpy.path.abspath(img.filepath)
+        shutil.copy(src_path, dst_dir)
+
+# #####################################################
+# Scene exporter - materials
+# #####################################################
+
+def extract_material_data(m, option_colors):
+    world = bpy.context.scene.world
+
+    material = { 'name': m.name }
+
+    material['colorDiffuse'] = [m.diffuse_intensity * m.diffuse_color[0],
+                                m.diffuse_intensity * m.diffuse_color[1],
+                                m.diffuse_intensity * m.diffuse_color[2]]
+
+    material['colorSpecular'] = [m.specular_intensity * m.specular_color[0],
+                                 m.specular_intensity * m.specular_color[1],
+                                 m.specular_intensity * m.specular_color[2]]
+
+    material['colorAmbient'] = [m.ambient * material['colorDiffuse'][0],
+                                m.ambient * material['colorDiffuse'][1],
+                                m.ambient * material['colorDiffuse'][2]]
+
+    material['transparency'] = m.alpha
+
+    # not sure about mapping values to Blinn-Phong shader
+    # Blender uses INT from [1,511] with default 0
+    # http://www.blender.org/documentation/blender_python_api_2_54_0/bpy.types.Material.html#bpy.types.Material.specular_hardness
+
+    material["specularCoef"] = m.specular_hardness
+
+    material["vertexColors"] = m.THREE_useVertexColors and option_colors
+
+    material['mapDiffuse'] = ""
+    material['mapLight'] = ""
+    material['mapSpecular'] = ""
+    material['mapNormal'] = ""
+    material['mapBump'] = ""
+
+    material['mapNormalFactor'] = 1.0
+    material['mapBumpScale'] = 1.0
+
+    textures = guess_material_textures(m)
+
+    if textures['diffuse']:
+        material['mapDiffuse'] = textures['diffuse']['texture'].image.name
+
+    if textures['light']:
+        material['mapLight'] = textures['light']['texture'].image.name
+
+    if textures['specular']:
+        material['mapSpecular'] = textures['specular']['texture'].image.name
+
+    if textures['normal']:
+        material['mapNormal'] = textures['normal']['texture'].image.name
+        if textures['normal']['slot'].use_map_normal:
+            material['mapNormalFactor'] = textures['normal']['slot'].normal_factor
+
+    if textures['bump']:
+        material['mapBump'] = textures['bump']['texture'].image.name
+        if textures['bump']['slot'].use_map_normal:
+            material['mapBumpScale'] = textures['bump']['slot'].normal_factor
+
+    material['shading'] = m.THREE_materialType
+    material['blending'] = m.THREE_blendingType
+    material['depthWrite'] = m.THREE_depthWrite
+    material['depthTest'] = m.THREE_depthTest
+    material['transparent'] = m.use_transparency
+
+    return material
+
+def guess_material_textures(material):
+    textures = {
+        'diffuse' : None,
+        'light'   : None,
+        'normal'  : None,
+        'specular': None,
+        'bump'    : None
+    }
+
+    # just take first textures of each, for the moment three.js materials can't handle more
+    # assume diffuse comes before lightmap, normalmap has checked flag
+
+    for i in range(len(material.texture_slots)):
+        slot = material.texture_slots[i]
+        if slot:
+            texture = slot.texture
+            if slot.use and texture and texture.type == 'IMAGE':
+
+                # normal map in Blender UI: textures => image sampling => normal map
+
+                if texture.use_normal_map:
+                    textures['normal'] = { "texture": texture, "slot": slot }
+
+                # bump map in Blender UI: textures => influence => geometry => normal
+
+                elif slot.use_map_normal:
+                    textures['bump'] = { "texture": texture, "slot": slot }
+
+                elif slot.use_map_specular or slot.use_map_hardness:
+                    textures['specular'] = { "texture": texture, "slot": slot }
+
+                else:
+                    if not textures['diffuse'] and not slot.blend_type == 'MULTIPLY':
+                        textures['diffuse'] = { "texture": texture, "slot": slot }
+
+                    else:
+                        textures['light'] = { "texture": texture, "slot": slot }
+
+                if textures['diffuse'] and textures['normal'] and textures['light'] and textures['specular'] and textures['bump']:
+                    break
+
+    return textures
+
+def generate_material_string(material):
+
+    material_id = material["name"]
+
+    # default to Lambert
+
+    shading = material.get("shading", "Lambert")
+
+    # normal and bump mapped materials must use Phong
+    # to get all required parameters for normal shader
+
+    if material['mapNormal'] or material['mapBump']:
+        shading = "Phong"
+
+    type_map = {
+    "Lambert"   : "MeshLambertMaterial",
+    "Phong"     : "MeshPhongMaterial"
+    }
+
+    material_type = type_map.get(shading, "MeshBasicMaterial")
+
+    parameters = '"color": %d' % rgb2int(material["colorDiffuse"])
+    parameters += ', "ambient": %d' % rgb2int(material["colorDiffuse"])
+    parameters += ', "opacity": %.2g' % material["transparency"]
+
+    if shading == "Phong":
+        parameters += ', "ambient": %d' % rgb2int(material["colorAmbient"])
+        parameters += ', "specular": %d' % rgb2int(material["colorSpecular"])
+        parameters += ', "shininess": %.1g' % material["specularCoef"]
+
+    colorMap = material['mapDiffuse']
+    lightMap = material['mapLight']
+    specularMap = material['mapSpecular']
+    normalMap = material['mapNormal']
+    bumpMap = material['mapBump']
+    normalMapFactor = material['mapNormalFactor']
+    bumpMapScale = material['mapBumpScale']
+
+    if colorMap:
+        parameters += ', "map": %s' % generate_string(colorMap)
+    if lightMap:
+        parameters += ', "lightMap": %s' % generate_string(lightMap)
+    if specularMap:
+        parameters += ', "specularMap": %s' % generate_string(specularMap)
+    if normalMap:
+        parameters += ', "normalMap": %s' % generate_string(normalMap)
+    if bumpMap:
+        parameters += ', "bumpMap": %s' % generate_string(bumpMap)
+
+    if normalMapFactor != 1.0:
+        parameters += ', "normalMapFactor": %g' % normalMapFactor
+
+    if bumpMapScale != 1.0:
+        parameters += ', "bumpMapScale": %g' % bumpMapScale
+
+    if material['vertexColors']:
+        parameters += ', "vertexColors": "vertex"'
+
+    if material['transparent']:
+        parameters += ', "transparent": true'
+
+    parameters += ', "blending": "%s"' % material['blending']
+
+    if not material['depthWrite']:
+        parameters += ', "depthWrite": false'
+
+    if not material['depthTest']:
+        parameters += ', "depthTest": false'
+
+
+    material_string = TEMPLATE_MATERIAL_SCENE % {
+    "material_id" : generate_string(material_id),
+    "type"        : generate_string(material_type),
+    "parameters"  : parameters
+    }
+
+    return material_string
+
+def generate_materials_scene(data):
+    chunks = []
+
+    def material_is_used(mat):
+        minimum_users = 1
+        if mat.use_fake_user:
+            minimum_users = 2 #we must ignore the "fake user" in this case
+        return mat.users >= minimum_users
+    
+    used_materials = [m for m in bpy.data.materials if material_is_used(m)]
+
+    for m in used_materials:
+        material = extract_material_data(m, data["use_colors"])
+        material_string = generate_material_string(material)
+        chunks.append(material_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - cameras
+# #####################################################
+
+def generate_cameras(data):
+    chunks = []
+
+    if data["use_cameras"]:
+
+        cams = bpy.data.objects
+        cams = [ob for ob in cams if (ob.type == 'CAMERA')]
+
+        if not cams:
+            camera = DEFAULTS["camera"]
+
+            if camera["type"] == "PerspectiveCamera":
+
+                camera_string = TEMPLATE_CAMERA_PERSPECTIVE % {
+                "camera_id" : generate_string(camera["name"]),
+                "fov"       : camera["fov"],
+                "aspect"    : camera["aspect"],
+                "near"      : camera["near"],
+                "far"       : camera["far"],
+                "position"  : generate_vec3(camera["position"]),
+                "target"    : generate_vec3(camera["target"])
+                }
+
+            elif camera["type"] == "OrthographicCamera":
+
+                camera_string = TEMPLATE_CAMERA_ORTHO % {
+                "camera_id" : generate_string(camera["name"]),
+                "left"      : camera["left"],
+                "right"     : camera["right"],
+                "top"       : camera["top"],
+                "bottom"    : camera["bottom"],
+                "near"      : camera["near"],
+                "far"       : camera["far"],
+                "position"  : generate_vec3(camera["position"]),
+                "target"    : generate_vec3(camera["target"])
+                }
+
+            chunks.append(camera_string)
+
+        else:
+
+            for cameraobj in cams:
+                camera = bpy.data.cameras[cameraobj.name]
+
+                # TODO:
+                #   Support more than perspective camera
+                #   Calculate a target/lookat
+                #   Get correct aspect ratio
+                if camera.id_data.type == "PERSP":
+
+                    camera_string = TEMPLATE_CAMERA_PERSPECTIVE % {
+                    "camera_id" : generate_string(camera.name),
+                    "fov"       : (camera.angle / 3.14) * 180.0,
+                    "aspect"    : 1.333,
+                    "near"      : camera.clip_start,
+                    "far"       : camera.clip_end,
+                    "position"  : generate_vec3([cameraobj.location[0], -cameraobj.location[1], cameraobj.location[2]], data["flipyz"]),
+                    "target"    : generate_vec3([0, 0, 0])
+                    }
+
+                chunks.append(camera_string)
+
+    return ",\n\n".join(chunks), len(chunks)
+
+# #####################################################
+# Scene exporter - lights
+# #####################################################
+
+def generate_lights(data):
+    chunks = []
+
+    if data["use_lights"]:
+        lamps = data["objects"]
+        lamps = [ob for ob in lamps if (ob.type == 'LAMP')]
+
+        for lamp in lamps:
+            light_string = ""
+            concrete_lamp = lamp.data
+
+            if concrete_lamp.type == "POINT":
+                light_string = TEMPLATE_LIGHT_POINT % {
+                    "light_id"      : generate_string(concrete_lamp.name),
+                    "position"      : generate_vec3(lamp.location, data["flipyz"]),
+                    "rotation"      : generate_vec3(lamp.rotation_euler, data["flipyz"]),
+                    "color"         : rgb2int(concrete_lamp.color),
+                    "distance"      : concrete_lamp.distance,
+                    "intensity"        : concrete_lamp.energy
+                }
+            elif concrete_lamp.type == "SUN":
+                light_string = TEMPLATE_LIGHT_SUN % {
+                    "light_id"      : generate_string(concrete_lamp.name),
+                    "position"      : generate_vec3(lamp.location, data["flipyz"]),
+                    "rotation"      : generate_vec3(lamp.rotation_euler, data["flipyz"]),
+                    "color"         : rgb2int(concrete_lamp.color),
+                    "distance"      : concrete_lamp.distance,
+                    "intensity"        : concrete_lamp.energy
+                }
+            elif concrete_lamp.type == "SPOT":
+                light_string = TEMPLATE_LIGHT_SPOT % {
+                    "light_id"      : generate_string(concrete_lamp.name),
+                    "position"      : generate_vec3(lamp.location, data["flipyz"]),
+                    "rotation"      : generate_vec3(lamp.rotation_euler, data["flipyz"]),
+                    "color"         : rgb2int(concrete_lamp.color),
+                    "distance"      : concrete_lamp.distance,
+                    "intensity"        : concrete_lamp.energy,
+                    "use_shadow"    : concrete_lamp.use_shadow,
+                    "angle"         : concrete_lamp.spot_size
+                }
+            elif concrete_lamp.type == "HEMI":
+                light_string = TEMPLATE_LIGHT_HEMI % {
+                    "light_id"      : generate_string(concrete_lamp.name),
+                    "position"      : generate_vec3(lamp.location, data["flipyz"]),
+                    "rotation"      : generate_vec3(lamp.rotation_euler, data["flipyz"]),
+                    "color"         : rgb2int(concrete_lamp.color),
+                    "distance"      : concrete_lamp.distance,
+                    "intensity"        : concrete_lamp.energy
+                }
+            elif concrete_lamp.type == "AREA":
+                light_string = TEMPLATE_LIGHT_AREA % {
+                    "light_id"      : generate_string(concrete_lamp.name),
+                    "position"      : generate_vec3(lamp.location, data["flipyz"]),
+                    "rotation"      : generate_vec3(lamp.rotation_euler, data["flipyz"]),
+                    "color"         : rgb2int(concrete_lamp.color),
+                    "distance"      : concrete_lamp.distance,
+                    "intensity"        : concrete_lamp.energy,
+                    "gamma"         : concrete_lamp.gamma,
+                    "shape"         : concrete_lamp.shape,
+                    "size"          : concrete_lamp.size,
+                    "size_y"        : concrete_lamp.size_y
+                }
+
+            chunks.append(light_string)
+
+        if not lamps:
+            lamps.append(DEFAULTS["light"])
+
+    return ",\n\n".join(chunks), len(chunks)
+
+
+# #####################################################
+# Scene exporter - embedded meshes
+# #####################################################
+
+def generate_embeds(data):
+
+    if data["embed_meshes"]:
+
+        chunks = []
+
+        for e in data["embeds"]:
+
+            embed = '"emb_%s": {%s}' % (e, data["embeds"][e])
+            chunks.append(embed)
+
+        return ",\n\n".join(chunks)
+
+    return ""
+
+# #####################################################
+# Scene exporter - generate ASCII scene
+# #####################################################
+
+def generate_ascii_scene(data):
+
+    objects, nobjects = generate_objects(data)
+    geometries, ngeometries = generate_geometries(data)
+    textures, ntextures = generate_textures_scene(data)
+    materials, nmaterials = generate_materials_scene(data)
+    lights, nlights = generate_lights(data)
+    cameras, ncameras = generate_cameras(data)
+
+    embeds = generate_embeds(data)
+
+    if nlights > 0:
+        if nobjects > 0:
+            objects = objects + ",\n\n" + lights
+        else:
+            objects = lights
+        nobjects += nlights
+
+    if ncameras > 0:
+        if nobjects > 0:
+            objects = objects + ",\n\n" + cameras
+        else:
+            objects = cameras
+        nobjects += ncameras
+
+    basetype = "relativeTo"
+
+    if data["base_html"]:
+        basetype += "HTML"
+    else:
+        basetype += "Scene"
+
+    sections = [
+    ["objects",    objects],
+    ["geometries", geometries],
+    ["textures",   textures],
+    ["materials",  materials],
+    ["embeds",     embeds]
+    ]
+
+    chunks = []
+    for label, content in sections:
+        if content:
+            chunks.append(generate_section(label, content))
+
+    sections_string = "\n".join(chunks)
+
+    default_camera = ""
+    if data["use_cameras"]:
+        cams = [ob for ob in bpy.data.objects if (ob.type == 'CAMERA' and ob.select)]
+        if not cams:
+            default_camera = "default_camera"
+        else:
+            default_camera = cams[0].name
+
+    parameters = {
+    "fname"     : data["source_file"],
+
+    "sections"  : sections_string,
+
+    "bgcolor"   : generate_vec3(DEFAULTS["bgcolor"]),
+    "bgalpha"   : DEFAULTS["bgalpha"],
+    "defcamera" :  generate_string(default_camera),
+
+    "nobjects"      : nobjects,
+    "ngeometries"   : ngeometries,
+    "ntextures"     : ntextures,
+    "basetype"      : generate_string(basetype),
+    "nmaterials"    : nmaterials,
+
+    "position"      : generate_vec3(DEFAULTS["position"]),
+    "rotation"      : generate_vec3(DEFAULTS["rotation"]),
+    "scale"         : generate_vec3(DEFAULTS["scale"])
+    }
+
+    text = TEMPLATE_SCENE_ASCII % parameters
+
+    return text
+
+def export_scene(scene, filepath, flipyz, option_colors, option_lights, option_cameras, option_embed_meshes, embeds, option_url_base_html, option_copy_textures):
+
+    source_file = os.path.basename(bpy.data.filepath)
+
+    # objects are contained in scene and linked groups
+    objects = []
+
+    # get scene objects
+    sceneobjects = scene.objects
+    for obj in sceneobjects:
+      objects.append(obj)
+
+    scene_text = ""
+    data = {
+    "scene"        : scene,
+    "objects"      : objects,
+    "embeds"       : embeds,
+    "source_file"  : source_file,
+    "filepath"     : filepath,
+    "flipyz"       : flipyz,
+    "use_colors"   : option_colors,
+    "use_lights"   : option_lights,
+    "use_cameras"  : option_cameras,
+    "embed_meshes" : option_embed_meshes,
+    "base_html"    : option_url_base_html,
+    "copy_textures": option_copy_textures
+    }
+    scene_text += generate_ascii_scene(data)
+
+    write_file(filepath, scene_text)
+
+# #####################################################
+# Main
+# #####################################################
+
+def save(operator, context, filepath = "",
+         option_flip_yz = True,
+         option_vertices = True,
+         option_vertices_truncate = False,
+         option_faces = True,
+         option_normals = True,
+         option_uv_coords = True,
+         option_materials = True,
+         option_colors = True,
+         option_bones = True,
+         option_skinning = True,
+         align_model = 0,
+         option_export_scene = False,
+         option_lights = False,
+         option_cameras = False,
+         option_scale = 1.0,
+         option_embed_meshes = True,
+         option_url_base_html = False,
+         option_copy_textures = False,
+         option_animation_morph = False,
+         option_animation_skeletal = False,
+         option_frame_step = 1,
+         option_all_meshes = True):
+
+    #print("URL TYPE", option_url_base_html)
+
+    filepath = ensure_extension(filepath, '.js')
+
+    scene = context.scene
+
+    if scene.objects.active:
+        bpy.ops.object.mode_set(mode='OBJECT')
+
+    if option_all_meshes:
+        sceneobjects = scene.objects
+    else:
+        sceneobjects = context.selected_objects
+
+    # objects are contained in scene and linked groups
+    objects = []
+
+    # get scene objects
+    for obj in sceneobjects:
+      objects.append(obj)
+
+    if option_export_scene:
+
+        geo_set = set()
+        embeds = {}
+
+        for object in objects:
+            if object.type == "MESH" and object.THREE_exportGeometry:
+
+                # create extra copy of geometry with applied modifiers
+                # (if they exist)
+
+                #if len(object.modifiers) > 0:
+                #    name = object.name
+
+                # otherwise can share geometry
+
+                #else:
+                name = object.data.name
+
+                if name not in geo_set:
+
+                    if option_embed_meshes:
+
+                        text, model_string = generate_mesh_string([object], scene,
+                                                        option_vertices,
+                                                        option_vertices_truncate,
+                                                        option_faces,
+                                                        option_normals,
+                                                        option_uv_coords,
+                                                        option_materials,
+                                                        option_colors,
+                                                        option_bones,
+                                                        option_skinning,
+                                                        False,          # align_model
+                                                        option_flip_yz,
+                                                        option_scale,
+                                                        False,          # export_single_model
+                                                        False,          # option_copy_textures
+                                                        filepath,
+                                                        option_animation_morph,
+                                                        option_animation_skeletal,
+                                                        option_frame_step)
+
+                        embeds[object.data.name] = model_string
+
+                    else:
+
+                        fname = generate_mesh_filename(name, filepath)
+                        export_mesh([object], scene,
+                                    fname,
+                                    option_vertices,
+                                    option_vertices_truncate,
+                                    option_faces,
+                                    option_normals,
+                                    option_uv_coords,
+                                    option_materials,
+                                    option_colors,
+                                    option_bones,
+                                    option_skinning,
+                                    False,          # align_model
+                                    option_flip_yz,
+                                    option_scale,
+                                    False,          # export_single_model
+                                    option_copy_textures,
+                                    option_animation_morph,
+                                    option_animation_skeletal,
+                                    option_frame_step)
+
+                    geo_set.add(name)
+
+        export_scene(scene, filepath,
+                     option_flip_yz,
+                     option_colors,
+                     option_lights,
+                     option_cameras,
+                     option_embed_meshes,
+                     embeds,
+                     option_url_base_html,
+                     option_copy_textures)
+
+    else:
+
+        export_mesh(objects, scene, filepath,
+                    option_vertices,
+                    option_vertices_truncate,
+                    option_faces,
+                    option_normals,
+                    option_uv_coords,
+                    option_materials,
+                    option_colors,
+                    option_bones,
+                    option_skinning,
+                    align_model,
+                    option_flip_yz,
+                    option_scale,
+                    True,            # export_single_model
+                    option_copy_textures,
+                    option_animation_morph,
+                    option_animation_skeletal,
+                    option_frame_step)
+
+    return {'FINISHED'}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/import_threejs.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/import_threejs.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/addons/io_mesh_threejs/import_threejs.py	(revision 28897)
@@ -0,0 +1,635 @@
+# ##### BEGIN GPL LICENSE BLOCK #####
+#
+#  This program is free software; you can redistribute it and/or
+#  modify it under the terms of the GNU General Public License
+#  as published by the Free Software Foundation; either version 2
+#  of the License, or (at your option) any later version.
+#
+#  This program 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 General Public License for more details.
+#
+#  You should have received a copy of the GNU General Public License
+#  along with this program; if not, write to the Free Software Foundation,
+#  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# ##### END GPL LICENSE BLOCK #####
+
+"""
+Blender importer for Three.js (ASCII JSON format).
+
+"""
+
+import os
+import time
+import json
+import bpy
+import mathutils
+from mathutils.geometry import tessellate_polygon
+from bpy_extras.image_utils import load_image
+
+# #####################################################
+# Generators
+# #####################################################
+def setColor(c, t):
+    c.r = t[0]
+    c.g = t[1]
+    c.b = t[2]
+
+def create_texture(filename, modelpath):
+    name = filename
+    texture = bpy.data.textures.new(name, type='IMAGE')
+
+    image = load_image(filename, modelpath)
+    has_data = False
+
+    if image:
+        texture.image = image
+        has_data = image.has_data
+
+    return texture
+
+def create_materials(data, modelpath):
+    materials = []
+    materials_data = data.get("materials", [])
+
+    for i, m in enumerate(materials_data):
+
+        name = m.get("DbgName", "material_%d" % i)
+
+        colorAmbient = m.get("colorAmbient", None)
+        colorDiffuse = m.get("colorDiffuse", None)
+        colorSpecular = m.get("colorSpecular", None)
+        alpha = m.get("transparency", 1.0)
+        specular_hardness = m.get("specularCoef", 0)
+
+        mapDiffuse = m.get("mapDiffuse", None)
+        mapLightmap = m.get("mapLightmap", None)
+
+        vertexColorsType = m.get("vertexColors", False)
+
+        useVertexColors = False
+        if vertexColorsType:
+            useVertexColors = True
+
+        material = bpy.data.materials.new(name)
+
+        material.THREE_useVertexColors = useVertexColors
+
+        if colorDiffuse:
+            setColor(material.diffuse_color, colorDiffuse)
+            material.diffuse_intensity = 1.0
+
+        if colorSpecular:
+            setColor(material.specular_color, colorSpecular)
+            material.specular_intensity = 1.0
+
+        if alpha < 1.0:
+            material.alpha = alpha
+            material.use_transparency = True
+
+        if specular_hardness:
+            material.specular_hardness = specular_hardness
+
+        if mapDiffuse:
+            texture = create_texture(mapDiffuse, modelpath)
+            mtex = material.texture_slots.add()
+            mtex.texture = texture
+            mtex.texture_coords = 'UV'
+            mtex.use = True
+            mtex.use_map_color_diffuse = True
+
+            material.active_texture = texture
+
+        materials.append(material)
+
+    return materials
+
+def create_mesh_object(name, vertices, materials, face_data, flipYZ, recalculate_normals):
+
+    faces         = face_data["faces"]
+    vertexNormals = face_data["vertexNormals"]
+    vertexColors  = face_data["vertexColors"]
+    vertexUVs     = face_data["vertexUVs"]
+    faceMaterials = face_data["materials"]
+    faceColors    = face_data["faceColors"]
+
+    edges = []
+
+    # Create a new mesh
+
+    me = bpy.data.meshes.new(name)
+    me.from_pydata(vertices, edges, faces)
+
+    # Handle normals
+
+    if not recalculate_normals:
+        me.update(calc_edges = True)
+
+    if face_data["hasVertexNormals"]:
+
+        print("setting vertex normals")
+        me.update(calc_tessface = True)
+
+        for fi in range(len(faces)):
+
+            if vertexNormals[fi]:
+
+                #print("setting face %i with %i vertices" % (fi, len(normals[fi])))
+
+                # if me.update() is called after setting vertex normals
+                # setting face.use_smooth overrides these normals
+                #  - this fixes weird shading artefacts (seems to come from sharing
+                #    of vertices between faces, didn't find a way how to set vertex normals
+                #    per face use of vertex as opposed to per vertex),
+                #  - probably this just overrides all custom vertex normals
+                #  - to preserve vertex normals from the original data
+                #    call me.update() before setting them
+
+                me.tessfaces[fi].use_smooth = True
+
+                if not recalculate_normals:
+                    for j in range(len(vertexNormals[fi])):
+
+                        vertexNormal = vertexNormals[fi][j]
+
+                        x = vertexNormal[0]
+                        y = vertexNormal[1]
+                        z = vertexNormal[2]
+
+                        if flipYZ:
+                            tmp = y
+                            y = -z
+                            z = tmp
+
+                            # flip normals (this make them look consistent with the original before export)
+
+                            #x = -x
+                            #y = -y
+                            #z = -z
+
+                        vi = me.tessfaces[fi].vertices[j]
+
+                        me.vertices[vi].normal.x = x
+                        me.vertices[vi].normal.y = y
+                        me.vertices[vi].normal.z = z
+
+    if recalculate_normals:
+        me.update(calc_edges = True)
+
+    # Handle colors
+
+    if face_data["hasVertexColors"]:
+
+        print("setting vertex colors")
+
+        me.vertex_colors.new("vertex_color_layer_0")
+
+        for fi in range(len(faces)):
+
+            if vertexColors[fi]:
+
+                face_colors = me.vertex_colors[0].data[fi]
+                face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+
+                for vi in range(len(vertexColors[fi])):
+
+                    r = vertexColors[fi][vi][0]
+                    g = vertexColors[fi][vi][1]
+                    b = vertexColors[fi][vi][2]
+
+                    face_colors[vi].r = r
+                    face_colors[vi].g = g
+                    face_colors[vi].b = b
+
+    elif face_data["hasFaceColors"]:
+
+        print("setting vertex colors from face colors")
+
+        me.vertex_colors.new("vertex_color_layer_0")
+
+        for fi in range(len(faces)):
+
+            if faceColors[fi]:
+
+                r = faceColors[fi][0]
+                g = faceColors[fi][1]
+                b = faceColors[fi][2]
+
+                face_colors = me.vertex_colors[0].data[fi]
+                face_colors = face_colors.color1, face_colors.color2, face_colors.color3, face_colors.color4
+
+                for vi in range(len(faces[fi])):
+
+                    face_colors[vi].r = r
+                    face_colors[vi].g = g
+                    face_colors[vi].b = b
+
+    # Handle uvs
+
+    if face_data["hasVertexUVs"]:
+
+        print("setting vertex uvs")
+
+        for li, layer in enumerate(vertexUVs):
+
+            me.uv_textures.new("uv_layer_%d" % li)
+
+            for fi in range(len(faces)):
+
+                if layer[fi]:
+
+                    uv_face = me.uv_textures[li].data[fi]
+                    face_uvs = uv_face.uv1, uv_face.uv2, uv_face.uv3, uv_face.uv4
+
+                    for vi in range(len(layer[fi])):
+
+                        u = layer[fi][vi][0]
+                        v = layer[fi][vi][1]
+
+                        face_uvs[vi].x = u
+                        face_uvs[vi].y = v
+
+                    active_texture = materials[faceMaterials[fi]].active_texture
+
+                    if active_texture:
+                        uv_face.image = active_texture.image
+
+
+    # Handle materials # 1
+
+    if face_data["hasMaterials"]:
+
+
+        print("setting materials (mesh)")
+
+        for m in materials:
+
+            me.materials.append(m)
+
+        print("setting materials (faces)")
+        me.update(calc_tessface = True)
+
+        for fi in range(len(faces)):
+
+            if faceMaterials[fi] >= 0:
+
+                me.tessfaces[fi].material_index = faceMaterials[fi]
+
+    # Create a new object
+
+    ob = bpy.data.objects.new(name, me)
+    ob.data = me                                # link the mesh data to the object
+
+
+    scene = bpy.context.scene                   # get the current scene
+    scene.objects.link(ob)                      # link the object into the scene
+
+    ob.location = scene.cursor_location         # position object at 3d-cursor
+
+
+# #####################################################
+# Faces
+# #####################################################
+
+def extract_faces(data):
+
+    result = {
+    "faces"         : [],
+    "materials"     : [],
+    "faceUVs"       : [],
+    "vertexUVs"     : [],
+    "faceNormals"   : [],
+    "vertexNormals" : [],
+    "faceColors"    : [],
+    "vertexColors"  : [],
+
+    "hasVertexNormals"  : False,
+    "hasVertexUVs"      : False,
+    "hasVertexColors"   : False,
+    "hasFaceColors"     : False,
+    "hasMaterials"      : False
+    }
+
+    faces = data.get("faces", [])
+    normals = data.get("normals", [])
+    colors = data.get("colors", [])
+
+    offset = 0
+    zLength = len(faces)
+
+    # disregard empty arrays
+
+    nUvLayers = 0
+
+    for layer in data["uvs"]:
+
+        if len(layer) > 0:
+            nUvLayers += 1
+            result["faceUVs"].append([])
+            result["vertexUVs"].append([])
+
+
+    while ( offset < zLength ):
+
+        type = faces[ offset ]
+        offset += 1
+
+        isQuad          	= isBitSet( type, 0 )
+        hasMaterial         = isBitSet( type, 1 )
+        hasFaceUv           = isBitSet( type, 2 )
+        hasFaceVertexUv     = isBitSet( type, 3 )
+        hasFaceNormal       = isBitSet( type, 4 )
+        hasFaceVertexNormal = isBitSet( type, 5 )
+        hasFaceColor	    = isBitSet( type, 6 )
+        hasFaceVertexColor  = isBitSet( type, 7 )
+
+        #print("type", type, "bits", isQuad, hasMaterial, hasFaceUv, hasFaceVertexUv, hasFaceNormal, hasFaceVertexNormal, hasFaceColor, hasFaceVertexColor)
+
+        result["hasVertexUVs"] = result["hasVertexUVs"] or hasFaceVertexUv
+        result["hasVertexNormals"] = result["hasVertexNormals"] or hasFaceVertexNormal
+        result["hasVertexColors"] = result["hasVertexColors"] or hasFaceVertexColor
+        result["hasFaceColors"] = result["hasFaceColors"] or hasFaceColor
+        result["hasMaterials"] = result["hasMaterials"] or hasMaterial
+
+        # vertices
+
+        if isQuad:
+
+            a = faces[ offset ]
+            offset += 1
+
+            b = faces[ offset ]
+            offset += 1
+
+            c = faces[ offset ]
+            offset += 1
+
+            d = faces[ offset ]
+            offset += 1
+
+            face = [a, b, c, d]
+
+            nVertices = 4
+
+        else:
+
+            a = faces[ offset ]
+            offset += 1
+
+            b = faces[ offset ]
+            offset += 1
+
+            c = faces[ offset ]
+            offset += 1
+
+            face = [a, b, c]
+
+            nVertices = 3
+
+        result["faces"].append(face)
+
+        # material
+
+        if hasMaterial:
+
+            materialIndex = faces[ offset ]
+            offset += 1
+
+        else:
+
+            materialIndex = -1
+
+        result["materials"].append(materialIndex)
+
+        # uvs
+
+        for i in range(nUvLayers):
+
+            faceUv = None
+
+            if hasFaceUv:
+
+                uvLayer = data["uvs"][ i ]
+
+                uvIndex = faces[ offset ]
+                offset += 1
+
+                u = uvLayer[ uvIndex * 2 ]
+                v = uvLayer[ uvIndex * 2 + 1 ]
+
+                faceUv = [u, v]
+
+            result["faceUVs"][i].append(faceUv)
+
+
+            if hasFaceVertexUv:
+
+                uvLayer = data["uvs"][ i ]
+
+                vertexUvs = []
+
+                for j in range(nVertices):
+
+                    uvIndex = faces[ offset ]
+                    offset += 1
+
+                    u = uvLayer[ uvIndex * 2 ]
+                    v = uvLayer[ uvIndex * 2 + 1 ]
+
+                    vertexUvs.append([u, v])
+
+            result["vertexUVs"][i].append(vertexUvs)
+
+
+        if hasFaceNormal:
+
+            normalIndex = faces[ offset ] * 3
+            offset += 1
+
+            x = normals[ normalIndex ]
+            y = normals[ normalIndex + 1 ]
+            z = normals[ normalIndex + 2 ]
+
+            faceNormal = [x, y, z]
+
+        else:
+
+            faceNormal = None
+
+        result["faceNormals"].append(faceNormal)
+
+
+        if hasFaceVertexNormal:
+
+            vertexNormals = []
+
+            for j in range(nVertices):
+
+                normalIndex = faces[ offset ] * 3
+                offset += 1
+
+                x = normals[ normalIndex ]
+                y = normals[ normalIndex + 1 ]
+                z = normals[ normalIndex + 2 ]
+
+                vertexNormals.append( [x, y, z] )
+
+
+        else:
+
+            vertexNormals = None
+
+        result["vertexNormals"].append(vertexNormals)
+
+
+        if hasFaceColor:
+
+            colorIndex = faces[ offset ]
+            offset += 1
+
+            faceColor = hexToTuple( colors[ colorIndex ] )
+
+        else:
+
+            faceColor = None
+
+        result["faceColors"].append(faceColor)
+
+
+        if hasFaceVertexColor:
+
+            vertexColors = []
+
+            for j in range(nVertices):
+
+                colorIndex = faces[ offset ]
+                offset += 1
+
+                color = hexToTuple( colors[ colorIndex ] )
+                vertexColors.append( color )
+
+        else:
+
+            vertexColors = None
+
+        result["vertexColors"].append(vertexColors)
+
+
+    return result
+
+# #####################################################
+# Utils
+# #####################################################
+
+def hexToTuple( hexColor ):
+    r = (( hexColor >> 16 ) & 0xff) / 255.0
+    g = (( hexColor >> 8 ) & 0xff) / 255.0
+    b = ( hexColor & 0xff) / 255.0
+    return (r, g, b)
+
+def isBitSet(value, position):
+    return value & ( 1 << position )
+
+def splitArray(data, chunkSize):
+    result = []
+    chunk = []
+    for i in range(len(data)):
+        if i > 0 and i % chunkSize == 0:
+            result.append(chunk)
+            chunk = []
+        chunk.append(data[i])
+    result.append(chunk)
+    return result
+
+
+def extract_json_string(text):
+    marker_begin = "var model ="
+    marker_end = "postMessage"
+
+    start = text.find(marker_begin) + len(marker_begin)
+    end = text.find(marker_end)
+    end = text.rfind("}", start, end)
+    return text[start:end+1].strip()
+
+def get_name(filepath):
+    return os.path.splitext(os.path.basename(filepath))[0]
+
+def get_path(filepath):
+    return os.path.dirname(filepath)
+
+# #####################################################
+# Parser
+# #####################################################
+
+def load(operator, context, filepath, option_flip_yz = True, recalculate_normals = True, option_worker = False):
+
+    print('\nimporting %r' % filepath)
+
+    time_main = time.time()
+
+    print("\tparsing JSON file...")
+
+    time_sub = time.time()
+
+    file = open(filepath, 'rU')
+    rawcontent = file.read()
+    file.close()
+
+    if option_worker:
+        json_string = extract_json_string(rawcontent)
+    else:
+        json_string = rawcontent
+    data = json.loads( json_string )
+
+    time_new = time.time()
+
+    print('parsing %.4f sec' % (time_new - time_sub))
+
+    time_sub = time_new
+
+    # flip YZ
+
+    vertices = splitArray(data["vertices"], 3)
+
+    if option_flip_yz:
+        vertices[:] = [(v[0], -v[2], v[1]) for v in vertices]
+
+    # extract faces
+
+    face_data = extract_faces(data)
+
+    # deselect all
+
+    bpy.ops.object.select_all(action='DESELECT')
+
+    nfaces = len(face_data["faces"])
+    nvertices = len(vertices)
+    nnormals = len(data.get("normals", [])) / 3
+    ncolors = len(data.get("colors", [])) / 3
+    nuvs = len(data.get("uvs", [])) / 2
+    nmaterials = len(data.get("materials", []))
+
+    print('\tbuilding geometry...\n\tfaces:%i, vertices:%i, vertex normals: %i, vertex uvs: %i, vertex colors: %i, materials: %i ...' % (
+        nfaces, nvertices, nnormals, nuvs, ncolors, nmaterials ))
+
+    # Create materials
+
+    materials = create_materials(data, get_path(filepath))
+
+    # Create new obj
+
+    create_mesh_object(get_name(filepath), vertices, materials, face_data, option_flip_yz, recalculate_normals)
+
+    scene = bpy.context.scene
+    scene.update()
+
+    time_new = time.time()
+
+    print('finished importing: %r in %.4f sec.' % (filepath, (time_new - time_main)))
+    return {'FINISHED'}
+
+
+if __name__ == "__main__":
+    register()
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/2.65/scripts/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/README.md
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/README.md	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/README.md	(revision 28897)
@@ -0,0 +1,39 @@
+# Three.js Blender Import/Export
+
+Imports and exports Three.js' ASCII JSON format.
+
+## Installation
+
+Copy the io_mesh_threejs folder to the scripts/addons folder. If it doesn't exist, create it. The full path is OS-dependent (see below).
+
+Once that is done, you need to activate the plugin. Open Blender preferences, look for
+Addons, search for `three`, enable the checkbox next to the `Import-Export: three.js format` entry.
+
+Goto Usage.
+
+### Windows
+
+Should look like this:
+
+    C:\Users\USERNAME\AppData\Roaming\Blender Foundation\Blender\2.6X\scripts\addons
+
+### OSX
+
+Depends on where blender.app is. Assuming you copied it to your Applications folder:
+
+    /Applications/Blender/blender.app/Contents/MacOS/2.6X/scripts/addons
+
+### Linux
+
+By default, this should look like:
+
+    /home/USERNAME/.config/blender/2.6X/scripts/addons
+
+For Ubuntu users who installed Blender 2.6.8 via apt-get, this is the location:
+
+    /usr/lib/blender/scripts/addons
+    
+
+## Usage
+
+Activate the Import-Export addon under "User Preferences" > "Addons" and then use the regular Import and Export menu within Blender, select `Three.js (js)`.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/blender/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/max/ThreeJSExporter.ms
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/max/ThreeJSExporter.ms	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/max/ThreeJSExporter.ms	(revision 28897)
@@ -0,0 +1,1074 @@
+-------------------------------------------------------------------------------------
+-- ThreeJSExporter.ms
+-- Exports geometry from 3ds max to Three.js models in ASCII JSON format v3
+-- By alteredq / http://alteredqualia.com
+-------------------------------------------------------------------------------------
+
+rollout ThreeJSExporter "ThreeJSExporter"
+(
+	-- Variables
+
+	local ostream,
+
+	headerFormat = "\"metadata\":
+{
+\"sourceFile\": \"%\",
+\"generatedBy\": \"3ds max ThreeJSExporter\",
+\"formatVersion\": 3.1,
+\"vertices\": %,
+\"normals\": %,
+\"colors\": %,
+\"uvs\": %,
+\"triangles\": %,
+\"materials\": %
+},
+
+",
+
+	vertexFormat = "%,%,%",
+
+	vertexNormalFormat = "%,%,%",
+	UVFormat = "%,%",
+
+	triFormat = "%,%,%,%",
+	triUVFormat = "%,%,%,%,%,%,%",
+	triNFormat = "%,%,%,%,%,%,%",
+	triUVNFormat = "%,%,%,%,%,%,%,%,%,%",
+
+	footerFormat = "}"
+
+	-------------------------------------------------------------------------------------
+	-- User interface
+
+
+	group "ThreeJSExporter  v0.8"
+	(
+
+		label msg "Exports selected meshes in Three.js ascii JSON format" align:#left
+		hyperLink lab1 "Original source at GitHub" address:"https://github.com/alteredq/three.js/blob/master/utils/exporters/max/ThreeJSExporter.ms" color:(color 255 120 0) align:#left
+
+		label dummy1 "--------------------------------------------------------" align:#left
+
+		checkbox exportColor "Export vertex colors" checked:false enabled:true
+		checkbox exportUv "Export uvs" checked:true enabled:true
+		checkbox exportNormal "Export normals" checked:true enabled:true
+		checkbox smoothNormal "Use vertex normals" checked:false enabled:true
+
+		label dummy2 "--------------------------------------------------------" align:#left
+
+		checkbox flipYZ "Flip YZ" checked:true enabled:true
+		checkbox flipUV "Flip UV" checked:false enabled:true
+		checkbox flipFace "Flip all faces" checked:false enabled:true
+		checkbox autoflipFace "Try fixing flipped faces" checked:false enabled:true
+
+		label dummy3 "--------------------------------------------------------" align:#left
+
+		button btn_export "Export selected objects"
+
+	)
+
+
+	-------------------------------------------------------------------------------------
+	-- Dump vertices
+
+	function DumpVertices src =
+	(
+
+		Format "\"vertices\": [" to:ostream
+
+		num = src.count
+
+		if num > 0 then
+		(
+
+			for i = 1 to num do
+			(
+
+				vert = src[i]
+
+				if flipYZ.checked then
+				(
+					x = vert.x
+					y = vert.z
+					z = vert.y
+
+					z *= -1
+
+				)
+				else
+				(
+
+					x = vert.x
+					y = vert.y
+					z = vert.z
+
+				)
+
+				Format vertexFormat x y z to:ostream
+
+				if i < num then Format "," to:ostream
+
+			)
+
+		)
+
+		Format "],\n\n" to:ostream
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Dump colors
+
+	function DumpColors src useColors =
+	(
+
+		Format "\"colors\": [" to:ostream
+
+		num = src.count
+
+		if num > 0 and useColors then
+		(
+
+			for i = 1 to num do
+			(
+
+				col = src[i]
+
+				r = col.r as Integer
+				g = col.g as Integer
+				b = col.b as Integer
+
+				hexNum = ( bit.shift r 16 ) + ( bit.shift g 8 ) + b
+
+				-- hexColor = formattedPrint hexNum format:"#x"
+				-- Format "%" hexColor to:ostream
+
+				decColor = formattedPrint hexNum format:"#d"
+				Format "%" decColor to:ostream
+
+				if i < num then Format "," to:ostream
+
+			)
+
+		)
+
+		Format "],\n\n" to:ostream
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Dump normals
+
+	function DumpNormals src =
+	(
+
+		Format "\"normals\": [" to:ostream
+
+		num = src.count
+
+		if num > 0 and exportNormal.checked then
+		(
+
+			for i = 1 to num do
+			(
+
+				normal = src[i]
+				normal = normalize normal as point3
+
+				if flipYZ.checked then
+				(
+
+					x = normal.x
+					y = normal.z
+					z = normal.y
+
+					z *= -1
+
+				)
+				else
+				(
+
+					x = normal.x
+					y = normal.y
+					z = normal.z
+
+				)
+
+				Format vertexNormalFormat x y z to:ostream
+
+				if i < num then Format "," to:ostream
+
+			)
+
+		)
+
+		Format "],\n\n" to:ostream
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Dump uvs
+
+	function DumpUvs src =
+	(
+
+		Format "\"uvs\": [[" to:ostream
+
+		num = src.count
+
+		if num > 0 and exportUv.checked then
+		(
+
+			for i = 1 to num do
+			(
+
+				uvw = src[i]
+
+				u = uvw.x
+
+				if flipUV.checked then
+				(
+					v = 1 - uvw.y
+				)
+				else
+				(
+					v = uvw.y
+				)
+
+				Format UVFormat u v to:ostream
+
+				if i < num then Format "," to:ostream
+
+			)
+
+		)
+
+		Format "]],\n\n" to:ostream
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Dump faces
+
+	function DumpFaces src useColors =
+	(
+
+		Format "\"faces\": [" to:ostream
+
+		num = src.count
+
+		if num > 0 then
+		(
+
+			for i = 1 to num do
+			(
+
+				zface = src[i]
+
+				fv  = zface[1]
+				fuv = zface[2]
+				m   = zface[3] - 1
+				fc  = zface[4]
+
+				needsFlip = zface[5]
+
+				isTriangle = true
+				hasMaterial = true
+				hasFaceUvs = false
+				hasFaceVertexUvs = ((classof fuv == Point3) and exportUv.checked)
+				hasFaceNormals = false
+				hasFaceVertexNormals = (exportNormal.checked)
+				hasFaceColors = false
+				hasFaceVertexColors = ((classof fc == Point3) and useColors)
+
+				faceType = 0
+				faceType = bit.set faceType 1 (not isTriangle)
+				faceType = bit.set faceType 2 hasMaterial
+				faceType = bit.set faceType 3 hasFaceUvs
+				faceType = bit.set faceType 4 hasFaceVertexUvs
+				faceType = bit.set faceType 5 hasFaceNormals
+				faceType = bit.set faceType 6 hasFaceVertexNormals
+				faceType = bit.set faceType 7 hasFaceColors
+				faceType = bit.set faceType 8 hasFaceVertexColors
+
+				if i > 1 then
+				(
+					Format "," faceType to:ostream
+				)
+
+				Format "%" faceType to:ostream
+
+				if isTriangle then
+				(
+
+					va = (fv.x - 1) as Integer
+					vb = (fv.y - 1) as Integer
+					vc = (fv.z - 1) as Integer
+
+					if flipFace.checked or needsFlip then
+					(
+
+						tmp = vb
+						vb = vc
+						vc = tmp
+
+					)
+
+
+					Format ",%,%,%" va vb vc to:ostream
+
+
+					if hasMaterial then
+					(
+
+						Format ",%" m to:ostream
+
+					)
+
+					if hasFaceVertexUvs then
+					(
+
+						ua = (fuv.x - 1) as Integer
+						ub = (fuv.y - 1) as Integer
+						uc = (fuv.z - 1) as Integer
+
+						if flipFace.checked or needsFlip then
+						(
+
+							tmp = ub
+							ub = uc
+							uc = tmp
+
+						)
+
+						Format ",%,%,%" ua ub uc to:ostream
+
+					)
+
+					if hasFaceVertexNormals then
+					(
+
+						if smoothNormal.checked then
+						(
+
+							-- normals have the same indices as vertices
+
+							na = va
+							nb = vb
+							nc = vc
+
+						)
+						else
+						(
+							-- normals have the same indices as face
+
+							na = i - 1
+							nb = na
+							nc = na
+
+						)
+
+						if flipFace.checked or needsFlip then
+						(
+
+							tmp = nb
+							nb = nc
+							nc = tmp
+
+						)
+
+						Format ",%,%,%" na nb nc to:ostream
+
+					)
+
+
+					if hasFaceVertexColors then
+					(
+
+						ca = (fc.x - 1) as Integer
+						cb = (fc.y - 1) as Integer
+						cc = (fc.z - 1) as Integer
+
+						if flipFace.checked or needsFlip then
+						(
+
+							tmp = cb
+							cb = cc
+							cc = tmp
+
+						)
+
+						Format ",%,%,%" ca cb cc to:ostream
+
+					)
+
+				)
+
+			)
+
+		)
+
+		Format "]\n\n" to:ostream
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Dump color
+
+	function DumpColor pcolor label =
+	(
+		r = pcolor.r / 255
+		g = pcolor.g / 255
+		b = pcolor.b / 255
+
+		fr = formattedPrint r format:".4f"
+		fg = formattedPrint g format:".4f"
+		fb = formattedPrint b format:".4f"
+
+		Format "\"%\"  : [%, %, %],\n" label fr fg fb to:ostream
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Dump map
+
+	function DumpMap pmap label =
+	(
+
+		if classof pmap == BitmapTexture then
+		(
+			bm = pmap.bitmap
+
+			if bm != undefined then
+			(
+
+				fname = filenameFromPath bm.filename
+				Format "\"%\"    : \"%\",\n" label fname to:ostream
+
+			)
+
+		)
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Export materials
+
+	function ExportMaterials zmaterials zcolors =
+	(
+
+		Format "\"materials\": [\n" to:ostream
+
+		totalMaterials = zmaterials.count
+
+		for i = 1 to totalMaterials do
+		(
+			mat = zmaterials[i]
+
+			Format "{\n" to:ostream
+
+			-- debug
+
+			Format "\"DbgIndex\" : %,\n" (i-1) to:ostream
+
+			if classof mat != BooleanClass then
+			(
+
+				useVertexColors = zcolors[i]
+
+				Format "\"DbgName\"  : \"%\",\n" mat.name to:ostream
+
+				-- colors
+
+				DumpColor mat.diffuse  "colorDiffuse"
+				DumpColor mat.ambient  "colorAmbient"
+				DumpColor mat.specular "colorSpecular"
+
+				t = mat.opacity / 100
+				s = mat.glossiness
+
+				Format "\"transparency\"  : %,\n" t to:ostream
+				Format "\"specularCoef\"  : %,\n" s to:ostream
+
+				-- maps
+
+				DumpMap mat.diffuseMap  "mapDiffuse"
+				DumpMap mat.ambientMap  "mapAmbient"
+				DumpMap mat.specularMap "mapSpecular"
+				DumpMap mat.bumpMap 	"mapBump"
+				DumpMap mat.opacityMap 	"mapAlpha"
+
+			)
+			else
+			(
+
+				useVertexColors = false
+
+				Format "\"DbgName\"  : \"%\",\n" "dummy" to:ostream
+
+				DumpColor red "colorDiffuse"
+
+			)
+
+			Format "\"vertexColors\" : %\n" useVertexColors to:ostream
+			Format "}" to:ostream
+
+			if ( i < totalMaterials ) then Format "," to:ostream
+			Format "\n\n" to:ostream
+
+		)
+
+		Format "],\n\n" to:ostream
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Extract vertices from mesh
+
+	function ExtractVertices obj whereto =
+	(
+
+		n = obj.numVerts
+
+		for i = 1 to n do
+		(
+
+			v = GetVert obj i
+			append whereto v
+
+		)
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Extract vertex colors from mesh
+
+	function ExtractColors obj whereto =
+	(
+
+		nColors = GetNumCPVVerts obj
+
+		if nColors > 0 then
+		(
+
+			for i = 1 to nColors do
+			(
+
+				c = GetVertColor obj i
+				append whereto c
+
+			)
+
+		)
+
+	)
+
+
+	-------------------------------------------------------------------------------------
+	-- Extract normals from mesh
+
+	function ExtractNormals obj whereto needsFlip =
+	(
+
+		if smoothNormal.checked then
+		(
+
+			num = obj.numVerts
+
+			for i = 1 to num do
+			(
+
+				n = GetNormal obj i
+
+				if flipFace.checked or needsFlip then
+				(
+					n.x *= -1
+					n.y *= -1
+					n.z *= -1
+				)
+
+				append whereto n
+
+			)
+
+		)
+		else
+		(
+
+			num = obj.numFaces
+
+			for i = 1 to num do
+			(
+
+				n = GetFaceNormal obj i
+
+				if flipFace.checked or needsFlip then
+				(
+					n.x *= -1
+					n.y *= -1
+					n.z *= -1
+				)
+
+				append whereto n
+
+			)
+
+		)
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Extract uvs from mesh
+
+	function ExtractUvs obj whereto =
+	(
+		n = obj.numTVerts
+
+		for i = 1 to n do
+		(
+
+			v = GetTVert obj i
+			append whereto v
+
+		)
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Extract faces from mesh
+
+	function ExtractFaces objMesh objMaterial whereto allMaterials needsFlip hasVColors offsetVert offsetUv offsetColor =
+	(
+		n = objMesh.numFaces
+		hasUVs = objMesh.numTVerts > 0
+
+		useMultiMaterial = false
+		materialIDList = #()
+
+		materialClass = classof objMaterial
+
+		if materialClass == StandardMaterial then
+		(
+
+			fm = findItem allMaterials objMaterial
+
+		)
+		else if materialClass == MultiMaterial then
+		(
+
+			useMultiMaterial = true
+
+			for i = 1 to n do
+			(
+
+				mID = GetFaceMatID objMesh i
+				materialIndex = findItem objMaterial.materialIDList mID
+
+				if materialIndex > 0 then
+				(
+
+					subMaterial = objMaterial.materialList[materialIndex]
+
+					mMergedIndex = findItem allMaterials subMaterial
+
+					if mMergedIndex > 0 then
+					(
+
+						materialIDList[mID] = mMergedIndex
+
+					)
+					else
+					(
+
+						materialIDList[mID] = findItem allMaterials false
+
+					)
+
+				)
+				else
+				(
+
+					materialIDList[mID] = findItem allMaterials false
+
+				)
+
+			)
+
+		)
+		else
+		(
+
+			-- undefined material
+
+			fm = findItem allMaterials false
+
+		)
+
+		for i = 1 to n do
+		(
+
+			zface = #()
+
+			fv = GetFace objMesh i
+
+			fv.x += offsetVert
+			fv.y += offsetVert
+			fv.z += offsetVert
+
+			if useMultiMaterial then
+			(
+
+				mID = GetFaceMatID objMesh i
+				fm = materialIDList[mID]
+
+			)
+
+			if hasUVs then
+			(
+
+				fuv = GetTVFace objMesh i
+
+				fuv.x += offsetUv
+				fuv.y += offsetUv
+				fuv.z += offsetUv
+
+			)
+			else
+			(
+
+				fuv = false
+
+			)
+
+			if hasVColors then
+			(
+
+				fc = GetVCFace objMesh i
+
+				fc.x += offsetColor
+				fc.y += offsetColor
+				fc.z += offsetColor
+
+			)
+			else
+			(
+
+				fc = false
+
+			)
+
+			append zface fv
+			append zface fuv
+			append zface fm
+			append zface fc
+			append zface needsFlip
+
+			append whereto zface
+
+		)
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Extract materials from eventual multi-material
+
+	function ExtractMaterials objMesh objMaterial whereto wheretoColors zname hasVColors =
+	(
+
+		materialClass = classof objMaterial
+
+		if materialClass == StandardMaterial then
+		(
+
+			if ( findItem whereto objMaterial ) == 0 then
+			(
+
+				append whereto objMaterial
+				append wheretoColors hasVColors
+
+			)
+
+		)
+		else if materialClass == MultiMaterial then
+		(
+
+			n = objMesh.numFaces
+
+			for i = 1 to n do
+			(
+
+				mID = getFaceMatId objMesh i
+				materialIndex = findItem objMaterial.materialIDList mID
+
+				if materialIndex > 0 then
+				(
+
+					subMaterial = objMaterial.materialList[materialIndex]
+
+					if ( findItem whereto subMaterial ) == 0 then
+					(
+
+						append whereto subMaterial
+						append wheretoColors hasVColors
+
+					)
+
+				)
+
+			)
+
+		)
+		else
+		(
+
+			-- unknown or undefined material
+
+			append whereto false
+			append wheretoColors false
+
+		)
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Hack to figure out if normals are messed up
+
+	function NeedsFaceFlip node =
+	(
+		needsFlip = false
+
+		local tmp = Snapshot node
+
+		face_normal = normalize ( getfacenormal tmp 1 )
+
+		face = getface tmp 1
+
+		va = getvert tmp face[1]
+		vb = getvert tmp face[2]
+		vc = getvert tmp face[3]
+
+		computed_normal = normalize ( cross (vc - vb)  (va - vb) )
+
+		if distance computed_normal face_normal > 0.1 then needsFlip = true
+
+		delete tmp
+
+		return needsFlip
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Extract only things that either already are or can be converted to meshes
+
+	function ExtractMesh node =
+	(
+
+		if SuperClassOf node == GeometryClass then
+		(
+			needsFlip = false
+			hasVColors = false
+
+			zmesh = SnapshotAsMesh node
+
+			if autoflipFace.checked then
+			(
+
+				needsFlip = NeedsFaceFlip node
+
+			)
+
+			if exportColor.checked and ( getNumCPVVerts zmesh ) > 0 then
+			(
+
+				hasVColors = true
+
+			)
+
+			return #( zmesh, node.name, node.material, needsFlip, hasVColors )
+
+		)
+
+		-- Not geometry ... could be a camera, light, etc.
+
+		return #( false, node.name, 0, false, false )
+
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Export scene
+
+	function ExportScene =
+	(
+
+		-- Extract meshes
+
+		meshObjects = #()
+
+		mergedVertices = #()
+		mergedNormals = #()
+		mergedColors = #()
+
+		mergedUvs = #()
+		mergedFaces = #()
+
+		mergedMaterials = #()
+		mergedMaterialsColors = #()
+
+		sceneHasVColors = false
+
+		for obj in selection do
+		(
+
+			result = ExtractMesh obj
+			meshObj = result[1]
+
+			if ClassOf meshObj == TriMesh then
+			(
+
+				meshName     = result[2]
+				meshMaterial = result[3]
+				needsFlip    = result[4]
+				hasVColors   = result[5]
+
+				sceneHasVColors = sceneHasVColors or hasVColors
+
+				append meshObjects result
+
+				vertexOffset = mergedVertices.count
+				uvOffset = mergedUvs.count
+				colorOffset = mergedColors.count
+
+				ExtractMaterials meshObj meshMaterial mergedMaterials mergedMaterialsColors meshName hasVColors
+
+				ExtractVertices meshObj mergedVertices
+				ExtractNormals meshObj mergedNormals needsFlip
+				ExtractColors meshObj mergedColors
+
+				ExtractUvs meshObj mergedUvs
+
+				ExtractFaces meshObj meshMaterial mergedFaces mergedMaterials needsFlip hasVColors vertexOffset uvOffset colorOffset
+
+			)
+
+		)
+
+		totalVertices = mergedVertices.count
+		totalFaces = mergedFaces.count
+		totalMaterials = mergedMaterials.count
+
+		totalColors = 0
+		totalNormals = 0
+		totalUvs = 0
+
+		useColors = false
+
+		if sceneHasVColors and exportColor.checked then
+		(
+
+			totalColors = mergedColors.count
+			useColors = true
+
+		)
+
+		if exportNormal.checked then
+		(
+
+			totalNormals = mergedNormals.count
+
+		)
+
+		if exportUv.checked then
+		(
+
+			totalUvs = mergedUvs.count
+
+		)
+
+
+		-- Dump objects (debug)
+
+		-- Format "// Source objects:\n\n" to:ostream
+
+		-- i = 0
+
+		-- for obj in meshObjects do
+		-- (
+
+		-- 	meshName = obj[2]
+		-- 	Format "// %: %\n" i meshName to:ostream
+		-- 	i += 1
+
+		-- )
+
+		-- Dump model
+
+		Format "{\n\n" to:ostream
+
+		-- Dump header
+
+		Format headerFormat maxFileName totalVertices totalNormals totalColors totalUvs totalFaces totalMaterials to:ostream
+
+		-- Dump all materials in the scene
+
+		ExportMaterials mergedMaterials mergedMaterialsColors
+
+		-- Dump merged data from all selected geometries
+
+		DumpVertices mergedVertices
+		DumpNormals mergedNormals
+		DumpColors mergedColors useColors
+		DumpUvs mergedUvs
+		DumpFaces mergedFaces useColors
+
+		-- Dump footer
+
+		Format footerFormat to:ostream
+
+	)
+
+
+	-------------------------------------------------------------------------------------
+	-- Open and prepare a file handle for writing
+
+	function GetSaveFileStream =
+	(
+		zname = getFilenameFile maxFileName
+		zname += ".js"
+
+		fname = GetSaveFileName filename:zname types:"JavaScript file (*.js)|*.js|All Files(*.*)|*.*|"
+		if fname == undefined then
+		(
+
+			return undefined
+
+		)
+
+		ostream = CreateFile fname
+		if ostream == undefined then
+		(
+
+			MessageBox "Couldn't open file for writing !"
+			return undefined
+
+		)
+
+		return ostream
+	)
+
+	-------------------------------------------------------------------------------------
+	-- Export button click handler
+
+	on btn_export pressed do
+	(
+		ostream = GetSaveFileStream()
+		if ostream != undefined then
+		(
+
+			ExportScene()
+			close ostream
+
+		)
+
+	)
+
+)
+createDialog ThreeJSExporter width:300
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/max/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/max/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/max/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/README.md
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/README.md	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/README.md	(revision 28897)
@@ -0,0 +1,24 @@
+# Three.js Maya Export
+
+Exports Maya models to Three.js' ASCII JSON format.  Currently supports exporting the following:
+
+- Vertices
+- Faces
+- Normals
+- UV sets
+- Material indices
+- Vertex colors
+
+## Installation
+
+Copy the scripts and plug-ins folders to the appropriate maya folder, where `maya-version` is your current version of Maya (eg. 2013-x64).
+
+- Windows: `C:\Users\username\Documents\maya\maya-version`
+- OSX: `~/Library/Preferences/Autodesk/maya/maya-version`
+- Linux: `/usr/autodesk/userconfig/maya/maya-version`
+
+After that, you need to activate the plugin.  In Maya, open `Window > Settings/Preferences > Plug-in Manager` and enable the checkboxes next to `threeJsFileTranslator.py`.
+
+## Usage
+
+Use the regular Export menus within Maya, select `Three.js`.
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/plug-ins/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/plug-ins/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/plug-ins/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/plug-ins/threeJsFileTranslator.py
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/plug-ins/threeJsFileTranslator.py	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/plug-ins/threeJsFileTranslator.py	(revision 28897)
@@ -0,0 +1,273 @@
+__author__ = 'Chris Lewis'
+__version__ = '0.1.0'
+__email__ = 'clewis1@c.ringling.edu'
+
+import sys
+import json
+
+import maya.cmds as mc
+from maya.OpenMaya import *
+from maya.OpenMayaMPx import *
+
+kPluginTranslatorTypeName = 'Three.js'
+kOptionScript = 'ThreeJsExportScript'
+kDefaultOptionsString = '0'
+
+FLOAT_PRECISION = 8
+
+
+# adds decimal precision to JSON encoding
+class DecimalEncoder(json.JSONEncoder):
+    def _iterencode(self, o, markers=None):
+        if isinstance(o, float):
+            s = str(o)
+            if '.' in s and len(s[s.index('.'):]) > FLOAT_PRECISION - 1:
+                s = '%.{0}f'.format(FLOAT_PRECISION) % o
+                while '.' in s and s[-1] == '0':
+                    s = s[:-1] # this actually removes the last "0" from the string
+                if s[-1] == '.': # added this test to avoid leaving "0." instead of "0.0",
+                    s += '0'    # which would throw an error while loading the file
+            return (s for s in [s])
+        return super(DecimalEncoder, self)._iterencode(o, markers)
+
+
+class ThreeJsError(Exception):
+    pass
+
+
+class ThreeJsWriter(object):
+    def __init__(self):
+        self.componentKeys = ['vertices', 'normals', 'colors', 'uvs', 'materials', 'faces']
+
+    def _parseOptions(self, optionsString):
+        self.options = dict([(x, False) for x in self.componentKeys])
+        optionsString = optionsString[2:] # trim off the "0;" that Maya adds to the options string
+        for option in optionsString.split(' '):
+            self.options[option] = True
+
+    def _updateOffsets(self):
+        for key in self.componentKeys:
+            if key == 'uvs':
+                continue
+            self.offsets[key] = len(getattr(self, key))
+        for i in range(len(self.uvs)):
+            self.offsets['uvs'][i] = len(self.uvs[i])
+
+    def _getTypeBitmask(self, options):
+        bitmask = 0
+        if options['materials']:
+            bitmask |= 2
+        if options['uvs']:
+            bitmask |= 8
+        if options['normals']:
+            bitmask |= 32
+        if options['colors']:
+            bitmask |= 128
+        return bitmask
+
+    def _exportMesh(self, dagPath, component):
+        mesh = MFnMesh(dagPath)
+        options = self.options.copy()
+        self._updateOffsets()
+
+        # export vertex data
+        if options['vertices']:
+            try:
+                iterVerts = MItMeshVertex(dagPath, component)
+                while not iterVerts.isDone():
+                    point = iterVerts.position(MSpace.kWorld)
+                    self.vertices += [point.x, point.y, point.z]
+                    iterVerts.next()
+            except:
+                options['vertices'] = False
+
+        # export material data
+        # TODO: actually parse material data
+        materialIndices = MIntArray()
+        if options['materials']:
+            try:
+                shaders = MObjectArray()
+                mesh.getConnectedShaders(0, shaders, materialIndices)
+                while len(self.materials) < shaders.length():
+                    self.materials.append({}) # placeholder material definition
+            except:
+                self.materials = [{}]
+
+        # export uv data
+        if options['uvs']:
+            try:
+                uvLayers = []
+                mesh.getUVSetNames(uvLayers)
+                while len(uvLayers) > len(self.uvs):
+                    self.uvs.append([])
+                    self.offsets['uvs'].append(0)
+                for i, layer in enumerate(uvLayers):
+                    uList = MFloatArray()
+                    vList = MFloatArray()
+                    mesh.getUVs(uList, vList, layer)
+                    for j in xrange(uList.length()):
+                        self.uvs[i] += [uList[j], vList[j]]
+            except:
+                options['uvs'] = False
+
+        # export normal data
+        if options['normals']:
+            try:
+                normals = MFloatVectorArray()
+                mesh.getNormals(normals, MSpace.kWorld)
+                for i in xrange(normals.length()):
+                    point = normals[i]
+                    self.normals += [point.x, point.y, point.z]
+            except:
+                options['normals'] = False
+
+        # export color data
+        if options['colors']:
+            try:
+                colors = MColorArray()
+                mesh.getColors(colors)
+                for i in xrange(colors.length()):
+                    color = colors[i]
+                    # uncolored vertices are set to (-1, -1, -1).  Clamps colors to (0, 0, 0).
+                    self.colors += [max(color.r, 0), max(color.g, 0), max(color.b, 0)]
+            except:
+                options['colors'] = False
+
+        # export face data
+        if not options['vertices']:
+            return
+        bitmask = self._getTypeBitmask(options)
+        iterPolys = MItMeshPolygon(dagPath, component)
+        while not iterPolys.isDone():
+            self.faces.append(bitmask)
+            # export face vertices
+            verts = MIntArray()
+            iterPolys.getVertices(verts)
+            for i in xrange(verts.length()):
+                self.faces.append(verts[i] + self.offsets['vertices'])
+            # export face vertex materials
+            if options['materials']:
+                if materialIndices.length():
+                    self.faces.append(materialIndices[iterPolys.index()])
+            # export face vertex uvs
+            if options['uvs']:
+                util = MScriptUtil()
+                uvPtr = util.asIntPtr()
+                for i, layer in enumerate(uvLayers):
+                    for j in xrange(verts.length()):
+                        iterPolys.getUVIndex(j, uvPtr, layer)
+                        uvIndex = util.getInt(uvPtr)
+                        self.faces.append(uvIndex + self.offsets['uvs'][i])
+            # export face vertex normals
+            if options['normals']:
+                for i in xrange(3):
+                    normalIndex = iterPolys.normalIndex(i)
+                    self.faces.append(normalIndex + self.offsets['normals'])
+            # export face vertex colors
+            if options['colors']:
+                colors = MIntArray()
+                iterPolys.getColorIndices(colors)
+                for i in xrange(colors.length()):
+                    self.faces.append(colors[i] + self.offsets['colors'])
+            iterPolys.next()
+
+    def _getMeshes(self, nodes):
+        meshes = []
+        for node in nodes:
+            if mc.nodeType(node) == 'mesh':
+                meshes.append(node)
+            else:
+                for child in mc.listRelatives(node, s=1):
+                    if mc.nodeType(child) == 'mesh':
+                        meshes.append(child)
+        return meshes
+
+    def _exportMeshes(self):
+        # export all
+        if self.accessMode == MPxFileTranslator.kExportAccessMode:
+            mc.select(self._getMeshes(mc.ls(typ='mesh')))
+        # export selection
+        elif self.accessMode == MPxFileTranslator.kExportActiveAccessMode:
+            mc.select(self._getMeshes(mc.ls(sl=1)))
+        else:
+            raise ThreeJsError('Unsupported access mode: {0}'.format(self.accessMode))
+        dups = [mc.duplicate(mesh)[0] for mesh in mc.ls(sl=1)]
+        combined = mc.polyUnite(dups, mergeUVSets=1, ch=0) if len(dups) > 1 else dups[0]
+        mc.polyTriangulate(combined)
+        mc.select(combined)
+        sel = MSelectionList()
+        MGlobal.getActiveSelectionList(sel)
+        mDag = MDagPath()
+        mComp = MObject()
+        sel.getDagPath(0, mDag, mComp)
+        self._exportMesh(mDag, mComp)
+        mc.delete(combined)
+
+    def write(self, path, optionString, accessMode):
+        self.path = path
+        self._parseOptions(optionString)
+        self.accessMode = accessMode
+        self.root = dict(metadata=dict(formatVersion=3))
+        self.offsets = dict()
+        for key in self.componentKeys:
+            setattr(self, key, [])
+            self.offsets[key] = 0
+        self.offsets['uvs'] = []
+        self.uvs = []
+        
+        self._exportMeshes()
+
+        # add the component buffers to the root JSON object
+        for key in self.componentKeys:
+            buffer_ = getattr(self, key)
+            if buffer_:
+                self.root[key] = buffer_
+
+        # materials are required for parsing
+        if not self.root.has_key('materials'):
+            self.root['materials'] = [{}]
+
+        # write the file
+        with file(self.path, 'w') as f:
+            f.write(json.dumps(self.root, separators=(',',':'), cls=DecimalEncoder))
+
+
+class ThreeJsTranslator(MPxFileTranslator):
+    def __init__(self):
+        MPxFileTranslator.__init__(self)
+
+    def haveWriteMethod(self):
+        return True
+
+    def filter(self):
+        return '*.js'
+
+    def defaultExtension(self):
+        return 'js'
+
+    def writer(self, fileObject, optionString, accessMode):
+        path = fileObject.fullName()
+        writer = ThreeJsWriter()
+        writer.write(path, optionString, accessMode)
+
+
+def translatorCreator():
+    return asMPxPtr(ThreeJsTranslator())
+
+
+def initializePlugin(mobject):
+    mplugin = MFnPlugin(mobject)
+    try:
+        mplugin.registerFileTranslator(kPluginTranslatorTypeName, None, translatorCreator, kOptionScript, kDefaultOptionsString)
+    except:
+        sys.stderr.write('Failed to register translator: %s' % kPluginTranslatorTypeName)
+        raise
+        
+
+def uninitializePlugin(mobject):
+    mplugin = MFnPlugin(mobject)
+    try:
+        mplugin.deregisterFileTranslator(kPluginTranslatorTypeName)
+    except:
+        sys.stderr.write('Failed to deregister translator: %s' % kPluginTranslatorTypeName)
+        raise
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/scripts/ThreeJsExportScript.mel
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/scripts/ThreeJsExportScript.mel	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/scripts/ThreeJsExportScript.mel	(revision 28897)
@@ -0,0 +1,37 @@
+// ThreeJsExportScript.mel
+// Author: Chris Lewis
+// Email: clewis1@c.ringling.edu
+
+global proc int ThreeJsExportScript(string $parent, string $action, string $settings, string $callback)
+{
+    if ($action == "post")
+    {
+        setParent $parent;
+        columnLayout -adj true;
+        checkBox -v true -l "Vertices" vertsCb;
+        checkBox -v true -l "Faces" facesCb;
+        checkBox -v true -l "Normals" normalsCb;
+        checkBox -v true -l "UVs" uvsCb;
+        checkBox -v false -l "Material Indices" materialsCb;
+        checkBox -v false -l "Colors" colorsCb;
+    }
+    else if ($action == "query")
+    {
+        string $option = "\"";
+        if (`checkBox -q -v vertsCb`)
+            $option += "vertices ";
+        if (`checkBox -q -v facesCb`)
+            $option += "faces ";
+        if (`checkBox -q -v normalsCb`)
+            $option += "normals ";
+        if (`checkBox -q -v uvsCb`)
+            $option += "uvs ";
+        if (`checkBox -q -v materialsCb`)
+            $option += "materials ";
+        if (`checkBox -q -v colorsCb`)
+            $option += "colors ";
+        $option += "\"";
+        eval($callback + $option);
+    }
+    return 1;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/scripts/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/scripts/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/exporters/maya/scripts/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/README.md
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/README.md	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/README.md	(revision 28897)
@@ -0,0 +1,13 @@
+To build the npm modules:
+
+1. install nodejs.
+2. install npm (if it was not installed with nodejs)
+3. install npm module dependencies (see utils/build/package.json)
+4. determine the version of THREE that you want to publish (usually it is specified in src/Three.js as the REVISION)
+5. increment the fix number above what was previously published if you are re-publishing an existing Three.js version.
+6. add "-dev" to the version number if this is a development branch.
+7. run the follow to build both the three and three-math node modules
+ * node build.js 0.54.3-dev
+8. npm publish node_module/three
+9. npm publish node_module/three-math
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/build.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/build.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/build.js	(revision 28897)
@@ -0,0 +1,110 @@
+/**
+ * Builds node_modules three and three-math
+ *
+ * Expects a single command line argument that is the build version in the format 0.54.4-dev
+ *
+ * @author bhouston / http://exocortex.com
+ */
+
+var fs = require( "fs" );
+var cp = require('child_process');
+
+var commandLineArguments = process.argv.splice(2);
+
+var outputRootDir = "./node_modules";
+var inputDir = "../../build";
+var readmeFileName = "../../README.md";
+
+var headerFileName = "./header.js";
+var footerFileName = "./footer.js";
+
+if( commandLineArguments.length == 0 ) {
+	throw new Error( "build version must be specified as a command line argument (e.g. 0.54.3-dev)");
+}
+var buildVersion = commandLineArguments[0];
+
+
+var concateFiles = function ( inputFileNames, outputFileName ) {
+	var buffer = [];
+	for ( var i = 0; i < inputFileNames.length; i++ ) {
+		buffer.push( 
+			fs.readFileSync( inputFileNames[i], 'utf8' )
+			);
+	}
+
+	var combinedContents = buffer.join("");
+	fs.writeFileSync( outputFileName, combinedContents, 'utf8' );   
+}
+
+var createTemplatedFile = function ( templateFileName, replaceSet, outputFileName ) {
+	var templateContents = fs.readFileSync( templateFileName, 'utf8' );
+	for( var token in replaceSet ) {
+		templateContents = templateContents.replace( "%"+token+"%", replaceSet[token] );
+	}
+	fs.writeFileSync( outputFileName, templateContents, 'utf8' );
+}
+
+var copyFile = function( sourceFileName, destinationFileName ) {
+
+	var contents = fs.readFileSync( sourceFileName, 'utf8' );
+	fs.writeFileSync( destinationFileName, contents, 'utf8' );
+
+}
+
+var buildModule = function ( name, version ) {
+
+	if( ! fs.existsSync( outputRootDir ) ) {
+		fs.mkdirSync( outputRootDir );
+	}
+
+	var outputModuleDir = outputRootDir + "/" + name;
+	if( ! fs.existsSync( outputModuleDir ) ) {
+		fs.mkdirSync( outputModuleDir );
+	}
+	// make directory moduleDir
+
+	var inputRawFileName = inputDir + "/" + name + ".js";
+	var outputRawFileName = outputModuleDir + "/" + name + ".js";
+
+	concateFiles( [ headerFileName, inputRawFileName, footerFileName ], outputRawFileName );
+
+	var inputMinifiedFileName = inputDir + "/" + name + ".min.js";
+	var outputMinifiedFileName = outputModuleDir + "/" + name + ".min.js";
+
+	concateFiles( [ headerFileName, inputMinifiedFileName, footerFileName ], outputMinifiedFileName );
+
+	var templatePackageFileName = "./" + name + ".package.json";
+	var replaceSet = {
+		"VERSION": buildVersion
+	};
+	var outputPackageFileName = outputModuleDir + "/package.json";
+	createTemplatedFile( templatePackageFileName, replaceSet, outputPackageFileName );
+
+	var outputReadmeFileName = outputModuleDir + "/README.md";
+	copyFile( readmeFileName, outputReadmeFileName );
+}
+
+var cmdExe, args;
+if (process.platform === 'win32' || process.platform === 'win64') {
+	cmdExe = "cmd.exe";
+	args = [ "/c", "build_all.bat" ];
+} else {
+	cmdExe = './build_all.sh';
+	args = [];
+}
+var opts = { "cwd": "../build" };
+var buildAll = cp.spawn( cmdExe, args, opts );
+
+buildAll.stdout.on('data', function (data) {
+  console.log('stdout: ' + data);
+});
+
+buildAll.stderr.on('data', function (data) {
+  console.log('stderr: ' + data);
+});
+
+buildAll.on( 'exit', function ( exitCode ) {
+	console.log( "exitCode: " + exitCode );
+	buildModule( "three" );
+	buildModule( "three-math" );
+});
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/footer.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/footer.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/footer.js	(revision 28897)
@@ -0,0 +1,13 @@
+
+// Export the THREE object for **Node.js**, with
+// backwards-compatibility for the old `require()` API. If we're in
+// the browser, add `_` as a global object via a string identifier,
+// for Closure Compiler "advanced" mode.
+if (typeof exports !== 'undefined') {
+  if (typeof module !== 'undefined' && module.exports) {
+    exports = module.exports = THREE;
+  }
+  exports.THREE = THREE;
+} else {
+  this['THREE'] = THREE;
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/header.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/header.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/header.js	(revision 28897)
@@ -0,0 +1,1 @@
+var self = self || {};
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/test.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/test.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/test.js	(revision 28897)
@@ -0,0 +1,35 @@
+/**
+ * For testing whether the node modules for three and three-math work properly.
+ *
+ * To test the node modules:
+ *  1. First build them, but don't submit them to npm, see README.md for instructions
+ *  2. Run "node test.js"
+ *  3. You should see a list of all of the types exposed by the two THREE modules.
+ *
+ * @author bhouston / http://exocortex.com
+ */
+
+var threemath = function () {
+	var THREE = require( "three-math" );
+
+	var a = new THREE.Vector3( 1, 1, 1 );
+	console.log( a );
+
+	for( var i in THREE ) {
+		console.log( i );
+	}
+};
+
+var three = function () {
+	var THREE = require( "three" );
+
+	var a = new THREE.Vector3( 1, 1, 1 );
+	console.log( a );
+
+	for( var i in THREE ) {
+		console.log( i );
+	}
+};
+
+threemath();
+three();
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/three-math.package.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/three-math.package.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/three-math.package.json	(revision 28897)
@@ -0,0 +1,49 @@
+{
+
+    "name" : "three-math",
+
+    "version" : "%VERSION%",
+
+    "description" : "JavaScript 3D Math library",
+
+    "keywords" : [
+        "3D",
+        "WebGL",
+        "Three",
+        "ThreeJS",
+        "CSS",
+        "engine",
+        "rendering",
+        "geometry",
+        "math"
+    ],
+
+    "homepage" : "http://threejs.org/",
+
+    "bugs" : {
+        "url" : "https://github.com/mrdoob/three.js/issues"
+    },
+
+    "author" : "three.js contributors",
+
+    "main" : "./three-math.js",
+
+    "repository" : {
+        "type" : "git",
+        "url" : "git://github.com/mrdoob/three.js.git"
+    },
+
+    "license" : {
+        "type" : "The MIT License",
+        "url" : "https://raw.github.com/mrdoob/three.js/master/LICENSE"
+    },
+
+    "engines" : {
+        "node" : "*"
+    },
+
+    "help" : {
+        "web" : "http://stackoverflow.com/questions/tagged/three.js"
+    }
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/three.package.json
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/three.package.json	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/npm/three.package.json	(revision 28897)
@@ -0,0 +1,49 @@
+{
+
+    "name" : "three",
+
+    "version" : "%VERSION%",
+
+    "description" : "JavaScript 3D library",
+
+    "keywords" : [
+        "3D",
+        "WebGL",
+        "Three",
+        "ThreeJS",
+        "CSS",
+        "engine",
+        "rendering",
+        "geometry",
+        "math"
+    ],
+
+    "homepage" : "http://threejs.org/",
+
+    "bugs" : {
+        "url" : "https://github.com/mrdoob/three.js/issues"
+    },
+
+    "author" : "three.js contributors",
+
+    "main" : "./three.js",
+
+    "repository" : {
+        "type" : "git",
+        "url" : "git://github.com/mrdoob/three.js.git"
+    },
+
+    "license" : {
+        "type" : "The MIT License",
+        "url" : "https://raw.github.com/mrdoob/three.js/master/LICENSE"
+    },
+
+    "engines" : {
+        "node" : "*"
+    },
+
+    "help" : {
+        "web" : "http://stackoverflow.com/questions/tagged/three.js"
+    }
+
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/desktop.ini
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/desktop.ini	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/desktop.ini	(revision 28897)
@@ -0,0 +1,5 @@
+[.ShellClassInfo]
+InfoTip=This folder is shared online.
+IconFile=C:\Program Files (x86)\Google\Drive\googledrivesync.exe
+IconIndex=12
+    
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/nodejs_server.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/nodejs_server.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/nodejs_server.sh	(revision 28897)
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cd `dirname $0`/../../
+node utils/servers/simplehttpserver.js
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/perl_server.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/perl_server.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/perl_server.sh	(revision 28897)
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+cd `dirname $0`/../../
+perl -MIO::All -e 'io(":8080")->fork->accept->(sub { $_[0] < io(-x $1 
++? "./$1 |" : $1) if /^GET \/(.*) / })'
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/python_server.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/python_server.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/python_server.sh	(revision 28897)
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cd `dirname $0`/../../
+python -m SimpleHTTPServer
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/readme.txt
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/readme.txt	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/readme.txt	(revision 28897)
@@ -0,0 +1,1 @@
+For more information about running files locally, visit https://github.com/mrdoob/three.js/wiki/How-to-run-things-locally
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/ruby_server.sh
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/ruby_server.sh	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/ruby_server.sh	(revision 28897)
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+cd `dirname $0`/../../
+ruby -r webrick -e "s = WEBrick::HTTPServer.new(:Port => 8000, :DocumentRoot => Dir.pwd); trap('INT') { s.shutdown }; s.start"
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/simplehttpserver.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/simplehttpserver.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/mrdoob-three.js-4862f5f/utils/servers/simplehttpserver.js	(revision 28897)
@@ -0,0 +1,101 @@
+/**
+ * a barebones HTTP server in JS
+ * to serve three.js easily
+ *
+ * @author zz85 https://github.com/zz85
+ *
+ * Usage: node simplehttpserver.js <port number>
+ *
+ * do not use in production servers
+ * and try
+ *     npm install http-server -g
+ * instead.
+ */
+
+var port = 8000,
+	http = require('http'),
+	urlParser = require('url'),
+	fs = require('fs'),
+	path = require('path'),
+	currentDir = process.cwd();
+
+
+port = process.argv[2] ? parseInt(process.argv[2], 0) : port;
+
+function handleRequest(request, response) {
+
+	var urlObject = urlParser.parse(request.url, true);
+	var pathname = decodeURIComponent(urlObject.pathname);
+
+	console.log('[' + (new Date()).toUTCString() + '] ' + '"' + request.method + ' ' + pathname + '"');
+
+	var filePath = path.join(currentDir, pathname);
+
+	fs.stat(filePath, function(err, stats) {
+
+		if (err) {
+			response.writeHead(404, {});
+			response.end('File not found!');
+			return;
+		}
+
+		if (stats.isFile()) {
+
+			fs.readFile(filePath, function(err, data) {
+
+				if (err) {
+					response.writeHead(404, {});
+					response.end('Opps. Resource not found');
+					return;
+				}
+
+				response.writeHead(200, {});
+				response.write(data);
+				response.end();
+			});
+
+		} else if (stats.isDirectory()) {
+
+			fs.readdir(filePath, function(error, files) {
+
+				if (error) {
+					response.writeHead(500, {});
+					response.end();
+					return;
+				}
+
+				var l = pathname.length;
+				if (pathname.substring(l-1)!='/') pathname += '/';
+
+				response.writeHead(200, {'Content-Type': 'text/html'});
+				response.write('<!DOCTYPE html>\n<html><head><meta charset="UTF-8"><title>' + filePath + '</title></head><body>');
+				response.write('<h1>' + filePath + '</h1>');
+				response.write('<ul style="list-style:none;font-family:courier new;">');
+				files.unshift('.', '..');
+				files.forEach(function(item) {
+
+					var urlpath = pathname + item,
+						itemStats = fs.statSync(currentDir + urlpath);
+
+					if (itemStats.isDirectory()) {
+						urlpath += '/';
+						item += '/';
+					}
+
+					response.write('<li><a href="'+ urlpath + '">' + item + '</a></li>');
+				});
+
+				response.end('</ul></body></html>');
+			});
+		}
+	});
+}
+
+http.createServer(handleRequest).listen(port);
+
+require('dns').lookup(require('os').hostname(), function (err, addr, fam) {
+ 	console.log('Running at http://' + addr  + ((port === 80) ? '' : ':') + port + '/');
+})
+
+console.log('Three.js server has started...');
+console.log('Base directory at ' + currentDir);
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/test.html
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/test.html	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/test.html	(revision 28897)
@@ -0,0 +1,72 @@
+<!doctype HTML>
+<html>
+<head>
+	<title>Test</title>
+    <script src="Kinect-1.8.0.js"></script>
+    <script src="http://ajax.aspnetcdn.com/ajax/jquery/jquery-1.9.1.min.js"></script>
+    <script>
+    	$(document).ready(function () {
+    		Kinect.connect("http://localhost", 8181);
+
+    		var sensor = Kinect.sensor(Kinect.DEFAULT_SENSOR_NAME, function (sensorToConfig, isConnected) {
+                if (isConnected) {
+                    /*/ Determine what is the engagement state upon connection
+                    sensorToConfig.getConfig(function (data) {
+                        var engagedUserId = findEngagedUser(data[Kinect.INTERACTION_STREAM_NAME].userStates);
+
+                        updateUserState(true, engagedUserId, sensorToConfig);
+                    });*/
+            alert("connected!")
+                } else {
+                    "stink!"
+                    //updateUserState(false, engagedUser, sensorToConfig);
+                }
+            });
+
+            var configuration = {
+             
+                "interaction" : {
+                    "enabled": true,
+                },
+             
+                "userviewer" : {
+                    "enabled": true,
+                    "resolution": "640x480", //320x240, 160x120, 128x96, 80x60
+                    "userColors": { "engaged": 0xffffffff, "tracked": 0xffffffff },
+                    "defaultUserColor": 0xffffffff, //RGBA
+                },
+             /*
+                "backgroundRemoval" : {
+                    "enabled": true,
+                    "resolution": "640x480", //1280x960
+                },
+             
+                "skeleton" : {
+                    "enabled": true,
+                },*/
+             
+                "sensorStatus" : {
+                    "enabled": true,
+                }
+             
+            };
+
+
+
+            //sensor.postConfig( configuration , function(){alert("Posted configuration")});
+
+            var uiAdapter = KinectUI.createAdapter(sensor);
+
+            var userViewerCanvasElement = $('#userViewerCanvas'); //document.getElementById("userViewerCanvas");
+           uiAdapter.bindStreamToCanvas(Kinect.USERVIEWER_STREAM_NAME, userViewerCanvasElement);
+    	});
+    </script>
+</head>
+
+<body>
+	<div class="coolDiv">Teach me how to dougie</div>
+    <canvas id="userViewerCanvas" class="streamImageCanvas" width="640" height="480" />
+
+</body>
+
+</html>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/tutorial.js
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/tutorial.js	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Content/Web/tutorial.js	(revision 28897)
@@ -0,0 +1,54 @@
+window.setTimeout(shoulders,100)
+var startPano = panorama.pano;
+
+var msgBox;
+function shoulders(){
+	msgBox = document.getElementById("tutorialBox");
+	msgBox.innerHTML = "move your shoulders side to side to look around. <br> keep the cursor on screen to use hand controls."; 
+	window.setTimeout(grip,6000);
+}
+
+function grip(){
+	document.getElementById( "pano" ).addEventListener( "handPointerLeave", handLeft);
+	document.getElementById( "pano" ).addEventListener("handPointerEnter", handEntered);
+	console.log("added annoying hand listeners");
+	msgBox.innerHTML = "Close your hand into a grip and hold it to steady your view. <br> If the cursor doesn't close to a grip the kinect didn't notice so try again."; 
+	window.addEventListener("handPointerGrip",didGrip);
+}
+
+var oldMessage;
+function handLeft() {
+ 	oldMessage = msgBox.innerHTML;
+    msgBox.innerHTML = "please move your hand over the screen"; 
+}
+
+function handEntered() {
+ msgBox.innerHTML = oldMessage;
+}
+
+function didGrip(){
+	window.removeEventListener("handPointerGrip", didGrip);
+	msgBox.innerHTML = "Great! <br> You may now move your gripped hand up and down to select and jump to distant panoramas."; 
+	window.addEventListener("handPointerGripRelease",lostGrip);
+}
+
+function lostGrip(){
+	//document.getElementById("kinectgripthing").innerHTML = jumpTargetLink;
+	//alert("lostGrip " + jumpTargetLink);
+	if(panorama.pano != startPano) {
+		window.removeEventListener("handPointerGripRelease", lostGrip);
+		//window.removeEventListener("handPointerLeave", handLeft);
+		//window.removeEventListener("handPointerEnter", handEntered);
+		msgBox.innerHTML = "Good work. This concludes the tutorial. <br> Have fun playing with the street!"; 
+		window.setTimeout(end,5000);
+	}
+	else {
+		window.addEventListener("handPointerGrip",didGrip);
+		msgBox.innerHTML = "You don't seem to be gripping; grip again to resume the tutorial."; 
+	}
+}
+
+function end(){
+	//alert("end");
+	msgBox.style.display = 'none';
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/InverseBooleanConverter.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/InverseBooleanConverter.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/InverseBooleanConverter.cs	(revision 28897)
@@ -0,0 +1,78 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="InverseBooleanConverter.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.WebserverBasics
+{
+    using System;
+    using System.Windows.Data;
+
+    /// <summary>
+    /// Converts a boolean value to its inverse.
+    /// </summary>
+    [ValueConversion(typeof(bool), typeof(bool))]
+    public class InverseBooleanConverter : IValueConverter
+    {
+        #region IValueConverter Members
+
+        /// <summary>
+        /// Converts a value.
+        /// </summary>
+        /// <param name="value">
+        /// The value produced by the binding source.
+        /// </param>
+        /// <param name="targetType">
+        /// The type of the binding target property.
+        /// </param>
+        /// <param name="parameter">
+        /// The converter parameter to use.
+        /// </param>
+        /// <param name="culture">
+        /// The culture to use in the converter.
+        /// </param>
+        /// <returns>
+        /// A converted value. If the method returns null, the valid null value is used.
+        /// </returns>
+        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            if (targetType != typeof(bool))
+            {
+                throw new InvalidOperationException("The target must be a boolean");
+            }
+
+            return !(bool)value;
+        }
+
+        /// <summary>
+        /// Converts a value. 
+        /// </summary>
+        /// <param name="value">
+        /// The value that is produced by the binding target.
+        /// </param>
+        /// <param name="targetType">
+        /// The type to convert to.
+        /// </param>
+        /// <param name="parameter">
+        /// The converter parameter to use.
+        /// </param>
+        /// <param name="culture">
+        /// The culture to use in the converter.
+        /// </param>
+        /// <returns>
+        /// A converted value. If the method returns null, the valid null value is used.
+        /// </returns>
+        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            if (targetType != typeof(bool))
+            {
+                throw new InvalidOperationException("The target must be a boolean");
+            }
+
+            return !(bool)value;
+        }
+
+        #endregion
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/MainWindow.xaml
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/MainWindow.xaml	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/MainWindow.xaml	(revision 28897)
@@ -0,0 +1,53 @@
+﻿<Window x:Class="Microsoft.Samples.Kinect.WebserverBasics.MainWindow"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:tk="clr-namespace:Microsoft.Kinect.Toolkit;assembly=Microsoft.Kinect.Toolkit"
+        xmlns:local="clr-namespace:Microsoft.Samples.Kinect.WebserverBasics"
+        Title="Webserver Basics" SizeToContent="Height" Width="800" FontSize="16"
+        Loaded="WindowLoaded"  Closing="WindowClosing">
+    
+    <Window.Resources>
+        <local:InverseBooleanConverter x:Key="inverseBooleanConverter"/>
+    </Window.Resources>
+    
+    <Grid>
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="Auto"/>
+            <ColumnDefinition Width="*"/>
+        </Grid.ColumnDefinitions>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="*"/>
+        </Grid.RowDefinitions>
+        <tk:KinectSensorChooserUI Grid.Row="0" Grid.Column="0" KinectSensorChooser="{Binding KinectSensorChooser}" HorizontalAlignment="Left" IsListening="False" Margin="5"/>
+        <DockPanel Grid.Row="0" Grid.Column="1" Margin="5">
+            <Button DockPanel.Dock="Top" Height="50" Width="100" Content="Start"
+                    IsEnabled="{Binding Path=IsStarted, Converter={StaticResource inverseBooleanConverter}}" Click="StartButtonOnClick"/>
+            <Button DockPanel.Dock="Top" Height="50" Width="100" Content="Stop"
+                    IsEnabled="{Binding Path=IsStarted}" Click="StopButtonOnClick"/>
+        </DockPanel>
+        <StackPanel Grid.Row="0" Grid.Column="2" Margin="10 0">
+            <TextBlock x:Name="OpenBrowserText" Margin="0 5">
+                <Hyperlink FontSize="16" x:Name="UriLink" Click="UriLinkOnClick">Open sample page in default browser</Hyperlink>
+                <LineBreak/>
+                <Run FontSize="11">See documentation for list of supported browsers</Run>
+            </TextBlock>
+            <TextBlock x:Name="ServingFilesText" Margin="0 5" FontSize="11" TextWrapping="WrapWithOverflow">
+                <Run FontSize="14" FontWeight="Bold">Serving files from:</Run>
+                <Run x:Name="RootDirectoryTextRun"/>
+                <LineBreak/>
+                Edit FileRootDirectory property in configuration file (.exe.config) and restart server application to serve files from a different directory.
+            </TextBlock>
+            <TextBlock x:Name="NotServingFilesText" Margin="0 5" FontSize="14" FontWeight="Bold" Visibility="Collapsed">
+                Not Serving Files
+            </TextBlock>
+        </StackPanel>
+        <Expander Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" VerticalContentAlignment="Stretch" Margin="5 0 5 5"
+                  ExpandDirection="Down" IsExpanded="True"
+                  Header="Errors:" FontWeight="Black">
+            <TextBox IsReadOnly="True" VerticalScrollBarVisibility="Visible" HorizontalScrollBarVisibility="Auto" MinHeight="200"
+                     FontSize="10" Text="{Binding Path=ErrorText}"/>
+        </Expander>
+    </Grid>
+</Window>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/MainWindow.xaml.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/MainWindow.xaml.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/MainWindow.xaml.cs	(revision 28897)
@@ -0,0 +1,302 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="MainWindow.xaml.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.WebserverBasics
+{
+    using System;
+    using System.Diagnostics;
+    using System.Diagnostics.CodeAnalysis;
+    using System.IO;
+    using System.Windows;
+
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Samples.Kinect.Webserver;
+
+    /// <summary>
+    /// Interaction logic for MainWindow.
+    /// </summary>
+    public partial class MainWindow : Window
+    {
+        public static readonly DependencyProperty IsStartedProperty = DependencyProperty.Register(
+            "IsStarted", typeof(bool), typeof(MainWindow), new PropertyMetadata(false));
+
+        public static readonly DependencyProperty ErrorTextProperty = DependencyProperty.Register(
+            "ErrorText", typeof(string), typeof(MainWindow), new PropertyMetadata(string.Empty));
+
+        /// <summary>
+        /// Name (URI sub-path) to be used by clients to refer to the default kinect sensor.
+        /// </summary>
+        private const string DefaultSensorChooserName = "default";
+
+        /// <summary>
+        /// Name of main page for web sample.
+        /// </summary>
+        private const string SamplePageName = "SamplePage.html";
+
+        /// <summary>
+        /// Character used to separate origins in list of origins allowed to access this server.
+        /// </summary>
+        private const char AllowedOriginSeparator = ',';
+
+        /// <summary>
+        /// Component that manages finding a Kinect sensor
+        /// </summary>
+        private readonly KinectSensorChooser sensorChooser = new KinectSensorChooser();
+
+        /// <summary>
+        /// Webserver that handles requests from web clients.
+        /// </summary>
+        private readonly KinectWebserver webserver = new KinectWebserver();
+
+        /// <summary>
+        /// Error buffer listener used to update error text in UI.
+        /// </summary>
+        private readonly RecentEventBufferTraceListener errorBufferListener;
+
+        /// <summary>
+        /// Initializes a new instance of the MainWindow class.
+        /// </summary>
+        public MainWindow()
+        {
+            this.InitializeComponent();
+
+            if (!IsSupportedOsVersion(Environment.OSVersion))
+            {
+                MessageBox.Show(
+                    Properties.Resources.SystemVersionNotSupportedMessage,
+                    Properties.Resources.SystemVersionNotSupportedCaption,
+                    MessageBoxButton.OK);
+                Application.Current.Shutdown();
+            }
+
+            this.DataContext = this;
+
+            this.errorBufferListener = Trace.Listeners["errorBufferListener"] as RecentEventBufferTraceListener;
+            if (this.errorBufferListener != null)
+            {
+                this.errorBufferListener.RecentEventBufferChanged += this.ErrorBufferListenerOnChanged;
+            }
+
+            Uri originUri;
+            try
+            {
+                originUri = new Uri(Properties.Settings.Default.OriginUri.Trim());
+            }
+            catch (UriFormatException)
+            {
+                Trace.TraceError("Invalid format for listening origin: \"{0}\"", Properties.Settings.Default.OriginUri);
+
+                // If we can't listen on origin, we can't serve any data whatsover, so there is no sense in continuing
+                // setup
+                return;
+            }
+
+            this.webserver.SensorChooserMap.Add(DefaultSensorChooserName, this.sensorChooser);
+            if (!string.IsNullOrWhiteSpace(Properties.Settings.Default.FileRootDirectory))
+            {
+                this.webserver.FileServerRootDirectory = Properties.Settings.Default.FileRootDirectory;
+
+                try
+                {
+                    this.RootDirectoryTextRun.Text = Path.GetFullPath(Properties.Settings.Default.FileRootDirectory);
+                }
+                catch (ArgumentException e)
+                {
+                    this.ShowNotServingFilesUi();
+                    Trace.TraceError("Exception encountered while parsing root directory for serving files:\n{0}", e);
+                }
+            }
+            else
+            {
+                this.ShowNotServingFilesUi();
+            }
+
+            this.webserver.OriginUri = originUri;
+            if (!string.IsNullOrWhiteSpace(Properties.Settings.Default.AccessControlAllowedOrigins))
+            {
+                foreach (var origin in Properties.Settings.Default.AccessControlAllowedOrigins.Split(AllowedOriginSeparator))
+                {
+                    try
+                    {
+                        originUri = new Uri(origin.Trim());
+                    }
+                    catch (UriFormatException)
+                    {
+                        Trace.TraceError("Invalid format for access control allowed origin: {0}", origin);
+                        continue;
+                    }
+
+                    this.webserver.AccessControlAllowedOrigins.Add(originUri);
+                }
+            }
+
+            //// TODO: Optionally add factories here for custom handlers:
+            ////       this.webserver.SensorStreamHandlerFactories.Add(new MyCustomSensorStreamHandlerFactory());
+            //// Your custom factory would implement ISensorStreamHandlerFactory, in which the
+            //// CreateHandler method would return a class derived from SensorStreamHandlerBase
+            //// which overrides one or more of its virtual methods.
+
+            this.webserver.Started += (server, args) => this.IsStarted = true;
+            this.webserver.Stopped += (server, args) => this.IsStarted = false;
+        }
+
+        /// <summary>
+        /// Gets the KinectSensorChooser component
+        /// </summary>
+        public KinectSensorChooser KinectSensorChooser
+        {
+            get { return this.sensorChooser; }
+        }
+
+        /// <summary>
+        /// true if server has started listening, false otherwise. 
+        /// </summary>
+        public bool IsStarted
+        {
+            get
+            {
+                return (bool)this.GetValue(IsStartedProperty);
+            }
+
+            set
+            {
+                this.SetValue(IsStartedProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Error text to display in UI.
+        /// </summary>
+        public string ErrorText
+        {
+            get
+            {
+                return (string)this.GetValue(ErrorTextProperty);
+            }
+
+            set
+            {
+                this.SetValue(ErrorTextProperty, value);
+            }
+        }
+
+        /// <summary>
+        /// Determine if running this application is supported in the specified operating
+        /// system version.
+        /// </summary>
+        /// <param name="systemInfo">
+        /// Operating system version information.
+        /// </param>
+        /// <returns>
+        /// true if running this application is supported in the specified operating system.
+        /// false otherwise.
+        /// </returns>
+        private static bool IsSupportedOsVersion(OperatingSystem systemInfo)
+        {
+            const int MinimumMajorVersion = 6;
+            const int MinimumMinorVersion = 2;
+
+            return (systemInfo.Version.Major > MinimumMajorVersion)
+                   || ((systemInfo.Version.Major == MinimumMajorVersion) && (systemInfo.Version.Minor >= MinimumMinorVersion));
+        }
+
+        /// <summary>
+        /// Start web server, reporting an error to trace listeners if exception is encountered.
+        /// </summary>
+        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Server application should never crash. Errors are reported in web server UI instead.")]
+        private void StartWebserver()
+        {
+            try
+            {
+                this.webserver.Start();
+            }
+            catch (Exception e)
+            {
+                Trace.TraceError("Unable to start server due to exception:\n{0}", e);
+            }
+        }
+
+        /// <summary>
+        /// Stop web server, reporting an error to trace listeners if exception is encountered.
+        /// </summary>
+        [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Server application should never crash. Errors are reported in web server UI instead.")]
+        private void StopWebserver()
+        {
+            try
+            {
+                this.webserver.Stop();
+            }
+            catch (Exception e)
+            {
+                Trace.TraceError("Unable to stop server due to exception:\n{0}", e);
+            }
+        }
+
+        /// <summary>
+        /// Show UI appropriate for when we're not serving static files.
+        /// </summary>
+        private void ShowNotServingFilesUi()
+        {
+            this.ServingFilesText.Visibility = Visibility.Collapsed;
+            this.OpenBrowserText.Visibility = Visibility.Collapsed;
+            this.NotServingFilesText.Visibility = Visibility.Visible;
+        }
+
+        private void WindowLoaded(object sender, RoutedEventArgs e)
+        {
+            this.sensorChooser.Start();
+
+            this.StartWebserver();
+        }
+
+        private void WindowClosing(object sender, System.ComponentModel.CancelEventArgs e)
+        {
+            this.StopWebserver();
+        }
+
+        private void StartButtonOnClick(object sender, RoutedEventArgs e)
+        {
+            this.StartWebserver();
+        }
+
+        private void StopButtonOnClick(object sender, RoutedEventArgs e)
+        {
+            this.StopWebserver();
+        }
+
+        private void ErrorBufferListenerOnChanged(object sender, EventArgs e)
+        {
+            this.Dispatcher.BeginInvoke((Action)(() => this.ErrorText = this.errorBufferListener.RecentEventBuffer));
+        }
+
+        /// <summary>
+        /// Event handler for UriLink's Click event.
+        /// </summary>
+        /// <param name="sender">
+        /// Object triggering the event.
+        /// </param>
+        /// <param name="e">
+        /// Event arguments.
+        /// </param>
+        private void UriLinkOnClick(object sender, RoutedEventArgs e)
+        {
+            // Shell execute the URI of sample page to open in default browser
+            var samplePageUri = this.webserver.OriginUri.ConcatenateSegments(this.webserver.FileServerBasePath, SamplePageName);
+
+            if (samplePageUri == null)
+            {
+                return;
+            }
+
+            var startInfo = new ProcessStartInfo(samplePageUri.ToString()) { UseShellExecute = true };
+            using (var process = new Process())
+            {
+                process.StartInfo = startInfo;
+                process.Start();
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/FileRequestHandler.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/FileRequestHandler.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/FileRequestHandler.cs	(revision 28897)
@@ -0,0 +1,229 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="FileRequestHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Diagnostics;
+    using System.IO;
+    using System.Net;
+    using System.Threading.Tasks;
+    using System.Web;
+
+    /// <summary>
+    /// Implementation of IHttpRequestHandler used to serve static file content.
+    /// </summary>
+    public class FileRequestHandler : IHttpRequestHandler
+    {
+        /// <summary>
+        /// Root directory in server's file system from which we're serving files.
+        /// </summary>
+        private readonly DirectoryInfo rootDirectory;
+
+        /// <summary>
+        /// Origin used as a helper for URI parsing and canonicalization.
+        /// </summary>
+        private readonly Uri parseHelperOrigin = new Uri("http://a");
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FileRequestHandler"/> class.
+        /// </summary>
+        /// <param name="rootDirectory">
+        /// Root directory in server's file system from which files should be served.
+        /// </param>
+        internal FileRequestHandler(DirectoryInfo rootDirectory)
+        {
+            if (rootDirectory == null)
+            {
+                throw new ArgumentNullException("rootDirectory");
+            }
+            
+            // Re-create directory name to ensure equivalence between directory names
+            // that end in "\" character and directory names that are identical except
+            // that they don't end in "\" character.
+            var normalizedDirectoryName = rootDirectory.Parent != null
+                                              ? Path.Combine(rootDirectory.Parent.FullName, rootDirectory.Name)
+                                              : rootDirectory.Name;
+            this.rootDirectory = new DirectoryInfo(normalizedDirectoryName);
+        }
+
+        /// <summary>
+        /// Prepares handler to start receiving HTTP requests.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public Task InitializeAsync()
+        {
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the URI prefix associated with this request
+        /// handler in the HttpListener.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public async Task HandleRequestAsync(HttpListenerContext requestContext, string subpath)
+        {
+            if (requestContext == null)
+            {
+                throw new ArgumentNullException("requestContext");
+            }
+
+            if (subpath == null)
+            {
+                throw new ArgumentNullException("subpath");
+            }
+
+            try
+            {
+                var statusCode = await this.ServeFileAsync(requestContext.Response, subpath);
+                CloseResponse(requestContext, statusCode);
+            }
+            catch (HttpListenerException e)
+            {
+                Trace.TraceWarning(
+                    "Problem encountered while serving file at subpath {0}. Client might have aborted request. Cause: \"{1}\"", subpath, e.Message);
+            }
+        }
+
+        /// <summary>
+        /// Cancel all pending operations.
+        /// </summary>
+        public void Cancel()
+        {
+        }
+
+        /// <summary>
+        /// Lets handler know that no more HTTP requests will be received, so that it can
+        /// clean up resources associated with request handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public Task UninitializeAsync()
+        {
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Close response stream and associate a status code with response.
+        /// </summary>
+        /// <param name="context">
+        /// Context whose response we should close.
+        /// </param>
+        /// <param name="statusCode">
+        /// Status code.
+        /// </param>
+        internal static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode)
+        {
+            context.Response.StatusCode = (int)statusCode;
+            context.Response.Close();
+        }
+
+        /// <summary>
+        /// Determine if the specified directory is an ancestor of the specified file.
+        /// </summary>
+        /// <param name="ancestorDirectory">
+        /// Information for potential ancestor directory.
+        /// </param>
+        /// <param name="file">
+        /// File for which to determine directory ancestry.
+        /// </param>
+        /// <returns>
+        /// True if <paramref name="ancestorDirectory"/> is an ancestor of <paramref name="file"/>.
+        /// </returns>
+        internal static bool IsAncestorDirectory(DirectoryInfo ancestorDirectory, FileInfo file)
+        {
+            var parentDirectory = file.Directory;
+
+            while (parentDirectory != null)
+            {
+                if (string.Compare(parentDirectory.FullName, ancestorDirectory.FullName, StringComparison.OrdinalIgnoreCase) == 0)
+                {
+                    return true;
+                }
+
+                parentDirectory = parentDirectory.Parent;
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Serve the file corresponding to the specified URI path.
+        /// </summary>
+        /// <param name="response">
+        /// HTTP response where file will be streamed, on success.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the URI prefix associated with this request
+        /// handler in the HttpListener.
+        /// </param>
+        /// <returns>
+        /// Await-able task holding an HTTP status code that should be part of response.
+        /// </returns>
+        private async Task<HttpStatusCode> ServeFileAsync(HttpListenerResponse response, string subpath)
+        {
+            Uri uri;
+
+            try
+            {
+                uri = new Uri(this.parseHelperOrigin, subpath);
+            }
+            catch (UriFormatException)
+            {
+                return HttpStatusCode.Forbidden;
+            }
+
+            var filePath = Path.Combine(this.rootDirectory.FullName, uri.AbsolutePath.Substring(1));
+            var fileInfo = new FileInfo(filePath);
+
+            if (!IsAncestorDirectory(this.rootDirectory, fileInfo))
+            {
+                return HttpStatusCode.Forbidden;
+            }
+
+            if (!fileInfo.Exists)
+            {
+                return HttpStatusCode.NotFound;
+            }
+
+            response.ContentType = MimeMapping.GetMimeMapping(fileInfo.FullName);
+
+            using (var fileStream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read))
+            {
+                await fileStream.CopyToAsync(response.OutputStream);
+            }
+
+            return HttpStatusCode.OK;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/FileRequestHandlerFactory.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/FileRequestHandlerFactory.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/FileRequestHandlerFactory.cs	(revision 28897)
@@ -0,0 +1,46 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="FileRequestHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Globalization;
+    using System.IO;
+
+    /// <summary>
+    /// Implementation of IHttpRequestHandlerFactory used to create instances of
+    /// <see cref="FileRequestHandler"/> objects.
+    /// </summary>
+    public class FileRequestHandlerFactory : IHttpRequestHandlerFactory
+    {
+        /// <summary>
+        /// Root directory in server's file system from which we're serving files.
+        /// </summary>
+        private readonly DirectoryInfo rootDirectory;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FileRequestHandlerFactory"/> class.
+        /// </summary>
+        /// <param name="rootDirectoryName">
+        /// Root directory name in server's file system from which files should be served.
+        /// The directory must exist at the time of the call.
+        /// </param>
+        internal FileRequestHandlerFactory(string rootDirectoryName)
+        {
+            if (!Directory.Exists(rootDirectoryName))
+            {
+                throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, @"The specified directory '{0}' does not exist", rootDirectoryName), "rootDirectoryName");
+            }
+
+            this.rootDirectory = new DirectoryInfo(rootDirectoryName);
+        }
+
+        public IHttpRequestHandler CreateHandler()
+        {
+            return new FileRequestHandler(this.rootDirectory);
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandler.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandler.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandler.cs	(revision 28897)
@@ -0,0 +1,84 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="IHttpRequestHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System.Net;
+    using System.Threading.Tasks;
+
+    /// <summary>
+    /// Interface called upon to handle HTTP requests.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Objects that implement this interface are not expected to be thread-safe but are expected
+    /// to support async operations, so instances should be created and instance methods should
+    /// always be called from a single thread that is associated with a SynchronizationContext
+    /// that provides single-threaded message dispatch
+    /// (e.g.: System.Windows.Threading.DispatcherSynchronizationContext).
+    /// </para>
+    /// <para>
+    /// This means that IHttpRequestHandlers still have to be aware of potential reentrancy
+    /// resulting from asynchronous operations, but they don't have to protect data access with
+    /// locks.
+    /// </para>
+    /// </remarks>
+    public interface IHttpRequestHandler
+    {
+        /// <summary>
+        /// Prepares handler to start receiving HTTP requests.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task InitializeAsync();
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the URI prefix associated with this request
+        /// handler in the HttpListener.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task HandleRequestAsync(HttpListenerContext requestContext, string subpath);
+
+        /// <summary>
+        /// Cancel all pending operations.
+        /// </summary>
+        void Cancel();
+
+        /// <summary>
+        /// Lets handler know that no more HTTP requests will be received, so that it can
+        /// clean up resources associated with request handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task UninitializeAsync();
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandlerFactory.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandlerFactory.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/IHttpRequestHandlerFactory.cs	(revision 28897)
@@ -0,0 +1,22 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="IHttpRequestHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    /// <summary>
+    /// Factory of <see cref="IHttpRequestHandler"/> objects.
+    /// </summary>
+    public interface IHttpRequestHandlerFactory
+    {
+        /// <summary>
+        /// Creates a request handler object.
+        /// </summary>
+        /// <returns>
+        /// A new <see cref="IHttpRequestHandler"/> instance.
+        /// </returns>
+        IHttpRequestHandler CreateHandler();
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/JsonSerializationExtensions.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/JsonSerializationExtensions.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/JsonSerializationExtensions.cs	(revision 28897)
@@ -0,0 +1,582 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="JsonSerializationExtensions.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Collections;
+    using System.Collections.Generic;
+    using System.Globalization;
+    using System.IO;
+    using System.Reflection;
+    using System.Runtime.Serialization;
+    using System.Text;
+    using System.Threading.Tasks;
+    using System.Web.Script.Serialization;
+
+    using Microsoft.Samples.Kinect.Webserver.Properties;
+
+    /// <summary>
+    /// Static class that defines extensions used to serialize/deserialize objects to/from
+    /// JSON strings.
+    /// </summary>
+    public static class JsonSerializationExtensions
+    {
+        /// <summary>
+        /// Serialization default buffer size.
+        /// </summary>
+        private const int BufferSize = 512;
+
+        /// <summary>
+        /// Serialize specified object to a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to serialize.
+        /// </typeparam>
+        /// <param name="obj">
+        /// Object to serialize. 
+        /// </param>
+        /// <param name="outputStream">
+        /// Stream where UTF8-encoded JSON representing object will be output.
+        /// </param>
+        public static void ToJson<T>(this T obj, Stream outputStream)
+        {
+            using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
+            {
+                writer.Write(obj.ToJson());
+            }
+        }
+
+        /// <summary>
+        /// Serialize specified object to a JSON string.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to serialize.
+        /// </typeparam>
+        /// <param name="obj">
+        /// Object to serialize. 
+        /// </param>
+        /// <returns>
+        /// JSON string representing serialized object.
+        /// </returns>
+        public static string ToJson<T>(this T obj)
+        {
+            return (new JavaScriptSerializer()).Serialize(obj);
+        }
+
+        /// <summary>
+        /// Serialize specified dictionary to a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <param name="dictionary">
+        /// Dictionary to serialize. 
+        /// </param>
+        /// <param name="outputStream">
+        /// Stream where UTF8-encoded JSON representing dictionary will be output.
+        /// </param>
+        /// <remarks>
+        /// <para>
+        /// Only IDictionary&lt;string,object&gt;  objects and default types will be
+        /// recognized by the serializer.
+        /// </para>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
+        /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
+        /// way to serialize dictionary objects as JSON.
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        public static void DictionaryToJson(this IDictionary<string, object> dictionary, Stream outputStream)
+        {
+            // Leave output stream open after we're done writing
+            using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
+            {
+                writer.Write(dictionary.DictionaryToJson());
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously serialize specified dictionary to a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <param name="dictionary">
+        /// Dictionary to serialize. 
+        /// </param>
+        /// <param name="outputStream">
+        /// Stream where UTF8-encoded JSON representing dictionary will be output.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Only IDictionary&lt;string,object&gt; objects and default types will be
+        /// recognized by the serializer.
+        /// </para>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
+        /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
+        /// way to serialize dictionary objects as JSON.
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        public static async Task DictionaryToJsonAsync(this IDictionary<string, object> dictionary, Stream outputStream)
+        {
+            // Leave output stream open after we're done writing
+            using (var writer = new StreamWriter(outputStream, new UTF8Encoding(false), BufferSize, true))
+            {
+                await writer.WriteAsync(dictionary.DictionaryToJson());
+            }
+        }
+
+        /// <summary>
+        /// Serialize specified dictionary to a JSON string.
+        /// </summary>
+        /// <param name="dictionary">
+        /// Dictionary to serialize. 
+        /// </param>
+        /// <returns>
+        /// JSON string representing serialized object.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Only IDictionary&lt;string,object&gt; objects and default types will be
+        /// recognized by the serializer.
+        /// </para>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// will be serialized as {"foo":23,"bar":"tar"}, rather than as
+        /// [{"Key":"foo","Value":23},{"Key":"bar","Value":"tar"}], which is the default
+        /// way to serialize dictionary objects as JSON.
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        public static string DictionaryToJson(this IDictionary<string, object> dictionary)
+        {
+            return (new JavaScriptSerializer()).Serialize(dictionary);
+        }
+        
+        /// <summary>
+        /// Deserialize specified object from UTF8-encoded JSON read from a stream.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to deserialize.
+        /// </typeparam>
+        /// <param name="inputStream">
+        /// Stream from which to read UTF8-encoded JSON representing serialized object.
+        /// </param>
+        /// <returns>
+        /// Deserialized object corresponding to input JSON.
+        /// </returns>
+        public static T FromJson<T>(this Stream inputStream)
+        {
+            using (var reader = new StreamReader(inputStream, Encoding.UTF8))
+            {
+                return reader.ReadToEnd().FromJson<T>();
+            }
+        }
+
+        /// <summary>
+        /// Deserialize specified object from a JSON string.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to deserialize.
+        /// </typeparam>
+        /// <param name="input">
+        /// JSON string representing serialized object.
+        /// </param>
+        /// <returns>
+        /// Deserialized object corresponding to JSON string.
+        /// </returns>
+        /// <remarks>
+        /// Errors encountered during serialization might throw SerializationException.
+        /// </remarks>
+        public static T FromJson<T>(this string input)
+        {
+            try
+            {
+                return (new JavaScriptSerializer()).Deserialize<T>(input);
+
+                // Convert exceptions to Serialization exception to provide a single exception to
+                // catch for callers.
+            }
+            catch (ArgumentException e)
+            {
+                throw new SerializationException(@"Exception encountered deserializing JSON string", e);
+            }
+            catch (InvalidOperationException e)
+            {
+                throw new SerializationException(@"Exception encountered deserializing JSON string", e);
+            }
+        }
+
+        /// <summary>
+        /// Deserialize specified dictionary from a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <param name="inputStream">
+        /// Stream containing UTF8-encoded JSON representation of dictionary.
+        /// </param>
+        /// <returns>
+        /// Deserialized dictionary.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a JSON object such as
+        /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        public static Dictionary<string, object> DictionaryFromJson(this Stream inputStream)
+        {
+            using (var reader = new StreamReader(inputStream, Encoding.UTF8))
+            {
+                return reader.ReadToEnd().DictionaryFromJson();
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously deserialize specified dictionary from a stream as UTF8-encoded JSON.
+        /// </summary>
+        /// <param name="inputStream">
+        /// Stream containing UTF8-encoded JSON representation of dictionary.
+        /// </param>
+        /// <returns>
+        /// Await-able task Deserialized dictionary.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a JSON object such as
+        /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// </remarks>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Clients won't have to create nested structure themselves. They will just await on task to get dictionary object.")]
+        public static async Task<Dictionary<string, object>> DictionaryFromJsonAsync(this Stream inputStream)
+        {
+            using (var reader = new StreamReader(inputStream, Encoding.UTF8))
+            {
+                var input = await reader.ReadToEndAsync();
+                return input.DictionaryFromJson();
+            }
+        }
+
+        /// <summary>
+        /// Deserialize specified dictionary from a JSON string.
+        /// </summary>
+        /// <param name="input">
+        /// JSON string containing JSON representation of dictionary.
+        /// </param>
+        /// <returns>
+        /// Deserialized dictionary.
+        /// </returns>
+        /// <remarks>
+        /// <para>
+        /// Dictionaries mapping string keys to object values will be treated as direct
+        /// representations of JSON objects, so that a JSON object such as
+        /// {"foo":23,"bar":"tar"} will be deserialized as a dictionary that contains:
+        /// <list type="bullet">
+        /// <item>
+        /// <description>a key "foo" that maps to a numeric value of 23</description>
+        /// </item>
+        /// <item>
+        /// <description>a key "bar" that maps to a string value of "tar"</description>
+        /// </item>
+        /// </list>
+        /// </para>
+        /// <para>
+        /// This method does not look for circular references and therefore does not
+        /// support them.
+        /// </para>
+        /// <para>
+        /// Errors encountered during serialization might throw SerializationException.
+        /// </para>
+        /// </remarks>
+        public static Dictionary<string, object> DictionaryFromJson(this string input)
+        {
+            try
+            {
+                return (new JavaScriptSerializer()).Deserialize<Dictionary<string, object>>(input);
+
+                // Convert exceptions to Serialization exception to provide a single exception to
+                // catch for callers.
+            }
+            catch (ArgumentException e)
+            {
+                throw new SerializationException(@"Exception encountered deserializing JSON string", e);
+            }
+            catch (InvalidOperationException e)
+            {
+                throw new SerializationException(@"Exception encountered deserializing JSON string", e);
+            }
+        }
+
+        /// <summary>
+        /// Extract serializable JSON data from specified value, ensuring that property names
+        /// match JSON naming conventions (camel case).
+        /// </summary>
+        /// <param name="value">The object to be converted.</param>
+        /// <returns>The converted object.</returns>
+        internal static object ExtractSerializableJsonData(object value)
+        {
+            if (value == null || IsSerializationPrimitive(value))
+            {
+                return value;
+            }
+
+            if (IsDictionary(value))
+            {
+                // The key type for the given dictionary must be string type.
+                if (!IsSerializableGenericDictionary(value) && !IsSerializableDictionary(value))
+                {
+                    throw new NotSupportedException(Resources.UnsupportedKeyType);
+                }
+
+                var result = new Dictionary<string, object>();
+                var dict = (IDictionary)value;
+
+                foreach (var key in dict.Keys)
+                {
+                    result.Add((string)key, ExtractSerializableJsonData(dict[key]));
+                }
+
+                return result;
+            }
+
+            if (IsEnumerable(value))
+            {
+                // For the object with IEnumerable interface, serialize each items in it.
+                var result = new List<object>();
+
+                foreach (var v in (IEnumerable)value)
+                {
+                    result.Add(ExtractSerializableJsonData(v));
+                }
+
+                return result;
+            }
+            else
+            {
+                var result = new Dictionary<string, object>();
+
+                foreach (var p in value.GetType().GetProperties())
+                {
+                    if (IsSerializableProperty(p))
+                    {
+                        result.Add(ToCamelCase(p.Name), ExtractSerializableJsonData(p.GetValue(value)));
+                    }
+                }
+
+                return result;
+            }
+        }
+
+        /// <summary>
+        /// Filters the classes represented in an array of Type objects.
+        /// </summary>
+        /// <param name="type">The Type object to which the filter is applied.</param>
+        /// <param name="criteria">An arbitrary object used to filter the list.</param>
+        /// <returns>True to include the Type in the filtered list; otherwise false.</returns>
+        private static bool InterfaceTypeFilter(Type type, object criteria)
+        {
+            var targetType = criteria as Type;
+
+            if (type.IsGenericType)
+            {
+                return type.GetGenericTypeDefinition() == targetType;
+            }
+
+            return targetType != null && type == targetType;
+        }
+
+        /// <summary>
+        /// Find the target interface from a given object.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <param name="targetInterface">The target interface to be found.</param>
+        /// <returns>The interface which has been found.</returns>
+        private static Type FindTargetInterface(object value, Type targetInterface)
+        {
+            var types = value.GetType().FindInterfaces(InterfaceTypeFilter, targetInterface);
+            return (types.Length > 0) ? types[0] : null;
+        }
+
+        /// <summary>
+        /// Determine if the given object has a serializable generic dictionary interface.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <returns>Returns true if it has the interface; otherwise false.</returns>
+        private static bool IsSerializableGenericDictionary(object value)
+        {
+            var genericDictType = typeof(IDictionary<object, object>).GetGenericTypeDefinition();
+            var type = FindTargetInterface(value, genericDictType);
+
+            if (type != null)
+            {
+                // Only the dictionaries with string type keys are serializable.
+                var argumentTypes = type.GetGenericArguments();
+                return argumentTypes.Length > 0 && argumentTypes[0] == typeof(string);
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Determine if the given object has a serializable dictionary interface.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <returns>Returns true if it has the interface; otherwise false.</returns>
+        private static bool IsSerializableDictionary(object value)
+        {
+            var type = FindTargetInterface(value, typeof(IDictionary));
+
+            if (type == null)
+            {
+                return false;
+            }
+
+            foreach (var key in ((IDictionary)value).Keys)
+            {
+                if (!(key is string))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Determine if the given object has a dictionary or generic dictionary interface.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <returns>Returns true if it has the interface; otherwise false.</returns>
+        private static bool IsDictionary(object value)
+        {
+            var dictType = FindTargetInterface(value, typeof(IDictionary));
+            if (dictType == null)
+            {
+                dictType = FindTargetInterface(value, typeof(IDictionary<object, object>).GetGenericTypeDefinition());
+            }
+
+            return dictType != null;
+        }
+
+        /// <summary>
+        /// Determine if the given object has an IEnumerable interface.
+        /// </summary>
+        /// <param name="value">The given object.</param>
+        /// <returns>Returns true if it has the interface; otherwise false.</returns>
+        private static bool IsEnumerable(object value)
+        {
+            var type = FindTargetInterface(value, typeof(IEnumerable));
+            return type != null;
+        }
+
+        /// <summary>
+        /// Convert the given string to camelCase.
+        /// </summary>
+        /// <param name="text">The given string.</param>
+        /// <returns>The returned string.</returns>
+        private static string ToCamelCase(string text)
+        {
+            if (string.IsNullOrEmpty(text))
+            {
+                return text;
+            }
+
+            return string.Format(CultureInfo.CurrentCulture, "{0}{1}", char.ToLower(text[0], CultureInfo.CurrentCulture), text.Substring(1));
+        }
+
+        /// <summary>
+        /// Determine if the given object is a primitive to be serialized.
+        /// </summary>
+        /// <param name="o">The input object.</param>
+        /// <returns>Return true if it is a serialization primitive, otherwise return false.</returns>
+        private static bool IsSerializationPrimitive(object o)
+        {
+            Type t = o.GetType();
+            return t.IsPrimitive || t.IsEnum || t == typeof(string) || t == typeof(DateTime);
+        }
+
+        /// <summary>
+        /// Determine if the given property is serializable.
+        /// </summary>
+        /// <param name="propertyInfo">The input property.</param>
+        /// <returns>Return true if it is serializable, otherwise return false.</returns>
+        private static bool IsSerializableProperty(PropertyInfo propertyInfo)
+        {
+            foreach (var accessor in propertyInfo.GetAccessors())
+            {
+                if (accessor.IsPublic && !accessor.IsStatic)
+                {
+                    return propertyInfo.GetGetMethod(false) != null && propertyInfo.CanWrite;
+                }
+            }
+
+            return false;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/KinectWebserver.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/KinectWebserver.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/KinectWebserver.cs	(revision 28897)
@@ -0,0 +1,453 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="KinectWebserver.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.ObjectModel;
+    using System.Globalization;
+    using System.Linq;
+
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Samples.Kinect.Webserver.Sensor;
+
+    /// <summary>
+    /// Server used to expose Kinect state and events to web clients.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Instances of this class are not thread-safe, and are expected to be called from
+    /// a single thread, e.g.: a UI thread.
+    /// </para>
+    /// <para>
+    /// By default listens for connections at origin http://localhost:8181 and does not
+    /// serve files until owner configures FileServerRootDirectory property.
+    /// </para>
+    /// </remarks>
+    public sealed class KinectWebserver
+    {
+        /// <summary>
+        /// Base URI path (within expected origin) for Kinect endpoints.
+        /// </summary>
+        private const string KinectEndpointBasePath = "/Kinect";
+
+        /// <summary>
+        /// Base URI path (within expected origin) for file server endpoints.
+        /// </summary>
+        private const string DefaultFileEndpointBasePath = "/files";
+
+        /// <summary>
+        /// Default HTTP URI origin to listen on.
+        /// </summary>
+        private static readonly Uri DefaultUriOrigin = new Uri("http://localhost:8181");
+        
+        /// <summary>
+        /// Mapping between URI base path names to be used by clients to refer to data from a
+        /// specific Kinect sensor and their corresponding KinectSensorChooser objects.
+        /// </summary>
+        private readonly Dictionary<string, KinectSensorChooser> sensorChooserMap = new Dictionary<string, KinectSensorChooser>();
+
+        /// <summary>
+        /// Origin Uris that are allowed to access data owned by this server.
+        /// </summary>
+        private readonly List<Uri> allowedOrigins = new List<Uri>();
+
+        /// <summary>
+        /// Collection of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption.
+        /// </summary>
+        private readonly Collection<ISensorStreamHandlerFactory> streamHandlerFactories;
+
+        /// <summary>
+        /// Snapshot of mapping between URI base path names to be used by clients to refer to
+        /// data from a specific Kinect sensor and their corresponding KinectSensorChooser
+        /// objects, taken every time we create a new listener.
+        /// </summary>
+        private Dictionary<string, KinectSensorChooser> sensorChooserMapSnapshot = new Dictionary<string, KinectSensorChooser>();
+
+        /// <summary>
+        /// Snapshot of origin Uris that are allowed to access data owned by this server,
+        /// taken every time we create a new listener.
+        /// </summary>
+        private List<Uri> allowedOriginsSnapshot = new List<Uri>();
+
+        /// <summary>
+        /// Snapshot of collection of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption, taken every time we create a new listener.
+        /// </summary>
+        private Collection<ISensorStreamHandlerFactory> streamHandlerFactoriesSnapshot = new Collection<ISensorStreamHandlerFactory>(); 
+
+        /// <summary>
+        /// Uri specifying origin associated with web server.
+        /// </summary>
+        private Uri originUri = DefaultUriOrigin;
+
+        /// <summary>
+        /// True if origin URI has changed since last time we started listening for
+        /// connections.
+        /// </summary>
+        private bool hasOriginChanged;
+
+        /// <summary>
+        /// URI base path name to be used by clients to refer to HTTP endpoint that serves
+        /// static files.
+        /// </summary>
+        private string fileServerBasePath = DefaultFileEndpointBasePath;
+
+        /// <summary>
+        /// Root directory in local file system from which static files are served.
+        /// </summary>
+        /// <remarks>
+        /// If null, static files will not be served.
+        /// </remarks>
+        private string fileServerRootDirectory;
+
+        /// <summary>
+        /// True if static file server settings have changed since last time we started
+        /// listening for connections.
+        /// </summary>
+        private bool hasFileServerChanged;
+        
+        /// <summary>
+        /// Listener that handles HTTP requests in a dedicated thread.
+        /// </summary>
+        private ThreadHostedHttpListener listener;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="KinectWebserver"/> class.
+        /// </summary>
+        public KinectWebserver()
+        {
+            this.streamHandlerFactories = KinectRequestHandlerFactory.CreateDefaultStreamHandlerFactories();
+        }
+
+        /// <summary>
+        /// Event used to signal that the server has started listening for connections.
+        /// </summary>
+        public event EventHandler<EventArgs> Started;
+
+        /// <summary>
+        /// Event used to signal that the server has stopped listening for connections.
+        /// </summary>
+        public event EventHandler<EventArgs> Stopped;
+
+        /// <summary>
+        /// Mapping between URI base path names to be used by clients to refer to data from a
+        /// specific Kinect sensor and their corresponding KinectSensorChooser objects.
+        /// </summary>
+        /// <remarks>
+        /// Changes made to this map are only reflected in the server request-handling
+        /// behavior after a call to <see cref="Start"/> method after the modification
+        /// has taken place.
+        /// </remarks>
+        public Dictionary<string, KinectSensorChooser> SensorChooserMap
+        {
+            get
+            {
+                return this.sensorChooserMap;
+            }
+        }
+
+        /// <summary>
+        /// Origin Uris that are allowed to access data served by this listener, in addition
+        /// to owned origin Uri.
+        /// </summary>
+        /// <remarks>
+        /// Changes made to this list are only reflected in the server request-handling
+        /// behavior after a call to <see cref="Start"/> method after the modification
+        /// has taken place.
+        /// </remarks>
+        public ICollection<Uri> AccessControlAllowedOrigins
+        {
+            get
+            {
+                return this.allowedOrigins;
+            }
+        }
+
+        /// <summary>
+        /// Set of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption.
+        /// </summary>
+        public ICollection<ISensorStreamHandlerFactory> SensorStreamHandlerFactories
+        {
+            get
+            {
+                return this.streamHandlerFactories;
+            }
+        }
+
+        /// <summary>
+        /// URI base path name to be used by clients to refer to HTTP endpoint that serves
+        /// static files.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// Must not be a null or empty string.
+        /// </para>
+        /// <para>
+        /// Valid examples: "file", "files", "content", "resources", etc.
+        /// </para>
+        /// <para>
+        /// Changes made to this map are only reflected in the server request-handling
+        /// behavior after a call to <see cref="Start"/> method after the modification
+        /// has taken place.
+        /// </para>
+        /// </remarks>
+        public string FileServerBasePath
+        {
+            get
+            {
+                return this.fileServerBasePath;
+            }
+
+            set
+            {
+                if (string.IsNullOrEmpty(value))
+                {
+                    throw new ArgumentException(@"FileServerBasePath value must not be null or empty");
+                }
+                
+                this.fileServerBasePath = value;
+                this.hasFileServerChanged = true;
+            }
+        }
+
+        /// <summary>
+        /// Origin URI where web server is listening for requests.
+        /// </summary>
+        public Uri OriginUri
+        {
+            get
+            {
+                return this.originUri;
+            }
+
+            set
+            {
+                if (value == null)
+                {
+                    throw new ArgumentNullException("value");
+                }
+
+                this.originUri = value;
+                this.hasOriginChanged = true;
+            }
+        }
+
+        /// <summary>
+        /// Root directory in local file system from which static files are served.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// If null, static files will not be served.
+        /// </para>
+        /// <para>
+        /// Changes made to this map are only reflected in the server request-handling
+        /// behavior after a call to <see cref="Start"/> method after the modification
+        /// has taken place.
+        /// </para>
+        /// </remarks>
+        public string FileServerRootDirectory
+        {
+            get
+            {
+                return this.fileServerRootDirectory;
+            }
+
+            set
+            {
+                this.fileServerRootDirectory = value;
+                this.hasFileServerChanged = true;
+            }
+        }
+
+        /// <summary>
+        /// Start listening for requests.
+        /// </summary>
+        public void Start()
+        {
+            if (this.SensorChooserMap.Count <= 0)
+            {
+                if (this.listener != null)
+                {
+                    this.listener.Stop();
+                }
+
+                return;
+            }
+
+            bool snapshotMatch = true;
+
+            // Check if the sensor chooser map, the allowed origins list or the collection
+            // of stream handler factories have changed since last snapshot was taken.
+            // If any of them has changed we need to create a new ThreadHostedHttpListener,
+            // since the listener handler factory map is immutable data per instance.
+            if (!SensorMapEquals(this.sensorChooserMap, this.sensorChooserMapSnapshot))
+            {
+                snapshotMatch = false;
+                this.sensorChooserMapSnapshot = new Dictionary<string, KinectSensorChooser>(this.sensorChooserMap);
+            }
+
+            if (!this.allowedOrigins.SequenceEqual(this.allowedOriginsSnapshot))
+            {
+                snapshotMatch = false;
+                this.allowedOriginsSnapshot = new List<Uri>(this.allowedOrigins);
+            }
+
+            if (!this.streamHandlerFactoriesSnapshot.SequenceEqual(this.streamHandlerFactories))
+            {
+                snapshotMatch = false;
+                this.streamHandlerFactoriesSnapshot = new Collection<ISensorStreamHandlerFactory>(this.streamHandlerFactories);
+            }
+
+            if (!snapshotMatch || this.hasFileServerChanged || this.hasOriginChanged || (this.listener == null))
+            {
+                if (this.listener != null)
+                {
+                    // we need to stop the current listener before creating a new one with
+                    // diferent parameters
+                    this.listener.Stop(true);
+                    this.listener.Started -= this.ListenerOnStarted;
+                    this.listener.Stopped -= this.ListenerOnStopped;
+                    this.listener = null;
+                }
+
+                var factoryMap = new Dictionary<string, IHttpRequestHandlerFactory>();
+                foreach (var entry in this.sensorChooserMapSnapshot)
+                {
+                    factoryMap.Add(
+                        string.Format(CultureInfo.InvariantCulture, "{0}/{1}", KinectEndpointBasePath, entry.Key),
+                        new KinectRequestHandlerFactory(entry.Value, this.streamHandlerFactoriesSnapshot));
+                }
+
+                if (!string.IsNullOrEmpty(this.fileServerRootDirectory))
+                {
+                    factoryMap.Add(
+                        this.fileServerBasePath,
+                        new FileRequestHandlerFactory(this.fileServerRootDirectory));
+                }
+
+                var origins = new[] { this.OriginUri };
+                this.listener = new ThreadHostedHttpListener(origins, this.allowedOriginsSnapshot, factoryMap);
+                this.listener.Started += this.ListenerOnStarted;
+                this.listener.Stopped += this.ListenerOnStopped;
+                this.listener.Start();
+
+                this.hasFileServerChanged = false;
+                this.hasOriginChanged = false;
+            }
+            else if (!this.listener.IsListening)
+            {
+                this.listener.Start();
+            }
+        }
+
+        /// <summary>
+        /// Stop listening for requests.
+        /// </summary>
+        public void Stop()
+        {
+            if (this.listener == null)
+            {
+                return;
+            }
+
+            this.listener.Stop();
+        }
+
+        /// <summary>
+        /// Determine if two sensor chooser maps are equivalent.
+        /// </summary>
+        /// <param name="left">
+        /// Reference dictionary in comparison.
+        /// </param>
+        /// <param name="right">
+        /// Other dictionary to compare against
+        /// </param>
+        /// <returns>
+        /// True if left and right dictionaries are equivalent. False otherwise.
+        /// </returns>
+        private static bool SensorMapEquals(Dictionary<string, KinectSensorChooser> left, Dictionary<string, KinectSensorChooser> right)
+        {
+            if (left == right)
+            {
+                return true;
+            }
+
+            if ((left == null) || (right == null))
+            {
+                return false;
+            }
+
+            if (left.Count != right.Count)
+            {
+                return false;
+            }
+
+            foreach (var entry in left)
+            {
+                KinectSensorChooser rightValue;
+                if (!right.TryGetValue(entry.Key, out rightValue))
+                {
+                    return false;
+                }
+
+                if (entry.Value == null)
+                {
+                    if (rightValue != null)
+                    {
+                        return false;
+                    }
+
+                    continue;
+                }
+
+                if (!entry.Value.Equals(rightValue))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Handler for ThreadHostedHttpListener.Started event.
+        /// </summary>
+        /// <param name="sender">
+        /// Object that sent the event.
+        /// </param>
+        /// <param name="args">
+        /// Event arguments.
+        /// </param>
+        private void ListenerOnStarted(object sender, EventArgs args)
+        {
+            if (this.Started != null)
+            {
+                this.Started(this, args);
+            }
+        }
+
+        /// <summary>
+        /// Handler for ThreadHostedHttpListener.Stopped event.
+        /// </summary>
+        /// <param name="sender">
+        /// Object that sent the event.
+        /// </param>
+        /// <param name="args">
+        /// Event arguments.
+        /// </param>
+        private void ListenerOnStopped(object sender, EventArgs args)
+        {
+            if (this.Stopped != null)
+            {
+                this.Stopped(this, args);
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.csproj
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.csproj	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.csproj	(revision 28897)
@@ -0,0 +1,156 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{9216E84F-1723-484E-8DE6-250B875F24B6}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Microsoft.Samples.Kinect.Webserver</RootNamespace>
+    <AssemblyName>Microsoft.Samples.Kinect.Webserver</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <WarningLevel>4</WarningLevel>
+    <TargetFrameworkProfile />
+  </PropertyGroup>
+  <PropertyGroup>
+    <OutputPath>$(SolutionDir)$(PlatformTarget).$(Configuration)\</OutputPath>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
+    <DebugSymbols>true</DebugSymbols>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <DebugType>full</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <Prefer32Bit>false</Prefer32Bit>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
+    <Optimize>true</Optimize>
+    <DefineConstants>TRACE</DefineConstants>
+    <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
+    <DebugType>pdbonly</DebugType>
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <Prefer32Bit>false</Prefer32Bit>
+    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Kinect.Toolkit.BackgroundRemoval">
+      <HintPath>$(KINECT_TOOLKIT_DIR)Assemblies\Microsoft.Kinect.Toolkit.BackgroundRemoval.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Kinect.Toolkit.Interaction">
+      <HintPath>$(KINECT_TOOLKIT_DIR)Assemblies\Microsoft.Kinect.Toolkit.Interaction.dll</HintPath>
+    </Reference>
+    <Reference Include="Microsoft.Kinect">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>$(KINECTSDK10_DIR)\Assemblies\Microsoft.Kinect.dll</HintPath>
+    </Reference>
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+    <Reference Include="System" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Runtime.Serialization" />
+    <Reference Include="System.Web" />
+    <Reference Include="System.Web.Extensions" />
+    <Reference Include="System.Xaml" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+    <Reference Include="WindowsBase" />
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="FileRequestHandler.cs" />
+    <Compile Include="FileRequestHandlerFactory.cs" />
+    <Compile Include="IHttpRequestHandler.cs" />
+    <Compile Include="IHttpRequestHandlerFactory.cs" />
+    <Compile Include="JsonSerializationExtensions.cs" />
+    <Compile Include="KinectWebserver.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="RpcResult.cs" />
+    <Compile Include="Sensor\BackgroundRemovalStreamHandler.cs" />
+    <Compile Include="Sensor\DefaultUserStateManager.cs" />
+    <Compile Include="Sensor\IUserStateManager.cs" />
+    <Compile Include="Sensor\InteractionStreamHandler.cs" />
+    <Compile Include="Sensor\ISensorStreamHandler.cs" />
+    <Compile Include="Sensor\ISensorStreamHandlerFactory.cs" />
+    <Compile Include="Sensor\KinectRequestHandler.cs" />
+    <Compile Include="Sensor\KinectRequestHandlerFactory.cs" />
+    <Compile Include="Sensor\SensorStatusStreamHandler.cs" />
+    <Compile Include="Sensor\SensorStreamHandlerBase.cs" />
+    <Compile Include="Sensor\SensorStreamHandlerContext.cs" />
+    <Compile Include="Sensor\SensorStreamHandlerFactory.cs" />
+    <Compile Include="Sensor\Serialization\BackgroundRemovalStreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\EventMessage.cs" />
+    <Compile Include="Sensor\Serialization\FunctionCallRequest.cs" />
+    <Compile Include="Sensor\Serialization\FunctionCallResponse.cs" />
+    <Compile Include="Sensor\Serialization\ImageHeaderStreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\InteractionStreamHitTestInfo.cs" />
+    <Compile Include="Sensor\Serialization\InteractionStreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\MessageHandPointer.cs" />
+    <Compile Include="Sensor\Serialization\SensorStatusEventMessage.cs" />
+    <Compile Include="Sensor\Serialization\SkeletonStreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\StateMappingEntry.cs" />
+    <Compile Include="Sensor\Serialization\StreamMessage.cs" />
+    <Compile Include="Sensor\Serialization\UserStatesChangedEventMessage.cs" />
+    <Compile Include="Sensor\Serialization\UserTrackingIdChangedEventArgs.cs" />
+    <Compile Include="Sensor\SkeletonStreamHandler.cs" />
+    <Compile Include="Sensor\UserActivityMeter.cs" />
+    <Compile Include="Sensor\UserActivityRecord.cs" />
+    <Compile Include="Sensor\UserStateChangedEventArgs.cs" />
+    <Compile Include="Sensor\UserViewerColorizer.cs" />
+    <Compile Include="SharedConstants.cs" />
+    <Compile Include="ThreadHostedHttpListener.cs" />
+    <Compile Include="UriUtilities.cs" />
+    <Compile Include="WebSocketChannelBase.cs" />
+    <Compile Include="WebSocketEventChannel.cs" />
+    <Compile Include="WebSocketMessage.cs" />
+    <Compile Include="WebSocketMessageExtensions.cs" />
+    <Compile Include="WebSocketRpcChannel.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="$(KINECT_TOOLKIT_DIR)Redist\x86\KinectInteraction180_32.dll">
+      <Link>KinectInteraction180_32.dll</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="$(KINECT_TOOLKIT_DIR)Redist\amd64\KinectInteraction180_64.dll">
+      <Link>KinectInteraction180_64.dll</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="$(KINECT_TOOLKIT_DIR)Redist\x86\KinectBackgroundRemoval180_32.dll">
+      <Link>KinectBackgroundRemoval180_32.dll</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="$(KINECT_TOOLKIT_DIR)Redist\amd64\KinectBackgroundRemoval180_64.dll">
+      <Link>KinectBackgroundRemoval180_64.dll</Link>
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.Kinect.Toolkit\Microsoft.Kinect.Toolkit.csproj">
+      <Project>{c6f9c31b-6130-4443-a5cc-ef0664552ecd}</Project>
+      <Name>Microsoft.Kinect.Toolkit</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.sln
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.sln	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Microsoft.Samples.Kinect.Webserver.sln	(revision 28897)
@@ -0,0 +1,25 @@
+﻿
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2012
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Samples.Kinect.Webserver", "Microsoft.Samples.Kinect.Webserver.csproj", "{9216E84F-1723-484E-8DE6-250B875F24B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kinect.Toolkit", "..\Microsoft.Kinect.Toolkit\Microsoft.Kinect.Toolkit.csproj", "{C6F9C31B-6130-4443-A5CC-EF0664552ECD}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/AssemblyInfo.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/AssemblyInfo.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/AssemblyInfo.cs	(revision 28897)
@@ -0,0 +1,25 @@
+﻿using System;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Windows;
+using System.Windows.Markup;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Microsoft.Samples.Kinect.Webserver")]
+[assembly: AssemblyDescription("Sample Microsoft® Web Server for Kinect on Windows")]
+[assembly: AssemblyConfiguration("")]
+
+// No theme-specific resource dictionaries
+// Generic theme dictionary is in this assembly
+[assembly: ThemeInfo(ResourceDictionaryLocation.None, ResourceDictionaryLocation.SourceAssembly)]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+[assembly: CLSCompliant(true)]
+[assembly: NeutralResourcesLanguageAttribute("en-US")]
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/Resources.Designer.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/Resources.Designer.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/Resources.Designer.cs	(revision 28897)
@@ -0,0 +1,171 @@
+﻿//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.18051
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Samples.Kinect.Webserver.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unrecognized property name.
+        /// </summary>
+        internal static string PropertyNameUnrecognized {
+            get {
+                return ResourceManager.GetString("PropertyNameUnrecognized", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Property is read-only.
+        /// </summary>
+        internal static string PropertyReadOnly {
+            get {
+                return ResourceManager.GetString("PropertyReadOnly", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Error while updating property value.
+        /// </summary>
+        internal static string PropertySetError {
+            get {
+                return ResourceManager.GetString("PropertySetError", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unrecognized property value format.
+        /// </summary>
+        internal static string PropertyValueInvalidFormat {
+            get {
+                return ResourceManager.GetString("PropertyValueInvalidFormat", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported resolution.
+        /// </summary>
+        internal static string PropertyValueUnsupportedResolution {
+            get {
+                return ResourceManager.GetString("PropertyValueUnsupportedResolution", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Socket connection has been abandoned by client.
+        /// </summary>
+        internal static string SocketClientDisconnectionDetected {
+            get {
+                return ResourceManager.GetString("SocketClientDisconnectionDetected", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Client has requested to close socket connection.
+        /// </summary>
+        internal static string SocketClosedByClient {
+            get {
+                return ResourceManager.GetString("SocketClosedByClient", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Server has requested to close socket connection.
+        /// </summary>
+        internal static string SocketClosedByServer {
+            get {
+                return ResourceManager.GetString("SocketClosedByServer", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unrecognized stream name.
+        /// </summary>
+        internal static string StreamNameUnrecognized {
+            get {
+                return ResourceManager.GetString("StreamNameUnrecognized", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported color format.
+        /// </summary>
+        internal static string UnsupportedColorFormat {
+            get {
+                return ResourceManager.GetString("UnsupportedColorFormat", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported key type for serialization of a dictionary, keys must be strings..
+        /// </summary>
+        internal static string UnsupportedKeyType {
+            get {
+                return ResourceManager.GetString("UnsupportedKeyType", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to Unsupported sensor stream type.
+        /// </summary>
+        internal static string UnsupportedStreamType {
+            get {
+                return ResourceManager.GetString("UnsupportedStreamType", resourceCulture);
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/Resources.resx
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/Resources.resx	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Properties/Resources.resx	(revision 28897)
@@ -0,0 +1,156 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="PropertyNameUnrecognized" xml:space="preserve">
+    <value>Unrecognized property name</value>
+  </data>
+  <data name="PropertyReadOnly" xml:space="preserve">
+    <value>Property is read-only</value>
+  </data>
+  <data name="PropertySetError" xml:space="preserve">
+    <value>Error while updating property value</value>
+  </data>
+  <data name="PropertyValueInvalidFormat" xml:space="preserve">
+    <value>Unrecognized property value format</value>
+  </data>
+  <data name="PropertyValueUnsupportedResolution" xml:space="preserve">
+    <value>Unsupported resolution</value>
+  </data>
+  <data name="SocketClientDisconnectionDetected" xml:space="preserve">
+    <value>Socket connection has been abandoned by client</value>
+  </data>
+  <data name="SocketClosedByClient" xml:space="preserve">
+    <value>Client has requested to close socket connection</value>
+  </data>
+  <data name="SocketClosedByServer" xml:space="preserve">
+    <value>Server has requested to close socket connection</value>
+  </data>
+  <data name="StreamNameUnrecognized" xml:space="preserve">
+    <value>Unrecognized stream name</value>
+  </data>
+  <data name="UnsupportedColorFormat" xml:space="preserve">
+    <value>Unsupported color format</value>
+  </data>
+  <data name="UnsupportedKeyType" xml:space="preserve">
+    <value>Unsupported key type for serialization of a dictionary, keys must be strings.</value>
+  </data>
+  <data name="UnsupportedStreamType" xml:space="preserve">
+    <value>Unsupported sensor stream type</value>
+  </data>
+</root>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/RpcResult.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/RpcResult.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/RpcResult.cs	(revision 28897)
@@ -0,0 +1,53 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="RpcResult.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    /// <summary>
+    /// Represents a remote procedure call result.
+    /// </summary>
+    /// <typeparam name="T">
+    /// Type of call result.
+    /// </typeparam>
+    public class RpcResult<T>
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RpcResult{T}"/> class.
+        /// </summary>
+        /// <param name="success">
+        /// True if call was successful. False otherwise.
+        /// </param>
+        /// <param name="result">
+        /// Result of remote procedure call.
+        /// </param>
+        public RpcResult(bool success, T result)
+        {
+            this.Success = success;
+            this.Result = result;
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RpcResult{T}"/> class with
+        /// the default value of type T as the result.
+        /// </summary>
+        /// <param name="success">
+        /// True if call was successful. False otherwise.
+        /// </param>
+        public RpcResult(bool success) : this(success, default(T))
+        {
+        }
+
+        /// <summary>
+        /// True if call was successful. False otherwise.
+        /// </summary>
+        public bool Success { get; private set; }
+
+        /// <summary>
+        /// Result of remote procedure call.
+        /// </summary>
+        public T Result { get; private set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/BackgroundRemovalStreamHandler.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/BackgroundRemovalStreamHandler.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/BackgroundRemovalStreamHandler.cs	(revision 28897)
@@ -0,0 +1,456 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="BackgroundRemovalStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+    using System.Linq;
+    using System.Text.RegularExpressions;
+    using System.Windows;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit.BackgroundRemoval;
+    using Microsoft.Samples.Kinect.Webserver.Properties;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of ISensorStreamHandler that exposes background removal streams.
+    /// </summary>
+    [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Disposable background removal stream is disposed when sensor is set to null")]
+    public class BackgroundRemovalStreamHandler : SensorStreamHandlerBase
+    {
+        /// <summary>
+        /// JSON name of background removal stream.
+        /// </summary>
+        internal const string BackgroundRemovalStreamName = "backgroundRemoval";
+
+        /// <summary>
+        /// JSON name for property representing the tracking user id.
+        /// </summary>
+        internal const string TrackingIdPropertyName = "trackingId";
+
+        /// <summary>
+        /// JSON name for property representing the background removed color image resolution.
+        /// </summary>
+        internal const string ResolutionPropertyName = "resolution";
+
+        /// <summary>
+        /// Regular expression that matches the background removed frame resolution property.
+        /// </summary>
+        private static readonly Regex BackgroundRemovalResolutionRegex = new Regex(@"^(?i)(\d+)x(\d+)$");
+
+        private static readonly KeyValuePair<ColorImageFormat, Size>[] BackgroundRemovalResolutions =
+        {
+            new KeyValuePair<ColorImageFormat, Size>(ColorImageFormat.RgbResolution640x480Fps30, new Size(640, 480)),
+            new KeyValuePair<ColorImageFormat, Size>(ColorImageFormat.RgbResolution1280x960Fps12, new Size(1280, 960))
+        };
+
+        /// <summary>
+        /// Context that allows this stream handler to communicate with its owner.
+        /// </summary>
+        private readonly SensorStreamHandlerContext ownerContext;
+
+        /// <summary>
+        /// Serializable background removal stream message, reused as background removed color frames arrive.
+        /// </summary>
+        private readonly BackgroundRemovalStreamMessage backgroundRemovalStreamMessage = new BackgroundRemovalStreamMessage { stream = BackgroundRemovalStreamName };
+
+        /// <summary>
+        /// Sensor providing data to background removal stream.
+        /// </summary>
+        private KinectSensor sensor;
+
+        /// <summary>
+        /// Entry point for background removal stream functionality.
+        /// </summary>
+        private BackgroundRemovedColorStream backgroundRemovalStream;
+
+        /// <summary>
+        /// Id of the user we choose to track.
+        /// </summary>
+        private int trackingId;
+
+        /// <summary>
+        /// True if background removal stream is enabled. Background removal stream is disabled by default.
+        /// </summary>
+        private bool backgroundRemovalStreamIsEnabled;
+
+        /// <summary>
+        /// The background removed color image format.
+        /// </summary>
+        private ColorImageFormat colorImageFormat = ColorImageFormat.RgbResolution640x480Fps30;
+
+        /// <summary>
+        /// Keep track if we're in the middle of processing a background removed color frame.
+        /// </summary>
+        private bool isProcessingBackgroundRemovedFrame;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="BackgroundRemovalStreamHandler"/> class
+        /// and associates it with a context that allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="ownerContext">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        internal BackgroundRemovalStreamHandler(SensorStreamHandlerContext ownerContext)
+        {
+            this.ownerContext = ownerContext;
+
+            this.AddStreamConfiguration(BackgroundRemovalStreamName, new StreamConfiguration(this.GetProperties, this.SetProperty));
+        }
+
+        /// <summary>
+        /// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        public override void OnSensorChanged(KinectSensor newSensor)
+        {
+            if (this.sensor != null)
+            {
+                try
+                {
+                    this.backgroundRemovalStream.BackgroundRemovedFrameReady -= this.BackgroundRemovedFrameReadyAsync;
+                    this.backgroundRemovalStream.Dispose();
+                    this.backgroundRemovalStream = null;
+
+                    this.sensor.ColorStream.Disable();
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+            }
+
+            this.sensor = newSensor;
+
+            if (newSensor != null)
+            {
+                this.backgroundRemovalStream = new BackgroundRemovedColorStream(newSensor);
+                this.backgroundRemovalStream.BackgroundRemovedFrameReady += this.BackgroundRemovedFrameReadyAsync;
+
+                // Force enabling the background removal stream because it hasn't been enabled before.
+                this.UpdateBackgroundRemovalFrameFormat(this.colorImageFormat, true);
+            }
+        }
+
+        /// <summary>
+        /// Process data from one Kinect color frame.
+        /// </summary>
+        /// <param name="colorData">
+        /// Kinect color data.
+        /// </param>
+        /// <param name="colorFrame">
+        /// <see cref="ColorImageFrame"/> from which we obtained color data.
+        /// </param>
+        public override void ProcessColor(byte[] colorData, ColorImageFrame colorFrame)
+        {
+            if (colorData == null)
+            {
+                throw new ArgumentNullException("colorData");
+            }
+
+            if (colorFrame == null)
+            {
+                throw new ArgumentNullException("colorFrame");
+            }
+
+            if (this.backgroundRemovalStreamIsEnabled)
+            {
+                this.backgroundRemovalStream.ProcessColor(colorData, colorFrame.Timestamp);
+            }
+        }
+
+        /// <summary>
+        /// Process data from one Kinect depth frame.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        public override void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
+        {
+            if (depthData == null)
+            {
+                throw new ArgumentNullException("depthData");
+            }
+
+            if (depthFrame == null)
+            {
+                throw new ArgumentNullException("depthFrame");
+            }
+
+            if (this.backgroundRemovalStreamIsEnabled)
+            {
+                this.backgroundRemovalStream.ProcessDepth(depthData, depthFrame.Timestamp);
+            }
+        }
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        public override void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
+        {
+            if (skeletons == null)
+            {
+                throw new ArgumentNullException("skeletons");
+            }
+
+            if (skeletonFrame == null)
+            {
+                throw new ArgumentNullException("skeletonFrame");
+            }
+
+            if (this.backgroundRemovalStreamIsEnabled)
+            {
+                this.backgroundRemovalStream.ProcessSkeleton(skeletons, skeletonFrame.Timestamp);
+            }
+        }
+
+        /// <summary>
+        /// Event handler for BackgroundRemovedColorStream's BackgroundRemovedFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="e">event arguments</param>
+        internal async void BackgroundRemovedFrameReadyAsync(object sender,  BackgroundRemovedColorFrameReadyEventArgs e)
+        {
+            if (!this.backgroundRemovalStreamIsEnabled)
+            {
+                // Directly return if the stream is not enabled.
+                return;
+            }
+
+            if (this.isProcessingBackgroundRemovedFrame)
+            {
+                // Re-entered BackgroundRemovedFrameReadyAsync while a previous frame is already being processed.
+                // Just ignore new frames until the current one finishes processing.
+                return;
+            }
+
+            this.isProcessingBackgroundRemovedFrame = true;
+
+            try
+            {
+                bool haveFrameData = false;
+
+                using (var backgroundRemovedFrame = e.OpenBackgroundRemovedColorFrame())
+                {
+                    if (backgroundRemovedFrame != null)
+                    {
+                        this.backgroundRemovalStreamMessage.UpdateBackgroundRemovedColorFrame(backgroundRemovedFrame);
+
+                        haveFrameData = true;
+                    }
+                }
+
+                if (haveFrameData)
+                {
+                    await this.ownerContext.SendTwoPartStreamMessageAsync(this.backgroundRemovalStreamMessage, this.backgroundRemovalStreamMessage.Buffer);
+                }
+            }
+            finally
+            {
+                this.isProcessingBackgroundRemovedFrame = false;
+            }
+        }
+
+        /// <summary>
+        /// Get the size for the given color image format.
+        /// </summary>
+        /// <param name="format">The color image format.</param>
+        /// <returns>The width and height of the given image.</returns>
+        private static Size GetColorImageSize(ColorImageFormat format)
+        {
+            try
+            {
+                var q = from item in BackgroundRemovalResolutions
+                    where item.Key == format
+                    select item.Value;
+
+                return q.Single();
+            }
+            catch (InvalidOperationException)
+            {
+                throw new ArgumentException(Resources.UnsupportedColorFormat, "format");
+            }
+        }
+
+        /// <summary>
+        /// Get the color image format for the specified width and height.
+        /// </summary>
+        /// <param name="width">
+        /// Image width.
+        /// </param>
+        /// <param name="height">
+        /// Image height.
+        /// </param>
+        /// <returns>
+        /// The color image format enumeration value.
+        /// </returns>
+        private static ColorImageFormat GetColorImageFormat(int width, int height)
+        {
+            try
+            {
+                var q = from item in BackgroundRemovalResolutions
+                        where (int)item.Value.Width == width && (int)item.Value.Height == height
+                        select item.Key;
+
+                return q.Single();
+            }
+            catch (InvalidOperationException)
+            {
+                throw new ArgumentException(Resources.UnsupportedColorFormat);
+            }
+        }
+
+        /// <summary>
+        /// Set the background removed color frame format.
+        /// </summary>
+        /// <param name="format">
+        /// The given color image format.
+        /// </param>
+        /// <param name="forceEnable">
+        /// Streams should be enabled even if new color image format is the same as the old one.
+        /// This is useful for the initial enabling of the stream.
+        /// </param>
+        private void UpdateBackgroundRemovalFrameFormat(ColorImageFormat format, bool forceEnable)
+        {
+            if (!forceEnable && (format == this.colorImageFormat))
+            {
+                // No work to do
+                return;
+            }
+
+            if (this.sensor != null)
+            {
+                try
+                {
+                    this.sensor.ColorStream.Enable(format);
+                    this.backgroundRemovalStream.Enable(format, DepthImageFormat.Resolution640x480Fps30);
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+            }
+
+            // Update the image format property if the action succeeded.
+            this.colorImageFormat = format;
+        }
+
+        /// <summary>
+        /// Gets a background removal stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        private void GetProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.backgroundRemovalStreamIsEnabled);
+            propertyMap.Add(TrackingIdPropertyName, this.trackingId);
+
+            var size = GetColorImageSize(this.colorImageFormat);
+            propertyMap.Add(ResolutionPropertyName, string.Format(CultureInfo.InvariantCulture, @"{0}x{1}", (int)size.Width, (int)size.Height));
+        }
+
+        /// <summary>
+        /// Set a background removal stream property value.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        private string SetProperty(string propertyName, object propertyValue)
+        {
+            bool recognized = true;
+
+            if (propertyValue == null)
+            {
+                return Resources.PropertyValueInvalidFormat;
+            }
+
+            try
+            {
+                switch (propertyName)
+                {
+                    case KinectRequestHandler.EnabledPropertyName:
+                        this.backgroundRemovalStreamIsEnabled = (bool)propertyValue;
+                        break;
+
+                    case TrackingIdPropertyName:
+                        {
+                            var oldTrackingId = this.trackingId;
+                            this.trackingId = (int)propertyValue;
+
+                            if (this.trackingId != oldTrackingId)
+                            {
+                                this.backgroundRemovalStream.SetTrackedPlayer(this.trackingId);
+                            }
+                        }
+
+                        break;
+
+                    case ResolutionPropertyName:
+                        var match = BackgroundRemovalResolutionRegex.Match((string)propertyValue);
+                        if (!match.Success || (match.Groups.Count != 3))
+                        {
+                            return Resources.PropertyValueInvalidFormat;
+                        }
+
+                        int width = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
+                        int height = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
+
+                        try
+                        {
+                            var format = GetColorImageFormat(width, height);
+                            this.UpdateBackgroundRemovalFrameFormat(format, false);
+                        }
+                        catch (ArgumentException)
+                        {
+                            return Resources.PropertyValueUnsupportedResolution;
+                        }
+
+                        break;
+
+                    default:
+                        recognized = false;
+                        break;
+                }
+
+                if (!recognized)
+                {
+                    return Resources.PropertyNameUnrecognized;
+                }
+            }
+            catch (InvalidCastException)
+            {
+                return Resources.PropertyValueInvalidFormat;
+            }
+
+            return null;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/DefaultUserStateManager.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/DefaultUserStateManager.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/DefaultUserStateManager.cs	(revision 28897)
@@ -0,0 +1,608 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="DefaultUserStateManager.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Kinect.Toolkit.Interaction;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Default implementation of <see cref="IUserStateManager"/> interface.
+    /// </summary>
+    public class DefaultUserStateManager : IUserStateManager
+    {
+        /// <summary>
+        /// Name of state representing a tracked user.
+        /// </summary>
+        public const string TrackedStateName = "tracked";
+
+        /// <summary>
+        /// Name of state representing an engaged user.
+        /// </summary>
+        public const string EngagedStateName = "engaged";
+
+        /// <summary>
+        /// Category of all events originating from this class.
+        /// </summary>
+        public const string EventCategory = "userState";
+
+        /// <summary>
+        /// Event type for primary user changed event.
+        /// </summary>
+        public const string PrimaryUserChangedEventType = "primaryUserChanged";
+
+        /// <summary>
+        /// Event type for user state changed event.
+        /// </summary>
+        public const string UserStatesChangedEventType = "userStatesChanged";
+
+        /// <summary>
+        /// Length (in milliseconds) of period of inactivity required
+        /// before users become candidates for tracking.
+        /// </summary>
+        internal const long MinimumInactivityBeforeTrackingMilliseconds = 500;
+
+        /// <summary>
+        /// Object used to keep track of user activity.
+        /// </summary>
+        private readonly UserActivityMeter activityMeter = new UserActivityMeter();
+
+        /// <summary>
+        /// Object used to synchronize modifications to engagement state.
+        /// </summary>
+        private readonly object lockObject = new object();
+
+        /// <summary>
+        /// Helper object used to keep track of previous set of tracked user ids while avoiding
+        /// object allocation while processing each frame.
+        /// </summary>
+        private HashSet<int> previousTrackedUserTrackingIds = new HashSet<int>();
+
+        /// <summary>
+        /// Map between user tracking IDs and user states exposed to clients.
+        /// </summary>
+        private Dictionary<int, string> publicUserStates = new Dictionary<int, string>();
+
+        /// <summary>
+        /// Dictionary used to accumulate mappings between user tracking IDs and user states
+        /// before they're ready to be exposed to clients.
+        /// </summary>
+        private Dictionary<int, string> userStatesAccumulator = new Dictionary<int, string>();
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="DefaultUserStateManager"/> class.
+        /// </summary>
+        public DefaultUserStateManager()
+        {
+            this.TrackedUserTrackingIds = new HashSet<int>();
+        }
+
+        /// <summary>
+        /// Event triggered whenever user state changes.
+        /// </summary>
+        public event EventHandler<UserStateChangedEventArgs> UserStateChanged;
+
+        /// <summary>
+        /// Dictionary mapping user tracking Ids to names used for states corresponding to
+        /// those users.
+        /// </summary>
+        public IDictionary<int, string> UserStates
+        {
+            get
+            {
+                return this.publicUserStates;
+            }
+        }
+
+        /// <summary>
+        /// Tracking ID corresponding to primary user.
+        /// </summary>
+        /// <remarks>
+        /// May be an invalid tracking id to represent that no user is currently primary.
+        /// </remarks>
+        public int PrimaryUserTrackingId { get; set; }
+
+        /// <summary>
+        /// Tracking ID corresponding to engaged user.
+        /// </summary>
+        /// <remarks>
+        /// May be an invalid tracking id to represent that no user is currently engaged.
+        /// </remarks>
+        private int EngagedUserTrackingId { get; set; }
+
+        /// <summary>
+        /// Set of tracking Ids corresponding to users currently considered to be tracked.
+        /// </summary>
+        private HashSet<int> TrackedUserTrackingIds { get; set; }
+
+        /// <summary>
+        /// Resets all state to the initial state, with no users remembered as engaged or tracked.
+        /// </summary>
+        public void Reset()
+        {
+            using (var callbackLock = new CallbackLock(this.lockObject))
+            {
+                this.activityMeter.Clear();
+                this.TrackedUserTrackingIds.Clear();
+                this.EngagedUserTrackingId = SharedConstants.InvalidUserTrackingId;
+                this.SetPrimaryUserTrackingId(SharedConstants.InvalidUserTrackingId, callbackLock);
+                this.UpdateUserStates(callbackLock);
+            }
+        }
+
+        /// <summary>
+        /// Determines which users should be tracked in the future, based on selection
+        /// metrics and engagement state.
+        /// </summary>
+        /// <param name="frameSkeletons">
+        /// Array of skeletons from which the appropriate user tracking Ids will be selected.
+        /// </param>
+        /// <param name="timestamp">
+        /// Timestamp from skeleton frame.
+        /// </param>
+        /// <param name="chosenTrackingIds">
+        /// Array that will contain the tracking Ids of users to track, sorted from most
+        /// important to least important user to track.
+        /// </param>
+        public void ChooseTrackedUsers(Skeleton[] frameSkeletons, long timestamp, int[] chosenTrackingIds)
+        {
+            if (frameSkeletons == null)
+            {
+                throw new ArgumentNullException("frameSkeletons");
+            }
+
+            if (chosenTrackingIds == null)
+            {
+                throw new ArgumentNullException("chosenTrackingIds");
+            }
+
+            var availableSkeletons = new List<Skeleton>(
+                from skeleton in frameSkeletons
+                where
+                    (skeleton.TrackingId != SharedConstants.InvalidUserTrackingId)
+                    &&
+                    ((skeleton.TrackingState == SkeletonTrackingState.Tracked)
+                     || (skeleton.TrackingState == SkeletonTrackingState.PositionOnly))
+                select skeleton);
+            var trackingCandidateSkeletons = new List<Skeleton>();
+
+            // Update user activity metrics
+            this.activityMeter.Update(availableSkeletons, timestamp);
+
+            foreach (var skeleton in availableSkeletons)
+            {
+                UserActivityRecord record;
+                if (this.activityMeter.TryGetActivityRecord(skeleton.TrackingId, out record))
+                {
+                    // The tracked skeletons become candidate skeletons for tracking if we have an activity record for them.
+                    trackingCandidateSkeletons.Add(skeleton);
+                }
+            }
+
+            // sort the currently tracked skeletons according to our tracking choice criteria
+            trackingCandidateSkeletons.Sort((left, right) => this.ComputeTrackingMetric(right).CompareTo(this.ComputeTrackingMetric(left)));
+
+            for (int i = 0; i < chosenTrackingIds.Length; ++i)
+            {
+                chosenTrackingIds[i] = (i < trackingCandidateSkeletons.Count) ? trackingCandidateSkeletons[i].TrackingId : SharedConstants.InvalidUserTrackingId;
+            }
+        }
+
+        /// <summary>
+        /// Called whenever the set of tracked users has changed.
+        /// </summary>
+        /// <param name="trackedUserInfo">
+        /// User information from which we'll update the set of tracked users and the primary user.
+        /// </param>
+        /// <param name="timestamp">
+        /// Interaction frame timestamp corresponding to given user information.
+        /// </param>
+        public void UpdateUserInformation(IEnumerable<UserInfo> trackedUserInfo, long timestamp)
+        {
+            bool foundEngagedUser = false;
+            int firstTrackedUser = SharedConstants.InvalidUserTrackingId;
+
+            using (var callbackLock = new CallbackLock(this.lockObject))
+            {
+                this.previousTrackedUserTrackingIds.Clear();
+                var nextTrackedIds = this.previousTrackedUserTrackingIds;
+                this.previousTrackedUserTrackingIds = this.TrackedUserTrackingIds;
+                this.TrackedUserTrackingIds = nextTrackedIds;
+
+                var trackedUserInfoArray = trackedUserInfo as UserInfo[] ?? trackedUserInfo.ToArray();
+
+                foreach (var userInfo in trackedUserInfoArray)
+                {
+                    if (userInfo.SkeletonTrackingId == SharedConstants.InvalidUserTrackingId)
+                    {
+                        continue;
+                    }
+
+                    if (this.EngagedUserTrackingId == userInfo.SkeletonTrackingId)
+                    {
+                        this.TrackedUserTrackingIds.Add(userInfo.SkeletonTrackingId);
+
+                        foundEngagedUser = true;
+                    }
+                    else if (HasTrackedHands(userInfo)
+                             && (this.previousTrackedUserTrackingIds.Contains(userInfo.SkeletonTrackingId)
+                                 || this.IsInactive(userInfo, timestamp)))
+                    {
+                        // Keep track of the non-engaged users we find that have at least one
+                        // tracked hand pointer and also either (1) were previously tracked or
+                        // (2) are not moving too much
+                        this.TrackedUserTrackingIds.Add(userInfo.SkeletonTrackingId);
+
+                        if (firstTrackedUser == SharedConstants.InvalidUserTrackingId)
+                        {
+                            // Consider the first non-engaged, stationary user as a candidate for engagement
+                            firstTrackedUser = userInfo.SkeletonTrackingId;
+                        }
+                    }
+                }
+
+                // If engaged user was not found in list of candidate users, engaged user has become invalid.
+                if (!foundEngagedUser)
+                {
+                    this.EngagedUserTrackingId = SharedConstants.InvalidUserTrackingId;
+                }
+
+                // Decide who should be the primary user, if anyone
+                this.UpdatePrimaryUser(trackedUserInfoArray, callbackLock);
+
+                // If there's a primary user, it is the preferred candidate for engagement.
+                // Otherwise, the first tracked user seen is the preferred candidate.
+                int candidateUserTrackingId = (this.PrimaryUserTrackingId != SharedConstants.InvalidUserTrackingId)
+                                                  ? this.PrimaryUserTrackingId
+                                                  : firstTrackedUser;
+
+                // If there is a valid candidate user that is not already the engaged user
+                if ((candidateUserTrackingId != SharedConstants.InvalidUserTrackingId)
+                    && (candidateUserTrackingId != this.EngagedUserTrackingId))
+                {
+                    // If there is currently no engaged user, or if candidate user is the
+                    // primary user controlling interactions while the currently engaged user
+                    // is not interacting
+                    if ((this.EngagedUserTrackingId == SharedConstants.InvalidUserTrackingId)
+                        || (candidateUserTrackingId == this.PrimaryUserTrackingId))
+                    {
+                        this.PromoteCandidateToEngaged(candidateUserTrackingId);
+                    }
+                }
+
+                // Update user states as the very last action, to include results from updates
+                // performed so far
+                this.UpdateUserStates(callbackLock);
+            }
+        }
+
+        /// <summary>
+        /// Promote candidate user to be the engaged user.
+        /// </summary>
+        /// <param name="candidateTrackingId">
+        /// Tracking Id of user to be promoted to engaged user.
+        /// If tracking Id does not match the Id of one of the currently tracked users,
+        /// no action is taken.
+        /// </param>
+        /// <returns>
+        /// True if specified candidate could be confirmed as the new engaged user,
+        /// false otherwise.
+        /// </returns>
+        public bool PromoteCandidateToEngaged(int candidateTrackingId)
+        {
+            bool isConfirmed = false;
+
+            if ((candidateTrackingId != SharedConstants.InvalidUserTrackingId) && this.TrackedUserTrackingIds.Contains(candidateTrackingId))
+            {
+                using (var callbackLock = new CallbackLock(this.lockObject))
+                {
+                    this.EngagedUserTrackingId = candidateTrackingId;
+                    this.UpdateUserStates(callbackLock);
+                }
+
+                isConfirmed = true;
+            }
+
+            return isConfirmed;
+        }
+
+        /// <summary>
+        /// Tries to get the last position observed for the specified user tracking Id.
+        /// </summary>
+        /// <param name="trackingId">
+        /// User tracking Id for which we're finding the last position observed.
+        /// </param>
+        /// <returns>
+        /// Skeleton point, if last position is being tracked for specified
+        /// tracking Id, null otherwise.
+        /// </returns>
+        public SkeletonPoint? TryGetLastPositionForId(int trackingId)
+        {
+            if (SharedConstants.InvalidUserTrackingId == trackingId)
+            {
+                return null;
+            }
+
+            UserActivityRecord record;
+            if (this.activityMeter.TryGetActivityRecord(trackingId, out record))
+            {
+                return record.LastPosition;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Get a JSON friendly array of user-tracking-id-to-state mapping entries
+        /// representing the specified user state map.
+        /// </summary>
+        /// <param name="userStates">
+        /// Dictionary mapping user tracking ids to user state names.
+        /// </param>
+        /// <returns>
+        /// Array of <see cref="StateMappingEntry"/> objects.
+        /// </returns>
+        internal static StateMappingEntry[] GetStateMappingEntryArray(IDictionary<int, string> userStates)
+        {
+            var mappingEntries = new StateMappingEntry[userStates.Count];
+            int entryIndex = 0;
+            foreach (var userStateEntry in userStates)
+            {
+                mappingEntries[entryIndex] = new StateMappingEntry { id = userStateEntry.Key, userState = userStateEntry.Value };
+                ++entryIndex;
+            }
+
+            return mappingEntries;
+        }
+
+        internal void SetPrimaryUserTrackingId(int newId, CallbackLock callbackLock)
+        {
+            int oldId = this.PrimaryUserTrackingId;
+            this.PrimaryUserTrackingId = newId;
+
+            if (oldId != newId)
+            {
+                callbackLock.LockExit +=
+                    () =>
+                    this.SendUserStateChanged(
+                        new UserTrackingIdChangedEventMessage
+                        {
+                            category = EventCategory,
+                            eventType = PrimaryUserChangedEventType,
+                            oldValue = oldId,
+                            newValue = newId
+                        });
+            }
+        }
+
+        /// <summary>
+        /// Determine if any of the specified user's hands is tracked.
+        /// </summary>
+        /// <param name="userInfo">
+        /// User information from which to determine hand tracking status.
+        /// </param>
+        /// <returns>
+        /// True if user has at least one tracked hand pointer. False otherwise.
+        /// </returns>
+        private static bool HasTrackedHands(UserInfo userInfo)
+        {
+            return userInfo.HandPointers.Any(handPointer => handPointer.IsTracked);
+        }
+        
+        /// <summary>
+        /// Update the primary user being tracked.
+        /// </summary>
+        /// <param name="candidateUserInfo">
+        /// User information collection from which we will choose a primary user.
+        /// </param>
+        /// <param name="callbackLock">
+        /// Lock used to delay all events until after we exit lock section.
+        /// </param>
+        private void UpdatePrimaryUser(IEnumerable<UserInfo> candidateUserInfo, CallbackLock callbackLock)
+        {
+            int firstPrimaryUserCandidate = SharedConstants.InvalidUserTrackingId;
+            bool currentPrimaryUserStillPrimary = false;
+            bool engagedUserIsPrimary = false;
+
+            var trackingIdsAvailable = new HashSet<int>();
+
+            foreach (var userInfo in candidateUserInfo)
+            {
+                if (userInfo.SkeletonTrackingId == SharedConstants.InvalidUserTrackingId)
+                {
+                    continue;
+                }
+
+                trackingIdsAvailable.Add(userInfo.SkeletonTrackingId);
+
+                foreach (var handPointer in userInfo.HandPointers)
+                {
+                    if (handPointer.IsPrimaryForUser)
+                    {
+                        if (this.PrimaryUserTrackingId == userInfo.SkeletonTrackingId)
+                        {
+                            // If the current primary user still has an active hand, we should continue to consider them the primary user.
+                            currentPrimaryUserStillPrimary = true;
+                        }
+                        else if (SharedConstants.InvalidUserTrackingId == firstPrimaryUserCandidate)
+                        {
+                            // Else if this is the first user with an active hand, they are the alternative candidate for primary user.
+                            firstPrimaryUserCandidate = userInfo.SkeletonTrackingId;
+                        }
+
+                        if (this.EngagedUserTrackingId == userInfo.SkeletonTrackingId)
+                        {
+                            engagedUserIsPrimary = true;
+                        }
+                    }
+                }
+            }
+
+            // If engaged user has a primary hand, always pick that user as primary user.
+            // If current primary user still has a primary hand, let them remain primary.
+            // Otherwise default to first primary user candidate seen.
+            int primaryUserTrackingId = engagedUserIsPrimary
+                                            ? this.EngagedUserTrackingId
+                                            : (currentPrimaryUserStillPrimary ? this.PrimaryUserTrackingId : firstPrimaryUserCandidate);
+            this.SetPrimaryUserTrackingId(primaryUserTrackingId, callbackLock);
+        }
+
+        /// <summary>
+        /// Calculate how valuable it will be to keep tracking the specified skeleton.
+        /// </summary>
+        /// <param name="skeleton">
+        /// Skeleton that is one of several candidates for tracking.
+        /// </param>
+        /// <returns>
+        /// A non-negative metric that estimates how valuable it is to keep tracking
+        /// the specified skeleton. The higher the value, the more valuable the skeleton
+        /// is estimated to be.
+        /// </returns>
+        private double ComputeTrackingMetric(Skeleton skeleton)
+        {
+            const double MaxCameraDistance = 4.0;
+
+            // Give preference to engaged users, then to tracked users, then to users
+            // near the center of the Kinect Sensor's field of view that are also
+            // closer (distance) to the KinectSensor and not moving around too much.
+            const double EngagedWeight = 100.0;
+            const double TrackedWeight = 50.0;
+            const double AngleFromCenterWeight = 1.30;
+            const double DistanceFromCameraWeight = 1.15;
+            const double BodyMovementWeight = 0.05;
+
+            double engagedMetric = (skeleton.TrackingId == this.EngagedUserTrackingId) ? 1.0 : 0.0;
+            double trackedMetric = this.TrackedUserTrackingIds.Contains(skeleton.TrackingId) ? 1.0 : 0.0;
+            double angleFromCenterMetric = (skeleton.Position.Z > 0.0) ? (1.0 - Math.Abs(2 * Math.Atan(skeleton.Position.X / skeleton.Position.Z) / Math.PI)) : 0.0;
+            double distanceFromCameraMetric = (MaxCameraDistance - skeleton.Position.Z) / MaxCameraDistance;
+            UserActivityRecord activityRecord;
+            double bodyMovementMetric = this.activityMeter.TryGetActivityRecord(skeleton.TrackingId, out activityRecord)
+                                            ? 1.0 - activityRecord.ActivityLevel
+                                            : 0.0;
+            return (EngagedWeight * engagedMetric) +
+                (TrackedWeight * trackedMetric) +
+                (AngleFromCenterWeight * angleFromCenterMetric) +
+                (DistanceFromCameraWeight * distanceFromCameraMetric) +
+                (BodyMovementWeight * bodyMovementMetric);
+        }
+
+        /// <summary>
+        /// Determine if the specified user information represents a user that has been
+        /// relatively inactive for at least a minimum period of time required for tracking.
+        /// </summary>
+        /// <param name="userInfo">
+        /// User information from which to determine inactivity.
+        /// </param>
+        /// <param name="timestamp">
+        /// Current timestamp used to determine how long user has been inactive.
+        /// </param>
+        /// <returns>
+        /// True if user is present in scene and has been inactive for a minimum threshold
+        /// period of time.
+        /// </returns>
+        private bool IsInactive(UserInfo userInfo, long timestamp)
+        {
+            UserActivityRecord record;
+            return this.activityMeter.TryGetActivityRecord(userInfo.SkeletonTrackingId, out record) && !record.IsActive
+                   && (record.StateTransitionTimestamp + MinimumInactivityBeforeTrackingMilliseconds <= timestamp);
+        }
+
+        /// <summary>
+        /// Determines if user states have changed.
+        /// </summary>
+        /// <returns>
+        /// true if accumulated user states are different from the ones currently visible
+        /// to clients.
+        /// </returns>
+        private bool HaveUserStatesChanged()
+        {
+            if (this.publicUserStates.Count != this.userStatesAccumulator.Count)
+            {
+                return true;
+            }
+
+            foreach (var stateEntry in this.publicUserStates)
+            {
+                string accumulatorState;
+                if (!this.userStatesAccumulator.TryGetValue(stateEntry.Key, out accumulatorState))
+                {
+                    // Key is absent from accumulator but present in current state map
+                    return true;
+                }
+
+                if (!stateEntry.Value.Equals(accumulatorState))
+                {
+                    // state names are present in both maps, but they're different
+                    return true;
+                }
+            }
+
+            return false;
+        }
+
+        /// <summary>
+        /// Update user states exposed to clients, if necessary.
+        /// </summary>
+        /// <param name="callbackLock">
+        /// Lock used to delay all events until after we exit lock section.
+        /// </param>
+        private void UpdateUserStates(CallbackLock callbackLock)
+        {
+            this.userStatesAccumulator.Clear();
+
+            // Add states for tracked users
+            foreach (var trackingId in this.TrackedUserTrackingIds)
+            {
+                this.userStatesAccumulator.Add(trackingId, TrackedStateName);
+            }
+
+            if (this.EngagedUserTrackingId != SharedConstants.InvalidUserTrackingId)
+            {
+                // Engaged state supersedes all other states
+                this.userStatesAccumulator[this.EngagedUserTrackingId] = EngagedStateName;
+            }
+
+            if (this.HaveUserStatesChanged())
+            {
+                var temporaryMap = this.publicUserStates;
+                this.publicUserStates = this.userStatesAccumulator;
+                this.userStatesAccumulator = temporaryMap;
+
+                var userStatesToSend = GetStateMappingEntryArray(this.publicUserStates);
+
+                callbackLock.LockExit +=
+                    () =>
+                    this.SendUserStateChanged(
+                        new UserStatesChangedEventMessage
+                        {
+                            category = EventCategory,
+                            eventType = UserStatesChangedEventType,
+                            userStates = userStatesToSend
+                        });
+            }
+        }
+
+        /// <summary>
+        /// Send UserStateChanged event if there are any subscribers.
+        /// </summary>
+        /// <param name="message">
+        /// Message to send.
+        /// </param>
+        private void SendUserStateChanged(EventMessage message)
+        {
+            if (this.UserStateChanged != null)
+            {
+                this.UserStateChanged(this, new UserStateChangedEventArgs(message));
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandler.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandler.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandler.cs	(revision 28897)
@@ -0,0 +1,168 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="ISensorStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System.Collections.Generic;
+    using System.Net;
+    using System.Threading.Tasks;
+
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Interface called upon to:
+    /// a) Process Kinect sensor data associated with a specific stream or set of streams.
+    /// b) Handle requests to deliver data associated with the same stream(s).
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// Objects that implement this interface are not expected to be thread-safe but are expected
+    /// to support async operations, so instances should be created and instance methods should
+    /// always be called from a single thread that is associated with a SynchronizationContext
+    /// that provides single-threaded message dispatch
+    /// (e.g.: System.Windows.Threading.DispatcherSynchronizationContext).
+    /// </para>
+    /// <para>
+    /// This means that IHttpRequestHandlers still have to be aware of potential reentrancy
+    /// resulting from asynchronous operations, but they don't have to protect data access with
+    /// locks.
+    /// </para>
+    /// </remarks>
+    public interface ISensorStreamHandler
+    {
+        /// <summary>
+        /// Get the names of the stream(s) supported by this stream handler.
+        /// </summary>
+        /// <returns>
+        /// An array of stream names.
+        /// </returns>
+        /// <remarks>
+        /// These names will be used in JSON objects to refer to individual streams.
+        /// </remarks>
+        string[] GetSupportedStreamNames();
+
+        /// <summary>
+        /// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        void OnSensorChanged(KinectSensor newSensor);
+
+        /// <summary>
+        /// Process data from one Kinect color frame.
+        /// </summary>
+        /// <param name="colorData">
+        /// Kinect color data.
+        /// </param>
+        /// <param name="colorFrame">
+        /// <see cref="ColorImageFrame"/> from which we obtained color data.
+        /// </param>
+        void ProcessColor(byte[] colorData, ColorImageFrame colorFrame);
+
+        /// <summary>
+        /// Process data from one Kinect depth frame.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame);
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame);
+
+        /// <summary>
+        /// Gets the state property values associated with the specified stream name.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be returned.
+        /// </param>
+        /// <returns>
+        /// Dictionary mapping property names to property values.
+        /// </returns>
+        IDictionary<string, object> GetState(string streamName);
+
+        /// <summary>
+        /// Attempts to set the specified state property values associated with the specified
+        /// stream name.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="properties">
+        /// Dictionary mapping property names to property values that should be set.
+        /// Must not be null.
+        /// </param>
+        /// <param name="errors">
+        /// Dictionary meant to receive mappings between property names to errors encountered
+        /// while trying to set each property value.
+        /// May be null.
+        /// </param>
+        /// <returns>
+        /// true if there were no errors encountered while setting state. false otherwise.
+        /// </returns>
+        /// <remarks>
+        /// If <paramref name="errors"/> is non-null, it is expected that it will be empty
+        /// when method is called.
+        /// </remarks>
+        bool SetState(string streamName, IReadOnlyDictionary<string, object> properties, IDictionary<string, object> errors);
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the stream name associated with this sensor stream
+        /// handler in the stream handler owner.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task HandleRequestAsync(string streamName, HttpListenerContext requestContext, string subpath);
+
+        /// <summary>
+        /// Cancel all pending operations
+        /// </summary>
+        void Cancel();
+
+        /// <summary>
+        /// Lets handler know that it should clean up resources associated with sensor stream
+        /// handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        Task UninitializeAsync();
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandlerFactory.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandlerFactory.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/ISensorStreamHandlerFactory.cs	(revision 28897)
@@ -0,0 +1,23 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="ISensorStreamHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    public interface ISensorStreamHandlerFactory
+    {
+        /// <summary>
+        /// Creates a sensor stream handler object and associates it with a context that
+        /// allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="context">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        /// <returns>
+        /// A new <see cref="ISensorStreamHandler"/> instance.
+        /// </returns>
+        ISensorStreamHandler CreateHandler(SensorStreamHandlerContext context);
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/IUserStateManager.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/IUserStateManager.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/IUserStateManager.cs	(revision 28897)
@@ -0,0 +1,69 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="IUserStateManager.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit.Interaction;
+
+    /// <summary>
+    /// Interface for objects that keep track of state of users associated with a specific
+    /// Kinect sensor.
+    /// </summary>
+    public interface IUserStateManager
+    {
+        /// <summary>
+        /// Event triggered whenever user state changes.
+        /// </summary>
+        event EventHandler<UserStateChangedEventArgs> UserStateChanged;
+
+        /// <summary>
+        /// Dictionary mapping user tracking Ids to names used for states corresponding to
+        /// those users.
+        /// </summary>
+        IDictionary<int, string> UserStates { get; }
+
+        /// <summary>
+        /// Tracking id of primary user associated with UI interactions.
+        /// </summary>
+        int PrimaryUserTrackingId { get; }
+
+        /// <summary>
+        /// Determines which users should be tracked in the future, based on selection
+        /// metrics and engagement state.
+        /// </summary>
+        /// <param name="frameSkeletons">
+        /// Array of skeletons from which the appropriate user tracking Ids will be selected.
+        /// </param>
+        /// <param name="timestamp">
+        /// Timestamp from skeleton frame.
+        /// </param>
+        /// <param name="chosenTrackingIds">
+        /// Array that will contain the tracking Ids of users to track, sorted from most
+        /// important to least important user to track.
+        /// </param>
+        void ChooseTrackedUsers(Skeleton[] frameSkeletons, long timestamp, int[] chosenTrackingIds);
+
+        /// <summary>
+        /// Called whenever the set of tracked users has changed.
+        /// </summary>
+        /// <param name="trackedUserInfo">
+        /// User information from which we'll update the set of tracked users and the primary user.
+        /// </param>
+        /// <param name="timestamp">
+        /// Interaction frame timestamp corresponding to given user information.
+        /// </param>
+        void UpdateUserInformation(IEnumerable<UserInfo> trackedUserInfo, long timestamp);
+
+        /// <summary>
+        /// Clear out all user state and start from scratch.
+        /// </summary>
+        void Reset();
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/InteractionStreamHandler.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/InteractionStreamHandler.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/InteractionStreamHandler.cs	(revision 28897)
@@ -0,0 +1,856 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="InteractionStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+    using System.Linq;
+    using System.Net;
+    using System.Text.RegularExpressions;
+    using System.Threading.Tasks;
+    using System.Windows;
+    using System.Windows.Media;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit.Interaction;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of ISensorStreamHandler that exposes interaction and user viewer
+    /// streams.
+    /// </summary>
+    [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Disposable interaction stream is disposed when sensor is set to null")]
+    public class InteractionStreamHandler : SensorStreamHandlerBase, IInteractionClient
+    {
+        /// <summary>
+        /// JSON name of interaction stream.
+        /// </summary>
+        internal const string InteractionStreamName = "interaction";
+
+        /// <summary>
+        /// JSON name for property representing primary user tracking ID.
+        /// </summary>
+        internal const string InteractionPrimaryUserPropertyName = "primaryUser";
+
+        /// <summary>
+        /// JSON name for property representing user states.
+        /// </summary>
+        internal const string InteractionUserStatesPropertyName = "userStates";
+
+        /// <summary>
+        /// JSON name of user viewer stream.
+        /// </summary>
+        internal const string UserViewerStreamName = "userviewer";
+
+        /// <summary>
+        /// JSON name for property representing user viewer image resolution.
+        /// </summary>
+        internal const string UserViewerResolutionPropertyName = "resolution";
+
+        /// <summary>
+        /// Default width for user viewer image.
+        /// </summary>
+        internal const int UserViewerDefaultWidth = 128;
+
+        /// <summary>
+        /// Default height for user viewer image.
+        /// </summary>
+        internal const int UserViewerDefaultHeight = 96;
+
+        /// <summary>
+        /// JSON name for property representing default color for users in user viewer image.
+        /// </summary>
+        internal const string UserViewerDefaultUserColorPropertyName = "defaultUserColor";
+
+        /// <summary>
+        /// JSON name for property representing a map between user states and colors that should
+        /// be used to represent those states in user viewer image.
+        /// </summary>
+        internal const string UserViewerUserColorsPropertyName = "userColors";
+
+        /// <summary>
+        /// Sub path for interaction client web-socket RPC endpoint owned by this handler.
+        /// </summary>
+        internal const string ClientUriSubpath = "CLIENT";
+
+        /// <summary>
+        /// Default value for default color for users in user viewer image (light gray).
+        /// </summary>
+        internal static readonly Color UserViewerDefaultDefaultUserColor = new Color { R = 0xd3, G = 0xd3, B = 0xd3, A = 0xff };
+
+        /// <summary>
+        /// Default color for tracked users in user viewer image (Kinect blue).
+        /// </summary>
+        internal static readonly Color UserViewerDefaultTrackedUserColor = new Color { R = 0x00, G = 0xbc, B = 0xf2, A = 0xff };
+
+        /// <summary>
+        /// Default color for engaged users in user viewer image (Kinect purple).
+        /// </summary>
+        internal static readonly Color UserViewerDefaultEngagedUserColor = new Color { R = 0x51, G = 0x1c, B = 0x74, A = 0xff };
+
+        /// <summary>
+        /// Regular expression that matches the user viewer resolution property.
+        /// </summary>
+        private static readonly Regex UserViewerResolutionRegex = new Regex(@"^(?i)(\d+)x(\d+)$");
+
+        private static readonly Size[] UserViewerSupportedResolutions =
+        {
+            new Size(640, 480), new Size(320, 240), new Size(160, 120),
+            new Size(128, 96), new Size(80, 60)
+        };
+
+        /// <summary>
+        /// Context that allows this stream handler to communicate with its owner.
+        /// </summary>
+        private readonly SensorStreamHandlerContext ownerContext;
+
+        /// <summary>
+        /// Serializable interaction stream message, reused as interaction frames arrive.
+        /// </summary>
+        private readonly InteractionStreamMessage interactionStreamMessage = new InteractionStreamMessage { stream = InteractionStreamName };
+
+        /// <summary>
+        /// Serializable user viewer stream message header, reused as depth frames arrive
+        /// and are colorized into the user viewer image.
+        /// </summary>
+        private readonly ImageHeaderStreamMessage userViewerStreamMessage = new ImageHeaderStreamMessage { stream = UserViewerStreamName };
+
+        /// <summary>
+        /// Ids of users we choose to track.
+        /// </summary>
+        private readonly int[] recommendedUserTrackingIds = new int[2];
+
+        /// <summary>
+        /// A map between user state names and colors that should be used to represent those
+        /// states in user viewer image.
+        /// </summary>
+        private readonly Dictionary<string, int> userViewerUserColors = new Dictionary<string, int>();
+
+        /// <summary>
+        /// Width of user viewer image.
+        /// </summary>
+        private readonly UserViewerColorizer userViewerColorizer = new UserViewerColorizer(UserViewerDefaultWidth, UserViewerDefaultHeight);
+
+        /// <summary>
+        /// User state manager.
+        /// </summary>
+        private readonly IUserStateManager userStateManager = new DefaultUserStateManager();
+
+        /// <summary>
+        /// Sensor providing data to interaction stream.
+        /// </summary>
+        private KinectSensor sensor;
+
+        /// <summary>
+        /// Entry point for interaction stream functionality.
+        /// </summary>
+        private InteractionStream interactionStream;
+
+        /// <summary>
+        /// Intermediate storage for the user information received from interaction stream.
+        /// </summary>
+        private UserInfo[] userInfos;
+
+        /// <summary>
+        /// true if interaction stream is enabled.
+        /// </summary>
+        private bool interactionIsEnabled;
+
+        /// <summary>
+        /// true if user viewer stream is enabled.
+        /// </summary>
+        private bool userViewerIsEnabled;
+
+        /// <summary>
+        /// Default color for users in user viewer image, in 32-bit RGBA format.
+        /// </summary>
+        private int userViewerDefaultUserColor;
+
+        /// <summary>
+        /// Keep track if we're in the middle of processing an interaction frame.
+        /// </summary>
+        private bool isProcessingInteractionFrame;
+
+        /// <summary>
+        /// Keep track if we're in the middle of processing a user viewer image.
+        /// </summary>
+        private bool isProcessingUserViewerImage;
+
+        /// <summary>
+        /// Channel used to perform remote procedure calls regarding IInteractionClient state.
+        /// </summary>
+        private WebSocketRpcChannel clientRpcChannel;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="InteractionStreamHandler"/> class
+        /// and associates it with a context that allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="ownerContext">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        internal InteractionStreamHandler(SensorStreamHandlerContext ownerContext)
+        {
+            this.userViewerDefaultUserColor = GetRgbaColorInt(UserViewerDefaultDefaultUserColor);
+            this.userViewerUserColors[DefaultUserStateManager.TrackedStateName] = GetRgbaColorInt(UserViewerDefaultTrackedUserColor);
+            this.userViewerUserColors[DefaultUserStateManager.EngagedStateName] = GetRgbaColorInt(UserViewerDefaultEngagedUserColor);
+            
+            this.ownerContext = ownerContext;
+            this.userStateManager.UserStateChanged += this.OnUserStateChanged;
+
+            this.AddStreamConfiguration(InteractionStreamName, new StreamConfiguration(this.GetInteractionStreamProperties, this.SetInteractionStreamProperty));
+            this.AddStreamConfiguration(UserViewerStreamName, new StreamConfiguration(this.GetUserViewerStreamProperties, this.SetUserViewerStreamProperty));
+        }
+
+        /// <summary>
+        /// True if we should process interaction data fed into interaction stream and user state manager.
+        /// False otherwise.
+        /// </summary>
+        private bool ShouldProcessInteractionData
+        {
+            get
+            {
+                // Check for a null userInfos since we may still get posted events from
+                // the stream after we have unregistered our event handler and deleted
+                // our buffers.
+                // Also we process interaction data even if only user viewer stream is
+                // enabled since data is used by IUserStateManager and UserViewerColorizer
+                // as well as by clients listening directly to interaction stream.
+                return (this.userInfos != null) && (this.interactionIsEnabled || this.userViewerIsEnabled);
+            }
+        }
+
+        /// <summary>
+        /// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        public override void OnSensorChanged(KinectSensor newSensor)
+        {
+            if (this.sensor != null)
+            {
+                try
+                {
+                    this.interactionStream.InteractionFrameReady -= this.InteractionFrameReadyAsync;
+                    this.interactionStream.Dispose();
+                    this.interactionStream = null;
+
+                    this.sensor.SkeletonStream.AppChoosesSkeletons = false;
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+
+                this.userInfos = null;
+            }
+
+            this.sensor = newSensor;
+
+            if (newSensor != null)
+            {
+                try
+                {
+                    this.interactionStream = new InteractionStream(newSensor, this);
+                    this.interactionStream.InteractionFrameReady += this.InteractionFrameReadyAsync;
+
+                    this.sensor.SkeletonStream.AppChoosesSkeletons = true;
+
+                    this.userInfos = new UserInfo[InteractionFrame.UserInfoArrayLength];
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+            }
+
+            this.userStateManager.Reset();
+            this.userViewerColorizer.ResetColorLookupTable();
+        }
+
+        /// <summary>
+        /// Process data from one Kinect depth frame.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        public override void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
+        {
+            if (depthData == null)
+            {
+                throw new ArgumentNullException("depthData");
+            }
+
+            if (depthFrame == null)
+            {
+                throw new ArgumentNullException("depthFrame");
+            }
+
+            if (this.ShouldProcessInteractionData)
+            {
+                this.interactionStream.ProcessDepth(depthData, depthFrame.Timestamp);
+            }
+
+            this.ProcessUserViewerImageAsync(depthData, depthFrame);
+        }
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        public override void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
+        {
+            if (skeletons == null)
+            {
+                throw new ArgumentNullException("skeletons");
+            }
+
+            if (skeletonFrame == null)
+            {
+                throw new ArgumentNullException("skeletonFrame");
+            }
+
+            if (this.ShouldProcessInteractionData)
+            {
+                this.interactionStream.ProcessSkeleton(skeletons, this.sensor.AccelerometerGetCurrentReading(), skeletonFrame.Timestamp);
+            }
+
+            this.userStateManager.ChooseTrackedUsers(skeletons, skeletonFrame.Timestamp, this.recommendedUserTrackingIds);
+
+            try
+            {
+                this.sensor.SkeletonStream.ChooseSkeletons(
+                    this.recommendedUserTrackingIds[0], this.recommendedUserTrackingIds[1]);
+            }
+            catch (InvalidOperationException)
+            {
+                // KinectSensor might enter an invalid state while choosing skeletons.
+                // E.g.: sensor might be abruptly unplugged.
+            }
+        }
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the stream name associated with this sensor stream
+        /// handler in the stream handler owner.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public override Task HandleRequestAsync(string streamName, HttpListenerContext requestContext, string subpath)
+        {
+            if (streamName == null)
+            {
+                throw new ArgumentNullException("streamName");
+            }
+
+            if (requestContext == null)
+            {
+                throw new ArgumentNullException("requestContext");
+            }
+
+            if (subpath == null)
+            {
+                throw new ArgumentNullException("subpath");
+            }
+
+            if (!InteractionStreamName.Equals(streamName))
+            {
+                // Only supported endpoints are related to interaction stream
+                KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.NotFound);
+                return SharedConstants.EmptyCompletedTask;
+            }
+
+            var splitPath = KinectRequestHandler.SplitUriSubpath(subpath);
+
+            if (splitPath == null)
+            {
+                KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.NotFound);
+                return SharedConstants.EmptyCompletedTask;
+            }
+
+            var pathComponent = splitPath.Item1;
+            switch (pathComponent)
+            {
+            case ClientUriSubpath:
+                // Only support one client at any one time
+                if (this.clientRpcChannel != null)
+                {
+                    if (this.clientRpcChannel.CheckConnectionStatus())
+                    {
+                        KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.Conflict);
+                        return SharedConstants.EmptyCompletedTask;
+                    }
+
+                    this.clientRpcChannel = null;
+                }
+
+                WebSocketRpcChannel.TryOpenAsync(
+                    requestContext,
+                    channel =>
+                        {
+                            // Check again in case another request came in before connection was established
+                            if (this.clientRpcChannel != null)
+                            {
+                                channel.Dispose();
+                            }
+
+                            this.clientRpcChannel = channel;
+                        },
+                    channel =>
+                        {
+                            // Only forget the current channel if it matches the channel being closed
+                            if (this.clientRpcChannel == channel)
+                            {
+                                this.clientRpcChannel = null;
+                            }
+                        });
+
+                break;
+
+            default:
+                KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.NotFound);
+                break;
+            }
+
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Cancel all pending operations.
+        /// </summary>
+        public override void Cancel()
+        {
+            if (this.clientRpcChannel != null)
+            {
+                this.clientRpcChannel.Cancel();
+            }
+        }
+
+        /// <summary>
+        /// Lets handler know that it should clean up resources associated with sensor stream
+        /// handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public override async Task UninitializeAsync()
+        {
+            if (this.clientRpcChannel != null)
+            {
+                await this.clientRpcChannel.CloseAsync();
+            }
+        }
+
+        /// <summary>
+        /// Gets interaction information available for a specified location in UI.
+        /// </summary>
+        /// <param name="skeletonTrackingId">
+        /// The skeleton tracking ID for which interaction information is being retrieved.
+        /// </param>
+        /// <param name="handType">
+        /// The hand type for which interaction information is being retrieved.
+        /// </param>
+        /// <param name="x">
+        /// X-coordinate of UI location for which interaction information is being retrieved.
+        /// 0.0 corresponds to left edge of interaction region and 1.0 corresponds to right edge
+        /// of interaction region.
+        /// </param>
+        /// <param name="y">
+        /// Y-coordinate of UI location for which interaction information is being retrieved.
+        /// 0.0 corresponds to top edge of interaction region and 1.0 corresponds to bottom edge
+        /// of interaction region.
+        /// </param>
+        /// <returns>
+        /// An <see cref="InteractionInfo"/> object instance.
+        /// </returns>
+        public InteractionInfo GetInteractionInfoAtLocation(int skeletonTrackingId, InteractionHandType handType, double x, double y)
+        {
+            var interactionInfo = new InteractionInfo { IsPressTarget = false, IsGripTarget = false };
+
+            if (this.interactionIsEnabled && (this.clientRpcChannel != null))
+            {
+                var result = this.clientRpcChannel.CallFunction<InteractionStreamHitTestInfo>("getInteractionInfoAtLocation", skeletonTrackingId, handType.ToString(), x, y);
+                if (result.Success)
+                {
+                    interactionInfo.IsGripTarget = result.Result.isGripTarget;
+                    interactionInfo.IsPressTarget = result.Result.isPressTarget;
+                    var elementId = result.Result.pressTargetControlId;
+                    interactionInfo.PressTargetControlId = (elementId != null) ? elementId.GetHashCode() : 0;
+                    interactionInfo.PressAttractionPointX = result.Result.pressAttractionPointX;
+                    interactionInfo.PressAttractionPointY = result.Result.pressAttractionPointY;
+                }
+            }
+
+            return interactionInfo;
+        }
+
+        /// <summary>
+        /// Converts a color into the corresponding 32-bit integer RGBA representation.
+        /// </summary>
+        /// <param name="color">
+        /// Color to convert
+        /// </param>
+        /// <returns>
+        /// 32-bit integer RGBA representation of color.
+        /// </returns>
+        internal static int GetRgbaColorInt(Color color)
+        {
+            return (color.A << 24) | (color.B << 16) | (color.G << 8) | color.R;
+        }
+
+        /// <summary>
+        /// Event handler for InteractionStream's InteractionFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="e">event arguments</param>
+        internal async void InteractionFrameReadyAsync(object sender, InteractionFrameReadyEventArgs e)
+        {
+            if (!this.ShouldProcessInteractionData)
+            {
+                return;
+            }
+
+            if (this.isProcessingInteractionFrame)
+            {
+                // Re-entered InteractionFrameReadyAsync while a previous frame is already being processed.
+                // Just ignore new frames until the current one finishes processing.
+                return;
+            }
+
+            this.isProcessingInteractionFrame = true;
+
+            try
+            {
+                bool haveFrameData = false;
+
+                using (var interactionFrame = e.OpenInteractionFrame())
+                {
+                    // Even though we checked value of userInfos above as part of
+                    // ShouldProcessInteractionData check, callbacks happening while
+                    // opening an interaction frame might have invalidated it, so we
+                    // check value again. 
+                    if ((interactionFrame != null) && (this.userInfos != null))
+                    {
+                        // Copy interaction frame data so we can dispose interaction frame
+                        // right away, even if data processing/event handling takes a while.
+                        interactionFrame.CopyInteractionDataTo(this.userInfos);
+                        this.interactionStreamMessage.timestamp = interactionFrame.Timestamp;
+                        haveFrameData = true;
+                    }
+                }
+
+                if (haveFrameData)
+                {
+                    this.userStateManager.UpdateUserInformation(this.userInfos, this.interactionStreamMessage.timestamp);
+                    this.userViewerColorizer.UpdateColorLookupTable(this.userInfos, this.userViewerDefaultUserColor, this.userStateManager.UserStates, this.userViewerUserColors);
+
+                    if (this.interactionIsEnabled)
+                    {
+                        this.interactionStreamMessage.UpdateHandPointers(this.userInfos, this.userStateManager.PrimaryUserTrackingId);
+                        await this.ownerContext.SendStreamMessageAsync(this.interactionStreamMessage);
+                    }
+                }
+            }
+            finally
+            {
+                this.isProcessingInteractionFrame = false;
+            }
+        }
+
+        /// <summary>
+        /// Gets all interaction stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        internal void GetInteractionStreamProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.interactionIsEnabled);
+            propertyMap.Add(InteractionPrimaryUserPropertyName, this.userStateManager.PrimaryUserTrackingId);
+            propertyMap.Add(InteractionUserStatesPropertyName, DefaultUserStateManager.GetStateMappingEntryArray(this.userStateManager.UserStates));
+        }
+
+        /// <summary>
+        /// Set an interaction stream property value.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        internal string SetInteractionStreamProperty(string propertyName, object propertyValue)
+        {
+            bool recognized = true;
+
+            if (propertyValue == null)
+            {
+                // None of the interaction stream properties accept a null value
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            try
+            {
+                switch (propertyName)
+                {
+                case KinectRequestHandler.EnabledPropertyName:
+                    this.interactionIsEnabled = (bool)propertyValue;
+                    break;
+
+                default:
+                    recognized = false;
+                    break;
+                }
+
+                if (!recognized)
+                {
+                    return Properties.Resources.PropertyNameUnrecognized;
+                }
+            }
+            catch (InvalidCastException)
+            {
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Gets all user viewer stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        internal void GetUserViewerStreamProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.userViewerIsEnabled);
+            propertyMap.Add(UserViewerResolutionPropertyName, string.Format(CultureInfo.InvariantCulture, @"{0}x{1}", this.userViewerColorizer.Width, this.userViewerColorizer.Height));
+            propertyMap.Add(UserViewerDefaultUserColorPropertyName, this.userViewerDefaultUserColor);
+            propertyMap.Add(UserViewerUserColorsPropertyName, this.userViewerUserColors);
+        }
+
+        /// <summary>
+        /// Set a user viewer stream property.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        internal string SetUserViewerStreamProperty(string propertyName, object propertyValue)
+        {
+            bool recognized = true;
+
+            try
+            {
+                switch (propertyName)
+                {
+                case KinectRequestHandler.EnabledPropertyName:
+                    this.userViewerIsEnabled = (bool)propertyValue;
+                    break;
+
+                case UserViewerResolutionPropertyName:
+                    if (propertyValue == null)
+                    {
+                        return Properties.Resources.PropertyValueInvalidFormat;
+                    }
+
+                    var match = UserViewerResolutionRegex.Match((string)propertyValue);
+                    if (!match.Success || (match.Groups.Count != 3))
+                    {
+                        return Properties.Resources.PropertyValueInvalidFormat;
+                    }
+
+                    int width = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
+                    int height = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
+
+                    if (!IsSupportedUserViewerResolution(width, height))
+                    {
+                        return Properties.Resources.PropertyValueUnsupportedResolution;
+                    }
+
+                    this.userViewerColorizer.SetResolution(width, height);
+                    break;
+
+                case UserViewerDefaultUserColorPropertyName:
+                    this.userViewerDefaultUserColor = (int)propertyValue;
+
+                    this.UpdateColorizerLookupTable();
+                    break;
+
+                case UserViewerUserColorsPropertyName:
+                    if (propertyValue == null)
+                    {
+                        // Null values just clear the set of user colors
+                        this.userViewerUserColors.Clear();
+                        break;
+                    }
+
+                    var userColors = (Dictionary<string, object>)propertyValue;
+
+                    // Verify that all dictionary values are integers
+                    bool allIntegers = userColors.Values.Select(color => color as int?).All(colorInt => colorInt != null);
+                    if (!allIntegers)
+                    {
+                        return Properties.Resources.PropertyValueInvalidFormat;
+                    }
+
+                    // If property value specified is compatible, copy values over
+                    this.userViewerUserColors.Clear();
+                    foreach (var entry in userColors)
+                    {
+                        this.userViewerUserColors.Add(entry.Key, (int)entry.Value);
+                    }
+
+                    this.UpdateColorizerLookupTable();
+                    break;
+
+                default:
+                    recognized = false;
+                    break;
+                }
+
+                if (!recognized)
+                {
+                    return Properties.Resources.PropertyNameUnrecognized;
+                }
+            }
+            catch (InvalidCastException)
+            {
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+            catch (NullReferenceException)
+            {
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            return null;
+        }
+
+        /// <summary>
+        /// Determine if the specified resolution is supported for user viewer images.
+        /// </summary>
+        /// <param name="width">
+        /// Image width.
+        /// </param>
+        /// <param name="height">
+        /// Image height.
+        /// </param>
+        /// <returns>
+        /// True if specified resolution is supported, false otherwise.
+        /// </returns>
+        private static bool IsSupportedUserViewerResolution(int width, int height)
+        {
+            return UserViewerSupportedResolutions.Any(resolution => ((int)resolution.Width == width) && ((int)resolution.Height == height));
+        }
+
+        /// <summary>
+        /// Handler for IUserStateManager.UserStateChanged event.
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="e">event arguments</param>
+        private async void OnUserStateChanged(object sender, UserStateChangedEventArgs e)
+        {
+            if (this.interactionIsEnabled)
+            {
+                // If enabled, forward all user state events to client
+                await this.ownerContext.SendEventMessageAsync(e.Message);
+            }
+        }
+
+        /// <summary>
+        /// Update colorizer lookup table from color-related configuration.
+        /// </summary>
+        private void UpdateColorizerLookupTable()
+        {
+            if (this.ShouldProcessInteractionData)
+            {
+                this.userViewerColorizer.UpdateColorLookupTable(
+                    this.userInfos, this.userViewerDefaultUserColor, this.userStateManager.UserStates, this.userViewerUserColors);
+            }
+        }
+
+        /// <summary>
+        /// Process depth data to obtain user viewer image.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        private async void ProcessUserViewerImageAsync(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
+        {
+            if (this.userViewerIsEnabled)
+            {
+                if (this.isProcessingUserViewerImage)
+                {
+                    // Re-entered ProcessUserViewerImageAsync while a previous image is already being processed.
+                    // Just ignore new depth frames until the current one finishes processing.
+                    return;
+                }
+
+                this.isProcessingUserViewerImage = true;
+
+                try
+                {
+                    this.userViewerColorizer.ColorizeDepthPixels(depthData, depthFrame.Width, depthFrame.Height);
+                    this.userViewerStreamMessage.timestamp = depthFrame.Timestamp;
+                    this.userViewerStreamMessage.width = this.userViewerColorizer.Width;
+                    this.userViewerStreamMessage.height = this.userViewerColorizer.Height;
+                    this.userViewerStreamMessage.bufferLength = this.userViewerColorizer.Buffer.Length;
+
+                    await this.ownerContext.SendTwoPartStreamMessageAsync(this.userViewerStreamMessage, this.userViewerColorizer.Buffer);
+                }
+                finally
+                {
+                    this.isProcessingUserViewerImage = false;
+                }
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandler.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandler.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandler.cs	(revision 28897)
@@ -0,0 +1,834 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="KinectRequestHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Collections.ObjectModel;
+    using System.Diagnostics;
+    using System.Globalization;
+    using System.Net;
+    using System.Net.WebSockets;
+    using System.Runtime.Serialization;
+    using System.Threading.Tasks;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Samples.Kinect.Webserver;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of IHttpRequestHandler used to handle communication to/from
+    /// a Kinect sensor represented by a specified KinectSensorChooser.
+    /// </summary>
+    public class KinectRequestHandler : IHttpRequestHandler
+    {
+        /// <summary>
+        /// Property name used to return success status to client.
+        /// </summary>
+        public const string SuccessPropertyName = "success";
+
+        /// <summary>
+        /// Property name used to return a set of property names that encountered errors to client.
+        /// </summary>
+        public const string ErrorsPropertyName = "errors";
+
+        /// <summary>
+        /// Property name used to represent "enabled" property of a stream.
+        /// </summary>
+        public const string EnabledPropertyName = "enabled";
+
+        /// <summary>
+        /// Sub path for REST endpoint owned by this handler.
+        /// </summary>
+        public const string StateUriSubpath = "STATE";
+
+        /// <summary>
+        /// Sub path for stream data web-socket endpoint owned by this handler.
+        /// </summary>
+        public const string StreamUriSubpath = "STREAM";
+
+        /// <summary>
+        /// Sub path for stream data web-socket endpoint owned by this handler.
+        /// </summary>
+        public const string EventsUriSubpath = "EVENTS";
+
+        /// <summary>
+        /// MIME type name for JSON data.
+        /// </summary>
+        internal const string JsonContentType = "application/json";
+
+        /// <summary>
+        /// Reserved names that streams can't have.
+        /// </summary>
+        private static readonly HashSet<string> ReservedNames = new HashSet<string>
+                                                                {
+                                                                    SuccessPropertyName.ToUpperInvariant(),
+                                                                    ErrorsPropertyName.ToUpperInvariant(),
+                                                                    StreamUriSubpath.ToUpperInvariant(),
+                                                                    EventsUriSubpath.ToUpperInvariant(),
+                                                                    StateUriSubpath.ToUpperInvariant()
+                                                                };
+
+        /// <summary>
+        /// Sensor chooser used to obtain a KinectSensor.
+        /// </summary>
+        private readonly KinectSensorChooser sensorChooser;
+
+        /// <summary>
+        /// Array of sensor stream handlers used to process kinect data and deliver
+        /// a data streams ready for web consumption.
+        /// </summary>
+        private readonly ISensorStreamHandler[] streamHandlers;
+
+        /// <summary>
+        /// Map of stream handler names to sensor stream handler objects
+        /// </summary>
+        private readonly Dictionary<string, ISensorStreamHandler> streamHandlerMap = new Dictionary<string, ISensorStreamHandler>();
+
+        /// <summary>
+        /// Map of uri names (expected to be case-insensitive) corresponding to a stream, to
+        /// their case-sensitive names used in JSON communications.
+        /// </summary>
+        private readonly Dictionary<string, string> uriName2StreamNameMap = new Dictionary<string, string>();
+
+        /// <summary>
+        /// Channel used to send event messages that are part of main data stream.
+        /// </summary>
+        private readonly List<WebSocketEventChannel> streamChannels = new List<WebSocketEventChannel>();
+
+        /// <summary>
+        /// Channel used to send event messages that are part of an events stream.
+        /// </summary>
+        private readonly List<WebSocketEventChannel> eventsChannels = new List<WebSocketEventChannel>();
+
+        /// <summary>
+        /// Intermediate storage for the skeleton data received from the Kinect sensor.
+        /// </summary>
+        private Skeleton[] skeletons;
+
+        /// <summary>
+        /// Kinect sensor currently associated with this request handler.
+        /// </summary>
+        private KinectSensor sensor;
+
+        /// <summary>
+        /// Initializes a new instance of the KinectRequestHandler class.
+        /// </summary>
+        /// <param name="sensorChooser">
+        /// Sensor chooser that will be used to obtain a KinectSensor.
+        /// </param>
+        /// <param name="streamHandlerFactories">
+        /// Collection of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption.
+        /// </param>
+        internal KinectRequestHandler(KinectSensorChooser sensorChooser, Collection<ISensorStreamHandlerFactory> streamHandlerFactories)
+        {
+            this.sensorChooser = sensorChooser;
+            this.streamHandlers = new ISensorStreamHandler[streamHandlerFactories.Count];
+            var streamHandlerContext = new SensorStreamHandlerContext(this.SendStreamMessageAsync, this.SendEventMessageAsync);
+
+            var normalizedNameSet = new HashSet<string>();
+
+            // Associate each of the supported stream names with the corresponding handlers 
+            for (int i = 0; i < streamHandlerFactories.Count; ++i)
+            {
+                var handler = streamHandlerFactories[i].CreateHandler(streamHandlerContext);
+                this.streamHandlers[i] = handler;
+                var names = handler.GetSupportedStreamNames();
+
+                foreach (var name in names)
+                {
+                    if (string.IsNullOrEmpty(name))
+                    {
+                        throw new InvalidOperationException(@"Empty stream names are not supported");
+                    }
+
+                    if (name.IndexOfAny(SharedConstants.UriPathComponentDelimiters) >= 0)
+                    {
+                        throw new InvalidOperationException(@"Stream names can't contain '/' character");
+                    }
+
+                    var normalizedName = name.ToUpperInvariant();
+
+                    if (ReservedNames.Contains(normalizedName))
+                    {
+                        throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "'{0}' is a reserved stream name", normalizedName));
+                    }
+
+                    if (normalizedNameSet.Contains(normalizedName))
+                    {
+                        throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "'{0}' is a duplicate stream name", normalizedName));
+                    }
+
+                    normalizedNameSet.Add(normalizedName);
+
+                    this.uriName2StreamNameMap.Add(normalizedName, name);
+                    this.streamHandlerMap.Add(name, handler);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Prepares handler to start receiving HTTP requests.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        public Task InitializeAsync()
+        {
+            this.OnKinectChanged(this.sensorChooser.Kinect);
+            this.sensorChooser.KinectChanged += this.SensorChooserKinectChanged;
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the URI prefix associated with this request
+        /// handler in the HttpListener.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        public async Task HandleRequestAsync(HttpListenerContext requestContext, string subpath)
+        {
+            if (requestContext == null)
+            {
+                throw new ArgumentNullException("requestContext");
+            }
+
+            if (subpath == null)
+            {
+                throw new ArgumentNullException("subpath");
+            }
+
+            var splitPath = SplitUriSubpath(subpath);
+
+            if (splitPath == null)
+            {
+                CloseResponse(requestContext, HttpStatusCode.NotFound);
+                return;
+            }
+
+            var pathComponent = splitPath.Item1;
+
+            try
+            {
+                switch (pathComponent)
+                {
+                case StateUriSubpath:
+                    await this.HandleStateRequest(requestContext);
+                    break;
+
+                case StreamUriSubpath:
+                    this.HandleStreamRequest(requestContext);
+                    break;
+
+                case EventsUriSubpath:
+                    this.HandleEventRequest(requestContext);
+                    break;
+
+                default:
+                    var remainingSubpath = splitPath.Item2;
+                    if (remainingSubpath == null)
+                    {
+                        CloseResponse(requestContext, HttpStatusCode.NotFound);
+                        return;
+                    }
+
+                    string streamName;
+                    if (!this.uriName2StreamNameMap.TryGetValue(pathComponent, out streamName))
+                    {
+                        CloseResponse(requestContext, HttpStatusCode.NotFound);
+                        return;
+                    }
+
+                    var streamHandler = this.streamHandlerMap[streamName];
+
+                    await streamHandler.HandleRequestAsync(streamName, requestContext, remainingSubpath);
+                    break;
+                }
+            }
+            catch (Exception e)
+            {
+                // If there's any exception while handling a request, return appropriate
+                // error status code rather than propagating exception further.
+                Trace.TraceError("Exception encountered while handling Kinect sensor request:\n{0}", e);
+                CloseResponse(requestContext, HttpStatusCode.InternalServerError);
+            }
+        }
+
+        /// <summary>
+        /// Cancel all pending operations.
+        /// </summary>
+        public void Cancel()
+        {
+            foreach (var channel in this.streamChannels.SafeCopy())
+            {
+                channel.Cancel();
+            }
+
+            foreach (var channel in this.eventsChannels.SafeCopy())
+            {
+                channel.Cancel();
+            }
+
+            foreach (var streamHandler in this.streamHandlers)
+            {
+                streamHandler.Cancel();
+            }
+        }
+
+        /// <summary>
+        /// Lets handler know that no more HTTP requests will be received, so that it can
+        /// clean up resources associated with request handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        public async Task UninitializeAsync()
+        {
+            foreach (var channel in this.streamChannels.SafeCopy())
+            {
+                await channel.CloseAsync();
+            }
+
+            foreach (var channel in this.eventsChannels.SafeCopy())
+            {
+                await channel.CloseAsync();
+            }
+
+            this.OnKinectChanged(null);
+
+            foreach (var handler in this.streamHandlers)
+            {
+                await handler.UninitializeAsync();
+            }
+        }
+
+        /// <summary>
+        /// Close response stream and associate a status code with response.
+        /// </summary>
+        /// <param name="context">
+        /// Context whose response we should close.
+        /// </param>
+        /// <param name="statusCode">
+        /// Status code.
+        /// </param>
+        internal static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode)
+        {
+            try
+            {
+                context.Response.StatusCode = (int)statusCode;
+                context.Response.Close();
+            }
+            catch (HttpListenerException e)
+            {
+                Trace.TraceWarning(
+                    "Problem encountered while sending response for kinect sensor request. Client might have aborted request. Cause: \"{0}\"", e.Message);
+            }
+        }
+
+        /// <summary>
+        /// Splits a URI sub-path into "first path component" and "rest of sub-path".
+        /// </summary>
+        /// <param name="subpath">
+        /// Uri sub-path. Expected to start with "/" character.
+        /// </param>
+        /// <returns>
+        /// <para>
+        /// A tuple containing two elements:
+        /// 1) first sub-path component
+        /// 2) rest of sub-path string
+        /// </para>
+        /// <para>
+        /// May be null if sub-path is badly formed.
+        /// </para>
+        /// </returns>
+        /// <remarks>
+        /// The returned path components will have been normalized to uppercase.
+        /// </remarks>
+        internal static Tuple<string, string> SplitUriSubpath(string subpath)
+        {
+            if (!subpath.StartsWith("/", StringComparison.OrdinalIgnoreCase))
+            {
+                return null;
+            }
+
+            subpath = subpath.Substring(1).ToUpperInvariant();
+            int delimiterIndex = subpath.IndexOfAny(SharedConstants.UriPathComponentDelimiters);
+            var firstComponent = (delimiterIndex < 0) ? subpath : subpath.Substring(0, delimiterIndex);
+            var remainingSubpath = (delimiterIndex < 0) ? null : subpath.Substring(delimiterIndex);
+
+            return new Tuple<string, string>(firstComponent, remainingSubpath);
+        }
+
+        /// <summary>
+        /// Add response headers that mean that response should not be cached.
+        /// </summary>
+        /// <param name="response">
+        /// Http response object.
+        /// </param>
+        /// <remarks>
+        /// This method needs to be called before starting to write the response output stream,
+        /// or the headers meant to be added will be silently ignored, since they can't be sent
+        /// after content is sent.
+        /// </remarks>
+        private static void AddNoCacheHeaders(HttpListenerResponse response)
+        {
+            response.Headers.Add("Cache-Control", "no-cache");
+            response.Headers.Add("Cache-Control", "no-store");
+            response.Headers.Add("Pragma", "no-cache");
+            response.Headers.Add("Expires", "Mon, 1 Jan 1990 00:00:00 GMT");
+        }
+
+        /// <summary>
+        /// Handle Http GET requests for state endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP GET request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task HandleGetStateRequest(HttpListenerContext requestContext)
+        {
+            // Don't cache results of any endpoint requests
+            AddNoCacheHeaders(requestContext.Response);
+            
+            var responseProperties = new Dictionary<string, object>();
+            foreach (var mapEntry in this.streamHandlerMap)
+            {
+                var handlerStatus = mapEntry.Value.GetState(mapEntry.Key);
+                responseProperties.Add(mapEntry.Key, handlerStatus);
+            }
+
+            requestContext.Response.ContentType = JsonContentType;
+            await responseProperties.DictionaryToJsonAsync(requestContext.Response.OutputStream);
+            CloseResponse(requestContext, HttpStatusCode.OK);
+        }
+
+        /// <summary>
+        /// Handle Http POST requests for state endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP POST request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task HandlePostStateRequest(HttpListenerContext requestContext)
+        {
+            // Don't cache results of any endpoint requests
+            AddNoCacheHeaders(requestContext.Response);
+            
+            Dictionary<string, object> requestProperties;
+
+            try
+            {
+                requestProperties = await requestContext.Request.InputStream.DictionaryFromJsonAsync();
+            }
+            catch (SerializationException)
+            {
+                requestProperties = null;
+            }
+
+            if (requestProperties == null)
+            {
+                CloseResponse(requestContext, HttpStatusCode.BadRequest);
+                return;
+            }
+
+            var responseProperties = new Dictionary<string, object>();
+            var errorStreamNames = new List<string>();
+
+            foreach (var requestEntry in requestProperties)
+            {
+                ISensorStreamHandler handler;
+                if (!this.streamHandlerMap.TryGetValue(requestEntry.Key, out handler))
+                {
+                    // Don't process unrecognized handlers
+                    responseProperties.Add(requestEntry.Key, Properties.Resources.StreamNameUnrecognized);
+                    errorStreamNames.Add(requestEntry.Key);
+                    continue;
+                }
+
+                var propertiesToSet = requestEntry.Value as IDictionary<string, object>;
+                if (propertiesToSet == null)
+                {
+                    continue;
+                }
+
+                var propertyErrors = new Dictionary<string, object>();
+                var success = handler.SetState(requestEntry.Key, new ReadOnlyDictionary<string, object>(propertiesToSet), propertyErrors);
+                if (!success)
+                {
+                    responseProperties.Add(requestEntry.Key, propertyErrors);
+                    errorStreamNames.Add(requestEntry.Key);
+                }
+            }
+
+            if (errorStreamNames.Count == 0)
+            {
+                responseProperties.Add(SuccessPropertyName, true);
+            }
+            else
+            {
+                // The only properties returned other than the "success" property are to indicate error,
+                // so if there are other properties present it means that we've encountered at least
+                // one error while trying to change state of the streams.
+                responseProperties.Add(SuccessPropertyName, false);
+                responseProperties.Add(ErrorsPropertyName, errorStreamNames.ToArray());
+            }
+
+            requestContext.Response.ContentType = JsonContentType;
+            await responseProperties.DictionaryToJsonAsync(requestContext.Response.OutputStream);
+            CloseResponse(requestContext, HttpStatusCode.OK);
+        }
+
+        /// <summary>
+        /// Handle Http requests for state endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task HandleStateRequest(HttpListenerContext requestContext)
+        {
+            const string AllowHeader = "Allow";
+            const string AllowedMethods = "GET, POST, OPTIONS";
+
+            switch (requestContext.Request.HttpMethod)
+            {
+                case "GET":
+                    await this.HandleGetStateRequest(requestContext);
+                    break;
+
+                case "POST":
+                    await this.HandlePostStateRequest(requestContext);
+                    break;
+
+                case "OPTIONS":
+                    requestContext.Response.Headers.Set(AllowHeader, AllowedMethods);
+                    CloseResponse(requestContext, HttpStatusCode.OK);
+                    break;
+
+                default:
+                    requestContext.Response.Headers.Set(AllowHeader, AllowedMethods);
+                    CloseResponse(requestContext, HttpStatusCode.MethodNotAllowed);
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// Handle Http requests for stream endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        private void HandleStreamRequest(HttpListenerContext requestContext)
+        {
+            WebSocketEventChannel.TryOpenAsync(
+                requestContext,
+                channel => this.streamChannels.Add(channel),
+                channel => this.streamChannels.Remove(channel));
+        }
+
+        /// <summary>
+        /// Handle Http requests for event endpoint.
+        /// </summary>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, and which will also contain associated
+        /// response upon return.
+        /// </param>
+        private void HandleEventRequest(HttpListenerContext requestContext)
+        {
+            WebSocketEventChannel.TryOpenAsync(
+                requestContext,
+                channel => this.eventsChannels.Add(channel),
+                channel => this.eventsChannels.Remove(channel));
+        }
+
+        /// <summary>
+        /// Asynchronously send stream message to client(s) of stream handler.
+        /// </summary>
+        /// <param name="message">
+        /// Stream message to send.
+        /// </param>
+        /// <param name="binaryPayload">
+        /// Binary payload of stream message. May be null if message does not require a binary
+        /// payload.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task SendStreamMessageAsync(StreamMessage message, byte[] binaryPayload)
+        {
+            var webSocketMessage = message.ToTextMessage();
+
+            foreach (var channel in this.streamChannels.SafeCopy())
+            {
+                if (channel == null)
+                {
+                    break;
+                }
+
+                if (binaryPayload != null)
+                {
+                    // If binary payload is non-null, send two-part stream message, with the first
+                    // part acting as a message header.
+                    await
+                            channel.SendMessagesAsync(
+                            webSocketMessage, new WebSocketMessage(new ArraySegment<byte>(binaryPayload), WebSocketMessageType.Binary));
+                    continue;
+                }
+
+                await channel.SendMessagesAsync(webSocketMessage);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously send event message to client(s) of stream handler.
+        /// </summary>
+        /// <param name="message">
+        /// Event message to send.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task SendEventMessageAsync(EventMessage message)
+        {
+            foreach (var channel in this.eventsChannels.SafeCopy())
+            {
+                await channel.SendMessagesAsync(message.ToTextMessage());
+            }
+        }
+
+        /// <summary>
+        /// Responds to KinectSensor changes.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New sensor associated with this KinectRequestHandler.
+        /// </param>
+        private void OnKinectChanged(KinectSensor newSensor)
+        {
+            if (this.sensor != null)
+            {
+                try
+                {
+                    this.sensor.ColorFrameReady -= this.SensorColorFrameReady;
+                    this.sensor.DepthFrameReady -= this.SensorDepthFrameReady;
+                    this.sensor.SkeletonFrameReady -= this.SensorSkeletonFrameReady;
+
+                    this.sensor.DepthStream.Range = DepthRange.Default;
+                    this.sensor.SkeletonStream.EnableTrackingInNearRange = false;
+                    this.sensor.DepthStream.Disable();
+                    this.sensor.SkeletonStream.Disable();
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+
+                this.sensor = null;
+                this.skeletons = null;
+            }
+
+            if (newSensor != null)
+            {
+                // Allocate space to put the skeleton and interaction data we'll receive
+                this.sensor = newSensor;
+
+                try
+                {
+                    newSensor.DepthStream.Enable(DepthImageFormat.Resolution640x480Fps30);
+                    newSensor.SkeletonStream.Enable();
+
+                    try
+                    {
+                        newSensor.DepthStream.Range = DepthRange.Near;
+                        newSensor.SkeletonStream.EnableTrackingInNearRange = true;
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // Non Kinect for Windows devices do not support Near mode, so reset back to default mode.
+                        newSensor.DepthStream.Range = DepthRange.Default;
+                        newSensor.SkeletonStream.EnableTrackingInNearRange = false;
+                    }
+
+                    this.skeletons = new Skeleton[newSensor.SkeletonStream.FrameSkeletonArrayLength];
+
+                    newSensor.ColorFrameReady += this.SensorColorFrameReady;
+                    newSensor.DepthFrameReady += this.SensorDepthFrameReady;
+                    newSensor.SkeletonFrameReady += this.SensorSkeletonFrameReady;
+                }
+                catch (InvalidOperationException)
+                {
+                    // KinectSensor might enter an invalid state while enabling/disabling streams or stream features.
+                    // E.g.: sensor might be abruptly unplugged.
+                }
+            }
+
+            foreach (var handler in this.streamHandlers)
+            {
+                handler.OnSensorChanged(newSensor);
+            }
+        }
+
+        /// <summary>
+        /// Handler for KinectSensorChooser's KinectChanged event.
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="args">event arguments</param>
+        private void SensorChooserKinectChanged(object sender, KinectChangedEventArgs args)
+        {
+            this.OnKinectChanged(args.NewSensor);
+        }
+        
+        /// <summary>
+        /// Handler for the Kinect sensor's ColorFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="colorImageFrameReadyEventArgs">event arguments</param>
+        private void SensorColorFrameReady(object sender, ColorImageFrameReadyEventArgs colorImageFrameReadyEventArgs)
+        {
+            // Even though we un-register all our event handlers when the sensor
+            // changes, there may still be an event for the old sensor in the queue
+            // due to the way the KinectSensor delivers events.  So check again here.
+            if (this.sensor != sender)
+            {
+                return;
+            }
+
+            using (var colorFrame = colorImageFrameReadyEventArgs.OpenColorImageFrame())
+            {
+                if (null != colorFrame)
+                {
+                    try
+                    {
+                        // Hand data to each handler to be processed
+                        foreach (var handler in this.streamHandlers)
+                        {
+                            handler.ProcessColor(colorFrame.GetRawPixelData(), colorFrame);
+                        }
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // ColorFrame functions may throw when the sensor gets
+                        // into a bad state.  Ignore the frame in that case.
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handler for the Kinect sensor's DepthFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="depthImageFrameReadyEventArgs">event arguments</param>
+        private void SensorDepthFrameReady(object sender, DepthImageFrameReadyEventArgs depthImageFrameReadyEventArgs)
+        {
+            // Even though we un-register all our event handlers when the sensor
+            // changes, there may still be an event for the old sensor in the queue
+            // due to the way the KinectSensor delivers events.  So check again here.
+            if (this.sensor != sender)
+            {
+                return;
+            }
+
+            using (var depthFrame = depthImageFrameReadyEventArgs.OpenDepthImageFrame())
+            {
+                if (null != depthFrame)
+                {
+                    var depthBuffer = depthFrame.GetRawPixelData();
+
+                    try
+                    {
+                        // Hand data to each handler to be processed
+                        foreach (var handler in this.streamHandlers)
+                        {
+                            handler.ProcessDepth(depthBuffer, depthFrame);
+                        }
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // DepthFrame functions may throw when the sensor gets
+                        // into a bad state.  Ignore the frame in that case.
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Handler for the Kinect sensor's SkeletonFrameReady event
+        /// </summary>
+        /// <param name="sender">object sending the event</param>
+        /// <param name="skeletonFrameReadyEventArgs">event arguments</param>
+        private void SensorSkeletonFrameReady(object sender, SkeletonFrameReadyEventArgs skeletonFrameReadyEventArgs)
+        {
+            // Even though we un-register all our event handlers when the sensor
+            // changes, there may still be an event for the old sensor in the queue
+            // due to the way the KinectSensor delivers events.  So check again here.
+            if (this.sensor != sender)
+            {
+                return;
+            }
+
+            using (SkeletonFrame skeletonFrame = skeletonFrameReadyEventArgs.OpenSkeletonFrame())
+            {
+                if (null != skeletonFrame)
+                {
+                    try
+                    {
+                        // Copy the skeleton data from the frame to an array used for temporary storage
+                        skeletonFrame.CopySkeletonDataTo(this.skeletons);
+
+                        foreach (var handler in this.streamHandlers)
+                        {
+                            handler.ProcessSkeleton(this.skeletons, skeletonFrame);
+                        }
+                    }
+                    catch (InvalidOperationException)
+                    {
+                        // SkeletonFrame functions may throw when the sensor gets
+                        // into a bad state.  Ignore the frame in that case.
+                    }
+                }
+            }
+        }
+    }
+
+    /// <summary>
+    /// Helper class that adds a SafeCopy extension method to a List of WebSocketEventChannel.
+    /// </summary>
+    internal static class ChannelListHelper
+    {
+        /// <summary>
+        /// Safely makes a copy of the list of stream channels.
+        /// </summary>
+        /// <returns>Array containing the stream channels.</returns>
+        public static IEnumerable<WebSocketEventChannel> SafeCopy(this List<WebSocketEventChannel> list)
+        {
+            var channels = new WebSocketEventChannel[list.Count];
+            list.CopyTo(channels);
+            return channels;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandlerFactory.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandlerFactory.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/KinectRequestHandlerFactory.cs	(revision 28897)
@@ -0,0 +1,98 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="KinectRequestHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System.Collections.ObjectModel;
+
+    using Microsoft.Kinect.Toolkit;
+    using Microsoft.Samples.Kinect.Webserver;
+
+    /// <summary>
+    /// Implementation of IHttpRequestHandlerFactory used to create instances of
+    /// <see cref="KinectRequestHandler"/> objects.
+    /// </summary>
+    public class KinectRequestHandlerFactory : IHttpRequestHandlerFactory
+    {
+        /// <summary>
+        /// Sensor chooser used to obtain a KinectSensor.
+        /// </summary>
+        private readonly KinectSensorChooser sensorChooser;
+
+        /// <summary>
+        /// Collection of sensor stream handler factories used to process kinect data and deliver
+        /// a data streams ready for web consumption.
+        /// </summary>
+        private readonly Collection<ISensorStreamHandlerFactory> streamHandlerFactories;
+
+        /// <summary>
+        /// Initializes a new instance of the KinectRequestHandlerFactory class.
+        /// </summary>
+        /// <param name="sensorChooser">
+        /// Sensor chooser that will be used to obtain a KinectSensor.
+        /// </param>
+        /// <remarks>
+        /// Default set of sensor stream handler factories will be used.
+        /// </remarks>
+        public KinectRequestHandlerFactory(KinectSensorChooser sensorChooser)
+        {
+            this.sensorChooser = sensorChooser;
+            this.streamHandlerFactories = CreateDefaultStreamHandlerFactories();
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the KinectRequestHandlerFactory class.
+        /// </summary>
+        /// <param name="sensorChooser">
+        /// Sensor chooser that will be used to obtain a KinectSensor.
+        /// </param>
+        /// <param name="streamHandlerFactories">
+        /// Collection of stream handler factories to be used to process kinect data and deliver
+        /// data streams ready for web consumption.
+        /// </param>
+        public KinectRequestHandlerFactory(KinectSensorChooser sensorChooser, Collection<ISensorStreamHandlerFactory> streamHandlerFactories)
+        {
+            this.sensorChooser = sensorChooser;
+            this.streamHandlerFactories = streamHandlerFactories;
+        }
+
+        /// <summary>
+        /// Create collection of default stream handler factories.
+        /// </summary>
+        /// <returns>
+        /// Collection containing default stream handler factories.
+        /// </returns>
+        public static Collection<ISensorStreamHandlerFactory> CreateDefaultStreamHandlerFactories()
+        {
+            var streamHandlerTypes = new[]
+            {
+                StreamHandlerType.Interaction,
+                StreamHandlerType.Skeleton,
+                StreamHandlerType.BackgroundRemoval,
+                StreamHandlerType.SensorStatus
+            };
+
+            var factoryCollection = new Collection<ISensorStreamHandlerFactory>();
+            foreach (var type in streamHandlerTypes)
+            {
+                factoryCollection.Add(new SensorStreamHandlerFactory(type));
+            }
+
+            return factoryCollection;
+        }
+
+        /// <summary>
+        /// Creates a request handler object.
+        /// </summary>
+        /// <returns>
+        /// A new <see cref="IHttpRequestHandler"/> instance.
+        /// </returns>
+        public IHttpRequestHandler CreateHandler()
+        {
+            return new KinectRequestHandler(this.sensorChooser, this.streamHandlerFactories);
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStatusStreamHandler.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStatusStreamHandler.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStatusStreamHandler.cs	(revision 28897)
@@ -0,0 +1,131 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SensorStatusStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System.Collections.Generic;
+
+    using Microsoft.Kinect;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of ISensorStatusStreamHandler that exposes sensor status events
+    /// </summary>
+    public class SensorStatusStreamHandler : SensorStreamHandlerBase
+    {
+        /// <summary>
+        /// JSON name of sensor stream.
+        /// </summary>
+        internal const string SensorStreamName = "sensorStatus";
+
+        /// <summary>
+        /// JSON name of sensor event category.
+        /// </summary>
+        internal const string SensorStatusEventCategory = "sensorStatus";
+
+        /// <summary>
+        /// JSON name of sensor event type.
+        /// </summary>
+        internal const string SensorStatusEventType = "statusChanged";
+
+        /// <summary>
+        /// JSON name of sensor status connected property.
+        /// </summary>
+        internal const string SensorStatusConnectedPropertyName = "connected";
+
+        /// <summary>
+        /// Context that allows this stream handler to communicate with its owner.
+        /// </summary>
+        private readonly SensorStreamHandlerContext ownerContext;
+
+        /// <summary>
+        /// Sensor providing data to sensor state.
+        /// </summary>
+        private KinectSensor sensor;
+
+        /// <summary>
+        /// true if kinect sensor is connected.
+        /// </summary>
+        private bool isConnected;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SensorStatusStreamHandler"/> class
+        /// and associates it with a context that allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="ownerContext">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        internal SensorStatusStreamHandler(SensorStreamHandlerContext ownerContext)
+        {
+            this.ownerContext = ownerContext;
+
+            this.AddStreamConfiguration(SensorStreamName, new StreamConfiguration(this.GetSensorStreamProperties, this.SetSensorStreamProperty));
+        }
+
+        /// <summary>
+        /// Lets ISensorStatusStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        public async override void OnSensorChanged(KinectSensor newSensor)
+        {
+            if (this.sensor != newSensor)
+            {
+                bool oldConnected = this.isConnected;
+                this.isConnected = newSensor != null;
+
+                if (oldConnected != this.isConnected)
+                {
+                    await this.ownerContext.SendEventMessageAsync(new SensorStatusEventMessage
+                    {
+                        category = SensorStatusEventCategory,
+                        eventType = SensorStatusEventType,
+                        connected = this.isConnected
+                    });
+                }
+            }
+
+            this.sensor = newSensor;
+        }
+
+        /// <summary>
+        /// Gets a sensor stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        private void GetSensorStreamProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(SensorStatusConnectedPropertyName, this.isConnected);
+        }
+
+        /// <summary>
+        /// Set a sensor stream property value.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        private string SetSensorStreamProperty(string propertyName, object propertyValue)
+        {
+            if (propertyName == SensorStatusConnectedPropertyName)
+            {
+                return Properties.Resources.PropertyReadOnly;
+            }
+            else
+            {
+                return Properties.Resources.PropertyNameUnrecognized;
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerBase.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerBase.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerBase.cs	(revision 28897)
@@ -0,0 +1,305 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="SensorStreamHandlerBase.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Linq;
+    using System.Net;
+    using System.Threading.Tasks;
+
+    using Microsoft.Kinect;
+
+    using PropertyMap = System.Collections.Generic.Dictionary<string, object>;
+
+    /// <summary>
+    /// Base implementation for <see cref="ISensorStreamHandler"/> interface.
+    /// </summary>
+    public class SensorStreamHandlerBase : ISensorStreamHandler
+    {
+        /// <summary>
+        /// Map of supported stream names to corresponding stream configurations.
+        /// </summary>
+        private readonly Dictionary<string, StreamConfiguration> streamHandlerConfiguration = new Dictionary<string, StreamConfiguration>();
+
+        /// <summary>
+        /// Object used to discard errors when clients didn't specify an error dictionary.
+        /// </summary>
+        private readonly IDictionary<string, object> errorSink = new PropertyMap();
+
+        /// <summary>
+        /// Array of supported stream names.
+        /// </summary>
+        private string[] supportedStreams;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SensorStreamHandlerBase"/> class.
+        /// </summary>
+        protected SensorStreamHandlerBase()
+        {
+        }
+
+        /// <summary>
+        /// Get the names of the stream(s) supported by this stream handler.
+        /// </summary>
+        /// <returns>
+        /// An array of stream names.
+        /// </returns>
+        /// <remarks>
+        /// These names will be used in JSON objects to refer to individual streams.
+        /// </remarks>
+        public string[] GetSupportedStreamNames()
+        {
+            return this.supportedStreams ?? (this.supportedStreams = this.streamHandlerConfiguration.Keys.ToArray());
+        }
+
+        /// <summary>
+        /// Lets ISensorStreamHandler know that Kinect Sensor associated with this stream
+        /// handler has changed.
+        /// </summary>
+        /// <param name="newSensor">
+        /// New KinectSensor.
+        /// </param>
+        public virtual void OnSensorChanged(KinectSensor newSensor)
+        {
+        }
+
+        /// <summary>
+        /// Process data from one Kinect color frame.
+        /// </summary>
+        /// <param name="colorData">
+        /// Kinect color data.
+        /// </param>
+        /// <param name="colorFrame">
+        /// <see cref="ColorImageFrame"/> from which we obtained color data.
+        /// </param>
+        public virtual void ProcessColor(byte[] colorData, ColorImageFrame colorFrame)
+        {
+        }
+
+        /// <summary>
+        /// Process data from one Kinect depth frame.
+        /// </summary>
+        /// <param name="depthData">
+        /// Kinect depth data.
+        /// </param>
+        /// <param name="depthFrame">
+        /// <see cref="DepthImageFrame"/> from which we obtained depth data.
+        /// </param>
+        public virtual void ProcessDepth(DepthImagePixel[] depthData, DepthImageFrame depthFrame)
+        {
+        }
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        public virtual void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
+        {
+        }
+
+        /// <summary>
+        /// Gets the state property values associated with the specified stream name.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be returned.
+        /// </param>
+        /// <returns>
+        /// Dictionary mapping property names to property values.
+        /// </returns>
+        public IDictionary<string, object> GetState(string streamName)
+        {
+            var propertyMap = new Dictionary<string, object>();
+
+            StreamConfiguration config;
+            if (!this.streamHandlerConfiguration.TryGetValue(streamName, out config))
+            {
+                throw new ArgumentException(@"Unsupported stream name", "streamName");
+            }
+
+            config.GetPropertiesCallback(propertyMap);
+
+            return propertyMap;
+        }
+
+        /// <summary>
+        /// Attempts to set the specified state property values associated with the specified
+        /// stream name.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="properties">
+        /// Dictionary mapping property names to property values that should be set.
+        /// Must not be null.
+        /// </param>
+        /// <param name="errors">
+        /// Dictionary meant to receive mappings between property names to errors encountered
+        /// while trying to set each property value.
+        /// May be null.
+        /// </param>
+        /// <returns>
+        /// true if there were no errors encountered while setting state. false otherwise.
+        /// </returns>
+        /// <remarks>
+        /// If <paramref name="errors"/> is non-null, it is expected that it will be empty
+        /// when method is called.
+        /// </remarks>
+        public bool SetState(string streamName, IReadOnlyDictionary<string, object> properties, IDictionary<string, object> errors)
+        {
+            bool successful = true;
+
+            if (properties == null)
+            {
+                throw new ArgumentException(@"properties must not be null", "properties");
+            }
+
+            if (errors == null)
+            {
+                // Guarantee code down the line that errors will not be null, but don't let
+                // the error sink ever get too large.
+                this.errorSink.Clear();
+                errors = this.errorSink;
+            }
+
+            StreamConfiguration config;
+            if (!this.streamHandlerConfiguration.TryGetValue(streamName, out config))
+            {
+                throw new ArgumentException(@"Unsupported stream name", "streamName");
+            }
+
+            foreach (var keyValuePair in properties)
+            {
+                try
+                {
+                    var error = config.SetPropertyCallback(keyValuePair.Key, keyValuePair.Value);
+                    if (error != null)
+                    {
+                        errors.Add(keyValuePair.Key, error);
+                        successful = false;
+                    }
+                }
+                catch (InvalidOperationException)
+                {
+                    successful = false;
+                    errors.Add(keyValuePair.Key, Properties.Resources.PropertySetError);
+                }
+            }
+
+            return successful;
+        }
+
+        /// <summary>
+        /// Handle an http request.
+        /// </summary>
+        /// <param name="streamName">
+        /// Name of stream for which property values should be set.
+        /// </param>
+        /// <param name="requestContext">
+        /// Context containing HTTP request data, which will also contain associated
+        /// response upon return.
+        /// </param>
+        /// <param name="subpath">
+        /// Request URI path relative to the stream name associated with this sensor stream
+        /// handler in the stream handler owner.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public virtual Task HandleRequestAsync(string streamName, HttpListenerContext requestContext, string subpath)
+        {
+            KinectRequestHandler.CloseResponse(requestContext, HttpStatusCode.NotFound);
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Cancel all pending operations
+        /// </summary>
+        public virtual void Cancel()
+        {
+        }
+
+        /// <summary>
+        /// Lets handler know that it should clean up resources associated with sensor stream
+        /// handling.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Return value should never be null. Implementations should use Task.FromResult(0)
+        /// if function is implemented synchronously so that callers can await without
+        /// needing to check for null.
+        /// </remarks>
+        public virtual Task UninitializeAsync()
+        {
+            return SharedConstants.EmptyCompletedTask;
+        }
+
+        /// <summary>
+        /// Add a configuration corresponding to the specified stream name.
+        /// </summary>
+        /// <param name="name">
+        /// Stream name.
+        /// </param>
+        /// <param name="configuration">
+        /// Stream configuration.
+        /// </param>
+        protected void AddStreamConfiguration(string name, StreamConfiguration configuration)
+        {
+            this.streamHandlerConfiguration.Add(name, configuration);
+            this.supportedStreams = null;
+        }
+
+        /// <summary>
+        /// Helper class used to configure SensorStreamHandlerBase subclass behavior.
+        /// </summary>
+        protected class StreamConfiguration
+        {
+            /// <summary>
+            /// Initializes a new instance of the <see cref="StreamConfiguration"/> class.
+            /// </summary>
+            /// <param name="getPropertiesCallback">
+            /// Callback function used to get all state property values.
+            /// </param>
+            /// <param name="setPropertyCallback">
+            /// Callback function used to set individual state property values.
+            /// </param>
+            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Outer generic is a functional type and won't require cumbersome syntax to use")]
+            public StreamConfiguration(Action<PropertyMap> getPropertiesCallback, Func<string, object, string> setPropertyCallback)
+            {
+                this.GetPropertiesCallback = getPropertiesCallback;
+                this.SetPropertyCallback = setPropertyCallback;
+            }
+
+            /// <summary>
+            /// Gets the callback function used to get all state property values.
+            /// </summary>
+            /// <remarks>
+            /// Callback parameter is a property name->value map where property values should
+            /// be set.
+            /// </remarks>
+            [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1006:DoNotNestGenericTypesInMemberSignatures", Justification = "Outer generic is a functional type and won't require cumbersome syntax to use")]
+            public Action<PropertyMap> GetPropertiesCallback { get; private set; }
+
+            /// <summary>
+            /// Gets the callback function used to set individual state property values.
+            /// </summary>
+            public Func<string, object, string> SetPropertyCallback { get; private set; }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerContext.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerContext.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerContext.cs	(revision 28897)
@@ -0,0 +1,57 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="SensorStreamHandlerContext.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Threading.Tasks;
+
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Provides context through which an instance of <see cref="ISensorStreamHandler"/> can
+    /// communicate back with its owner.
+    /// </summary>
+    public sealed class SensorStreamHandlerContext
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SensorStreamHandlerContext"/> class.
+        /// </summary>
+        /// <param name="sendTwoPartStreamMessageAsync">
+        /// Function used to asynchronously send two-part (textual header plus binary payload) stream
+        /// message to client(s) of stream handler. If binary (second) part of message is null, only
+        /// textual header (first) part will be sent.
+        /// </param>
+        /// <param name="sendEventMessageAsync">
+        /// Function used to asynchronously send event message to client(s) of stream handler.
+        /// </param>
+        public SensorStreamHandlerContext(
+            Func<StreamMessage, byte[], Task> sendTwoPartStreamMessageAsync,
+            Func<EventMessage, Task> sendEventMessageAsync)
+        {
+            this.SendTwoPartStreamMessageAsync = sendTwoPartStreamMessageAsync;
+            this.SendStreamMessageAsync = message => sendTwoPartStreamMessageAsync(message, null);
+            this.SendEventMessageAsync = sendEventMessageAsync;
+        }
+
+        /// <summary>
+        /// Function used to asynchronously send stream message to client(s) of stream handler.
+        /// </summary>
+        public Func<StreamMessage, Task> SendStreamMessageAsync { get; private set; }
+
+        /// <summary>
+        /// Function used to asynchronously send two-part (textual header plus binary payload) stream
+        /// message to client(s) of stream handler. If binary (second) part of message is null, only
+        /// textual header (first) part will be sent.
+        /// </summary>
+        public Func<StreamMessage, byte[], Task> SendTwoPartStreamMessageAsync { get; private set; }
+
+        /// <summary>
+        /// Function used to asynchronously send event message to client(s) of stream handler.
+        /// </summary>
+        public Func<EventMessage, Task> SendEventMessageAsync { get; private set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerFactory.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerFactory.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SensorStreamHandlerFactory.cs	(revision 28897)
@@ -0,0 +1,70 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SensorStreamHandlerFactory.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using Microsoft.Samples.Kinect.Webserver.Properties;
+
+    /// <summary>
+    /// The supported sensor stream type.
+    /// </summary>
+    public enum StreamHandlerType
+    {
+        Skeleton,
+        Interaction,
+        BackgroundRemoval,
+        SensorStatus,
+    }
+
+    /// <summary>
+    /// Implementation of ISensorStreamHandlerFactory used to create instances of
+    /// sensor stream handler objects.
+    /// </summary>
+    public class SensorStreamHandlerFactory : ISensorStreamHandlerFactory
+    {
+        /// <summary>
+        /// The type of the created stream.
+        /// </summary>
+        private readonly StreamHandlerType streamType;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SensorStreamHandlerFactory"/> class.
+        /// </summary>
+        /// <param name="streamType">The stream type.</param>
+        public SensorStreamHandlerFactory(StreamHandlerType streamType)
+        {
+            this.streamType = streamType;
+        }
+
+        /// <summary>
+        /// Creates a sensor stream handler object and associates it with a context that
+        /// allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="context">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        /// <returns>
+        /// A new <see cref="ISensorStreamHandler"/> instance.
+        /// </returns>
+        public ISensorStreamHandler CreateHandler(SensorStreamHandlerContext context)
+        {
+            switch (streamType)
+            {
+                case StreamHandlerType.Skeleton:
+                    return new SkeletonStreamHandler(context);
+                case StreamHandlerType.Interaction:
+                    return new InteractionStreamHandler(context);
+                case StreamHandlerType.BackgroundRemoval:
+                    return new BackgroundRemovalStreamHandler(context);
+                case StreamHandlerType.SensorStatus:
+                    return new SensorStatusStreamHandler(context);
+                default:
+                    throw new NotSupportedException(Resources.UnsupportedStreamType);
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/BackgroundRemovalStreamMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/BackgroundRemovalStreamMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/BackgroundRemovalStreamMessage.cs	(revision 28897)
@@ -0,0 +1,96 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="BackgroundRemovalStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System;
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect.Toolkit.BackgroundRemoval;
+
+    /// <summary>
+    /// Serializable representation of a background removed color stream message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    public class BackgroundRemovalStreamMessage : ImageHeaderStreamMessage
+    {
+        /// <summary>
+        /// Bytes per pixel const.
+        /// </summary>
+        private const int BytesPerPixel = 4;
+
+        /// <summary>
+        /// Tracking ID of the player currently being tracked. Pixels that do not belong
+        /// to this player are removed.
+        /// </summary>
+        /// <remarks>
+        /// This value will be 0 if no player is found in the corresponding color frame.
+        /// </remarks>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "tracked", Justification = "Lower case names allowed for JSON serialization.")]
+        public int trackedPlayerId { get; set; }
+
+        /// <summary>
+        /// The average depth of the pixels corresponding to the foreground player.
+        /// </summary>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "average", Justification = "Lower case names allowed for JSON serialization.")]
+        public short averageDepth { get; set; }
+
+        /// <summary>
+        /// Buffer that holds background removed color image.
+        /// </summary>
+        internal byte[] Buffer { get; private set; }
+
+        /// <summary>
+        /// Update background removed color frame.
+        /// </summary>
+        /// <param name="frame">The input frame.</param>
+        public void UpdateBackgroundRemovedColorFrame(BackgroundRemovedColorFrame frame)
+        {
+            if (frame == null)
+            {
+                throw new ArgumentNullException("frame");
+            }
+
+            this.timestamp = frame.Timestamp;
+            this.width = frame.Width;
+            this.height = frame.Height;
+            this.bufferLength = frame.PixelDataLength;
+            this.trackedPlayerId = frame.TrackedPlayerId;
+            this.averageDepth = frame.AverageDepth;
+
+            if ((this.Buffer == null) || (this.Buffer.Length != this.bufferLength))
+            {
+                this.Buffer = new byte[this.bufferLength];
+            }
+
+            unsafe
+            {
+                fixed (byte* messageDataPtr = this.Buffer)
+                {
+                    fixed (byte* frameDataPtr = frame.GetRawPixelData())
+                    {
+                        byte* messageDataPixelPtr = messageDataPtr;
+                        byte* frameDataPixelPtr = frameDataPtr;
+
+                        byte* messageDataPixelPtrEnd = messageDataPixelPtr + this.bufferLength;
+
+                        while (messageDataPixelPtr != messageDataPixelPtrEnd)
+                        {
+                            // Convert from BGRA to RGBA format
+                            *(messageDataPixelPtr++) = *(frameDataPixelPtr + 2);
+                            *(messageDataPixelPtr++) = *(frameDataPixelPtr + 1);
+                            *(messageDataPixelPtr++) = *frameDataPixelPtr;
+                            *(messageDataPixelPtr++) = *(frameDataPixelPtr + 3);
+
+                            frameDataPixelPtr += BytesPerPixel;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/EventMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/EventMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/EventMessage.cs	(revision 28897)
@@ -0,0 +1,29 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="EventMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Base class for web socket messages sent over "stream" endpoint.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    public class EventMessage
+    {
+        /// <summary>
+        /// Category to which this event pertains.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "category", Justification = "Lower case names allowed for JSON serialization.")]
+        public string category { get; set; }
+
+        /// <summary>
+        /// Type of event being sent.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "event", Justification = "Lower case names allowed for JSON serialization.")]
+        public string eventType { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallRequest.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallRequest.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallRequest.cs	(revision 28897)
@@ -0,0 +1,48 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="FunctionCallRequest.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Serializable representation of a function call request.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class FunctionCallRequest
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="FunctionCallRequest"/> class.
+        /// </summary>
+        /// <param name="functionName">
+        /// Name of remote function to invoke.
+        /// </param>
+        /// <param name="args">
+        /// Function arguments.
+        /// </param>
+        /// <param name="sequenceId">
+        /// Sequence Id used to match function call request with its response.
+        /// </param>
+        public FunctionCallRequest(string functionName, object[] args, int sequenceId)
+        {
+            this.name = functionName;
+            this.args = args;
+            this.id = sequenceId;
+        }
+
+        public string name { get; set; }
+
+        /// <summary>
+        /// Function arguments.
+        /// </summary>
+        public object[] args { get; set; }
+
+        /// <summary>
+        /// Sequence Id used to match function call request with its response.
+        /// </summary>
+        public int id { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallResponse.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallResponse.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/FunctionCallResponse.cs	(revision 28897)
@@ -0,0 +1,30 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="FunctionCallResponse.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Serializable representation of a function call response.
+    /// </summary>
+    /// <typeparam name="T">
+    /// Type of function call result.
+    /// </typeparam>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class FunctionCallResponse<T>
+    {
+        /// <summary>
+        /// Sequence Id used to match function call request with its response.
+        /// </summary>
+        public int id { get; set; }
+
+        /// <summary>
+        /// Result of remote function call.
+        /// </summary>
+        public T result { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/ImageHeaderStreamMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/ImageHeaderStreamMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/ImageHeaderStreamMessage.cs	(revision 28897)
@@ -0,0 +1,35 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="ImageHeaderStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Serializable representation of a stream message that represents an image message header to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    public class ImageHeaderStreamMessage : StreamMessage
+    {
+        /// <summary>
+        /// Image width
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "width", Justification = "Lower case names allowed for JSON serialization.")]
+        public int width { get; set; }
+
+        /// <summary>
+        /// Image height.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "height", Justification = "Lower case names allowed for JSON serialization.")]
+        public int height { get; set; }
+
+        /// <summary>
+        /// Number of bytes in image buffer.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "buffer", Justification = "Lower case names allowed for JSON serialization.")]
+        public int bufferLength { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamHitTestInfo.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamHitTestInfo.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamHitTestInfo.cs	(revision 28897)
@@ -0,0 +1,55 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="InteractionStreamHitTestInfo.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect.Toolkit.Interaction;
+
+    /// <summary>
+    /// Represents interaction information available for a specified location in UI.
+    /// </summary>
+    /// <remarks>
+    /// This class is equivalent to <see cref="InteractionInfo"/>, but meant for serializing
+    /// from JSON responses from a web client.
+    /// </remarks>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class InteractionStreamHitTestInfo
+    {
+        /// <summary>
+        /// True if interaction target can respond to press actions.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isPressTarget { get; set; }
+
+        /// <summary>
+        /// Identifier for control that corresponds to UI location of interest.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "press", Justification = "Lower case names allowed for JSON serialization.")]
+        public string pressTargetControlId { get; set; }
+
+        /// <summary>
+        /// X-coordinate of point towards which user's hand pointer should be
+        /// attracted as the user's hand performs a press action.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "press", Justification = "Lower case names allowed for JSON serialization.")]
+        public double pressAttractionPointX { get; set; }
+
+        /// <summary>
+        /// Y-coordinate of point towards which user's hand pointer should be
+        /// attracted as the user's hand performs a press action.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "press", Justification = "Lower case names allowed for JSON serialization.")]
+        public double pressAttractionPointY { get; set; }
+
+        /// <summary>
+        /// True if interaction target can respond to grip/release actions.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isGripTarget { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/InteractionStreamMessage.cs	(revision 28897)
@@ -0,0 +1,119 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="InteractionStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect.Toolkit.Interaction;
+
+    /// <summary>
+    /// Serializable representation of an interaction stream message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    public class InteractionStreamMessage : StreamMessage
+    {
+        /// <summary>
+        /// Maximum number of hand pointers we can track.
+        /// </summary>
+        private const int MaximumHandPointers = 4; // 2 hands for 2 tracked users
+
+        /// <summary>
+        /// Internal array of MessageHandPointer objects, allocated once and then reused.
+        /// </summary>
+        private readonly MessageHandPointer[] internalHandPointers;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="InteractionStreamMessage"/> class.
+        /// </summary>
+        public InteractionStreamMessage()
+        {
+            this.internalHandPointers = new MessageHandPointer[MaximumHandPointers];
+
+            for (int i = 0; i < this.internalHandPointers.Length; ++i)
+            {
+                this.internalHandPointers[i] = new MessageHandPointer();
+            }
+        }
+
+        /// <summary>
+        /// Serializable hand pointer array.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "hand", Justification = "Lower case names allowed for JSON serialization.")]
+        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Array properties allowed for JSON serialization.")]
+        public MessageHandPointer[] handPointers { get; set; }
+
+        /// <summary>
+        /// Update hand pointers from specified user info data.
+        /// </summary>
+        /// <param name="userInfoData">
+        /// Enumeration of UserInfo structures.
+        /// </param>
+        /// <param name="primaryUserTrackingId">
+        /// Tracking ID of primary user.
+        /// </param>
+        public void UpdateHandPointers(IEnumerable<UserInfo> userInfoData, int primaryUserTrackingId)
+        {
+            int handPointerIndex = 0;
+
+            if (userInfoData == null)
+            {
+                throw new ArgumentNullException("userInfoData");
+            }
+
+            foreach (var user in userInfoData)
+            {
+                foreach (var handPointer in user.HandPointers)
+                {
+                    if (user.SkeletonTrackingId == SharedConstants.InvalidUserTrackingId)
+                    {
+                        continue;
+                    }
+
+                    var messageHandPointer = this.internalHandPointers[handPointerIndex];
+                    messageHandPointer.trackingId = user.SkeletonTrackingId;
+                    messageHandPointer.handType = handPointer.HandType.ToString();
+                    messageHandPointer.isTracked = handPointer.IsTracked;
+                    messageHandPointer.isActive = handPointer.IsActive;
+                    messageHandPointer.isInteractive = handPointer.IsInteractive;
+                    messageHandPointer.isPressed = handPointer.IsPressed;
+                    messageHandPointer.isPrimaryHandOfUser = handPointer.IsPrimaryForUser;
+                    messageHandPointer.isPrimaryUser = primaryUserTrackingId == user.SkeletonTrackingId;
+                    messageHandPointer.handEventType = handPointer.HandEventType.ToString();
+                    messageHandPointer.x = handPointer.X;
+                    messageHandPointer.y = handPointer.Y;
+                    messageHandPointer.pressExtent = handPointer.PressExtent;
+                    messageHandPointer.rawX = handPointer.RawX;
+                    messageHandPointer.rawY = handPointer.RawY;
+                    messageHandPointer.rawZ = handPointer.RawZ;
+
+                    if (++handPointerIndex >= MaximumHandPointers)
+                    {
+                        break;
+                    }
+                }
+
+                if (handPointerIndex >= MaximumHandPointers)
+                {
+                    break;
+                }
+            }
+
+            if ((this.handPointers == null) || (this.handPointers.Length != handPointerIndex))
+            {
+                this.handPointers = new MessageHandPointer[handPointerIndex];
+            }
+
+            for (handPointerIndex = 0; handPointerIndex < this.handPointers.Length; ++handPointerIndex)
+            {
+                this.handPointers[handPointerIndex] = this.internalHandPointers[handPointerIndex];
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/MessageHandPointer.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/MessageHandPointer.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/MessageHandPointer.cs	(revision 28897)
@@ -0,0 +1,183 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="MessageHandPointer.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+    using System.Globalization;
+
+    /// <summary>
+    /// Serializable representation of a hand pointer.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Used by serialization code")]
+    public class MessageHandPointer
+    {
+        /// <summary>
+        /// User tracking Id corresponding to this hand pointer.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "tracking", Justification = "Lower case names allowed for JSON serialization.")]
+        public int trackingId { get; set; }
+
+        /// <summary>
+        /// Indicates whether this hand pointer refers to right or left hand.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "hand", Justification = "Lower case names allowed for JSON serialization.")]
+        public string handType { get; set; }
+
+        /// <summary>
+        /// Indicates which event type (grip/release) is currently triggered, if any.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "hand", Justification = "Lower case names allowed for JSON serialization.")]
+        public string handEventType { get; set; }
+
+        /// <summary>
+        /// Indicates whether this hand pointer is currently being tracked.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isTracked { get; set; }
+
+        /// <summary>
+        /// Indicates whether hand is one of possibly several candidates to be labeled as
+        /// the primary hand for the user.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isActive { get; set; }
+
+        /// <summary>
+        /// Indicates whether hand is within physical interaction region that maps to the
+        /// user interface.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isInteractive { get; set; }
+
+        /// <summary>
+        /// Indicates whether  hand is currently performing "press" action.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isPressed { get; set; }
+
+        /// <summary>
+        /// Indicates whether this hand is the primary hand for corresponding user.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isPrimaryHandOfUser { get; set; }
+
+        /// <summary>
+        /// Indicates whether corresponding user is the primary user.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "is", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool isPrimaryUser { get; set; }
+
+        /// <summary>
+        /// Gets interaction-adjusted X-coordinate of hand pointer position.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 corresponds to left edge of interaction region and 1.0 corresponds to right edge
+        /// of interaction region, but values could be outside of this range.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "x", Justification = "Lower case names allowed for JSON serialization.")]
+        public double x { get; set; }
+
+        /// <summary>
+        /// Gets interaction-adjusted Y-coordinate of hand pointer position.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 corresponds to top edge of interaction region and 1.0 corresponds to bottom edge
+        /// of interaction region, but values could be outside of this range.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "y", Justification = "Lower case names allowed for JSON serialization.")]
+        public double y { get; set; }
+
+        /// <summary>
+        /// Gets a interaction-adjusted measure of how much pressing is being performed by hand.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 means that hand is not performing a press action at all, and 1.0 means that
+        /// hand is at the trigger point for press action, but values could be greater than 1.0.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "press", Justification = "Lower case names allowed for JSON serialization.")]
+        public double pressExtent { get; set; }
+
+        /// <summary>
+        /// Gets X-coordinate of hand pointer position.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 corresponds to left edge of interaction region and 1.0 corresponds to right edge
+        /// of interaction region, but values could be outside of this range.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "raw", Justification = "Lower case names allowed for JSON serialization.")]
+        public double rawX { get; set; }
+
+        /// <summary>
+        /// Gets Y-coordinate of hand pointer position.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 corresponds to top edge of interaction region and 1.0 corresponds to bottom edge
+        /// of interaction region, but values could be outside of this range.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "raw", Justification = "Lower case names allowed for JSON serialization.")]
+        public double rawY { get; set; }
+
+        /// <summary>
+        /// Gets a measure of how much pressing is being performed by hand.
+        /// </summary>
+        /// <remarks>
+        /// 0.0 means that hand is not performing a press action at all, and 1.0 means that
+        /// hand is at the trigger point for press action, but values could be greater than 1.0.
+        /// </remarks>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "raw", Justification = "Lower case names allowed for JSON serialization.")]
+        public double rawZ { get; set; }
+
+        public static implicit operator MessageHandPointer(Dictionary<string, object> handPointerDictionary)
+        {
+            return FromDictionary(handPointerDictionary);
+        }
+
+        public static MessageHandPointer FromDictionary(Dictionary<string, object> handPointerDictionary)
+        {
+            if (handPointerDictionary == null)
+            {
+                throw new ArgumentNullException("handPointerDictionary");
+            }
+
+            var messageHandPointer = new MessageHandPointer
+                                     {
+                                         trackingId = (int)handPointerDictionary["trackingId"],
+                                         handType = (string)handPointerDictionary["handType"],
+                                         handEventType = (string)handPointerDictionary["handEventType"],
+                                         isTracked = (bool)handPointerDictionary["isTracked"],
+                                         isActive = (bool)handPointerDictionary["isActive"],
+                                         isInteractive = (bool)handPointerDictionary["isInteractive"],
+                                         isPressed = (bool)handPointerDictionary["isPressed"],
+                                         isPrimaryHandOfUser = (bool)handPointerDictionary["isPrimaryHandOfUser"],
+                                         isPrimaryUser = (bool)handPointerDictionary["isPrimaryUser"],
+                                         x =
+                                             double.Parse(
+                                                 handPointerDictionary["x"].ToString(), CultureInfo.InvariantCulture),
+                                         y =
+                                             double.Parse(
+                                                 handPointerDictionary["y"].ToString(), CultureInfo.InvariantCulture),
+                                         pressExtent =
+                                             double.Parse(
+                                                 handPointerDictionary["pressExtent"].ToString(),
+                                                 CultureInfo.InvariantCulture),
+                                         rawX =
+                                             double.Parse(
+                                                 handPointerDictionary["rawX"].ToString(), CultureInfo.InvariantCulture),
+                                         rawY =
+                                             double.Parse(
+                                                 handPointerDictionary["rawY"].ToString(), CultureInfo.InvariantCulture),
+                                         rawZ =
+                                             double.Parse(
+                                                 handPointerDictionary["rawZ"].ToString(), CultureInfo.InvariantCulture)
+                                     };
+            return messageHandPointer;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SensorStatusEventMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SensorStatusEventMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SensorStatusEventMessage.cs	(revision 28897)
@@ -0,0 +1,24 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SensorStatusEventMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Serializable representation of an sensor status event message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    internal class SensorStatusEventMessage : EventMessage
+    {
+        /// <summary>
+        /// status of sensor.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "connected", Justification = "Lower case names allowed for JSON serialization.")]
+        public bool connected { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SkeletonStreamMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SkeletonStreamMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/SkeletonStreamMessage.cs	(revision 28897)
@@ -0,0 +1,53 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SkeletonStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Serializable representation of an skeleton stream message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    public class SkeletonStreamMessage : StreamMessage
+    {
+        /// <summary>
+        /// Serializable skeleton array.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "skeletons", Justification = "Lower case names allowed for JSON serialization.")]
+        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Array properties allowed for JSON serialization.")]
+        public object[] skeletons { get; set; }
+
+        /// <summary>
+        /// Update hand pointers from specified user info data.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Enumeration of UserInfo structures.
+        /// </param>
+        public void UpdateSkeletons(Skeleton[] skeletons)
+        {
+            if (skeletons == null)
+            {
+                throw new ArgumentNullException("skeletons");
+            }
+
+            if (this.skeletons == null || this.skeletons.Length != skeletons.Length)
+            {
+                this.skeletons = new object[skeletons.Length];
+            }
+
+            for (int i = 0; i < this.skeletons.Length; i ++)
+            {
+                this.skeletons[i] = JsonSerializationExtensions.ExtractSerializableJsonData(skeletons[i]);
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StateMappingEntry.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StateMappingEntry.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StateMappingEntry.cs	(revision 28897)
@@ -0,0 +1,29 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="StateMappingEntry.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Represents a single entry mapping a user tracking id to a user state.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class StateMappingEntry
+    {
+        /// <summary>
+        /// User tracking id.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "id", Justification = "Lower case names allowed for JSON serialization.")]
+        public int id { get; set; }
+
+        /// <summary>
+        /// User state associated with tracking id
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "user", Justification = "Lower case names allowed for JSON serialization.")]
+        public string userState { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StreamMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StreamMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/StreamMessage.cs	(revision 28897)
@@ -0,0 +1,29 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="StreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Base class for web socket messages sent over "stream" endpoint.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    public class StreamMessage
+    {
+        /// <summary>
+        /// Name of stream that owns this message.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "stream", Justification = "Lower case names allowed for JSON serialization.")]
+        public string stream { get; set; }
+
+        /// <summary>
+        /// Stream message timestamp.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "timestamp", Justification = "Lower case names allowed for JSON serialization.")]
+        public long timestamp { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserStatesChangedEventMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserStatesChangedEventMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserStatesChangedEventMessage.cs	(revision 28897)
@@ -0,0 +1,23 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserStatesChangedEventMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Event message used to notify clients that user states have changed.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class UserStatesChangedEventMessage : EventMessage
+    {
+        /// <summary>
+        /// New map of user tracking IDs to user state names.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "user", Justification = "Lower case names allowed for JSON serialization.")]
+        public StateMappingEntry[] userStates { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserTrackingIdChangedEventArgs.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserTrackingIdChangedEventArgs.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/Serialization/UserTrackingIdChangedEventArgs.cs	(revision 28897)
@@ -0,0 +1,29 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserTrackingIdChangedEventArgs.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor.Serialization
+{
+    using System.Diagnostics.CodeAnalysis;
+
+    /// <summary>
+    /// Event message used to notify clients that a user tracking id has changed.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter", Justification = "Lower case names allowed for JSON serialization.")]
+    internal class UserTrackingIdChangedEventMessage : EventMessage
+    {
+        /// <summary>
+        /// Old user tracking identifier.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "old", Justification = "Lower case names allowed for JSON serialization.")]
+        public int oldValue { get; set; }
+
+        /// <summary>
+        /// New user tracking identifier.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "new", Justification = "Lower case names allowed for JSON serialization.")]
+        public int newValue { get; set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamHandler.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamHandler.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamHandler.cs	(revision 28897)
@@ -0,0 +1,178 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SkeletonStreamHandler.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+
+    using Microsoft.Kinect;
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Implementation of ISensorStreamHandler that exposes skeleton streams.
+    /// </summary>
+    public class SkeletonStreamHandler : SensorStreamHandlerBase
+    {
+        /// <summary>
+        /// JSON name of skeleton stream.
+        /// </summary>
+        internal const string SkeletonStreamName = "skeleton";
+
+        /// <summary>
+        /// Context that allows this stream handler to communicate with its owner.
+        /// </summary>
+        private readonly SensorStreamHandlerContext ownerContext;
+
+        /// <summary>
+        /// Serializable skeleton stream message, reused as skeleton frames arrive.
+        /// </summary>
+        private readonly SkeletonStreamMessage skeletonStreamMessage = new SkeletonStreamMessage { stream = SkeletonStreamName };
+
+        /// <summary>
+        /// true if skeleton stream is enabled. Skeleton stream is disabled by default.
+        /// </summary>
+        private bool skeletonIsEnabled;
+
+        /// <summary>
+        /// Keep track if we're in the middle of processing an skeleton frame.
+        /// </summary>
+        private bool isProcessingSkeletonFrame;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="SkeletonStreamHandler"/> class
+        /// and associates it with a context that allows it to communicate with its owner.
+        /// </summary>
+        /// <param name="ownerContext">
+        /// An instance of <see cref="SensorStreamHandlerContext"/> class.
+        /// </param>
+        internal SkeletonStreamHandler(SensorStreamHandlerContext ownerContext)
+        {
+            this.ownerContext = ownerContext;
+
+            this.AddStreamConfiguration(SkeletonStreamName, new StreamConfiguration(this.GetProperties, this.SetProperty));
+        }
+
+        /// <summary>
+        /// Process data from one Kinect skeleton frame.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="skeletonFrame">
+        /// <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        public override void ProcessSkeleton(Skeleton[] skeletons, SkeletonFrame skeletonFrame)
+        {
+            if (skeletonFrame == null)
+            {
+                throw new ArgumentNullException("skeletonFrame");
+            }
+
+            this.ProcessSkeletonAsync(skeletons, skeletonFrame.Timestamp);
+        }
+
+        /// <summary>
+        /// Process skeletons in async mode.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Kinect skeleton data.
+        /// </param>
+        /// <param name="timestamp">
+        /// Timestamp of <see cref="SkeletonFrame"/> from which we obtained skeleton data.
+        /// </param>
+        internal async void ProcessSkeletonAsync(Skeleton[] skeletons, long timestamp)
+        {
+            if (!this.skeletonIsEnabled)
+            {
+                return;
+            }
+
+            if (this.isProcessingSkeletonFrame)
+            {
+                // Re-entered SkeletonFrameReadyAsync while a previous frame is already being processed.
+                // Just ignore new frames until the current one finishes processing.
+                return;
+            }
+
+            this.isProcessingSkeletonFrame = true;
+
+            try
+            {
+                if (skeletons != null)
+                {
+                    this.skeletonStreamMessage.timestamp = timestamp;
+                    this.skeletonStreamMessage.UpdateSkeletons(skeletons);
+
+                    await this.ownerContext.SendStreamMessageAsync(this.skeletonStreamMessage);
+                }
+            }
+            finally
+            {
+                this.isProcessingSkeletonFrame = false;
+            }
+        }
+
+        /// <summary>
+        /// Gets a skeleton stream property value.
+        /// </summary>
+        /// <param name="propertyMap">
+        /// Property name->value map where property values should be set.
+        /// </param>
+        private void GetProperties(Dictionary<string, object> propertyMap)
+        {
+            propertyMap.Add(KinectRequestHandler.EnabledPropertyName, this.skeletonIsEnabled);
+        }
+
+        /// <summary>
+        /// Set a skeleton stream property value.
+        /// </summary>
+        /// <param name="propertyName">
+        /// Name of property to set.
+        /// </param>
+        /// <param name="propertyValue">
+        /// Property value to set.
+        /// </param>
+        /// <returns>
+        /// null if property setting was successful, error message otherwise.
+        /// </returns>
+        private string SetProperty(string propertyName, object propertyValue)
+        {
+            bool recognized = true;
+
+            if (propertyValue == null)
+            {
+                // None of the skeleton stream properties accept a null value
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            try
+            {
+                switch (propertyName)
+                {
+                    case KinectRequestHandler.EnabledPropertyName:
+                        this.skeletonIsEnabled = (bool)propertyValue;
+                        break;
+
+                    default:
+                        recognized = false;
+                        break;
+                }
+            }
+            catch (InvalidCastException)
+            {
+                return Properties.Resources.PropertyValueInvalidFormat;
+            }
+
+            if (!recognized)
+            {
+                return Properties.Resources.PropertyNameUnrecognized;
+            }
+
+            return null;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/SkeletonStreamMessage.cs	(revision 28897)
@@ -0,0 +1,53 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SkeletonStreamMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics.CodeAnalysis;
+
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Serializable representation of an skeleton stream message to send to client.
+    /// </summary>
+    [SuppressMessage("StyleCop.CSharp.NamingRules", "SA1300:ElementMustBeginWithUpperCaseLetter",
+        Justification = "Lower case names allowed for JSON serialization.")]
+    public class SkeletonStreamMessage : StreamMessage
+    {
+        /// <summary>
+        /// Serializable skeleton array.
+        /// </summary>
+        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "skeletons", Justification = "Lower case names allowed for JSON serialization.")]
+        [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Array properties allowed for JSON serialization.")]
+        public object[] skeletons { get; set; }
+
+        /// <summary>
+        /// Update hand pointers from specified user info data.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Enumeration of UserInfo structures.
+        /// </param>
+        public void UpdateSkeletons(Skeleton[] skeletons)
+        {
+            if (skeletons == null)
+            {
+                throw new ArgumentNullException("skeletons");
+            }
+
+            if (this.skeletons == null || this.skeletons.Length != skeletons.Length)
+            {
+                this.skeletons = new object[skeletons.Length];
+            }
+
+            for (int i = 0; i < this.skeletons.Length; i ++)
+            {
+                this.skeletons[i] = JsonSerializationExtensions.ExtractSerializableJsonData(skeletons[i]);
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityMeter.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityMeter.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityMeter.cs	(revision 28897)
@@ -0,0 +1,98 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserActivityMeter.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System.Collections.Generic;
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Helper class used to measure user activity.
+    /// </summary>
+    internal class UserActivityMeter
+    {
+        private readonly Dictionary<int, UserActivityRecord> activityRecords = new Dictionary<int, UserActivityRecord>();
+        private int totalUpdatesSoFar;
+
+        /// <summary>
+        /// Clears all user activity metrics.
+        /// </summary>
+        public void Clear()
+        {
+            this.activityRecords.Clear();
+        }
+
+        /// <summary>
+        /// Update user activity metrics with data from a collection of skeletons.
+        /// </summary>
+        /// <param name="skeletons">
+        /// Collection of skeletons to use to update activity metrics.
+        /// </param>
+        /// <param name="timestamp">
+        /// Time when skeleton array was received for processing.
+        /// </param>
+        /// <remarks>
+        /// UserActivityMeter assumes that this method is called regularly, e.g.: once
+        /// per skeleton frame received by application, so if a user whose activity was
+        /// previously measured is now absent, activity record will be removed.
+        /// </remarks>
+        public void Update(ICollection<Skeleton> skeletons, long timestamp)
+        {
+            foreach (var skeleton in skeletons)
+            {
+                UserActivityRecord record;
+
+                if (this.activityRecords.TryGetValue(skeleton.TrackingId, out record))
+                {
+                    record.Update(skeleton.Position, this.totalUpdatesSoFar, timestamp);
+                }
+                else
+                {
+                    record = new UserActivityRecord(skeleton.Position, this.totalUpdatesSoFar, timestamp);
+                    this.activityRecords[skeleton.TrackingId] = record;
+                }
+            }
+
+            // Remove activity records corresponding to users that are no longer being tracked
+            var idsToRemove = new List<int>();
+            foreach (var record in this.activityRecords)
+            {
+                if (record.Value.LastUpdateId != this.totalUpdatesSoFar)
+                {
+                    idsToRemove.Add(record.Key);
+                }
+            }
+
+            foreach (var id in idsToRemove)
+            {
+                this.activityRecords.Remove(id);
+            }
+
+            ++this.totalUpdatesSoFar;
+        }
+
+        /// <summary>
+        /// Gets the activity record associated with the specified user.
+        /// </summary>
+        /// <param name="userTrackingId">
+        /// Skeleton tracking Id of user associated with the activity record to
+        /// retrieve.
+        /// </param>
+        /// <param name="record">
+        /// [out] When this method returns, contains the record associated with the
+        /// specified user tracking Id, if the appropriate activity record is found.
+        /// This parameter is passed uninitialized.
+        /// </param>
+        /// <returns>
+        /// <code>true</code> if the UserActivityMeter contains an activity record
+        /// for the specified user tracking Id; otherwise, <code>false</code>.
+        /// </returns>
+        public bool TryGetActivityRecord(int userTrackingId, out UserActivityRecord record)
+        {
+            return this.activityRecords.TryGetValue(userTrackingId, out record);
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityRecord.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityRecord.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserActivityRecord.cs	(revision 28897)
@@ -0,0 +1,105 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserActivityRecord.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+
+    using Microsoft.Kinect;
+
+    /// <summary>
+    /// Represents activity state and metrics for a single user.
+    /// </summary>
+    internal class UserActivityRecord
+    {
+        // Activity level, above which a user is considered to be in "active" state.
+        private const double ActivityMetricThreshold = 0.1;
+
+        private double activityLevel;
+
+        public UserActivityRecord(SkeletonPoint position, int updateId, long timestamp)
+        {
+            this.ActivityLevel = 0.0;
+            this.LastPosition = position;
+            this.LastUpdateId = updateId;
+            this.IsActive = false;
+            this.StateTransitionTimestamp = timestamp;
+        }
+
+        /// <summary>
+        /// User activity level metric being tracked.
+        /// </summary>
+        /// <remarks>
+        /// Value is always in [0.0, 1.0] interval.
+        /// </remarks>
+        public double ActivityLevel
+        {
+            get
+            {
+                return this.activityLevel;
+            }
+
+            private set
+            {
+                this.activityLevel = Math.Max(0.0, Math.Min(1.0, value));
+            }
+        }
+
+        /// <summary>
+        /// Id of last update that touched this activity record.
+        /// </summary>
+        public int LastUpdateId { get; private set; }
+
+        /// <summary>
+        /// True if user activity is currently larger than the activity threshold.
+        /// </summary>
+        public bool IsActive { get; private set; }
+
+        /// <summary>
+        /// Time when IsActive state last changed from true to false or vice versa.
+        /// </summary>
+        public long StateTransitionTimestamp { get; private set; }
+
+        /// <summary>
+        /// Last position where user was observed.
+        /// </summary>
+        public SkeletonPoint LastPosition { get; private set; }
+
+        public void Update(SkeletonPoint position, int updateId, long timestamp)
+        {
+            // Movement magnitude gets scaled by this amount in order to get the current activity metric
+            const double DeltaScalingFactor = 10.0;
+
+            // Controls how quickly new values of the metric displace old values. 1.0 means that new values
+            // for metric immediately replace old values, while smaller decay amounts mean that old metric
+            // values influence the metric for a longer amount of time (i.e.: decay more slowly).
+            const double ActivityDecay = 0.1;
+
+            var delta = new SkeletonPoint
+            {
+                X = position.X - this.LastPosition.X,
+                Y = position.Y - this.LastPosition.Y,
+                Z = position.Z - this.LastPosition.Z
+            };
+
+            double deltaLengthSquared = (delta.X * delta.X) + (delta.Y * delta.Y) + (delta.Z * delta.Z);
+            double newMetric = DeltaScalingFactor * Math.Sqrt(deltaLengthSquared);
+
+            this.ActivityLevel = ((1.0 - ActivityDecay) * this.ActivityLevel) + (ActivityDecay * newMetric);
+
+            bool newIsActive = this.ActivityLevel >= ActivityMetricThreshold;
+
+            if (newIsActive != this.IsActive)
+            {
+                this.IsActive = newIsActive;
+                this.StateTransitionTimestamp = timestamp;
+            }
+
+            this.LastPosition = position;
+            this.LastUpdateId = updateId;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserStateChangedEventArgs.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserStateChangedEventArgs.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserStateChangedEventArgs.cs	(revision 28897)
@@ -0,0 +1,34 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserStateChangedEventArgs.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Event arguments for IUserStateManager.UserStateChanged event.
+    /// </summary>
+    public class UserStateChangedEventArgs : EventArgs
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="UserStateChangedEventArgs"/> class.
+        /// </summary>
+        /// <param name="message">
+        /// Representation of event as a web message to be sent.
+        /// </param>
+        public UserStateChangedEventArgs(EventMessage message)
+        {
+            this.Message = message;
+        }
+
+        /// <summary>
+        /// Representation of event as a web message to be sent.
+        /// </summary>
+        public EventMessage Message { get; private set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserViewerColorizer.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserViewerColorizer.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/Sensor/UserViewerColorizer.cs	(revision 28897)
@@ -0,0 +1,239 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UserViewerColorizer.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver.Sensor
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.Globalization;
+
+    using Microsoft.Kinect;
+    using Microsoft.Kinect.Toolkit.Interaction;
+
+    /// <summary>
+    /// Colorizes a depth image according to desired size and color mapping.
+    /// </summary>
+    internal class UserViewerColorizer
+    {
+        /// <summary>
+        /// Number of bytes per pixel in colorized image.
+        /// </summary>
+        private const int BytesPerPixel = 4;
+
+        /// <summary>
+        /// Background color is always transparent.
+        /// </summary>
+        private const int BackgroundColor = 0x00000000;
+
+        /// <summary>
+        /// Lookup table mapping player indexes (observable in depth image pixels) to 32-bit
+        /// ARGB color.
+        /// </summary>
+        private readonly int[] playerColorLookupTable;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="UserViewerColorizer"/> class.
+        /// </summary>
+        /// <param name="width">
+        /// Desired width of colorized image.
+        /// </param>
+        /// <param name="height">
+        /// Desired height of colorized image.
+        /// </param>
+        public UserViewerColorizer(int width, int height)
+        {
+            this.playerColorLookupTable = new int[SharedConstants.MaxUsersTracked + 1];
+
+            this.SetResolution(width, height);
+        }
+
+        /// <summary>
+        /// Desired width of colorized image.
+        /// </summary>
+        public int Width { get; private set; }
+
+        /// <summary>
+        /// Desired height of colorized image.
+        /// </summary>
+        public int Height { get; private set; }
+
+        /// <summary>
+        /// Buffer that holds colorized image.
+        /// </summary>
+        public byte[] Buffer { get; private set; }
+
+        /// <summary>
+        /// Updates the desired resolution for user viewer image.
+        /// </summary>
+        /// <param name="width">
+        /// Desired width of colorized image.
+        /// </param>
+        /// <param name="height">
+        /// Desired height of colorized image.
+        /// </param>
+        public void SetResolution(int width, int height)
+        {
+            if ((this.Buffer == null) || (this.Width != width) || (this.Height != height))
+            {
+                this.Width = width;
+                this.Height = height;
+
+                this.Buffer = new byte[width * height * BytesPerPixel];
+            }
+        }
+
+        /// <summary>
+        /// Color the user viewer image pixels appropriately given the specified depth image.
+        /// </summary>
+        /// <param name="depthImagePixels">
+        /// Depth image from which we will colorize user viewer image.
+        /// </param>
+        /// <param name="depthWidth">
+        /// Width of depth image, in pixels.
+        /// </param>
+        /// <param name="depthHeight">
+        /// Height of depth image, in pixels.
+        /// </param>
+        public void ColorizeDepthPixels(DepthImagePixel[] depthImagePixels, int depthWidth, int depthHeight)
+        {
+            if (depthImagePixels == null)
+            {
+                throw new ArgumentNullException("depthImagePixels");
+            }
+
+            if (depthWidth <= 0)
+            {
+                throw new ArgumentException(@"Width of depth image must be greater than zero", "depthWidth");
+            }
+
+            if (depthWidth % this.Width != 0)
+            {
+                throw new ArgumentException(
+                    string.Format(
+                        CultureInfo.InvariantCulture,
+                        "Depth image width '{0}' is not a multiple of the desired user viewer image width '{1}'",
+                        depthWidth,
+                        this.Width),
+                    "depthWidth");
+            }
+
+            if (depthHeight <= 0)
+            {
+                throw new ArgumentException(@"Height of depth image must be greater than zero", "depthHeight");
+            }
+
+            if (depthHeight % this.Height != 0)
+            {
+                throw new ArgumentException(
+                    string.Format(
+                        CultureInfo.InvariantCulture,
+                        "Depth image height '{0}' is not a multiple of the desired user viewer image height '{1}'",
+                        depthHeight,
+                        this.Height),
+                    "depthHeight");
+            }
+
+            int downscaleFactor = depthWidth / this.Width;
+            Debug.Assert(depthHeight / this.Height == downscaleFactor, "Downscale factor in x and y dimensions should be exactly the same.");
+
+            int pixelDisplacementBetweenRows = depthWidth * downscaleFactor;
+
+            unsafe
+            {
+                fixed (byte* colorBufferPtr = this.Buffer)
+                {
+                    fixed (DepthImagePixel* depthImagePixelPtr = depthImagePixels)
+                    {
+                        fixed (int* playerColorLookupPtr = this.playerColorLookupTable)
+                        {
+                            // Write color values using int pointers instead of byte pointers,
+                            // since each color pixel is 32-bits wide.
+                            int* colorBufferIntPtr = (int*)colorBufferPtr;
+                            DepthImagePixel* currentPixelRowPtr = depthImagePixelPtr;
+
+                            for (int row = 0; row < depthHeight; row += downscaleFactor)
+                            {
+                                DepthImagePixel* currentPixelPtr = currentPixelRowPtr;
+                                for (int column = 0; column < depthWidth; column += downscaleFactor)
+                                {
+                                    *colorBufferIntPtr++ = playerColorLookupPtr[currentPixelPtr->PlayerIndex];
+                                    currentPixelPtr += downscaleFactor;
+                                }
+
+                                currentPixelRowPtr += pixelDisplacementBetweenRows;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /// <summary>
+        /// Reset the lookup table mapping user indexes (observable in depth image pixels)
+        /// to 32-bit ARGB color to map all indexes to background color.
+        /// </summary>
+        public void ResetColorLookupTable()
+        {
+            // Initialize all player indexes to background color
+            for (int entryIndex = 0; entryIndex < this.playerColorLookupTable.Length; ++entryIndex)
+            {
+                this.playerColorLookupTable[entryIndex] = BackgroundColor;
+            }
+        }
+
+        /// <summary>
+        /// Update the lookup table mapping user indexes (observable in depth image pixels)
+        /// to 32-bit ARGB color.
+        /// </summary>
+        /// <param name="userInfos">
+        /// User information obtained from an <see cref="InteractionFrame"/>.
+        /// </param>
+        /// <param name="defaultUserColor">
+        /// 32-bit ARGB representation of default color assigned to users.
+        /// </param>
+        /// <param name="userStates">
+        /// Mapping between user tracking IDs and user states (obtained from
+        /// <see cref="IUserStateManager"/>).
+        /// </param>
+        /// <param name="userColors">
+        /// Mapping between user states and 32-bit ARGB color.
+        /// </param>
+        public void UpdateColorLookupTable(UserInfo[] userInfos, int defaultUserColor, IDictionary<int, string> userStates, IDictionary<string, int> userColors)
+        {
+            if ((userInfos == null) || (userStates == null) || (userColors == null))
+            {
+                this.ResetColorLookupTable();
+                return;
+            }
+
+            // Reset lookup table to have all player indexes map to default user color
+            for (int i = 1; i < this.playerColorLookupTable.Length; i++)
+            {
+                this.playerColorLookupTable[i] = defaultUserColor;
+            }
+
+            // Iterate through user tracking Ids to populate color table.
+            for (int i = 0; i < userInfos.Length; ++i)
+            {
+                // Player indexes in depth image are shifted by one in order to be able to
+                // use zero as a marker to mean "pixel does not correspond to any player".
+                int depthPlayerIndex = i + 1;
+                var trackingId = userInfos[i].SkeletonTrackingId;
+
+                string state;
+                if (userStates.TryGetValue(trackingId, out state))
+                {
+                    int color;
+                    if (userColors.TryGetValue(state, out color))
+                    {
+                        this.playerColorLookupTable[depthPlayerIndex] = color;
+                    }
+                }
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/SharedConstants.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/SharedConstants.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/SharedConstants.cs	(revision 28897)
@@ -0,0 +1,55 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="SharedConstants.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System.Threading.Tasks;
+
+    /// <summary>
+    /// Placeholder class that declares commonly used constants and configuration values.
+    /// </summary>
+    internal static class SharedConstants
+    {
+        /// <summary>
+        /// Invalid tracking identifier.
+        /// </summary>
+        internal const int InvalidUserTrackingId = 0;
+
+        /// <summary>
+        /// Maximum number of users that can get assigned a skeleton tracking id.
+        /// </summary>
+        internal const int MaxUsersTracked = 6;
+
+        /// <summary>
+        /// Array of standard character delimiters used to separate path components in a URI
+        /// string.
+        /// </summary>
+        internal static readonly char[] UriPathComponentDelimiters = new[] { '/', '?' };
+
+        /// <summary>
+        /// Empty task with a <see cref="TaskStatus.RanToCompletion"/> status, so awaiting
+        /// on task will return immediately.
+        /// </summary>
+        /// <remarks>
+        /// This is a helper value to be returned from synchronous functions that implement
+        /// an asynchronous contract, so clients can always await without needing to check
+        /// for null.
+        /// </remarks>
+        internal static readonly Task EmptyCompletedTask = Task.FromResult(0);
+
+        /// <summary>
+        /// Create a new task that will block all clients awaiting on it until
+        /// <see cref="Task.Start()"/> is called on it.
+        /// </summary>
+        /// <returns>
+        /// New await-able task.
+        /// </returns>
+        internal static Task CreateNonstartedTask()
+        {
+            return new Task(() => { });
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/ThreadHostedHttpListener.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/ThreadHostedHttpListener.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/ThreadHostedHttpListener.cs	(revision 28897)
@@ -0,0 +1,652 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="ThreadHostedHttpListener.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.Net;
+    using System.Threading;
+    using System.Windows.Threading;
+
+    using Microsoft.Kinect.Toolkit;
+
+    /// <summary>
+    /// HTTP request/response handler that listens for and processes HTTP requests in
+    /// a thread dedicated to that single purpose.
+    /// </summary>
+    public sealed class ThreadHostedHttpListener
+    {
+        /// <summary>
+        /// URI origins for which server is expected to be listening.
+        /// </summary>
+        private readonly List<Uri> ownedOriginUris = new List<Uri>();
+
+        /// <summary>
+        /// Origin Uris that are allowed to access data served by this listener.
+        /// </summary>
+        private readonly HashSet<Uri> allowedOriginUris = new HashSet<Uri>();
+
+        /// <summary>
+        /// Mapping between URI paths and factories of request handlers that can correspond
+        /// to them.
+        /// </summary>
+        private readonly Dictionary<string, IHttpRequestHandlerFactory> requestHandlerFactoryMap;
+
+        /// <summary>
+        /// SynchronizationContext wrapper used to track event handlers for Started event.
+        /// </summary>
+        private readonly ContextEventWrapper<EventArgs> startedContextWrapper =
+            new ContextEventWrapper<EventArgs>(ContextSynchronizationMethod.Post);
+
+        /// <summary>
+        /// SynchronizationContext wrapper used to track event handlers for Stopped event.
+        /// </summary>
+        private readonly ContextEventWrapper<EventArgs> stoppedContextWrapper =
+            new ContextEventWrapper<EventArgs>(ContextSynchronizationMethod.Post);
+
+        /// <summary>
+        /// Object used to synchronize access to data shared between client calling thread(s)
+        /// and listener thread.
+        /// </summary>
+        private readonly object lockObject = new object();
+
+        /// <summary>
+        /// Data shared between client calling thread(s) and listener thread.
+        /// </summary>
+        private SharedThreadData threadData;
+
+        /// <summary>
+        /// Initializes a new instance of the ThreadHostedHttpListener class.
+        /// </summary>
+        /// <param name="ownedOrigins">
+        /// URI origins for which server is expected to be listening.
+        /// </param>
+        /// <param name="allowedOrigins">
+        /// Origin Uris that are allowed to access data served by this listener, in addition
+        /// to owned origin Uris. May be empty or null if only owned origins are allowed to
+        /// access server data.
+        /// </param>
+        /// <param name="requestHandlerFactoryMap">
+        /// Mapping between URI paths and factories of request handlers that can correspond
+        /// to them.
+        /// </param>
+        public ThreadHostedHttpListener(IEnumerable<Uri> ownedOrigins, IEnumerable<Uri> allowedOrigins, Dictionary<string, IHttpRequestHandlerFactory> requestHandlerFactoryMap)
+        {
+            if (ownedOrigins == null)
+            {
+                throw new ArgumentNullException("ownedOrigins");
+            }
+
+            if (requestHandlerFactoryMap == null)
+            {
+                throw new ArgumentNullException("requestHandlerFactoryMap");
+            }
+
+            this.requestHandlerFactoryMap = requestHandlerFactoryMap;
+
+            foreach (var origin in ownedOrigins)
+            {
+                this.ownedOriginUris.Add(origin);
+
+                this.allowedOriginUris.Add(origin);
+            }
+
+            if (allowedOrigins != null)
+            {
+                foreach (var origin in allowedOrigins)
+                {
+                    this.allowedOriginUris.Add(origin);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Event used to signal that the server has started listening for connections.
+        /// </summary>
+        public event EventHandler<EventArgs> Started
+        {
+            add { this.startedContextWrapper.AddHandler(value); }
+
+            remove { this.startedContextWrapper.RemoveHandler(value); }
+        }
+
+        /// <summary>
+        /// Event used to signal that the server has stopped listening for connections.
+        /// </summary>
+        public event EventHandler<EventArgs> Stopped
+        {
+            add { this.stoppedContextWrapper.AddHandler(value); }
+
+            remove { this.stoppedContextWrapper.RemoveHandler(value); }
+        }
+
+        /// <summary>
+        /// True if listener has a thread actively listening for HTTP requests.
+        /// </summary>
+        public bool IsListening
+        {
+            get
+            {
+                return (this.threadData != null) && this.threadData.Thread.IsAlive;
+            }
+        }
+
+        /// <summary>
+        /// Start listening for requests.
+        /// </summary>
+        public void Start()
+        {
+            lock (this.lockObject)
+            {
+                Thread oldThread = null;
+
+                // If thread is currently running
+                if (this.IsListening)
+                {
+                    if (!this.threadData.StopRequestSent)
+                    {
+                        // Thread is already running and ready to handle requests, so there
+                        // is no need to start up a new one.
+                        return;
+                    }
+
+                    // If thread is running, but still in the process of winding down,
+                    // dissociate server from currently running thread without waiting for
+                    // thread to finish.
+                    // New thread will wait for previous thread to finish so that there is
+                    // no conflict between two different threads listening on the same URI
+                    // prefixes.
+                    oldThread = this.threadData.Thread;
+                    this.threadData = null;
+                }
+
+                this.threadData = new SharedThreadData
+                {
+                    Thread = new Thread(this.ListenerThread),
+                    PreviousThread = oldThread
+                };
+                this.threadData.Thread.Start(this.threadData);
+            }
+        }
+
+        /// <summary>
+        /// Stop listening for requests, optionally waiting for thread to finish.
+        /// </summary>
+        /// <param name="wait">
+        /// True if caller wants to wait until listener thread has terminated before
+        /// returning.
+        /// False otherwise.
+        /// </param>
+        public void Stop(bool wait)
+        {
+            Thread listenerThread = null;
+
+            lock (this.lockObject)
+            {
+                if (this.IsListening)
+                {
+                    if (!this.threadData.StopRequestSent)
+                    {
+                        // Request the thread to end, but keep remembering the old thread
+                        // data in case listener gets re-started immediately and new thread
+                        // has to wait for old thread to finish before getting started.
+                        SendStopMessage(this.threadData);
+                    }
+
+                    if (wait)
+                    {
+                        listenerThread = this.threadData.Thread;
+                    }
+                }
+            }
+
+            if (listenerThread != null)
+            {
+                listenerThread.Join();
+            }
+        }
+
+        /// <summary>
+        /// Stop listening for requests but don't wait for thread to finish.
+        /// </summary>
+        public void Stop()
+        {
+            this.Stop(false);
+        }
+
+        /// <summary>
+        /// Let listener thread know that it should wind down and stop listening for incoming
+        /// HTTP requests.
+        /// </summary>
+        /// <param name="threadData">
+        /// Object containing shared data used to communicate with listener thread.
+        /// </param>
+        private static void SendStopMessage(SharedThreadData threadData)
+        {
+            if (threadData.StateManager != null)
+            {
+                // If there's already a state manager associated with the server thread,
+                // tell it to stop listening.
+                threadData.StateManager.Stop();
+            }
+            
+            // Even if there's no dispatcher associated with the server thread yet,
+            // let thread know that it should bail out immediately after starting
+            // up, and never push a dispatcher frame at all.
+            threadData.StopRequestSent = true;
+        }
+
+        /// <summary>
+        /// Thread procedure for HTTP listener thread.
+        /// </summary>
+        /// <param name="data">
+        /// Object containing shared data used to communicate between client thread
+        /// and listener thread.
+        /// </param>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Main listener thread should never crash. Errors are captured in web server trace intead.")]
+        private void ListenerThread(object data)
+        {
+            var sharedData = (SharedThreadData)data;
+            
+            lock (this.lockObject)
+            {
+                if (sharedData.StopRequestSent)
+                {
+                    return;
+                }
+
+                sharedData.StateManager = new ListenerThreadStateManager(this.ownedOriginUris, this.allowedOriginUris, this.requestHandlerFactoryMap);
+            }
+
+            try
+            {
+                if (sharedData.PreviousThread != null)
+                {
+                    // Wait for the previous thread to finish so that only one thread can be listening
+                    // on the specified prefixes at any one time.
+                    sharedData.PreviousThread.Join();
+                    sharedData.PreviousThread = null;
+                }
+
+                //// After this point, it is expected that the only mutation of shared data triggered by
+                //// another thread will be to signal that this thread should stop listening.
+
+                this.startedContextWrapper.Invoke(this, EventArgs.Empty);
+
+                sharedData.StateManager.Listen();
+            }
+            catch (Exception e)
+            {
+                Trace.TraceError("Exception encountered while listening for connections:\n{0}", e);
+            }
+            finally
+            {
+                sharedData.StateManager.Dispose();
+
+                this.stoppedContextWrapper.Invoke(this, EventArgs.Empty);
+            }
+        }
+
+        /// <summary>
+        /// Represents state that needs to be shared between listener thread and client calling thread.
+        /// </summary>
+        private sealed class SharedThreadData
+        {
+            /// <summary>
+            /// Listener thread.
+            /// </summary>
+            public Thread Thread { get; set; }
+
+            /// <summary>
+            /// Object used to manage state and safely communicate with listener thread.
+            /// </summary>
+            public ListenerThreadStateManager StateManager { get; set; }
+
+            /// <summary>
+            /// True if the specific listener thread associated with this shared data
+            /// has previously been requested to stop.
+            /// False otherwise.
+            /// </summary>
+            public bool StopRequestSent { get; set; }
+            
+            /// <summary>
+            /// Previous listener thread, which had not fully finished processing before
+            /// a new thread was requested. Most of the time this will be null.
+            /// </summary>
+            public Thread PreviousThread { get; set; }
+        }
+
+        /// <summary>
+        /// Manages state used by a single listener thread.
+        /// </summary>
+        private sealed class ListenerThreadStateManager : IDisposable
+        {
+            /// <summary>
+            /// URI origins for which server is expected to be listening.
+            /// </summary>
+            private readonly List<Uri> ownedOriginUris;
+
+            /// <summary>
+            /// Origin Uris that are allowed to access data served by this listener.
+            /// </summary>
+            private readonly HashSet<Uri> allowedOriginUris;
+
+            /// <summary>
+            /// HttpListener used to wait for incoming HTTP requests.
+            /// </summary>
+            private readonly HttpListener listener = new HttpListener();
+
+            /// <summary>
+            /// Mapping between URI paths and factories of request handlers that can correspond
+            /// to them.
+            /// </summary>
+            private readonly Dictionary<string, IHttpRequestHandlerFactory> requestHandlerFactoryMap = new Dictionary<string, IHttpRequestHandlerFactory>();
+
+            /// <summary>
+            /// Mapping between URI paths and corresponding request handlers.
+            /// </summary>
+            private readonly Dictionary<string, IHttpRequestHandler> requestHandlerMap = new Dictionary<string, IHttpRequestHandler>();
+
+            /// <summary>
+            /// Dispatcher used to manage the queue of work done in listener thread. 
+            /// </summary>
+            private readonly Dispatcher dispatcher;
+
+            /// <summary>
+            /// Represents main execution loop in listener thread.
+            /// </summary>
+            private readonly DispatcherFrame frame;
+
+            /// <summary>
+            /// Asynchronous result indicating that we're currently waiting for some HTTP
+            /// client to initiate a request.
+            /// </summary>
+            private IAsyncResult getContextResult;
+
+            /// <summary>
+            /// Initializes a new instance of the ListenerThreadStateManager class.
+            /// </summary>
+            /// <param name="ownedOriginUris">
+            /// URI origins for which server is expected to be listening.
+            /// </param>
+            /// <param name="allowedOriginUris">
+            /// URI origins that are allowed to access data served by this listener.
+            /// </param>
+            /// <param name="requestHandlerFactoryMap">
+            /// Mapping between URI paths and factories of request handlers that can correspond
+            /// to them.
+            /// </param>
+            internal ListenerThreadStateManager(List<Uri> ownedOriginUris, HashSet<Uri> allowedOriginUris, Dictionary<string, IHttpRequestHandlerFactory> requestHandlerFactoryMap)
+            {
+                this.ownedOriginUris = ownedOriginUris;
+                this.allowedOriginUris = allowedOriginUris;
+                this.dispatcher = Dispatcher.CurrentDispatcher;
+                this.frame = new DispatcherFrame { Continue = true };
+                this.requestHandlerFactoryMap = requestHandlerFactoryMap;
+            }
+
+            /// <summary>
+            /// Releases resources used while listening for HTTP requests.
+            /// </summary>
+            public void Dispose()
+            {
+                this.listener.Close();
+            }
+
+            internal void Stop()
+            {
+                this.dispatcher.BeginInvoke((Action)(() =>
+                {
+                    foreach (var handler in this.requestHandlerMap)
+                    {
+                        handler.Value.Cancel();
+                    }
+
+                    this.frame.Continue = false;
+                }));
+            }
+            
+            /// <summary>
+            /// Initializes request handlers, listens for incoming HTTP requests until client
+            /// requests us to stop and then uninitializes request handlers.
+            /// </summary>
+            internal void Listen()
+            {
+                foreach (var entry in this.requestHandlerFactoryMap)
+                {
+                    var path = entry.Key;
+
+                    // To simplify lookup against "PathAndQuery" property of Uri objects,
+                    // we ensure that this has the starting forward slash that PathAndQuery
+                    // property values have.
+                    if (!path.StartsWith("/", StringComparison.OrdinalIgnoreCase))
+                    {
+                        path = "/" + path;
+                    }
+
+                    // Listen for each handler path under each origin
+                    foreach (var originUri in this.ownedOriginUris)
+                    {
+                        // HttpListener only listens to URIs that end in "/", but also remember
+                        // path exactly as requested by client associated with request handler,
+                        // to match subpath expressions expected by handler
+                        var uriBuilder = new UriBuilder(originUri) { Path = path };
+                        var prefix = uriBuilder.ToString();
+                        if (!prefix.EndsWith("/", StringComparison.OrdinalIgnoreCase))
+                        {
+                            prefix = prefix + "/";
+                        }
+
+                        listener.Prefixes.Add(prefix);
+                    }
+
+                    requestHandlerMap.Add(path, entry.Value.CreateHandler());
+                }
+            
+                this.listener.Start();
+                try
+                {
+                    var initialize = (Action)(async () =>
+                        {
+                            foreach (var handler in requestHandlerMap.Values)
+                            {
+                                await handler.InitializeAsync();
+                            }
+
+                            this.getContextResult = this.listener.BeginGetContext(this.GetContextCallback, null);
+                        });
+                    this.dispatcher.BeginInvoke(DispatcherPriority.Normal, initialize);
+                    Dispatcher.PushFrame(this.frame);
+
+                    var uninitializeFrame = new DispatcherFrame { Continue = true };
+                    var uninitialize = (Action)(async () =>
+                        {
+                            foreach (var handler in this.requestHandlerMap.Values)
+                            {
+                                await handler.UninitializeAsync();
+                            }
+
+                            uninitializeFrame.Continue = false;
+                        });
+                    this.dispatcher.BeginInvoke(DispatcherPriority.Normal, uninitialize);
+                    Dispatcher.PushFrame(uninitializeFrame);
+                }
+                finally
+                {
+                    this.listener.Stop();
+                }
+            }
+
+            /// <summary>
+            /// Close response stream and associate a status code with response.
+            /// </summary>
+            /// <param name="context">
+            /// Context whose response we should close.
+            /// </param>
+            /// <param name="statusCode">
+            /// Status code.
+            /// </param>
+            private static void CloseResponse(HttpListenerContext context, HttpStatusCode statusCode)
+            {
+                try
+                {
+                    context.Response.StatusCode = (int)statusCode;
+                    context.Response.Close();
+                }
+                catch (HttpListenerException e)
+                {
+                    Trace.TraceWarning(
+                        "Problem encountered while sending response for kinect sensor request. Client might have aborted request. Cause: \"{0}\"", e.Message);
+                }
+            }
+
+            /// <summary>
+            /// Checks if this is corresponds to a cross-origin request and, if so, prepares
+            /// the response with the appropriate headers or even body, if necessary.
+            /// </summary>
+            /// <param name="context">
+            /// Listener context containing the request and associated response object.
+            /// </param>
+            /// <returns>
+            /// True if request was initiated by an explicitly allowed origin URI.
+            /// False if origin URI for request is not allowed.
+            /// </returns>
+            private bool HandleCrossOrigin(HttpListenerContext context)
+            {
+                const string OriginHeader = "Origin";
+                const string AllowOriginHeader = "Access-Control-Allow-Origin";
+                const string RequestHeadersHeader = "Access-Control-Request-Headers";
+                const string AllowHeadersHeader = "Access-Control-Allow-Headers";
+                const string RequestMethodHeader = "Access-Control-Request-Method";
+                const string AllowMethodHeader = "Access-Control-Allow-Methods";
+
+                // Origin header is not required, since it is up to browser to
+                // detect when cross-origin security checks are needed.
+                var originValue = context.Request.Headers[OriginHeader];
+                if (originValue != null)
+                {
+                    // If origin header is present, check if it's in allowed list
+                    Uri originUri;
+                    try
+                    {
+                        originUri = new Uri(originValue);
+                    }
+                    catch (UriFormatException)
+                    {
+                        return false;
+                    }
+
+                    if (!this.allowedOriginUris.Contains(originUri))
+                    {
+                        return false;
+                    }
+
+                    // We allow all origins to access this server's data
+                    context.Response.Headers.Add(AllowOriginHeader, originValue);
+                }
+
+                var requestHeaders = context.Request.Headers[RequestHeadersHeader];
+                if (requestHeaders != null)
+                {
+                    // We allow all headers in cross-origin server requests
+                    context.Response.Headers.Add(AllowHeadersHeader, requestHeaders);
+                }
+
+                var requestMethod = context.Request.Headers[RequestMethodHeader];
+                if (requestMethod != null)
+                {
+                    // We allow all methods in cross-origin server requests
+                    context.Response.Headers.Add(AllowMethodHeader, requestMethod);
+                }
+
+                return true;
+            }
+
+            /// <summary>
+            /// Callback used by listener to let us know when we have received an HTTP
+            /// context corresponding to an earlier call to BeginGetContext.
+            /// </summary>
+            /// <param name="result">
+            /// Status of asynchronous operation.
+            /// </param>
+            private void GetContextCallback(IAsyncResult result)
+            {
+                this.dispatcher.BeginInvoke((Action)(() =>
+                    {
+                        if (!this.listener.IsListening)
+                        {
+                            return;
+                        }
+
+                        Debug.Assert(result == this.getContextResult, "remembered GetContext result should match result handed to callback.");
+
+                        var httpListenerContext = this.listener.EndGetContext(result);
+                        this.getContextResult = null;
+
+                        this.HandleRequestAsync(httpListenerContext);
+
+                        if (this.frame.Continue)
+                        {
+                            this.getContextResult = this.listener.BeginGetContext(this.GetContextCallback, null);
+                        }
+                    }));
+            }
+
+            /// <summary>
+            /// Handle an HTTP request asynchronously
+            /// </summary>
+            /// <param name="httpListenerContext">
+            /// Context containing HTTP request and response information
+            /// </param>
+            private async void HandleRequestAsync(HttpListenerContext httpListenerContext)
+            {
+                var uri = httpListenerContext.Request.Url;
+                var clientAddress = httpListenerContext.Request.RemoteEndPoint != null
+                                        ? httpListenerContext.Request.RemoteEndPoint.Address
+                                        : IPAddress.None;
+                var requestOverview = string.Format("URI=\"{0}\", client=\"{1}\"", uri, clientAddress);
+
+                try
+                {
+                    bool foundHandler = false;
+
+                    if (!this.HandleCrossOrigin(httpListenerContext))
+                    {
+                        CloseResponse(httpListenerContext, HttpStatusCode.Forbidden);
+                    }
+                    else
+                    {
+                        foreach (var entry in this.requestHandlerMap)
+                        {
+                            if (uri.PathAndQuery.StartsWith(entry.Key, StringComparison.InvariantCultureIgnoreCase))
+                            {
+                                foundHandler = true;
+                                var subPath = uri.PathAndQuery.Substring(entry.Key.Length);
+                                await entry.Value.HandleRequestAsync(httpListenerContext, subPath);
+
+                                break;
+                            }
+                        }
+
+                        if (!foundHandler)
+                        {
+                            CloseResponse(httpListenerContext, HttpStatusCode.NotFound);
+                        }
+                    }
+
+                    Trace.TraceInformation("Request for {0} completed with result: {1}", requestOverview, httpListenerContext.Response.StatusCode);
+                }
+                catch (Exception e)
+                {
+                    Trace.TraceError("Exception encountered while handling request for {0}:\n{1}", requestOverview, e);
+                    CloseResponse(httpListenerContext, HttpStatusCode.InternalServerError);
+                }
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/UriUtilities.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/UriUtilities.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/UriUtilities.cs	(revision 28897)
@@ -0,0 +1,85 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="UriUtilities.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Diagnostics;
+
+    /// <summary>
+    /// Static class that defines uri manipulation utilities.
+    /// </summary>
+    public static class UriUtilities
+    {
+        /// <summary>
+        /// Separator between URI path segments.
+        /// </summary>
+        public const string PathSeparator = "/";
+
+        /// <summary>
+        /// Concatenate specified path segments at the end of specified URI.
+        /// </summary>
+        /// <param name="uri">
+        /// Absolute URI to serve as starting point of concatenation.
+        /// </param>
+        /// <param name="pathSegments">
+        /// Path segments to concatenate at the end of URI.
+        /// </param>
+        /// <returns>
+        /// URI that represents the combination of the specified uri and path segments.
+        /// May be null if uri segments could not be concatenated.
+        /// </returns>
+        public static Uri ConcatenateSegments(this Uri uri, params string[] pathSegments)
+        {
+            Uri result = uri;
+
+            if (uri == null)
+            {
+                throw new ArgumentNullException("uri");
+            }
+
+            if (pathSegments == null)
+            {
+                throw new ArgumentNullException("pathSegments");
+            }
+
+            for (int i = 0; i < pathSegments.Length; ++i)
+            {
+                var segment = pathSegments[i];
+
+                if (segment == null)
+                {
+                    throw new ArgumentException(@"One or more of the specified path segments is null", "pathSegments");
+                }
+
+                if (i < pathSegments.Length - 1)
+                {
+                    // For each element other than the last element, make sure it ends in the
+                    // path separator character so that it's treated as a path segment rather
+                    // than an endpoint or resource (see CoInternetCombineIUri documentation
+                    // for an explanation of standard URI combination behavior)
+                    segment = segment.EndsWith(PathSeparator, StringComparison.OrdinalIgnoreCase) ? segment : (segment + PathSeparator);
+                }
+
+                // Now call the standard URI class to take care of canonicalization and other
+                // combination functionality
+                var previous = result;
+                try
+                {
+                    result = new Uri(previous, new Uri(segment.Trim(), UriKind.Relative));
+                }
+                catch (UriFormatException)
+                {
+                    Trace.TraceError("Unable to concatenate uri \"{0}\" with path segment \"{1}\"", previous, segment);
+                    result = null;
+                    break;
+                }
+            }
+
+            return result;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketChannelBase.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketChannelBase.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketChannelBase.cs	(revision 28897)
@@ -0,0 +1,612 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="WebSocketChannelBase.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Diagnostics;
+    using System.Net;
+    using System.Net.WebSockets;
+    using System.Threading;
+    using System.Threading.Tasks;
+    using System.Windows.Threading;
+
+    /// <summary>
+    /// Base class representing a web socket communication channel.
+    /// </summary>
+    /// <remarks>
+    /// This class has asynchronous functionality but it is NOT thread-safe, so it is expected
+    /// to be called by a single-threaded scheduler, e.g.: one running over a Dispatcher or
+    /// other SynchronizationContext implementation.
+    /// </remarks>
+    public class WebSocketChannelBase : IDisposable
+    {
+        /// <summary>
+        /// Maximum time allowed to pass between successive checks for web socket disconnection.
+        /// </summary>
+        private static readonly TimeSpan DisconnectionCheckTimeout = TimeSpan.FromSeconds(2.0);
+
+        /// <summary>
+        /// Timer used to ensure we periodically check for disconnection even if no messages are
+        /// being sent between server and client.
+        /// </summary>
+        /// <remarks>
+        /// The disconnection timer helps us notice that there is a web socket resource ready to
+        /// be disposed.
+        /// </remarks>
+        private readonly DispatcherTimer disconnectionCheckTimer = new DispatcherTimer();
+
+        /// <summary>
+        /// Action to perform when web socket becomes closed.
+        /// </summary>
+        private readonly Action<WebSocketChannelBase> closedAction;
+
+        /// <summary>
+        /// Non-null if someone has requested that this object be closed and disposed. Null otherwise.
+        /// </summary>
+        private Task disposingTask;
+
+        /// <summary>
+        /// Non-null if channel is currently sending a message. Null otherwise.
+        /// </summary>
+        private Task sendingTask;
+
+        /// <summary>
+        /// True if we're performing the protocol close handshake and notifying clients
+        /// that socket has been closed.
+        /// </summary>
+        private bool isClosing;
+
+        /// <summary>
+        /// True if this object has already been disposed. False otherwise.
+        /// </summary>
+        private bool isDisposed;
+
+        /// <summary>
+        /// True if Closed event has been sent already. False otherwise.
+        /// </summary>
+        private bool closedSent;
+
+        /// <summary>
+        /// True if disconnection monitoring task has been started, false otherwise.
+        /// </summary>
+        private bool isDisconnectionMonitorStarted;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebSocketChannelBase"/> class.
+        /// </summary>
+        /// <param name="context">
+        /// Web socket context.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket becomes closed.
+        /// </param>
+        protected WebSocketChannelBase(WebSocketContext context, Action<WebSocketChannelBase> closedAction)
+        {
+            if ((context == null) || (context.WebSocket == null))
+            {
+                throw new ArgumentNullException("context", @"Context and associated web socket must not be null");
+            }
+
+            this.Socket = context.WebSocket;
+            this.CancellationTokenSource = new CancellationTokenSource();
+
+            this.disconnectionCheckTimer.Interval = DisconnectionCheckTimeout;
+            this.disconnectionCheckTimer.Tick += this.OnDisconnectionCheckTimerTick;
+            this.disconnectionCheckTimer.Start();
+
+            this.closedAction = closedAction;
+        }
+
+        /// <summary>
+        /// True if this web socket channel is open for sending/receiving messages.
+        /// </summary>
+        public bool IsOpen
+        {
+            get
+            {
+                return !this.isDisposed && ((this.Socket.State == WebSocketState.Open) || (this.Socket.State == WebSocketState.Connecting));
+            }
+        }
+
+        /// <summary>
+        /// Web socket used for communications.
+        /// </summary>
+        protected WebSocket Socket { get; private set; }
+
+        /// <summary>
+        /// Token source used to cancel pending socket operations.
+        /// </summary>
+        protected CancellationTokenSource CancellationTokenSource { get; private set; }
+
+        /// <summary>
+        /// Cancel all pending socket operations.
+        /// </summary>
+        public void Cancel()
+        {
+            this.CancellationTokenSource.Cancel();
+        }
+
+        /// <summary>
+        /// Implement IDisposable.
+        /// </summary>
+        /// <remarks>
+        /// Releases resources right away if underlying socket is already closed.
+        /// Otherwise asynchronously awaits to complete web socket close handshake
+        /// and then disposes resources.
+        /// Call <see cref="CloseAsync"/> instead to be able to await for socket to
+        /// finish disposing resources.
+        /// </remarks>
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Asynchronous disposal. Does call Dispose(true) and GC.SuppressFinalize.")]
+        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1816:CallGCSuppressFinalizeCorrectly", Justification = "Asynchronous disposal. Does call Dispose(true) and GC.SuppressFinalize.")]
+        public async void Dispose()
+        {
+            await this.CloseAsync();
+        }
+
+        /// <summary>
+        /// Cancel all pending operations, initiate web socket close handshake and send Closed
+        /// event to clients  if necessary.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        /// <remarks>
+        /// Disposes of socket resources. There is no need to call <see cref="Dispose"/> after
+        /// calling this method.
+        /// </remarks>
+        public async Task CloseAsync()
+        {
+            if (this.disposingTask != null)
+            {
+                await this.disposingTask;
+                return;
+            }
+
+            this.disposingTask = this.CloseAndDisposeAsync();
+            await this.disposingTask;
+        }
+
+        /// <summary>
+        /// Determine if this web socket channel is open for sending/receiving messages
+        /// or if it has been closed
+        /// </summary>
+        /// <returns>
+        /// True if this web socket channel is still open, false otherwise.
+        /// </returns>
+        /// <remarks>
+        /// This call is expected to perform more comprehensive connection state checks
+        /// than IsOpen property, which might include sending remote messages, if the
+        /// specific <see cref="WebSocketChannelBase"/> subclass warrants it, so callers
+        /// should be careful not to call this method too often.
+        /// </remarks>
+        public virtual bool CheckConnectionStatus()
+        {
+            return this.IsOpen;
+        }
+
+        /// <summary>
+        /// Try to establish a web socket context from the specified HTTP request context.
+        /// </summary>
+        /// <param name="listenerContext">
+        /// HTTP listener context.
+        /// </param>
+        /// <returns>
+        /// A web socket context if communications channel was successfully established.
+        /// Null if web socket channel could not be established.
+        /// </returns>
+        /// <remarks>
+        /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
+        /// web socket channel could not be established, an appropriate status code will be
+        /// returned via <paramref name="listenerContext"/>'s Response property, and the return
+        /// value will be null.
+        /// </remarks>
+        protected static async Task<WebSocketContext> HandleWebSocketRequestAsync(HttpListenerContext listenerContext)
+        {
+            if (!listenerContext.Request.IsWebSocketRequest)
+            {
+                listenerContext.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+                listenerContext.Response.Close();
+                return null;
+            }
+
+            try
+            {
+                return await listenerContext.AcceptWebSocketAsync(null);
+            }
+            catch (WebSocketException)
+            {
+                listenerContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+                listenerContext.Response.Close();
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// Sends data over the WebSocket connection asynchronously.
+        /// </summary>
+        /// <param name="buffer">
+        /// The buffer to be sent over the connection.
+        /// </param>
+        /// <param name="messageType">
+        /// Indicates whether the application is sending a binary or text message.
+        /// </param>
+        /// <returns>
+        /// true if the message was sent successfully. false otherwise.
+        /// </returns>
+        protected async Task<bool> SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType)
+        {
+            if (await this.CheckForDisconnectionAsync())
+            {
+                return false;
+            }
+
+            if (this.sendingTask != null)
+            {
+                Trace.TraceError("Channel is unable to start sending a new websocket message while it is already sending a previous message.");
+                return false;
+            }
+
+            bool result = true;
+
+            // We create a separate task from the one corresponding to the WebSocket.SendAsync
+            // method call, because we need to provide a guarantee to other parts of the code
+            // waiting on this task that SendAsync method call has returned by the time the
+            // sendingTask has completed.
+            // If we don't do this, it would be possible for other code awaiting on task to
+            // start executing before SendAsync stack frame gets a chance to clear the "sending"
+            // state to get ready to receive other calls that initiate data sending.
+            this.sendingTask = SharedConstants.CreateNonstartedTask();
+
+            try
+            {
+                await this.Socket.SendAsync(buffer, messageType, true, this.CancellationTokenSource.Token);
+            }
+            catch (Exception e)
+            {
+                if (!IsSendReceiveException(e))
+                {
+                    throw;
+                }
+
+                result = false;
+            }
+            finally
+            {
+                this.sendingTask.Start();
+                this.sendingTask = null;
+            }
+
+            if (!result)
+            {
+                // Client might have disconnected
+                await this.CheckForDisconnectionAsync();
+            }
+
+            return result;
+        }
+
+        /// <summary>
+        /// Receives data from the WebSocket connection asynchronously.
+        /// </summary>
+        /// <param name="buffer">
+        /// References the application buffer that is the storage location for the received data.
+        /// </param>
+        /// <returns>
+        /// A receive result representing a full message if we could receive one successfully
+        /// within the specified buffer. null otherwise.
+        /// </returns>
+        /// <remarks>
+        /// This method receives data from socket until either we get to the end of message sent
+        /// by socket client or we fill the specified buffer. If we don't get to end of message
+        /// within the allocated buffer space, we return a result value indicating that message
+        /// is still incomplete.
+        /// </remarks>
+        protected async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer)
+        {
+            if (await this.CheckForDisconnectionAsync())
+            {
+                return null;
+            }
+
+            int receiveOffset = 0;
+            int receiveCount = 0;
+            bool isResponseComplete = false;
+            WebSocketReceiveResult receiveResult = null;
+                
+            try
+            {
+                while (!isResponseComplete)
+                {
+                    if (receiveCount >= buffer.Count)
+                    {
+                        // If we've filled up the buffer and response message is still not
+                        // complete, we won't have space for response at all, so just return
+                        // incomplete message.
+                        return new WebSocketReceiveResult(
+                            receiveCount, receiveResult != null ? receiveResult.MessageType : WebSocketMessageType.Text, false);
+                    }
+
+                    receiveResult = await this.Socket.ReceiveAsync(new ArraySegment<byte>(buffer.Array, receiveOffset + buffer.Offset, buffer.Count - receiveOffset), this.CancellationTokenSource.Token);
+
+                    if (receiveResult.MessageType == WebSocketMessageType.Close)
+                    {
+                        await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByClient, true);
+                        return null;
+                    }
+
+                    receiveCount += receiveResult.Count;
+                    receiveOffset += receiveResult.Count;
+                    isResponseComplete = receiveResult.EndOfMessage;
+                }
+
+                receiveResult = new WebSocketReceiveResult(receiveCount, receiveResult.MessageType, true);
+            }
+            catch (Exception e)
+            {
+                if (!IsSendReceiveException(e))
+                {
+                    throw;
+                }
+
+                return null;
+            }
+
+            return receiveResult;
+        }
+
+        /// <summary>
+        /// Dispose resources owned by this object.
+        /// </summary>
+        /// <param name="disposing">
+        /// True if called from IDisposable.Dispose. False if called by runtime during
+        /// finalization.
+        /// </param>
+        protected virtual void Dispose(bool disposing)
+        {
+            if (!this.isDisposed)
+            {
+                if (disposing)
+                {
+                    this.disconnectionCheckTimer.Stop();
+
+                    this.Socket.Dispose();
+                    this.Socket = null;
+
+                    this.CancellationTokenSource.Dispose();
+                    this.CancellationTokenSource = null;
+                }
+
+                this.isDisposed = true;
+            }
+        }
+
+        /// <summary>
+        /// Start monitoring for client disconnection requests.
+        /// </summary>
+        /// <remarks>
+        /// Should not be called if legitimate (non-disconnection request) messages are
+        /// expected from client.
+        /// </remarks>
+        protected async void StartDisconnectionMonitor()
+        {
+            if (this.isDisposed)
+            {
+                return;
+            }
+
+            if (this.isDisconnectionMonitorStarted)
+            {
+                // Monitor is already started
+                return;
+            }
+
+            this.isDisconnectionMonitorStarted = true;
+            var dummyBuffer = new byte[1];
+
+            try
+            {
+                WebSocketReceiveResult result;
+
+                do
+                {
+                    // We don't use a real cancellation token because explicitly cancelling a
+                    // send or receive operation will put the socket in "Aborted" state, and
+                    // it will appear to client as if we have forcefully closed the connection,
+                    // when in reality our goal is just to passively monitor for client closing
+                    // requests until socket connection is closed by either party.
+                    var receiveTask = this.Socket.ReceiveAsync(new ArraySegment<byte>(dummyBuffer), CancellationToken.None);
+                    await receiveTask;
+
+                    if (!receiveTask.IsCompleted)
+                    {
+                        return;
+                    }
+
+                    result = receiveTask.Result;
+                }
+                while (result.MessageType != WebSocketMessageType.Close); // If message received was not a close message, keep looping.
+
+                // We have received a close message, so initiate closing actions.
+                await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByClient, true);
+            }
+            catch (WebSocketException)
+            {
+                // If connection closing is server-driven, our call to receive data will throw
+                // a WebSocketException and we won't receive any closing message from client.
+            }
+        }
+
+        /// <summary>
+        /// Determine if the specified exception is one of the standard web socket send/receive
+        /// exceptions.
+        /// </summary>
+        /// <param name="ex">
+        /// Caught exception.
+        /// </param>
+        /// <returns>
+        /// True if exception is of a type recognized as a standard web socket send/receive
+        /// exception.
+        /// </returns>
+        private static bool IsSendReceiveException(Exception ex)
+        {
+            return (ex is WebSocketException) || (ex is HttpListenerException) || (ex is OperationCanceledException);
+        }
+
+        /// <summary>
+        /// Checks if socket has been disconnected
+        /// </summary>
+        /// <returns>
+        /// true if web socket has been disconnected already. false otherwise.
+        /// </returns>
+        private async Task<bool> CheckForDisconnectionAsync()
+        {
+            if (this.isDisposed || this.isClosing)
+            {
+                return true;
+            }
+
+            // Every time we check for disconnection, stop the disconnection check timer
+            this.disconnectionCheckTimer.Stop();
+
+            bool isDisconnected = !this.IsOpen;
+
+            if (!isDisconnected)
+            {
+                // re-start timer if we're still connected
+                this.disconnectionCheckTimer.Start();
+            }
+            else
+            {
+                this.CancellationTokenSource.Cancel();
+                await this.SendCloseMessagesAsync(Properties.Resources.SocketClientDisconnectionDetected, false);
+            }
+
+            return isDisconnected;
+        }
+
+        /// <summary>
+        /// Send Closed event if it hasn't been sent already.
+        /// </summary>
+        private void SendClosed()
+        {
+            if (!this.closedSent)
+            {
+                if (this.closedAction != null)
+                {
+                    this.closedAction(this);
+                }
+
+                this.closedSent = true;
+            }
+        }
+
+        /// <summary>
+        /// Cancel all pending operations, initiate web socket close handshake, send Closed
+        /// event to clients if necessary, and dispose of socket resources.
+        /// </summary>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task CloseAndDisposeAsync()
+        {
+            if (!await this.CheckForDisconnectionAsync())
+            {
+                this.CancellationTokenSource.Cancel();
+
+                await this.SendCloseMessagesAsync(Properties.Resources.SocketClosedByServer, false);
+            }
+
+            this.Dispose(true);
+
+            GC.SuppressFinalize(this);
+        }
+
+        /// <summary>
+        /// Gracefully close socket by performing the protocol close handshake and notify clients
+        /// that socket has been closed.
+        /// </summary>
+        /// <param name="closeMessage">
+        /// Human readable explanation as to why the connection is being closed.
+        /// </param>
+        /// <param name="awaitAcknowledgement">
+        /// True if we should wait for client acknowledgement of socket close request.
+        /// False if we shouldn't wait.
+        /// </param>
+        /// <returns>
+        /// Await-able task.
+        /// </returns>
+        private async Task SendCloseMessagesAsync(string closeMessage, bool awaitAcknowledgement)
+        {
+            // If we're already closing, there's no need to start closing again
+            if (this.isClosing)
+            {
+                return;
+            }
+
+            this.isClosing = true;
+
+            try
+            {
+                // Store task reference because instance variable could be set to null while
+                // we await, and await operator will reference task again once we are done
+                // awaiting.
+                var task = this.sendingTask;
+                if (task != null)
+                {
+                    // Wait for the sending task to complete, if it is pending.
+                    await task;
+                }
+            }
+            catch (Exception e)
+            {
+                if (!IsSendReceiveException(e))
+                {
+                    throw;
+                }
+            }
+
+            try
+            {
+                if (awaitAcknowledgement)
+                {
+                    await this.Socket.CloseAsync(WebSocketCloseStatus.NormalClosure, closeMessage, CancellationToken.None);
+                }
+                else
+                {
+                    await this.Socket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, closeMessage, CancellationToken.None);
+                }
+            }
+            catch (Exception e)
+            {
+                if (!IsSendReceiveException(e))
+                {
+                    throw;
+                }
+
+                Trace.TraceWarning(
+                    "Problem encountered while closing socket. Client might have gone away abruptly without initiating socket close handshake.\n{0}",
+                    e);
+            }
+
+            this.SendClosed();
+        }
+
+        /// <summary>
+        /// Handler for Tick event of disconnection check timer.
+        /// </summary>
+        /// <param name="sender">
+        /// Object that sent this event.
+        /// </param>
+        /// <param name="args">
+        /// Event arguments.
+        /// </param>
+        private async void OnDisconnectionCheckTimerTick(object sender, EventArgs args)
+        {
+            await this.CheckForDisconnectionAsync();
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketEventChannel.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketEventChannel.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketEventChannel.cs	(revision 28897)
@@ -0,0 +1,197 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="WebSocketEventChannel.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.Net;
+    using System.Net.WebSockets;
+    using System.Threading.Tasks;
+
+    /// <summary>
+    /// Web socket communication channel used for sending events to clients.
+    /// </summary>
+    public sealed class WebSocketEventChannel : WebSocketChannelBase
+    {
+        /// <summary>
+        /// If more than this number of send tasks are overlapping (i.e.: simultaneously
+        /// awaiting to finish sending), it is considered as an indication that more
+        /// events are happening at a faster pace than can be handled by the underlying
+        /// web socket.
+        /// </summary>
+        private const int MaximumOverlappingTaskCount = 10;
+
+        /// <summary>
+        /// Keeps track of task representing the last send request initiated.
+        /// </summary>
+        private Task lastSendTask;
+
+        /// <summary>
+        /// Number of overlapping send tasks.
+        /// </summary>
+        private int overlappingTaskCount;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebSocketEventChannel"/> class.
+        /// </summary>
+        /// <param name="context">
+        /// Web socket context.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket becomes closed.
+        /// </param>
+        internal WebSocketEventChannel(WebSocketContext context, Action<WebSocketChannelBase> closedAction)
+            : base(context, closedAction)
+        {
+            // Always monitor for disconnections.
+            this.StartDisconnectionMonitor();
+        }
+
+        /// <summary>
+        /// Attempt to open a new event channel from the specified HTTP listener context.
+        /// </summary>
+        /// <param name="listenerContext">
+        /// HTTP listener context.
+        /// </param>
+        /// <param name="openedAction">
+        /// Action to perform when web socket is opened.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket is closed.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <remarks>
+        /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
+        /// web socket channel could not be established, an appropriate status code will be
+        /// returned via <paramref name="listenerContext"/>'s Response property.
+        /// </remarks>
+        public static async void TryOpenAsync(
+            HttpListenerContext listenerContext, Action<WebSocketEventChannel> openedAction, Action<WebSocketEventChannel> closedAction)
+        {
+            var socketContext = await HandleWebSocketRequestAsync(listenerContext);
+
+            if (socketContext != null)
+            {
+                var channel = new WebSocketEventChannel(socketContext, closedChannel => closedAction(closedChannel as WebSocketEventChannel));
+                openedAction(channel);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously sends a batch of messages over the web socket channel.
+        /// </summary>
+        /// <param name="messages">
+        /// Batch of messages to be sent as an atomic block through the web socket.
+        /// </param>
+        /// <returns>
+        /// true if the messages were sent successfully. false otherwise.
+        /// </returns>
+        public async Task<bool> SendMessagesAsync(params WebSocketMessage[] messages)
+        {
+            if (messages.Length == 0)
+            {
+                // No work to be done
+                return true;
+            }
+
+            ++this.overlappingTaskCount;
+
+            Task<Task<bool>> getSendTaskTask = null;
+
+            try
+            {
+                if (this.overlappingTaskCount > MaximumOverlappingTaskCount)
+                {
+                    throw new InvalidOperationException(@"Events are being generated faster than web socket channel can handle");
+                }
+
+                // Create a function whose only purpose is to return a task representing
+                // the real work that needs to be done to send the messages, and a task
+                // corresponding to this function.
+                // We're basically creating a linked list of tasks, where each task waits
+                // for the previous task (if it exists) to finish processing before starting
+                // to do its own work.
+                // We do things in this way rather than adding message data to a queue that
+                // then processes each message in order, to avoid the additional data copy
+                // (and potential allocation) that would be required to allow for deferred
+                // processing. The current contract has message data be processed in the same
+                // function stack frame in which an awaiting client called us.
+                Func<object, Task<bool>> getSendTaskFunction = previousTask => this.SerializedSendMessages(previousTask, messages);
+                if (this.lastSendTask == null)
+                {
+                    getSendTaskTask = new Task<Task<bool>>(getSendTaskFunction, null);
+                    getSendTaskTask.Start();
+                }
+                else
+                {
+                    getSendTaskTask = this.lastSendTask.ContinueWith(getSendTaskFunction);
+                }
+                
+                this.lastSendTask = getSendTaskTask;
+
+                // After awaiting for this nested task we will have a task that represents
+                // the real work of actually sending the messages.
+                Task<bool> sendTask = await getSendTaskTask;
+                return await sendTask;
+            }
+            finally
+            {
+                // If no other send tasks came in while we were waiting for this send batch
+                // to complete, clear the last send task state so that future batches don't
+                // think that they are overlapping a previous batch.
+                if (this.lastSendTask == getSendTaskTask)
+                {
+                    this.lastSendTask = null;
+                }
+
+                --this.overlappingTaskCount;
+
+                Debug.Assert((this.overlappingTaskCount > 0) || (this.lastSendTask == null), "Whenever no more tasks overlap, there should be no pending send task to await");
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously sends a batch of messages over the web socket channel after ensuring
+        /// that all previous message batches have completed the sending process.
+        /// </summary>
+        /// <param name="previous">
+        /// Previous message-batch-sending task that needs to complete before we start
+        /// sending new messages. May be null, if this batch was requested after all previous
+        /// batches had already finished sending.
+        /// </param>
+        /// <param name="messages">
+        /// Batch of messages to send.
+        /// </param>
+        /// <returns>
+        /// true if the messages were sent successfully. false otherwise.
+        /// </returns>
+        private async Task<bool> SerializedSendMessages(object previous, IEnumerable<WebSocketMessage> messages)
+        {
+            var previousTask = previous as Task<Task<bool>>;
+            if (previousTask != null)
+            {
+                // The previous task, if non-null, is a task that returns the task that does
+                // the real work of sending the previous batch of messages, so we need to wait
+                // for the result task to finish to ensure that messages are serialized in the
+                // expected order.
+                await previousTask.Result;
+            }
+
+            foreach (var message in messages)
+            {
+                if (!await this.SendAsync(message.Content, message.MessageType))
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketMessage.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketMessage.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketMessage.cs	(revision 28897)
@@ -0,0 +1,42 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="WebSocketMessage.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Net.WebSockets;
+
+    /// <summary>
+    /// Represents a web socket message with associated type (UTF8 versus binary).
+    /// </summary>
+    public class WebSocketMessage
+    {
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebSocketMessage"/> class.
+        /// </summary>
+        /// <param name="messageContent">
+        /// Message content.
+        /// </param>
+        /// <param name="messageType">
+        /// Type of message (UTF8 versus binary).
+        /// </param>
+        public WebSocketMessage(ArraySegment<byte> messageContent, WebSocketMessageType messageType)
+        {
+            this.Content = messageContent;
+            this.MessageType = messageType;
+        }
+
+        /// <summary>
+        /// Message content.
+        /// </summary>
+        public ArraySegment<byte> Content { get; private set; }
+
+        /// <summary>
+        /// Message type (UTF8 text versus binary).
+        /// </summary>
+        public WebSocketMessageType MessageType { get; private set; }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketMessageExtensions.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketMessageExtensions.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketMessageExtensions.cs	(revision 28897)
@@ -0,0 +1,70 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="WebSocketMessageExtensions.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.IO;
+    using System.Net.WebSockets;
+
+    /// <summary>
+    /// Static class that defines extensions used to serialize objects into web socket messages.
+    /// </summary>
+    public static class WebSocketMessageExtensions
+    {
+        /// <summary>
+        /// Serializes object as a UTF8-encoded JSON string and creates a web socket message to
+        /// be sent over the wire.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of object to serialize and send as a message.
+        /// </typeparam>
+        /// <param name="obj">
+        /// Object to serialize and send as a message.
+        /// </param>
+        /// <returns>
+        /// Web socket message ready to be sent.
+        /// </returns>
+        public static WebSocketMessage ToTextMessage<T>(this T obj)
+        {
+            using (var stream = new MemoryStream())
+            {
+                obj.ToJson(stream);
+                return new WebSocketMessage(new ArraySegment<byte>(stream.GetBuffer(), 0, (int)stream.Length), WebSocketMessageType.Text);
+            }
+        }
+
+        /// <summary>
+        /// Takes an array of bytes representing UTF8-encoded text and creates a web socket message
+        /// to be sent over the wire.
+        /// </summary>
+        /// <param name="textData">
+        /// Array of bytes representing UTF8-encoded text.
+        /// </param>
+        /// <returns>
+        /// Web socket message ready to be sent.
+        /// </returns>
+        public static WebSocketMessage ToTextMessage(this byte[] textData)
+        {
+            return new WebSocketMessage(new ArraySegment<byte>(textData), WebSocketMessageType.Text);
+        }
+
+        /// <summary>
+        /// Takes an array of bytes representing binary data and creates a web socket message
+        /// to be sent over the wire.
+        /// </summary>
+        /// <param name="data">
+        /// Array of bytes representing binary data.
+        /// </param>
+        /// <returns>
+        /// Web socket message ready to be sent.
+        /// </returns>
+        public static WebSocketMessage ToBinaryMessage(this byte[] data)
+        {
+            return new WebSocketMessage(new ArraySegment<byte>(data), WebSocketMessageType.Binary);
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketRpcChannel.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketRpcChannel.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/WebSocketRpcChannel.cs	(revision 28897)
@@ -0,0 +1,364 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="WebSocketRpcChannel.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.Webserver
+{
+    using System;
+    using System.Diagnostics;
+    using System.IO;
+    using System.Net;
+    using System.Net.WebSockets;
+    using System.Runtime.Serialization;
+    using System.Threading.Tasks;
+    using System.Windows.Threading;
+
+    using Microsoft.Samples.Kinect.Webserver.Sensor.Serialization;
+
+    /// <summary>
+    /// Web socket communication channel used for server to call remote procedures exposed
+    /// by web client and synchronously wait for the call to return with results.
+    /// </summary>
+    /// <remarks>
+    /// <para>
+    /// All communications performed by this channel are sent/received as UTF8-encoded web
+    /// socket text messages.
+    /// </para>
+    /// <para>
+    /// We only support one single RPC call to be sent at a time, by any client.
+    /// Calls initiated before a previous call completes will simply fail.
+    /// </para>
+    /// </remarks>
+    public sealed class WebSocketRpcChannel : WebSocketChannelBase
+    {
+        /// <summary>
+        /// Default number of bytes in buffer used to receive RPC responses from client.
+        /// </summary>
+        private const int DefaultReceiveBufferSize = 2048;
+
+        /// <summary>
+        /// Function name used to verify that client connection is still fully operational.
+        /// </summary>
+        private const string PingFunctionName = "ping";
+
+        /// <summary>
+        /// Buffer used to receive RPC responses from client.
+        /// </summary>
+        private byte[] receiveBuffer;
+
+        /// <summary>
+        /// Sequence Id of last function call performed.
+        /// </summary>
+        /// <remarks>
+        /// This is incremented with each function call, to make sequence ids unique.
+        /// </remarks>
+        private int sequenceId;
+
+        /// <summary>
+        /// True if we're currently waiting for an RPC call to come back with a response.
+        /// </summary>
+        private bool isInCall;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="WebSocketRpcChannel"/> class.
+        /// </summary>
+        /// <param name="context">
+        /// Web socket context.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket becomes closed.
+        /// </param>
+        /// <param name="receiveBufferSize">
+        /// Number of bytes in buffer used to receive RPC responses from client.
+        /// </param>
+        private WebSocketRpcChannel(WebSocketContext context, Action<WebSocketChannelBase> closedAction, int receiveBufferSize)
+            : base(context, closedAction)
+        {
+            this.receiveBuffer = new byte[receiveBufferSize];
+        }
+
+        /// <summary>
+        /// Attempt to open a new RPC channel from the specified HTTP listener context.
+        /// </summary>
+        /// <param name="listenerContext">
+        /// HTTP listener context.
+        /// </param>
+        /// <param name="openedAction">
+        /// Action to perform when web socket is opened.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket is closed.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <param name="receiveBufferSize">
+        /// Number of bytes in buffer used to receive RPC responses from client.
+        /// </param>
+        /// <remarks>
+        /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
+        /// web socket channel could not be established, an appropriate status code will be
+        /// returned via <paramref name="listenerContext"/>'s Response property.
+        /// </remarks>
+        public static async void TryOpenAsync(
+            HttpListenerContext listenerContext,
+            Action<WebSocketRpcChannel> openedAction,
+            Action<WebSocketRpcChannel> closedAction,
+            int receiveBufferSize)
+        {
+            var socketContext = await HandleWebSocketRequestAsync(listenerContext);
+
+            if (socketContext != null)
+            {
+                var channel = new WebSocketRpcChannel(
+                    socketContext, closedChannel => closedAction(closedChannel as WebSocketRpcChannel), receiveBufferSize);
+                openedAction(channel);
+            }
+        }
+
+        /// <summary>
+        /// Attempt to open a new RPC channel from the specified HTTP listener context.
+        /// </summary>
+        /// <param name="listenerContext">
+        /// HTTP listener context.
+        /// </param>
+        /// <param name="openedAction">
+        /// Action to perform when web socket is opened.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <param name="closedAction">
+        /// Action to perform when web socket is closed.
+        /// Will never be called if web channel can't be opened.
+        /// </param>
+        /// <remarks>
+        /// <para>
+        /// If <paramref name="listenerContext"/> does not represent a web socket request, or if
+        /// web socket channel could not be established, an appropriate status code will be
+        /// returned via <paramref name="listenerContext"/>'s Response property.
+        /// </para>
+        /// <para>
+        /// Will use default receive buffer size.
+        /// </para>
+        /// </remarks>
+        public static void TryOpenAsync(
+            HttpListenerContext listenerContext,
+            Action<WebSocketRpcChannel> openedAction,
+            Action<WebSocketRpcChannel> closedAction)
+        {
+            TryOpenAsync(listenerContext, openedAction, closedAction, DefaultReceiveBufferSize);
+        }
+
+        /// <summary>
+        /// Determine if this web socket channel is open for sending/receiving messages
+        /// or if it has been closed
+        /// </summary>
+        /// <returns>
+        /// True if this web socket channel is still open, false otherwise.
+        /// </returns>
+        /// <remarks>
+        /// This call is expected to perform more comprehensive connection state checks
+        /// than IsOpen property, which might include sending remote messages, if the
+        /// specific <see cref="WebSocketChannelBase"/> subclass warrants it, so callers
+        /// should be careful not to call this method too often.
+        /// </remarks>
+        public override bool CheckConnectionStatus()
+        {
+            if (!base.CheckConnectionStatus())
+            {
+                return false;
+            }
+
+            if (this.isInCall)
+            {
+                return true;
+            }
+
+            var result = this.CallFunction<bool>(PingFunctionName);
+            var isValidConnection = result.Success && result.Result;
+
+            // If client did not respond to our ping, but socket is still open, close it
+            // because we're dealing with a client that does not respect our expected
+            // communication contract.
+            if (this.IsOpen && !isValidConnection)
+            {
+                this.Dispose();
+            }
+
+            return isValidConnection;
+        }
+
+        /// <summary>
+        /// Perform synchronous RPC function call.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of function call result.
+        /// </typeparam>
+        /// <param name="functionName">
+        /// Name of remote function to invoke.
+        /// </param>
+        /// <param name="args">
+        /// Function arguments.
+        /// </param>
+        /// <returns>
+        /// Result of RPC call.
+        /// </returns>
+        public RpcResult<T> CallFunction<T>(string functionName, params object[] args)
+        {
+            if (this.isInCall)
+            {
+                return new RpcResult<T>(false);
+            }
+
+            this.isInCall = true;
+
+            try
+            {
+                var frame = new DispatcherFrame { Continue = true };
+                RpcResult<T> result = null;
+                try
+                {
+                    // Push dispatcher frame with a single posted message to process before
+                    // breaking out of frame
+                    Dispatcher.CurrentDispatcher.BeginInvoke(
+                        (Action)(async () =>
+                        {
+                            try
+                            {
+                                result = await this.SendReceiveAsync<T>(functionName, args);
+                            }
+                            catch (Exception e)
+                            {
+                                Trace.TraceError("Error while sending/receiving data during remote function call:\n{0}", e);
+                                result = new RpcResult<T>(false);
+                            }
+
+                            frame.Continue = false;
+                        }));
+                    Dispatcher.PushFrame(frame);
+
+                    return result;
+                }
+                catch (AggregateException e)
+                {
+                    Trace.TraceError("Error while sending/receiving data during remote function call:\n{0}", e);
+                    return new RpcResult<T>(false);
+                }
+            }
+            finally
+            {
+                this.isInCall = false;
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously send RPC function call request and process response, ensuring that
+        /// response matches request.
+        /// </summary>
+        /// <typeparam name="T">
+        /// Type of function call result.
+        /// </typeparam>
+        /// <param name="functionName">
+        /// Name of remote function to invoke.
+        /// </param>
+        /// <param name="args">
+        /// Function arguments.
+        /// </param>
+        /// <returns>
+        /// Result of RPC call, as an await-able task.
+        /// </returns>
+        private async Task<RpcResult<T>> SendReceiveAsync<T>(string functionName, object[] args)
+        {
+            var call = new FunctionCallRequest(functionName, args, ++this.sequenceId);
+
+            using (var callStream = new MemoryStream())
+            {
+                call.ToJson(callStream);
+                var sendResult =
+                    await
+                    this.SendAsync(new ArraySegment<byte>(callStream.GetBuffer(), 0, (int)callStream.Length), WebSocketMessageType.Text);
+                if (!sendResult)
+                {
+                    return new RpcResult<T>(false);
+                }
+            }
+
+            var receiveResult = await this.ReceiveCompleteMessageAsync();
+            if (receiveResult == null)
+            {
+                return new RpcResult<T>(false);
+            }
+
+            using (var responseStream = new MemoryStream(this.receiveBuffer, 0, receiveResult.Count))
+            {
+                FunctionCallResponse<T> callResponse;
+
+                try
+                {
+                    callResponse = responseStream.FromJson<FunctionCallResponse<T>>();
+                }
+                catch (SerializationException)
+                {
+                    return new RpcResult<T>(false);
+                }
+
+                if (callResponse.id != call.id)
+                {
+                    // call and response sequence ids don't match, so call did not succeed
+                    return new RpcResult<T>(false);
+                }
+
+                return new RpcResult<T>(true, callResponse.result);
+            }
+        }
+
+        /// <summary>
+        /// Asynchronously wait for RPC function call response, until a complete message has
+        /// been received.
+        /// </summary>
+        /// <returns>
+        /// WebSocketReceiveResult if complete, non-empty, text message has been received.
+        /// Null if any failure occurred while receiving message.
+        /// </returns>
+        private async Task<WebSocketReceiveResult> ReceiveCompleteMessageAsync()
+        {
+            int receiveCount = 0;
+            WebSocketReceiveResult receiveResult;
+
+            do
+            {
+                receiveResult =
+                    await
+                    this.ReceiveAsync(new ArraySegment<byte>(this.receiveBuffer, receiveCount, this.receiveBuffer.Length - receiveCount));
+
+                if ((receiveResult == null) || (receiveResult.MessageType != WebSocketMessageType.Text))
+                {
+                    return null;
+                }
+
+                receiveCount += receiveResult.Count;
+
+                if (receiveResult.EndOfMessage)
+                {
+                    break;
+                }
+
+                // This can only happen if we've filled the buffer and message is still not completely
+                // received, so we double the buffer size.
+                Debug.Assert(receiveCount == this.receiveBuffer.Length, "ReceiveAsync method should guarantee that incomplete messages are only returned when buffer is completely full");
+
+                var newBuffer = new byte[receiveCount * 2];
+                Array.Copy(this.receiveBuffer, newBuffer, receiveCount);
+                this.receiveBuffer = newBuffer;
+            }
+            while (!receiveResult.EndOfMessage);
+
+            if (receiveCount == 0)
+            {
+                return null;
+            }
+
+            return new WebSocketReceiveResult(receiveCount, WebSocketMessageType.Text, true);
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/obj/Debug/Microsoft.Samples.Kinect.Webserver.csproj.FileListAbsolute.txt
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/obj/Debug/Microsoft.Samples.Kinect.Webserver.csproj.FileListAbsolute.txt	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Microsoft.Samples.Kinect.Webserver/obj/Debug/Microsoft.Samples.Kinect.Webserver.csproj.FileListAbsolute.txt	(revision 28897)
@@ -0,0 +1,11 @@
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\KinectInteraction180_32.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\KinectInteraction180_64.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\KinectBackgroundRemoval180_32.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\KinectBackgroundRemoval180_64.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\Microsoft.Samples.Kinect.Webserver.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\WebserverBasics-WPF\.Debug\Microsoft.Samples.Kinect.Webserver.pdb
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\Microsoft.Samples.Kinect.Webserver\obj\Debug\Microsoft.Samples.Kinect.Webserver.csprojResolveAssemblyReference.cache
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\Microsoft.Samples.Kinect.Webserver\obj\Debug\Microsoft.Samples.Kinect.Webserver.Properties.Resources.resources
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\Microsoft.Samples.Kinect.Webserver\obj\Debug\Microsoft.Samples.Kinect.Webserver.csproj.GenerateResource.Cache
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\Microsoft.Samples.Kinect.Webserver\obj\Debug\Microsoft.Samples.Kinect.Webserver.dll
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\Microsoft.Samples.Kinect.Webserver\obj\Debug\Microsoft.Samples.Kinect.Webserver.pdb
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.csproj
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.csproj	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.csproj	(revision 28897)
@@ -0,0 +1,191 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{8CA0A735-73B4-4EB6-A1B6-0B4C228D77D8}</ProjectGuid>
+    <OutputType>WinExe</OutputType>
+    <AppDesignerFolder>Properties</AppDesignerFolder>
+    <RootNamespace>Microsoft.Samples.Kinect.WebserverBasics</RootNamespace>
+    <AssemblyName>Playing-in-the-Street-WPF</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <FileAlignment>512</FileAlignment>
+    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
+    <WarningLevel>4</WarningLevel>
+    <PublishUrl>publish\</PublishUrl>
+    <Install>true</Install>
+    <InstallFrom>Disk</InstallFrom>
+    <UpdateEnabled>false</UpdateEnabled>
+    <UpdateMode>Foreground</UpdateMode>
+    <UpdateInterval>7</UpdateInterval>
+    <UpdateIntervalUnits>Days</UpdateIntervalUnits>
+    <UpdatePeriodically>false</UpdatePeriodically>
+    <UpdateRequired>false</UpdateRequired>
+    <MapFileExtensions>true</MapFileExtensions>
+    <ApplicationRevision>0</ApplicationRevision>
+    <ApplicationVersion>1.0.0.%2a</ApplicationVersion>
+    <IsWebBootstrapper>false</IsWebBootstrapper>
+    <UseApplicationTrust>false</UseApplicationTrust>
+    <BootstrapperEnabled>true</BootstrapperEnabled>
+  </PropertyGroup>
+  <PropertyGroup>
+    <ApplicationIcon>Images\Kinect.ico</ApplicationIcon>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DefineConstants>DEBUG;TRACE</DefineConstants>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|AnyCPU'">
+    <PlatformTarget>AnyCPU</PlatformTarget>
+    <DefineConstants>TRACE</DefineConstants>
+  </PropertyGroup>
+  <PropertyGroup>
+    <OutputPath>$(BinDir)$(PlatformTarget).$(Configuration)\</OutputPath>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="Microsoft.Kinect">
+      <SpecificVersion>False</SpecificVersion>
+      <HintPath>$(KINECTSDK10_DIR)\Assemblies\Microsoft.Kinect.dll</HintPath>
+    </Reference>
+    <Reference Include="System" />
+    <Reference Include="System.Data" />
+    <Reference Include="System.Xml" />
+    <Reference Include="Microsoft.CSharp" />
+    <Reference Include="System.Core" />
+    <Reference Include="System.Xml.Linq" />
+    <Reference Include="System.Data.DataSetExtensions" />
+    <Reference Include="System.Xaml">
+      <RequiredTargetFramework>4.0</RequiredTargetFramework>
+    </Reference>
+    <Reference Include="WindowsBase" />
+    <Reference Include="PresentationCore" />
+    <Reference Include="PresentationFramework" />
+  </ItemGroup>
+  <ItemGroup>
+    <ApplicationDefinition Include="App.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </ApplicationDefinition>
+    <Page Include="MainWindow.xaml">
+      <Generator>MSBuild:Compile</Generator>
+      <SubType>Designer</SubType>
+    </Page>
+    <Compile Include="App.xaml.cs">
+      <DependentUpon>App.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="RecentEventBufferTraceListener.cs" />
+    <Compile Include="InverseBooleanConverter.cs" />
+    <Compile Include="MainWindow.xaml.cs">
+      <DependentUpon>MainWindow.xaml</DependentUpon>
+      <SubType>Code</SubType>
+    </Compile>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="Properties\AssemblyInfo.cs">
+      <SubType>Code</SubType>
+    </Compile>
+    <Compile Include="Properties\Resources.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DesignTime>True</DesignTime>
+      <DependentUpon>Resources.resx</DependentUpon>
+    </Compile>
+    <Compile Include="Properties\Settings.Designer.cs">
+      <AutoGen>True</AutoGen>
+      <DependentUpon>Settings.settings</DependentUpon>
+      <DesignTimeSharedInput>True</DesignTimeSharedInput>
+    </Compile>
+    <EmbeddedResource Include="Properties\Resources.resx">
+      <Generator>ResXFileCodeGenerator</Generator>
+      <LastGenOutput>Resources.Designer.cs</LastGenOutput>
+    </EmbeddedResource>
+    <None Include="Properties\Settings.settings">
+      <Generator>SettingsSingleFileGenerator</Generator>
+      <LastGenOutput>Settings.Designer.cs</LastGenOutput>
+    </None>
+    <AppDesigner Include="Properties\" />
+    <Content Include="Content\Web\3d.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\css\coolstyle.css">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\doGSVP.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\doKinect.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\doStreetView.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\GSVPano.js-master\build\GSVPano.min.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\GSVPano.js-master\src\GSVPano.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\Images\Logo.png">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\Kinect-1.8.0.css">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\Kinect-1.8.0.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\KinectWorker-1.8.0.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\mrdoob-three.js-4862f5f\build\three.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\mrdoob-three.js-4862f5f\build\three.min.js">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Content Include="Content\Web\SamplePage.html">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+    <Resource Include="Images\Kinect.ico" />
+    <Content Include="WebserverBasic-WPF.exe.config">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </Content>
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Microsoft.Kinect.Toolkit\Microsoft.Kinect.Toolkit.csproj">
+      <Project>{C6F9C31B-6130-4443-A5CC-EF0664552ECD}</Project>
+      <Name>Microsoft.Kinect.Toolkit</Name>
+    </ProjectReference>
+    <ProjectReference Include="..\Microsoft.Samples.Kinect.Webserver\Microsoft.Samples.Kinect.Webserver.csproj">
+      <Project>{9216e84f-1723-484e-8de6-250b875f24b6}</Project>
+      <Name>Microsoft.Samples.Kinect.Webserver</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <BootstrapperPackage Include=".NETFramework,Version=v4.5">
+      <Visible>False</Visible>
+      <ProductName>Microsoft .NET Framework 4.5 %28x86 and x64%29</ProductName>
+      <Install>true</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Client.3.5">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1 Client Profile</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+    <BootstrapperPackage Include="Microsoft.Net.Framework.3.5.SP1">
+      <Visible>False</Visible>
+      <ProductName>.NET Framework 3.5 SP1</ProductName>
+      <Install>false</Install>
+    </BootstrapperPackage>
+  </ItemGroup>
+  <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
+  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
+       Other similar extension points exist, see Microsoft.Common.targets.
+  <Target Name="BeforeBuild">
+  </Target>
+  <Target Name="AfterBuild">
+  </Target>
+  -->
+</Project>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.csproj.user
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.csproj.user	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.csproj.user	(revision 28897)
@@ -0,0 +1,14 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <PublishUrlHistory>publish\</PublishUrlHistory>
+    <InstallUrlHistory />
+    <SupportUrlHistory />
+    <UpdateUrlHistory />
+    <BootstrapperUrlHistory />
+    <ErrorReportUrlHistory />
+    <FallbackCulture>en-US</FallbackCulture>
+    <VerifyUploadedFiles>false</VerifyUploadedFiles>
+    <ProjectView>ShowAllFiles</ProjectView>
+  </PropertyGroup>
+</Project>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.sln
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.sln	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Playing-in-the-Street-WPF.sln	(revision 28897)
@@ -0,0 +1,34 @@
+﻿
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio 2013
+VisualStudioVersion = 12.0.21005.1
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playing-in-the-Street-WPF", "Playing-in-the-Street-WPF.csproj", "{8CA0A735-73B4-4EB6-A1B6-0B4C228D77D8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Samples.Kinect.Webserver", "..\Microsoft.Samples.Kinect.Webserver\Microsoft.Samples.Kinect.Webserver.csproj", "{9216E84F-1723-484E-8DE6-250B875F24B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Kinect.Toolkit", "..\Microsoft.Kinect.Toolkit\Microsoft.Kinect.Toolkit.csproj", "{C6F9C31B-6130-4443-A5CC-EF0664552ECD}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{8CA0A735-73B4-4EB6-A1B6-0B4C228D77D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{8CA0A735-73B4-4EB6-A1B6-0B4C228D77D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{8CA0A735-73B4-4EB6-A1B6-0B4C228D77D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{8CA0A735-73B4-4EB6-A1B6-0B4C228D77D8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9216E84F-1723-484E-8DE6-250B875F24B6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C6F9C31B-6130-4443-A5CC-EF0664552ECD}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/AssemblyInfo.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/AssemblyInfo.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/AssemblyInfo.cs	(revision 28897)
@@ -0,0 +1,53 @@
+﻿//------------------------------------------------------------------------------
+// <copyright file="AssemblyInfo.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Reflection;
+using System.Resources;
+using System.Runtime.InteropServices;
+using System.Windows;
+
+// General Information about an assembly is controlled through the following 
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Kinect for Playing in the Street - WPF")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible 
+// to COM components.  If you need to access a type in this assembly from 
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// In order to begin building localizable applications, set 
+// <UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
+// inside a <PropertyGroup>.  For example, if you are using US english
+// in your source files, set the <UICulture> to en-US.  Then uncomment
+// the NeutralResourceLanguage attribute below.  Update the "en-US" in
+// the line below to match the UICulture setting in the project file.
+
+// [assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
+[assembly: ThemeInfo(
+    ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located
+    // (used if a resource is not found in the page, 
+    // or application resource dictionaries)
+    ResourceDictionaryLocation.SourceAssembly)] // where the generic resource dictionary is located
+    // (used if a resource is not found in the page, 
+    // app, or any theme specific resource dictionaries)
+
+// Version information for an assembly consists of the following four values:
+//
+//      Major Version
+//      Minor Version 
+//      Build Number
+//      Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers 
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: NeutralResourcesLanguage("en-US")]
+[assembly: CLSCompliant(true)]
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Resources.Designer.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Resources.Designer.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Resources.Designer.cs	(revision 28897)
@@ -0,0 +1,81 @@
+﻿//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.34011
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.WebserverBasics.Properties {
+    using System;
+    
+    
+    /// <summary>
+    ///   A strongly-typed resource class, for looking up localized strings, etc.
+    /// </summary>
+    // This class was auto-generated by the StronglyTypedResourceBuilder
+    // class via a tool like ResGen or Visual Studio.
+    // To add or remove a member, edit your .ResX file then rerun ResGen
+    // with the /str option, or rebuild your VS project.
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    internal class Resources {
+        
+        private static global::System.Resources.ResourceManager resourceMan;
+        
+        private static global::System.Globalization.CultureInfo resourceCulture;
+        
+        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+        internal Resources() {
+        }
+        
+        /// <summary>
+        ///   Returns the cached ResourceManager instance used by this class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Resources.ResourceManager ResourceManager {
+            get {
+                if (object.ReferenceEquals(resourceMan, null)) {
+                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Samples.Kinect.WebserverBasics.Properties.Resources", typeof(Resources).Assembly);
+                    resourceMan = temp;
+                }
+                return resourceMan;
+            }
+        }
+        
+        /// <summary>
+        ///   Overrides the current thread's CurrentUICulture property for all
+        ///   resource lookups using this strongly typed resource class.
+        /// </summary>
+        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+        internal static global::System.Globalization.CultureInfo Culture {
+            get {
+                return resourceCulture;
+            }
+            set {
+                resourceCulture = value;
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to System version not supported.
+        /// </summary>
+        internal static string SystemVersionNotSupportedCaption {
+            get {
+                return ResourceManager.GetString("SystemVersionNotSupportedCaption", resourceCulture);
+            }
+        }
+        
+        /// <summary>
+        ///   Looks up a localized string similar to This sample requires Windows 8 or later version. System.Net.WebSocket class is not implemented in your current operating system version. See sample documentation for more details..
+        /// </summary>
+        internal static string SystemVersionNotSupportedMessage {
+            get {
+                return ResourceManager.GetString("SystemVersionNotSupportedMessage", resourceCulture);
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Resources.resx
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Resources.resx	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Resources.resx	(revision 28897)
@@ -0,0 +1,126 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <!-- 
+    Microsoft ResX Schema 
+    
+    Version 2.0
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
+    associated with the data types.
+    
+    Example:
+    
+    ... ado.net/XML headers & schema ...
+    <resheader name="resmimetype">text/microsoft-resx</resheader>
+    <resheader name="version">2.0</resheader>
+    <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+    <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+    <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+    <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+    <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+        <value>[base64 mime encoded serialized .NET Framework object]</value>
+    </data>
+    <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+        <comment>This is a comment</comment>
+    </data>
+                
+    There are any number of "resheader" rows that contain simple 
+    name/value pairs.
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
+    mimetype set.
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
+    extensible. For a given mimetype the value must be set accordingly:
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
+    read any of the formats listed below.
+    
+    mimetype: application/x-microsoft.net.object.binary.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+            : and then encoded with base64 encoding.
+    
+    mimetype: application/x-microsoft.net.object.soap.base64
+    value   : The object must be serialized with 
+            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+            : and then encoded with base64 encoding.
+
+    mimetype: application/x-microsoft.net.object.bytearray.base64
+    value   : The object must be serialized into a byte array 
+            : using a System.ComponentModel.TypeConverter
+            : and then encoded with base64 encoding.
+    -->
+  <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+    <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+    <xsd:element name="root" msdata:IsDataSet="true">
+      <xsd:complexType>
+        <xsd:choice maxOccurs="unbounded">
+          <xsd:element name="metadata">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" />
+              </xsd:sequence>
+              <xsd:attribute name="name" use="required" type="xsd:string" />
+              <xsd:attribute name="type" type="xsd:string" />
+              <xsd:attribute name="mimetype" type="xsd:string" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="assembly">
+            <xsd:complexType>
+              <xsd:attribute name="alias" type="xsd:string" />
+              <xsd:attribute name="name" type="xsd:string" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="data">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+                <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+              <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+              <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+              <xsd:attribute ref="xml:space" />
+            </xsd:complexType>
+          </xsd:element>
+          <xsd:element name="resheader">
+            <xsd:complexType>
+              <xsd:sequence>
+                <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+              </xsd:sequence>
+              <xsd:attribute name="name" type="xsd:string" use="required" />
+            </xsd:complexType>
+          </xsd:element>
+        </xsd:choice>
+      </xsd:complexType>
+    </xsd:element>
+  </xsd:schema>
+  <resheader name="resmimetype">
+    <value>text/microsoft-resx</value>
+  </resheader>
+  <resheader name="version">
+    <value>2.0</value>
+  </resheader>
+  <resheader name="reader">
+    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <resheader name="writer">
+    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+  </resheader>
+  <data name="SystemVersionNotSupportedCaption" xml:space="preserve">
+    <value>System version not supported</value>
+  </data>
+  <data name="SystemVersionNotSupportedMessage" xml:space="preserve">
+    <value>This sample requires Windows 8 or later version. System.Net.WebSocket class is not implemented in your current operating system version. See sample documentation for more details.</value>
+  </data>
+</root>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Settings.Designer.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Settings.Designer.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Settings.Designer.cs	(revision 28897)
@@ -0,0 +1,53 @@
+﻿//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.34011
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.WebserverBasics.Properties {
+    
+    
+    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")]
+    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
+        
+        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
+        
+        public static Settings Default {
+            get {
+                return defaultInstance;
+            }
+        }
+        
+        [global::System.Configuration.ApplicationScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("http://localhost:8181")]
+        public string OriginUri {
+            get {
+                return ((string)(this["OriginUri"]));
+            }
+        }
+        
+        [global::System.Configuration.ApplicationScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("Content\\Web")]
+        public string FileRootDirectory {
+            get {
+                return ((string)(this["FileRootDirectory"]));
+            }
+        }
+        
+        [global::System.Configuration.ApplicationScopedSettingAttribute()]
+        [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [global::System.Configuration.DefaultSettingValueAttribute("")]
+        public string AccessControlAllowedOrigins {
+            get {
+                return ((string)(this["AccessControlAllowedOrigins"]));
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Settings.settings
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Settings.settings	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/Properties/Settings.settings	(revision 28897)
@@ -0,0 +1,15 @@
+﻿<?xml version='1.0' encoding='utf-8'?>
+<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)" GeneratedClassNamespace="Microsoft.Samples.Kinect.WebserverBasics.Properties" GeneratedClassName="Settings">
+  <Profiles />
+  <Settings>
+    <Setting Name="OriginUri" Type="System.String" Scope="Application">
+      <Value Profile="(Default)">http://localhost:8181</Value>
+    </Setting>
+    <Setting Name="FileRootDirectory" Type="System.String" Scope="Application">
+      <Value Profile="(Default)">Content\Web</Value>
+    </Setting>
+    <Setting Name="AccessControlAllowedOrigins" Type="System.String" Scope="Application">
+      <Value Profile="(Default)" />
+    </Setting>
+  </Settings>
+</SettingsFile>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/RecentEventBufferTraceListener.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/RecentEventBufferTraceListener.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/RecentEventBufferTraceListener.cs	(revision 28897)
@@ -0,0 +1,112 @@
+﻿// -----------------------------------------------------------------------
+// <copyright file="RecentEventBufferTraceListener.cs" company="Microsoft">
+//     Copyright (c) Microsoft Corporation.  All rights reserved.
+// </copyright>
+// -----------------------------------------------------------------------
+
+namespace Microsoft.Samples.Kinect.WebserverBasics
+{
+    using System;
+    using System.Collections.Generic;
+    using System.Diagnostics;
+    using System.Text;
+
+    /// <summary>
+    /// Remembers the most recent trace events observed and exposes them as
+    /// a chunk of displayable text.
+    /// </summary>
+    public class RecentEventBufferTraceListener : TraceListener
+    {
+        private const int DefaultMaximumLines = 10;
+
+        private readonly StringBuilder traceLineBuilder = new StringBuilder();
+
+        private readonly Queue<string> entries;
+
+        private readonly int maximumLines;
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RecentEventBufferTraceListener"/> class.
+        /// </summary>
+        /// <remarks>
+        /// Remembers the default number of trace lines.
+        /// </remarks>
+        public RecentEventBufferTraceListener()
+            : this(DefaultMaximumLines)
+        {
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="RecentEventBufferTraceListener"/> class.
+        /// </summary>
+        /// <param name="maximumLines">
+        /// Maximum number of trace lines to remember.
+        /// </param>
+        public RecentEventBufferTraceListener(int maximumLines)
+        {
+            this.maximumLines = maximumLines;
+            this.entries = new Queue<string>(maximumLines);
+        }
+
+        public event EventHandler<EventArgs> RecentEventBufferChanged;
+
+        /// <summary>
+        /// Displayable string representing buffer of recent trace events seen.
+        /// </summary>
+        public string RecentEventBuffer { get; private set; }
+
+        /// <summary>
+        /// Remembers the specified message as the latest message seen.
+        /// </summary>
+        /// <param name="message">
+        /// Message to remember.
+        /// </param>
+        public override void Write(string message)
+        {
+            this.traceLineBuilder.Append(message);
+        }
+
+        /// <summary>
+        /// Remembers the specified message, followed by a line terminator, as the latest
+        /// message seen.
+        /// </summary>
+        /// <param name="message">
+        /// Message to remember.
+        /// </param>
+        public override void WriteLine(string message)
+        {
+            this.traceLineBuilder.Append(message);
+            this.traceLineBuilder.Append("\n");
+            this.QueueMessage(this.traceLineBuilder.ToString());
+            this.traceLineBuilder.Clear();
+        }
+
+        /// <summary>
+        /// Remembers the specified message as the latest message seen.
+        /// </summary>
+        /// <param name="message">
+        /// Message to remember.
+        /// </param>
+        private void QueueMessage(string message)
+        {
+            this.entries.Enqueue(message);
+            if (this.entries.Count > this.maximumLines)
+            {
+                this.entries.Dequeue();
+            }
+
+            var builder = new StringBuilder();
+            foreach (var entry in this.entries)
+            {
+                builder.Append(entry);
+            }
+
+            this.RecentEventBuffer = builder.ToString();
+
+            if (this.RecentEventBufferChanged != null)
+            {
+                this.RecentEventBufferChanged(this, EventArgs.Empty);
+            }
+        }
+    }
+}
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/WebserverBasic-WPF.exe.config
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/WebserverBasic-WPF.exe.config	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/WebserverBasic-WPF.exe.config	(revision 28897)
@@ -0,0 +1,33 @@
+﻿<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+  <configSections>
+    <sectionGroup name="applicationSettings" type="System.Configuration.ApplicationSettingsGroup, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" >
+      <section name="Microsoft.Samples.Kinect.WebserverBasics.Properties.Settings" type="System.Configuration.ClientSettingsSection, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
+    </sectionGroup>
+  </configSections>
+  <system.diagnostics>
+    <trace autoflush="true" indentsize="2">
+      <listeners>
+        <add name="infoLoggingListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="WebServer.log" traceOutputOptions="DateTime">
+          <filter type="System.Diagnostics.EventTypeFilter" initializeData="Information"/>
+        </add>
+        <add name="errorBufferListener" type="Microsoft.Samples.Kinect.WebserverBasics.RecentEventBufferTraceListener, WebserverBasics-WPF" initializeData="10" traceOutputOptions="DateTime">
+          <filter type="System.Diagnostics.EventTypeFilter" initializeData="Error"/>
+        </add>
+      </listeners>
+    </trace>
+  </system.diagnostics>
+  <applicationSettings>
+    <Microsoft.Samples.Kinect.WebserverBasics.Properties.Settings>
+      <setting name="OriginUri" serializeAs="String">
+        <value>http://localhost:8181</value>
+      </setting>
+      <setting name="FileRootDirectory" serializeAs="String">
+        <value>Content\Web</value>
+      </setting>
+      <setting name="AccessControlAllowedOrigins" serializeAs="String">
+        <value/>
+      </setting>
+    </Microsoft.Samples.Kinect.WebserverBasics.Properties.Settings>
+  </applicationSettings>
+</configuration>
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/App.g.i.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/App.g.i.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/App.g.i.cs	(revision 28897)
@@ -0,0 +1,69 @@
+﻿#pragma checksum "..\..\App.xaml" "{406ea660-64cf-4c82-b6f0-42d48172a799}" "ADD922C3CC20B024C1BFD0E399FE6087"
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.34011
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Automation;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Effects;
+using System.Windows.Media.Imaging;
+using System.Windows.Media.Media3D;
+using System.Windows.Media.TextFormatting;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using System.Windows.Shell;
+
+
+namespace Microsoft.Samples.Kinect.WebserverBasics {
+    
+    
+    /// <summary>
+    /// App
+    /// </summary>
+    public partial class App : System.Windows.Application {
+        
+        /// <summary>
+        /// InitializeComponent
+        /// </summary>
+        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
+        public void InitializeComponent() {
+            
+            #line 4 "..\..\App.xaml"
+            this.StartupUri = new System.Uri("MainWindow.xaml", System.UriKind.Relative);
+            
+            #line default
+            #line hidden
+        }
+        
+        /// <summary>
+        /// Application Entry Point.
+        /// </summary>
+        [System.STAThreadAttribute()]
+        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
+        public static void Main() {
+            Microsoft.Samples.Kinect.WebserverBasics.App app = new Microsoft.Samples.Kinect.WebserverBasics.App();
+            app.InitializeComponent();
+            app.Run();
+        }
+    }
+}
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/GeneratedInternalTypeHelper.g.i.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/GeneratedInternalTypeHelper.g.i.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/GeneratedInternalTypeHelper.g.i.cs	(revision 28897)
@@ -0,0 +1,62 @@
+﻿//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.34011
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace XamlGeneratedNamespace {
+    
+    
+    /// <summary>
+    /// GeneratedInternalTypeHelper
+    /// </summary>
+    [System.Diagnostics.DebuggerNonUserCodeAttribute()]
+    [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
+    [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+    public sealed class GeneratedInternalTypeHelper : System.Windows.Markup.InternalTypeHelper {
+        
+        /// <summary>
+        /// CreateInstance
+        /// </summary>
+        protected override object CreateInstance(System.Type type, System.Globalization.CultureInfo culture) {
+            return System.Activator.CreateInstance(type, ((System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic) 
+                            | (System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.CreateInstance)), null, null, culture);
+        }
+        
+        /// <summary>
+        /// GetPropertyValue
+        /// </summary>
+        protected override object GetPropertyValue(System.Reflection.PropertyInfo propertyInfo, object target, System.Globalization.CultureInfo culture) {
+            return propertyInfo.GetValue(target, System.Reflection.BindingFlags.Default, null, null, culture);
+        }
+        
+        /// <summary>
+        /// SetPropertyValue
+        /// </summary>
+        protected override void SetPropertyValue(System.Reflection.PropertyInfo propertyInfo, object target, object value, System.Globalization.CultureInfo culture) {
+            propertyInfo.SetValue(target, value, System.Reflection.BindingFlags.Default, null, null, culture);
+        }
+        
+        /// <summary>
+        /// CreateDelegate
+        /// </summary>
+        protected override System.Delegate CreateDelegate(System.Type delegateType, object target, string handler) {
+            return ((System.Delegate)(target.GetType().InvokeMember("_CreateDelegate", (System.Reflection.BindingFlags.InvokeMethod 
+                            | (System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)), null, target, new object[] {
+                        delegateType,
+                        handler}, null)));
+        }
+        
+        /// <summary>
+        /// AddEventHandler
+        /// </summary>
+        protected override void AddEventHandler(System.Reflection.EventInfo eventInfo, object target, System.Delegate handler) {
+            eventInfo.AddEventHandler(target, handler);
+        }
+    }
+}
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/MainWindow.g.i.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/MainWindow.g.i.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/MainWindow.g.i.cs	(revision 28897)
@@ -0,0 +1,170 @@
+﻿#pragma checksum "..\..\MainWindow.xaml" "{406ea660-64cf-4c82-b6f0-42d48172a799}" "D4CC16CCD3ADFF10ED0E76688AFD6566"
+//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.34011
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+using Microsoft.Kinect.Toolkit;
+using Microsoft.Samples.Kinect.WebserverBasics;
+using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Automation;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Ink;
+using System.Windows.Input;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Windows.Media.Animation;
+using System.Windows.Media.Effects;
+using System.Windows.Media.Imaging;
+using System.Windows.Media.Media3D;
+using System.Windows.Media.TextFormatting;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using System.Windows.Shell;
+
+
+namespace Microsoft.Samples.Kinect.WebserverBasics {
+    
+    
+    /// <summary>
+    /// MainWindow
+    /// </summary>
+    public partial class MainWindow : System.Windows.Window, System.Windows.Markup.IComponentConnector {
+        
+        
+        #line 31 "..\..\MainWindow.xaml"
+        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+        internal System.Windows.Controls.TextBlock OpenBrowserText;
+        
+        #line default
+        #line hidden
+        
+        
+        #line 32 "..\..\MainWindow.xaml"
+        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+        internal System.Windows.Documents.Hyperlink UriLink;
+        
+        #line default
+        #line hidden
+        
+        
+        #line 36 "..\..\MainWindow.xaml"
+        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+        internal System.Windows.Controls.TextBlock ServingFilesText;
+        
+        #line default
+        #line hidden
+        
+        
+        #line 38 "..\..\MainWindow.xaml"
+        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+        internal System.Windows.Documents.Run RootDirectoryTextRun;
+        
+        #line default
+        #line hidden
+        
+        
+        #line 42 "..\..\MainWindow.xaml"
+        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
+        internal System.Windows.Controls.TextBlock NotServingFilesText;
+        
+        #line default
+        #line hidden
+        
+        private bool _contentLoaded;
+        
+        /// <summary>
+        /// InitializeComponent
+        /// </summary>
+        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
+        public void InitializeComponent() {
+            if (_contentLoaded) {
+                return;
+            }
+            _contentLoaded = true;
+            System.Uri resourceLocater = new System.Uri("/Playing-in-the-Street-WPF;component/mainwindow.xaml", System.UriKind.Relative);
+            
+            #line 1 "..\..\MainWindow.xaml"
+            System.Windows.Application.LoadComponent(this, resourceLocater);
+            
+            #line default
+            #line hidden
+        }
+        
+        [System.Diagnostics.DebuggerNonUserCodeAttribute()]
+        [System.CodeDom.Compiler.GeneratedCodeAttribute("PresentationBuildTasks", "4.0.0.0")]
+        [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)]
+        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes")]
+        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")]
+        [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily")]
+        void System.Windows.Markup.IComponentConnector.Connect(int connectionId, object target) {
+            switch (connectionId)
+            {
+            case 1:
+            
+            #line 7 "..\..\MainWindow.xaml"
+            ((Microsoft.Samples.Kinect.WebserverBasics.MainWindow)(target)).Loaded += new System.Windows.RoutedEventHandler(this.WindowLoaded);
+            
+            #line default
+            #line hidden
+            
+            #line 7 "..\..\MainWindow.xaml"
+            ((Microsoft.Samples.Kinect.WebserverBasics.MainWindow)(target)).Closing += new System.ComponentModel.CancelEventHandler(this.WindowClosing);
+            
+            #line default
+            #line hidden
+            return;
+            case 2:
+            
+            #line 26 "..\..\MainWindow.xaml"
+            ((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.StartButtonOnClick);
+            
+            #line default
+            #line hidden
+            return;
+            case 3:
+            
+            #line 28 "..\..\MainWindow.xaml"
+            ((System.Windows.Controls.Button)(target)).Click += new System.Windows.RoutedEventHandler(this.StopButtonOnClick);
+            
+            #line default
+            #line hidden
+            return;
+            case 4:
+            this.OpenBrowserText = ((System.Windows.Controls.TextBlock)(target));
+            return;
+            case 5:
+            this.UriLink = ((System.Windows.Documents.Hyperlink)(target));
+            
+            #line 32 "..\..\MainWindow.xaml"
+            this.UriLink.Click += new System.Windows.RoutedEventHandler(this.UriLinkOnClick);
+            
+            #line default
+            #line hidden
+            return;
+            case 6:
+            this.ServingFilesText = ((System.Windows.Controls.TextBlock)(target));
+            return;
+            case 7:
+            this.RootDirectoryTextRun = ((System.Windows.Documents.Run)(target));
+            return;
+            case 8:
+            this.NotServingFilesText = ((System.Windows.Controls.TextBlock)(target));
+            return;
+            }
+            this._contentLoaded = true;
+        }
+    }
+}
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/Playing-in-the-Street-WPF_Content.g.i.cs
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/Playing-in-the-Street-WPF_Content.g.i.cs	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/Playing-in-the-Street-WPF_Content.g.i.cs	(revision 28897)
@@ -0,0 +1,27 @@
+﻿//------------------------------------------------------------------------------
+// <auto-generated>
+//     This code was generated by a tool.
+//     Runtime Version:4.0.30319.34011
+//
+//     Changes to this file may cause incorrect behavior and will be lost if
+//     the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/3d.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/css/coolstyle.css")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/dogsvp.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/dokinect.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/dostreetview.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/gsvpano.js-master/build/gsvpano.min.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/gsvpano.js-master/src/gsvpano.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/images/logo.png")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/kinect-1.8.0.css")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/kinect-1.8.0.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/kinectworker-1.8.0.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/mrdoob-three.js-4862f5f/build/three.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/mrdoob-three.js-4862f5f/build/three.min.js")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("content/web/samplepage.html")]
+[assembly: System.Windows.Resources.AssemblyAssociatedContentFileAttribute("webserverbasic-wpf.exe.config")]
+
+
Index: /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/Playing-in-the-Street-WPF_MarkupCompile.i.cache
===================================================================
--- /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/Playing-in-the-Street-WPF_MarkupCompile.i.cache	(revision 28897)
+++ /other-projects/playing-in-the-street/summer-2013/trunk/Playing-in-the-Street-WPF/obj/Debug/Playing-in-the-Street-WPF_MarkupCompile.i.cache	(revision 28897)
@@ -0,0 +1,20 @@
+﻿Playing-in-the-Street-WPF
+
+
+winexe
+C#
+.cs
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\Playing-in-the-Street-WPF\obj\Debug\
+Microsoft.Samples.Kinect.WebserverBasics
+none
+false
+DEBUG;TRACE
+C:\Users\David\Documents\Visual Studio 2013\Projects\Kinect-Summer-2013\Playing-in-the-Street-WPF\App.xaml
+11151548125
+151279104786
+111834450961
+151884567689
+MainWindow.xaml;
+
+False
+
